Using REST to upload a File to Document library (Including different site collection)


The code I’m going to show you today will show you how you can upload a file to a document library using REST. This solution could be used within a Sandbox application.

To upload files, it is much better to use REST than the SharePoint API. The main reason is that SharePoint JSOM/CSOM can only upload files a maximum size of 1.5MB, where REST is good up to 2GB.

My example here has a simple GUI, it is an Upload form, which allows the user to pick from a list the site they wish to upload the file to, a file upload box, and an Upload button.

HTML Code.

<SharePoint:FormDigest runat="server"/>
<div>
   <label id="lblInfo" class="label" for="ProposalSiteUrl">Please enter your Site Collection URL or leave blank to upload file to this Site Collection document library:</label>
</div>
<div>
   <select id="ProposalSiteUrl" class="dropdown" title="Select a site">
                    <option value="http://intranet.cannonfodder.local">This Site</option>
                    <option value="http://intranet.cannonfodder.local/sites/teamsite1">Team Site 1</option>
                    <option value="http://intranet.cannonfodder.local/sites/teamsite2">Team Site 2</option>
                    <option value="http://intranet.cannonfodder.local/sites/teamsite3">Team Site 3</option>
      </select>
</div>
<div class="div">
        <input id="AttachmentUploadField" type="file" size="98"/>
        <input id="AttachAttachmentButton" class="button" onclick="CannonFodder.Utilities.SaveAttachment();" type="button" value="Upload"/>
</div>
<div class="div">
         <label id="lblResult" class="result"></label>
</div>

The user can select from the dropdown a site to send the file to. Select the file using the file picker and click Upload. You can see in this example I have hard coded the option values for the dropdown list. Any results pass or failed will be displayed to the user using the lblResult label.

JavaScript

This code will require JQuery for grabbing the element values from the page and to perform JQuery Ajax request. Ensure that your page has JQuery loaded. In this example I grabbing JQuery from Google.

<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Personally, JavaScript isn’t my preferred programming language and I’m trying to write better JavaScript. I’m ensuring I’m not making it part of the Global namespace, and using boxing methods etc. Therefore in this example here you should find this a good practice writing JavaScript.
First I’m going to namespace my JavaScript Methods.

var CannonFodder = CannonFodder || {};
CannonFodder.Utilities = function(){
var currentDlg;
 //Code to write in here.
 return {
      SaveAttachment:saveAttachment
 };
}();

The above piece of code, will create a namespace called CannonFodder.Utilities with one public method called SaveAttachment which calls the private method saveAttachement. We haven’t written saveAttachment yet. We will write a bunch of functions inside the section where I have commented ‘//Code to write in here‘. The first one will be saveAttachement. (Notice small ‘s’ for save) This method will be the only method in this section that can be called from outside of CannonFodder.Utilities. The variable currentDlg
is a global variable for the scope of CannonFodder.Utilities. It is so I can display a waiting dialog screen to the user. This isn’t necessary for this code to work, it just gives the demo a nicer user experience.

The saveAttachment method

var saveAttachment = function(){
    //Validate if the upload file has a file selected.
    if(!validated()){
            jQuery('#lblResult').text("Please choose a file!");
            return;
    }
    
    //Dialog variables
    var dlgTitle;
    var dlgMsg;
    var dlgWidth;
    var dlgHeight;
    dlgTitle = "Uploading Your File...";
    dlgMsg = "<br />Please wait whilst your file is being uploaded<br /><br />Please do not close your browser window.";
    dlgHeight = 300;
    dlgWidth = 500;
        
    //Create Dialog box.
   if (currentDlg == null) {
           currentDlg = SP.UI.ModalDialog.showWaitScreenWithNoClose(dlgTitle, dlgMsg, dlgHeight, dlgWidth);
   }

   //Get File        
   var file = jQuery("#AttachmentUploadField")[0].files[0];

   //Upload the file
   uploadFile(file);
};

The code above doesn’t do much, it first calls a validation method (yet to write) to check if the user has selected a file to upload. If not, it updates the lblresult text to inform the user they must upload a file. If validation passes, we set up a dialog box to inform the user that the upload is taking place (it will take a long time for a 2GB file). Next we display the dialog to the user, grab the file and passes it to the uploadFile method.

The validated method

var validated = function(){
   var file = jQuery("#AttachmentUploadField")[0].files[0];
   if(file == null){        
          return false;
  }
  else{
    return true;
  }
};

The validated method grabs the Upload control and checks if there is a file available. If it is there then the function returns true, else it returns false.

The uploadFile method

var uploadFile = function (file) {
    proposalSiteUrl = jQuery('#ProposalSiteUrl option:selected').val();        
        
    if (proposalSiteUrl == _spPageContextInfo.webAbsoluteUrl) {
        uploadFileLocal(file);
    }
    else{
        uploadFileCrossSite(file, proposalSiteUrl);
    }
};

Our upload file still isn’t the actual code that uploads the file to the given URL. Here I’m checking if the site I’m uploading to is the same Site Collection I’m running my upload page from or not. If it is, then I can do a very simple upload (uploadFIleLocal). If I need to upload to a different Site Collection, then I will be required to perform a cross site call (uploadFileCrossSite).

The uploadFileLocal method

To explain this, I am going to show this method in bits and explain each section.

