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;
            }