Improve your typeAhead with “xTypeAheadify.js” – a jQuery plugin specifically for XPages

In this article I will introduce the xTypeAheadify.js jQuery plugin which I have created and submitted to openNTF.org. xTypeAheadify works in conjunction with the XPages typeAhead control to created an enhanced user experience above and beyond the out of the box control. The plugin is an evolution of the original enhancement I published back in February 2012.

I went down the path of creating a jQuery plugin rather than using an XPages custom control because of the power of the selectors. This method provides greater flexibility in assigning the plugin to specific fields and also allows for other enhancements via the jQuery chaining capability.

Introduction

The current out of the box XPages typeAhead control is a very simple and easy way for a developer to provide real-time lookup capability within their XPages. This capability meets lots of different use cases, simplifies the user interface and most importantly increases data fidelity. Unfortunately the capability falls a little short in overall user experience due to a lack of feedback to the user that a lookup is being performed and there is no indication that no results were found.

xTypeAheadify.js

xTypeAheadify adds the following fully customizable enhancements to the out of the box typeAhead control:

  • waitingIcon – A visual indicator when the user is typing to indicate a lookup is in progress
  • failIcon – A visual indicator to indicate to the user that no results are found
  • toolTipMsg – A optional popup message when no results are found to assist the user
  • tooTipPosition – A positional optional for the popup message
  • continueAfterFail – A option to prevent further typing once a non-matching string has been entered
  • visualTimeout – A timeout option allowing the developer to determine how long the icons are displayed for
xTypeAheadify custom waiting icon
xTypeAheadify custom waiting icon
The xTypeAhead custom icon showing no results and the visual popup message
Two examples of the xTypeAhead custom icon showing no results and the visual popup message

Demonstration

Click here to see working demonstrations of xTypeAheadify.js

Get the code

GitHub

The code is currently posted on github and can be checked out and modified by anyone who feels the need to 🙂

OpenNTF

The project is released on OpenNTF

http://www.openntf.org/internal/home.nsf/project.xsp?action=openDocument&name=xTypeAheadify.js

What is xTypeAheadify.js

xTypeAheadify.js is a JavaScript library which is added to your notes database as a JavaScript Library. It added to the XPage (along with jQuery) as an xp:resource

	<xp:this.resources>
		<xp:script src="js/jquery-1.7.1.min.js" clientSide="true"></xp:script>
		<xp:script src="/xTypeAheadify.js" clientSide="true"></xp:script>
	</xp:this.resources>

Default functionality

The plugin has five optional, customizable, parameters but in its most basic format the plugin is instantiated using one line of code. The following code will add a default waiting icon, default fail icon and default dojo popup to the inputText1 typeAhead field on the form.

			$("INPUT[id$='inputText1']").xTypeAheadify()

Chainable
The plugin is “chainable” and can be integrated into any standard jQuery method call chain

			$("INPUT[id$='inputText1']").xTypeAheadify().css('color', 'blue')
xTypeaheadify is a chainable plugin
xTypeaheadify is a chainable plugin

Customizable

Using the parameters described above and demonstrated below, the developer has control of what is displayed and when.  This code would be added as  XML markup at the bottom of your XPage source tab

<script>
	//This runs BEFORE the onLoad and before the dojo changes all the HTML
	//because we are getting ALL typeAheads then we need to set a flag to easily tag off
	//in this case the .getTypeAhead
	$("[dojoType$='TypeAhead']").addClass('getTypeAhead')
</script>

<xp:eventHandler event="onClientLoad" submit="false">
	<xp:this.script><![CDATA[
		$(".getTypeAhead INPUT[role='textbox']").xTypeAheadify({
			continueAfterFail: false,
			failIcon: 'Stop.png',
			waitingIcon : "loading.gif",
			visualTimeout : 5000,
			toolTipMsg : "You must select from the displayed list",
			toolTipPosition : "['above', 'after']" //"above", "below", "after", "before"
		})

	]]>
	</xp:this.script>
</xp:eventHandler>

How does it work?

I have added the xTypeAheadify.js to my demonstration database as a JavaScript Library rather than an attached js file. This is to facilitate easier reading of the code and easier accessibility for developers to make modifications to the code.

To start, we create a typeAhead field in your XPage in this case I created a field with an @dbColumn as the suggestions.

Creating a basic XPage typeAhead

We add the the xTypeAheadify.js either as a Javascript Library or a js attachment in the database.

Then you add your xTypeAheadify to your XPage in the onClientLoad event as shown below…..

Using selectors to add the plugin to the typeAhead fields

Individual Fields

You can either add the effect to the typeAhead field individually (where inputText1 is the name of your field)

<xp:eventHandler event="onClientLoad" submit="false">
	<xp:this.script><![CDATA[
		$("INPUT[id$='inputText1']").xTypeAheadify()
	]]></xp:this.script>
