Medryx Observations

July 29, 2008

A Dynamic Tab Container

Filed under: dojo — Tags: , , — maulin @ 4:37 pm

I think tab containers are great. And I think dojo’s tab container implementation is better than great. Its so easy with simple markup to make tabs and really create elegant user interfaces. Further, you lazy load the content of the tabs to make for a reall good user experience, all without much effort at all.

But I frequently use tabs as a tool to filter data views. For example, if I have a table, and want a few standard “views” of that table, I’ll put each view in a tab. Well this leads to a lot of repetitive code. What I really want is to tell the tab container what tabs I want to build, and then create a few small bits of data that can be plugged into each view to “customize” it.

So the “old way” of doing this looked like the following. I’ll use the “stateStore” from many of dijit’s tests as my sample data.

<div>
<div title="All">
<h2>All</h2>
<table border="0">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
</tr>
</thead>
</table>
</div>
<div title="Countries">
<h2>Countries</h2>
<table border="0">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
</tr>
</thead>
</table>
</div>
<div title="Cities">
<h2>Cities</h2>
<table border="0">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
</tr>
</thead>
</table>
</div>
</div>

That’s a lot of code to have three tabs each with a slightly different filtered view of the same table. Enter DataTabContainer.

<div>
<h2>${name}</h2>
<table border="0">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
</tr>
</thead>
</table>
</div>

Ahhh.. Much better. Well, once I had this working for a simple array of tabs, I thought — “how about I swap in a store for the tabs?”. So instead of (or in addition to) the “data” attribute of the widget, you can provide a “store”, “query”, “queryOptions”, and “sort” attribute, much like DataGrid. If you have both a store and data, the tabs in “data” are pre-pended to the store objects. This allows you to include an “All” tab quite easily at the front of the container.

This was actually quite a bit trickier to build than I initially thought. I ended up using a lot of code from dojox.widgets.Iterator, munged with the store/query code from DataGrid to put it together. It basically works like this:

  1. Convert the child elements of the DataTabContainer to a custom widget via dijit.Declaration (DataTabContainer, it may surprise you, actually extends dijit.Declaration, and not TabContainer).
  2. In the buildRendering stage, copy down to the location of the node (since dijit.Declaration is a “pseudo” widget that destroys its own domNode after parsing it).
  3. In place of the destroyed node, create a TabContainer.
  4. Iterate over the data array (or fetch the store items) and create tab for each item in the array. (Using the “titleAttribute” attribute of DataTabContainer as the title of each tab).
  5. In startup(), create an instance of the dijit.Declaration class for the currently selected tab. Before doing so, do a string substitution on the dijit.Declaration class’s prototype.templateString. Note that this is not a simple dojo.string.substitute because it uses the store’s getValue method to get the value to place in the substitution.

And that is it. It works pretty well. Other features I could imagine:

  • If the DataTabContainer is the child of an already existing TabContainer, then use an attribute like “embed=’true’” to skip the creation of the TabContainer step in buildRendering, and instead, just add the tabs to the surrounding container.
  • Allow a “pre-data” and “post-data” to add items around the store fetch items. Right now it only supports “pre” data.

Let me know what you think!


Update:

Well you can forget almost everything I mention about the implementation of this above. I have figured out a much simpler implementation that creates a mixin class to TabContainer, AccordionContainer, or StackContainer and uses the content of the container as a “templateString” that is sent to ContentPane.setContent. Much easier. And now you can do dynamic Accordions and Stacks. Nice! So the updated source is here. And you can try it here. Just remember, to use this code you need to dojo.require the original container (TabContainer, AccordionContainer, or StackContainer), the original pane (ContentPane), and the mixed in stuff (medryx.widgets.DynamicStackContainer).

July 24, 2008

JsonRestStore — Custom Services, Schemas, and Lazy Loading

Filed under: dojo, javascript — Tags: , , — maulin @ 7:02 am

