Tuesday, March 15, 2011

Templating

User interfaces are made out of static content, interactive components and dynamic data display as HTML. All those elements are tied together in an HTML document (or a fragment). And we use templates to define those HTML fragments. Templates are stored in the ontology and associated with ontology individuals or classes. In addition template associations are contextualized depending on the kind of display needed (e.g. standalone, as part of a list, full/short etc.). Thus when performing a look for a template to display a particular object, additional contextual parameters determine the actual template to be used. For instance a template displaying an object as standalone, using the whole space available on a page, will likely display all of its properties, while a display as an element of a list will use only most relevant/distinguising properties. 

We are using an officially supported jQuery plugin:

http://api.jquery.com/jquery.tmpl/

It has some limitations. Like nearly all JavaScript templating engines, it starts with some sort of philosophy of how things should be done. But it has enough flexibility by allowing arbitrary function calls and logic inside templates, while dealing with simple cases the same way others do.

Because often the data object needed for a template is an aggregate of several different pieces, sometimes metadata, sometime operational data, often a combination of both, we needed an easy mechanism to assemble it and only then call the template. The main problem is that the template engine can't deal with data obtained asynchronously - it would basically need to delay evaluation and the call to apply the template will need to be asynchronous itself. But there's no support in the jquery.tmpl API for that. So we implemented such a mechanism of delayed evaluation ourselves. One can construct a JavaScript object where each value is actually an instance of AsyncCall:

var obj = { x : new AsyncCall({url:"server:port/etc", async:true, etc...}, function(value) { alert('got x!'); return value; }}

An AsyncCall instance encapsulates the AJAX parameters (mandatory) to use with $.ajax and a callback function (optional) invoked when that particular object property was received at the client. Note that the callback function actually receives the value returned by the server as a parameter and it is expected to return the final value assigned to 'x'. That is, the callback function can "massage" that returned value or return something else completely. 

Given a JavaScript object with AsyncCall values, one can synchronize on the event that they all have been obtained like this:

onObjectReady(obj, function (obj) { alert('all values of ' + obj + ' were received'); };

Below is a complete example of a preliminary version of ServiceDirect-like re-implementation with this framework. The metaService.delay(...) is just a convenience method to construct an AsyncCall for a particular REST service.


load : function (app, parent) {
this.parentElement = parent;
var self = this;
var components = {
InquiryTypesComponent: undefined,
InquiryTableComponent: undefined,
InquiryTable: metaService.delay("/individuals/InquiryTable", function (obj) {
this.InquiryTableComponent = uiEngine.getRenderer(obj.type)(obj);
return "<div id='InquiryTableTopDiv'>";
}),
InquiryTypes: metaService.delay("/classes/sub?parentClass=Inquiry", function (obj) {
self.inquiries = obj.classes;
this.InquiryTypesComponent = self.makeInquiriesDropDown();
return "<div id='InquiryTypesTopDiv'>";
})
};
onObjectReady(components, function() {
if (app.hasTemplate) {
var div = document.createElement("div");
$.tmpl(app.hasTemplate.hasContents, components).appendTo(div);
self.setui(div);
$("#InquiryTypesTopDiv").append(components.InquiryTypesComponent);
$("#InquiryTableTopDiv").append(components.InquiryTableComponent);
}
else
throw new Error('Missing application top level template');
});
}

And here is a portion of the template that is being displayed:

<table width="100%" height="100%" border="0" cellspacing="0" cellpadding="0">
<tbody><tr height="100%">
<td class="contentGrey" dir="ltr">
<table>
<tbody><tr><td>
<table border="0"><tbody><tr><td><span style="width: 1000px">To request a service or report a problem start by selecting the desired service type from the pull-down list. </span></td></tr>
<tr><td><span style="width: 1000px">NOTE: If you do not see the service you would like to request included in the pull-down
list, please contact us by dialing 3-1-1. Call Specialists will be glad to assist you.
If you live outside of Miami-Dade County, please call 305-468-5900.</span></td></tr>
</tbody></table>
{{html InquiryTypes}}
</td></tr>
<tr><td>
</td></tr>
</tbody></table>
</td>
</tr>
</tbody></table>
{{html InquiryTable}}



Note that we use the {{html }} templating tag so the template engine doesn't escape HTML tags from the insert text. Note also that the two object properties used in the template, InquiryTypes and InquiryTable both have a callback associated to convert the data received from the server into HTML text. The callbacks work in tandem with the main (top-level) callback: they construct DOM trees for UI to be inserted and just return 'div' placeholders where those DOM trees are inserted by the top-level callback. This avoids serializing a DOM tree into HTML text and then parsing it back. In the case of InquiryTypes, the data is a list of service/inquiry types and we dynamically create a dropdown box from it. In the case of InquiryTable, the data is an actual UI component described in the ontology and rendered by the client-side UIEngine.

Some more work is perhaps needed to abstract further and simplify this example. Clearly many templates won't need such acrobatics. For instance, templates that simply display data returned by a database query would be rendered in a much simpler way. But here we have a full dynamic interface with complete UI components streamed as metadata from the ontology. So some extra gluing work at the client-side is necessary.

Friday, March 11, 2011

Querying the Operations Database

Querying for lists of individuals matching a set of criteria is a fundamental functionality of the OperationService. Because the arbitrariness of kinds of entities and of their complexity, querying should be fairly generic, yet intuitive and simple enough to perform from client-side JavaScript. 

The /op/list URL is going to be used to query for a list of entities. The criteria are specified as a query parameter named 'q'. The result is a JSON array of individuals. The query itself is too a JSON object that serves both to specify the selection criteria and the content of the result. The query object specifies a pattern to be matched and completed for each entity found at the back-end. It is inspired the MQL query language developed by MetaWeb for the Freebase semantic database. For more on MQL itself, consult the MQL Manual

In our implementations of those ideas, we won't follow MQL exactly because our meta model is OWL, which is different from their meta model. We will implement various features as the need arises. 

This approach to querying is generally known as query-by-example, or pattern-matching. An alternative would be to specify a logical query expression, with logical operators and or not. Constructing query expressions is usually harder than providing a pattern. However, even with the pattern-matching approach we may need some logical operators. When matching a list of values of the same property for example, we may need to specify whether we want any (an OR operation) or all (an AND operation) of those values to be matched. For a functional property, the interpretation can only be any, but when a property can actually have multiple values, as is common in OWL, a choice has to be made. Perhaps the "ignored prefix idea" from MQL (see example below) can be adapted with meaningful property prefixes, e.g. { "either:prop" : value1, "either:prop" : value2 }.

More on this to come as we make progress....

Some Examples

Here is how to obtain all inquiries (service requests) submitted by a particular user and that are still open:


query = { "type" : "Inquiry",
"submittedBy" : "user123",
"1:hasStatus" : "ServiceRequestInProgress", "2:hasStatus" : "ServiceRequestStarted",
"submittedOn" : null,
"lastUpdatedOn" : null,
"title": null
}

queryResult = opService.get("/list", {q : query});

$(queryResult).each(function (x) {
document.write(x.title + ", " + x.hasStatus + ", " + x.lastUpdatedOn);
});

The code above will obtain all inquiries (note that the type criterion also matches subclasses of Inquiry) submitted by user123 with one of the listed statuses and will return a JavaScript array where each entity has the properties listed in the query. When a property is listed with a value null, that simply means that we want that property returned as part of the query results.  Note that the hasStatus property appears twice, with different prefixes 1: and 2:. Those prefixes are ignored, they are simply a device to list the same property name more than once.