</xp:eventHandler>

To get more than one field by id we change the selector to select the fields using the comma to separate selectors (here selecting inputText1 and inputText3)

			$("INPUT[id$='inputText1'], INPUT[id$='inputText3']").xTypeAheadify()

All typeAhead Fields
We can select all on the page using the following code. This is more complex because it takes two steps to ensure we attach to just the typeAhead fields and not any other fields on the page. If you view the source of your XPage (with a typeAhead on it) you will see that there are attributes dojoType=”ibm.xsp.widget.layout.TypeAhead” on the fields. Unfortunately this is lost when the widget is instantiated onClientLoad. Using the initial

	$("[dojoType$='TypeAhead']").addClass('getTypeAhead')

 

<input type="text" id="view:_id1:_id2:_id47:inputText1" name="view:_id1:_id2:_id47:inputText1" style="font-size:12pt" class="xspInputFieldEditBox" dojoType="ibm.xsp.widget.layout.TypeAhead" store="view__id1__id2__id47_typeAhead1">1<br id="view:_id1:_id2:_id47:br1">

we are able to tag each typeAhead field with the getTypeAhead class. This in turn is transformed into the following code by the widget.

Then we are able to select in the onClientLoad using the class and the INPUT text box inside it

	$(".getTypeAhead INPUT[role='textbox']") //select all INPUT with attribute "role" inside the elements with class .getTypeAhead
Firebug display of the typeAhead field capability
Firebug display of the typeAhead field capability

Images

If you are adding images to the database yourself they should be added either as image resources or through the WebContents folder.

Event Handling

The plugin works by adding custom events to the selected field based on the user interaction with the page. The plugin adds the following:

  • onblur
    • triggered when the user clicks out of the field.
    • Removes visual indicators from the webpage
  • keydown
    • triggered when the user starts to type
    • displays the visual indicators if appropriate
    • check to see if a fail has been encountered and if the parameter is set – prevents further typing
  • keyup
    • checks to see if the field is blank and clears all visual indicators if they are displayed
  • onchange
    • if the user selects a value from the displayed list the visual indicators are removed

The plugin also intercepts the dojo xhr request to the database and if zero results are returned then a visual indicator is displayed along with e a dojo tooltip (if the parameters are set appropriately)

Source Control Code repository

The full code for the plugin is available on github and can be checked out and modified if you want to.

https://github.com/markyroden/xTypeAheadify/blob/master/xTypeAheadify.js

Conclusion

I hope that you will find use for this plugin. I have already received positive feedback from the customers who I have implemented the capability for and I look forward to hearing what you think and how it can be improved further.

Next?

This works nicely for single select typeAhead fields but the out of the box multiple typeAhead is, quite frankly, ugly…..adding a custom separator and just increasing the text in the field.

typeAhead multi-value separator
typeAhead multi-value separator

What I want to do is something like this…… just not today

xTypeAheadify the next version?
xTypeAheadify the next version?

jQuery in Xpages – development tip – using Firebug Console

In this article I will demonstrate how using Firebug can significantly reduce your development time with jQuery (or dojo) when you are trying to use selectors. There is no doubt that that out of the box functionality provided by XPages is very powerful, but to accomplish the capabilities we all know (and love?) the accompanying HTML is complex and not necessarily easy to navigate manually.

For more information on FireBug check this out

Things are busy at home and at work so I won’t have a plugin demonstration this week – just a development tip

Introduction
As I have discussed in this blog, using dojo and JQuery selectors are a very powerful way to facilitate “enhancing” your user experience with some nice UI changes. Many times however this can be a frustratingly slow process if you are doing trial and error development something like this:

  • Write your jQuery selector code in the XPage source panel
    • Save
    • Test In Webpage
      • Nothing happens
  • Change your jQuery code is XPage source panel
    • Save
    • Test in Webpage
      • Nothing happens
    • Curse
  • Change your jQuery code in XPage source Panel
  • Remember what it was like in the good old pre-XPages days
  • *sigh blissfully*
  • Curse
  • continue…..

Using the FireBug console we can significantly speed up this process by enacting real time changes to the webpage and not creating the XPage code until we know we have good working JavaScript.

Example

Here is a sample page with a typeAhead and a ViewPanel

typeAhead and ViewPanel
typeAhead and ViewPanel

and here’s our firebug console with a simple piece of jQuery code to change the css of all input fields, giving them a red border

  • Enter the text
  • Hit run
  • And there we have it – nothing happened ?!?
  • Curse
  • #FAIL 😦
fail
fail

What happened?

Well if you look over on the left you can see the jQuery object which has been returned

jQuery object returned
jQuery object returned

Clicking on it shows us the element and look it DID get set……..

