Visual Studio Web Essentials, Minified and Source Map files


Working in the SharePoint 2013 online world I have found myself deep in the world of JavaScript files. Now as a good developer I ensure that my JavaScript files are all minified before deploying to a production server. However what happens if there is a JavaScript error on the production server. You would normally see an error like below:

Line: 1, Column: 83008. Well yeah, that’s going to be easy to solve. To makes matters worse, if I look at the cannonfodder.min.js file, line 1 looks a bit like the following:

However you could hit “Turn Pretty On” button

IE11

Chrome – (Bottom left)

Which makes the above text looks like the following:

Not exactly the file I originally wrote, as functions and variables are still minified to single letters.

One solution that I used to use was have a custom action script link, that points a piece of JavaScript code, that either adds the minified or un-minified file to the page depending if there was “jsdebug” in the query string on the page.


var Cannonfodder = Cannonfodder || {};

Cannonfodder.LoadMainJs = (function () {

    var scriptElement = document.createElement('script');

    scriptElement.setAttribute('type', 'text/javascript');

    if (window.location.href.indexOf('jsdebug') != -1) {

        scriptElement.setAttribute('src', '/assets/scripts/cannonfodder.js ');

    } else {

        scriptElement.setAttribute('src', '/assets/scripts/cannonfodder.min.js ');

    }

    document.getElementsByTagName('head')[0].appendChild(scriptElement);

}());

There is nothing wrong with doing the above, the only issue is that you are inserting the javascript file into the page with a custom action. If you wanted just to add the JavaScript link to your masterpage, or HTML page etc you couldn’t use this method.

This is where minified and source map files come in handy.

 

Web Essentials for Visual Studio

Before I explain further about Minified and Source Map files, let me show you a Visual Studio plugin that will do most of the work for you. It’s called Web Essentials and can be downloaded from http://vswebessentials.com/. It is available for Visual Studio 2010, 2012 and 2013. It has many features, but the feature you are installing it for, is so that it automatically minifies and creates a map file for your JavaScript files.

How to minify

Once you have installed Web Essentials extension, and have a project with a group of JavaScript files, all you need to do is select the JavaScript files, and right click.

You will see that you have a new menu item “Web Essentials” and inside this menu item, you can Create JavaScript bundle file. By clicking this you are asked to name your bundle. Once you have named your bundle and click OK, it will create 4 files based on your bundle name.

The files:

[BundleName].js – is all the JavaScript files put together into one file, that isn’t minified. This is the easy to read version. (Although not used any further)

[BundleName].min.js – is all the JavaScript files put together into one file, but this file is minified.

[BundleName].min.js.map – is the source map file. This is a file that holds information about your original files. When you query a certai line and column number in your generated minified JavaScript you can do a lookup in the source map which returns the original location. A map file can look as simple as the following.


{

 "version" : 3,

"file": "Cannonfodder.min.js",

"mappings": "AAAAA,SAASA,kBAAmB,CAACC",

"sources": ["test.js"],

"names": ["window", "src", "maps", "are", "fun"]

}

 

[BundleName].js.bundle – This is an XML file, also the file that groups the other 3 files. This XML file contains the files that need to be minified, also it determines the order. The bundle file looks similar to the following:


<?xml version="1.0" encoding="utf-8"?>

<bundle minify="true" runOnBuild="true" output="Cannonfodder.js">

<!-- The order of the <file> elements determines the order of them when bundled. -->

<file>test.js</file>

<file>anotherfile.js</file>

<file>mycoreutilities.js</file>

</bundle>

 

What is really cool about using web essentials, is any changes you make to your original javascript files, as soon as you save it within Visual Studio, the 3 bundle files are recreated/updated. ([bundle].js, [bundle].min.js, [bundle].min.js.map)

 

What to deploy to the server

I’ve created a basic demo to explain what needs to be deployed to the server.

What I have is a module called assets. I added 3 JavaScript files to my assets module. Then, using Web Essentials created my Cannonfodder bundle from the 3 JavaScript files. Lastly I have created a Custom Action Script link which adds “~SiteCollection/Assets/Cannonfodder.min.js” to the page for simplicity. (Save worrying about creating a Master Page for this demo)

The files highlighted in yellow are the files, I’ve included in the Elements.xml file which will be uploaded to the server.

When I hit my page, the only file that I’ve told to load on the page is the minified file Cannonfodder.min.js.

 

Browsers and debugging the minified file

I have deployed my sandbox solution to the server, and using Chrome I can see my original files instead of the minified files. Here I will show you how this works.

By default Chrome doesn’t load the unminfied files, even when you enable the developer tools (F12) it doesn’t automatically load the unminified files. To get this to work you need to go into the settings of Chrome, by clicking on the cog on the top right of the developer toolbar.

You need to ensure “Enable JS source maps” is turned on. Now refresh the page. In the source panel, you will now see your original files and the minified files.

With “Enable JS Source maps” turned off, you will not get the original files in the source panel. (There are more files loaded here before I took my screenshot, compared to the last screenshot)

Ok, so I can see my original files, but how does that help me? Well now I can actually put a break point on my original code, which gets hit as my page runs.

This may seem like magic, but it is to do with the final line you will find in the min.js file.


/*

//# sourceMappingURL=Cannonfodder.min.js.map

*/

This line tells the browser to read the map file if found, it can then load the original files from the map file. The mappings in the map file, allows the browser to work out exactly where in the minified file you are in the original files. So for example, if you click “turn pretty on” for your minified file, and then attempt to put a break point in this file, the actual breakpoint it makes is in your original file. You can see this by the “Breakpoints window”.

 

Other Browsers

Firefox in the debug tools can view the original files. Apparently according to this Microsoft Article http://msdn.microsoft.com/en-us/library/dn255007(v=vs.85).aspx IE11 with Windows 8.1 update also can do this. However I cannot see the extra buttons that Microsoft States are there to perform what I did in Chrome.

What I have


What Microsoft says I should have


The last two buttons are Just My Code and Enable source maps. If anyone works out how to get these two buttons to appear, please place a message in the comments below.

Update: I have just installed the latest updates for my Windows 8 machine and can confirm that them buttons are now there. I obviously didn’t have 8.1 installed.

 

Issues I’ve discovered

The main issue I’ve had when debugging my files is that in the console window, if I wanted to see a variable value, I would normally just type the variable into the console and it would give me my value. However, because the browser is actually running your minified file, and showing you the original files, your variables are not actually your variables.

What do I mean? Well, below I have put a break point on my simple displayDateandName(name) function. This breakpoint is showing my original file.

When I try and get “date” variable, it states it’s undefined. With the “name” variable it displays nothing. This is because it is running your minified file, and you can tell this by looking at the Local Scope Variable window.

You can see variables called

n: “Paul”

t: Tue Apr 08 2014 21:59:40 GMT+0100 (GMT Daylight Time)

Which if you can find it within your minified file, you will see that what these variables have been minified too

Resources

http://msdn.microsoft.com/en-us/library/dn255007(v=vs.85).aspx – IE 11 and Map Files

https://developers.google.com/chrome-developer-tools/docs/javascript-debugging#source-maps – Chrome and Map Files.

http://www.codeproject.com/Articles/649271/How-to-Enable-Source-Maps-in-Firefox – Firefox and Map files

