Roland Oldengarm - Independent IT Contractor

Living in the coolest little capital Wellington, New Zealand!

How to get a Yammer Like counter on your page

We have an Office 365 portal and our client wanted to have a Yammer Like button. Alright, that’s easy! Add the Yammer Embed Like and we are done.

The client came back and said: Ah that looks good, but could you also please add the number of likes?
And there is where this blog post is about: Because it’s impossible to get the number of likes, created by the Yammer Like Embed. I have implemented a solution, without a custom database, and is working like a charm. It takes some time though…

There is one solution when you Google for this problem: Record the likes/unlikes in a custom database / list. This has a big drawback that it may not be up-to-date. If somebody would like it from Yammer, it would not be visible in our Office 365 page.

Secondly, I found out that the likes created by the Yammer Embed Like is creating Open Graph objects in Yammer. According to the documentation they should be visible in Yammer as well, e.g. in the “All” feed. However, I have tested this a lot, and could not see my likes anywhere. So, this defeats the purpose of using Yammer, if it’s visible nowhere that somebody liked a post.

My solution

  1. Whenever a user visits the page, the code verifies there is a discussion for that page. If not, a discussion is created, using the administrators token. So, every page will have a “Main” discussion created by the administrator.
  2. A custom like / unlike button is created that will like / unlike this discussion.
  3. The like counter queries the number of likes for that discussion.

It has the advantage that there is more activity in Yammer, every news page in Office 365 will have a discussion in Yammer.

Components

  • Web API to communicate with Yammer via REST.
  • JavaScript in SharePoint that communicates with this Web API.

Web API methods

I had to use Web API, to be able to authenticate to Yammer as the admin, and to impersonate.  Please refer to my blog post how to embed a Yammer poll for more information as well. I also had to enable CORS, because my Office 365 site is obviously in a different domain. That’s quite easy, just decorate your Web API controller with:


[EnableCors(origins: "*", headers: "*", methods: "*")]

Routing

I have implemented all of this in the Provider Hosted app created in my Yammer poll. So, it was a mix of regular MVC and Web API. I had some issues with the Web API routing, and found out after talking to a colleague that the Web API routing must be configured first:


protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}

Web API methods

I have created the following methods in my Web API controller:


public string StartDiscussion([FromBody]YammerMessage message)

public int GetMessageId(int groupId, string url)

public int LikedMessage(int id)

public bool GetLikeStatus(int id, string email)

public string LikeMessage([FromBody] LikeMessageRequest request)

public class YammerMessage
{
public int groupId { get; set; }
public string url { get; set; }
public string title { get; set; }
public string description { get; set; }
}

public class LikeMessageRequest
{
public int id { get; set; }
public string email { get; set; }
public bool unlike { get; set; }
}

The flow is as follows:

  1. A user visits the page, GetMessageId is called to retrieve the Yammer Message ID for the current URL. If this returns -1, it is either a new page, or a page that was created earlier, StartDiscussion is called to start the “admin” discussion.
  2. With this ID, LikedMessage is called to retrieve the number of likes  & GetLikeStatus will be invoked to see if the current user liked the page or not.
  3. A like/unlike button is added to the HTML. When this button is clicked, LikeMessage is called. It returns the HttpResponse from the request to Yammer, which should be empty.

See at the end of the post for all JavaScript and C# code.

Be careful

Just a reminder that I’m using undocumented REST API calls to retrieve messages in a group, and to like a Yammer conversation. Although unlikely, Yammer may change these methods without notice, breaking this code. We decided to take this risk.

The source code

Javascript

// window.YammerConfig should contain an object with all required variables, e.g. network name, news group ID, etc.
var YammerConfig = window.YammerConfig;

; (function ($) {
YamYam.CreateNamespace('YamYam.Modules');

YamYam.Modules.Yammer = {
yammerMainCommentId : '',
addDiscussionToPage: function (url, title, description) {
var dfd = jQuery.Deferred();
$.ajax({
contentType: 'application/json',
url: YammerConfig.providerUrl + '/api/yammermessage/startdiscussion',

type: 'POST',
data: JSON.stringify(
{
'groupId': YammerConfig.newsGroupId,
'url': url,
'title': title,
'description': description,
})
})
.done(function(data) {
var json = $.parseXML(data);
var id = $(json).find('message:first > id').text();
dfd.resolve(id);
})
.fail(function (data) {
dfd.reject(data);
});

return dfd.promise();
},

configureLike : function() {
$.ajax({
url: YammerConfig.providerUrl + '/api/yammermessage/likedmessage/' + YamYam.Modules.Yammer.yammerMainCommentId,
type: 'GET',
}).done(function (data) {

$("#yammer-like").append($("<span>").text('Likes: '))
.append($("<span>")
.attr('id', 'yammer-like-counter')
.css('margin-right', '10px')
.text(data))
.append($("<a>")
.hide().attr('id', 'yammer-like-button').append($("<span>").text('Like')).click(function () {
$.ajax({
contentType: 'application/json',
url: YammerConfig.providerUrl + '/api/yammermessage/likemessage',
type: 'POST',
data: JSON.stringify(
{
'email': YamYam.Modules.Yammer.userEmail,
'id': YamYam.Modules.Yammer.yammerMainCommentId
})
})
.done(function() {
$('#yammer-like-counter').text(parseInt($('#yammer-like-counter').text()) + 1);
$("#yammer-like-button").hide();
$("#yammer-unlike-button").show();
});
}))
.append($("<a>").hide().attr('id', 'yammer-unlike-button')
.append($("<span>").addClass('liked-text').text('Liked'))
.append($("<span>").addClass('unlike-text').text('Unlike'))
.click(function () {
$.ajax({
contentType: 'application/json',
url: YammerConfig.providerUrl + '/api/yammermessage/likemessage',
type: 'POST',
data: JSON.stringify(
{
'email': YamYam.Modules.Yammer.userEmail,
'id': YamYam.Modules.Yammer.yammerMainCommentId,
'unlike' :'true'
})
})
.done(function() {
$('#yammer-like-counter').text(parseInt($('#yammer-like-counter').text()) - 1);
$("#yammer-unlike-button").hide();
$("#yammer-like-button").show();
});
}));

$.ajax({
url: YammerConfig.providerUrl + '/api/yammermessage/getlikestatus/' + YamYam.Modules.Yammer.yammerMainCommentId + "?email=" + YamYam.Modules.Yammer.userEmail,
type: 'GET',
}).done(function (liked) {
if (liked) {
$("#yammer-unlike-button").show();
}
else {
$("#yammer-like-button").show();
}
});

})
.fail(function (data) {
});
},

embedCommentLikeFollow: function () {

var networkUrl = YammerConfig.networkName;
var newsGroupId = YammerConfig.newsGroupId;

var pageUrl = document.URL;
var pageTitle = null;
var pageDescription = null;
if ($('span#DeltaPlaceHolderPageTitleInTitleArea')) {
pageTitle = $("span#DeltaPlaceHolderPageTitleInTitleArea").text().trim();
}

var context = SP.ClientContext.get_current();
var web = context.get_web();
var currentUser = web.get_currentUser();
context.load(currentUser);
context.executeQueryAsync(function() {
YamYam.Modules.Yammer.userEmail = currentUser.get_email();
});

var list = web.get_lists().getById(_spPageContextInfo.pageListId);
var listItem = list.getItemById(_spPageContextInfo.pageItemId);

context.load(listItem);

context.executeQueryAsync(
function() {
pageDescription = listItem.get_item('Description');
$.ajax({
url: YammerConfig.providerUrl + '/api/yammermessage/getmessageid?groupid=' + newsGroupId + "&url=" + pageUrl,
type: 'GET'
}).done(function(messageId) {
if (messageId == null || messageId == -1) {
YamYam.Modules.Yammer.addDiscussionToPage(pageUrl, pageTitle, pageDescription).then(function (id) {
YamYam.Modules.Yammer.yammerMainCommentId = id;
YamYam.Modules.Yammer.configureLike();
context.executeQueryAsync(function (success) {
if (console.log) {
console.log(success);

}
}, function (error) {
if (console.log) {
console.log(error);
}
});
},
function (error) {
if (console.log) {
console.log(error);
}
});
} else {
YamYam.Modules.Yammer.yammerMainCommentId = messageId;
YamYam.Modules.Yammer.configureLike();
}
});

});

}

}

$(document).ready(function () {
var inDesignMode = document.forms[MSOWebPartPageFormName].MSOLayout_InDesignMode.value;

if (inDesignMode != "1") {
// don't embed when in edit mode
YamYam.Modules.Yammer.embedCommentLikeFollow();
}

});

})(jQuery);

C# WebApi

[System.Web.Http.HttpPost]
public string StartDiscussion([FromBody] YammerMessage message) {
var restUrl = string.Format("https://www.yammer.com/api/v1/messages");
var body = string.Format("og_url={0}&og_type=page&og_description={1}&og_title={2}&group_id={3}", message.url, message.description, message.title, message.groupId);
try {
var response = ConnectionHelper.MakePostRequest(body, restUrl, SettingsHelper.AuthenticationToken);
return response;

} catch (Exception e) {
return e.Message;
}
}

[System.Web.Http.HttpGet]
public int GetMessageId(int groupId, string url) {
var restUrl = string.Format("https://www.yammer.com/api/v1/messages/in_group/{0}.json", groupId);
try {
var response = ConnectionHelper.MakeGetRequest(restUrl, SettingsHelper.AuthenticationToken);
var messagesInGroup = new JavaScriptSerializer().Deserialize < MessagesInGroup > (response);
var message = messagesInGroup.messages.FirstOrDefault(
m = > m.content_excerpt.Trim().Equals(url, StringComparison.InvariantCultureIgnoreCase));

return message != null ? message.id : -1;

} catch (Exception) {
return -1;
}
}

[System.Web.Http.HttpGet]
public int LikedMessage(int id) {
var url = string.Format("https://www.yammer.com/api/v1/users/liked_message/{0}.json", id);

try {
var response = ConnectionHelper.MakeGetRequest(url, SettingsHelper.AuthenticationToken);
var likedMessage = new JavaScriptSerializer().Deserialize < LikedMessage > (response);
//todo: pagination
return likedMessage.users.Count;

} catch (Exception) {
return -1;
}
}

[System.Web.Http.HttpGet]
public bool GetLikeStatus(int id, string email) {
var url = string.Format("https://www.yammer.com/api/v1/users/liked_message/{0}.json", id);#
try {
var response = ConnectionHelper.MakeGetRequest(url, SettingsHelper.AuthenticationToken);
var likedMessage = new JavaScriptSerializer().Deserialize < LikedMessage > (response);
//todo: pagination
return likedMessage.users.Any(u = > u.email.Equals(email, StringComparison.InvariantCultureIgnoreCase));

} catch (Exception) {
return false;
}
}

[System.Web.Http.HttpPost]
public string LikeMessage([FromBody] LikeMessageRequest request) {
const string url = "https://www.yammer.com/api/v1/messages/liked_by/current";
var token = AuthenticationHelper.GetImpersonatedToken(request.email);

var body = string.Format("message_id={0}&access_token={1}{2}", request.id, token, request.unlike ? "&_method=DELETE" : "");

try {
var response = ConnectionHelper.MakePostRequest(body, url, token);
return response;
} catch (Exception e) {
return e.Message;
}
}

ConnectionHelper uses MakeGetRequest and MakePostRequest from this excellent blog post. AuthenticationHelper reuses code created in my earlier blog post.

2 Comments

  1. Hi,
    great post.
    can you send me all the files cs for this example?
    The link https://rolandoldengarm.wordpress.com/2015/07/08/how-to-properly-embed-a-yammer-poll-in-sharepoint/ is broken and i can’t complete my project 🙁
    Thanks

Leave a Reply

Your email address will not be published.

*