Viewing the field through firebug
Viewing the field through firebug

What’s happening is that the INPUT we see is actually masked by DIV styles which are pegged as “!important” in the stylesheet and over-ride the inline style 😦 Can you imagine how long it would have taken you to figure that out just by looking at the code? Using FireBug has given us a quick insight into what’s going on….so if we want a red field then we have to traverse up the Document Model (DOM) tree and color the DIV containing the INPUT field…..

As you can see below I tried a few examples but could not get the field to color – and then I took too many parents and made all the DIVs red. it is also a fascinating way to see in real time how the jQuery DOM navigation works…..still I don’t have what I was looking for – a red field on its own 😦

Too many Red DIVS
Too many Red DIVS

Starting again if you look at the HTML in the DOM you can see what needs to be selected and we can make it red….select the DIV with the id ending in inputText1

  • yay !
A Red field!
A Red field!

Now of course you wouldn’t do this in real life – you would set a class and/or style on the field in the XPage client but this is a demonstration 😉

Coloring the viewPanel

We can use a CSS3 selector to style the nth column of the viewPanel – but unfortunately this does not work in IE

	<style>
		TABLE[id$='viewPanel1'] td:nth-child(3) {background-color: yellow}
	</style>
CSS fail in IE
CSS fail in IE

But jQuery rocks browser incompatibility issues and we can use firebug to get the right  jQuery and then apply it through the code to work in IE. Yes we used firebug to make sure the jQuery works – but this is jQuery and it is BUILT to overcome browser incompatibility issues. Instead of using the CSS3 nth-child selector, jQuery detects IE and trverses the table manually looking for the nth child in a loop – this is inefficient but the final effect is the same.

This is a big reason why you should use a library like jQuery or dojo – they were designed to help get around browser incompatibility problems.

jQuery CSS selector
jQuery in the firebug console
	<script>
		$("TABLE[id$='viewPanel1'] td:nth-child(3)").css('background-color', 'green')
	</script>
jQuery rocks around IE CSS support failure

 

Conclusion

I hope you enjoyed this quick(ish) tip. This only scratches the surface on FireBug and there is SO much more to learn/discuss !

 

jQuery in XPages #10 – JQVMAP (Vector Maps)

In this article I will demonstrate how to implement the JQVMAP jQuery plugin into an XPage. There are 4 maps to chose from but users are able to create their own and add more maps if they need to. In addition to the out of the box functionality I have added some XPage lookup data to demonstrate the integration potential. As I have written this article ideas and thoughts have come at me quick and fast and this is much larger than I had originally intended. If you need maps on your website this is a fantastic flexible solution.

JQVMAP

Demonstration

The XPages integration of JQVMAP is demonstrated here

This capability does not yet work properly in IE – Chrome/Safari/Firefox only.

Download

The demonstration database can be downloaded from the link above or from here

Introduction

JQVMAP is a jQuery plugin based on the original jVectorMap plugin. The plugin provides an abundance of parameterized options for the developer to create their own maps, add color, add interactivity and provide a very slick solution to a broad requirement of many customers, to provide mapping capabilities to their website.

JQVMAP in an XPage

Users are able to select either a country or state and pertinent information regarding their selection will be displayed on the page.

How does it work?

I added jQuery and the relevant map files to my database’s WebContent folder.
They are referenced in the XPage as follows:

	<xp:this.resources>
		<xp:script src="js/jquery-1.7.1.min.js" clientSide="true"></xp:script>
		<xp:script src="js/jquery.vmap.js" clientSide="true"></xp:script>
		<xp:script src="js/vmap/jquery.vmap.world.js" clientSide="true"></xp:script>
		<xp:script src="js/vmap/jquery.vmap.sampledata.js"
			clientSide="true"></xp:script>
		<xp:styleSheet href="css/jqvmap.css"></xp:styleSheet>
	</xp:this.resources>

Adding a Map

Implementing the basic JQVMAP is as simple as adding the following jQuery to your XPage

	jQuery(document).ready(function() {
		jQuery('#vmap').vectorMap({
		    map: 'germany_en'
		});
	});

There are many paramaters which can be added to the map, most of which are self explanatory. For a full list of all the parameters available check out the project on github.

Colorizing the map

The example comes with a sampledata.js file which contains 2010 GDP information and is formatted like this:

