Getting Mileage Out of the Client-Side Object Model, Part 2

In my last post I discussed the JavaScript client-object model that was introduced in SharePoint 2010. Now I want to show an example from my own experience.

If you're supporting a SharePoint environment for large a large number of employees, you should consider having a Q&A discussion board specifically for SharePoint questions. It can be a good alternative to a support ticket or incident tracking system for support calls that can be resolved with simple training or instructions, especially since A) users can read answers to other questions, possibly answering their own, and B) users can answer each others' questions.

My team put one into place shortly before upgrading our environment from SharePoint 2007 to 2010. We set an alert on the discussion board for our entire team so that we could answer questions as they popped up. Over the course of ten months we received an average of about 3 questions per month.

In September I was contacted by a customer who wanted to display notifications about activity on a discussion board on their SharePoint site. I was able to meet this need with a smattering of JavaScript using the client-side object model, and decided that I liked the result so much that I would apply it to my SharePoint discussion board as well. The results were amazing! Activity on the discussion board skyrocketed: we went from 3 questions in August to 18 questions in September.

So what was this magic JavaScript that made such a difference?

We already had a simple link to our "SharePoint Q&A" but this JavaScript added a running total of the number of questions asked today and within the last 30 days to the bottom of the quick launch(see the screenshot above).

This increased the visibility of the discussion board because it is styled slightly differently from the other navigation nodes on the quick launch. It also is more informative than a mere link; it clearly indicates that the Q&A list is being used to ask questions, as opposed to being a collection of static FAQs. Finally, assuming there's any significant level of activity on the discussion board, it makes asking questions less intimidating for users, knowing that other people are also asking questions; I think of this as the notion of safety in numbers.

So how does it work?

JavaScript variables indicate which list to pull from, the number of days back to count, where to display the component, and what text to display. The JavaScript itself moves the component HTML to the correct place in the quick launch, then uses the client object model to run a query against the list to determine the number of items that were created today and the number of items created in the last X days.

How to use:

  1. Create a text file that contains the code below, but with the following JavaScript variables set to the correct values for your situation:
    • recentActivityListName: the name of the list you want to target
    • daysOfItemsToPull: the number of days back you want to count items (30 days by default)
    • singularTerm and pluralTerm: the singular and plural terms that describe the list items (e.g. Question/Questions or Reply/Replies)
    • showOnTopOfQuicklaunch: true to show on top of quick launch, false to show above recycle bin
    • headingText: (Optional) Change this if you want the text displayed on the quick launch to be different from the name of the list
  2. Upload the text file to a library on the site where all visitors will be able to read it.
  3. Add a content editor web part to each page where you want the component to appear.
  4. Edit the content link property of the content editor web part and set it to the URL of the uploaded text file. You can then set the content editor web part to be hidden.
  5. For navigational consistency, I recommend that you add the web part to the default view of the target list, as well as to any deeper level views such as those in discussion boards and document sets.

One way in which this could obviously be improved would be to wrap it in a custom function, reducing the risk of collisions in the global variable namespace.

Was  this useful for you? Do you have any suggestions for improvement? Let me know in the comments!

Here's the code!

#SPCSOM_Post_Container{text-align:left;width:100%;font-family:segoe ui;}
	padding-left:11px; font-family:Verdana, Arial, sans-serif; 
	border-bottom:1px solid #dfdfdf;background: #ededed;
	display:table-cell;width:60%; padding-bottom:2px;
