Fully Functional Likes in Search Results (SharePoint 2013)

I’ve been struggling with this way too long… But in the end and after a whole lot of googling I was able to make this work as magic (that is with JavaScript/jQuery). What you’ll need to do is create a new Display Template for your search, add some managed properties to it and put some magical JS code. I will assume you know how to create a new Display Template, but if not you can search on google for it or you can read this blog series by Microsoft’s own Bella Engen. So let’s get started.

Display Template

For your display template you’ll have to add two managed properties. The ones that you will be needing are the List ID and the List Item ID, you can add LikesCount as well but the main problem with it is that if you show that property it wouldn’t be updated until your next crawl which kind of ruins the whole thing since if you just unliked the item you might want that to show on other user’s searches as well, so retrieving that value in real-time with JavaScript would be your best bet. Back to business, in my case those properties where already there and mapped (I think they might be by default), but if in your case the managed properties are not created go ahead and create them. Here’s how those properties looked:

Managed Properties

Look at the ListID and ListItemID Managed Properties

So from there you have to add those two properties to the Display Template as follows:


<mso:ManagedPropertyMapping msdt:dt="string">'ListItemID':'ListItemID','ListID':'ListID','Title':'Title','Path':'Path','Description':'Description','EditorOWSUSER':'EditorOWSUSER','LastModifiedTime':'LastModifiedTime','CollapsingStatus':'CollapsingStatus','DocId':'DocId','HitHighlightedSummary':'HitHighlightedSummary','HitHighlightedProperties':'HitHighlightedProperties','FileExtension':'FileExtension','ViewsLifeTime':'ViewsLifeTime','ParentLink':'ParentLink','FileType':'FileType','IsContainer':'IsContainer','SecondaryFileExtension':'SecondaryFileExtension','DisplayAuthor':'DisplayAuthor','Likes Count'{Likes Count}:'LikesCount'</mso:ManagedPropertyMapping>

As you can see on that line I also left the LikesCount added, but you don’t really need it. After having that let’s leave the Display Template for a second and move on to…

The JavaScript

I’ve made this on a separate JS file which I’ve added to the Search Results page. Here I’ve made several functions to handle the likes. The first one would be the click for the “Like/Unlike” link, but you wouldn’t understand that function until you see the one that goes and checks if the user had already liked the item, for this I have to thank Simon Pedersen for his post where he has a similar function to the one I was working on (and yes, at this point I’m wondering if I’m a programmer or just a good googler). So anyway, here’s the function I’ve made:


function getUserLikedPage(listID, itemID) {
  var didUserLiked = false;
  var context = new SP.ClientContext(_spPageContextInfo.webServerRelativeUrl);
  var list = context.get_web().get_lists().getById(listID);
  var item = list.getItemById(itemID);
  var likes, success = false;

  context.load(item, "LikedBy", "ID", "LikesCount");
  context.executeQueryAsync(Function.createDelegate(this, function (success) {
    // Check if the user id of the current users is in the collection LikedBy.
    var usersLiked = item.get_item('LikedBy');
    likes = item.get_item('LikesCount');
    if (!SP.ScriptHelpers.isNullOrUndefined(usersLiked)) {
      for (var i = 0; i < usersLiked.length; i++) {
        var user = usersLiked[i];
        if (user.$1E_1 === _spPageContextInfo.userId) {
          didUserLiked = true;
        }
      }
    }
    var span = $('span.MyLikes[listid=' + listID + '][itemid=' + itemID +']');
    span.data('userliked',didUserLiked);
    if(didUserLiked)
      span.next().text("Unlike");
    else
      span.next().text("Like");
    span.text(likes);
  }),
    Function.createDelegate(this, function (sender, args) {
      //Custom error handling if needed
      console.log('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
  }));
}

At the end this function does a lot of things, some are self-explanatory, but some I need to explain you. What I’ve decided to do is to set a data attribute on the element that holds the number of likes so that I can get the value from there whenever and wherever I need it, that’s done by the

span.data('userliked',didUserLiked);

line, and then to get that value you just need to do the following

span.data('userliked');

and be done with it. The other thing I think I need to explain is that I’m changing the value of the Text for the like/unlike here instead of doing it from the Display Template because it needs to be done asynchronously and as the display template goes item by item it won’t wait until you get the result, so this will be updated via JavaScript on my end.

Now I can show you the other two functions which could be just one, but I just felt like splitting them…


function SetLike(sender, listID, itemID){
  var didUserLiked = $(sender).prev().data('userliked');
  setActualLike(listID, itemID, didUserLiked);
  return false;
}

function setActualLike(listID, itemID, didUserLiked) {
  var span = $('span.MyLikes[listid=' + listID + '][itemid=' + itemID +']');
  var countLikes = span.text();
  var aContextObject = new SP.ClientContext();
  EnsureScriptFunc('reputation.js', 'Microsoft.Office.Server.ReputationModel.Reputation', function() {
    Microsoft.Office.Server.ReputationModel.Reputation.setLike(aContextObject, listID.substring(1, 37), itemID, !didUserLiked);
    aContextObject.executeQueryAsync(function() {
      if(didUserLiked) {
        countLikes--;
        span.next().text("Like");
      } else {
        countLikes++;
        span.next().text("Unlike");
      }
      span.text(countLikes);
      span.data('userliked',!didUserLiked);
    }, function(sender, args) {
      // Custom error handling if needed

    });
  });
}

That concludes the JavaScript part. So…

Let’s go back to the Display Template

Now we need to do make this whole thing work… So we need to call the functions when needed and pass them the values of the ListID and ListItemID managed properties. So let’s start with the JavaScript part of the display template:


var listID = $getItemValue(ctx, "ListID").value;

var itemID = $getItemValue(ctx, "ListItemID").value;

if(listID != null && itemID != null)

getUserLikedPage(listID, itemID);

This will get you the ListID and ListItemID properties on two handy javascript variables and if they are not null they will be sent to the getUserLikedPage from the separate JS file. Then we need to change the HTML part so that you can display the things that you want displayed, here’s that code:


<div id="_#= $htmlEncode(itemId) =#_" name="Item" data-displaytemplate="My_DisplayItem" class="ms-srch-item" onmouseover="_#= ctx.currentItem_ShowHoverPanelCallback =#_" onmouseout="_#= ctx.currentItem_HideHoverPanelCallback =#_">
  _#=ctx.RenderBody(ctx)=#_
  <div>Likes: <span class="MyLikes" ListID="_#= listID =#_" ItemID="_#= itemID =#_"></span> <a onclick="SetLike('_#= listID =#_', '_#= itemID =#_')"></a></div>

  <div id="_#= $htmlEncode(hoverId) =#_" class="ms-srch-hover-outerContainer"></div>
</div>

As you can see I’ve also added the ListID and ListItemID properties to the span as attributes to be able to find that easily with my jQuery.

With this code you should be getting your likes on the search results and being able to Like or Unlike from those same results (if you have set the Display Template correctly and activated the Rating features of the list).

And here’s how the end result looks like on my end:

End Result

If you have any questions feel free to ask and I’ll try to answer them below.