Using jQuery.resize to Enhance Responsive Design

Twitter Bootstrap provides some convenient grid scaffolding to use for responsive design applications.  The xs-sm-md and lg classes allow you to adjust your design based on the screen real estate available.  Sometimes, however, this isn’t enough.  Recently when working on laying out a couple portlets horizontally on a page I discovered that I needed some additional control over the display (or lack thereof) of some columns in my portlets. In short, I wanted to hide some of those columns when the width of the screen was at a certain horizontal threshold.  The standard bootstrap classes were not granular enough for my needs.    Time for some jQuery wizardry.

Plan:

  1. Use $(window).resize to listen for the window size changing
  2. Check the size of the desired elements against certain thresholds
  3. Perform DOM manipulation as necessary to add/remove elements
	<div class="panel panel-primary panel-heading-custom">
		<div class="panel-heading">
			<h2 class="">
				Extending Responsive Design with jQuery.resize()
			</h2>
		</div>
		<div class="row row1">
			<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6 panel-body-header">Panel Left</div>
			<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6 panel-body-header">Panel Right</div>
		</div>
		<div class="row">
			<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6 panel-body-header">
				Width:<span id="panel-left-width"></span>
			</div>
			<div class="col-lg-6 col-md-6 col-sm-6 col-xs-6 panel-body-header">
				Width:<span id="panel-right-width"></span>
			</div>
		</div>
		<div class="row">
			<div id="panel-left" class="col-lg-6 col-md-6 col-sm-6 col-xs-6 panel-body-custom">
				<div class="panel-body-inner-left">
					<table>
						<thead>
							<th class="c1">Column 1</th>
							<th class="c2">Column 2</th>
							<th class="c3">Column 3</th>
							<th class="c4">Column 4</th>
						</thead>
						<tbody>
							<tr>
								<td class="c1"></td>
								<td class="c2"></td>
								<td class="c3"></td>
								<td class="c4"></td>
							</tr>
						</tbody>
					</table>
				</div>
			</div>
			<div id="panel-right" class="col-lg-6 col-md-6 col-sm-6 col-xs-6  panel-body-custom">
				<div class="panel-body-inner-right">
					<table>
						<thead>
							<th class="c1">Column 1</th>
							<th class="c2">Column 2</th>
							<th class="c3">Column 3</th>
							<th class="c4">Column 4</th>
						</thead>
						<tbody>
							<tr>
								<td class="c1"></td>
								<td class="c2"></td>
								<td class="c3"></td>
								<td class="c4"></td>
							</tr>
						</tbody>
					</table>
				</div>
			</div>
		</div>
	</div>

The following bit of javascript will check the size of the panels and remove a column if the width of the panel becomes less than 500 (and then 400) pixels.

$('document').ready( function() {
	// initialize panel width display
	resizer();

	$(window).resize(function(){
	    resizer();
	});
});

function resizer() {
    $('#panel-left-width').text($('#panel-left').width());
    $('#panel-right-width').text($('#panel-right').width());
    plw = $('#panel-left').width();
    prw = $('#panel-right').width();
    $('#panel-left-width').text(plw);
    $('#panel-right-width').text(prw);
    if (plw &lt; 500) {
	$('.c4').hide();
    } else {
	$('.c4').show();
    }
    if (prw &lt; 500) {
	$('.c4').hide();
    } else {
	$('.c4').show();
    }
    if (plw &lt; 400) {
	$('.c3').hide();
    } else {
	$('.c3').show();
    }
    if (prw &lt; 400) {
	$('.c3').hide();
    } else {
	$('.c3').show();
    }
}

Simple and effective. This could be extended to other uses as well, taking into account different devices in addition to screen size.

See a working demo here.

An Effective Use of the ‘attr’ Property on xPage Elements

JQuery is a common javascript library today that can be used for any number of client side operations.  Sometimes it can be difficult to construct the right selector with xPage elements due to the way the engine renders the id tag.  Sure it is common to use the elements class attribute as a selector but sometimes that can only serve to muck up your design as you may already have several style sheets used for design.  That is where I have found a nice use for the ‘attr’ tag in xPage elements.

Consider the following use case.  I have a timesheet form that has a repeat control nested inside another repeat control.  The first repeat loops through the days of the week while the second loops through a set number of possible project entries per day.  I want to have all but the first row hidden for each day on load of a new timesheet, then have buttons to add a line and remove a line.  I can use jQuery but instead of trying to add the row number to my class attribute of each row, or trying to parse out the id I can use a new attribute called “rowNum” and grab that on the client side.

xPage Markup