.SPCSOM_Link_Styles{color:#515151 !important;}
.SPCSOM_Link_Styles:hover{text-decoration:underline !important;}
	padding-left:11px; font-family:Verdana, Arial, sans-serif;
	padding-bottom:2px;border-bottom:1px solid #dfdfdf;
	background-color: rgb(249,200,159); 
	background-image: url("/_layouts/images/selbg.png");
	background-repeat:repeat-x; background-attachment:scroll; 
	background-position-x:left; background-position-y:top;zoom:1;

// Configuration Variables
var recentActivityListName = "SharePoint Q&A" ;
var daysOfItemsToPull = 30 ; 
var singularTerm = "Question" ; 
var pluralTerm = "Questions" ;
var showOnTopOfQuicklaunch = false;
var headingText = recentActivityListName ;

<div id="SPCSOM_Post_Container" class="SPCSOM_Invisible">
	<div class="menu vertical menu-vertical">
		<div id="SPCSOM_Post_Heading" class="SPCSOM_Post_Heading">
		<div style="display:table;width:100%"><div style="display:table-row">
			<div class="SPCSOM_Post_Counter" id="SPCSOM_Post_New"></div>
			<div class="SPCSOM_Post_Counter_Subtext" id="SPCSOM_Post_New_Subtext">
			<div style="display:table-row">
			<div class="SPCSOM_Post_Counter"id="SPCSOM_Post_Old">
				<img src="/_layouts/images/loading16.gif"/></div>
			<div class="SPCSOM_Post_Counter_Subtext" id="SPCSOM_Post_Old_Subtext" >
				Last <script>document.write(daysOfItemsToPull);</script> days


// Get the widget and get the quicklaunch
var containerDiv = document.getElementById("SPCSOM_Post_Container");
var quicklaunch = document.getElementById("s4-leftpanel-content");
// Move the widget into the quick launch while it is still invisible
if(this.showOnTopOfQuicklaunch){ //add it to the top
}else{ //add it to the bottom, above the recycle bin
	var kiddo = quicklaunch.lastChild;
	var papa = quicklaunch;
	var targetDepth = 2;
	var depth = 0;
	while(kiddo.hasChildNodes() && depth < targetDepth){
		papa = kiddo;
		kiddo = kiddo.lastChild;
		depth += 1;
	papa.insertBefore(containerDiv, kiddo);
// Make the widget visible
containerDiv.className = "SPCSOM_Visible";
	var clientContext = new SP.ClientContext.get_current();
	var list = clientContext.get_web().get_lists().getByTitle(recentActivityListName);
	var postsCamlQuery = new SP.CamlQuery();
	var postsCamlString = '<View><Query>'+
				'<FieldRef Name="Created" />'+
				'<Value Type="DateTime">'+
					'<Today OffsetDays="-'+daysOfItemsToPull+'"/>'+
	this.discussionPosts = list.getItems(postsCamlQuery); 
	this.discussionList = list; 
	this.discussionListRootFolder = list.get_rootFolder();

function getPostsSuccess(){
	// Highlight the heading if we're actually on a view of the list
	var linkToList = this.discussionList.get_rootFolder().get_serverRelativeUrl();
	var selected = "SPCSOM_Post_Heading";
	var currUrl = window.location.href;
	var escLink = escape(linkToList);
	if(currUrl.indexOf(escLink) > -1 || escape(currUrl).indexOf(escLink) > -1 ){
		selected = "SPCSOM_Post_Heading_Selected ";
	document.getElementById("SPCSOM_Post_Heading").className = selected;
	// Get today's date so we can count up today's items
	var today = new Date(); 
	var todaysDate = today.getUTCDate(); 
	var todaysMonth = today.getUTCMonth(); 
	var todaysYear = today.getUTCFullYear();
	// Counter for both today's items and the total in query
	var newPostCount = 0; var oldPostCount = 0;
	var listItemEnumerator = this.discussionPosts.getEnumerator();
		oldPostCount += 1;
		var createdDatetime = listItemEnumerator.get_current().get_item('Created');
		var createdYear = createdDatetime.getUTCFullYear(); 
		if(createdYear == todaysYear){ 
			var createdMonth = createdDatetime.getUTCMonth(); 
			if(todaysMonth == createdMonth){
				var createdDate = createdDatetime.getUTCDate(); 
				if(createdDate == todaysDate){
	// Make sure we have the right plurality for our terms
	var newTerm = this.pluralTerm;
	if(newPostCount == 1){
		newTerm = this.singularTerm;
	var oldTerm = this.pluralTerm;
	if(oldPostCount == 1){
		oldTerm = this.singularTerm;
	// Display item counts
	document.getElementById("SPCSOM_Post_Heading").innerHTML = '<a href="'+linkToList+
		'?SortField=Created&SortDir=Desc" class="SPCSOM_Link_Styles">' + 
		this.headingText + '</a>';
	document.getElementById("SPCSOM_Post_New").innerHTML = newPostCount + " "+newTerm;
	document.getElementById("SPCSOM_Post_New_Subtext").innerHTML = "Today";
	document.getElementById("SPCSOM_Post_Old").innerHTML = oldPostCount + " "+oldTerm;
	document.getElementById("SPCSOM_Post_Old_Subtext").innerHTML = "Last "+
		daysOfItemsToPull+" days";
function getPostsFail(sender, args){
	document.getElementById("SPCSOM_Post_Container").innerHTML =
		'The page was unable to populate the data this time.<br/>'+
		'<span style="color:white">' + args.get_message() + '</span>\n';

Add comment

  • Comment
  • Preview

Recent Comments

Comment RSS

Post History

<<  August 2020  >>

View posts in large calendar