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

Advertisements