<xp:repeat id="timesheetCard" indexVar="idx" var="rowNum" repeatControls="true" rows="7">
 <xp:this.value><![CDATA[#{javascript:var arrRow = [0,1,2,3,4,5,6]; return arrRow;}]]></xp:this.value>
 <xp:tr>
 <xp:this.attrs>
 <xp:attr name="row">
 <xp:this.value><![CDATA[#{javascript:rowNum}]]></xp:this.value>
 </xp:attr>
 </xp:this.attrs>
 .
 .
 .
 </xp:tr>
 </xp:repeat>

So here I have used the ‘attr’ property of the xpage element to calculate an attribute I called “row”.  I could have done this using straight HTML but by converting the tag to an xPage tag I could access it though the designer properties section and get myself a javascript editor to use.

Now, on the client side wherever I want to have access to this row I can access it via jQuery as shown here:

$('[row=5]').show()

The above code will find the row with a row=5 and show that row.  This would be useful if I had that row hidden previously.

The above is not the only possible use case for ‘attr’.  You can use it anywhere you want to add additional metadata to your markup for use client-side.  A nice tool to have in your toolbox!

When ‘font-weight: bold’ Is Not Quite Right

Lately I’ve been doing a lot of front-end UI work and in doing so there have been situations where I want to call attention to a piece of text.  Sometimes displaying the text in a BOLD font-weight is just right, but other times that feels more like cracking a nut with a sledge hammer.  CSS has supported a more granular approach to the font-weight attribute for some time but it’s a feature that can be easily forgotten.  In addition to normal, bold, and italics designations, you can use relative terms like “lighter” or “bolder” which apply the weight relative to the parent element. Perhaps more useful is the numeric designations available.  CSS supports font-weight’s of 100 to 900 in even increments of 100.   The catch is that the font you are using needs to support that level of granularity.

I had planned to code out a whole series of examples but I found an article that has already done this so no need to go through that exercise.  Read here to see examples of what this looks like and more details on how it’s applied to fonts that only support the normal and bold font-weights.

CSS Tricks: Font-Weight

Converting HTML Tags to XPage Tags

As xPages becomes a common platform for other modern web frameworks, you may find yourself writing a lot of standard HTML (such as Twitter Boostrap markup) in your Domino Designer source panel.  Here is a convenient reminder that most HTML tags can be converted to xPage tags by adding the xp: prefix.

So,

<div>...</div>

becomes

 

 <xp:div>...</xp:div>

Now it may not be immediately obvious why this would be a useful pattern but xPage tags have with them all the API hooks that are available for the standard xPages controls.  One use case could be to leverage server side logic to selectively render certain parts of your code.   For example you might have some mark up that resembles this:

<div id="show-when-editable" class="row">
<div class="col-md-2">...</div>
<div class="col-md-4">...</div>
<div class="col-md-6">...</div>
</div>
<div id="hide-when-editable" class="row">
<div class="col-md-2">...</div>
<div class="col-md-4">...</div>
<div class="col-md-6">...</div>
</div>

If you want to show the first div in edit mode and the second otherwise you could convert the above to the following:

<xp:div styleClass="row">
<xp:this.rendered><![CDATA[#{javascript:document.isEditable()==true}]]></xp:this.rendered>
<div class="col-md-2">...</div>
<div class="col-md-4">...</div>
<div class="col-md-6">...</div>
</xp:div>
<xp:div styleClass="row">
<xp:this.rendered><![CDATA[#{javascript:document.isEditable()==false}]]></xp:this.rendered>
<div class="col-md-2">...</div>
<div class="col-md-4">...</div>
<div class="col-md-6">...</div>
</xp:div>

A couple things to note about this type of tag conversion:

–  <xp:div> tags don’t respect normal HTML attributes.  For this reason, you need to also convert class="class-name" to styleClass="class-name"

– To use other attributes you can add an attribute block.  For example, code to add a data-title="My Title" to your tag would look like this:

<xp:this.attrs>
<xp:attr name="data-title">
<xp:this.value><![CDATA[#{javascript:'My Title'}]]></xp:this.value>
</xp:attr>
</xp:this.attrs>

– I haven’t tested this with all legal HTML tags, so there may be some incompatibilities but it does work well with most of the standard building block tags used for construction Boostrap grids and tables.  If you know of other limitations, please feel free to share.

Using a placeholder with Select2 in xPages

Select2 is a fantastic component that provides for a very nice alternative to the classic combo box.  While working with it this week I ran into an issue where I was unable to get the placeholder option to work.  Turns out you need to seed your values array with a null value or else the placeholder won’t display.

Using this select2 initialization code:

//Initialize Select2 Drop Down
$(‘.select2’).select2( {
placeholder: “Select value”,
allowClear: true,
minimumResultsForSearch:-1
})

This works:

var arr = new Array(“”);
var values = @DbColumn()  \\Insert your own code here
var list = arr.concat(values);
return list;

This does not work:

var values = @DbColumn()  \\Insert your own code here
return values;

A subtle but important distinction.  Thanks to Johnny Oldenberger for this tip.  I found this helpful information on one of his blog posts

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).