Thursday, September 25, 2014

Using Knockout and CSOM (Client Side Object Model) for pages libraries and binding the output in a jcarousel Slider - The full Example

KnockoutJS is a fantastic library when you are looking for a drop-in enhancement that brings in client side data-binding and applies the Model-View-View-Model design pattern to your websites.
Knockout works with a ViewModel that can either be a JS object or a JS function. Either ways, your ViewModel is your data source. Using Knockout, you can bind DOM elements to your model using a declarative syntax. Knockout also provides Templating (for repetitive structures in a web page) and dependency tracking using ko.observableArray. With dependency tracking, if a property is changed, it will automatically notify the UI. The UI reflects these changes and can also change the value to automatically update the source object again.
Using REST APIs and CSOM in your SharePoint implementations nowadays is a must for the rise of using SharePoint apps and SharePoint online, the glory of Server Side Object Model is fading and the technology now is going towards the  client side operations.

In this article we will discuss a simple feature - Getting SharePoint List Item form Page Library using CSOM - Then we will bind the returned results to a predefined HTML DOM elements to have those results in jcarousel slider.



  1. Get Page List Items  using CSOM
If you want to get pages (List Items) from SharePoint using Client Side scripts you have two approaches, CSOM or using REST APIs, In our case we will use CSOM as when I tried calling REST API for my pages library including the Roll up Image Field "http://server/en/News/_api/web/Lists/getbytitle('Pages')/items?$select=Id,Title,FileRef,PublishingRollupImage" , I had the following error:


Anyway to select the Pages Items we will use the below method "SelectNewsPages" I've commented the code inline with the explanation for each line :

Add the following Script links:


1
2
    <script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/4.0/1/MicrosoftAjax.js"></script>
    <script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>

Select News Pages JS:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
function SelectNewsPages() {
    //Get pages library sub web 
    var WebUrl = _spPageContextInfo.webAbsoluteUrl + "/News";
    //Load Context according to news sub site
    var context = new SP.ClientContext(WebUrl);
    var NewsWeb = context.get_web();
    //Get Pages Library (List)
    var PagesList = NewsWeb.get_lists().getByTitle('Pages');

    //Build the selection query
    //in this example we select a specific content type, Ordered By Article date desc and row limited to 5 Items

    var NewsContentTypeId = '0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D00C2CE4371BE4442CB9AE069A5FDF4163A';
    var query = new SP.CamlQuery();
    query.set_viewXml("<View><Query><Where><BeginsWith><FieldRef Name='ContentTypeId' /><Value Type='ContentTypeId'>" + NewsContentTypeId + "</Value></BeginsWith></Where><OrderBy><FieldRef Name='ArticleStartDate' Ascending='FALSE' /></OrderBy></Query><RowLimit>5</RowLimit></View>");

    //Get List Items
    var collListItem = PagesList.getItems(query);
    //Include the view fields
    context.load(collListItem, 'Include(FileRef,Title,RoutingRuleDescription,ArticleStartDate,PublishingRollupImage,ContentType)');

    //Execute your Query Async, and define the success and failure handlers
    context.executeQueryAsync(
        Function.createDelegate(this, function () {
            //DO Some Logic for Success
        }),
        Function.createDelegate(this, function () {
            alert(args.get_message());
        }));
}

Now lets under stand the Knockout view model, Simply in our model we will define:

 - The Object that we will bind ( If you are using REST APIs the data already is in JSON format so no need to define your own structure )
 - The observable array that will contain all objects to be bind
 - The get method that will fill the array

First download the following files and add the following references: 


1
2
<script type="text/javascript" src="/_layouts/15/myScripts/knockout-3.2.0.js"></script>
<script type="text/javascript" src="/_layouts/15/myScripts/ko.sp-1.0.min.Ex.js"></script>


REST API Example:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function EmployeeModal() {
    var self = this;
    //Data array holding objects to be bind
    self.Employees = ko.observableArray([]);

    //Get method
    $.getJSON(_spPageContextInfo.webAbsoluteUrl + "/_vti_bin/listdata.svc/Employees?$expand=Skills,ModifiedBy",
                 function (data) {
                     if (data.d.results) {
                         self.Employees(ko.toJS(data.d.results));
                     }
                 }
           );
}
$(document).ready(function () {
    ko.applyBindings(new EmployeeModal());
});

For our example using CSOM :

Add following in the header


1
2
3
4
5
6
7
8
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
<link rel="stylesheet" type="text/css" href="/_layouts/15/myScripts/jcarousel.basic.css" />
<script src="/_layouts/15/myScripts/jcarousel.basic.js" type="text/javascript"></script>
<script src="/_layouts/15/myScripts/jquery.jcarousel.min.js" type="text/javascript"></script>
<script type="text/javascript" src="//ajax.aspnetcdn.com/ajax/4.0/1/MicrosoftAjax.js"></script>
<script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>
<script type="text/javascript" src="/_layouts/15/myScripts/knockout-3.2.0.js"></script>
<script type="text/javascript" src="/_layouts/15/myScripts/ko.sp-1.0.min.Ex.js"></script>

Following the HTML DOM to be bind

