Category Archives: SharePoint

SharePoint 2013 Hosted App (Part 5) – Chrome Control

SharePoint Hosted App series:

Following from my SharePoint Hosted App series, In this post I would like to show you how you could create chrome control to blend your app with the host site.

Without further ado, please find below the list of steps to create simple chrome control:

1. Create new Project using App for SharePoint template in Microsoft Visual Studio 2013, enter the Name and Location, then click Ok

chromecontrol1

2. In Specifying the app for SharePoint settings window, enter the Site Collection URL and select SharePoint-hosted, then click Finish

myfirstapp2

3. Right click Scripts module > Add > Existing Item to add sp.ui.controls.js (this is OOB javascript file located in C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\TEMPLATE\LAYOUTS)

chromecontrol4

4. Right click Pages module > Add > New Item to add html page

chromecontrol2

5. On New Item window, select Html Page and provide the name Home.html 

chromecontrol3

6. Populate Home.html to look like below

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Chrome Control sample</title>
    <script type="text/javascript" src="../Scripts/sp.ui.controls.js"></script>
</head>
<body>
    <div id="chrome_ctrl_container" data-ms-control="SP.UI.Controls.Navigation"
         data-ms-options='{
            "appHelpPageUrl" : "../Pages/Help.html",
            "appIconUrl" : "../Images/AppIcon.png",
            "appTitle" : "Chrome Control sample",
            "settingsLinks" : [
                {
                    "linkUrl" : "../Pages/AboutUs.html",
                    "displayName" : "About Us"
                },
                {
                    "linkUrl" : "../Pages/Services.html",
                    "displayName" : "Services"
                }
            ]
         }'></div>
    <div style="margin: 50px;">
        <h2>Home</h2>
        <p>This is the home page</p>
    </div>
</body>
</html>

7. Right click Pages folder > Add > New Item to add html

8. On New Item window, select Html Page and provide the name AboutUs.html

9. Populate AboutUs.html to look like below

<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>About Us</title>
    <script type="text/javascript" src="../Scripts/sp.ui.controls.js"></script>
</head>
<body>
    <div id="chrome_ctrl_container" data-ms-control="SP.UI.Controls.Navigation"
         data-ms-options='{
            "siteUrl" : "http://sp2013vm/",
            "appHelpPageUrl" : "../Pages/Help.html",
            "appIconUrl" : "../Images/AppIcon.png",
            "appTitle" : "Chrome Control sample",
            "settingsLinks" : [
                {
                    "linkUrl" : "../Pages/AboutUs.html",
                    "displayName" : "About Us"
                },
                {
                    "linkUrl" : "../Pages/Services.html",
                    "displayName" : "Services"
                }
            ]
         }'></div>
    <div style="margin: 50px;">
        <h2>About Us</h2>
        <p>About Us page</p>
    </div>
</body>
</html>

10. Repeat step 6 to 9 for adding Services.html and Help.html, don’t forget to modify the content of the page

11. Click AppManifest.xml and Set the Start page to ChromeControl\Pages\Home.aspx

chromecontrol5

12. Build the solution and Package the app

13. Browse to your App Catalog Site. Click new app to add new custom app, then enter the Name, Short Description, Long Description, checked Enabled and click Save

14. Browse to your Site Collection > Site Actions > Site Contents > Add an App > From your Organization > click Chrome Control > Add it to add the custom app to your SharePoint site

15. Click on Chrome Control App

chromecontrol6 chromecontrol7

I changed the look of the host site to Blossom, it will also change the looks of chrome control.

chromecontrol8 chromecontrol9 chromecontrol10

References

Advertisements

SharePoint 2013 Hosted App (Part 3) – Access list from SharePoint Hosted App

SharePoint Hosted App series:

Following from my SharePoint Hosted App series, In this post I would like to show you how you could access a list from your SharePoint Hosted App.

Without further ado, please find below the list of steps to add SharePoint Hosted App Part:

1. Continue from previous solution in Part 2, we need to provision the list. To do that, I created new site column

myfirstapp18.2

2. Create new content type

myfirstapp18.3

myfirstapp18.4

3. Create new list and assign the content type

myfirstapp18.6

myfirstapp18.7

myfirstapp18.8

4. Update Default.aspx to add drop down control to show all list items