http://vswebessentials.com/ – Visual Studio Extension Web Essentials.

http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/ – HTML5Rocks Introduction to JavaScript Source Maps.

 

AMS – App Model Samples


A good set of examples of Apps have been released by Microsoft. They can be found on CodePlex. Most will work on 365 and on Prem.

Quick list of included scenarios.

  • Cloud base site collection provisioning
  • Creating site collections remotely using SP Apps in on-premises
  • Provision and integrate Yammer group into site provisioning
  • Manage and update theme settings in host web
  • Changing host web rendering with custom CSS
  • Site policy management with CSOM
  • Wiki page manipulation using CSOM
  • Site collection enumeration
  • Setting up Apps to Windows Azure with specific roles
  • People picker implementation for provider hosted app
  • Taxonomy picker implementation for provider hosted app
  • Utilization of JavaScript injection pattern to modify the UI for end users
  • Uploading of large files to SharePoint
  • Deploy branding to personal OneDrive Pro sites automatically
  • Connect to and manipulate Office365 sites using console application
  • Provide custom template pattern for sub site creation
  • Manipulation of user profile properties remotely
  • Build sync of user profile properties in cloud
  • Taxonomy driven navigation control using JavaScript
  • Mass deployment of branding elements from console app
  • Hybrid self-service site collection provisioning – one UI for cloud and on-prem
  • Synchronization of user profile pictures from on-prem to SharePoint Online
  • Dynamic permission handling
  • Remote event receivers from host web

https://officeams.codeplex.com/

Setting default Content Types for List/Libraries using JavaScript


Long time ago, when using SharePoint 2010, I had to set the default Content types on a bunch of SharePoint Lists and Libraries. Of course I had the ease of doing this in C#. In a recent project I had to do the same thing, however being a SharePoint 2013 online project, I could only do this in JavaScript. It is amazing how something you know so well in C# that would take less than an hour to do, can take hours in JavaScript if you aren’t a JavaScript expert like myself.

How you use this code is down to you, my code originally was part of a huge JavaScript configuration file, using namespaces and callbacks. For simplicity sake, I have written this blog just with straight JavaScript code to perform setting the default content type, and show you how to set up your environment to test this. This isn’t production ready code, just a way to show you how you can do it.

Environment Setup

  • Create yourself a simple team site.
  • Create some Site Content Types, I’ve based mine on Document.

  • Create yourself a couple of Document Libraries.
    • Change the Advanced Settings to Allow management of content types
    • Click Add from existing site content types and add one or more content types to your library.

I have created a HR library which consists of CF Holiday content type, and an Accounts library which has CF Claim Form, CF Invoice and CF Purchase Order added to it.

Below HR Library

Below Accounts Library

This is the environment set up. Currently the Content Types I wish to use have already been added to the libraries, and Document is the default content type.

Code and Demo

As this is purely a demo, I’m going to use SharePoint Designer to create a basic page, that when you click a button, it updates the Default Content Types for lists and libraries. It’s not ideal using SPD as there is no intellisense, but you can quickly create a page and upload to SharePoint, which is great for testing like this.

  • Connect SharePoint Designer to your site.
  • Open the All Files section, then right click and add an aspx page. I called mine test.aspx
  • Open your aspx page, you will get a warning, Click Yes to open the page in advanced mode.
  • Delete everything in the page and paste the following. This will give you a basic page, for a Standard SharePoint Master Page.
<%@ Assembly Name="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"%>
<%@ Import Namespace="Microsoft.SharePoint.WebPartPages" %> <%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %> <%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Import Namespace="Microsoft.SharePoint" %> <%@ Assembly Name="Microsoft.Web.CommandUI, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Page Language="C#" MasterPageFile="~masterurl/default.master" Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage,Microsoft.SharePoint,Version=15.0.0.0,Culture=neutral,PublicKeyToken=71e9bce111e9429c" meta:progid="SharePoint.WebPartPage.Document" meta:webpartpageexpansion="full" %>
<asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server" >
</asp:Content>
<asp:Content ContentPlaceHolderId="PlaceHolderPageTitle" runat="server">
    <SharePoint:ProjectProperty Property="Title" runat="server"/>
</asp:Content>
<asp:Content ContentPlaceHolderId="PlaceHolderPageTitleInTitleArea" runat="server">
    <SharePoint:ProjectProperty Property="Title" runat="server"/>
</asp:Content>
<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
</asp:Content>
  • If you save this page and then navigate to your site, you should get just a blank page, no errors. So your site URL would be similar to http://cf/sites/teams/test.aspx

First thing is to ensure I have all my references I require for the code to work. I’m using underscore.js. I have found underscore to be very useful for looping through arrays. I’ve downloaded underscore.js and using SharePoint designer I have created a folder called Assets/Scripts and placed the underscore.js file in there.

  • Add a reference inside your test.aspx. Put the reference inside your PlaceHolderAdditionalPageHead. I’ve also put stubs in place ready for our JavaScript in a later step.
<asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server" >
    <script type="text/javascript" src="Assets/Scripts/underscore.js"></script>
    <script type="text/javascript">
        /*Our javascript code will go here */
    </script>
</asp:Content>
  • In PlaceHolderMain content placeholder, add a button that will call our JavaScript function to set Default Content Types.
<div>
    <button type="button" onclick="setDefaultContentType();return false">Update Default Content Types</button>
</div>
  • Within the PlaceHolderAdditionalPageHead after the underscore.js reference, add the following script.
//Collection of Lists and Default Content Types for given list.
 var defaultContentTypes = [
        { ListTitle: 'HR', ContentTypeId: '0x0101003293FA81A1DB8B47AC008BE39BCE695C0057F88502D28F9042A061813A2DA79AE9', ContentTypeName: 'CF Holiday' },
        { ListTitle: 'Accounts', ContentTypeId: '0x0101005C18A4FC55B2C84FB8DD5E5A71D1DA02000529316EC50BA14CB28D9E8D69BA43B4', ContentTypeName: 'CF Claim Form' }
];

 var context;

 function setDefaultContentType(){
    context = SP.ClientContext.get_current();
    _.each(defaultContentTypes, function(current){
var rootFolder = context.get_web().get_lists().getByTitle(current.ListTitle).get_rootFolder();
var contentTypes = context.get_web().get_lists().getByTitle(current.ListTitle).get_contentTypes();

context.load(rootFolder, 'ContentTypeOrder', 'UniqueContentTypeOrder');
context.load(contentTypes);
});

context.executeQueryAsync(loadContentTypesSuccess, onScriptFailure);
 };

function loadContentTypesSuccess(){
        _.each(defaultContentTypes, function (current) {
              var originalCTO;
              var rootFolder = context.get_web().get_lists().getByTitle(current.ListTitle).get_rootFolder();
              var contentTypes = context.get_web().get_lists().getByTitle(current.ListTitle).get_contentTypes();
              originalCTO = rootFolder.get_contentTypeOrder();

              var newCTO = new Array();
              var contentTypeEnum = contentTypes.getEnumerator();
                  while (contentTypeEnum.moveNext()) {
                        var currentCT = contentTypeEnum.get_current();
                        if (currentCT.get_name().toLowerCase() == 'folder')
                            continue;

                         if (currentCT.get_name().toLowerCase() == current.ContentTypeName.toLowerCase()) {
                            newCTO.splice(0, 0, currentCT.get_id());
                            continue;
                         }

                         for (i = 0; i < originalCTO.length; i++) {
                             if (originalCTO[i].toString() == currentCT.get_id().toString()) {
                                     newCTO.push(currentCT.get_id());
                                     break;
                             }
                         }
                      }

                      rootFolder.set_uniqueContentTypeOrder(newCTO);
                      rootFolder.update();
                });
                context.executeQueryAsync(onUpdateContentTypes, onScriptFailure);
};