var sample_data = {“af”:”16.63″,”al”:”11.58″,”dz”:”158.97″,”ao”:”85.81″,”ag”:”1.1″,”ar”:”351.02″,”am”:”8.83″,”au”:”1219.72″,”at”:”366.26″,”az”:”52.17″,”bs”:”7.54″,”bh”:”21.73″,”bd”:”105.4″,”bb”:”3.96″,”by”:”52.89″,”be”:”461.33″,”bz”:”1.43″,”bj”:”6.49″,”bt”:”1.4″,”bo”:”19.18″,”ba”:”16.2″,”bw”:”12.5″,”br”:”2023.53″,”bn”:”11.96″,”bg”:”44.84″,”bf”:”8.67″,”bi”:”1.47″ etc etc

When the map is built the data is passed into the plugin via these parameters

		    values: sample_data,
		    scaleColors: ['#C8EEFF', '#006491'],

The plugin then determines the color scale between the two passed in color parameters and scales each region based on the data provided. And quite frankly that rocks, bigtime! Changing the scale colors can do this to your maps and is very effective if you are looking to show coal usage or rainforest or anything where color enhances the information

Colorizing the map
Colorizing the map

So where’s the XPage stuff?

I have added some XPage functionality to the world map in two ways – a partial refresh and a REST service.

As the user mouses over a region there is a callback to the REST service to provide me some JSON from the database. Adding the REST service creates the JSON from the vwCountryDoc view

<xe:restService id="restService1" pathInfo="countryLookup">
 <xe:this.service>
 <xe:viewJsonService viewName="vwCountryDoc" var="entry"
 contentType="text/plain" count="1" defaultColumns="true">
 <xe:this.keys>
 <![CDATA[#{javascript:return facesContext.getExternalContext().getRequest().getParameter("code")}]]>
 </xe:this.keys>
 </xe:viewJsonService>
 </xe:this.service>
 </xe:restService>

creates http://demo.xomino.com/xomino/jQinX.nsf/xJQVMAP.xsp/countryLookup?code=gb

  {
      "@entryid":"77-A19E1314CB4B2C0B852579FE0047FF23",
      "@unid":"A19E1314CB4B2C0B852579FE0047FF23",
      "@noteid":"505E",
      "@position":"77",
      "@siblings":248,
      "@form":"CountryDoc",
      "CountryCode":"GB",
      "Name":"United Kingdom",
      "POP":"62,262,000",
      "Areakm":"242,900",
      "Aream":"93,800",
      "Life":"79.4",
      "HighestPeak":"Ben Nevis\t1,344 m (4,409 ft)\t"
  }

We add onRegionOver and onRegionOut callbacks to the map creation and with that we are able to read the JSON, parse the information (building a table) into a hidden span, and show it/hide it depending on the callback.

		jQuery('#vmap').vectorMap({
		    map: '#{javascript: getComponent("comboBox1").getValue()}',
		    backgroundColor: '#333333',
		    color: '#ffffff',
		    hoverOpacity: 0.7,
		    selectedColor: '#666666',
		    enableZoom: true,
		    showTooltip: false,
		    values: sample_data,
		    scaleColors: ['#C8EEFF', '#006491'],
		    normalizeFunction: 'polynomial',
		    onRegionOver: function (event, code, region){
		    	url="xJQVMAP.xsp/countryLookup?code="+code;
	    		//get the REST stream
	    	   	$.getJSON(url,
		    		function (data){
				    	temp=region;
				    	temp=temp.replace(/ /g, '_');
				    	var theTable='<table cellpadding=2 border=0 class="hoverTable"><tr><td>'
				    	theTable += '<img src="http://www.flagslist.com/clist/flags/'+temp+'.png" height=30 width=60 border=0/></td>'
		    			theTable += "<td><h1 style='color: white'>"+data[0].Name+"</h1></td></tr>"
		    			theTable += "<tr><td>Population</td><td>"+data[0].POP+"</td></tr>"
		    			theTable += "<tr><td>Life Expectency</td><td>"+data[0].Life+"</td></tr>"
		    			theTable += "<tr><td>Area km/<sup>2</sup></td><td>"+data[0].Areakm+"</td></tr>"
		    			theTable += "<tr><td>Highest Peak</td><td>"+data[0].HighestPeak+"</td></tr>"
			    		theTable+= "</table>"
			    		$(".hoverShow").html(theTable).css('display', 'block')
				    	console.log(theTable)
			    	});
		   		},
		   	onRegionOut: 	function(element, code, region)
		   		{
		   			$(".hoverShow").html('&amp;nsbp;').css('display', 'none')
		   		},
		    onRegionClick: function(element, code, region)
			    {
			    	//message only relevent to the world map
			    	var temp="#{javascript: getComponent("comboBox1").getValue()}"
			    	if (temp != "world_en"){ return false}
				    var message = 'You clicked "'
			            + region
			            + '" which has the code: '
			            + code.toUpperCase()
			            + "\n\n2010 GDP:"
			            + sample_data;
			        alert(message);
			        sTemp='<img src="http://www.flagslist.com/clist/flags/'+region.replace(/ /g, '_')+'.png" height=30 width=60 border=0/>'
			        $("[id$=code1]").val(code);
			        $('#flag').html(sTemp)
			        $("[id$=button1]").click()
			    }
		});
	});

And this creates the Top Left table in the example

Displaying information on the fly using JSON REST data
Displaying information on the fly using JSON REST data

You will see there is an onRegionClick event as well. When the user clicks on the region I am triggering a hidden button to be clicked and that runs a partiaRefresh on the bottom left section which displays information from the database via computed fields. This also pulls data from the sampledata.js and displays the GDP value

			<xp:table id="countryData" style="width: 700px" cellpadding="5">
				<xp:tr>

					<xp:this.rendered><![CDATA[#{javascript:return (getComponent('comboBox1').getValue() == "world_en")
}]]></xp:this.rendered>
					<xp:td style="width: 100px"></xp:td>
					<xp:td>
						<div id="flag" style="width: 200px">
							<xp:text escape="false" id="computedField4">
								<xp:this.value escaped="false">
									<![CDATA[#{javascript:var temp = getComponent("code1").getValue();
var temp1 = @DbLookup(@DbName(), "vwCountryDoc", temp, 2);

return '<img src="http://www.flagslist.com/clist/flags/'+temp1.replace(/ /g, '_')+'.png" height=30 width=60 border=0 />'
}]]>
								</xp:this.value>
							</xp:text>

						</div>
					</xp:td>
				</xp:tr>
				<xp:tr>
					<xp:this.rendered><![CDATA[#{javascript:return (getComponent('comboBox1').getValue() == "world_en")
}]]></xp:this.rendered>
					<xp:td>Country Code:</xp:td>
					<xp:td>
						<xp:inputText id="code1" defaultValue="GB"
							style="border: 1px solid white">

						</xp:inputText>
					</xp:td>
				</xp:tr>
				<xp:tr>
					<xp:this.rendered><![CDATA[#{javascript:return (getComponent('comboBox1').getValue() == "world_en")
}]]></xp:this.rendered>
					<xp:td>Name:</xp:td>
					<xp:td>
						<xp:text escape="true" id="computedField1">
							<xp:this.value>
								<![CDATA[#{javascript:var temp = getComponent("code1").getValue();
				@DbLookup(@DbName(), "vwCountryDoc", temp, 2)}]]>
							</xp:this.value>
						</xp:text>
					</xp:td>
				</xp:tr>
				<xp:tr>
					<xp:this.rendered><![CDATA[#{javascript:return (getComponent('comboBox1').getValue() == "world_en")
}]]></xp:this.rendered>
					<xp:td>Population</xp:td>
					<xp:td>
						<xp:text escape="true" id="computedField2">
							<xp:this.value>
								<![CDATA[#{javascript:var temp = getComponent("code1").getValue();
				@DbLookup(@DbName(), "vwCountryDoc", temp, 3)}]]>
							</xp:this.value>
						</xp:text>
					</xp:td>
				</xp:tr>
				<xp:tr>
					<xp:this.rendered><![CDATA[#{javascript:return (getComponent('comboBox1').getValue() == "world_en")
}]]></xp:this.rendered>
					<xp:td>Area</xp:td>
					<xp:td>
						<xp:text escape="true" id="computedField3">
							<xp:this.value>
								<![CDATA[#{javascript:var temp = getComponent("code1").getValue();
				@DbLookup(@DbName(), "vwCountryDoc", temp, 4)}]]>
							</xp:this.value>
						</xp:text>
					</xp:td>
				</xp:tr>
			</xp:table>

