Monday, May 18, 2015

Automatically adding friendly Url to SharePoint Page according to its level/sub level

Friendly URLs

SharePoint 2013’s friendly URLs capability is extremely straightforward in that these URLs are links that correspond directly to a term within your organization or on a particular site or page as well as correspond to your organization’s navigation term set.
The .aspx ending is no longer required after site or page name as well as the default.aspx page can be dropped from the URL's reference entirely.
One of the most important point that would make you implement friendly Urls is the advantage of SEO, as now your Urls will not be like http://yourdoamain/en/MediaGallery/AlbumDetails.aspx?Id=3 , instead you will have  http://yourdoamain/en/MediaGallery/Albums/season-1, Which can highly indicates the content I'll be reading in this url

The issue

When you enable Managed Metadata Navigation, you could have this option "Create friendly URLs for new pages automatically", This will create a Term for each page and attach this term to the Page Urls, Till this point everything is wonderful .



This feature in action will behave in a strange way, which will give us a non desirable results, For example , let's assume having a Variation site collection with a News sub-site http://mySiteCollection/en/News, The default page for this sub-site is http://mySiteCollection/en/News/Pages/default.aspx.

Now we enabled the Managed Metadata Navigation to have Smart Urls, So the default page now would be connected to a Managed Navigation Term "News & Articles" , So the Url will be http://mySiteCollection/en/news-articles.

Add new Article Page under this sub-site let's say its title will be "Article 1", After the page creation you will find that the new page is attached to the parent navigation term



SharePoint adds new page terms to the root Term Set assigned for site navigation, no matter any  level the new page has been created.

The Solution

When creating a SharePoint page, A List Item Event Receiver could be attached to the sub-site pages library to check if the current sub-site default page is attached to a Navigation Term, If so the page event receiver will create a new Navigation Term under the default page term based on the page title as a child to maintain the same hierarchy.

So, In our example when creating a new page under the News sub-site, The event receiver will check the sub-site default page and will found it is attached to "News & Articles" Navigation term, Then the new page navigation term will be created under this term, The result will be http://mySiteCollection/en/news-articles/article-1

This solution is easy to apply but there are some challenges to keep in mind:

Page titles and special characters
As we are handling creating the new terms, We should handle the term friendly url segment the same way SharePoint handles it to avoid any broken urls, and to keep consistency


string formattedFriendlyUrlSegment = PageTitle.RemoveAccent();
friendlyUrlterm.FriendlyUrlSegment.Value = formattedFriendlyUrlSegment.Slugify();

//Removes white spaces
public static string RemoveAccent(this string txt)
{
 byte[] bytes = System.Text.Encoding.GetEncoding("Cyrillic").GetBytes(txt);
 return System.Text.Encoding.ASCII.GetString(bytes);
}

public static string Slugify(this string phrase)
{
 string str = phrase.RemoveAccent().ToLower();
 str = System.Text.RegularExpressions.Regex.Replace(str, @"[^a-z0-9\s-]", ""); // Remove all non valid chars          
 str = System.Text.RegularExpressions.Regex.Replace(str, @"\s+", " ").Trim(); // convert multiple spaces into one space  
 str = System.Text.RegularExpressions.Regex.Replace(str, @"\s", "-"); // //Replace spaces by dashes
 return str;
}

Variations 
In some cases we have we needed to maintain the same term for both variation labels (Source and target) for both pages, So our plan will be to create a Term, and for each variation language will create a navigation term label to translate the term

Full Code:
Here is the full code for this article 


///<summary>
/// Creates a Friendly URL for a Publishing Page
/// </summary>
/// <param name="contextWeb">SPWeb Object which contains Publishing Page</param>
/// <param name="parentName">Parent Navigation Term</param>
/// <param name="page">Publishing Page for which the Friendly URL is added</param>
/// <param name="friendlyName">Friendly Name for the Page</param>
/// <param name="AddToNavigation">Flag to indicate whether to add this Navigation Term to Quick Launch and Top Naviagation</param>
/// <returns></returns>
public static string AddFriendlyUrl(SPWeb contextWeb, PublishingPage page, bool AddToNavigation)
{
 return AddFriendlyUrl(contextWeb, page, AddToNavigation, GetWebDefaultPageTerm(contextWeb));
}

