jQuery Formatted xPage from JSON Source

In my last post I detailed a technique I used to hack the design of the xPages Extension Library DataView control to enhance a printable page of our corporate directory. In this post I’ll show an alternative method that does not use any xPage controls.  In this example all the heavy lifting is done using javascript and jQuery to parse out a JSON formatted Notes view. To format the printable display I used another javascript library, Dynatable.js, which is very nice table formatting library for jQuery.
To start off I added the following HTML to my xpage by editing the source directly.
<body>
<div>
<div id="location-template">
<table>
<thead>
<th class="dynatable-head" data-dynatable-column="details">Details</th>
</thead>
<tbody class="sitebody"></tbody>
</table>
</div>
<div id="list-template">
<table>
<thead>
<th class="nameHdr dynatable-head" data-dynatable-column="name1">Name</th>
<th class="nbrHdr dynatable-head" data-dynatable-column="ext1">Ext</th>
<th class="nameHdr dynatable-head" data-dynatable-column="name2">Name</th>
<th class="nbrHdr dynatable-head" data-dynatable-column="ext2">Ext</th>
<th class="nameHdr dynatable-head" data-dynatable-column="name3">Name</th>
<th class="nbrHdr dynatable-head" data-dynatable-column="ext3">Ext</th>
<th class="nameHdr dynatable-head" data-dynatable-column="name4">Name</th>
<th class="nbrHdr dynatable-head" data-dynatable-column="ext4">Ext</th>
</thead>
<tbody class="listbody"></tbody>
</table>
</div>
</div>
<div class="list-content"></div>
</body></div>
<div dir="ltr">
In this code I have specified two table templates: location-template for the location header information on my print list and list-template for the phone number details (name and number).  These div’s will display on my xPage by default but I will hide them at the end by adding some CSS styling. It’s basically straight forward table html with the important stuff happening on the table column definitions.  Each column contains the dynatable-head class and the data-dynatable-column attribute.  Those are for the Dynatable.js use as we will see later.
My data source is just a simple notes view categorized for my purposes by location and then phone number type (conference room, production phone, individual, etc…).  Very much the same view used in the example in my previous post.  As you know you can render a view in JSON format by using the specific url parameters for JSON. So my view URL becomes something like this where ?openview is replaced with
?readviewentries&outputformat=JSON.
I placed all my code in the onClientLoad event but you could just as easily break it out into script libraries.   To start I need to use the jQuery.getJSON()function to read in the source data.  When successful, the callback function is executed.  That callback function is using the jQuery.each() function to iterate through each JSON entry, calling a callback function itself for each entry in the JSON object.  It is that inner function that will house all the processing necessary to generate the desired output.
$.getJSON("xPrintListCat?readviewentries&count=5000&outputformat=JSON",function(data) {
  $.each(data.viewentry, function(index, obj) {
    .
    .
    << output generation code goes here >>
    .
    .
  })
})
As I iterate through the entries in the view, I will want to handle them differently depending on the type of row.  If it’s the top level category I will know I have a new location to process.  If I have a second level category I have a new type and if I have a third level category I will have a name and number to process.  To determine where I am at for any given entry it’s helpful to look at what the browser sees. Adding a console.log(data) between the .getJSON and .each functions I can see the entire representation of the view as seen by the browser.
View DOM Explaination
So for each entry, I can calculate the level (1,2 or 3) of categorization and the position (exact location in the view hierarchy).
var pos = data.viewentry[index]["@position"]
var level = data.viewentry[index].entrydata.length;
The logic is that if I have a level 1 entry, I’m going to capture the location in a variable and add a horizontal rule to the main content area, the DIV with a class of “list-content”.  If I have a level 2 entry, I need to check to make sure it is a phone number category and not the level 2 entry for the location details.  To do this I’m using a regular expression to match on the position.  If the position is in the format of n.1 I know that I’m at the first level 2 category for that location. This is due to my view sorting.
if (pos.match("^[0-9]+\.1") == null) {
  //Not in the format of n.1, process as if a phone number category
}
Since each phone category is going to correspond to its own html table, I need to know how many rows are in that table before I start building the data array so that I can apply the data in order to achieve alphabetic sorting down each column and then to the next rather than alphabetic sorting across the rows.  To calculate the number of rows for each table I use the @descendants parameter of the view entry and then apply the Math.ceil function to the number of descendants.
entries = data.viewentry[index]["@descendants"];
rows = Math.ceil(entries/4);    //where 4 is the number of columns
Finally all the action happens if I have a level 3 entry.  Basic logic is that if I have a level 3 entry for the location details I will create a simple one row table for the location details and add it to the list-content DIV.  If I have a level 3 entry for a phone category I will add that entry to the data array for the proper column.  When I get to the last entry for that category I render a new table to the list-content DIV and then apply the data array to that table.
var template = $($("#list-template").html())
//clone the template and add to the DOM
listContent.append(template.clone().attr('id',"loc-" + location + "-" + type))
//get the table that was just added
var myTable = $('#loc-' + location + "-" + type)
//add table header 
myTable.prepend("<caption class=\"typeHeader\">" + phoneType + "</caption>")
//render table to the DOM using Dynatable.js
myTable.dynatable( {
  dataset : {
  records : finalData},
  features : {
  paginate : false,
  sort : false,
  search : false,
  recordCount : false}
});
After iterating through each entry I finish with some clean up CSS adding some classes to the table and hiding the templates.
Here is the finished product in action.  A button at the bottom of the page will reveal the entire code.
CONCLUSION
This technique acts very much like an xAgent in principal where you start with a blank xPage as your canvas and write data to it as you go through your processing.  It may not ultimately be the best solution for this particular use case but it demonstrates how data can be sourced from JSON, manipulated, and then rendered to the DOM using CSJS and jQuery.  The use of the external Dynatable.js library is interesting too and my use of it in this example barely scratches the surface of what that library is capable of producing.  Your mileage may vary but I found this scaled up just fine for our use case of about 2,000 documents in the source view.
Advertisements