The newest and most complete store in the dojox.data arsenal is dojox.data.JsonRestStore. Its primary purpose is to make interaction between a RESTful data source and dojo.data seamless and simple, and it accomplishes that goal with ease. But a quick peek under the covers and you will find that you may very well be using JsonRestStore for all your Json dojo.data needs. This article aims to show you how you can use JsonRestStore and its more advanced features like lazy entity and property loading and client side filtering, and how you can adapt JsonRestStore to any data source that provides JSON data to your client.

RESTful web services map CRUD services to the HTTP protocol: Create = Put, Read = Get, Update = Post, and Delete = Delete. You can easily sort through the data that would be passed to each of these services as well — Create gets the full data of a new object without an id, Update gets the full data of the updated object with an id, read gets an id who’s object should be returned and delete gets an id whose object should be deleted. Its not rocket science. But if you broaden your thinking from REST, you’ll see this is essentially an API for interfacing with server side data stores. REST is simply one way of implementing it. Instead of REST, you could use an RPC service, or any other service, so long as you could map the service to this API.

JsonRestStore expects you to provide it a service that implements the REST API. The easiest way to do this is to simply specify a target url as a String. JsonRestStore will then automatically send that URL the commands in the API using the REST protocol described above. However, instead of simply providing a target URL, you can actually initialize a JsonRestStore with your own service. The service is simple an object that has the following methods that each return a dojo.Deferred:

  • A directly callable function that takes an id or a query/queryOptions pair and returns the results of the query
  • A put function that takes an id and value and creates the value at the given id
  • A post function that takes an id and value and appends/updates the value at the given id
  • A delete function that takes an id and deletes the value at that id

It is important to know that if you do provide a custom service, you must still provide a unique string as the “target” attribute for the JsonRestStore. As I will explain later — JsonRestStore actually manages interdependencies between objects and between data stores — and the target attribute is how it uniquely identifies one store from another, as well as how it identifies which store a particular object belongs to.

So lets start coding. First lets define a custom service that we will be providing to JsonRestStore. Lets say we are building a system for paging physicians. Each physician has a pager. We’ll start simple (this is illustrative code, not real code!):

var mdService = function(query, queryOptions) {
return dojo.xhrGet({url:"queryUrl.php", handleAs:'json', content:{query:query, queryOptions:queryOptions}});
}
mdService.put = function(id, value) {
mdService.post(id, value);
}
mdService.post = function(id, value) {
return dojo.xhrPost({url:"actionUrl.php", handleAs:'json', content:{action:"update", id:id, value:value}});
}
mdService['delete'] = function(id) {
return dojo.xhrPost({url:"actionUrl.php", handleAs:'json', content:{action:"delete", id:id}});
}

And that is it. Now lets feed this service to JsonRestStore and create new store for our physicians:

var mdStore = new dojox.data.JsonRestStore({target:"md", service:mdService});

You can now use this store as you would any other store — feed it to a tree, or a grid, or to any other dojo.data enabled widget, and everything just works. But it gets better. Much better.

As I stated before, lets build our paging system. In our system we have about 10 pagers and 30 physicians. At any one time, each pager is assigned to 0 or 1 physicians. So in addition to being able to navigate from physician to pager given a list of physicians, we need to be able to navigate from pager to physician given a list of pagers. So lets build that store:

var pagerService = function(query, queryOptions) {
return dojo.xhrGet({url:"pagers.php", handleAs:'json',content:{query:query, queryOptions:queryOptions}});
}
pagerService.put = function(id, value) {
  var d = new dojo.Deferred();
  d.callback(); //put is a noop for pagers
  return d;
}
pagerService.post = function(id, value) {
return dojo.xhrPost({url:"updatePager.php", handleAs:'json', content:{id:id, value:value}});
}
pagerService['delete'] = function(id) {
  var d = new dojo.Deferred();
  d.callback(); //delete is a noop for pagers
  return d;
}
 
var pagerStore = new dojox.data.JsonRestStore({target:"pager", service:pagerService});

This where the magic starts. Lets create some data:

[
  {"id":"1", "name":"Maulin Shah, MD", "pager":{$ref:"pager/1"} },
  {"id":"2", "name":"Doogie Howser, MD", "pager":{$ref:"pager/2"} }
]

and here are some pagers

[
  {"id":"1", "md":{$ref:"md/1"}, "number":"12345", "name":"Yellow Pager"},
  {"id":"2", "md":{$ref:"md/2"}, "number":"54321", "name":"Triage Pager"}
]

Notice the “$ref” notation. This is JsonSchema style notation for indicating references to other objects. This lets you simply define the reference without loading the referenced object, and the referenced object will be lazily loaded when necessary. Notice the the $ref is essentially a “path” to the referenced object — or in less RESTy lingo, its the “class” of the referenced object along with its id (or perhaps its the “table” of the referenced object along with its primary key, if that’s the lingo that floats your boat).

Note that you do not have to make a reference lazy, or you make the reference partially lazy. I’ll go over these concepts a bit later.)

So what happens when we load some physicians into a grid?

<table border="0">
<thead>
<tr>
<th>Physician</th>
<th>Pager</th>
</tr>
</thead>
</table>

This would create a grid that looks something like:

Physician Pager
Maulin Shah, MD [Object object]
Doogie Howser, MD [Object object]

Woops. Not quite what we wanted. The problem here is that the pager object has no toString() method. So we have two options to solve this. Thie first involves using a schema and object prototype with your store.

Each store accepts a “schema” attribute as an initialization parameter. The schema is a JsonSchema style object describing the properties of the json objects that are returned for that store. In addition to the usual “properties” attribute of the schema, the schema can have a special “prototype” attribute that defines an object prototype that is used when the object of this class are instantiated. Thus you can mixin your own custom methods or properties into each instance of objects managed by your store. So lets create the prototype for the pager store:

 var pagerSchema = { prototype: {
      toString:function() {
        return this.number;
      }
    }
  }

Feed this to the store when it is created, and we’ve solved our problem:

  var pagerStore = new dojox.data.JsonRestStore({target:"pager", service:pagerService, schema:pagerSchema});

Now your table will look like you expect:

Physician Pager
Maulin Shah, MD 12345
Doogie Howser, MD 54321

(The second way to solve this problem involves over-riding the getValue method of JsonRestStore to allow dot-path notation. I’ll describe that in a future post that discusses my BeanStore extension of JsonRestStore.)

So what happened behind the scenes in rendering this table? The grid called physicianStore.fetch with no query (which is the same as “fetch all”). The Json described above was returned, with its references to the pager objects. JsonRestStore magically instantiates a new first class javascript object for each item in the Json, using the prototype in our schema as the prototype for the object. When mdStore.getValue(item, “pager”) is called by the Grid, the mdStore initiates a synchronous lazy call to pagerStore’s fetchItemByIdentity method which returns the pager, instantiated with the pager schema, which has a toString method which returns the number.

You could see how laziness could get out of hand very quickly. If we have a hundred physicians, we would have 100 synchronous calls to the server to lazy load pagers. That is not ideal. Lazy loading should really be reserved for more of a master-detail type usage when most lazily referenced items are never actually returned. So how could we make this more efficient? We can modify the data returned in our physicians data to include the pager number as well as a reference to the pager.

[
  {"id":"1", "name":"Maulin Shah, MD", "pager":{$ref:"pager/1", "number":"12345"} },
  {"id":"2", "name":"Doogie Howser, MD", "pager":{$ref:"pager/2", "number":"54321"} }
]

Now this is really cool. Now when rendering the grid, we incur no new requests to the server, since the pager number is already available locally. But now lets say we added a function where clicking on a row of our grid popped up an alert with the name of the pager. Since the name is not available, it would now lazily load the pager (i.e. call pagerStore.fetchItemByIdentity) and flesh out the rest of the pager object. Pretty clever eh?