This builds the popup and bottom left information

Reading JSON data deom a js file and using a partialRefresh to display information
Reading JSON data from a .js file and using a partialRefresh to display information

CallBack events
Because of the callback events we are also able to add/display change other data on the screen. Taken directly from the site we have the following events available.

  • onLabelShow function(element, label, code)
    • Callback function which will be called before label is shown. Label DOM object and country code will be passed to the callback as arguments.
  • onRegionOver function(element, code, region)
    • Callback function which will be called when the mouse cursor enters the region path. Country code will be passed to the callback as argument.
  • onRegionOut function(element, code, region)
    • Callback function which will be called when the mouse cursor leaves the region path. Country code will be passed to the callback as argument.
  • onRegionClick function(element, code, region)
    • Callback function which will be called when the user clicks the region path. Country code will be passed to the callback as argument.
Conclusion
This article grew and grew as I was writing it, the possibilities for this capability are astounding and to think that it is free (MIT license) and takes less than an hour to get up and running it is phenomenal. I have really enjoyed making this article and believe me when I say it could have been a lot longer 🙂

Demonstration

The XPages integration of JQVMAP is demonstrated here

Download

The demonstration database can be downloaded from the link above or from here

jQuery in XPages #9 – PageSlide

In this article I will describe how to implement and provide examples of use the jQuery plugin PageSlide within an XPage. This is a fairly simple plugin with very few parameters but provides an alternate method for displaying information to users