<%-- The following 4 lines are ASP.NET directives needed when using SharePoint components --%>
<%@ Page Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" MasterPageFile="~masterurl/default.master" Language="VB" %>
<%@ Register TagPrefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, 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" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<%-- The markup and script in the following Content element will be placed in the <head> of the page --%>
<asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
    <script type="text/javascript" src="../Scripts/jquery-1.9.1.min.js"></script>
    <script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>
    <script type="text/javascript" src="/_layouts/15/sp.js"></script>
    <meta name="WebPartPageExpansion" content="full" />

    <!-- Add your CSS styles to the following file -->
    <link rel="Stylesheet" type="text/css" href="../Content/App.css" />
    <!-- Add your JavaScript to the following file -->
    <script type="text/javascript" src="../Scripts/App.js"></script>
</asp:Content>

<%-- The markup in the following Content element will be placed in the TitleArea of the page --%>
<asp:Content ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server">
    Page Title
</asp:Content>

<%-- The markup and script in the following Content element will be placed in the <body> of the page --%>
<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
    <div id="message">
        Hello ...
    </div>

    <div>
        <input id="clickMe" type="button" value="I can guess your name" />
    </div>

    <div id="myHomeTown">
    </div>
</asp:Content>

5. Update App.js to include all methods required

// This code runs when the DOM is ready and creates a context object which is needed to use the SharePoint object model
$(document).ready(function () {
    MyFirstApp = {
        element: '',
        loadUrl: '',
        createUrl: '',
        user: '',
        init: function (element) {
            var context = SP.ClientContext.get_current();
            var user = context.get_web().get_currentUser();
            context.load(user);

            MyFirstApp.element = element;
            MyFirstApp.loadUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getByTitle('Cities')/items?$select=Title,Country";
            MyFirstApp.createUrl = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getByTitle('Cities')/items";
            MyFirstApp.user = user;
        },
        load: function () {
            $.ajax({
                url: MyFirstApp.loadUrl,
                method: "GET",
                headers: { "Accept": "application/json; odata=verbose" },
                success: MyFirstApp.onLoadSuccess,
                error: MyFirstApp.onLoadError
            });
        },
        onLoadSuccess: function (data) {
            var results = data.d.results;
            var html = "My sweet home town: <select>";
            for (var i = 0; i < results.length ; i++) {
                html += "<option value='" + results[i].Title + "'>" + results[i].Title + ", " + results[i].Country + "</option>";
            }
            html += "</select>";
            MyFirstApp.element.html(html);
        },
        onLoadError: function (errMsg) {
            MyFirstApp.element.html(JSON.stringify(errMsg));
        },
        add: function (city, country) {
            var itemType = "SP.Data.CitiesListItem";
            var item = {
                "__metadata": { "type": itemType },
                "Title": city,
                "Country": country,
            };

            $.ajax({
                url: MyFirstApp.createUrl,
                type: "POST",
                contentType: "application/json;odata=verbose",
                data: JSON.stringify(item),
                headers: {
                    "Accept": "application/json;odata=verbose",
                    "X-RequestDigest": $("#__REQUESTDIGEST").val()
                },
                success: MyFirstApp.onAddSuccess,
                error: MyFirstApp.onAddError
            });
        },
        onAddSuccess: function () {

        },
        onAddError: function (errMsg) {
            MyFirstApp.element.html(JSON.stringify(errMsg));
        },
        getUserName: function () {
            var context = SP.ClientContext.get_current();
            var user = context.get_web().get_currentUser();
            context.load(user);
            context.executeQueryAsync(MyFirstApp.onGetUserNameSuccess, MyFirstApp.onGetUserNameError);
        },
        onGetUserNameSuccess: function () {
            $('#message').text('Hello ' + MyFirstApp.user.get_title());
        },
        onGetUserNameError: function (sender, args) {
            $('#message').text('Failed to get user name. Error:' + args.get_message());
        }
    }

    // initialization
    MyFirstApp.init($('#myHomeTown'));

    // attach clickMe onclick event
    $('#clickMe').onclick = MyFirstApp.getUserName();

    // add items into Cities list 
    MyFirstApp.add("Jakarta", "Indonesia");
    MyFirstApp.add("Melbourne", "Australia");
    MyFirstApp.add("Cleveland", "USA");
    MyFirstApp.add("Boston", "USA");

    // populate dropdown from Cities list
    MyFirstApp.load();
});

