While using the SmartGWT library makes easy to develop applications for smartclient,making some really interesting widgets available to user, together with databinding and various other goodies,  there are still some areas where things could be improved to make development even more easy. While some of these improvements are already made, they are not available in the LGPL package.

One of things which can make development faster is the way DataSources are created.
Currently you have to define the DataSource class in java file in the client package (which gets compiled / translated to javascript) or alternatively you have to declare it in javascript. You have to manually create each of the DataSource fields, and add it manually to the DataSource itself. Also, DataSources themselves arent really useful without back-end support so you need to somehow get the data from server. For this there are various possibilities, like Web Services, REST, simple XML, etc.

There is however a better way to do this. The smartclient server allows you (next to some other things) to define the DataSource in XML. Then this is used to actually generate the DataSource based on what information is in the XML (fields, field names, titles, length, and various other fields). Also it allows defining a so called DMI object, which is the server side part of the DataSource, tightly integrated with it. The server itself offers a hierarchy of classes and a servlet which basically allows one to define a class which provides the data to the DataSource. This class is actually a simple object wich provides fetch(), add(), update() and delete() methods, and return the information in a standardised way. The server takes care of processing this and sending it back to the DataSource who requested the information.

These two functionalities allow shorter development time, and also centralize the place where datasource information is stored. In the server-less approach you have to specify the structure of the DataSource when you define the DataSource and also you have to know the structure of that DataSource at least in the places where you send back the information requested by fetch(), add(), update() and delete().And also, you have to take care of serializing the data.
In the smartclient server, all this is handled automatically. You just need to create the XML for DataSource, and the DMI object which has the role of returning the (list / collection) of objects. More easy. Much cleaner.

So, let’s take them in order.

First, let’s see how can we improve working with DataSource.

Creating a DataSource would mean creating a new class which extends DataSource, then set up it’s fields, the place where the data is retrieved, it’s id, etc. Something like the code bellow (class declaration omitted, only the initialisation itself is shown):

setID(id);
setRecordXPath("/List/country");
DataSourceField countryNameField = new DataSourceField("countryName", FieldType.TEXT, "Country");
DataSourceField countryCodeField = new DataSourceField("countryCode", FieldType.TEXT, "Code");
DataSourceField independenceField = new DataSourceField("independence", FieldType.DATE, "Independence");
DataSourceField populationField = new DataSourceField("population", FieldType.INTEGER, "Population");
DataSourceField gdpField = new DataSourceField("gdp", FieldType.FLOAT, "GDP ($B)");

setFields(countryNameField, countryCodeField, independenceField, populationField, gdpField);
setDataURL("ds/test_data/country.data.xml");

Having this kind of code written for each DataSource we define is tiresome and unnecessary, if we can define a generator which does this for us, based on a XML specifying the information to be generated.

The DataSource declaration could be something like:

<?xml version="1.0" encoding="UTF-8"?>
<DataSource ID="DataSourceId">
<fields>
<field title="ID" name="id" type="text" required="true" primaryKey="true"/>
<field title="Creator" name="creator" type="text" valueXPath="creator/displayName"/>
<field title="Name" name="name" type="text"/>
<field title="Description" name="description" type="text"/>
<field title="Bla Bla" name="blabla" type="text"/>
</fields>
<serverObject className="DMIPackage.DMIHandler"/>
</DataSource>

Because later we need to identify the DataSource on server side also (to retrieve the DMI object) we need to store this somewhere in a package which is not compiled to Javascript. Also, for that purpose we added the serverObject tag. But our front-end is Javascript, we need a way to create some code based on this XML, code which ends up running on the client side. For this we can use a GWT generator.

First, we need to define an interface which will trigger the generator itself (somewhere in the client package). Something like:

public interface IDataSource
{
public void initFields();
}

is a good place to start. In initFields() we will generate the boilerplate code. We need to configure the module to trigger the generator for this interface. For this we need to edit the module XML file and add something like

<generate-with>
<when-type-assignable />
</generate-with>

In order to trigger the generator for a given DataSource, we need to create a interface for that DataSource and extend it from our IDataSource interface. Each time code like this is encountered:

IDataSource aDS = (IDataSource) GWT.create(ADataSource.class);

where ADataSource is a interface extending our IDataSource interface, our generator will be called to generate the source of that class. I won’t go in detail about how one writes a code generator for GWT, there is already lot if info about this subject available on Internet. Still, some clarifications are required:

  • the file name of the XML is generated from the class name to be generated. This can be made in various ways, this was easier to implement (alternatively we could add a tag in XML file to specify for which datasource provides information, load all the DataSource XMLs and search the one we need)
  • resources can be retrieved using ResourceOracle:
    ResourceOracle oracle = context.getResourcesOracle();
    Map<String, Resource> map = oracle.getResourceMap();
    Resource res = map.get(resourceName);
    
    // find our resource
  • In order to make things easier and to reduce the quantity of code generated, the class we generate extends a base class. This base class extends REST DataSource and set up the add() fetch() remove() update()URLs to point to our servlet which simulates DMI, something like (the constructor is shown):
    public BaseDataSource()
    {
    setID(id);
    setFetchDataURL("/DMI");
    setAddDataURL("/DMI");
    setRemoveDataURL("/DMI");
    setUpdateDataURL("/DMI");
    
    OperationBinding fetch = new OperationBinding();
    fetch.setOperationType(DSOperationType.FETCH);
    fetch.setDataProtocol(DSProtocol.POSTPARAMS);
    
    OperationBinding add = new OperationBinding();
    add.setOperationType(DSOperationType.ADD);
    add.setDataProtocol(DSProtocol.POSTPARAMS);
    
    OperationBinding update = new OperationBinding();
    update.setOperationType(DSOperationType.UPDATE);
    update.setDataProtocol(DSProtocol.POSTPARAMS);
    
    OperationBinding remove = new OperationBinding();
    remove.setOperationType(DSOperationType.REMOVE);
    remove.setDataProtocol(DSProtocol.POSTPARAMS);
    setOperationBindings(fetch, add, update, remove);
    
    initFields();
    }

All left now is to generate the code which set up the fields and add them to DataSource. I wont go in detail of this either. It’s as easy as getting all the fields, checking their type, and based on their type write the code which create the fields, set them up according to the information in XML and at the end add those fields to the DataSource. That’s all.

Handling server side of DataSources will be in a separate post.

  1. […] So far we have a way to generate the DataSource code based on a XML file describing the DataSource, making declaration of DataSources on client side (almost) a breeze. Now we need a way to either generate the server side code which is called by the DataSource or to create a generic one which can be used for all DataSources. […]

You must be logged in to post a comment.