Dealing with Dates, and localization in XPages

In this article I will discuss and demonstrate how to deal with dates in a localized application. We will look at the dojo.locale() library and  discuss how localization affects dates in XPages.

Background

Dates in JavaScript – nasty horrible things, to be avoided at all costs. So much so that there are a number of date handling specific libraries including moment.js and date.js. In addition to these two Dojo has it’s own built in date parsing library and that can also be utilized with multiple languages (localization) and the jQueryUI datepicker functionality also allows for date formatting. So we have a number of options depending on how we want to utilize the available resources.

Localization

Localization is the fancy term for making your application multilingual. For a more in depth look at how to do that at the application level check out http://www-10.lotus.com/ldd/ddwiki.nsf/dx/XPageLocaleTimezone.htm or go see Brad and Kathy at this year’s connect14.

When you turn localization on within your XPages application (R9 – Application properties / Design /International Options) the server generates a dojoConfig.locale variable at the top of your XPages webpage.

date1

This gives us programmatic access to the language at anytime.

For those interested the actual language is passed to the server through the header information when requesting the page. In this case I am telling the server that I was one of three languages (should the application support them)

  1. en, en-us (american english)
  2. fr (french)
  3. de (german)

date2

Within the XPages international options I can specify the languages – and the server will send out the first language supported by the application which coincides with what the browser is asking for.

So if the application supports french, dutch and german – I will get a french page served to me because that comes before german in my list of languages.

Most Americans who have never had to deal with this are thinking – who cares everyone uses mm/dd/yyyy right??? And they would be wrong. Almost nobody else deals with mm/dd/yyyy and if you manually validate mm/dd/yyyy for an international application you will fail in other countries.

Dojo

In XPages we get dojo out of the box and with that we get date formatting functions. The dojo/date/locale() “Creates a string from a Date object using a known localized pattern.“. It is really rather nice and simple and as you can see from the documentation page there are *many* different formats in which you can return the date.

I needed to display the day of the week, month, day number and year at the top of a page, so I created my own date translation function in the code as follows. What this function does is that it accepts a date object and a pattern. The function then returns a string based on the pattern.

require(["dojo/date/locale"])

function formatLocaleDate(date, pattern){
	//example formatLocaleDate(date, "EEE MMM d yyyy")
	return dojo.date.locale.format(
		    date,
		    {
		    	selector: 'date',
		    	formatLength: "short",
		    	locale: dojoConfig.locale,
		    	datePattern: pattern
		    }
		)
};

So in this case the example given would return

“lun jan 13 2014” in french
“mon jan 13 2014” in english

But this still does not determine if mm/dd/yyyy is a valid format in your date field.

Here is the simple date test for that.

If you try the following and dojoConfig.locale = ‘en-us’ you will not pass. But in ‘fr’ you will get a date object.

var temp = dojo.date.locale.parse('20/12/2013',
    {
    locale: dojoConfig.locale,
    formatLength: "short",
    selector: "date"
  })

if(temp){
    console.log('pass')
}

The REST service
A rest service date field comes along in ISO format

[
  {
      "@entryid":"1-AE3FB4C32057B87C85257C22000A83DD",
      "firstname":"Alfred",
      "lastname":"Hall",
      "address":"2139 Jail Drive",
      "state":"IL",
      "city":"Tremont",
      "noteid":"NT0000BD22",
      "lastMod":"2013-12-20T06:00Z" //ISO date format
  },

and because of that it is language independent (smart that). So if we want to turn that into something appropriate for the french (dd/mm/yyyy) or american (mm/dd/yyyy) we have to use the dojo locale library.

var temp = new Date('2013-12-20T06:00Z')
temp = dojo.date.locale.format(temp,{
    selector: "date",
    formatLength: "short"
  });

In American English we get : “12/20/2013”
In french we get : “20/12/2013”

In this way we get the correct formatting without having to write code in each language.

Conclusion

How ever you are dealing with date formatting, date validation or REST service JSON data (in my case in Ext JS) there are dojo date formatting functions available for free on the server. If you need localization then even better because you life is may significantly simpler than it would otherwise be.

PS 

Before anyone starts to have a go about ooo Marky used Dojo….?!?! Yeah I did, because it made sense to meet the requirement. Would I use Dojo to meet this requirement in a non-XPages application? Probably not 🙂

Advertisements

EXTJS in XPages #14 – Grid editing and saving data via REST CRUD

In this article I will demonstrate the ability to edit data within and EXTJS grid and have the changes saved directly to the domino database via the same REST service from whence they came.

I have to say a big thank you to Steve Zavocki who actually found my old failed attempt to make this work in my demo database. It was unblogged because I couldn’t get it to work – well that was in April, he asked some questions about it and I came back to it and with another 4 months of EXTJS experience under my belt I figured it out in no time. Without the prompting it might have sat there for another 4 months untouched.

EXTJS in XPages series
Here are links to all of the previous articles in this series

Demonstration
The EXTJS in XPages demonstration database can be found at http://demo.xomino.com/xomino/Extjs.nsf/xRESTCRUD.xsp

Download
The sample database that will accompany this series is available from the menu in the live demo database from this link – http://demo.xomino.com/xomino/Extjs.nsf

Introduction
This is really where everything starts to come together and we begin to be able to create functionality customers have always been asking for. Being able to update the data in a view without having to:

  • go to the view
  • go into a document
  • edit it
  • change it
  • save it
  • go back to the view

The Row Editor plugin
The row editor plugin is very easy to add to your grid – you create the variable for the rowEditor (taken from the website example)

 var rowEditing = Ext.create('Ext.grid.plugin.RowEditing', {
 listeners: {
	cancelEdit: function(rowEditing, context) {
	// Canceling editing of a locally added, unsaved record: remove it
	 if (context.record.phantom) {
		store.remove(context.record);
	 }
	}
 }
 });

and you add it to the grid in the same way as other plugins

	var grid = Ext.create('Ext.grid.Panel', {
	renderTo: 'gridHere',
	plugins: [rowEditing], // <----------
	height: 400,
	frame: true,
	etc.....

and as simple as that you can now double click on a grid and edit the columns!

rc1

Making it communicate with the REST service

In this example I am using a store with a proxy type of REST. The EXTJS grid already understands this is a REST service and does most of the heavy lifting by itself. For more information on how a REST service works in Domino then check out Chris Toohey’s article 

There is also more documentation on the XPages REST here http://www.openntf.org/blogs/openntf.nsf/d6plinks/NHEF-93YBTB

var store = Ext.create('Ext.data.Store', {
        autoLoad: true,
        autoSync: true,
        model: 'Person',
        proxy: {
    		pageParam: 'p', // <---important to add
            type: 'rest',      // <---- type of proxy is REST
            url: 'xRestService.xsp/byFirstNameFlat2',
            reader: {
                type: 'json',
                root: 'data'
            },
            writer: {
                type: 'json'  // <- data is written back in JSON format
            }
        }
    });

As you can see it also defines the write format back to the REST service. When you click on the UPDATE button on the row editor a URL is generated and data is sent. It does this via a “PUT” request which only sends a small amount of data to the server.

rc2

Enabling a PUT request

By default PUT is not enabled on a Domino server and you can enable it by turning on domino data services on the server or by adding it to the allowed HTTP methods on the web site document in the Address Book

For more information on that check out this blog article by Michael Brownlee

Domino REST service

The REST service URL which generates the data looks like this

http://demo.xomino.com/xomino/Extjs.nsf/xRestService.xsp/byFirstNameFlat2?_dc=1375392340494&p=1&start=0&limit=25

The REST service POST look like this

http://demo.xomino.com/xomino/Extjs.nsf/xRestService.xsp/byFirstNameFlat2/66F3A5FE2B2A38CC85257B1B00254E6D?_dc=1375392353545

As you can see the UNID has been added to the URL after the byFirstNameFlat2/
The _dc= is a random number generated by the grid

It is important to note that the grid automatically adds a “page=1” parameter to the REST service call and for some reason I cannot fathom this makes the REST service fail!

byFirstNameFlat2?_dc=1375392340494&page=1&start=0&limit=25 —-> FAIL

so I added the pageParam to the store proxy config and told it to use “p” instead – “p=1” apparently means nothing to a REST service and we all carry on happily….

and the UNID comes from where exactly?

So the other thing the grid does is by default it assumes that you want to send back the “id” field to the REST service and appends the “id” field onto the end of it

byFirstNameFlat2

becomes

byFirstNameFlat2/66F3A5FE2B2A38CC85257B1B00254E6D

To do this I added the UNID as a field in the REST service and added that as a hidden column in the grid.

Ext.define('Person', {
    extend: 'Ext.data.Model',
    fields: ['firstname', 'lastname', 'address', 'city', 'state', 'id']
});

 

        columns: [{
            header: 'id',
            sortable: true,
            dataIndex: 'id',
            hidden: true
        }, {

“id” is not the only field name which can be used as there is another proxy parameter called “idParam” similar to the pageParam which will allow you to name the “unid” field however you like – but the grid must know which field in the grid is the UNID and which it should throw back at the REST service

And that is pretty much it – you can download the sample database and take a look at the code for yourself.

If you want to get really fancy (and we will in the next blog article) you can also edit numbers/date fields and add validation as well to ensure that your users and not submitting rubbish back to the REST.

If you want to be uber secure as well there is always the querySaveDocument event on the REST service which you could also use to validate the incoming data.

rc3

Conclusion

In this article we have seen how we can easily edit data directly in the data grid and from that we can update our domino data with a tiny http post back to the server. This is good for your network traffic and fantastic for your user experience.

No XPage controls were harmed in the creation of this demonstration

Searching XPages REST service and returning the real count in the results.

In this article I will show you how I managed to get the search results count into a REST service and why I wanted to do that.

Introduction

Back in August 2012 I posted this on Stackoverflow. I will answer the question there but I don’t feel like I have “answered” it, just worked around it.

How do I make the XPages REST control return the overall number of filtered matches?

http://stackoverflow.com/questions/11786930/how-do-i-make-the-xpages-rest-control-return-the-overall-number-of-filtered-matc

Problem

When you create a viewItemFileService REST service using the the ExtensionLibrary control you get a response which looks like this:

{
    "@timestamp":"2013-03-12T23:25:57Z",
    "@toplevelentries":1299,
    "items":
    [
      {
          "@entryid":"100-8DE5EBF5E35C17F285257B1B00254A8F",
          "@unid":"8DE5EBF5E35C17F285257B1B00254A8F",
          "firstname":"Benjamin",
          "lastname":"Imran",
          "address":"2112 Hall Street",
          "city":"Las Vegas",
          "state":"NV",
          "zip":89121,
          "country":"US",
          "email":"Benjamin.A.Wayman@mailinator.com"
      },

which is fine…and from that you can ascertain that there are 1299 documents in the view (@toplevelentries)

The problem is when you execute a search against this REST service (for example ‘[address] contains street’) you still get @toplevelentries: 1299

This causes me great angst when it comes to the EXTJS grids because I want to use the Infinite Scrolling capability. What that does is calculates based on the size of the view (1299) how large a scroll bar to create and then display 25 entries at a time and load them as necessary. This is really smart except after you have searched the REST service. The grid still thinks there are 1299 documents even when there is only a subset matching the search results. This causes a lot of blank scrolling 😦

In my example the search ‘[address] contains street’ returns 338 matches. But this causes the following to happen with the infinite grid

rest1

And that sucks. Also note 52 pages in the pager!!!!

The EXTJS code to get the number of documents in the service looks like this:

 var store = Ext.create('Ext.data.Store', {
        // allow the grid to interact with the paging scroller by buffering
        buffered: true,
        pageSize: 25,
        leadingBufferZone: 25,
        autoLoad: true,
        autoDestroy: true,
        autoSync: true,
        model: 'Person',
        proxy: {
            type: 'rest',
            url: 'xRestService3.xsp/marky?count=25',
            reader: {
                type: 'json',
                root: 'items',
                totalProperty : '@toplevelentries' // This is the determining value
            },
            writer: {
                type: 'json'
            }
        }
    });

And as you can see form the code @toplevelentries is always 1299

So how do we get the 338 number to the grid without causing:

  • Some major hacktastic, back end to front end, extra ajax calls, and  redrawing the grid on the fly shenanigans
  • Manually re-writting the REST service from scratch (which I could do but would cause much pain and suffering)
  • Performance issues with XPiNC connecting to the server
  • My eyes to bleed…..

This has baffled me for months and here’s how I got around it.

Solution

In the REST service you can create a custom column. In this case we are just returning the requestScope value for markyCount in the column “theRealCount”:

<xe:this.columns>
	<xe:restViewColumn name="theRealCount">
		<xe:this.value>
		<![CDATA[#{javascript: return requestScope.get("markyCount")}]]>
		</xe:this.value>
	</xe:restViewColumn>
</xe:this.columns>

And markyCount is going to be the 338 value from the search. So how do we get that?

The REST service has an id value which can be computed – and I really don’t care what the id is because I am not binding it to a control on the form. So I used the opportunity for some server side computation of the id to create a little FTSearch.

In the example below I am simply getting the view used by the REST service (byFirstName) doing an FTsearch on it and then putting the count in the markyCount requestScope.

<xe:this.id>
	<![CDATA[${javascript:
		var theView:NotesView = database.getView("byFirstName")
		var count = theView.FTSearch("[address] contains street");
		requestScope.put("markyCount", count)
		return "searchMe" //irrelevant value
		}
	]]>
</xe:this.id>

So what this does is create a REST service output which looks like this:

{
    "@timestamp":"2013-03-12T23:25:57Z",
    "@toplevelentries":1299,
    "items":
    [
      {
          "@entryid":"100-8DE5EBF5E35C17F285257B1B00254A8F",
          "@unid":"8DE5EBF5E35C17F285257B1B00254A8F",
          "firstname":"Benjamin",
          "lastname":"Imran",
          "address":"2112 Hall Street",
          "city":"Las Vegas",
          "state":"NV",
          "zip":89121,
          "country":"US",
          "email":"Benjamin.A.Wayman@mailinator.com",
          "theRealCount": 338  //this is the new column
      },

Note theRealCount column with 338 in it. This is then added to every column output by the REST service. So yes the downside is that we are adding computation and output data size to the feed but it appears to make an insignificant amount of difference to the output speed of the service.

We then change the EXTJS code to look at the theRealCount value in the first Item and not @toplevelentries

 var store = Ext.create('Ext.data.Store', {
        // allow the grid to interact with the paging scroller by buffering
        buffered: true,
        pageSize: 25,
        leadingBufferZone: 25,
        autoLoad: true,
        autoDestroy: true,
        autoSync: true,
        model: 'Person',
        proxy: {
            type: 'rest',
            url: 'xRestService3.xsp/marky?count=25',
            reader: {
                type: 'json',
                root: 'items',
                totalProperty : 'items[0].theRealCount' // This is the determining value
            },
            writer: {
                type: 'json'
            }
        }
    });

And we get this – no more blank lines and 14 pages in the pager 🙂

rest2

Searching a REST service – when the default is not everything

I have wasted too much time over the last couple of days figuring out why my REST service will only return 32 entries….

Problem
This would seam like a simple search using the viewJSONService and I would expect in my case to see 198 results

<xe:restService id="marky" pathInfo="marky">
	<xe:this.service>
		<xe:viewJsonService defaultColumns="true" viewName="ByName-First">
			<xe:this.search><![CDATA["Avenue"]]></xe:this.search>
		</xe:viewJsonService>
	</xe:this.service>
</xe:restService>

But I only get 32 – why???

Solution

Something simple, small and unassuming – searchMaxDocs. When it is not set the default is not All Matching Documents as I had assumed…

search1

If you do NOT set searchMaxDocs to ZERO you will never get more than 32 documents in your search results.

<xe:restService id="marky" pathInfo="marky">
	<xe:this.service>
		<xe:viewJsonService defaultColumns="true" viewName="ByName-First" searchMaxDocs="0">
			<xe:this.search><![CDATA["Avenue"]]></xe:this.search>
		</xe:viewJsonService>
	</xe:this.service>
</xe:restService>

Conclusion

The default number of results when searching a REST service is 32

Always set your searchMaxDocs=”0″

A couple of Tips on how to optimize your XPages REST Service

In this article will demonstrate how simple planning and insight can make your XPages EXTLib REST service more efficient.

Problem

Whatever you are using your REST service for, wither it is to feed charts, grids, other application or whatever it is, they take up data bandwidth and processing time.

Consider the following notes view

rest1

If I display that in a simple REST service one of the entries looks like this:


  {
      "@entryid":"1-45995DDEB92AA70C882579F0008280CB",
      "@unid":"45995DDEB92AA70C882579F0008280CB",
      "@noteid":"1A12",
      "@position":"1",
      "@siblings":200,
      "@form":"Contact",
      "$13":"Adriana Kerr",
      "FirstName":"Adriana",
      "LastName":"Kerr",
      "EMail":"adriana_kerr@renovations.com",
      "City":"Norwalk",
      "State":"CA",
      "Created":"2012-04-30T23:45:26Z",
      "$10":"Adriana Kerr"
  },

This batch of data takes up 462 bytes. The first 6 lines the attributes @entryid to @form you have no control over. But you do have controll over the others.

The field name displayed comes from the computed column value in the view. If you have a column which is just a single field the programmatic name will reflect the field Name.

rest2

But if you have a column value which not just a fieldname then you will have a programmatic id assigned

rest3

1) Taking control of the column names

The point is that these values can be changed

rest4

And in doing this your rest service changes:


  {
      "@entryid":"1-45995DDEB92AA70C882579F0008280CB",
      "@unid":"45995DDEB92AA70C882579F0008280CB",
      "@noteid":"1A12",
      "@position":"1",
      "@siblings":200,
      "@form":"Contact",
      "1":"Adriana Kerr",
      "2":"Adriana",
      "3":"Kerr",
      "4":"adriana_kerr@renovations.com",
      "5":"Norwalk",
      "6":"CA",
      "7":"2012-04-30T23:45:26Z",
      "8":"Adriana Kerr"
  },

And this batch of data takes up 426 bytes (as opposed to 462 before). This right there represents a saving of 8% which might not seem like much but this is only a small view with only a few columns with only short field name.

If you scale this up to a large view with 25 columns and large field names that can make 2000 bytes per entry – I just performed a quick test on one of my larger REST services and saved over 25% of the data (which is what inspired me to write the article).

“Soooo what” I hear you say – well consider 500 data entries in my world. That ramps up very quickly to a 1M data transfer – saving 25% on that is a significant bandwidth saving – not to mention the reduction in load on your browser having to parse that amount of data.

XPiNC memory 

This is especially pertinent in the XPiNC world which I have the unfortunate pleasure of playing with – it is Firefox 3.5 and has memory issues. I have nothing quantifiable to back this up but there is no doubt about it in my mind – a large data volume causes issues in the browser memory management. This is highlighted by a slowdown of the interface – you know the things we USED to have a problem with but don’t any more with Chrome and Firefox browsers.

2) Better control and only send what you need….

This should be common sense but if you are upgrading an existing database and want to take advantage of existing views, might not always be apparent.

By default the view send all the columns when you select  defaultColumns=”true”

<xe:restService id="restService3" pathInfo="contactjson">
	<xe:this.service>
		<xe:viewJsonService viewName="AllContacts"
				var="entry" contentType="text/plain"
				count="10" defaultColumns="true">
		</xe:viewJsonService>
	</xe:this.service>
</xe:restService>

By going into the all properties and selecting the columns you want you have more control and save bandwidth/memory management

In this case I put the column names back and programmatically added the columns to the REST service

rest5

And then I added the id and firstName column programmatically to the REST service – but as you can see I forced the name of the field in this case to be 1 and 2

<xe:restService id="restService3" pathInfo="contactjson">
	<xe:this.service>
		<xe:viewJsonService viewName="AllContacts"
				var="entry" contentType="text/plain"
				count="10" defaultColumns="false">
			<xe:this.columns>
				<xe:restViewColumn name="1" columnName="id"></xe:restViewColumn>
				<xe:restViewColumn name="2" columnName="firstName"></xe:restViewColumn>
			</xe:this.columns>
		</xe:viewJsonService>
	</xe:this.service>
</xe:restService>

This creates the optimized JSON feed with the smaller field name, without changing it at the column.

  {
      "@entryid":"1-45995DDEB92AA70C882579F0008280CB",
      "@unid":"45995DDEB92AA70C882579F0008280CB",
      "@noteid":"1A12",
      "@position":"1",
      "@siblings":200,
      "@form":"Contact",
      "1":"Adriana Kerr",
      "2":"Adriana",
  },

This code is (for example) all that is needed from this view and at 263 Bytes it is 39% smaller than the original
Caveats

  • If the bulk of your data is in the field value and not in the field names you will not see such a large improvement.
  • Changing the column names in a view needs to be done carefully as those names could be referenced elsewhere.
  • When you have a $10 programmatic column name and you change the formula in the column the name is recomputed – be aware that this can change

jQuery in XPages #8 – Tokeninput autocomplete – how does it work?

This article is written in support of the original jQuery in XPages #8 – Tokeninput autocomplete article

Demonstration

The XPages integration of Tokeninput is demonstrated here

Download

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

How to add Tokeninput to your database

Tokeninput consists of one js file and three css files. These can be added to your database by dragging and dropping them into the WebContents folder of the database (available through the Package Explorer)

Token2

The js files can be added to our database (along with the jQuery library) using the following XML markup in the XPage source

	<xp:this.resources>
		<xp:script src="js/jquery.tokeninput.js" clientSide="true"></xp:script>
		<xp:script src="js/jquery-1.7.1.min.js" clientSide="true"></xp:script>
		<xp:styleSheet href="css/Tokeninput/token-input.css"></xp:styleSheet>
		<xp:styleSheet href="css/Tokeninput/token-input-facebook.css"></xp:styleSheet>
		<xp:styleSheet href="css/Tokeninput/token-input-mac.css"></xp:styleSheet>
	</xp:this.resources>

Basic inner workings of Tokeninput

Tokeninput searches a given JSON string for a specified search parameter. As with many jQuery plugins there are set default which can be over-riden with user provided parmeters. By default the expected string format is:

	{{"id": "12345", "name": "Marky Roden"}]

and the code to add this to your XPage is as simple as the following where inputText3 is our text field and .tokenInput is out plugin instantiation:

	<xp:inputText value="#{sample.Custom}" id="inputText3">
	</xp:inputText>

	<xp:scriptBlock id="scriptBlock4">
		<xp:this.value>
		<![CDATA[
			$(document).ready(function() {
				$("#[id$=inputText3]").tokenInput("xTokeninput.xsp/contactjson", {
				  propertyToSearch: "email"
				});
			});
		]]>
		</xp:this.value>
	</xp:scriptBlock>

As we will see later, “xTokeninput.xsp/contactjson” is a REST service we are going to create on the page and propertyToSearch: “email” is the JSON string property we want to search

Example selecting from a REST service
Example selecting from a REST service

Changing the theme
Using the “theme” paramater we are able to determine the style in which the selected “tokens” are displayed to the user. Adding the facebook theme which comes with the plugin we change the way the results are displayed (not the results themselves).

			$(document).ready(function() {
				$("#[id$=inputText3]").tokenInput("xTokeninput.xsp/contactjson", {
				  propertyToSearch: "email",
				  theme: facebook
				});
			});
Example REST data with facebook theme
Example REST data with facebook theme

Data sources for Tokeninput

As you can see from the provided example we are able to GET (or POST) the JSON data from many sources, I have provided just a few examples, but really the options are only bounded by your needs and imagination.

A boring old Notes Agent

Before I get any grief for not using an XAgent the reason I use this as my first example is that it is probably the easiest to understand and will demonstrate how Tokeninput can generate and manage the search results itself (using it’s own internal ajax calls).
By default if you provide a URL (not a JSON object) to Tokeninput it will assume that is the URL to search and it will add “q=” to the end of the provide URL string, and look for the results. This really is as simple as it gets for Tokeninput, not a single parameter, all assumed default values. custom1 is the name of our text field and peopleLookup is our notes agent.

		$(document).ready(function() {
			$("#[id$=custom1]").tokenInput("peopleLookup?openagent?open&", {
			})
	    });

The peopleLookup agent provides a JSON string based on document fields values in a view, a snippet is below and the original is provided in the sample database

sQuery = StrRight(doc.Query_String_Decoded(0), "&q=")
Set docNAB = viewNAB.getdocumentbykey(sQuery)
i=0
Print {content-type: text/json}
	Print "["
	'loop for 10 results
	Do While Not docNAB Is Nothing And i<10

		'Because this ia JSON string each entry needs to have a comma after it except the first and last
		'so we add a comma because we know we are adding another value here
		If i>0 Then
			Print {, }
		End If
			Print |{"id":"|+docNAB.id(0)+|","name":"|+docNAB.FirstName(0)+| |+docNab.MiddleInitial(0)+| |+docNAB.LastName(0)+|"}|
			Set docNAB=viewNAB.getnextdocument(docNAB)
		i=i+1
	Loop
	Print "]"

As you can see from the picture below the JSON string maps directly to the Tokeninput displayed user options. The URL this JSON came from was http://demo.xomino.com/xomino/jQinX.nsf/peopleLookup?openagent?open&q=b

JSON data shown in firebug along with results
JSON data shown in firebug along with results

XPage REST Data Source

Using the fantastic OpenNTF extension library (and included out of the box in 8.5.3 UP1) we are able to add a REST service to the XPages. What does that mean? Well what the REST service allows us to do at its most basic is it allows us to create a JSON object from a notesview in a format that makes sense. Unlike the readviewentries option (discussed later in the article) the format created is easy to parse, easy to read and most importantly for Tokeninput, flat. If you want to take a look at the feed you will notice that it within the url of the example page (xTokeninput.xsp)

The code for the REST service is shown below:

	<xe:restService id="restService1" pathInfo="contactjson">
		<xe:this.service>
			<xe:viewJsonService viewName="AllContacts2" var="entry"
				contentType="text/plain" count="10">
				<xe:this.columns>
					<xe:restViewColumn name="id" columnName="id">
					</xe:restViewColumn>
					<xe:restViewColumn name="email"
						columnName="email">
					</xe:restViewColumn>
				</xe:this.columns>
				<xe:this.keys><![CDATA[#{javascript:return facesContext.getExternalContext().getRequest().getParameter("q")}]]></xe:this.keys>
			</xe:viewJsonService>
		</xe:this.service>
	</xe:restService>

and this is how it breaks down…..

  • viewName=”AllContacts2″ specifies the view which we want to pull the data from
  • var = Entry gives us access to the viewentry for further manipulation (see OpenNTF sample database for examples)
  • name = “name” is the name of the JSON Object [{“name”: “marky@whatever.com”}]
  • columnName = “email” is the programmatic name of the view column which is the object value [{“name”: “marky@whatever.com”}]
  • count=10 determines the number of results returned.

For each column we want provided in the feed we add a restViewColumn and give it the matching columnName and the JSON object value to display as (name=”id”)

The View Column Programmatic name must be used not the column Header
The View Column Programmatic name must be used not the column Header
	<xe:restViewColumn name="id" columnName="id"></xe:restViewColumn>

We can provide a “key” to the REST service which acts likes a getDocumentByKey.

	<xe:this.keys><![CDATA[#{javascript:return facesContext.getExternalContext().getRequest().getParameter("q")}]]></xe:this.keys>

The service is called every time the user enters a new key value “ma” would create the URL request to

http://demo.xomino.com/xomino/jQinX.nsf/xTokeninput.xsp/contactjson?q=ma

The key then extract the “ma” from the Query_String and provides the first document matching “ma” and then 10 documents after that. These become the displayed results from Tokeninput. A picture of the results was shown above in the initial discussion.

Standard Domino ?ReadViewEntries&OutputFormat=JSON

Since version 8 (officially) we have been able to get view data in a JSON format using the following type of URL
blah.nsf/viewname?ReadViewEntries&StarkKey=x&OutputFormat=JSON. But unfortunately the domino creation wizards at IBM decided to make the output format look more like a notesview than anything which works in Javascript (kinda the point of JSON). It ugly. So we have to work to get it into the format we need for Tokeninput. Click here to see an example of the JSON format and then you’ll have to take it to jsBeautifier.org to see the indentation. The major headache is that “@” values are properties and not objects values which makes for confusing code.

I have working proof of concept for this but I do not like the solution so it is really hard for me to write about it. With so many “better” ways to get the data from a notes view this is not something I recommend. If anyone is interested please contact me directly (@MarkyRoden) and we can discuss.

Twitter Data Source

The specification for the publically available twitter API is available from and the simplified format looks like this (looking at the feed for @MarkyRoden)

Click Here for the Feed link

[{
    "created_at": "Sat Apr 28 15:42:51 +0000 2012",
    "id": 196263215288684545,
    "id_str": "196263215288684545",
    "text": "@byrd1089 jQuery Tokeninput - hopefully will figure out how to parse readviewentires to the necessary format :)",
    "source": "\u003ca href=\"http:\/\/www.tweetdeck.com\" rel=\"nofollow\"\u003eTweetDeck\u003c\/a\u003e",
    "truncated": false,
    "in_reply_to_status_id": 196254762453303296,
    "in_reply_to_status_id_str": "196254762453303296",
    "in_reply_to_user_id": 92247920,
    "in_reply_to_user_id_str": "92247920",
    "in_reply_to_screen_name": "byrd1089",
    "user": {
        "id": 463110112,
        "id_str": "463110112",
        "name": "Mark Roden",
        "screen_name": "MarkyRoden",
        "location": "Reston, VA USA",

The source code for my example twitter pull is below. What I am doing is loading the data via “screen_name” value (e.g. edbrill or openntf) for the last 100 entries and returning the 10 entries matching the search parameter.  The parameters do the following thing:

  • propertyToSearch: the field to be searched “text”
  • queryParam: the Object name to query from the API “screen_name”
  • tokenFormatted: the HTML describing the way the results are displayed to the user
  • results formatted: the HTML describing the way the select result is stored
  • onResult: a callback function (in this case correcting a bug in Tokeninput replacing ( and ) in the return string)
			$(document).ready(function() {
				$("#[id$=inputText6]").tokenInput("https://api.twitter.com/1/statuses/user_timeline.json?include_entities=true&include_rts=true&count=10", {
					propertyToSearch: "text",
					queryParam: "screen_name",
					resultsFormatter: function(item){ return "<li>" + "<img src='" + item.user.profile_image_url + "' title='" + item.user.screen_name + "' height='40px' width='40px' />" + "<div style='display: inline-block; padding-left: 10px; width:80%'><div class='full_name'>" + item.text  + "</div></div></li>" },
					tokenFormatter: function(item) { return "<li>" + "<img src='" + item.user.profile_image_url + "' title='" + item.user.screen_name + "' height='40px' width='40px' />" + "<div style='display: inline-block; padding-left: 10px; width:80%'><div class='full_name'>" + item.text  + "</div></div></li>" },
					onResult: function (results) {
      					jQuery.each(results, function (index, value) {
				        	value.text = value.text.replace(/\(/g, '(').replace(/\)/g, ')');
						});
				      return results;
				    }
			    });
		    });

And this to me with where Tokeninput takes the typeahead autocomplete to the next level…..

Example Twitter feed with formatted results
Example Twitter feed with formatted results

so what happens when this is submitted? If you remember the default submitted value in the Tokeninput is “id”….and If you look at the feed there is an id tag for every tweet…..

([{
    "created_at": "Sat Apr 28 15:42:51 +0000 2012",
    "id": 196263215288684545,
    "id_str": "196263215288684545",
    "text": "@byrd1089 jQuery Tokeninput - hopefully will figure out how to parse readviewentires to the necessary format :)",
    "source": "\u003ca href=\"http:\/\/www.tweetdeck.com\" rel=\"nofollow\"\u003eTweetDeck\u003c\/a\u003e",
    "truncated": false,

and when we submit the value back to the database we get a multivalue field containing all the tweet ids. If you want to change the field stored use the “tokenValue” parameter. The Tokeninput field is mapped to the “Custom” field in the XPage through the data source.

Showing the submitted tweet ids stored in the notesdocument
Showing the submitted tweet ids stored in the notesdocument

Scratching the surface

The possibilities with Tokeninput are only limited by the developers imagination and the data source available. I have demonstrated how we can create our own JSON Feed using a REST feed, how to create a simple JSON feed using a notesagent, how to parse an existing notes view readviewentries JSON feed and how to parse an external feed (twitter) and display some fantastic result results while maintaining data control.

In my opinion (from a user experience) this trumps the XPages typeAhead field capability hands down . This also trumps the XPages typeAhead from a developer perspective as well because it will take data from sources other than an internal notes view and will return multiple values which the current type ahead will not.

Demonstration site

This article is published in conjunction with the original post and  demonstration site

And then what?

To be submitted to OpenNTF – I am working on a custom control to add a dynamic Tokeninput field to any XPage. It will provide a direct alternative to the dojo typeAhead and provide greater flexibility for users.

It’s just going to be ready for this week’s article 🙂

Work in progress - custom control for adding tokenizer to an XPage using REST
Work in progress - custom control for adding tokenizer to an XPage using REST