If you’ve ever created a view panel for a categorized view that includes a Totals column, you’ve probably noticed that the view is not indented properly. This is pretty common to find on modernization projects where views from an existing Notes client application are being surfaced in XPages. In this article, I’ll show how you can fix that with client side JavaScript and dojo.
Standard Categorized View Indentation
I created a categorized view with a long category value for my database of NFL football teams. When I create a view that just includes that category column, along with the location and team name fields, it indents the way I would expect.
Categorized View with a Totals Column
However, if I add a totals column to the view and create a view panel for the view, the formatting is pretty awful — especially if I have a long category value.
Since it has to display something else (the totals column) in the category row, it breaks the whole row up into cells that correspond to each column.
<tr> <td class="xspColumnViewStart"> <div class="xspColumnViewStart"> {Source for expand/collaps button removed} <a id="view:_id1:viewPanel1:0:viewColumn1__shrink:1link" style="cursor:pointer;" class="xspColumnViewStart"> American Football Conference - East Division</a> </div> </td> <td class="xspColumnViewMiddle"></td> <td class="xspColumnViewMiddle"></td> <td class="xspColumnViewEnd"> <span id="view:_id1:viewPanel1:0:viewColumn4:_internalViewText" class="xspTextViewColumn">4</span> </td> </tr>
Compare this with the source of the category row in the first screen shot above, where you can see that it sets the colspan attribute to cover all cells in the row, with provides proper indentation.
<tr> <td colspan="3" class="xspColumnViewStart"> <div class="xspColumnViewStart"> {Source for expand/collapse button removed} <a id="view:_id1:viewPanel1:0:viewColumn1__shrink:1link" style="cursor:pointer;" class="xspColumnViewStart"> American Football Conference - East Division</a> </div> </td> </tr>
Dojo to the Rescue
So, what we need is a way to get the category column to span across multiple cells so that the values below it can be indented properly. Fortunately, with no extra configuration, we have dojo libraries available.
Stay Classy
The cleanest way I’ve found to help the necessary code identify the category cells easily is to apply a class to the category column in the view.
However, we need to apply it conditionally, because the class will be applied to the first table cell in every row in the view — even the ones that aren’t categories.
On the category column, I set the style class’s computed value to this:
if (rowHandle.isCategory()) { return 'category'; } else { return ''; }
This assumes that I have set my view panel’s var to rowHandle.
Code to Fix the Indentation
XPages and Custom Controls have an onClientLoad event that we can use to run code to process the view after its loaded.
This code will work on each categorized row. It will remove the blank cells between the category value and the totals column and add a colspan attribute to the category cell to use up the blank space, thereby indenting the rows below more naturally.
// Get a list of all rows in the view panel. dojo.query('.xspDataTableViewPanel table tr').forEach(function(nodeRow, indexRow) { // Locate the category cells within the context of the view rows dojo.query('td.category', nodeRow).forEach(function(nodeCat){ // Execute a search on the parent node (row) and remove all cells until data is found var emptyCells = 0; var keepCounting = true; dojo.query('td', nodeRow).forEach(function(nodeTD, indexTD){ // Check all non-category cells until a non-empty cell is found if ((keepCounting) && !dojo.hasClass(nodeTD, 'category')){ if (nodeTD.innerHTML == '') { emptyCells +=1; dojo.destroy(nodeTD); } else { keepCounting = false; } } }); // Add a colspan attribute to the category cell (1 + [# of empty cells]) dojo.attr(nodeCat, 'colspan', 1+emptyCells); }); });
Here is the result:
Handle View Paging
The onClientLoad code works great when the view first loads, but the problem is that views are generally updated with partial refreshes as the user pages through, so we need to run the code on that event as well.
One option is to trigger a full refresh when the view is paged through, by selecting the view pager and disabling the partial refresh option, but there’s a better way.
Using the technique I demonstrated in my last post, all I need to do is move the onClientLoad event handler into the view panel and then this code will run when the page is loaded and when the user pages through the view.
Have you tackled this problem in a different way? If so, I’d love to hear how you did it.
