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.

11 comments:

  1. Hi Mikael
    I almost got this to work. One simple question though. How do you create the HTML link to display that full star if its followed and empty star if its not?

    The code work just using a simple onclick="toggleFollow()". But I have the feeling that it needs a bit more than that to work proper.

    /Ulrich

    ReplyDelete
    Replies
    1. Hi,
      the "magic" happens with $('#favoriteLink').toggleClass("favIconSelected", this.result.get_value());

      which toggles a CSS class to switch from a yellow to white star if it's already followed. Basically changing the background image of that container.

      Delete
  2. Thanks for the nice info !!
    Just a quick query, can we follow a list item (consider an eg of following a Blog post) ??

    ReplyDelete
    Replies
    1. Hi,
      Yes you can. Look at the line "var docUrl = window.location.pro..." in the getUrl() function and you see it used the id of the list and the item to do a follow.

      The issue which isn't covered in the post is the matter of getting updates on the followed items in your feed. In theory you have to hook up an event receiver, but I haven't gotten it to work 100% in my tests yet and haven't had time to pursue it recently. I'll try and get the post out there with some findings.

      Delete
    2. I am getting internal error when i try to follow a sharepoint list item( Microsoft.Office.Server.Social.SPSocialException Internal error code 7) This is my docUrl now. (http://servername:portno/Blogname?List={8c320758-8270-42ef-a888-b977e0bc4aef}&itemId=1

      Delete
    3. Hi,
      I'll try to get time to test this. In the mean time, you may try to follow the dispform url instead, replacing the logic in getUrl(). The question remains though if you will get updates on changes once you have hooked the event receiver for follow updates to the list.

      Delete
  3. Hi,
    unfortunately it is not possible to follow list Items. This ist the error code result with code 7.

    Regards
    Martin

    ReplyDelete
    Replies
    1. Good to know, and I haven't tried much more on this either. Seems it works best on what is there oob.

      Delete
    2. Hi

      yes you are right, we cannot follow list items, blog post pages etc. this works fine for documents and pages, anything that uses 1 display or edit page and renders content according to the ID in URL cannot be followed.

      Delete
  4. Mikael,
    This is helpful. I wondered, is there a way to create 'follow' links next to a list of specific sites/people within a site collection? So essentially rather than getting the URL of the current page and following that, actually specifying a different URL to follow? Ideally based on the first (simpler!) method above. The idea is to allow someone to pick a whole load of things they want to follow from just one page, without having to go to each of the individual sites/user profiles. Thanks! Cat

    ReplyDelete
    Replies
    1. Hi,
      As mentioned in other comments, replace the logic of getUrl() and pass in whatever url you want. It could even be http://cnn.com, which would then show in the users list of followed pages.

      And for sites and people you want to change the set_actorType() line to the type you are following.

      Delete