SharePoint Discussion with jQuery
Recently I have been doing some work for a large bank with some rather restrictive policies on their SharePoint environment - no custom code can be installed. SharePoint Designer used to be an option, but with some changes to VPN settings I’m limited to IE6 on a painfully slow java remote desktop that changes the quotes in my code to umlauts.
The sites I was building make extensive use of discussion lists, but the out of box web parts weren’t really adequate - the threaded view in particular doesn’t like appearing on custom pages.
The requirement was for a web part that could display recent posts with threaded comments on the site home page - similar to the blog template, but a bit more flexible. I’ve done similar things in the past, building a custom web part to display comments with a news article, but that wasn’t an option in this environment.
What I came up with was some javascript that could be pasted into a content editor web part to render the required view with jQuery. I kept the rendering and web service access fairly well seperated, rendering from a simple custom data structure - partly to simplify the code and partly so I could debug the javascript independent of any SharePoint issues.
I used Darren Johnstone’s JSAPI for web service access - a very useful piece of code, and much easier than the manual approach I used last time I had to use sharepoint web services from javascript.
The included javascript files (jquery, jsapi and optionally the code below if you want reuse instead of quick copy/paste editing) can be placed in any document library with appropriate permissions.
First, the shared functions - one to retrieve the list data and put it into a structure more easily manipulated in javascript, and another to render the html.
function GetTopLevelPosts(weburl, listname, listurl, postlist, src) {
var lists = new SPAPI_Lists(weburl);
var res = lists.getListItems(
listname,
"",
"",
'',
10,
'TRUE',
null);
//alert(res);
var rows = res.responseXML.getElementsByTagName('z:row');
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var fn = row.getAttribute("ows_FileRef");
var ih = fn.indexOf("#") + 1;
fn = fn.substr(ih, fn.length - ih);
var t = row.getAttribute("ows_Body");
var np = {
Title: row.getAttribute("ows_Title"),
Threading: row.getAttribute("ows_Threading"),
Date: row.getAttribute("ows_Created"),
PostedBy: row.getAttribute("ows_PersonViewMinimal"),
Text: t,
ReplyLink: listurl + "/NewForm.aspx?RootFolder=/" + fn +
"&ContentTypeID=0x0107&DiscussionParentID=" + row.getAttribute("ows_ID") + "&Source=" + src,
MoreLink: listurl + "/Threaded.aspx?RootFolder=/" + fn + "",
Replies: []
};
var att = row.getAttribute('ows_Attachments');
if (att != '0') {
var at2 = att.split(';#');
np.AttachmentUrl = at2[1];
var atfnl = np.AttachmentUrl.split('/');
np.AttachmentName = atfnl[atfnl.length - 1];
}
postlist[postlist.length] = np;
GetChildPosts(lists, listname, listurl, fn, np.Replies, src);
}
}
function GetChildPosts(lists, listname, listurl, fn, postlist, src) {
var unthreaded = new Array();
var res = lists.getListItems(
listname,
"",
"0",
'',
100,
'/' + fn + 'TRUE',
null);
//alert(res);
var rows = res.responseXML.getElementsByTagName('z:row');
for (var i = 0; i < rows.length; i++) {
var row = rows[i];
var t = row.getAttribute("ows_Body");
var ii = t.indexOf("= 0) t = t.substr(0, ii);
var np = {
Title: row.getAttribute("ows_Title"),
Threading: row.getAttribute("ows_Threading"),
Date: row.getAttribute("ows_Created"),
PostedBy: row.getAttribute("ows_PersonViewMinimal"),
Text: t,
ReplyLink: listurl + "/NewForm.aspx?RootFolder=/" + fn +
"&ContentTypeID=0x0107&DiscussionParentID=" + row.getAttribute("ows_ID") + '&Source=' + src,
Replies: []
};
var att = row.getAttribute('ows_Attachments');
if (att != '0') {
var at2 = att.split(';#');
np.AttachmentUrl = at2[1];
var atfnl = np.AttachmentUrl.split('/');
np.AttachmentName = atfnl[atfnl.length - 1];
}
unthreaded[unthreaded.length] = np;
}
for (var i = 0; i = -1; j--) {
if (j < 0) {
postlist[postlist.length] = unthreaded[i];
}
else {
if (unthreaded[i].Threading.indexOf(unthreaded[j].Threading) == 0) {
unthreaded[j].Replies[unthreaded[j].Replies.length] = unthreaded[i];
break;
}
}
}
}
}
function RenderPosts(parentdiv, postlist) {
for (var i = 0; i < postlist.length; i++) {
var post = postlist[i];
//console.log(post.Text);
var postdiv = $(document.createElement("div"));
var posttextdiv = $(document.createElement("div"));
var postcommentdiv = $(document.createElement("div"));
var postheaderdiv = $(document.createElement("div"));
var postfooterdiv = $(document.createElement("div"));
parentdiv.append(postdiv);
postdiv.append(postheaderdiv);
postdiv.append(posttextdiv);
postdiv.append(postfooterdiv);
postdiv.append(postcommentdiv);
postdiv.addClass("post");
postheaderdiv.addClass("postheader");
posttextdiv.addClass("posttext");
postfooterdiv.addClass("postfooter");
postcommentdiv.addClass("postcomment");
postheaderdiv.html(post.PostedBy + " - " + post.Date + " <a>Reply</a>");
posttextdiv.html(post.Text);
postfooterdiv.html("");
RenderPosts(postcommentdiv, post.Replies);
}
}
function RenderTopLevelPosts(parentdiv, postlist) {
for (var i = 0; i < postlist.length; i++) {
var post = postlist[i];
//console.log(post.Text);
var postdiv = $(document.createElement("div"));
var posttextdiv = $(document.createElement("div"));
var postcommentdiv = $(document.createElement("div"));
var postheaderdiv = $(document.createElement("div"));
var postfooterdiv = $(document.createElement("div"));
parentdiv.append(postdiv);
postdiv.append(postheaderdiv);
postdiv.append(posttextdiv);
postdiv.append(postfooterdiv);
postdiv.append(postcommentdiv);
postdiv.append("<hr></hr>");
postdiv.addClass("toppost");
postheaderdiv.addClass("toppostheader");
posttextdiv.addClass("topposttext");
postfooterdiv.addClass("toppostfooter");
postcommentdiv.addClass("toppostcomment");
postheaderdiv.html("<span>" + post.Title + "</span><br></br><span>" + post.PostedBy + " - " + post.Date + "</span>");
posttextdiv.html(post.Text);
var alnk = "";
if (post.AttachmentUrl != null) {
alnk = "<a>Attachment: " + post.AttachmentName + "</a> - ";
}
postfooterdiv.html(alnk + post.Replies.length + " comments - <a>Reply</a>");
RenderPosts(postcommentdiv, post.Replies);
}
}
To use the code just add the html elements and call to the above methods to a content editor web part.
<div id="discussion1">
</div><br></br>
var posts = new Array();
GetTopLevelPosts("http://tqcdev08/2009", "Discussions", "/2009/Lists/Discussions", posts);
RenderTopLevelPosts($("#discussion1"), posts);
The css I used is below - this version doesn’t look that great, but is easily customised.
.toppost
{
border: 1px solid black;
font-family: Verdana;
font-size: 8pt;
margin-bottom: 20px;
width: 600px;
}
.toppostheader
{
margin-bottom: 10px;
}
.toppostheader .title
{
font-weight: bold;
font-size: 10pt;
}
.toppostheader .byline
{
font-size: 8pt;
color: Gray;
}
.post
{
margin-top: 10px;
margin-left: 30px;
}