function onUpdateContentTypes(){
    alert('Updated Default ContentTypes');
};

function onScriptFailure(sender, args){
      alert(args.get_message() + '\n' + args.get_stackTrace());
};

Let me explain each section to you. Right at the top of the script we have a global variable called defaultContentTypes. Here we have the List Name, the Content Type ID and Name which will be set as the default content type. If you have more than 2 lists to set the content type, just add another line to this collection for each list. You can add as many, or as little as you want, the code is written to be flexible here.

The first function setDefaultContentType (which is what the button will call when pressed), gets the SharePoint context, then using the underscore.js _.each function, loops through each list in our defaultContentTypes variable. Before we can update anything we need to grab the information from SharePoint. This is what this looping function does. It gets the List rootFolder, and current content types for that list.

The line below ensures that rootFolder contains the values for the properties ContentTypeOrder, and UniqueContentTypeOrder. The ContentTypeOrder is required to get the current order of the Content Types, and the UniqueContentTypeOrder is required because when we set the new order back, this is the property to use. Leaving them out of the following line would throw an error stating “The property or field ‘ContentTypeOrder’ has not been initialized”.

context.load(rootFolder, 'ContentTypeOrder', 'UniqueContentTypeOrder');

Once we have loaded all the lists and content types (loadContentTypesSuccess) we then loop through all in defaultContentTypes again. However because we have already loaded the rootFolder in the context, this time when we call it, we will have values. We grab the original order of the content types, and loop through them. Within the while statement there is 3 checks.

  1. If content type == “folder” then don’t add it.
  2. If content type == our default content type, then add first in the array.
  3. If it is still a content type to display, add it to the end of the content type order array.

The reason why the 3rd check is written as below, is if one of the Content types is not visible on the New button, it ensures that it continues to behave this way.

for (i = 0; i < originalCTO.length; i++) {
     if (originalCTO[i].toString() == currentCT.get_id().toString()) {
                newCTO.push(currentCT.get_id());
                break;
     }
}

Lastly, once we have obtained the correct Content Type Order for the list, setting the default Content type at number one, we need to write back to SharePoint and save this data. On the root folder, we set_uniqueContentTypeOrder passing in the new array order. Then call executeQueryAsync, which will either alert success or failure.

If we see this in action, by loading up the test.aspx page we will just have a simple button, saying “Update Default Content Type”. We click this button and hopefully receive a successful alert.

If we look at our Accounts library, we can see the CF Claims form is now the default content type.

And if we look at our HR library, we can see the CF Holiday is now the default content type.

If in your code, you didn’t want to make the SharePoint Document content type visible, in your code, just don’t add it to the newCTO array, like what we do with Folder.

if (currentCT.get_name().toLowerCase() == 'folder' || currentCT.get_name().toLowerCase() == 'document')
    continue;

JSLink and SP.ClientContext being null or undefined.


JSLink files are new to SharePoint 2013 and have the ability to quickly and easily change how SharePoint displays it’s fields, lists views, webparts etc.

In a simple example of showing how to put a JSLink together with a given view in a SharePoint Online project, this example will explain to those who has never attempted to create a JSLink how the basic pieces fit together before I explain the SP.ClientContext being null or undefined.

Simple JSLink Example

In our example, I’m going to manually create a team site. Then in my code, I’m going to create 2 lists. One is a Customers List, based on a Contact form, and the other is an Order form. The Customer form is only required in this example to give an order a lookup to a customer. In the next example where we are using the SP.ClientContext, I will be working against the Customer List. For now the JSLink will be against the Order list. When the JSLink for the order list run, it will highlight the row with either Red, Orange, or Green depending on the status of the order. Please note I’m already assuming that JQuery is running on every page, so I’m not going to show you how to do that (Required for JSLinkCustomers.js). Although it is built into the solution that you can download at the end of this blog post. This example is going to be a Sandbox solution so that you can also use it in SharePoint Online.

  • Create yourself a standard Team Site and a Sandbox Empty SharePoint 2013 project pointing to your Team Site.
  • Add a List to your solution. Give the list name Customers, and create a customizable list based on Contacts.

  • This should give you a List Definition and a List Instance. Using the GUI of the List Definition, click on the Views Tab. Add a new View name called JSLinkContacts, don’t give it a Row Limit. It’s also a good idea to set the view to read only. This will prevent a user from changing the columns. (Although if you are using this just for a web part that you are programmatically adding to a webpart zone, you can set this view to hidden) In the Selected columns add the following
    • ID
    • Last Name (Link Title)
    • First Name
    • Full Name
    • Address
    • City
    • Zip/Postal Code
    • Country/Region
  • Save the file. If you now double click on the Schema.xml file, a dialog box will appear saying that the Schema.xml is already open and if you want to close it. Agree to this. The schema.xml file should now be open on your screen.

    Annoyingly, Visual Studio will screw up the format layout of the page. If you scroll down the page to the <Views> section one of the views will be named JSLinkContacts, the name we gave the view in the GUI. You will need to add the <JSLink>~site/Assets/Scripts/jslinkCustomers.js</JSLink>. The link points to the current Site file /Assets/Scripts/jslinkCustomers.js which we will add in a later step. The code for my view is below.

<View BaseViewID="2" Name="1b32d37a-ad5d-4f2b-84c7-3f3789d14bd8" DisplayName="JSLinkContacts" Type="HTML" WebPartZoneID="Main" SetupPath="pages\viewpage.aspx" Url="JSLinkContacts.aspx">
        <ViewFields>
         <FieldRef Name="ID" />
         <FieldRef Name="LinkTitle" />
          <FieldRef Name="FirstName" />
          <FieldRef Name="FullName" />
          <FieldRef Name="WorkAddress" />
          <FieldRef Name="WorkCity" />
          <FieldRef Name="WorkZip" />
          <FieldRef Name="WorkCountry" />
        </ViewFields>
        <Query>
          <OrderBy>
            <FieldRef Name="Title"></FieldRef>
            <FieldRef Name="FirstName"></FieldRef>
          </OrderBy>
        </Query>
        <Toolbar Type="Standard" />
        <XslLink Default="TRUE">main.xsl</XslLink>
        <JSLink>~site/Assets/Scripts/jslinkCustomers.js</JSLink>
      </View>

