Sunday, June 8, 2014

working with JSON model dates in UI5

Update 2: Article now also available on SCN.
Update: included binding example.

Let's say you've designed an OData Service that returns an Entity with a Date-/Time-value (Edm.DateTime).
Let's also say that you're consuming the service with SAP UI5, particularly with the JSON Model (sap.ui.model.json.JSONModel).
And to top things off, you're binding that to a DateTime UI control (sap.m.DateTimeInput) to display a nice mobile datepicker.

Then you might have gone through a hell of a time already :)

No, seriously - making those three things play together nicely isn't difficult, but provides some obstacles. John Patterson has written an excellent article explaining the handling of OData DateTime with Gateway and UI5.
What didn't make it into the article was handling Edm.DateTime in the UI5 JSON model explicitly.

So off we go:

First, Gateway's Edm.DateTime are represented in the UI5 JSON Model as Strings à la
 /Date(1402166783294)/
This seems to represent a JavaScript timestamp object.
Strange?
Me thinks too.
But that's the way it is (with UI5 version 1.20.4 as of this writing at least).

Next, if you're trying to bind this to the DateTimeInput, this will fail - the UI control expects a JavaScript Date object: "Type Error: Expected JavaScript Date Object".

So the JSON model Date strings needs to be converted to a Date object at runtime in order to be usable in UI5 controls.

I've found two ways to do that.

The first extracts the numbers from the string, using them to construct a new Date object.

// this comes from SAP NW Gateway
// and is in the JSON model
var sJsonDate = "/Date(1402166783294)/";

var sNumber = sJsonDate.replace(/[^0-9]+/g,'');
var iNumber = sNumber * 1; //trick seventeen
var oDate = new Date(iNumber);
Attention: JS String.replace method returns a string that can't be used directly to construct a Date object. Multiplying it with 1 causes an implicit type cast to Integer - the latter goes directly into new Date() then.

The second way uses the RegExp object. With its exec Method, all content between the two "/" of the Date string are extracted - the resulting Date(1402166783294) is auto-assigned to RegExp.$1 and subsequently used for a new call.

// this comes from SAP NW Gateway 
// and is in the JSON Model
var sJsonDate = "/Date(1402166783294)/";
/\/(.+)\//.exec(sJsonDate);
var oDate = eval("new " + RegExp.$1);
Clue here is, that exec returns an Object - Date(1402166783294) resides in RegExp.$1 as an object representation. So with the help of eval(), it can directly be used with "new" for instantiating a Date object.

Both methods work: they result in converting the Gateway-JSON-Model "/Date(1402166783294)/" into its Date object representation, that can then be used for the sap.m.DateInput control.


var oDateTimeInput1 = new sap.m.DateTimeInput({
   dateValue: oDate,
   displayFormat: "MMM yyyy",
   valueFormat: "yyyy-MM-dd"
});

Let's combine this with the proper DateInput "type" property and UI5's binding and formatting API.
The dateValue property of the UI control is bound to the path "/date" of the JSON model. The value the JSON model injects then gets formatted with one of the above two snippets.

        var oData = {  
                date: sJsonDate 
        }; 
        var oJsonModel = new sap.ui.model.json.JSONModel(); 
        oJsonModel.setData(oData); 
        var oDateTimeInput3 = new sap.m.DateTimeInput({ 
            type: sap.m.DateTimeInputType.Date, 
            displayFormat: "dd - M - yy"
            dateValue: { 
                path: "/date"
                formatter: function(sDate) { 
                    if(sDate) { 
                        /\/(.+)\//.exec(sDate); 
                        return eval("new " + RegExp.$1); 
//                        var sNumber = sDate.replace(/[^0-9]+/g,''); 
//                        var iNumber = sNumber * 1; //trick seventeen 
//                        return new Date(iNumber); 
                    } 
                } 
            } 
        }); 
        oDateTimeInput3.setModel(oJsonModel);  

And that's it - a working DatePicker UI control, bound to a JSON model that holds values from a Gateway OData call.
Good karma all around.

Hint: both ways take < 1ms in execution time - so no performance gain possible here, use the possibility of your liking. 

A working example can be found on GitHub.