Index: messageforums-app/src/webapp/js/forumTopicThreadsSorter.js =================================================================== --- messageforums-app/src/webapp/js/forumTopicThreadsSorter.js (revision 0) +++ messageforums-app/src/webapp/js/forumTopicThreadsSorter.js (revision 0) @@ -0,0 +1,386 @@ +/* Client side sorting for sakai forums topic threads page + * + * threadsSorter: jQuery plugin + * @example : $('table').threadsSorter(); + * Version : 1.1 + * @requires : tablesorter plugin + * Author : Yuanhua Qu, Texas State University + * Date : 7/24/2010 + * Mail : yq12@txstate.edu + * Description : This threadsSorter jquery is built on top of tablesorter jquery plguin. + * It handles specifically for sakai forums topic threads sorting case. + * Should also handle sorting normal table correctly. Works tested with IE7 & IE8, + * firefox 3 and safari 3 & 5 + * Each thread in the topic is called -- parent + * Each response directly to the thread (parent) is called -- child(ren) + * Each response to the child and deeper from there is called grandchild(ren). + * All responses are called descendants which should include children and grandchildren. + * Sorting result expected: + * 1. If sorted by threads, all parents are sorted; Each parent's children are also sorted; + * but grandchildren are not sorted (This satisfies our users' need of no deeper sorting needed. + * Though potentially all could be sorted with same level of messages, only need little bit more + * work to get it done if there is such requirement.) + * Thread/message/response still keep their logic relative layout after sorting. + * 2. If sorted by date, all parents are sorted; Each parent's descendants (children and grandchildren) + * are also sorted. Descendants are not ordered by logic but date. + * 3. If sorted by author, it is absolutely sorted by author's firstname lastname order so that + * instructor will get all the messages grouped by author; Threads/messages/responses are + * out of their logic relative layout after sorting. This satisfies the use case of + * instructor's insterest to see messages of certain students. + * If you would like sorting by author behaves the same way as sorting by date, just comment out + * 3 lines: + * if ($(e.currentTarget).get(0).column == 2) { + * return false; + * } + * Note : It depends on the row class, id and indent to identify parent-descendant relationship. + * It is highly customized sorting result based on our users' needs. + * version 1.1 fixed issue with IE browsers. + */ + + +/* This helps to convert padding value from px to relative value */ +$.fn.toEm = function(settings){ + settings = jQuery.extend({ + scope: 'body' + }, settings); + var that = parseInt(this[0],10); + var scopeTest = jQuery('
 
').appendTo(settings.scope); + var scopeVal = scopeTest.height(); + scopeTest.remove(); + return (that / scopeVal).toFixed(0) + 'em'; +}; + + +/* add parser for link column 'Thread' , extend parser of tablesorter object */ +/* 'A' fix for IE */ +$.tablesorter.addParser({ + id: "link", + is: function(s) { + return /^<(a|A)/.test(s); + }, + format: function(s) { + var str = $(s).siblings("a|A").text(); + return jQuery.trim($(s).siblings("a|A").text().toLowerCase()); + }, + type: "text" +}); + +// overwrite sortCSS defined locally in jquerytablesorter.js +// it was defined wrong way +// Changed that in jquery.tablesorter.js file, comment out here +// sortCSS = ["headerSortUp", "headerSortDown"]; + +jQuery.fn.threadsSorter = function() { + return this.each(function(){ + + /* util */ + function isParent(node){ + if (node.className == "hierItemBlock") + return true; + else + return false; + } + + function isDescendant(node){ + if (node.id.match(new RegExp('_id_[0-9]+__hide_division_'))) + return true; + else + return false; + } + + function isChild(node){ + var paddingValue = $(node).find("td").eq(1).css("padding-left"); + if (paddingValue.indexOf("px")>=0){ + paddingValue = $(parseInt(paddingValue.replace("px",""))).toEm(); + } + if(paddingValue == "1em"){ + return true; + } + else + return false; + } + + /* Making header looks similar to sakai other sorting tables' header */ + function decorateHeaders(){ + var cssObj = { 'text-decoration': 'underline', + 'color':'#3355bb' + } + $('.header').each( function () { + var str = $(this).text().split(" "); + if(str[0] == "Authored"){ + str[0] = "Author"; + } + if(str[0] != ""){ + $(this).css(cssObj); + $(this).attr("title","Sort By " + str[0]); + } + }), + + $('.header').hover( + function () { + $(this).css("color","#0000ee"); + }, + function () { + $(this).css("color","#3355bb"); + } + ); + } + + /* get original table cache before it's sorted to + record and mark the parent/desendant relationship in each row. + Need to remember row index for the children of each children node */ + function buildOriginalCache(table) { + var totalRows, cache; + totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0, + cache = {row:[], childrenId:[]}; + var parentId = null; + var descendantCount; + + for (var i=0;i < totalRows; ++i) { + var c = table.tBodies[0].rows[i], cols = []; + if (isParent(c)) { + descendantCount = 0; + parentId = "parent" + i; + c.parentId = "parent" + i; + for(var k=i+1; k= 0){ + var pixels = parseInt(leftpadding.replace("px", "")); + paddingDigitValue = parseInt($(pixels).toEm().replace("em","")); + } + else{ + paddingDigitValue = parseInt(leftpadding.replace("em","")); + } + + // while (next ) is a grandchild, save the array index in the originalCacheTable for the row; + while(paddingDigitValue > 1){ + grandChildrenCount++; + grandChildrenRows.push(tempRowIndex); + next = next + 1; + tempRowIndex = tempRowIndex +1; + row = table.rows[next]; + leftpadding = $(row).find("td").eq(1).css("padding-left"); + if(leftpadding.indexOf("px") >= 0){ + var pixels = parseInt(leftpadding.replace("px", "")); + paddingDigitValue = parseInt($(pixels).toEm().replace("em","")); + } + else{ + paddingDigitValue = parseInt(leftpadding.replace("em","")); + } + } //end of while loop + c.grandChildrenCount = grandChildrenCount; + c.grandChildrenRows = grandChildrenRows; + }// end of isChild + }//end of isDescendant + cache.row.push($(c)); + }; //end of first for loop + return cache; + };//end of buildOriginalCache + + /* Record order of sorted list for children nodes */ + function buildSortedCache(table){ + var totalRows, cache; + totalRows = (table.tBodies[0] && table.tBodies[0].rows.length) || 0, + cache = {row:[]}; + + for (var i=0;i < totalRows; ++i) { + var c = table.tBodies[0].rows[i]; + var descendantRow = []; + if (isParent(c)) { + var parentId = c.parentId; + var totalDescendant = c.descendantCount; + if (totalDescendant != 0) { + var descendant = 0; + for(var k=0; k 0 || m == 0; m--){ + //get child row number in the sorted cache + var childRow = this.descendantRow[m]; + //append child + $(cache.row[childRow]).insertAfter(this); + } + } + else{ + //Get children sorted, keep logic of grandchildren and deeper descendents. + for (var m = this.descendantCount -1; m > 0 || m == 0; m--){ + //get children row numbers in the sorted cache + var childRow = this.descendantRow[m]; + var leftpadding; + var paddingValue; + //make it working cross browsers and versions hopefully, fixed IE7 & 8 + leftpadding = $(cache.row[childRow][0].children[1]).css("padding-left"); + if(leftpadding.indexOf("px")>=0){ + var pixels = leftpadding.replace("px",""); + paddingValue = $(parseInt(pixels)).toEm(); + } + else{ + paddingValue = leftpadding; + } + if(paddingValue == "1em"){ + // Insert child + var insertedRow = $(cache.row[childRow]).insertAfter(this); + // Find its grandchildren and insert them in the original order + var count = cache.row[childRow][0].grandChildrenCount; + if(count != 0 ){ + var rowIndexArray = cache.row[childRow][0].grandChildrenRows; + for (var n = 0; n < count; n++){ + insertedRow = $(original.row[rowIndexArray[n]]).insertAfter(insertedRow); + } + } + + } + }//end of for + } + } + }); + }// end of buildForumSortedTable + + /* build original table cache to mark parent-child relationship */ + cacheOriginalTable = buildOriginalCache(this); + + /* Calling jquery library tablesorter plugin function to do general sorting */ + /* disable sorting on first column */ + $(this).tablesorter({ + headers:{ + 0:{ sorter: false} + } + }); + + //Showing headers clickable and sortable like sakai style + decorateHeaders(); + + $this = $(this); + + /* + * add another click handler doing customized sorting for forum topic threads table + * except first column + */ + + $(this).find("th:gt(0)").click(function(e){ + + //IE supports srcElement, not currentTarget + if(!e.currentTarget) + e.currentTarget = e.srcElement; + + //If sorted by Author, sort all authors regardless of parent/descendent relationship. + + //Comment out following 3 lines if you would like to keep parent/descendent relationship + //and keep Author sorted within each level. + + if ($(e.currentTarget).get(0).column == 2) { + return false; + } + + //Sort and keep parent/descendent relationship after sorting + //Set timer to be 10 ms to be executed later than executing functions in tablesorter; this + //fixes latency issue with IE browser.bugid:3565 + setTimeout(function() { + //build cache for normally sorted table + cacheSortedTable = buildSortedCache($this[0]); + //build forum special sorted table + sortedByThread = ($(e.currentTarget).get(0).cellIndex) ==1; + buildForumSortedTable($this[0],cacheSortedTable,sortedByThread,cacheOriginalTable); + },10); + return false; + }); + + + /* + * The sakai expand/collapse will reload the tables which wipe out the sorted rows when expending/collapsing. + * We added handler for expand/collapse when clicking on first column header -- the expand/collapse icon + * to overwrite the out of box behavior, so that the table still remains sorted and sorting direction indicator + * still shows up while it's expanding/collapsing. + */ + + var imageCollapseUrl = "/messageforums-tool/images/collapse.gif"; + var imageExpandUrl = "/messageforums-tool/images/expand.gif"; + + var expandCollapseCol = $this[0].tHead.rows[0].cells[0]; + + $(expandCollapseCol).find("a").replaceWith("Expand All/Collapse All"); + $(expandCollapseCol).css("cursor", "pointer"); + var flip = 0; //indicates click times for expand all/collapse all + + $(this).find("th:eq(0)").click(function(e){ + flip++; + + if(flip %2 == 0){ + $($this[0].tBodies[0].rows).not(".hierItemBlock").hide(); + //Sync icons showing consitent for collapsing + $(e.target).replaceWith("Expand All/Collapse All"); + $(".attach img").attr({'src': imageCollapseUrl, 'alt':'Expand/Collapse', 'title':'Expand/Collapse'}) + } + else { + $($this[0].tBodies[0].rows).not(".hierItemBlock").show(); + //Sync icons showing consitent for expanding + $(e.target).replaceWith("Expand All/Collapse All"); + $(".attach img").attr({'src': imageExpandUrl, 'alt':'Expand/Collapse', 'title':'Expand/Collapse'}); + } + mySetMainFrameHeight($('iframe', parent.document)[0].id); + return false; + + }); + + }); + +}; //end of jquery threadsSorter plugin Property changes on: messageforums-app/src/webapp/js/forumTopicThreadsSorter.js ___________________________________________________________________ Added: svn:keywords + Date Revision Author HeadURL Id Added: svn:eol-style + native Index: messageforums-app/src/webapp/jsp/discussionForum/message/dfAllMessages.jsp =================================================================== --- messageforums-app/src/webapp/jsp/discussionForum/message/dfAllMessages.jsp (revision 79843) +++ messageforums-app/src/webapp/jsp/discussionForum/message/dfAllMessages.jsp (working copy) @@ -14,6 +14,14 @@ + + + <%--// //plugin required below