PageSlide.js

Demonstration

The XPages integration of PageSlide is demonstrated here

Download

The demonstration database can be downloaded from the link above or from here

Introduction

PageSlide by Scott Robin is a simple plugin which allows external content to be displayed in a sidebar on the page. There are only two parameters, direction (left or right) and modal (true or false).  It is really quite simple. As you will see my implementation does not use XPage XML markup to add resources or to activate the capability. I found that this caused issues in the browser. The capability works well using the example code from the website, applied in an XPage.

PageSlide showing data from another XPage to the side of the main
PageSlide showing data from another XPage to the side of the main

How does it work?

I added jQuery, the pageslide js library and the pageslide.css stylesheet to my database through the WebContents folder and added them to to my XPage via script and link tags.

		  <script src="js/jquery.pageslide.js"></script>
		  <script src="js/jquery-1.7.1.min.js"></script>
		  <link rel="stylesheet" type="text/css" href="css/jquery.pageslide.css" />

The basic concept works like this:

  • You create a link on the page
    • < a href=”xSomething.xsp” class=”aClass”>
  • You select the link in question
    • $(“a.aClass”).
  • You pageslide it
    • pageslide({direction: “left”, modal: false})
	<a href="xPageSlide_1.xsp" class="myClass">Display user information</a>
	<script>$("a.myClass").pageslide({ direction: "left", modal: false});</script>

When clicking on the link this produces the effect of opening xPageSlide_1.xsp in the right hand panel of the screen (direction: left moves everything to the left).
If the modal = true paramater is included then the “slide” can only be closed programmatically. This forces user interaction with the information. If not included or modal = false then clicking anywhere in the screen will make the slide disappear.

And that is pretty much it….. nothing complicated this week 🙂

More examples
I created a custom control with custom parameters so that the capability can be added to any page.

Custom Control properties
Custom Control properties

The example page contains a demo of this custom capability and it is build like this:

  • A link with a dynamic href is added to the form along with
  • A scriptBlock which adds the PageSlide capability to the link passing the custom parameters as necessary.
<xp:link escape="true" text="CompositeData example"
	id="compositeDataExample"
	value="${compositeData.content}" styleClass="marky">
</xp:link>
<xp:scriptBlock id="scriptBlock1">
	<xp:this.value><![CDATA[
		bModal = "#{compositeData.modal}"
		bModal == "false" ? false : true
		$("[id$=compositeDataExample]").pageslide(
			{ 	direction: "#{compositeData.direction}",
				modal: bModal
			});
	]]></xp:this.value>
</xp:scriptBlock>

The other example I created involves using sessionScope variables to look up information dynamically.

sliding in a new XPage with dynamic content
sliding in a new XPage with dynamic content

There is a hidden anchor tag on the form which is has PageSlide attached to it in a similar way as described above. It is hidden becasue we are going to “click” it programmatically when a user is selected in the comboBox.

<a href="xPageSlide_1.xsp" class="marky2" style="display:none">Display user information</a>
<script>$("a.marky2").pageslide({ direction: "right", modal: true });</script>

The combobox has a simple SSJS lookup formula showing the first column of the AllContacts view
#{javascript:@DbColumn(“”, “AllContacts”, 1)}

<xp:comboBox id="comboBox1" defaultValue="Pick a person">
	<xp:selectItem itemLabel="Pick a person" itemValue="Pick a person"></xp:selectItem><xp:selectItems>
		<xp:this.value><![CDATA[#{javascript:@DbColumn("", "AllContacts", 1)}]]></xp:this.value>
	</xp:selectItems>
	<xp:eventHandler event="onchange" submit="true" refreshMode="partial" refreshId="computedField1">
	</xp:eventHandler>
</xp:comboBox>

In the onChange event I perform a partial refresh of the computedField1. computedField1 looks like this and returns some JavaScript which triggers the link “.click()”

<xp:text escape="false" id="computedField1">
	<xp:this.value><![CDATA[#{javascript:var person = sessionScope.person=getComponent("comboBox1").getValue();
		var disp = "<script>$('a.marky2').click()</script>";
		disp2 = (person == "Pick a person" ? "" : disp);
		return disp2
	}]]>
	</xp:this.value>
</xp:text>
  • If a person is picked the computed field is refreshed
    • The computed field sets a sessionScope variable based on the value selected
      • var person = sessionScope.person=getComponent(“comboBox1”).getValue()
    • The refreshed value contains a script tag which executes when the refresh is loaded.
      • var disp = “// “;
        disp2 = (person == “Pick a person” ? “” : disp);
        return disp2
    • $(‘a.marky2’).click() clicks the hidden link opening the page slide

The xPageSlide_1.xsp page takes the sessionScope variable and looks up the user’s information based on the variable set in the computedField1 above.

<xp:div id="div1" style="display:nonex">
				    <h2 style="color: white">User Information</h2>
				    <xp:br></xp:br><xp:br></xp:br>
					<xp:text escape="true" id="computedField1"><xp:this.value><![CDATA[#{javascript:var per = sessionScope.person;
@DbLookup(@DbName(), "vwContacts", per, 4)}]]></xp:this.value></xp:text>

					<xp:br></xp:br>
					<xp:br></xp:br>
					<xp:text escape="true" id="computedField5"><xp:this.value><![CDATA[#{javascript:var per1 = sessionScope.person;
@DbLookup(@DbName(), "vwContacts", per1, 5)}]]></xp:this.value></xp:text>
					<xp:br></xp:br>
					<xp:br></xp:br>
					<xp:text escape="true" id="computedField3"><xp:this.value><![CDATA[#{javascript:var per2 = sessionScope.person;
@DbLookup(@DbName(), "vwContacts", per2, 6)}]]></xp:this.value></xp:text>
					<xp:br></xp:br>					<xp:br></xp:br>
										<xp:button id="button1" value="Close" style="display: block">
		<xp:eventHandler event="onclick" submit="false">
			<xp:this.script><![CDATA[parent.$.pageslide.close()]]></xp:this.script>
		</xp:eventHandler></xp:button><xp:br></xp:br>

