Wednesday, March 27, 2013

Following or favorite pages in SharePoint 2013 using JavaScript

Recently I was tasked with a project requirement that the user should be able to add pages to a favorite list. To me this sounds much like using the new following feature of SharePoint 2013 which allows you to follow documents, sites, people and tags. And the difference between a document and a page in SharePoint is more semantic than technical. Also, we don’t want the user to navigate to the pages library in order to follow the page, but do it with a link/icon on the page itself. That said, the hover panel “Follow” link does not appear by default for Pages libraries either :)

Instead of creating a custom list to hold a users favorites we decided to use the built in social following functionality using JavaScript CSOM.

So, how do you go about following a page? There are at least a couple of ways. One, write your own custom CSOM script, or two, tap into what SharePoint uses when you click “Follow” on a document or a site.
image

I’ll start with the latter. When you click “Follow” in SharePoint 2013, what happens is that followingcommon.js is loaded. This script defines several functions on the window object, where the main one is SetFollowStatus with several overloads. The signature of SetFollowStatis is as follows:

window.SetFollowStatus = function(sUrl, bFollow, bIsDoc, successCallback, failureCallback) {
...
}

In order to follow a page in SharePoint, simply add the following via a .js file or on your page via a content editor web part. You also may want to trigger followPage from a click, and not by default when the page opens. By removing followPage() from the SP.SOD statement, you can call it yourself at will and still be sure that followingcommon.js is loaded.

function getUrl() {
    return window.location.protocol + "//" + window.location.host + window.location.pathname;
}

function followPage() {
    window.SetFollowStatus(getUrl(), true, true);
}

SP.SOD.executeFunc('followingcommon.js', null, function () {
    followPage();
});

In this sample I have not specified any callbacks as a message will show on the screen letting you now that the page is now followed, or already followed.

The drawback with re-using SharePoint’s internal script is that it won’t show if a page is already followed or not. It will merely try to follow the page, and alert you to if it’s now followed or already followed. If you then want to add a page link to un-follow the page, you would either have to add custom scripts or provide two links where you change the boolean parameter for following or not. Also note the use of SP.SOD.executeFunc which will make sure followingcommon.js is loaded before my followPage function is called.

The second approach writes all code from scratch using SocialFollowingManager directly (MSDN Reference). The code below have methods to check if the page is already followed, to follow a page, and to stop following a page. There is also a toggle helper function to easily allow follow or un-follow, more like adding the page as a favorite or not. The custom code approach is what we use in the project and we use a star icon to highlight the toggling.

image


A special note should be taken to read the getUrl() function. This uses built-in accessible variables from _spPageContextInfo which I wrote about here to build an item url on the form:

http://site/Pages/?listId=&itemId=

I discovered this when looking at the built-in following scripts. You may also pass the full url to a document and it will work equally well. The code should be easy to follow and commented throughout.

// variable to hold the following status of the page
var pageIsFollowed;

// Get the url of the current page
function getUrl() {
    var ctxCurrent = SP.ClientContext.get_current();
    var url = ctxCurrent.get_url();
    var docUrl = window.location.protocol + "//" + window.location.host + url + "?listId=" + _spPageContextInfo.pageListId + "&itemId=" + _spPageContextInfo.pageItemId;
    return docUrl;
}

// Check if the current page is already followed
function isAlreadyFollowed() {
    var clientContext = SP.ClientContext.get_current();
    var socialManager = new SP.Social.SocialFollowingManager(clientContext);
    var socialActor = new SP.Social.SocialActorInfo();
    socialActor.set_contentUri(getUrl());
    socialActor.set_actorType(SP.Social.SocialActorTypes.documents);

    this.result = socialManager.isFollowed(socialActor);
    clientContext.executeQueryAsync(Function.createDelegate(this, this.onCheckFollowSucceeded), Function.createDelegate(this, this.onQueryFailed));
}

// Toggle the star if followed or not using jQuery
function onCheckFollowSucceeded() {
    $('#favoriteLink').toggleClass("favIconSelected", this.result.get_value());
    // set global variable
    pageIsFollowed = this.result.get_value();
}

// Alert error
function onQueryFailed(sender, args) {
    alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
}

// Follow the page
function startFollowing() {
    var clientContext = SP.ClientContext.get_current();
    var socialManager = new SP.Social.SocialFollowingManager(clientContext);
    var siteActorInfo = new SP.Social.SocialActorInfo();
    siteActorInfo.set_contentUri(getUrl());
    siteActorInfo.set_actorType(SP.Social.SocialActorTypes.documents);
    // follow call
    socialManager.follow(siteActorInfo);
    // upon success, we reexecute isAlreadyFollowed to make sure it was followed - a bit lazy :)
    clientContext.executeQueryAsync(Function.createDelegate(this, this.isAlreadyFollowed), Function.createDelegate(this, this.onQueryFailed));
}

// Stop follow the page
function stopFollowing() {
    var clientContext = SP.ClientContext.get_current();
    var socialManager = new SP.Social.SocialFollowingManager(clientContext);
    var siteActorInfo = new SP.Social.SocialActorInfo();
    siteActorInfo.set_contentUri(getUrl());
    siteActorInfo.set_actorType(SP.Social.SocialActorTypes.documents);
    // stop following call
    socialManager.stopFollowing(siteActorInfo);
    // upon success, we reexecute isAlreadyFollowed to make sure it was stopped - a bit lazy :)
    clientContext.executeQueryAsync(Function.createDelegate(this, this.isAlreadyFollowed), Function.createDelegate(this, this.onQueryFailed));
}

// function attached to the click event of the star
function toggleFollow() {
    if (!pageIsFollowed) {
        startFollowing();
    } else {
        stopFollowing();
    }
}

// on load, check if page is followed or not and toggle star accordingly
$(document).ready(function () {
    // Make sure SP.ClientContext is available
    SP.SOD.executeFunc('sp.js', 'SP.ClientContext', function () {
        // Make sure SocialFollowingManager is available
        SP.SOD.executeFunc('userprofile', 'SP.Social.SocialFollowingManager', function () {
            isAlreadyFollowed();
        });
    });
});

That’s all there is to it to follow an item in SharePoint Smile There are of course some issues as well, which I will cover in a later post.