I added to the query to order the results by Last Name, First Name.

  • Create another List Definition and List Instance, and call this on Orders. This is a customizable list based on Default (Blank).
  • On the Columns tab, we are going to add new items.
    • First change the text Title to Order.
    • Add Total Price of type Currency, make this Required.
    • Add Status of type Choice. In Properties (F4) under Type, Items, add the 3 items
      • Not Actioned
      • Awaiting Delivery
      • Delivered
    • Add Customer of type Lookup. In Properties (F4) Under Type, Put the List as Lists/Customers and ShowField as FullName

  • Click on the Views Tab. Add a new View name called JSLinkOrders, don’t give it a Row Limit. In the Selected columns add the following
    • Customer
    • Title (LinkTitle)
    • Status
    • Total Price
  • Save the file. If you now double click on the Schema.xml file, a dialog box will appear saying that the Schema.xml is already open and if you want to close it. Agree to this. The schema.xml file should now be open on your screen. If you scroll down the page to the <Views> section one of the views will be named JSLinkOrders, the name we gave the view in the GUI. You will need to add the <JSLink>~site/Assets/Scripts/jslinkOrders.js</JSLink>. The link points to the current Site file /Assets/Scripts/jslinkOrders.js which we will add in a later step. The code for my view is below.
<View BaseViewID="2" Name="d78c7747-a75e-4886-9a45-1e5a4c02fed1" DisplayName="JSLinkOrders" Type="HTML" ReadOnly="TRUE" WebPartZoneID="Main" SetupPath="pages\viewpage.aspx" Url="JSLinkOrders.aspx">
        <ViewFields>
          <FieldRef Name="Customer" />
          <FieldRef Name="LinkTitle" />
          <FieldRef Name="Status" />
          <FieldRef Name="TotalPrice" />
        </ViewFields>
        <Query>
          <OrderBy>
            <FieldRef Name="Modified" Ascending="FALSE"/>
          </OrderBy>
        </Query>
        <Toolbar Type="Standard" />
        <JSLink>~site/Assets/Scripts/jslinkOrders.js</JSLink>
        <XslLink Default="TRUE">main.xsl</XslLink>
      </View>

What you have done is created a view, with the columns we require in our jslinkOrder.js file, and ensured that the jsLinkOrders.js file will be run when viewing that view.

  • Add a new Module to your solution. Add two JavaScript files. One called jsLinkCustomer.js and one called jsLinkOrders.js. Update the elements file to below, this will ensure we deploy it to /assets/scripts folder.
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="MO_ListViewsJsLink" Url="Assets/Scripts">
    <File Path="MO_ListViewsJsLink\jslinkCustomers.js" Url="jslinkCustomers.js" ReplaceContent="TRUE" Type="Ghostable" />
    <File Path="MO_ListViewsJsLink\jslinkorders.js" Url="jslinkorders.js" ReplaceContent="TRUE" Type="Ghostable" />
  </Module>