In this article I have touched on just a few advanced features available with JsonRestStore — using a custom service, using a schema to provide an object prototype, lazily loading objects, and sparsely loading objects. But there is plenty more goodness to be found in this store! Stay tuned for an article on client-side caching and other great features of JsonRestStore!

JsonRestStore retires MedryxORM

Filed under: MedryxORM, dojo — Tags: , , , — maulin @ 5:46 am

When I was first designing the MedryxORM, I had a need for a data store that could perform advanced functions that no store in Dojo 1.1 had available. However with the addition and significant improvement of dojox.data.JsonRestStore, I am happy to say, that I think, after only one month, I can safely retire MedryxORM. This is a good thing. I always opt for off-the-shelf over custom solutions. And, you get the whole dojo communtiy supporting you when you use an official dojo.data store.

In the last month, I’ve worked with Kris Zyp — the Json guru at Dojo — and I really think the JsonRestStore has come a long way to solving many problems with complicated client side data management. First off, let me make this very clear — despite its name, JsonRestStore does not require you to use a RESTful data source or web service. Any web service will do — RPC, or home-grown. You just plug in your services into the JsonRestStore, and the store handles the rest. I actually think its name is its biggest flaw. Perhaps it should simply be JsonStore, so that more people would use it… but I digress.

So here are the features of JsonRestStore that have allowed my to say goodbye to the EntityManager and MedryxORM:

  1. Unique Object Identity: One of my requirements was that if the server sent down an entity, I wanted to be sure that however I accessed that entity, that I got the same entity. So whether I used store.getValue() or item.foo, or item.getFoo(), or item.getBar().getFoo(), I always want the same entity. In my design, there was therefore one store that handled all kinds of entities. This proved a bit clunky, because to access anything from the store via fetch, you had to provide an “entityClass”. JsonRestStore accomplishes this same goal without the clunkiness. With JsonRestStore you create one store for each entityClass. Behind the scenes, JsonRestStore uses dojox.rpc.JsonRest to be sure object identity is maintained. This means that you need a store for each entityClass, but that makes it crystal clear what you are querying for when you do a fetch. Overall, a better design IMHO.
  2. Conversion of JSON hashes to first-class JS objects, allowing for custom functions and business logic to be implemented within entities: If I have an entity, I want to, for example be able to create a toString() method on it, and each time an entity of that type created, it should inherit the toString(). The server should not worry about javascript methods, so sending it in the native JSON data is not the right solution for lots of reasons. My EntityManager used the “class metadata” to determine the class of information being retrieved from the server, and instantiating each object from the metadata, and then mixing in the server data into the object instance. JsonRestStore has a similar “class metadata” concept, but it is more standardized — and based on JsonSchema. If you provide a schema, and it has “prototype” property, then any methods/fields in the prototype are used when the entity is instantiated. Since standardization is a good thing, and since JsonSchema is emerging in the JS world as a standard, I think this is probably a better approach than the custom metadata that was created for MedryxORM — despite the fact that it was loosely based on EJB3/Hibernate.
  3. Built-in laziness: Use store.getValue() and you lazy load items for free. There is a little bit of learning on how to handle laziness on the server side, but its pretty straightforward (and I hope to write a post about it soon).
  4. Client side filtering/sorting: JsonRestStore allows you to cache query results on the client and then filter them and sort them seamlessly on the client. This safely retires my “named queries”, and is both more efficient and more extensible.

Honestly, the only thing missing from JsonRestStore is de-novo support for javabean style getters and setters that also give you free laziness, and support for dot-path notation in getValue(). I’ve added this with a simple additional class called BeanStore that extends JsonRestStore, and I have submitted BeanStore to Dojo to see if they think it is broadly applicable enough to include in dojox.data. We’ll see.

I have to say, the most irritating part of all of this is that I lose 2 weeks of work. But I can always look back at how intricately I now understand dojo.data and JsonRestStore and know the time was not completely wasted. In my next post, I hope to share with you my understanding of JsonRestStore and its use.

Powered by WordPress