</xp:div>

and with a little styling we are done for a consistent look and feel

<style>
DIV {
    background-color: #333333;
    color: #FFFFFF;
    height: 100%;
    padding: 20px;
    position: fixed;
    top: 0;
    width: 260px;
    z-index: 999999;
}

H2 { color: white }
</style>

Conclusion

I could easily see myself using PageSlide if I wanted to use a hidden menu or if I wanted to have a status update bar running in the background of my webpage which was accessed and made visible to the user when they wanted it to be there.

This is certainly the simplest plugin I have written about, but this is a very elegant solution to displaying hidden information to a user on demand.

Demonstration

The XPages integration of PageSlide is demonstrated here

 

Dynamically Expanding XPage viewPanel Rows on mouseover

In this article I will demonstrate a technique for dynamically expanding and contracting viewPanel rows. This provides a nice clean interface for the user and allows content to be made visible as necessary. This article also highlights many of the core jQuery capabilities.

Problem

When moving from a notes client application to the web often times view columns can be large and unwieldy

A boring notes view
A boring notes view
Unwieldy data view on an XPage
Unwieldy data view on an XPage

Solution

What we are going to do is not only neaten up the display but provide some simple user interactivity to allow access to data as needed. Here is a quick video of the capability in action.

Demonstration

You can see a working example of the capability on the xomino site. In Firefox and Chrome the fade effect look just fine but due to issues with alpha filters fade does not work consistently in IE.

How does it work?

As you can see from the pictures above, once we put our view into an xPage and display it on the web, the entire contents of the data field are displayed on the page. This can make for an irritating session with customer who don’t want to have to scroll page after page to find their information but want the same view to be available through the notes client.

Once solution would be to determine the medium through which your data is being viewed and truncate it accordingly. But I went another route and made a compromise where the data is available if the users wants it (adding functionality to the page without them having to click and open the document) but does not take up a large amount of space.

I added jQuery and jQueryUI (CSS not code) to my database through the WebContent folder and to my XPage like this

	<xp:this.resources>
		<xp:script src="js/jquery-1.7.2.min.js" clientSide="true"></xp:script>
		<xp:styleSheet
			href="css/custom-theme/jquery-ui-1.8.19.custom.css">
		</xp:styleSheet>
	</xp:this.resources>

I started out by creating a data source of my view and dragging the fields to the XPage, creating a viewPanel on the form. I then “fixed” the width of someData column:

		<xp:viewColumn columnName="somedata" id="viewColumn4">
			<xp:this.facets>
				<xp:viewColumnHeader value="Somedata" xp:key="header" style="width:300px"
					id="viewColumnHeader4">
				</xp:viewColumnHeader>
			</xp:this.facets>
		</xp:viewColumn>

I then created a scriptblock on the XPage after the viewPanel (controls > other > core controls >scriptblock). Within all Properties > data > value I added the following code….

			 $("#[id$='viewPanel1'] tr td:nth-child(4)").each(function (i) {
			 	if ($(this).innerHeight()>=100){
				 	$(this).append('<span class="ui-icon ui-icon-arrowthick-1-s"></span>').children(":first").wrap('<div class="wrapper" />')
			 	}
		    });

and that on its own made quite a difference….

modified viewPanel
modified viewPanel

