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

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″