var uploadFileLocal = function (file) {
      var digest = jQuery("#__REQUESTDIGEST").val();
      var webUrl = _spPageContextInfo.webAbsoluteUrl;
      var libraryName = "Documents";

      var reader = new FileReader();
      var arrayBuffer;

First we need to set all our variables. The digest request is important, it is a client side token to validate post backs to SharePoint to prevent attacks where the user might be tricked into posting data back to the server. It is unique to a user and a site. It is only valid for a limited time. The first line gets the current request digest from the page. This digest is required in our ajax post request when we upload the file. The other variables are pretty standard, web url, the library name to upload to, a file reader and array buffer.

        reader.onload = function (e) {
            arrayBuffer = reader.result;

            url = webUrl + "/_api/web/lists/getByTitle(@TargetLibrary)/RootFolder/files/add(url=@TargetFileName,overwrite='true')?" +
               "@TargetLibrary='" + libraryName + "'" +
               "&@TargetFileName='" + file.name + "'";

            //JQuery Ajax call here

       };
       reader.readAsArrayBuffer(file);
  };

The next part of the uploadFileLocal function that continues straight after var arrayBuffer. Our reader needs to read in the file, and on the onload event, we need to prepare our REST url to past into the JQuery ajax request. (The JQuery Ajax call is shown in the next piece of code). The reader.onload function doesn’t fire until after reader.readAsArrayBuffer(file) is called. The REST url uses two parameters @TargetLibrary and @TargetFileName, the actual file data is put into arraryBuffer and used in the JQuery Ajax call.

        
jQuery.ajax({
        url: url,
       type: "POST",
       data: arrayBuffer,
    headers: {
            "Accept": "application/json; odata=verbose",
            "X-RequestDigest": digest
            },    
contentType: "application/json;odata=verbose",
processData: false,
    success: function () {
            jQuery('#lblResult').text("Successfully uploaded file locally.");
            if (currentDlg != null) {
                        currentDlg.close();
            }
         },
     error: function (arr, error) {
               jQuery('#lblResult').text("Error uploading file locally.");
               if (currentDlg != null) {
                      currentDlg.close();
               }
          }
});

Insert the above code in the previous snippet where the comment //JQuery Ajax call here is. This is a typical Ajax call using JQuery. http://api.jquery.com/jquery.ajax/ We pass in our REST url, state that it’s a POST request, and set the data to our file arrayBuffer. Our headers show that the call is a json call, and here we pass in the request digest we obtained in the very first variable of the uploadfileLocal function. The contentType and processData are standard for this type of call. Lastly I have created a success and error callbacks, with inline functions. The functions are similar, both updating the lblResult text with a success or fail message, and then close the dialog that is on show to the user. In the error function we could use the error variable to obtain the actual error message and display it to the user.

This concludes uploading a file to the same site. However if you try to use the same code to upload to a different site it will fail. The reason is all to do with the digest request. As I stated earlier, this is a client side token that is unique to the user and site. Therefore when you try to access a different site to what you are on, you will be passing an invalid digest request. You first need to obtain the digest request for the other site before you can upload/post data to it. Please note: The user will need contribute access to the site to be able to upload a file.

The uploadFileCrossSite method

var uploadFileCrossSite = function (file, webUrl) {
        url = webUrl + "/_api/contextinfo";
        jQuery.ajax({
            url: url,
            type: "POST",
            headers: {
                "Accept": "application/json; odata=verbose"
            },
            contentType: "application/json;odata=verbose",
            success: function (data) {
               var digest = data.d.GetContextWebInformation.FormDigestValue;
                var libraryName = "Documents";

                var reader = new FileReader();
                var arrayBuffer;

                reader.onload = function (e) {
                    arrayBuffer = reader.result;

                    url = webUrl + "/_api/web/lists/getByTitle(@TargetLibrary)/RootFolder/files/add(url=@TargetFileName,overwrite='true')?" +
                       "@TargetLibrary='" + libraryName + "'" +
                       "&@TargetFileName='" + file.name + "'";

                    jQuery.ajax({
                        url: url,
                        type: "POST",
                        data: arrayBuffer,
                        headers: {
                            "Accept": "application/json; odata=verbose",
                            "X-RequestDigest": digest
                        },
                        contentType: "application/json;odata=verbose",
                        processData: false,
                        success: function () {
                        jQuery('#lblResult').text("Successfully uploaded file to different site.");
                          if (currentDlg != null) {
                              currentDlg.close();
                              }
                        },
                        error: function () {
                          jQuery('#lblResult').text("Error uploading file to different site.");
                          if (currentDlg != null) {
                              currentDlg.close();
                              }

                        }
                    });
                };

                reader.readAsArrayBuffer(file);

            },
       error: function () {
            jQuery('#lblResult').text("Error accessing other site.");
               if (currentDlg != null) {
                   currentDlg.close();
              }
          }
      });
 };

The uploadFileCrossSite code I have shown as whole, mainly because I have explained most of it in the uploadFileLocal function above. The lines I’ve highlighted 1-11 is a JQuery Ajax call into the target site. By requesting the context info, we can obtain the request digest of the target site in the success handler, data.d.GetContextWebInformation.FormDigestValue. We can then use this value to pass in as the digest to upload the file to the target site. The code to upload the file now is identical as before, obtaining the reader, calling the onload event, then on the success of the Ajax request we update the lblResult with a success message and close the dialog displayed.

The final error:function() call lines 53-59 highlighted at the bottom of the code is the error callback if there is an error contacting the target site for the request digest.

I can now test the form, I’ve selected my second team site, and selected a document DemoWordDoc3.docx to upload. I’ve then clicked the Upload button.

The file dialog appears in the centre of the screen so the user knows something is happening.

When the file has been uploaded, the user gets the message that it has successfully uploaded.

If I check out my Team Site 2, you can see that the DemoWordDoc3 is in the Documents library.

You can obtain my upload page from my OneDrive. To get into your SharePoint site, it is easiest just to copy it in using SharePoint Designer.

http://1drv.ms/1tcIDIQ

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