This is what the jQuery code just did

  • Select all the 4th column Table cells in the viewPanel and cycle through each of them
 //select all elements (one in this case) where the id ends with (id$=) viewPanel1
 //within that select all rows (tr)
 //within that select all the table cells (td)
 //within that select only the cells which are the 4th child element (:nth-child(4))
 //cycle through each of them (.each(function(i)) where i is the nth item in the cycle
 $("#[id$='viewPanel1'] tr td:nth-child(4)").each(function (i)
  • Because we have fixed the column width we are able to accurately ascertain the height of the cell contents on the page
  • As we then cycle through the viewPanel TD cells we determine if the cell contents are larger than 100pixels. If they are then we surround the contents with a wrapper with a fixed height.
 //$(this) means each jQuery object (representing the TD as we cycle through the column
 //.append is as it sounds - append to the end this SPAN (with the arrow in it)
 //.children(:first) selects the first SPAN within the TD we are working on and
 //.wrap means surround the SPAN with the DIV
	$(this).append('<span class="ui-icon ui-icon-arrowthick-1-s"></span>').children(":first").wrap('<div class="wrapper" />')

ok trying to visualize what is going on we start with something which looks like this

<td>
  <span #1>
  </span>
</td>

We .append() another span

<td>
  <span #1>
  </span>
  <span #2>
  </span>
</td>

We then .wrap() the :first Child of the TD with a Div

<td>
  <div>
    <span #1>
    </span>
  </div>
  <span #2>
  </span>
</td>

And this is what we have made (looking at it through FireFox FireBug

The new Table Cell viewed in Firebug
The new Table Cell viewed in Firebug

You have to admire the ability to “chain” jQuery objects and methods together 🙂

Styling the wrapper

We add the following stylesheet to the XPage to ensure that the DIV .wrapper class keeps the display in check

	<style>
		.wrapper {
			height: 50px;
			overflow: hidden;
			padding-bottom: 5px;
		}
	</style>

Animating the Table Rows

So once we now have our reformatted table rows they look better but anything over 50px cannot be “read” as it is hidden.

What we are going to do is add an mouseenter and mouseleave event to the DIV class=wrapper we just added inside our table cell.

Here’s the breakdown in English and the code beneath it

on mouse enter

  • Select all table rows which are NOT the one we are currently over
    • Fade the whole row to 20%
  • Get the SPAN next in the DOM below the wrapper DIV (the one with the down arrow in it)
    • Toggle OFF the down arrow class (ui-icon-arrowthick-1-s)
    • Toggle ON the up arrow class (ui-icon-arrowthick-1-n)
  • Animate the height of the TD to match the height of the .wrapper

on mouse leave

  • Select all table rows which are NOT the one we are currently over
    • Fade the whole row back to 100%
  • Get the SPAN next in the DOM below the wrapper DIV (the one with the up arrow in it)
    • Toggle ON  arrow class (ui-icon-arrowthick-1-s)
    • Toggle OFF the up arrow class (ui-icon-arrowthick-1-n)
  • Animate the height of the TD to match the initial height of the .wrapper
			initHeight = $('.wrapper').height();

		    $('.wrapper').bind('mouseenter', function() {
		        $("#[id$='viewPanel1'] TR:gt(0)").not($(this).closest('tr')).stop(true, true).fadeTo('normal', 0.2);
		        $(this).closest('Div').next('span').toggleClass('ui-icon-arrowthick-1-n').toggleClass('ui-icon-arrowthick-1-s')
		        $(this).stop(true, true).animate({
		            height: this.scrollHeight
		        });
		    });
		    $('.wrapper').bind('mouseleave', function() {
		        $("#[id$='viewPanel1'] TR").not($(this).closest('tr')).stop(true, true).fadeTo('normal', 1);
		        $(this).closest('Div').next('span').toggleClass('ui-icon-arrowthick-1-n').toggleClass('ui-icon-arrowthick-1-s')
		        $(this).stop(true, true).animate({
		            height: initHeight
		        });
		    });

In summary

And there you have it. We have looked at a broad range of jQuery capabilities and brought them all together to create a great looking interface for the end user. All these capabilities are part of the jQuery core and I only used jQueryUI to get the cute up and down arrows. If you wanted to use an icon there is nothing stopping you removing the jQueryUI dependency.

We have looked at jQuery Selectors

  • attribute selector $(“#id$=
  • class selector $(‘.wrapper
  • :nth-child
  • :first

We have looked at DOM traversing and manipulation techniques

  • .children()
  • .next()
  • .closest()
  • .not()
  • .append()
  • .wrap()
We have looked at attaching events to DOM elements
  • .bind()
We have looked at object properties
  • .innerHeight()
  • .height()
We have looked at multiple visual manipulation capabilities
  • .fadeTo()
  • .animate()
  • .toggleClass()
  • .stop()

Demonstration

You can see a working example of the capability on the xomino site.