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.

16 thoughts on “Fully Functional Likes in Search Results (SharePoint 2013)

  1. Are there any extra JavaScript/jquery files, besides the reputation.js, that have to be included somewhere in the master page or the display template? I’ve tried many different ways but this fails for me every time it gets to the $(span part.

    Like

  2. Thanks for sharing this. I have tried this out but get the following error:
    ‘getUserLikedPage’ is undefined (CoreRender: ~sitecollection/_catalogs/masterpage/Display Templates/Search/Item_TechToolsTest.js)

    Thoughts?

    Like

  3. Hi Santiago, I’m getting the same thing that Brian was getting. I have the js code in the master page but I think the problem is that it is getting loaded after the display template, so when the display template calls the function it isn’t there yet. I haven’t solved the issue as of yet. But thanks for the article, I’m getting close to getting it working.

    Like

  4. Same error with Brian and John as well 😦 ‘getUserLikedPage’ is undefined. I tried calling the JS on page layout and even on the display template itself, I still get the error. Still trying to figure it out.. but many thanks Santiago!

    Like

  5. I went to debug the function SetLike, it says “sender is undefined”. How do I create a variable for “sender”? Because on the onclick we define listID and itemID only? –>SetLike(‘_#= listID =#_’, ‘_#= itemID =#_’)

    Like

      • Hi Santiago thanks for replying 🙂 I tried to change the html to:

        <a onclick="SetLike((this),'_#= listID =#_', '_#= itemID =#_') so it passes the id to the sender for:

        function SetLike(sender, listID, itemID)

        However when I debug, listID value goes to sender, the itemID goes to listID, then itemID is undefined 😦

        What do you this should I put on the html code so it passes something to the sender on the SetLike javascript?

        Thank you very much!

        Liked by 1 person

      • Hi Karen, it surely is strange that you sender gets undefined since JavaScript should be passing the event object (without the “this” on the onclick), and I think that’s why you are getting the “this” on the ListID parameter of the SetLike function. Try changing the SetLike function to use $(this) instead of $(sender) and remove sender from the parameters to see if that will work for you.

        Like

    • I’ve finally gotten this all to work. I will try to help. My plan was to post a similar blog about this soon, but until then….
      I changed his html in the item template to accommodate the smiley face in natural SharePoint 2013, his code doesn’t do this, so keep that in mind. The main thing you need is the onclick=’SetLike(event.target, “_#= listID =#_” , “_#= itemID =#_”)’, the event target will be the sender.

      Likes:

      Like

Leave a Comment