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.

Hacking the xPages Data View CSS to Enhance Display

A few weeks ago I was tasked with creating a new printable version of our corporate directory for use in production areas where phones were not located near workstations.  Until this point this list had been maintained manually.

After looking around at a few options I settled on the Data View control from the xPages Extension Library.  This control allows one to easily set the number of columns, which in my case was important because they wanted specifically a four column display.

Our corporate directory has three data types in play here:  location documents, individual person documents and documents for all other non-individual phone numbers.  First I set up the source view categorized first by location and secondly by the phone category.   It looked something like this (the data below has been changed for privacy).

Source View in Notes

Source View

Connecting that data source up to the data view control yielded initially a display that was far from what I was seeking.

Unformatted Data View

With some CSS overrides and jQuery DOM manipulation I was able to convert that to this:

Data View Formatted

Ahhh, much better.  Below is the custom CSS that I used.  The first six are just custom classes I used to style the site headers, category headers as well as the detail sections.  Basic stuff.   Then I used two classes that were already included in the xPages extension library theme I was using.  Adding them to my custom CSS allowed me to override some margins and spacing. Finally the last two classes are used by the jQuery code to enhance the breaks between locations.

/* Custom Classes */
.catSite {
font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif;
font-size: 18px;
color: #365F91;
}
.catCategory {
font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif;
font-weight: Bold;
font-size: 14px;
color: #365F91;
padding-top: 8px;
}
.siteDetail {
font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif;
font-size: 12px;
color: #365F91;
}
.nbrDetail {
font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif;
font-size: 12px;
}
.nameCell {
width: 165px;
border-bottom: 1px solid #ddd;
}
.nbrCell {
width: 40px;
border-bottom: 1px solid #ddd;
text-align: center;
}
/* Overrides to Existing Classes */
.lotusTable td {
padding: 0px;
border-top: 0px;
}
.lotusTable th {
padding-top: 0px;
border-top: 0px;
}
/* Type Selector Formatting */
h2 {
font-family: Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif;
font-size: 24px;
padding-left: 10px;
}
hr {
background: #8C8C8C;
clear: both;
float: none;
width: 100%;
height: 1px;
margin: 0 0 0.6em;
border: none;
}

The jQuery code I used is run from the onPageLoad event.

//jQuery magic to enhance formatting by manipulating DOM
//Removes some extra white space at the top of the page
$("thead").remove();
//Remove Top Level Categorization
$(".catLocation").text("");
//Replace + with horizontal rule for each site
//this sets each site off from the previous
var s1 = $(".catSite");
$.each(s1, function( index, value ) {
  var s2 = $(this).text()
  $(this).html(s2.replace("+","<br><hr>"))
});
//remove the nameCell and nbrCell class from the site class elements to eliminate extra underline
//replace the address delimiter tilde from view data with break tag to start a new line for each address data
var d1 = $(".siteDetail");
var d2 = d1.parent()
d2.removeClass("nameCell")
d2.next('td').removeClass("nbrCell")
$.each(d1, function( index, value ) {
  var d3 = $(this).text()
  $(this).html(d3.replace(/\~/g,'<br>'))
});
//add horizontal rule at the end of the list
//$("table").append("<BR><HR>")

Really not very much code to achieve a nice result.   Of course, there is still the overhead of the extension library code and the CSS that is loaded there is large and cumbersome to override in places.  If you wish for more styling flexibility there are other options.   In my next post (hopefully next week) I will demonstrate how to use a JSON feed from a Notes view to write out the same data without using the Data View control (hint: it also involves jQuery and a special jQuery plugin).

A Stoplight Status Component Using Bootstrap

I had a request to create a “stoplight” status section in a recent application.  The purpose of this section would be for the users to register a red, yellow, green status to a number of categories on a product development information form.

Using a combination of Twitter Bootstrap and jQuery I was able to create a nice looking component that provided the exact functionality they were hoping to achieve.

Here is a link to the demo page where you can see this in action.

Button Code

<div>
<a id="cat_1_btn" class="cat1" href="javascript:statusToggle('cat1')" role="button">
Strategic Support</a>
<a id="cat_2_btn" class="cat2" href="javascript:statusToggle('cat2')" role="button">
Competitive Landscape</a>
<a id="cat_3_btn" class="cat3" href="javascript:statusToggle('cat3')" role="button">
Product Development</a>
</div>

statusToggle Code

All the below function does is to check the current CSS class of the button and toggle it to the next one.  Very simple.

function statusToggle(category) { 
 if ($("." + category).hasClass("btn btn-success btn-xs btn-custom")) { 
   $("." + category).removeClass("btn btn-success btn-xs btn-custom")
   $("." + category).addClass("btn btn-danger btn-xs btn-custom")
   $("." + category + "Status").val("danger")
 } else if ($("." + category).hasClass("btn btn-danger btn-xs btn-custom")) { 
   $("." + category).removeClass("btn btn-danger btn-xs btn-custom")
   $("." + category).addClass("btn btn-warning btn-xs btn-custom")
   $("." + category + "Status").val("warning")
 } else if ($("." + category).hasClass("btn btn-warning btn-xs btn-custom")) { 
   $("." + category).removeClass("btn btn-warning btn-xs btn-custom")
   $("." + category).addClass("btn btn-success btn-xs btn-custom")
   $("." + category + "Status").val("success")
 }
};

The CSS classes set by the above code is standard Twitter Bootstrap CSS. The final btn-custom class only serves to add some margins and bold weight to the button text.  Nothing special there.

Interestingly enough, the above code was initially implemented in an Oracle APEX application.   Since it was all javascript and CSS I was able to easily port the code over to a sample xPages application without changing really anything.