6. Update App.manifest to provide app Read permission to access the list

myfirstapp19.2

7. Rebuild the solution and Package the app

myfirstapp6

myfirstapp8

8. Remove existing MyFirstApp from your Site Collection > Site Actions > Site Contents

myfirstapp17

9. Update MyFirstApp.app from App Catalog site

myfirstapp9

10. Browse to your Site Collection > Site Actions > Site Contents > Add an App > From your Organization > click MyFirstApp to add the custom app to your SharePoint site

myfirstapp1011. Result

myfirstapp20

SharePoint 2013 Hosted App (Part 4) – Create App Part

Following from my previous post for SharePoint 2013 Hosted App (Part 2), In this post I would like to show you another entrance to your SharePoint App. SharePoint App can be added like a web part as well into web part zone.

SharePoint Hosted App series:

Without further ado, please find below the list of steps to add SharePoint Hosted App Part:

1. Continue from previous solution in Part 2, right click on the MyFirstApp project > Add > New Item … 

myfirstapp12

2. In New Item page, select Client Web Part (Host Web) and click Add

myfirstapp13

3. In Specify the client web part page window, select Create a new web page for the client web part content, enter Page Name and click Finish

myfirstapp13.2

4. Now the project should have additional MyFirstAppPart module with Elements.xml and MyFirstAppPart.aspx page. Update MyFirstAppPart.aspx page to have:

  • same body content as Default.aspx
  • App.js and App.css linked
<%@ Page language="C#" Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ 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" %>
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<WebPartPages:AllowFraming ID="AllowFraming" runat="server" />

<html>
<head>
    <title></title>

    <script type="text/javascript" src="../Scripts/jquery-1.9.1.min.js"></script>
    <script type="text/javascript" src="/_layouts/15/MicrosoftAjax.js"></script>
    <script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>
    <script type="text/javascript" src="/_layouts/15/sp.js"></script>

    <script type="text/javascript">
        // Set the style of the client web part page to be consistent with the host web.
        (function () {
            'use strict';

            var hostUrl = '';
            if (document.URL.indexOf('?') != -1) {
                var params = document.URL.split('?')[1].split('&');
                for (var i = 0; i < params.length; i++) {
                    var p = decodeURIComponent(params[i]);
                    if (/^SPHostUrl=/i.test(p)) {
                        hostUrl = p.split('=')[1];
                        document.write('<link rel="stylesheet" href="' + hostUrl + '/_layouts/15/defaultcss.ashx" />');
                        break;
                    }
                }
            }
            if (hostUrl == '') {
                document.write('<link rel="stylesheet" href="/_layouts/15/1033/styles/themable/corev15.css" />');
            }
        })();
    </script>
    <!-- Add your JavaScript to the following file -->
    <script type="text/javascript" src="../Scripts/App.js"></script>
    <!-- Add your CSS styles to the following file -->
    <link rel="Stylesheet" type="text/css" href="../Content/App.css" />
</head>
<body>
    <div id="message">
        Hello ...
    </div>

    <div>
        <input id="clickMe" type="button" value="I can guess your name" />
    </div>
</body>
</html>

5. Rebuild the solution

myfirstapp6

6. Publish the solution, then click Package the app

myfirstapp7

myfirstapp8

7. Browse to your App Catalog Site. Click new app to add new custom app, then enter the Name,Short Description, Long Description, checked Enabled and click Save

myfirstapp9

myfirstapp9.2

8. Remove existing MyFirstApp from your Site Collection > Site Actions > Site Contents

myfirstapp17

9. Browse to your Site Collection > Site Actions > Site Contents > Add an App > From your Organization > click MyFirstApp to add the custom app to your SharePoint site

myfirstapp10

10. Edit Home.aspx page to add Part and Save back the page

myfirstapp15

myfirstapp16

SharePoint 2013 Hosted App (Part 2) – Create simple SharePoint Hosted App

Its been a while since the last time I’ve created my post for SharePoint 2013 Hosted App (Part 1) and now I would like to continue the series.

SharePoint Hosted App series:

Without further ado, please find below the list of steps to create SharePoint Hosted App:

1. Create new Project using App for SharePoint template in Microsoft Visual Studio 2013, enter the Name and Location, then click Ok

myfirstapp1