- Item Template Definition in the header


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    <%--Set the ID of the item template to be linked in the main foreach binder--%>
    <script type="text/html" id="NewsItem">
        <li>
            <a data-bind="attr: { href: Url, title: Title }">
                <img data-bind="attr: { src: Image, alt: Title }" width='900' height='395' />
            </a>
            <h3 data-bind="text: Title"></h3>
            <p data-bind="text: Summary"></p>
        </li>
    </script>

- Main Carousel container 


1
2
3
4
5
6
7
8
 <div class="jcarousel-wrapper">
        <div class="jcarousel">
            <%--The binder will itterate using foreach in the KO observable array 'sliderAllAnnouncments' , Binding the objects using the predefined item template--%>
            <ul class="jcarousel-ul" data-bind="template: { name: 'NewsItem', foreach: sliderAllAnnouncments }" />
        </div>
        <a href="#" class="jcarousel-control-prev bg-arrow-left"></a>
        <a href="#" class="jcarousel-control-next bg-arrow-right"></a>
    </div>

The Knockout View Model using CSOM - FULL CODE INCLUDING PREVIOUS SNIPPET :


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
//Load a loading image till the items are acquired 
$(window).load(function () {
    if ($(".jcarousel-ul").html() != null) {
        $(".jcarousel-ul").html("<CENTER><div class='loader' style='position:absolute;left:50%; top:50%;'><CENTER><img src='/_layouts/15/EQNewsScripts//ajax-loader.gif' /></CENTER></div></CENTER>");
        $(".loader").show();
        //Delay the calling till the sp.js is loaded
        SP.SOD.executeFunc('sp.js', 'SP.ClientContext', LoadNews)
    }
})

//Intialize the view model and apply bindings
function LoadNews() {
    var VM = new viewModel();
    VM.sliderRetrieveAnnouncments();
    ko.applyBindings(VM);
}

function viewModel() {
    var self = this;

    // Definition for the bind object
    self.sliderAnnouncement = function (Url, Title, Image, Summary, Date) {
        this.Url = Url;
        this.Title = Title;
        this.Image = Image;
        this.Summary = Summary;
        this.Date = Date;
    }

    // Definition for Array holding objects to be binded
    self.sliderAllAnnouncments = ko.observableArray([]);

    //Get Items using CSOM
    self.sliderRetrieveAnnouncments = function () {
        var WebUrl = _spPageContextInfo.webAbsoluteUrl + "/News";
        var context = new SP.ClientContext(WebUrl);
        var NewsWeb = context.get_web();
        var PagesList = NewsWeb.get_lists().getByTitle('Pages');
        var query = new SP.CamlQuery();
        query.set_viewXml("<View><Query><Where><BeginsWith><FieldRef Name='ContentTypeId' /><Value Type='ContentTypeId'>0x010100C568DB52D9D0A14D9B2FDCC96666E9F2007948130EC3DB064584E219954237AF3900242457EFB8B24247815D688C526CD44D00C2CE4371BE4442CB9AE069A5FDF4163A</Value></BeginsWith></Where><OrderBy><FieldRef Name='ArticleStartDate' Ascending='FALSE' /></OrderBy></Query><RowLimit>5</RowLimit></View>");
        var collListItem = PagesList.getItems(query);
        context.load(collListItem, 'Include(FileRef,Title,RoutingRuleDescription,ArticleStartDate,PublishingRollupImage,ContentType)');
        context.executeQueryAsync(
            Function.createDelegate(this, function () {

                //The data loaded successfully, So hide the loader section and begin pushing data
                $(".loader").hide();
                $(".jcarousel-ul").empty();

                var listItemEnumerator = collListItem.getEnumerator();
                var length = collListItem.get_count();
                if (length > 0) {
                    //Fetch the returned data and wrap to the observable array
                    while (listItemEnumerator.moveNext()) {
                        var oListItem = listItemEnumerator.get_current();

                        if (oListItem.get_item('PublishingRollupImage') != null) {
                            var URL = oListItem.get_item('FileRef');
                            var pageTitle = oListItem.get_item('Title');
                            var pageSummary = oListItem.get_item('RoutingRuleDescription');
                            var pageDate = oListItem.get_item('ArticleStartDate');

                            var pageImage = '';
                            pageImage = oListItem.get_item('PublishingRollupImage');
                            pageImage = pageImage.substr(pageImage.indexOf('src="') + 5, pageImage.length);
                            pageImage = pageImage.substr(0, pageImage.indexOf('"'));

                            //Push the new data to the data observable array 
                            self.sliderAllAnnouncments.push(new self.sliderAnnouncement(URL, pageTitle, pageImage, pageSummary, pageDate));
                        }
                    }


                    //Data are binded to the HTML DOM, Rejester the <ul> tag for jcarousel
                    $('.jcarousel')
                            .jcarousel({
                                wrap: 'circular'
                            })
                            .jcarouselAutoscroll({
                                interval: 3000,
                                target: '+=1',
                                autostart: true
                            })
                    ;
                }
            }),
            Function.createDelegate(this, function () {
                $(".loader").hide();
                alert(args.get_message());
            }));
    }
}

Here is the final result :

Happy SharePointing hope this article helps you.

No comments:

Post a Comment