</Elements>
  • From now on will won’t touch the jsLinkCustomer.js file until the next example, but everything is now in place ready for learning about how to use the SP.Context in jslink file.
  • Open the jslinkorders.js file. There is essential two bits to make up the JSLink file. The Registering of the template view, and the custom rendering of each item. However I’m going to write this file to ensure it has it own namespace (good practice) and that it will still work in a minimal download strategy environment. (Thanks to Wictor Wilen blog to help me fix the issue I was having getting this code to work in MDS environment. http://www.wictorwilen.se/the-correct-way-to-execute-javascript-functions-in-sharepoint-2013-mds-enabled-sites )
  • First create the namespace.
Type.registerNamespace('JSLinkDemo');
JSLinkDemo.WebParts = JSLinkDemo.WebParts || {};
JSLinkDemo.WebParts.Orders = JSLinkDemo.WebParts.Orders || {};
  • Create the Orders function, with 3 returns. These returns are;
    • CustomItemHtml – Allows customization of each row
    • MdsRegisterViewTemplate – Set up for a MDS environment
    • RegisterViewTemplate – Configures the View Template for the view
JSLinkDemo.WebParts.Orders = function () {
        return {
CustomItemHtml: customItemHtml,
RegisterViewTemplate: registerViewTemplate,
MdsRegisterViewTemplate: mdsRegisterViewTemplate
    };
}();
  • At the bottom of the page under the JSLinkDemo.Webparts.Orders section, this piece of code will determine if it’s a MDS environment and either setup the Template using the MdsRegisterViewTemplate or the standard way.
if (typeof _spPageContextInfo != "undefined" && _spPageContextInfo != null) {
    JSLinkDemo.WebParts.Orders.MdsRegisterViewTemplate();
}
else {
    JSLinkDemo.WebParts.Orders.RegisterViewTemplate();
}
  • Now we can add the code for each of the return methods. Between JSLinkDemo.WebParts.Orders = function(){ and return{ . First we will add the mdsRegisterViewTemplate. For further information on how this works for MDS please view Wictor Wilen blog
var mdsRegisterViewTemplate = function () {
        var thisUrl = _spPageContextInfo.siteServerRelativeUrl + "/Assets/Scripts/jslinkorders.js";
        JSLinkDemo.WebParts.Orders.RegisterViewTemplate();
        RegisterModuleInit(thisUrl, JSLinkDemo.WebParts.Orders.RegisterViewTemplate);
    };
  • Next we will add the code for registering the view template. Stepping through the code, we create an overrideCtx object. In the overrideCtx.Templates we give a Header for the headings (I’ve done very bad inline css styling). The Item template points to the function that displays each Item in the list. Then we give Footer HTML. In this example it will display a simple table on the screen. The overrideCtx.BaseViewID points to the View ID in your schema of the list for your view called JSLinkOrders, and the overrideCtx.ListTemplateType points to the list template number. Yours will probably be 10001, if you are following this walkthrough, I changed mine, so that if you are running directly from my solution there won’t be a chance of corruption of custom code that might already be on your farm. Last line of the code registers the override template.
  var registerViewTemplate = function () {
        var overrideCtx = {};
        overrideCtx.Templates = {};
        overrideCtx.Templates.Header = "<table style='border-spacing:0px' class=\"ordersListView-wp\"><tr style='text-align:left'><th style='width:150px'>Customer</th><th style='width:150px'>Order</th><th style='width:150px;text-align:right'>Total</th></tr>";
        overrideCtx.Templates.Item = JSLinkDemo.WebParts.Orders.CustomItemHtml;
        overrideCtx.Templates.Footer = "</table>";
        overrideCtx.BaseViewID = 2;
        overrideCtx.ListTemplateType = 10556;
        SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
    };

 
  • Lastly the CustomItemHtml function. This is called for every item it is going to display. You can grab the CurrentItem from the ctx passed in, and then create HTML to return.
var customItemHtml = function (ctx) {
        var itemHtml = "";
        var customer = ctx.CurrentItem.Customer[0].lookupValue;
        var order = ctx.CurrentItem.Title;
        var status = ctx.CurrentItem.Status;
        var total = ctx.CurrentItem.TotalPrice;
        var color = "";
        switch (status) {
            case "Not Actioned":
                color = "Red";
                break;
            case "Awaiting Delivery":
                color = "Orange";
                break;
            case "Delivered":
                color = "LightGreen";
                break;
        }
        itemHtml = "<tr style='background-color:" + color + ";color:white'><td>" + customer + "</td><td>" + order + "</td><td style='text-align:right'>" + total + "</td></tr>";
        return itemHtml;
    };

If you are unsure what the CurrentItem.[property] name is called when writing your own jslink file, you can always deploy first with just Title, and as long as the other columns are in your schema view, by debugging the customItemHtml function using IE or Chrome console, you can see what the name of the other properties are called.

  • The full code for jslinkorder.js is as follows.
Type.registerNamespace('JSLinkDemo');
JSLinkDemo.WebParts = JSLinkDemo.WebParts || {};
JSLinkDemo.WebParts.Orders = JSLinkDemo.WebParts.Orders || {};
JSLinkDemo.WebParts.Orders = function () {
    var customItemHtml = function (ctx) {
        var itemHtml = "";
        var customer = ctx.CurrentItem.Customer[0].lookupValue;
        var order = ctx.CurrentItem.Title;
        var status = ctx.CurrentItem.Status;
        var total = ctx.CurrentItem.TotalPrice;
        var color = "";
        switch (status) {
            case "Not Actioned":
                color = "Red";
                break;
            case "Awaiting Delivery":
                color = "Orange";
                break;
            case "Delivered":
                color = "LightGreen";
                break;
        }
        itemHtml = "<tr style='background-color:" + color + ";color:white'><td>" + customer + "</td><td>" + order + "</td><td style='text-align:right'>" + total + "</td></tr>";
        return itemHtml;
    };
    var mdsRegisterViewTemplate = function () {
        var thisUrl = _spPageContextInfo.siteServerRelativeUrl + "/Assets/Scripts/jslinkorders.js";
        JSLinkDemo.WebParts.Orders.RegisterViewTemplate();
        RegisterModuleInit(thisUrl, JSLinkDemo.WebParts.Orders.RegisterViewTemplate);
    };
    var registerViewTemplate = function () {
        var overrideCtx = {};
        overrideCtx.Templates = {};
        overrideCtx.Templates.Header = "<table style='border-spacing:0px' class=\"ordersListView-wp\"><tr style='text-align:left'><th style='width:150px'>Customer</th><th style='width:150px'>Order</th><th style='width:150px;text-align:right'>Total</th></tr>";
        overrideCtx.Templates.Item = JSLinkDemo.WebParts.Orders.CustomItemHtml;
        overrideCtx.Templates.Footer = "</table>";
        overrideCtx.BaseViewID = 2;
        overrideCtx.ListTemplateType = 10556;
        SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
    };
    return {
        CustomItemHtml: customItemHtml,
        RegisterViewTemplate: registerViewTemplate,
        MdsRegisterViewTemplate: mdsRegisterViewTemplate
    };
}();
if (typeof _spPageContextInfo != "undefined" && _spPageContextInfo != null) {
    JSLinkDemo.WebParts.Orders.MdsRegisterViewTemplate();
}
else {
    JSLinkDemo.WebParts.Orders.RegisterViewTemplate();
}

Ensure that everything is in your Web Feature and then deploy to your site. Navigate to your Customers list, and add some customers.

Then navigate to your Orders list and add some orders for your customers. If you have set up your lookup field correctly you will be able to see the Customers in your drop down list of customers.

Hopefully if you have done everything correctly when you click the JSLinkOrders link next to All Items, you will have the list displayed to you in different colours depending on Status. If you happen to see no change, check in the console window (better using Chrome) to;

  • Ensure that you have reference the js file correctly.
  • The JS File is installed in the correct place [site]/assets/scripts/jslinkorder.js
  • There are no coding errors in your js file.

When everything is OK, you will get something similar to below. Easy on the eye it is not, however it does give a basic example of JSLink.

Getting SP.ClientContext to work!

It seems that if you wanted to do any SharePoint calls in a JSLink file you would need to do it within the customerItemHtml function. Well you can’t. If you do, you will get an ‘undefined’ when calling SP.ClientContext. You can test it yourself on your previous example. In a browser put a breakpoint on var itemHtml = “”; inside your customItemHtml function. Then in the console window type SP.ClientContext.

Actually while compiling this blog, I’ve actually realised that it is probably a good idea you cannot make any calls during the item rendering, as it would be fired for every item. That’s a lot of ajax calls. So how do we get around this? When you are setting up the overrideCtx, there is an OnPostRender property you can set to point to a function. Inside this function you will have access to SP.ClientContext.

What I worked out I could do, was put all items in a JavaScript array object, and then loop through the object in the OnPostRender function, manipulate them how I needed too, making a call in one batch call, then display the results using JQuery.

JSLink Example using SP.ClientContext

Here we are going to create the JavaScript for the JSLinkContacts view. The code will display the customers, but it will also perform a call and retrieve the customers’ orders and display them too.

Following on from our last example, we already have a jsLinkCustomer.js file. Open this file and put the basic’s in place first.

  • First create the namespace
Type.registerNamespace('JSLinkDemo');
JSLinkDemo.WebParts = JSLinkDemo.WebParts || {};
JSLinkDemo.WebParts.Customers = JSLinkDemo.WebParts.Customers || {};
  • Create the Customers function with the 3 return methods. Also add a variable object called ordersListItems. We will be adding our order items to this array later when we perform our executeQueryAsync call later on.
JSLinkDemo.WebParts.Customers = function () {
    var ordersListItems = [];
 return {
        CustomItemHtml: customItemHtml,
        RegisterViewTemplate: registerViewTemplate,
        MdsRegisterViewTemplate: mdsRegisterViewTemplate
    };
}();
  • At the bottom of the page under the JSLinkDemo.Webparts.Customers section, this piece of code will determine if it’s a MDS environment and either setup the Template using the MdsRegisterViewTemplate or the standard way.
if (typeof _spPageContextInfo != "undefined" && _spPageContextInfo != null) {
    JSLinkDemo.WebParts.Customers.MdsRegisterViewTemplate();
}
else {
    JSLinkDemo.WebParts.Customers.RegisterViewTemplate();
}
  • As we did for the jsLinkorders.js file, now we can add the code for each of the return methods. Between JSLinkDemo.WebParts.Customers = function(){ and return{ . First we will add the mdsRegisterViewTemplate. This is identical really to the jsLinkOrders.js file, except for the thisUrl variable.
    var mdsRegisterViewTemplate = function () {
        var thisUrl = _spPageContextInfo.siteServerRelativeUrl + "/Assets/Scripts/jslinkCustomers.js";
        JSLinkDemo.WebParts.Customers.RegisterViewTemplate();
        RegisterModuleInit(thisUrl, JSLinkDemo.WebParts.Customers.RegisterViewTemplate);
    };
  • Next we will add the code for registering the view template with the inclusion of the OnPostRender. In the Header section we need to ensure we have a unique class name of the table, as we will be using JQuery later to attach the items to it. (Again I have done inline styling for simplicity.) Also notice we have JSLinkDemo.WebParts.Customers.Items = [] this sets up an array that we can add the items to in the customItemHtml code.
var registerViewTemplate = function () {
        var overrideCtx = {};
        overrideCtx.Templates = {};
        JSLinkDemo.WebParts.Customers.Items = [];
        overrideCtx.Templates.Header = "<table style='border-spacing:0px' class=\"customerListView-items\"><tr style='text-align:left'><th style='width:150px'>Customer</th><th style='width:200px'>Address</th><th style='width:150px'>Post Code</th></tr>";
        overrideCtx.Templates.Item = JSLinkDemo.WebParts.Customers.CustomItemHtml;
        overrideCtx.Templates.Footer = "</table>";
        overrideCtx.Templates.OnPostRender = onPostRenderItemTemplate;
        overrideCtx.BaseViewID = 2;
        overrideCtx.ListTemplateType = 10555;
        SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
    };
  • Last of the basic code is the customItemHtml function. Here all we are doing is grabbing all the values that we want and add them to the JSLinkDemo.WebParts.Customers.Items array that we initialized in the registerViewTemplate. Ensure you return a blank string, as this function is supposed to be giving the custom HTML for the item.
var customItemHtml = function (ctx) {
        var item = {};
        item.FullName = ctx.CurrentItem["FullName"];
        item.Address = ctx.CurrentItem["WorkAddress"];
        item.City = ctx.CurrentItem["WorkCity"];
        item.Zip = ctx.CurrentItem["WorkZip"];
        item.Country = ctx.CurrentItem["WorkCountry"];
        item.LastName = ctx.CurrentItem["Title"];
        item.FirstName = ctx.CurrentItem["FirstName"];
        item.Id = ctx.CurrentItem["ID"];
        JSLinkDemo.WebParts.Customers.Items.push(item);
        return "";
    };
  • So onto the new bit really. All these functions sit between JSLinkDemo.WebParts.Customers = function(){ and return{ . The next function we are going to write is the onPostRenderItemTemplate. To ensure that the Post Render Item code runs when the SP.ClientContext is available, I’ve made the code call another function but it will execute this code after the ‘sp.js’ file has loaded. It is handy putting this code here, as on another project I was doing, I needed to wait until the SP.UserProfile.js file was available before making a call. Therefore I changed the ‘sp.js’ for ‘sp.userprofile.js’.
var onPostRenderItemTemplate = function (renderCtx) {
        ExecuteOrDelayUntilScriptLoaded(function () { getOrdersAndDisplay(); }, 'sp.js');
    };
  • The getOrdersAndDisplay function, gets the current context using SP.ClientContext.get_current(), obtains the Orders list. Then looping through each item stored in our array, I’m able to create a CAML query to return the orders for the given customer ID. The CustomerID and orders items are stored in an array. The reason why I’m storing them in an array is because after I’ve execute the query, I need to know which set of results relate to which customer. I cannot assume that the CustomerID in in numerical order from 1 with no missing customers. If you look at the code, I’m loading up the currentCtx.Load method but only call executeQueryAsync once, this is a much better, and cost effective call to the SharePoint server. The execute either calls loadOrdersSuccess to render the customer and orders (explained in the next step) or the call will fail calling loadOrdersFailed. This will just display an alert box with the error message. I’m not including that code in the blog post, but it can be found within the solution you can download at the end of this post.
  var getOrdersAndDisplay = function () {
        var currentCtx = new SP.ClientContext.get_current();
        var ordersList = currentCtx.get_web().get_lists().getByTitle('Orders');
        currentCtx.load(ordersList);
        for (var i = 0; i < JSLinkDemo.WebParts.Customers.Items.length; i++) {
            displayCustomer(JSLinkDemo.WebParts.Customers.Items[i]);
            var camlQuery = new SP.CamlQuery();
            var queryXml = "<View><Query><Where><Eq><FieldRef Name='Customer' LookupId='TRUE'/><Value Type='Lookup'>" + JSLinkDemo.WebParts.Customers.Items[i].Id + "</Value></Eq></Where></Query></View>";
            camlQuery.set_viewXml(queryXml);
            var customerAndOrderList = [];
            customerAndOrderList.Customer = JSLinkDemo.WebParts.Customers.Items[i];
            customerAndOrderList.Order = ordersList.getItems(camlQuery);
            ordersListItems[i] = customerAndOrderList;
            currentCtx.load(ordersListItems[i].Order);
        }
        currentCtx.executeQueryAsync(loadOrdersSuccess, loadOrdersFailed);
    };
  • The loadOrderSuccess function loops through each customer in the JSLinkDemo.WebParts.Customer.Items array. If the customer has an order, then it will display the Customer details and the orders that they have. I have also included the JavaScript functions displayCustomer, displayOrderTable,
    and displayOrder. They all use JQuery, and requires customerId to know where to add the line in the table we are building.
var loadOrdersSuccess = function () {
        for (var i = 0; i < JSLinkDemo.WebParts.Customers.Items.length; i++) {
            var customerId = ordersListItems[i].Customer.Id;
            if (ordersListItems[i].Order.get_count() != 0) {
                displayCustomer(ordersListItems[i].Customer);
                displayOrderTable(customerId);
                var ordersEnum = ordersListItems[i].Order.getEnumerator();
                while (ordersEnum.moveNext()) {
                    var currentOrder = ordersEnum.get_current();
                    displayOrder(currentOrder, customerId);
                }
            }
        }
    };
   var displayCustomer = function (customer) {
        jQuery('.customerListView-items').append("<tr style='background-color:aliceblue' class='customerIDNumber" + customer.Id + "'><td>" + customer.FullName + "</td><td>" + customer.Address + "</td><td>" + customer.Zip + "</td></tr>");
    };
    var displayOrderTable = function(customerID) {
        var customerclass = ".customerIDNumber" + customerID;
        var insertText = "<tr><td colspan='3'><table class='orderCustomerIDNumber" + customerID + "' style='border-spacing:0px'></table></td></tr>"
        jQuery(customerclass).after(insertText);
    };
    var displayOrder = function(currentOrder, customerID) {
        var customerClass = ".orderCustomerIDNumber" + customerID;
        jQuery(customerClass).append("<tr style='text-align:left'><td style='width:50px'></td><td style='width:150px'>" + currentOrder.get_fieldValues().Title + "</td><td style='width:150px'>" + currentOrder.get_fieldValues().Status + "</td><td style='text-align:right;width:150px'>$" + (currentOrder.get_fieldValues().TotalPrice).toFixed(2) + "</td></tr>");
    };
  • The full code for jslinkCustomers.js is as follows.
Type.registerNamespace('JSLinkDemo');
JSLinkDemo.WebParts = JSLinkDemo.WebParts || {};
JSLinkDemo.WebParts.Customers = JSLinkDemo.WebParts.Customers || {};
JSLinkDemo.WebParts.Customers = function () {
    var ordersListItems = [];
    var customItemHtml = function (ctx) {
        var item = {};
        item.FullName = ctx.CurrentItem["FullName"];
        item.Address = ctx.CurrentItem["WorkAddress"];
        item.City = ctx.CurrentItem["WorkCity"];
        item.Zip = ctx.CurrentItem["WorkZip"];
        item.Country = ctx.CurrentItem["WorkCountry"];
        item.LastName = ctx.CurrentItem["Title"];
        item.FirstName = ctx.CurrentItem["FirstName"];
        item.Id = ctx.CurrentItem["ID"];
        JSLinkDemo.WebParts.Customers.Items.push(item);
        return "";
    };
    var displayCustomer = function (customer) {
        jQuery('.customerListView-items').append("<tr style='background-color:aliceblue' class='customerIDNumber" + customer.Id + "'><td>" + customer.FullName + "</td><td>" + customer.Address + "</td><td>" + customer.Zip + "</td></tr>");
    };
    var displayOrderTable = function(customerID) {
        var customerclass = ".customerIDNumber" + customerID;
        var insertText = "<tr><td colspan='3'><table class='orderCustomerIDNumber" + customerID + "' style='border-spacing:0px'></table></td></tr>"
        jQuery(customerclass).after(insertText);
    };
    var displayOrder = function(currentOrder, customerID) {
        var customerClass = ".orderCustomerIDNumber" + customerID;
        jQuery(customerClass).append("<tr style='text-align:left'><td style='width:50px'></td><td style='width:150px'>" + currentOrder.get_fieldValues().Title + "</td><td style='width:150px'>" + currentOrder.get_fieldValues().Status + "</td><td style='text-align:right;width:150px'>$" + CommaFormatted((currentOrder.get_fieldValues().TotalPrice).toFixed(2)) + "</td></tr>");
    };
    function CommaFormatted(amount) {
        var delimiter = ","; // replace comma if desired
        var a = amount.split('.', 2)
        var d = a[1];
        var i = parseInt(a[0]);
        if (isNaN(i)) { return ''; }
        var minus = '';
        if (i < 0) { minus = '-'; }
        i = Math.abs(i);
        var n = new String(i);
        var a = [];
        while (n.length > 3) {
            var nn = n.substr(n.length - 3);
            a.unshift(nn);
            n = n.substr(0, n.length - 3);
        }
        if (n.length > 0) { a.unshift(n); }
        n = a.join(delimiter);
        if (d.length < 1) { amount = n; }
        else { amount = n + '.' + d; }
        amount = minus + amount;
        return amount;
    }
    var getOrdersAndDisplay = function () {
        var currentCtx = new SP.ClientContext.get_current();
        var ordersList = currentCtx.get_web().get_lists().getByTitle('Orders');
        currentCtx.load(ordersList);
        for (var i = 0; i < JSLinkDemo.WebParts.Customers.Items.length; i++) {
            var camlQuery = new SP.CamlQuery();
            var queryXml = "<View><Query><Where><Eq><FieldRef Name='Customer' LookupId='TRUE'/><Value Type='Lookup'>" + JSLinkDemo.WebParts.Customers.Items[i].Id + "</Value></Eq></Where></Query></View>";
            camlQuery.set_viewXml(queryXml);
            var customerAndOrderList = [];
            customerAndOrderList.Customer = JSLinkDemo.WebParts.Customers.Items[i];
            customerAndOrderList.Order = ordersList.getItems(camlQuery);
            ordersListItems[i] = customerAndOrderList;
            currentCtx.load(ordersListItems[i].Order);
        }
        currentCtx.executeQueryAsync(loadOrdersSuccess, loadOrdersFailed);
    };
    var loadOrdersSuccess = function () {
        for (var i = 0; i < JSLinkDemo.WebParts.Customers.Items.length; i++) {
            var customerId = ordersListItems[i].Customer.Id;
            if (ordersListItems[i].Order.get_count() != 0) {
                displayCustomer(ordersListItems[i].Customer);
                displayOrderTable(customerId);
                var ordersEnum = ordersListItems[i].Order.getEnumerator();
                while (ordersEnum.moveNext()) {
                    var currentOrder = ordersEnum.get_current();
                    displayOrder(currentOrder, customerId);
                }
            }
        }
    };
    var loadOrdersFailed = function (sender, args) {
        alert(args.get_message() + '\n' + args.get_stackTrace());
    };
    var onPostRenderItemTemplate = function (renderCtx) {
        ExecuteOrDelayUntilScriptLoaded(function () { getOrdersAndDisplay(); }, 'sp.js');
    };
    var mdsRegisterViewTemplate = function () {
        var thisUrl = _spPageContextInfo.siteServerRelativeUrl + "/Assets/Scripts/jslinkCustomers.js";
        JSLinkDemo.WebParts.Customers.RegisterViewTemplate();
        RegisterModuleInit(thisUrl, JSLinkDemo.WebParts.Customers.RegisterViewTemplate);
    };
    var registerViewTemplate = function () {
        var overrideCtx = {};
        overrideCtx.Templates = {};
        JSLinkDemo.WebParts.Customers.Items = [];
        overrideCtx.Templates.Header = "<table style='border-spacing:0px' class=\"customerListView-items\"><tr style='text-align:left'><th style='width:150px'>Customer</th><th style='width:200px'>Address</th><th style='width:150px'>Post Code</th></tr>";
        overrideCtx.Templates.Item = JSLinkDemo.WebParts.Customers.CustomItemHtml;
        overrideCtx.Templates.Footer = "</table>";
        overrideCtx.Templates.OnPostRender = onPostRenderItemTemplate;
        overrideCtx.BaseViewID = 2;
        overrideCtx.ListTemplateType = 10555;
        SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideCtx);
    };
    return {
        CustomItemHtml: customItemHtml,
        RegisterViewTemplate: registerViewTemplate,
        MdsRegisterViewTemplate: mdsRegisterViewTemplate
    };
}();
if (typeof _spPageContextInfo != "undefined" && _spPageContextInfo != null) {
    JSLinkDemo.WebParts.Customers.MdsRegisterViewTemplate();
}
else {
    JSLinkDemo.WebParts.Customers.RegisterViewTemplate();
}

By re-packaging and deploying the solution, either using Visual Studio or doing it manually (You can disable the feature, remove the Sandbox Solution, add the solution and re-activate the feature, by clearing your cache your changes should work).

Navigate back to your Customer List, and you will see your customers you entered previously.

Next to All contacts, you will see the link for JSLinkContacts view. Click on this link, and you customer list will now be displayed with the relevant orders with the customer name.

With JSLink you can do all sorts of things, really if you are a JQuery/JavaScript wizard, there not much you can’t do. Last thing I will show you, is you may have noticed that my namespaces were all JSLinkDemo.WebParts but these aren’t really webparts. These views could be hidden in the list, and called within a ListView WebPart to be displayed on a homepage of a site for example.

If you create a webpage, and add the customer list WebPart to the page, and select the view of our JSLinkContact (won’t be able to do this if the view is hidden). Then after you save the page, you will have a nice little webpart that prevents users from editing anything but provides them with the information they need. If you used the UserProfile.js to obtain the current user City, you could use that information to only show orders related to their city.

The link to the solution can be found on my OneDrive

Why are my publishing pages saying I have so many spelling errors?


In my publishing site, I have a basic custom content type that is based on the SharePoint out the box Page Content type. I have created a custom page layout. My layout contains a PublishingRollupImage, PublishingPageContent, and I’ve created a section on my page layout that only shows when you edit the page which contains PublishingContact. The publishing contact is mainly for search.

When my page is in edit mode, the screen looks like the following:

So I add a picture and some content to the page, and then go to publish it. The Page Content has been double checked and there is no spelling mistakes in there. However when I click publish, I get a pop-up box saying there are 73 spelling error(s) found. How is this possible? There isn’t even 73 words on the page.

The clue why this is happening is actually shown on the page. Where I have Page Owner at the bottom of the page, it clearly states “Spelling Errors Found.” But why? It’s just my name. If you click on the red text, it brings up a pop up, showing you what the spelling mistakes are.

What is actually happening is the user control HTML is being read. The user control has <textarea> which is hidden by css. This textarea is used by SharePoint to correctly display the User on the page, with the link to the user profile information. It is this text that is being spell checked.

There is a simple fix that you can add to your page layouts (assuming your page has jQuery already loaded). The textarea needs an attribute added to it called “excludeFromSpellCheck” which makes the SharePoint spell checker ignore the text within it. On your page layout page, find the ContentPlaceHolderID=”PlaceHolderMain” and right at the bottom of this section add a PublishingWebControls:EditModePanel with the following Javascript inside.

<PublishingWebControls:EditModePanel ID="SpellCheckEditModePanel" runat="server">
<script type="text/javascript">
jQuery(".ms-usereditor textarea").attr("excludeFromSpellCheck", "true");
</script>
</PublishingWebControls:EditModePanel>

The reason why we put it inside an EditModePanel is because the spell checking is only carried out when the page is in edit mode, so there is no need to run the JavaScript in display mode.

As you can see after implementing the jQuery change, we no longer get any spelling errors found.

SharePoint Online menu icons missing in Internet Explorer


For some reason the past couple of days I’ve noticed that inside my VM the icons at the top right of my SharePoint Online site was missing.

Missing icons

How it should look

It worked fine in Chrome, Firefox. It also worked on IE on my base PC, my PC at home, inside my Hyper V machine, but for some reason not inside Virtual Machine at work. Every SharePoint Online site didn’t have the icons in Internet Explorer.

The way I fixed it was resetting my Internet Explorer. Please ensure all Internet Explorer browsers are closed apart from the one you have open to access Internet Options.

  • Goto Internet Options.
  • Click on the Advanced tab.
  • Then under Reset Internet Explorer settings click Reset

  • Tick the box Delete personal settings (Might not need to do this, but now I’ve fixed the problem, I’m unable to go back and test). Click Reset.

  • Once the Reset has complete you are asked to reboot your PC. After you have done this, if you navigate back to your SharePoint Online site you will see the icons are back.

    Why they disappeared in the first place I do not know, but I’m glad to get them back.


Image Renditions


Image renditions are new to SharePoint 2013 publishing sites, and allow you to customise the appearance of an image and save that customisation as a template. You can upload a large photo to your SharePoint image library and ensure that it is always a given size on a given page, display template, search result etc. The best reason to use renditions is instead of using CSS where a 4mb image after it is resized, it is still a 4mb in size. Using renditions SharePoint will resize your image and create a new file, therefore your 4mb 1680×1050 resized to 100×62 could end up being 22kb, which is a lot better for a web image.

Update: To get Image Renditions to work you must turn on BLOB cache feature. For more information on how to configure BLOB cach, refer to http://technet.microsoft.com/en-us/library/cc770229.aspx

The point and click way.

  1. On your site, goto site settings.
  2. Under Look and Feel click on Image Renditions.
  3. The page you will be taken to will probably have a few renditions on the page. As you can see from my screen shot, they have an ID, Name, Width and Height. When you upload a picture to the image library, SharePoint will create 4 images, one for each rendition. If there is a Width but no Height, SharePoint will ratio the height to the given width.
  4. Click Add new item, fill in the Name, Width (optional), and Height (optional) then click Save.

The coding way.

Using an XML file and an elements file you can code the image rendition. Before showing you how to do this, I’ll show you where the file needs to be uploaded too. From Site Settings under Web Designer Galleries click on Master pages and page layouts. In the Master Page Gallery you will see a PublishingImageRenditions.xml file. Download a copy of this file, we will use this as a base template for our demo.

  1. Open up Visual Studio and create a new SharePoint 2013 empty project (Sandbox, as this will work for SharePoint online too)
  2. Right click the project and add new item. Select Module and give it a name. (I’ve called mine MO_ImageRendition)
  3. Rename the Sample.txt to PublishingImageRenditions.xml and paste in the content from the downloaded PublishingImageRenditions.xml from earlier. Or replace the file with the downloaded file. Whatever is easier for you.
  4. To add a new Rendition, change the NextId to the next number up. Then add the following section after the last </ImageRendition> but before </Renditions>
    <ImageRendition>
    <Height>-1</Height>
    <Id>5</Id>
    <Name>CannonFodder Display Rendition</Name>
    <Version>1</Version>
    <Width>150</Width>
    </ImageRendition>
    

    Above I have put -1 for height. This means I don’t want a given height, I want a ratio based on the Width. So it will take the original image ratio and calculate the height automatically based on the width being 150px. I have also given it a name of CannonFodder Display Rendition, which isn’t really meaningful. I recommend giving it a name for where you expect to us it. So for example you have a HomePage news rollup which will have an image of 150px x 150px the name could be HomePage News 150×150.

  5. In the Elements file, change the file line so that it says the following.
    <File Path=”MO_ImageRendition\PublishingImageRenditions.xml” Url=”_catalogs/masterpage/PublishingImageRenditions.xml” ReplaceContent=”TRUE” />
    
  6. Ensure your module is part of Feature1 and scoped at Site, Publish the project and create a WSP.
  7. Upload your solution to SharePoint, and activate your feature. Then head back to Image Renditions under Look and Feel.

See Image Renditions in action

If you go to your publishing Images and upload a picture. After the picture has been uploaded, click on the 3 ellipse and click on Edit Renditions.

From using Chrome Network, I’m able to see the size of each image on this page. The original image is 53Kb 610px x 469px, as you can see each Rendition is a different file size. Rendition 3, which is Display Template Large Picture is 468×220 at 16.8kb.

Here in the Edit Renditions you can edit the image of each rendition, if you want. For example if you think that SharePoint has trimmed the image and remove the most important part of the image, you can move the trimmed area to include the bit you think that should be showing. Unfortunately, I am unsure how to do this in code at the moment.

Using Image Rendition with point and click.

I have a publishing page and I’m using the out the box page layout Article Page – Image on the Left. I’ve clicked to insert an image and it’s brought up the Edit Image Properties dialog box. Once I have selected the picture, under Selected Images there is Image Rendition.

Below I have selected Display Template Large Picture. You can see here why the naming is important.

Using Image Rendition in your code

To use an image rendition all you need to do is in the src of your image tag include ?RenditionId= with the number of the Rendition. Ideally you could create a knockout webpart, or use the rendition in a display template for a search query web part. Below I’m just adding a Script Editor to a page and adding the following line of code:

<h3>My Image using Rendition ID 5</h3>
<img alt="picture" src="/sites/OTBPUB/PublishingImages/cl01cloudgate.jpg?RenditionID=5" />

Couple of gotchas with Renditions and Search

I have been playing about with Renditions and Search for a couple of weeks now, and they have tripped me up a couple of times. Here is what I have learnt.

  1. When you use a rendition for example on a publishing page, so that the article image is always the same size no matter what size the user uploads, the search picks up the Rendition string as well. So in display templates for search query webparts, you need to check the src of the picture and possible strip out the Rendition ID and then replace with the Rendition ID you want to use for the display template.

    In my display template my JavaScript code look like the following

    var pictureURL = $getItemValue(ctx, "Picture URL");
    pictureURL.overrideValueRenderer($urlHtmlEncode);
    
    //get the Src of the picture
    var pictureSrc = pictureURL.inputValue.match(/src="([^\s]+)"/i)[1];
    if (pictureSrc.toLowerCase().indexOf("renditionid") >= 0) {
         pictureSrc = pictureSrc.replace(/RenditionID=\d/i, "RenditionID=" + renditionID)
    } else {
         pictureSrc = pictureSrc + "?RenditionID=" + renditionID;
    }
    
  2. The rendition file PublishingImageRenditions.xml needs to be where the picture is being pulled from, not where the picture is being displayed.

Hopefully you can see Image Rendition is a great addition to SharePoint.