2. In Specifying the app for SharePoint settings window, enter the Site Collection URL and select SharePoint-hosted, then click Finish

myfirstapp2

3. By default, the SharePoint App Project already has Default.aspx with App.js and App.css linked. I’ve updated the Default.aspx to have message div and a button

<%-- The following 4 lines are ASP.NET directives needed when using SharePoint components --%>
<%@ Page Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" MasterPageFile="~masterurl/default.master" Language="VB" %>
<%@ Register TagPrefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, 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" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

<%-- The markup and script in the following Content element will be placed in the <head> of the page --%>
<asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
    <script type="text/javascript" src="../Scripts/jquery-1.9.1.min.js"></script>
    <script type="text/javascript" src="/_layouts/15/sp.runtime.js"></script>
    <script type="text/javascript" src="/_layouts/15/sp.js"></script>
    <meta name="WebPartPageExpansion" content="full" />

    <!-- Add your CSS styles to the following file -->
    <link rel="Stylesheet" type="text/css" href="../Content/App.css" />
    <!-- Add your JavaScript to the following file -->
    <script type="text/javascript" src="../Scripts/App.js"></script>
</asp:Content>

<%-- The markup in the following Content element will be placed in the TitleArea of the page --%>
<asp:Content ContentPlaceHolderID="PlaceHolderPageTitleInTitleArea" runat="server">
    Page Title
</asp:Content>

<%-- The markup and script in the following Content element will be placed in the <body> of the page --%>
<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
    <div id="message">
        Hello ...
    </div>

    <div>
        <input id="clickMe" type="button" value="I can guess your name" />
    </div>
</asp:Content>

4. In App.js file, I’ve implemented clickMe_Click function

// This code runs when the DOM is ready and creates a context object which is needed to use the SharePoint object model
$(document).ready(function () {
    MyFirstApp = {
        user: '',
        init: function (element) {
            var context = SP.ClientContext.get_current();
            var user = context.get_web().get_currentUser();
            context.load(user);
            MyFirstApp.user = user;
        },
        getUserName: function () {
            var context = SP.ClientContext.get_current();
            var user = context.get_web().get_currentUser();
            context.load(user);
            context.executeQueryAsync(MyFirstApp.onGetUserNameSuccess, MyFirstApp.onGetUserNameError);
        },
        onGetUserNameSuccess: function () {
            $('#message').text('Hello ' + MyFirstApp.user.get_title());
        },
        onGetUserNameError: function (sender, args) {
            $('#message').text('Failed to get user name. Error:' + args.get_message());
        }
    }

    // initialization
    MyFirstApp.init($('#myHomeTown'));

    // attach clickMe onclick event
    $('#clickMe').onclick = MyFirstApp.getUserName();
});

5. In App.cs file, I’ve added bit of styling to message div

#message {
    font-family: Arial;
    font-size: 18px;
    font-weight: bold;
}

6. Rebuild the solution

myfirstapp6

7. Publish the solution, then click Package the app

myfirstapp7

myfirstapp8

8. Browse to your App Catalog Site. Click new app to add new custom app, then enter the Name, Short Description, Long Description, checked Enabled and click Save

myfirstapp9

myfirstapp9.2

9. Browse to your Site Collection > Site Actions > Site Contents > Add an App > From your Organization > click MyFirstApp > Add it to add the custom app to your SharePoint site

myfirstapp10

10. Click MyFirstApp from your Site Collection > Site Actions > Site Contents, it will launch your new custom app

myfirstapp11

myfirstapp11.2

Client Side Object Model (CSOM) only works in Page Edit Mode

Following my previous post with accessing SharePoint List items using REST API, I’m starting to use Client Side Object Model in the Script Editor Webpart. Then, I found another issue where my CSOM script is only works when the Page in Edit mode. Why would that be ? Why I found two strange things in a week ?

After analyzing and placing breakpoints to the script using developer tools F12, I’ve found that the Out of the Box SharePoint javascript sp.js is not yet loaded when my script executed. Then, I realize my CSOM script really need something to ensure sp.js loaded prior executing my CSOM script => SP.SOD.executeFunc.

Below is my updated code:

<script type="text/javascript">
var ctx;
var listTitle = "My list";
var list;
var items;