public static string AddFriendlyUrl(SPWeb contextWeb, PublishingPage page, bool AddToNavigation, NavigationTerm ParentNavigationTerm)
{
 string relativeUrl = string.Empty;
 SPSecurity.RunWithElevatedPrivileges(() =>
 {
  try
  {
   IList<NavigationTerm> terms = TaxonomyNavigation.GetFriendlyUrlsForListItem(page.ListItem, true);

   // create Taxonomy Session for the context web
   TaxonomySession taxonomySession = TaxonomyNavigation.CreateTaxonomySessionForEdit(contextWeb);

   //Page is already attached to a navigation term / Page Updated
   if (terms.Count > 0)
   {
    //already associated to Metadata navigation
    NavigationTerm friendlyUrlterm = terms[0].GetAsEditable(taxonomySession);
    Term taxonomyTerm = friendlyUrlterm.GetTaxonomyTerm(taxonomySession);

    //for variation taxonomy terms
    if (!taxonomyTerm.IsSourceTerm)
    {
     friendlyUrlterm.FriendlyUrlSegment.UsesDefaultValue = false;
     taxonomyTerm = taxonomyTerm.SourceTerm;
    }


    bool labelUpdated = false;
    //Iterate through all labels for term translation  
    foreach (Microsoft.SharePoint.Taxonomy.Label label in taxonomyTerm.Labels)
    {
     //Update the label that suites the cuurent web, i.e if current web is en-US then this will update the English Value
     //if this web is ar-SA this will update the Arabic label value
     if (label.Language == contextWeb.Language)
     {
      label.Value = page.Title;
      labelUpdated = true;
     }
     
     //Always build the FriendlyUrlSegment from the English label
     if (label.Language == 1033)
     {
      string formattedFriendlyUrlSegment = label.Value.RemoveAccent();
      friendlyUrlterm.FriendlyUrlSegment.Value = formattedFriendlyUrlSegment.Slugify();
     }
    }

    //Create the language label if it doesn't exist
    if (!labelUpdated)
    {
     taxonomyTerm.CreateLabel(page.Title, (int)contextWeb.Language, false);
    }

    taxonomyTerm.TermStore.CommitAll();
   }
   else
   {
    //New Page 
    if (page.PublishingWeb.Label.IsSource)
    {
     // get the current Navigation Term Set
     NavigationTermSet currNavTermSet = TaxonomyNavigation.GetTermSetForWeb(contextWeb, StandardNavigationProviderNames.GlobalNavigationTaxonomyProvider, true);

     // Make the Term Set Editable
     NavigationTermSet editableTermSet = currNavTermSet.GetAsEditable(taxonomySession);

     // Get the Parent Term using the Parent Name Parameter matching the term Label
     NavigationTerm defaultPageFriendlyUrlterm = ParentNavigationTerm;

     // if a matching parent Term exists get a refernce to it
     if (defaultPageFriendlyUrlterm != null && !String.IsNullOrEmpty(page.Title))
     {
      // make the parent Term editable
      NavigationTerm editNewTerm = defaultPageFriendlyUrlterm.GetAsEditable(taxonomySession);

      // create the new FriendlyUrl along with the new Navigation Term
      relativeUrl = page.AddFriendlyUrl(page.Title, editNewTerm, AddToNavigation);
     }
    }
   }

   if (page.ListItem[new Guid(Constant.PublishingIsFurlPageId)] != null && !Boolean.Parse(page.ListItem[new Guid(Constant.PublishingIsFurlPageId)].ToString()))
   {
    page.ListItem[new Guid(Constant.PublishingIsFurlPageId)] = true;
    page.ListItem.SystemUpdate();
   }

  }
  catch (Exception ex)
  {
   Logger.LogException(ex);
   Logger.LogException(new Exception("StackTrace : " + ex.StackTrace));
   throw ex;
  }
 });
 return relativeUrl;
}

public static NavigationTerm GetWebDefaultPageTerm(SPWeb web)
{
 //list for saving the urls
 NavigationTerm friendlyUrlterm = null;

 //check if the current web is a publishing weg
 if (PublishingWeb.IsPublishingWeb(web))
 {
  PublishingWeb publishingWeb = PublishingWeb.GetPublishingWeb(web);
  SPListItem DefaultPage = publishingWeb.DefaultPage.Item;

  //get the pages list id
  Guid listId = PublishingWeb.GetPagesListId(web);

  //retrieve the pages list
  SPList pagesList = web.Lists[listId];


  //retrieve the terms used for the navigation (this can be multiple terms)
  IList<NavigationTerm> terms = TaxonomyNavigation.GetFriendlyUrlsForListItem(DefaultPage, true);

  string url = string.Empty;

  //check if the pages has terms associated with it
  if (terms.Count > 0)
  {
   //use the GetResolvedDisplayUrl to retrieve the page friendly urls
   friendlyUrlterm = terms[0];
  }

 }

 return friendlyUrlterm;
}


Hope you found this article useful :) 

Wednesday, February 18, 2015

Deploying Managed Metadata Fields declaratively in SharePoint - Tips and Tricks

In this article, we will see how we can deploy Managed metadata fields in a declarative way in SharePoint 

A managed metadata column lets you control the information that people can enter into a column. Users select the terms or phrases that they enter in the column from a pre-defined set of managed terms.

The challenge comes when you want to provision your custom managed metadata navigation field in a declarative way, and adding this column to a content type or a list instance without losing all the powerful features that comes along using this field type.

Out of the Box, if you added a managed column to your list SharePoint performs some actions behind the scene:
  1. Creates a hidden note field attached to your column
  2. Attached your list to two event receivers to update the taxonomy hidden list .
  3. Fills in some properties related to the current web and taxonomy list id
  4. Creates a taxonomy search managed  property after a full crawl that could be used as a refiner or even as a category column if you are planning to use the Product Catalog model.
So there are a lot of articles explaining how to deploy Managed Metadata Fields declarative in SharePoint, but they are scattered :) , so I decided to aggregate all the tips and tricks to cover this task.

  • Taxonomy Field  declarative XML, no tricks here :)

<Field ID="{20A3C69E-FFFB-43F4-BBDF-2D22BAF0EB84}"
         Type="TaxonomyFieldType"
         DisplayName="$Resources:myResourceFile,EventType"
         ShowField="Term1033"
         Required="TRUE"
         EnforceUniqueValues="FALSE"
         Group="Custom"
         StaticName="CustomEventType"
         Name="CustomEventType"
         Filterable="TRUE"
         Sortable="TRUE" />


  • SharePoint creates a hidden note field to be field with selected values while filling the taxonomy field, here comes the tricky part, while adding your custom field to the list you MUST add this hidden field along with it, The Display name should follow the following convention 'TaxonomyField_0' and the static name should follow the following convention 'TaxononmyFieldTaxHTField0', without following this conventions SharePoint search will not generate the crawled property for your custom column 'ows_taxId_CustomEventType', and for sure will not create the managed property 'owstaxidKataraEventType' 

<Field ID="{09F37A61-50FE-413E-941F-3BEE2A1B5BF8}"
         Name="CustomEventTypeTaxHTField0"
         StaticName="KataraEventTypeTaxHTField0"
         SourceID="http://schemas.microsoft.com/sharepoint/v3/fields"
         Type="Note"
         DisplayName="CustomEventType_0"
         Group="Katara"
         Hidden="TRUE" />
  • After declaring the schema of both field you should get them connected together, and should attach the created column to a specific Term Set and specific Term to choose from as a source, this could be achived by adding a feature event receiver using the following code snippet.

SPSite site = properties.Feature.Parent as SPSite;
Guid eventFieldId = new Guid("{20A3C69E-FFFB-43F4-BBDF-2D22BAF0EB84}");

if (site.RootWeb.Fields.Contains(eventFieldId))
{
 TaxonomySession session = new TaxonomySession(site);
 if (session.TermStores.Count != 0)
 {
  var termStore = session.TermStores["Managed Metadata Serivce"];
  var group = termStore.Groups["YOUR GROUP NAME"];
  var termSet = group.TermSets["YOUR TERM SET NAME"];

  var eventTypeTerm = termSet.Terms["THE TERM NAME CONTAINING YOUR VALUE"];

  TaxonomyField eventField = site.RootWeb.Fields[eventFieldId] as TaxonomyField;
  
  //Attach the note field to the taxonomy field
  eventField.TextField = new Guid("{09F37A61-50FE-413E-941F-3BEE2A1B5BF8}"); 

  // Connect to MMS
  eventField.SspId = termSet.TermStore.Id;
  eventField.TermSetId = termSet.Id;
  eventField.TargetTemplate = string.Empty;
  eventField.AnchorId = eventTypeTerm.Id;
  eventField.LookupWebId = site.RootWeb.ID;
  
  if (eventField.TypeAsString == "TaxonomyFieldTypeMulti")
   ageGroupField.AllowMultipleValues = true;
   
  eventField.Update();
 }
}
  • In this stage we have our field provisioned and added to our site as a site column, the next step is to add it to a content type, the trick here is we must add two hidden fields 'TaxCatchAll' & 'TaxCatchAllLabel' fields,If it doesn’t, then you won’t get facets showing up correctly in faceted search. Note that not having the TaxCatchAll and TaxCatchAllLabel pair of columns in your list or library or content type can cause that - See more at: http://magenic.com/BlogArchive/CorrectlyProvisioningManagedMetadataTaxonomyF#sthash.hlkj6Xqo.dpuf
  •  so our field references will be like the following 

<FieldRef ID="{20A3C69E-FFFB-43F4-BBDF-2D22BAF0EB84}" DisplayName="$Resources:FILENAME_Columns,EventType;" Required="TRUE" Name="CustomEventType" Filterable="TRUE" Sortable="TRUE" />
<FieldRef ID="{09F37A61-50FE-413E-941F-3BEE2A1B5BF8}" DisplayName="CustomEventType_0" Hidden="TRUE" Name="CustomEventTypeTaxHTField0" />
<FieldRef ID="{f3b0adf9-c1a2-4b02-920d-943fba4b3611}" DisplayName="Taxonomy Catch All Column" Required="FALSE" Hidden="TRUE" Name="TaxCatchAll" Sealed="TRUE" Sortable="FALSE" />
<FieldRef ID="{8f6b6dd8-9357-4019-8172-966fcd502ed2}" DisplayName="Taxonomy Catch All Column1" Required="FALSE" Hidden="TRUE" Name="TaxCatchAllLabel" ReadOnly="TRUE" Sealed="TRUE" Sortable="FALSE" />
  •  Now we come to the next trick, referencing those columns to a list template in the schema.xml file , the trick here is to declare the TaxHiddenList  'TaxCatchAll' & 'TaxCatchAllLabel' correctly to the the list schema, those fields are a lookup columns so they need the information list (source), following is the definetion, NOTE the List attribute. 

<Field Type="LookupMulti" DisplayName="Taxonomy Catch All Column" StaticName="TaxCatchAll" Name="TaxCatchAll" ID="{f3b0adf9-c1a2-4b02-920d-943fba4b3611}" ShowInViewForms="FALSE" List="Lists/TaxonomyHiddenList" Required="FALSE" Hidden="TRUE" CanToggleHidden="TRUE" ShowField="CatchAllData" SourceID="{1e46f7fe-3764-40b5-abd1-1746c716214b}" Mult="TRUE" Sortable="FALSE" AllowDeletion="TRUE" Sealed="TRUE" Version="2" />
<Field Type="LookupMulti" DisplayName="Taxonomy Catch All Column1" StaticName="TaxCatchAllLabel" Name="TaxCatchAllLabel" ID="{8f6b6dd8-9357-4019-8172-966fcd502ed2}" ShowInViewForms="FALSE" List="Lists/TaxonomyHiddenList" Required="FALSE" Hidden="TRUE" CanToggleHidden="TRUE" ShowField="CatchAllDataLabel" FieldRef="{F3B0ADF9-C1A2-4b02-920D-943FBA4B3611}" SourceID="{1e46f7fe-3764-40b5-abd1-1746c716214b}" ReadOnly="TRUE" Mult="TRUE" Sortable="FALSE" AllowDeletion="TRUE" Sealed="TRUE" Version="2" />

  • Finally, we need to attache the "TaxonomyItemSynchronousAddedEventReceiver" & "TaxonomyItemUpdatingEventReceiver" to update all the hidden fields.

<Receiver>
  <Name>TaxonomyItemSynchronousAddedEventReceiver</Name>
  <Type>ItemAdding</Type>
  <Assembly>Microsoft.SharePoint.Taxonomy, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
  <Class>Microsoft.SharePoint.Taxonomy.TaxonomyItemEventReceiver</Class>
  <SequenceNumber>10000</SequenceNumber>
</Receiver>
<Receiver>
  <Name>TaxonomyItemUpdatingEventReceiver</Name>
  <Type>ItemUpdating</Type>
  <Assembly>Microsoft.SharePoint.Taxonomy, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c</Assembly>
  <Class>Microsoft.SharePoint.Taxonomy.TaxonomyItemEventReceiver</Class>
  <SequenceNumber>10000</SequenceNumber>
</Receiver>

Now you are all set to enjoy many many features and ideas that this colum type offers :)

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.