Ratings for SharePoint lists

03.03.2010 Updated code for the file “RatingForSharePoint.js” due to a bug occurring when the ID column was hidden, but not placed far right in the view.

I got this request after enabling “rating” of the blog comments:

…How can we implement the thumbs up or down functionality you have on your site to a sharepoint list?

Tony


I have made a solution that uses a separate list to hold all the ratings. There will be written one line in this list for each rating of an item (list item or document).

This solution allows for one rating per item, per user, per session (browser session – new window = new session). There are an option to restrict or allow multiple ratings per user in different sessions.

The rating of the items are available in a list view (plain type, no boxed style), and in grouped plain list views. It is also available in DispForm.

ListView:
IMG

DispForm:
IMG

DispForm if already rated:
IMG

To enable rating of items, you add a calculated column to your list with this code:

=""

Yes, only “equals” and two double quotes. You then assures that this new calculated column and the ID column is visible in the list view (there is an option in the script to hide the ID column).

Then you create a custom list for the ratings, with the name: RatingForSharePoint, and add these fields:

  • Plus The type of information in this column is: Number
  • Minus The type of information in this column is: Number
  • ListUrl The type of information in this column is: Single line of text
  • ItemID The type of information in this column is: Single line of text
  • ListGuid The type of information in this column is: Single line of text
  • ListName The type of information in this column is: Single line of text

As always we start like this:
Create a document library to hold your scripts (or a folder on the root created in SharePoint Designer). In this example i have made a document library with a relative URL of “/test/English/Javascript” (a sub site named “test” with a sub site named “English” with a document library named “Javascript”):
IMG

The jQuery-library is found here. The pictures and the sourcecode refers to jquery-1.3.2.min. If you download another version, be sure to update the script reference in the sourcecode.

The scripts “interaction.js” and “stringBuffer.js” is created by Erucy and published on CodePlex.

The file “sessvars.js” is found here.

The sourcecode for the file “RatingForSharePoint.js” is found below.

Add a CEWP below the listView and add this code:

<script type="text/javascript" src="/test/English/Javascript/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="/test/English/Javascript/interaction.js"></script>
<script type="text/javascript" src="/test/English/Javascript/stringBuffer.js"></script>
<script type="text/javascript" src="/test/English/Javascript/sessvars.js"></script>
<script type="text/javascript" src="/test/English/Javascript/RatingForSharePoint.js"></script>
<script type="text/javascript">
ratingForSharePoint('Rating',true,true);
</script>

Parameters explained:

  • FieldInternalName: FieldInternalName of the calculated column created above.
  • hideIdCol: true to hide the ID column, false to let it be visible.
  • rateOnlyOnce: true to allow only one rating per user, false to allow multiple ratings (in different sessions)

Add a CEWP below your DispForm, and add this code:

<script type="text/javascript" src="/test/English/Javascript/jquery-1.3.2.min.js"></script>
<script type="text/javascript" src="/test/English/Javascript/interaction.js"></script>
<script type="text/javascript" src="/test/English/Javascript/stringBuffer.js"></script>
<script type="text/javascript" src="/test/English/Javascript/sessvars.js"></script>
<script type="text/javascript" src="/test/English/Javascript/RatingForSharePoint.js"></script>
<script type="text/javascript">
// You must edit this GUID to match THIS list's GUID - this is NOT the GUID of the "RatingForSharePoint"
thisListsGuid = '{77C35652-1D4D-4B09-B58C-D941068D251E}';
ratingForSharePointDispForm('Rating',true);
</script>

Parameters explained:

  • thisListsGuid: This list’s GUID – this is NOT the GUID of the “RatingForSharePoint”
  • FieldInternalName: FieldInternalName of the calculated column created above
  • rateOnlyOnce: true to allow only one rating per user, false to allow multiple ratings (in different sessions)

Read here how to add a CEWP to the NewForm, DispForm or EditForm, how to find the list Guid of your list, and how to find the FieldInternalName of your columns.

The sourcecode for the file “RatingForSharePoint.js” looks like this:

/* Rating for SharePoint lists
 * ---------------------------------------------
 * Created by Alexander Bautz
 * alexander.bautz@gmail.com
 * https://spjsblog.com
 * v1.1
 * LastMod: 03.03.2010
 * ---------------------------------------------
 * Must include reference to:
 *  jQuery - http://jquery.com
 *  interaction.js - http://spjslib.codeplex.com
 *  stringBuffer.js - http://spjslib.codeplex.com
 *  sessvars.js - http://www.thomasfrank.se
 *  RatingForSharePoint.js - This file
 * ---------------------------------------------
*/

// If anonymous the SharePoint variable "_spUserId" does not exist
if(typeof(_spUserId)=='undefined'){
	_spUserId = '';
}
/************************************************************
*********************** ListView Code ***********************
************************************************************/
function ratingForSharePoint(FieldInternalName,hideIdCol,rateOnlyOnce){
if(typeof(ctx)=='object'){
	var thisListsGuid=ctx.listName;
}
	if(typeof(currentRatingObj)=='undefined'){
		currentRatingObj = getRating();
	}
	if(typeof(FieldInternalName)!='undefined'){
		intName = FieldInternalName;
		hideId = hideIdCol;
		rateOnce = rateOnlyOnce;
		// Fiend index of "Rating" column
		$(".ms-viewheadertr th").each(function(){
			if($(this).find('table:first').attr('name')==intName){
				colIndex = $(this).attr('cellIndex');
				displayName = $(this).find('table:first').attr('displayname');
				// Remove filtering possibility
				$(this).removeClass('ms-vh2').addClass('ms-vh2-nograd').html(displayName);
			}
		});
		// Find index of ID column
		$(".ms-viewheadertr th").each(function(){
			if($(this).find('table:first').attr('name')=='ID'){
				IdColIndex = $(this).attr('cellIndex');
				// Hide ID column
				if(hideId){
					$(this).remove();
				}
			}
		});
		if(typeof(colIndex)=='undefined' || typeof(IdColIndex)=='undefined'){
			alert("Both the column with FieldInternalName ""+FieldInternalName+"" and the ID-column must be in the view.");
			return false;
		}
	}

	$("table.ms-listviewtable tbody:not([id^='aggr']) >tr[beenthere!=1]:has(td.ms-vb2)").each(function(){
		$(this).attr('beenthere',1);
		var itemAlreadyRate = false;
		var thisTd = $(this).find("td[cellIndex=" + colIndex + "]");
		var thisIdColumn = $(this).find("td[cellIndex=" + IdColIndex + "]");
		var thisId = thisIdColumn.text()
		if(hideId){
			thisIdColumn.remove();
		}
		var rPos = 0;
		var rNeg = 0;
		if(currentRatingObj[thisId]!=undefined){
			// Previously rated by current user
			if(rateOnce && currentRatingObj[thisId]['ratedByMe']==true){
				itemAlreadyRate = true;
			}
			rPos = currentRatingObj[thisId]['Plus'];
			rNeg = currentRatingObj[thisId]['Minus'];
		}
		// Rated in this session
		if(sessvars[thisListsGuid+thisId]==1 && currentRatingObj!=false){
			itemAlreadyRate = true;
		}

		var str = "<div style='color:gray'>";
		str += "<span><img ";
		if(!itemAlreadyRate){
			str += "onclick='javascript:rateMe("up","+thisId+")' title='Rate Up' ";
			str += "style='vertical-align:middle;cursor:pointer' ";
		}else{
			str += "title='You have already rated this item "+currentRatingObj[thisId]['ratedByMeVal']+"' ";
			str += "style='vertical-align:middle;cursor:no-drop' ";
		}
		str += "src='/_layouts/images/arrupi.gif'></span>";
		str += "<span style='font-weight:bold;' id='rateUp_"+thisId+"'>"+rPos+"</span>";
		str += "<span><img ";
		if(!itemAlreadyRate){
			str += "onclick='javascript:rateMe("down","+thisId+")' title='Rate Down' ";
			str += "style='vertical-align:middle;cursor:pointer' ";
		}else{
			str += "title='You have already rated this item "+currentRatingObj[thisId]['ratedByMeVal']+"' ";
			str += "style='vertical-align:middle;cursor:no-drop' ";
		}
		str += "src='/_layouts/images/arrdowni.gif'></span>";
		str += "<span style='font-weight:bold;' id='rateDown_"+thisId+"'>"+rNeg+"</span>";
		str += "</div>";
		// Write HTML
		thisTd.html(str);
	});
}

function getRating(){
// Path to webservices
wsBaseUrl = L_Menu_BaseUrl + '/_vti_bin/';
var query = "<Where><Eq><FieldRef Name='ListGuid' /><Value Type='Text'>"+ctx.listName+"</Value></Eq></Where>";
var rating = queryItems('RatingForSharePoint',query,['Plus','Minus','ItemID','Author']);
	if(rating.count==-1){
		alert("An error occured in the query:n"+query);
	}else if(rating.count>0){
	ratingObj = {};
		$.each(rating.items,function(i,item){
		var authorIdRaw = item['Author'];
		var authorId = authorIdRaw.substring(0,authorIdRaw.indexOf(';#'));
		var plusVal = item['Plus'];
		var minusVal = item['Minus'];
		if(plusVal==null)plusVal=0;
		if(minusVal==null)minusVal=0;
			if(ratingObj[item['ItemID']]==undefined){
				ratingObj[item['ItemID']]={'Plus':parseInt(plusVal),'Minus':parseInt(minusVal)};
			}else{
				ratingObj[item['ItemID']]['Plus']+=parseInt(plusVal);
				ratingObj[item['ItemID']]['Minus']+=parseInt(minusVal);
			}
			// Rated by current user
			if(_spUserId==authorId){
				ratingObj[item['ItemID']]['ratedByMe']=true;
				if(item['Plus']>0){
					ratingObj[item['ItemID']]['ratedByMeVal']="+1";
				}else{
					ratingObj[item['ItemID']]['ratedByMeVal']="-1";
				}
			}
		});
		return ratingObj;
	}else{
		return false;
	}
}

/************************************************************
*********************** DispForm Code ***********************
************************************************************/
function ratingForSharePointDispForm(FieldInternalName,rateOnlyOnce){
	var queryStr = getQueryParameters();
	if(typeof(fields)=='undefined')fields = init_fields();
	var itemAlreadyRate = false;
	var thisId = queryStr.ID;
	var currentRatingObj = getRatingDispForm(thisId);
	if(currentRatingObj[thisId]==undefined){
		rPos = 0;
		rNeg = 0;
	}else{
		// Previously rated by current user
		if(rateOnlyOnce && currentRatingObj[thisId]['ratedByMe']==true){
			itemAlreadyRate = true;
		}
		rPos = currentRatingObj[thisId]['Plus'];
		rNeg = currentRatingObj[thisId]['Minus'];
	}
	// Rated in this session
	if(sessvars[thisListsGuid+thisId]==1 && currentRatingObj!=false){
		itemAlreadyRate = true;
	}
	var thisTd = $(fields[FieldInternalName]).find('.ms-formbody');
	var str = "<div style='color:gray'>";
	str += "<img ";
	if(!itemAlreadyRate){
		str += "onclick='javascript:rateMe("up","+thisId+")' title='Rate Up' ";
		str += "style='vertical-align:middle;cursor:pointer' ";
	}else{
		str += "title='You have already rated this item "+currentRatingObj[thisId]['ratedByMeVal']+"' ";
		str += "style='vertical-align:middle;cursor:no-drop' ";
	}
	str += "src='/_layouts/images/arrupi.gif'>";
	str += "<span style='font-weight:bold;' id='rateUp_"+thisId+"'>"+rPos+"</span>";
	str += "<img ";
	if(!itemAlreadyRate){
		str += "onclick='javascript:rateMe("down","+thisId+")' title='Rate Down' ";
		str += "style='vertical-align:middle;cursor:pointer' ";
	}else{
		str += "title='You have already rated this item "+currentRatingObj[thisId]['ratedByMeVal']+"' ";
		str += "style='vertical-align:middle;cursor:no-drop' ";
	}
	str += "src='/_layouts/images/arrdowni.gif'>";
	str += "<span style='font-weight:bold;' id='rateDown_"+thisId+"'>"+rNeg+"</span>";
	str += "</div>";
	// Write HTML
	thisTd.html(str);
}

function getRatingDispForm(itemID){
// Path to webservices
wsBaseUrl = L_Menu_BaseUrl + '/_vti_bin/';
var query = "<Where><And><Eq><FieldRef Name='ListGuid' /><Value Type='Text'>"+thisListsGuid+"</Value></Eq>"+
			"<Eq><FieldRef Name='ItemID' /><Value Type='Text'>"+itemID+"</Value></Eq></And></Where>";
var rating = queryItems('RatingForSharePoint',query,['Plus','Minus','ItemID','Author']);
	if(rating.count==-1){
		alert("An error occured in the query:n"+query);
	}else if(rating.count>0){
	ratingObj = {};
		$.each(rating.items,function(i,item){
			var authorIdRaw = item['Author'];
			var authorId = authorIdRaw.substring(0,authorIdRaw.indexOf(';#'));
			var plusVal = item['Plus'];
			var minusVal = item['Minus'];
			if(plusVal==null)plusVal=0;
			if(minusVal==null)minusVal=0;
			if(ratingObj[item['ItemID']]==undefined){
				ratingObj[item['ItemID']]={'Plus':parseInt(plusVal),'Minus':parseInt(minusVal)};
			}else{
				ratingObj[item['ItemID']]['Plus']+=parseInt(plusVal);
				ratingObj[item['ItemID']]['Minus']+=parseInt(minusVal);
			}
			// Rated by current user
			if(_spUserId==authorId){
				ratingObj[item['ItemID']]['ratedByMe']=true;
				if(item['Plus']>0){
					ratingObj[item['ItemID']]['ratedByMeVal']="+1";
				}else{
					ratingObj[item['ItemID']]['ratedByMeVal']="-1";
				}
			}
		});
		return ratingObj;
	}else{
		return false;
	}
}

/************************************************************
*********************** Shared Code ***********************
************************************************************/
function rateMe(upORdown,id){
if(typeof(ctx)=='object'){
	var listGuid=ctx.listName;
	var listTitle = $.trim($("td.ms-pagetitle").text());
}else{
	var listGuid=thisListsGuid;
	var listTitle = $.trim($("td.ms-pagetitle a").text());
}
// Only rate item if not already rated in this session
if(sessvars[listGuid+id]==undefined){
	plus='';
	minus='';
	if(upORdown=='up'){
		sessvars[listGuid+id]=1;
			currVal = parseInt($("#rateUp_"+id).text());
			$("#rateUp_"+id).text(currVal+1)
				.prev().css({'background-color':'#C5E3BF'})
				.parent().find('img').attr({'onclick':'','title':'Rated +1'});
			// Prepare for writing rating
			plus=1;
	}else{
		sessvars[listGuid+id]=1;
		currVal = parseInt($("#rateDown_"+id).text());
		$("#rateDown_"+id).text(currVal+1)
			//.parent().css({'background-color':'#FFE4E1'})
			.prev().css({'background-color':'#FFE4E1'})
			.parent().find('img').attr({'onclick':'','title':'Rated -1'});
		// Prepare for writing rating
		minus=1;
	}

		// Get url
		var urlDir = location.pathname;

		// Path to webservices
		wsBaseUrl = L_Menu_BaseUrl + '/_vti_bin/';
		// Add item
		var res = addItem('RatingForSharePoint',
							{'Title':"Rating for: " + listTitle,
							'Plus':plus,
							'Minus':minus,
							'ListUrl':urlDir,
							'ListGuid':listGuid,
							'ListName':listTitle,
							'ItemID':id});
	}
}

// Attaches a call to the function to the "expand grouped elements function" for it to function in grouped listview's
function ExpGroupRenderData(htmlToRender, groupName, isLoaded){
	var tbody=document.getElementById("tbod"+groupName+"_");
	var wrapDiv=document.createElement("DIV");
	wrapDiv.innerHTML="<TABLE><TBODY id="tbod"+groupName+"_" isLoaded=""+isLoaded+"">"+htmlToRender+"</TBODY></TABLE>";
	tbody.parentNode.replaceChild(wrapDiv.firstChild.firstChild,tbody);
ratingForSharePoint();
}

// Function to separate each url search string parameters
function getQueryParameters(){
qObj = {};
var urlSearch = window.location.search;
	if(urlSearch.length>0){
		var qpart = urlSearch.substring(1).split('&');
		$.each(qpart,function(i,item){
			var splitAgain = item.split('=');
			qObj[splitAgain[0]] = splitAgain[1];
		});
	}
return qObj;
}

function init_fields(){
var res = {};
$("td.ms-formbody").each(function(){
if($(this).html().indexOf('FieldInternalName="')<0) return;
var start = $(this).html().indexOf('FieldInternalName="')+19;
var stopp = $(this).html().indexOf('FieldType="')-7;
var nm = $(this).html().substring(start,stopp);
res[nm] = this.parentNode;
});
return res;
}

Save as “RatingForSharePoint.js”, mind the file extension, and upload to the scriptlibrary as shown above.

Ask is something is unclear, and please report any bugs.

Regards
Alexander

47 Comments on “Ratings for SharePoint lists

    1. As for now, the images are set in the file “RatingForSharePoint.js”.

      I have used “arrupi.gif” and “arrdowni.gif” from the “/_Layouts/images/” directory to assure that all users have them available.

      Replace the images as you like, just search and replace in the file.

      Alexander

  1. Agree, great job. 2 questions, I know this can be converted to the star rating, instead of up/down, but is this an easy modification? Can this be added to pages in addition to a list item, track by URL in addition to itemID (maybe create an or get page ID)?

    I have created a library where new bullitins are added. once they are approved they are visible for users. A user can then read the document. Once read there is a link to click to mark the item read. the link opens a popup win that goes to a newform. they agree they read and click OK, takes them to a thank you page. Recently they asked if user could rate the item. I am thinking i could use this script, add the user_ID and I can get the result with less work.

    1. It is possible, but requires some additional work. I reckon i will be updating this solution in the following weeks, but cannot promise anything right now.

      Alexander

  2. I have tried to replicate this and I am failing. I know it is something simple. I installed as described. Works perfectly from the dispform. In the list view the calcultaed fields writes to the title field and nothing in the calc field, no tracking. if i set ID to visible, it lays out correctly, but still not tracking. I have checked and rechecked my src URLs.

    1. Very strange – if you look at the code in line 51, i should give en alert if you do not have both the “Rating” and the “ID” column in the view.

      Do you have any other code in this page that could interfere with this script?

      Alexander

    2. No other code on the page, the ID and Calc field is in view. No alert. I even created a new list and started from new. same issue. The DispForm works perfectly. I even copies the src links from there to make sure i had them correct. it is bizzare.

    3. I cant figure it out. I scraped it all and created new lists in a new site collection. Same issue. the dispform works fine, but in the list view I get no error, and no results. I found if ID hide set to false, the layout is fine.it does not work, but the rate up/down is in the rating column. if UD col set to hide, it moves the rate up/down column to the left one column. let me know if there is any other info I can provide to help trouble shoot

  3. k, I have made some changes. The order of my columns,
    from ID, Title, Ratings
    TO Attachments, ID, Title, Ratings

    The script is functioning, but the column alignment is still off by one. My text in the Title column has been replaced with the rating Up/Down arrows and numbers.

    1. Another modification. If I DO NOT hide the ID column, with the fields in the order above, it work perfectly. So this may just be a bug. That attachment column may be what is causing the bug.

    2. Hi,
      I have reproduced the bug and will post a fix soon. Until this is supplied, i think it should work if you put the ID column to the far right in your list view.

      Alexander

  4. Hi Alexander,

    can you tell me how to use the “ExpGroupRenderData(htmlToRender, groupName, isLoaded)” Function?
    I don’t know which parameters need to be delivered.
    What is “htmlToRender” and “isLoaded”?

    Is there a possibility to use it in a Newsletter View?

    Thanks a lot!

    Martin

    1. This function is a generic SharePoint function to handle expanding of groups and sub groups in a grouped list view. The only reason it is included in the script is to insert the call to “our function – ratingForSharePoint()” at the bottom of the function.

      It should work in a Newsletter view.

      Alexander

  5. Great Solution Alexander,

    How do I apply the Thumbs Up & Down for a Blog Post?
    /Lists/Posts/Post.aspx?ID=1

    I’ve tried using the DispForm Code, but the Thumbs don’t display.
    Can this be modified for Blog Posts?

    Thanks, Brett.

  6. Every time user is clicking like/dislike it is making separate entry in the rating list. Is there is any way we can increment the same ItemID count if different user clicks on same ItemID? E.g. If user A click on like for ItemID 1 and user B also put like on Item ID 1, both entries are getting recorded as separate entity rather than updating the same ItemID. My requirement is like/dislike entries should be updated against each ItemID rather than as separate entries.

    1. Hi,
      This cannot be done due to the possibility for two (or more) users trying to write to the same item simultaneously. SharePoint cannot handle this scenario, and you would either get an error message, or just loose some “hits”.

      Alexander

  7. Hi,
    I am trying to use this for Wiki page, but unfortunately I am not able to do that. Can you please give me some suggestion to add ratings system or Like/Dislike option for Wiki Page.

    Nithin

    1. Hi,
      I have not had the time to test it in a Wiki page, and at the moment I’m kind of swamped so I cannot help you on this right now.

      Please post back if you find a solution as others might benefit from it.

      Alexander

      1. Hi Alex,

        Even I didn’t get any solution for this, but for timing I have created general listings and used as a Wiki page and included Like/Dislike option with attachment.

  8. I am attempting to use this on my default.aspx page with a list web part. All the images show up and you can click up or down, but when you refresh the page it looses its value. Any suggestions?

    1. Hi,
      Does the values show up in the “RatingForSharePoint” list?
      Which browser are you using?
      Which version of jQuery are you using?

      Alexander

  9. Alex – Thanks for the wonderful solution.

    A question- Can we have the names of the people who liked and dislikes the entry? (something like facebook??)

    Thank you much!

    1. SPUser, Alexander is on hiatus for a while and won’t be adding anything new to his Blog for the moment as described in his latest post “Status Update”.
      I asked the same question for the Hit/Like Counter: Alexander Bautz Says:
      February 17, 2011 at 10:04 am
      Hi,
      The hit counter stores each “hit” or “like” in a separate line in the “hit counter list”. To retrieve this you would have to write a script to pull them in and count the hits and likes on the individual items. This might take a few seconds, but is no big deal.

      The problem is that as the script is setup right now, it does not store the “title” for each record – only the URL. To have the “Most viewed” display in a proper manner, you would have to do another query against the list where the hits are being registered to fetch the title.

      Again not hard to do, but adds a few seconds to the page render (depending on the number of line in the counter list).

  10. I realize that support is sporadic, but am hoping someone can answer a question. I was able to get this to work flawlessly in IE. But, when I view the same site/list in Firefox, Chrome or Safari, I get the alert that the Ratings and ID field need to be visible. In IE it works without a problem, but on the other browsers, the alert comes up no matter what I try and the ratings don’t show up. Does anyone have any idea why it is doing this?

    1. Hi,
      This post is a bit outdated and the scripts are IE only – due to a limitation in “interaction.js” related to how the script parses xml.

      I’ll put it on the “need update list”, but cannot promise anything.

      Alexander

    2. No worries, at least I know I’m not crazy haha The script is perfect as is, and in truth, our mandated browser is IE, so technically I can get away with saying “if you aren’t following the rules, don’t expect it to work!” hehe This was a fantastic solution for those of us who can’t do anything server side. So, I am happy nonetheless! Thanks again!

  11. Hi Alex,
    I have a customization here. we have to book ‘meeting room’ as well as ‘bridge’ here, can i use this code to check availability for both the resources on the selected time and then save the form?

  12. I found this blog very interesting. Is there a way to do something similar without the ranking, just track unique visitors to a list item. perhaps when they open the dispform. I am trying to show unread items for each user. it would be nice to see the items unread, but I really just want a count. Maybe in the secondary list we can add listitemid and title. Could create a link back to unread items. let me know your thoughts

    1. Hi,
      You are welcome to modify this solution , but I think this is best answered with a custom made solution. I’ll add this as a request, but unfortunately I have a lot in the queue already so it may take some time.

      Alexander

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.