// Ensure 'sp.js' is loaded before your code runs.
SP.SOD.executeFunc('sp.js', 'SP.ClientContext', sharePointListReady);

// Create an instance of the current context.
function sharePointListReady() {
 ctx = SP.ClientContext.get_current();
 list = ctx.get_web().get_lists().getByTitle(listTitle);
 var query = new SP.CamlQuery();
 query.ViewXml = "<View><Query><Where><Eq><FieldRef Name='ID'/><Value Type='Counter'>1</Value></Eq></Where></Query></View>";
 items = list.getItems(query);
 ctx.load(items);
 ctx.executeQueryAsync(onRequestSucceeded, onRequestFailed);
}

function onRequestSucceeded() {
 var itemValues = items.getItemAtIndex(0).get_fieldValues();
 alert(itemValues.Title);
}

function onRequestFailed(sender, args) {
 alert('Error: ' + args.get_message());
}
</script>

It works ! great… But the code above returns all items =( it seems like the Caml Query not working. I’ve stuck for quite sometime googling and I found msdn article that use query.set_viewXml(query) to set the Caml Query instead of query.ViewXml. This time is really working.

Below is my final code:

<script type="text/javascript">
var ctx;
var listTitle = "My list";
var list;
var items;

// Ensure 'sp.js' is loaded before your code runs.
SP.SOD.executeFunc('sp.js', 'SP.ClientContext', sharePointListReady);

// Create an instance of the current context.
function sharePointListReady() {
 ctx = SP.ClientContext.get_current();
 list = ctx.get_web().get_lists().getByTitle(listTitle);
 var query = new SP.CamlQuery();
 query.set_viewXml("<View><Query><Where><Eq><FieldRef Name='ID'/><Value Type='Counter'>1</Value></Eq></Where></Query></View>");
 items = list.getItems(query);
 ctx.load(items);
 ctx.executeQueryAsync(onRequestSucceeded, onRequestFailed);
}

function onRequestSucceeded() {
 var itemValues = items.getItemAtIndex(0).get_fieldValues();
 alert(itemValues.Title);
}

function onRequestFailed(sender, args) {
 alert('Error: ' + args.get_message());
}
</script>

References

Retrieve Publishing Image column from SharePoint REST API

Recently, I utilized SharePoint REST API to retrieve list item and one of the column is Publishing Image. I’m half surprise and half confuse why the Publishing Image column is not returned by the REST API. Trying to create a new custom list with Out of the Box Publishing Image site column (Page Image) and I still cannot find the publishing image column from REST API call result.

http://siteurl/_api/web/lists/getbytitle(“listTitle&#8221;)/items?$filter=Active eq 1&$select=Title,PublishingPageImage

Quickly google it and found this post and I’m feeling devastated. Why would you want to make a second call just for the one column.

Below is the same code from the post and Thanks to Vadim Gremyachev

function getJson(endpointUri, success, error)
{
    $.ajax({
       url: endpointUri,
       type: "GET",
       processData: false,
       contentType: "application/json;odata=verbose",
       headers: {
          "Accept": "application/json;odata=verbose"
       },
       success: success,
       error: error
    });
}

function getPublishingPage(webUrl,listName,listItemId,publishingProperties, success, failure)
{
    var itemUri =  webUrl + "/_api/web/lists/getbytitle('" + listName + "')/items(" + listItemId + ")";
    getJson(itemUri,
       function(data){
           var pageItem = data.d;

           var selectProperties = [];
           for(var idx in publishingProperties){
               if(!pageItem.hasOwnProperty(publishingProperties[idx])){
                   selectProperties.push(publishingProperties[idx]);
               }
           }
           if(selectProperties.length > 0) {
              //construct an additional query
              var query = '/FieldValuesAsHtml?$select=' + selectProperties.join(',');
              var endpointUri = pageItem['__metadata'].uri + query;
              getJson(endpointUri,
                 function(data){
                    for(var property in data.d){
                       if(property == "__metadata") continue;
                       pageItem[property] = data.d[property];
                    }
                    success(pageItem);
                 },
                 failure);
           }
           else {
              success(pageItem);
           }
        },
       failure);
}

getPublishingPage(_spPageContextInfo.webAbsoluteUrl,'Pages',3,['PublishingRollupImage','PublishingPageImage'],printPageDetails,logError);

function printPageDetails(pageItem)
{
    console.log('Page Content: ' + pageItem.PublishingPageContent);
    console.log('Page Title: ' + pageItem.Title);
    console.log('Page Rollup Image ' + pageItem.PublishingRollupImage);
}

function logError(error){
    console.log(JSON.stringify(error));
}

References

http://stackoverflow.com/questions/25852997/retrieve-publishing-image-field-with-sharepoint-2013-rest-api-csom

Deploy SharePoint 2013 Display Template through Visual Studio

We create more and more display templates as soon as we utilize Content By Search Webpart or Search Result Webpart in SharePoint 2013. The recent project that I’ve done includes authoring (internal) site and publishing (external) site through Cross Site Publishing. Since the publishing site is customer facing / external site, we use display templates heavily and I think we have around 90 display templates.

In this article, I would like to show you how to deploy them through Visual Studio. Without further ado, I want to show you my very basic display template:

<html xmlns:mso="urn:schemas-microsoft-com:office:office" xmlns:msdt="uuid:C2F41010-65B3-11d1-A29F-00AA00C14882">
<head>
    <title>My Custom Item Template</title>

    <!--[if gte mso 9]><xml>
    <mso:CustomDocumentProperties>
    <mso:TemplateHidden msdt:dt="string">0</mso:TemplateHidden>
    <mso:ManagedPropertyMapping msdt:dt="string">'Link URL'{Link URL}:'Path','Line 1'{Line 1}:'Title', 'ListItemID':'ListItemID', 'owstaxIdCNPKeywords':'owstaxIdCNPKeywords'</mso:ManagedPropertyMapping>
    <mso:MasterPageDescription msdt:dt="string">This Item Display Template will show a small thumbnail icon next to a hyperlink of the item title, with an additional line that is available for a custom managed property.</mso:MasterPageDescription>
    <mso:ContentTypeId msdt:dt="string">0x0101002039C03B61C64EC4A04F5361F385106603</mso:ContentTypeId>
    <mso:TargetControlType msdt:dt="string">;#Content Web Parts;#</mso:TargetControlType>
    <mso:HtmlDesignAssociated msdt:dt="string">1</mso:HtmlDesignAssociated>
    <mso:CrawlerXSLFile msdt:dt="string">/_catalogs/masterpage/MyDisplayTemplates/Server_MyItem.xsl, /_catalogs/masterpage/MyDisplayTemplates/Server_MyItem.xsl</mso:CrawlerXSLFile>
    </mso:CustomDocumentProperties>
    </xml><![endif]-->
</head>

<body>
    <div>
        <!--#_
                var title = $getItemValue(ctx, "Line 1");
                var itemURL = $getItemValue(ctx, "Link URL");
        _#-->
        <a class="mydiv" data-item="_#= $htmlEncode(itemURL) =#_">_#= $htmlEncode(title) =#_</a>
    </div>
</body>
</html>

As you noticed above, I’ve specified CrawlerXSLFile property. Basically this is required for Search Engine Optimization, to allow search crawler to crawl content inside your Content By Search Webpart/ Search Result Webpart. You could read great post from Waldek here. This is same thing like we did when we would like to customize Content Query Webpart in SharePoint 2010.

<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform' xmlns:ddwrt='http://schemas.microsoft.com/WebParts/v2/DataView/runtime'>
	<xsl:template match='/'>
		<xsl:apply-templates />
	</xsl:template>

	<xsl:template match='ResultTable'>
		<xsl:apply-templates select='Rows'/>
	</xsl:template>

	<xsl:template match='Rows'>
		<xsl:apply-templates select='Row' />
	</xsl:template>

	<xsl:template match='Row'>
		<xsl:for-each select='*'>
			<xsl:if test="name(.) = 'title'">
			   <xsl:value-of select="." />
			</xsl:if>
		</xsl:for-each>
	</xsl:template>
</xsl:stylesheet>

Now, it come to the deployment part in Visual Studio. Below is the list of steps:
1. Open Visual Studio and Create SharePoint Empty Project as Farm Solution
2. Add New Module, called it DisplayTemplates
3. Add your .html and .xsl display templates into DisplayTemplates Module
4. Edit Elements.xml in DisplayTemplates module

<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="DisplayTemplates" Url="_catalogs/masterPage">
    <File Path="DisplayTemplates\MyItem.html" Url="MyDisplayTemplates/MyItem.html" ReplaceContent="True" Type="GhostableInLibrary" Level="Published"/>
    <File Path="DisplayTemplates\Server_MyItem.xsl" Url="MyDisplayTemplates/Server_MyItem.xsl" Type="GhostableInLibrary" Level="Published" ReplaceContent="TRUE"  />
  </Module>
</Elements>

5. Ensure Display Template Module included in your Feature

 DisplayTemplate1

6. Add Feature Receiver

using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint.Utilities;
using System;
using System.Runtime.InteropServices;
using System.Xml;

namespace DisplayTemplatesDemo.Features.Feature1 {
    /// <summary>
    /// This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade.
    /// </summary>
    /// <remarks>
    /// The GUID attached to this class may be used during packaging and should not be modified.
    /// </remarks>

    [Guid("4d50c166-1095-4d7c-a4c1-61b35687cbf5")]
    public class Feature1EventReceiver : SPFeatureReceiver {
        /// <summary>
        /// Feature activated, we just change the item title of display template to force SharePoint generates the associated .js file
        /// </summary>
        /// <param name="properties"></param>
        public override void FeatureActivated(SPFeatureReceiverProperties properties) 
        {
            try {
                var site = (SPSite)properties.Feature.Parent;
                var web = site.RootWeb;

                //get all display templates
                string allDisplayTemplates = getAllDisplayTemplates(site.RootWeb, properties.Definition);
                //get the master pages gallery
                var gallery = web.GetCatalog(SPListTemplateType.MasterPageCatalog);

                foreach (var displayTemplate in allDisplayTemplates.Split(new char[] {','}))
                {
                    var url = SPUtility.ConcatUrls(gallery.RootFolder.Url, "CNPGallery/Display Templates");
                    url = SPUtility.ConcatUrls(url, displayTemplate);
                    //get the file
                    var fileOrFolder = web.GetFileOrFolderObject(url);

                    if (fileOrFolder != null && fileOrFolder is SPFile) {
                        var file = (SPFile)fileOrFolder;
                        if (file.Exists) {
                            //determine if the gallery requires files to be checked out before editing
                            //if so, check this one out
                            if (gallery.ForceCheckout)
                                file.CheckOut();

                            //make a simple change
                            file.Item["Title"] = (file.Item.Title != null ? file.Item.Title : displayTemplate);
                            file.Item.Update();

                            //if check out required, check it in
                            if (gallery.ForceCheckout)
                                file.CheckIn(string.Empty);
                            //if the gallery has minor versioning enabled, publish a major
                            if (gallery.EnableMinorVersions)
                                file.Publish(string.Empty);
                            //if the gallery required approval, approve it
                            if (gallery.EnableModeration)
                                file.Approve(string.Empty);
                        }
                    }
                }
            }
            catch (Exception ex) {
                //deactivate the feature
                var site = (SPSite)properties.Feature.Parent;
                site.Features.Remove(properties.Feature.DefinitionId);

                SPUtility.TransferToErrorPage(ex.Message);
            }
        }

        private string getAllDisplayTemplates(SPWeb web, SPFeatureDefinition definition)
        {
            string result = string.Empty;
            try
            {
                string wpcatalogUrl = SPUrlUtility.CombineUrl(web.Url, "_catalogs/masterpage");

                // Get the Feature.xml for the feature
                XmlDocument featureXml = new XmlDocument();
                featureXml.LoadXml(definition.GetXmlDefinition(web.Locale).OuterXml);

                XmlNamespaceManager nsMgr = new XmlNamespaceManager(featureXml.NameTable);
                nsMgr.AddNamespace("sp", "http://schemas.microsoft.com/sharepoint/");

                // Get Location attribute of each ElementManifest inside ElementManifests inside the Feature
                foreach (XmlNode locationNode in featureXml.SelectNodes("/sp:Feature/sp:ElementManifests/sp:ElementFile/@Location", nsMgr))
                {
                    if (result != string.Empty)
                        result += ",";
                    result += locationNode.Value.Replace("Templates\\", string.Empty);
                }
            }
            catch(Exception ex)
            {
                result = string.Empty;
            }
            return result;
        }
    }
}

7. Finally the solution look like below

 DisplayTemplate2

8. Build and Deploy the solution

%d bloggers like this: