Easy RIA v1.0 Developer’s Guide

Contents

1       Installing Easy RIA.. 2

2       Overview.. 2

3       Step 1: Create Object Model & Hibernate Mappings. 10

4       Step 2: Create & Register Services. 11

5       Step 3: Register CRUD Service Operations for Each Model Object. 12

6       Step 4: Create & Register Search Queries. 13

7       Step 5: Create & Register Reference Queries. 14

8       Step 6: Create Metadata for the Search Module. 15

9       Step 7: Create Metadata for Manage Model Object Module. 20

10          Step 8: Create Menu. 24

11          Step 9: Defining GWT Module and Entry Point for the Application. 25

12          Step 10: Running the Application in Hosted Mode. 25

13          Step 11:  Creating the WAR Distribution. 26

14          Advanced Topics. 26

14.1      I18N & L10N.. 26

14.2      Multiple Inheritance Collection. 27

14.3      Custom Renderers & Listeners. 27

14.4      Security. 27

14.5      CSS. 27

 


 

1         Installing Easy RIA

·         Download the latest version of the Easy RIA jar file here.  The complete source code for Easy RIA is also available at the same location.  We recommend that you also download the source code as it contains 2 sample projects: Catalog and TreeTable.  In this guide, we will be using the Catalog sample project to demonstrate the key concepts behind Easy RIA.  If you’re new to GWT, the Catalog project can serve as your starter project.

·         Easy RIA depends on the POJOSoft Core Libraries.  Download the latest version of the Core Libraries here.

·         Add both jar files to your GWT project classpath.

·         Specify the usage of Easy RIA in your GWT module xml file.  For example, here is the content of Catalog.gwt.xml (the module file for the Catalog sample project):

                                                                                                                                                    

<module>

<inherits name='com.google.gwt.user.User'/>

            <inherits name='com.google.gwt.i18n.I18N’/>

            <inherits name='org.pojosoft.ria.gwt.Ria'/>

            <entry-point class='org.pojosoft.catalog.web.gwt.client.Home'/>

</module>

 

Note the bolded line.

·         Link to the “ria.css” and “datepicker.css” style sheets from your HTML or JSP page containers.  For example, here the code snippet for Home.jsp in Catalog sample project:

<head>

            <meta name="gwt:property" content="<%=localeProp%>">

            <link type="text/css" rel="stylesheet" href="ria.css"/>

            <link type="text/css" rel="stylesheet" href="datePicker.css"/>

            <title>Catalog Sample Project</title>

</head>

2         Overview

During our development of Open Enrollment LMS, the majority of the pages (over 80%) were of the records management type (e.g.  CRUD operations on model objects and their owning collections).  The Easy RIA framework was created as a result of this need.  Easy RIA allows pages to be created simply by defining the metadata for the page: anything from a search page, to search results page, to add/update/edit page.  There’s no client code since the pages are dynamically generated using the metadata declared in XML files.  The framework also allows for custom page layouts/renderers/listeners so that if you need a feature that’s not supported by the framework, you can easily add it yourself.

Note that Easy RIA currently supports GWT 1.4.  Adobe Flex support will be offered in a future version.  This means that in the future, using the same metadata, you will have a choice of using either GWT or Flex as your UI.

Let’s walk thru a simple example and see a few screens generated by Easy RAI using our Catalog sample project.

Here’s the Object Model for the Catalog application:

ObjectModel.jpg

And here’s the corresponding CatalogService for managing Catalog and CatalogItem model objects:

Service.jpg

The model objects and the CatalogService will need to be registered with the Easy RIA framework so that it knows which CRUD operations go with which model object.  Next, the metadata for the Search Page, the Search Result Page, and the Manage Catalog page will need to be created so the framework knows how to render these pages.  Lastly, the metadata for the menu will have to be created.  Note that all metadata are stored in XML files.  If declarative XML is not your cup of tea, this framework is probably not for youJ.  We are looking at using Annotations as an alternative.

To create the distribution WAR file, you’ll need to use the ant task (“ant dist”) that comes with the sample app. 

Here are some sample pages that are automatically rendered by the Easy RIA framework.

Here’s a Search Catalog Page:

CatalogSearch.jpg

 

Here’s a Search Catalog Results Page:

CatalogSearchResults.jpg

 

When the user clicks on the edit image to edit the record, the Edit Catalog Page is displayed:

EditCatalogRecord.jpg

 

There are 2 tabs in the Edit Catalog page: Summary tab and Catalog Items tab.  The Summary tab allows for update/delete of a Catalog model object.  The Catalog Items tab allows the user to manage a child collection of CatalogItem records, as shown below:

EditCatalogRecordItems.jpg

 

User can click on “Add New” to add a new CatalogItem to the catalog (via a model popup window):

AddNewCatalogItem.jpg

 

Or Edit an existing CatalogItem record via a model popup window:

UpdateCatalogItem.jpg

 

And here’s an Add New Catalog page (notice the Calendar widget support):

AddNewCatalog.jpg

 

All of the above pages are generated using the metadata defined in the layouts for the pages.  The menus are also defined using metadata.

In the sections below, we will go thru, step by step, the gory details of putting the entire application together using the Easy RIA framework.

 If you are building an RIA application that is backed by an Object Model, and most of your application pages involve the querying the management (create/update/delete) of the model objects (and their relationships) in the object model, then this framework may be for you.

3         Step 1: Create Object Model & Hibernate Mappings

We are assuming that your application is backed by an Object Model.  If not, you still can certainly use the framework.  But in that case, you’re using it without any of the backend persistence services support offered by the framework.

We will use the Catalog sample application in this developer’s guide so you can readily reference the code.  Again, here’s the Catalog sample application’s Object Model:

ObjectModel.jpg

The Catalog model object contains a collection of CatalogItem.  Each Catalog has a CatalogStatus, which is of type Reference.

Our Catalog sample application uses Hibernate for persistence.  If you are using Hibernate, you will need to create the Hibernate mappings for your model objects.

4         Step 2: Create & Register Services

Here’s the CatalogService:

Service.jpg

The CatalogService allows Easy RIA to perform CRUD operations for the Catalog and CatalogItem model objects.  After creating the services, you will need to register these services with Spring.  This will allow Easy RIA to dynamically lookup these services using their service names.

5         Step 3: Register CRUD Service Operations for Each Model Object

The purpose of this step is to tell Easy RIA how to handle CRUD operations for each model object that it manages.  This is done in the Persistence-meta.xml (under WEB-INF/meta):

    <modelObject class="org.pojosoft.ria.gwt.client.meta.ModelObjectPersistenceMeta">

      <id>Catalog</id>

      <modelObjectClass>org.pojosoft.catalog.model.Catalog</modelObjectClass>

      <modelObjectIdClass>java.lang.String</modelObjectIdClass>

      <nested>false</nested>

      <persistenceActions>

        <action class="org.pojosoft.ria.gwt.client.meta.ModelObjectPersistenceActionMeta">

          <name>get</name>

          <serviceName>catalog.catalogService</serviceName>

          <methodName>getCatalog</methodName>

        </action>

        <action class="org.pojosoft.ria.gwt.client.meta.ModelObjectPersistenceActionMeta">

          <name>add</name>

          <serviceName>catalog.catalogService</serviceName>

          <methodName>addCatalog</methodName>

        </action>

        <action class="org.pojosoft.ria.gwt.client.meta.ModelObjectPersistenceActionMeta">

          <name>update</name>

          <serviceName>catalog.catalogService</serviceName>

          <methodName>updateCatalog</methodName>

        </action>

        <action class="org.pojosoft.ria.gwt.client.meta.ModelObjectPersistenceActionMeta">

          <name>remove</name>

          <serviceName>catalog.catalogService</serviceName>

          <methodName>removeCatalog</methodName>

        </action>

      </persistenceActions>

    </modelObject>

 

Brief explanation of the important elements:

·         modelObject’s “class” attribute value – this is always “org.pojosoft.ria.gwt.client.meta.ModelObjectPersistenceMeta”

·         id – the unique name identifying the Model Object

·         nested – whether this Model Object is nested (has a parent and child collection of the same type)

·         persistenceActions.action.name – has to be one of these values:

o   get – identifies a getter operation for the Model Object

o   add – identifies an add operation for the Model Object

o   update – identifies an update operation for the Model Object

o   remove – identifies a remove operation for the Model Object

·         persistenceActions.action.serviceName – the name of the service as registered in Spring

·         persistenceActions.action.methodName – the service method name to invoke for the corresponding get/add/update/remove operation

6         Step 4: Create & Register Search Queries

Easy RIA uses the iBATIS framework to provide dynamic queries support.  The dynamic query functionality is used by the search pages.  For each search page, you will need to define a dynamic iBATIS query.  The dynamic queries are defined and stored in Search.xml (under WEB-INF/classes).

Here’s the dynamic query for the Catalog Search page:

  <resultMap id="getCatalogsResultMap" class="java.util.HashMap">

    <result property="id" column="id"/>

    <result property="name" column="name"/>

    <result property="description" column="description"/>

    <result property="status" column="status"/>

  </resultMap>

 

  <statement id="getCatalogs" resultMap="getCatalogsResultMap" parameterClass="java.util.Map">

    SELECT catalog_id as id, name, description, status_id as status

    FROM catalog

    <dynamic prepend="WHERE">

      <isPropertyAvailable prepend="AND" property="id">

        UPPER(catalog_id) $operator$ #id#

      </isPropertyAvailable>

      <isPropertyAvailable prepend="AND" property="name">

           UPPER(name) $operator$ #name#

      </isPropertyAvailable>

      <isPropertyAvailable prepend="AND" property="description">

           UPPER(description) $operator$ #description#

      </isPropertyAvailable>

      <isPropertyAvailable prepend="AND" property="status">

        status_id = #status#

      </isPropertyAvailable>

    </dynamic>

    order by id, name

  </statement>

 

The dynamic query has 2 parts: the first part (the resultMap element) defines how the results from the query are stored; the second part (the statement element) defines the query itself.  The query’s dynamic where clause includes the columns that may be entered by the user during the search.  The results are stored and returned as a Map.

7         Step 5: Create & Register Reference Queries

Again, here’s the Catalog Search page:

CatalogSearch.jpg

 

The Status field is a drop-down box showing list of valid statuses the user can select.  We call these reference values (because the CatalogStatus model object extends the Reference model object.  In fact, all references must extend Reference).

The list of values used to populate the drop-down box comes from an iBATIS query.  These reference queries are defined and stored in Reference.xml (under WEB-INF/classes).

Here’s an example of a Reference Query for CatalogStatus:

<resultMap id="keyedResultMap" class="java.util.HashMap">

    <result property="id" column="ID"/>

    <result property="description" column="DESCRIPTION"/>

  </resultMap>

 

  <statement id="getCatalogStatuses" resultMap="keyedResultMap" parameterClass="java.util.Map">

      SELECT ID, DESCRIPTION FROM REF_CATALOG_STATUS

  </statement>

8         Step 6: Create Metadata for the Search Module

If you need Search functionality for a particular module, you will need to define the metadata for both the Search page and Search Result page.  Defining the metadata for a page may seem to be overwhelming and confusing at first, but once you get a hang of the key concepts, it will become straightforward in no time.

Here are the key concepts behind defining the metadata to support the search functionality for a model object:

1.       The search functionality for a model object is composed of three pages:

a.       The Search Page – this is where the search criteria is presented to the user

b.      The Search Results Page – this is where the results of the search is displayed

c.       The Nested Search Results Page – this is where the results of a nested search are displayed.  A nested search is a popup search where the user is trying to find a particular id of a record.  For example:  in the CatalogItem add form, the user may not know the id of a parent Catalog to use, so he would use the nested search functionality to look for the id.

2.       A Search Module for a model object is composed of the page layout for the Search Page, the layout for the Search Results Page, and the (optional if do you not need the nested search results feature) the layout for the Nested Search Results Page.  Each page layout contains metadata defining:

a.       The data elements of a page

b.      The various actions the user can perform

c.       Renderers (for rendering anything from the entire page to various components of a page)

d.      Event Listeners (for handling events)

 

Here’s a complete example of a Catalog Search Module.  Each Search Module is stored in a separate xml file.  In this case, it’s the CatalogSearch-meta.xml, located under WEB-INF/meta.

<module class="org.pojosoft.ria.gwt.client.meta.ModuleMeta" id="CatalogSearch" label="header.Catalog.Search">

 

  <renderer class="org.pojosoft.ria.gwt.client.meta.RendererMeta" clazz="org.pojosoft.ria.gwt.client.ui.SearchComposite">

    <params>

      <entry>

        <string>headerRenderer</string>

        <string>org.pojosoft.ria.gwt.client.ui.DefaultSearchHeaderRenderer</string>

      </entry>

    </params>

  </renderer>

 

  <layouts>

 

    <layout class="org.pojosoft.ria.gwt.client.meta.RecordLayoutMeta" id="Search" label="header.Summary" modelObjectName="Catalog">

 

      <renderer class="org.pojosoft.ria.gwt.client.meta.RendererMeta" clazz="org.pojosoft.ria.gwt.client.ui.DefaultSearchLayoutRenderer">

        <listeners>

          <listener class="org.pojosoft.ria.gwt.client.meta.EventListenerMeta" name="searchListener"

                    clazz="org.pojosoft.ria.gwt.client.ui.DefaultRecordLayoutListener">

            <params>

              <entry>

                <string>queryName</string>

                <string>search.getCatalogs</string>

              </entry>

            </params>

          </listener>

        </listeners>

      </renderer>

 

      <fields>

        <field class="org.pojosoft.ria.gwt.client.meta.SearchTextBoxMeta" searchOperator="startWith" name="id" property="id" label="label.Id" size="30"/>

        <field class="org.pojosoft.ria.gwt.client.meta.SearchTextBoxMeta" searchOperator="contain" name="desc" property="desc" label="label.Desc" size="90"/>

        <field class="org.pojosoft.ria.gwt.client.meta.ModelObjectReferenceListBoxMeta" name="status" property="status"

               label="label.Status" modelObjectName="CatalogStatus">

          <dataProvider class="org.pojosoft.ria.gwt.client.meta.DataProviderMeta"

                        clazz="org.pojosoft.ria.gwt.client.ui.DefaultModelObjectReferenceListDataProvider">

            <params>

              <entry>

                <string>queryName</string>

                <string>reference.getCatalogStatuses</string>

              </entry>

            </params>

          </dataProvider>

        </field>

      </fields>

 

      <actions>

        <action class="org.pojosoft.ria.gwt.client.meta.ActionMeta" name="search" label="button.Search"/>

        <action class="org.pojosoft.ria.gwt.client.meta.ActionMeta" name="reset" label="button.Reset"/>

      </actions>

 

    </layout>

 

    <layout class="org.pojosoft.ria.gwt.client.meta.CollectionSummaryDetailLayoutMeta" id="SearchResult"

            label="header.Catalog.SearchResults" modelObjectName="Catalog">

 

      <renderer class="org.pojosoft.ria.gwt.client.meta.RendererMeta"

                clazz="org.pojosoft.ria.gwt.client.ui.DefaultSearchResultRenderer">

        <params>

          <entry>

            <string>modelObjectPersistenceRef</string>

            <string>Catalog</string>

          </entry>

        </params>

      </renderer>

 

      <summary class="org.pojosoft.ria.gwt.client.meta.CollectionSummaryMeta">

 

        <treeTable class="org.pojosoft.ria.gwt.client.meta.TreeTableMeta" nested="true" expandAll="true" image="record.gif">

 

          <cellRenderer class="org.pojosoft.ria.gwt.client.meta.RendererMeta" clazz="org.pojosoft.ria.gwt.client.ui.DefaultSearchResultTreeTableCellRenderer">

            <listeners>

              <listener class="org.pojosoft.ria.gwt.client.meta.EventListenerMeta" clazz="org.pojosoft.ria.gwt.client.ui.treetable.DefaultTreeTableCellListener"/>

            </listeners>

          </cellRenderer>

 

          <columns>

            <column class="org.pojosoft.ria.gwt.client.meta.ModelColumnMeta" name="id" property="id" label="label.Id"

                    width="20%" sort="true"/>

            <column class="org.pojosoft.ria.gwt.client.meta.ModelColumnMeta" name="name" property="name"

                    label="label.Name" width="20%" sort="true"/>

            <column class="org.pojosoft.ria.gwt.client.meta.ModelColumnMeta" name="status" property="status"

                    label="label.Status" width="20%" sort="true"/>

            <column class="org.pojosoft.ria.gwt.client.meta.ModelColumnMeta" name="description" property="description"

                    label="label.Desc" width="20%" sort="true"/>

            <column class="org.pojosoft.ria.gwt.client.meta.ActionColumnMeta" name="action" label="label.Action" width="20%">

              <actions>

                <action class="org.pojosoft.ria.gwt.client.meta.ActionMeta" name="edit" label="label.Edit" image="edit.gif"/>

                <action class="org.pojosoft.ria.gwt.client.meta.ActionMeta" name="remove" label="label.Remove" image="remove.gif"/>

              </actions>

            </column>

          </columns>

 

        </treeTable>

 

      </summary>

 

    </layout>

 

    <layout class="org.pojosoft.ria.gwt.client.meta.CollectionSummaryDetailLayoutMeta" id="NestedSearchResult"

            label="header.Catalog.SearchResults" modelObjectName="Catalog">

 

      <renderer class="org.pojosoft.ria.gwt.client.meta.RendererMeta"

                clazz="org.pojosoft.ria.gwt.client.ui.DefaultSearchResultRenderer">

        <params>

          <entry>

            <string>modelObjectPersistenceRef</string>

            <string>Catalog</string>

          </entry>

        </params>

      </renderer>

 

      <summary class="org.pojosoft.ria.gwt.client.meta.CollectionSummaryMeta">

 

        <treeTable class="org.pojosoft.ria.gwt.client.meta.TreeTableMeta" nested="true" expandAll="true" image="record.gif">

 

          <cellRenderer class="org.pojosoft.ria.gwt.client.meta.RendererMeta" clazz="org.pojosoft.ria.gwt.client.ui.DefaultSingleSelectSearchResultTreeTableCellRenderer"/>

 

          <columns>

            <column class="org.pojosoft.ria.gwt.client.meta.ModelColumnMeta" name="id" property="id" label="label.Id"

                    width="20%" sort="true"/>

            <column class="org.pojosoft.ria.gwt.client.meta.ModelColumnMeta" name="name" property="name"

                    label="label.Name" width="20%" sort="true"/>

            <column class="org.pojosoft.ria.gwt.client.meta.ModelColumnMeta" name="status" property="status"

                    label="label.Status" width="20%" sort="true"/>

            <column class="org.pojosoft.ria.gwt.client.meta.ModelColumnMeta" name="description" property="description"

                    label="label.Desc" width="20%" sort="true"/>

            <column class="org.pojosoft.ria.gwt.client.meta.ActionColumnMeta" name="action" label="label.Action" width="20%">

              <actions>

                <action class="org.pojosoft.ria.gwt.client.meta.ActionMeta" name="select" label="label.Select"/>

              </actions>

            </column>

          </columns>

 

        </treeTable>

 

      </summary>

 

    </layout>

 

  </layouts>

 

</module>

 

Key things to note:

·         The metadata is define and stored using XStream format (http://xstream.codehaus.org).  Using XStream allows us to declaratively define in XML how the page should be layout.  At runtime, the XML is deserialized into its Object form (i.e. Java classes).  These classes (containing the layout metadata for the module & pages) are sent to the client side, where they are used by the Easy RIA’s rendering engine (for GWT) to render the pages.

·         Whenever you see the attribute class”, its value represents the Java class that XStream will deserialize the XML into.  Do not modify any of these values.

·         Whenever you see the attribute “clazz”, its value represents the class name of either a Renderer or a Listener.   Renderers are responsible for rendering the UI component.  Listeners are responsible for handling the events generated by the UI components.

·         Each module has a renderer.  Note that the default header renderer for a Search Module is “org.pojosoft.ria.gwt.client.ui.DefaultSearchHeaderRenderer”.  If you want to use your own custom header renderer, create one and register it with the framework before using.  See the code in Home.java (or the Easy RIA API online here) on how to implement & register a custom renderer.

·         There are 3 types of layout:

o   Record Layout – for use when the fields are to be displayed in a record format.

o   Collection Layout – for use to display a collection (of records) in a tabular format (in our case, we use a TreeTable).  Note that Easy RIA supports nested records.  So if the records in the collection are nested, the TreeTable widget will render the record as a combination of a Tree and a Table (therefore the name TreeTable).

o   Custom Layout – for use to define custom pages.  If you are using a Custom Layout, you must create a register your custom renderer with the framework before using it in the metadata.

·         For the Search layout (the layout whose id=”Search”):

o   It is of type record layout (“org.pojosoft.ria.gwt.client.meta.RecordLayoutMeta”).

o   The “queryName” parameter value of the listener for Search layout’s renderer must match a dynamic iBATIS query name stored in Search.xml (under WEB-INF/classes).

o   The fields define the input fields for the Search page.

o   For a Reference field (where class= "org.pojosoft.ria.gwt.client.meta.ModelObjectReferenceListBoxMeta"), the “queryName” parameter value of its “dataProvider” must match an iBATIS query name stored in Reference.xml (under WEB-INF/classes).

o   The actions “search” and “reset” are the default actions that the default Search page renderer & listener understand.

·         For the Search Results layout (the layout whose id=”SearchResult”):

o   It is of type collection layout (“org.pojosoft.ria.gwt.client.meta.CollectionSummaryMeta”).

o   The “modelObjectPersistenceRef” parameter value for the Search Result layout’s renderer must match an entry already defined in Persistence-meta.xml (under WEB-INF/meta).

o   The “summary” section (i.e. the search results collection) is being rendered using a TreeTable.   Note the default cell renderer and listener for the TreeTable.

o   By default, the “edit” and “remove” actions are supported by the default cell renderer and listener for the TreeTable.  If you need to support custom actions, you will need to create your own renderer (and register it with the framework before using it in the metadata) so it knows how to render the custom action and handle the custom action’s events.

·         For the Nested Search Results layout (the layout whose id=”NestedSearchResult”), its metadata is exactly like that of Search Results layout, except for the “actions” element.

 

Refer to the Easy RIA Javadoc API available online here for complete reference and explanations of all the different metadata classes that you can use, including the renderers and listeners that come packaged with the Easy RIA framework.

9         Step 7: Create Metadata for Manage Model Object Module

Each Manage Model Object Module contains the layouts for managing (add/update/delete) a model object and its child collection(s).  In our Catalog example, the Catalog model object contains a CatalogItem child collection.  This means that there should be 2 layouts defined in the Manage Catalog Module metadata xml (one for the Catalog and one for its CatalogItem child collection):

<module class="org.pojosoft.ria.gwt.client.meta.ModuleMeta" id="Catalog" label="header.Catalog" modelObjectName="Catalog">

 <renderer class="org.pojosoft.ria.gwt.client.meta.RendererMeta" clazz="org.pojosoft.ria.gwt.client.ui.ModuleComposite">

    <params>

      <entry>

        <string>headerRenderer</string>

        <string>org.pojosoft.ria.gwt.client.ui.DefaultModuleHeaderRenderer</string>

      </entry>

    </params>

  </renderer>

 

  <layouts>

 

    <layout class="org.pojosoft.ria.gwt.client.meta.RecordLayoutMeta" id="Summary" label="header.Summary" modelObjectName="Catalog">

 

      <renderer class="org.pojosoft.ria.gwt.client.meta.RendererMeta" clazz="org.pojosoft.ria.gwt.client.ui.DefaultRecordLayoutRenderer">

        <dataProvider class="org.pojosoft.ria.gwt.client.meta.ModelObjectDataProviderMeta"

                      clazz="org.pojosoft.ria.gwt.client.ui.DefaultModelObjectDataProvider"/>

        <listeners>

          <listener class="org.pojosoft.ria.gwt.client.meta.EventListenerMeta" clazz="org.pojosoft.ria.gwt.client.ui.DefaultRecordLayoutListener"/>

        </listeners>

      </renderer>

 

      <fields>

        <field class="org.pojosoft.ria.gwt.client.meta.TextBoxMeta" name="id" property="id" label="label.Id" size="30" required="true"/>

        <field class="org.pojosoft.ria.gwt.client.meta.ModelObjectReferenceListBoxMeta" name="status.id" property="status.id"

               label="label.Status" required="true" modelObjectName="CatalogStatus">

          <dataProvider class="org.pojosoft.ria.gwt.client.meta.DataProviderMeta"

                        clazz="org.pojosoft.ria.gwt.client.ui.DefaultModelObjectReferenceListDataProvider">

            <params>

              <entry>

                <string>queryName</string>

                <string>reference.getCatalogStatuses</string>

              </entry>

            </params>

          </dataProvider>

        </field>

        <field class="org.pojosoft.ria.gwt.client.meta.TextBoxMeta" name="name" property="name" label="label.Name" size="30" />

        <field class="org.pojosoft.ria.gwt.client.meta.TextAreaMeta" name="description" property="description" label="label.Desc"

               width="50" visibleLineCount="8"/>

        <field class="org.pojosoft.ria.gwt.client.meta.TextBoxMeta" name="version" property="version" label="label.Version" size="30"

               visible="false"/>

 

           <field class="org.pojosoft.ria.gwt.client.meta.DateInputBoxMeta" name="lastUpdateDate" property="lastUpdateDate"

                   label="label.lastUpdateDate" size="30" readOnly="false" converter="DateFieldConverter"/>

 

      </fields>

 

      <actions>

        <action class="org.pojosoft.ria.gwt.client.meta.ActionMeta" name="create" label="button.Create"/>

        <action class="org.pojosoft.ria.gwt.client.meta.ActionMeta" name="update" label="button.Update"/>

        <action class="org.pojosoft.ria.gwt.client.meta.ActionMeta" name="remove" label="button.Remove"/>

      </actions>

 

    </layout>

 

    <!-- items -->

    <layout class="org.pojosoft.ria.gwt.client.meta.CollectionSummaryDetailLayoutMeta" id="items"

            label="header.Catalog.Items" modelObjectName="Item">

 

      <renderer class="org.pojosoft.ria.gwt.client.meta.RendererMeta"

                clazz="org.pojosoft.ria.gwt.client.ui.DefaultCollectionSummaryDetailLayoutRenderer">

        <dataProvider class="org.pojosoft.ria.gwt.client.meta.ModelObjectCollectionDataProviderMeta"

                      clazz="org.pojosoft.ria.gwt.client.ui.DefaultModelObjectChildCollectionDataProvider"

                      collectionProperty="items"/>

        <listeners>

          <listener class="org.pojosoft.ria.gwt.client.meta.EventListenerMeta" clazz="org.pojosoft.ria.gwt.client.ui.DefaultCollectionLayoutListener"/>

        </listeners>

      </renderer>

 

      <summary class="org.pojosoft.ria.gwt.client.meta.CollectionSummaryMeta" label="header.Catalog.Items">

 

        <treeTable class="org.pojosoft.ria.gwt.client.meta.TreeTableMeta" image="record.gif">

 

          <cellRenderer class="org.pojosoft.ria.gwt.client.meta.RendererMeta" clazz="org.pojosoft.ria.gwt.client.ui.DefaultCollectionTreeTableCellRenderer">

            <listeners>

              <listener class="org.pojosoft.ria.gwt.client.meta.EventListenerMeta" clazz="org.pojosoft.ria.gwt.client.ui.treetable.DefaultTreeTableCellListener"/>

            </listeners>

          </cellRenderer>

 

          <columns>

            <column class="org.pojosoft.ria.gwt.client.meta.ModelColumnMeta" name="id" property="id"

                    label="label.Id" width="15%" sort="true"/>

            <column class="org.pojosoft.ria.gwt.client.meta.ModelColumnMeta" name="name" property="name"

                    label="label.Name" width="15%" sort="true"/>

            <column class="org.pojosoft.ria.gwt.client.meta.ModelColumnMeta" name="description" property="description"

                    label="label.Desc" width="15%" sort="true"/>

            <column class="org.pojosoft.ria.gwt.client.meta.ModelColumnMeta" name="price" property="price"

                    label="label.Price" width="10%" sort="true"/>

            <column class="org.pojosoft.ria.gwt.client.meta.ActionColumnMeta" name="action" label="label.Action" width="15%">

              <actions>

                <action class="org.pojosoft.ria.gwt.client.meta.ActionMeta" name="edit" label="label.Edit" image="edit.gif">

                  <params>

                    <entry>

                      <string>readOnlyFields</string>

                      <list>

                        <string>catalog.id</string>

                      </list>

                    </entry>

                  </params>

                </action>

                <action class="org.pojosoft.ria.gwt.client.meta.ActionMeta" name="remove" label="label.Remove" image="remove.gif"/>

              </actions>

            </column>

          </columns>

 

        </treeTable>

 

        <actions>

          <action class="org.pojosoft.ria.gwt.client.meta.ActionMeta" name="addNew" label="button.AddNew">

            <params>

              <entry>

                <string>parentFieldProperty</string>

                <string>catalog.id</string>

              </entry>

            </params>

          </action>

        </actions>

 

      </summary>

 

    </layout>

 

  </layouts>

 

</module>

 

Key things to note:

·         As in the Search Module, the metadata is defined and stored using XStream format.

·         Whenever you see the attribute “class”, its value represents the Java class that XStream will deserialize the XML into.  Do not modify any of these values.

·         Whenever you see the attribute “clazz”, its value represents the class name of either a Renderer or a Listener.   Renderers are responsible for rendering the UI component.  Listeners are responsible for handling the events generated by the UI components.

·         Each module has a renderer.  Note that the default header renderer for a Manage Module is “org.pojosoft.ria.gwt.client.ui.DefaultModuleHeaderRenderer”.  If you want to use your own custom header renderer, create one and register it with the framework before using.  See the code in Home.java (or the Easy RIA API online here) on how to implement & register a custom renderer.

·         The “modelObjectName” attribute of the “module” element must match an entry defined in Persistence-meta.xml.  This tells the framework which model object is this module for.

·         There are 3 types of layout: Record Layout, Collection Layout, and Custom Layout.  These 3 layout types are described in the Search Module discussion (Step 7).

·         For the Summary layout (the layout whose id=”Summary”):

o   It is of type record layout (“org.pojosoft.ria.gwt.client.meta.RecordLayoutMeta”).

o   The “modelObjectName” attribute of the “layout” element must match an entry defined in Persistence-meta.xml.  This allows the framework to map the CRUD operations defined in Persistence-meta.xml with the model object used in this layout.

o   The default renderer for a summary record layout is “org.pojosoft.ria.gwt.client.ui.DefaultRecordLayoutRenderer”.  The “dataProvider” registered with this renderer is responsible for providing the renderer with the data to render.  One or more listeners can be registered with this renderer.  The default listener (“org.pojosoft.ria.gwt.client.ui.DefaultRecordLayoutListener”) handles the default “create”, “update”, and “remove” actions defined in the “actions” element.

o   The fields define the fields to be rendered for the record.

o   For a Reference field (where class= "org.pojosoft.ria.gwt.client.meta.ModelObjectReferenceListBoxMeta"), the “queryName” parameter value of its “dataProvider” must match an iBATIS query name stored in Reference.xml (under WEB-INF/classes).

o   The actions “create”, “update”, and “remove” are the default actions that the layout’s renderer & listener understand.

·         For the child Collection layout (the layout whose id=” items”):

o   It is of type collection layout (“org.pojosoft.ria.gwt.client.meta.CollectionSummaryMeta”).

o   The “modelObjectName” attribute of the “layout” element must match an entry defined in Persistence-meta.xml.  This allows the framework to map the CRUD operations defined in Persistence-meta.xml with the model object used in this layout.

o   The default renderer for a collection layout is “org.pojosoft.ria.gwt.client.ui.DefaultCollectionSummaryDetailLayoutRenderer”.  The “dataProvider” registered with this renderer is responsible for providing the renderer with the data to render.  One or more listeners can be registered with this renderer.  The default listener (“org.pojosoft.ria.gwt.client.ui.DefaultCollectionLayoutListener”) handles the default “edit” and “remove” actions (for each record) defined in the “actions” element.

o   The “readOnlyFields” parameter of the “edit” action tells the framework that when displaying the edit form for this record, make sure that the values listed in the “readOnlyFields” parameter are read-only fields.

o   The “addNew” action allows the user to add a new record to the collection.  The “parentFieldProperty” parameter of the “addNew” action tells the framework which property (i.e. attribute) of the model object is read-only.

o   The child collection is being rendered using a TreeTable.   Note the default cell renderer and listener for the TreeTable.

o   By default, the “edit” and “remove” actions are supported by the default cell renderer and listener for the TreeTable.  If you need to support custom actions, you will need to create your own renderer (and register it with the framework before using it in the metadata) so it knows how to render the custom action and handle the custom action’s events.

 

Refer to the Easy RIA Javadoc API available online here for complete reference and explanations of all the different metadata classes that you can use, including the renderers and listeners that come with the Easy RIA framework.

10    Step 8: Create Menu

The entire menu can also be defined using metadata.  It is quite straightforward to define an entire menu hierarchy (menus and sub-menus) for an application.  Easy RIA allows the menu to be integrated with its security framework, allowing menus to be disabled/enabled based on the user’s role(s).  You don’t have to use this feature if you don’t want to.

Here’s the menu metadata used for the Catalog sample application:

<org.pojosoft.ria.gwt.client.meta.MenuMeta>

 

  <menuItems>

 

    <org.pojosoft.ria.gwt.client.meta.MenuItemMeta>

      <id>Demo</id>

      <label>menu.Top.Demo</label>

      <enabled>false</enabled>

      <subMenuItems>

        <org.pojosoft.ria.gwt.client.meta.MenuItemMeta>

          <id>Catalog</id>

          <label>menu.Catalog</label>

          <enabled>false</enabled>

          <commandClass>org.pojosoft.catalog.web.gwt.client.menu.CatalogMenuCommand</commandClass>

        </org.pojosoft.ria.gwt.client.meta.MenuItemMeta>

      </subMenuItems>

    </org.pojosoft.ria.gwt.client.meta.MenuItemMeta>

 

  </menuItems>

 

</org.pojosoft.ria.gwt.client.meta.MenuMeta>

 

Each leaf menu is associated with a command.  A menu command is responsible for handling the menu action.   All menu commands must implement the interface MenuCommand.  You must register each command defined in the metadata.  See the code in Home.java (or the Easy RIA API online here) on how to implement & register a menu command.

11    Step 9: Defining GWT Module and Entry Point for the Application

Here is the GWT module file Catalog.gwt.xml for the Catalog sample applcation:

<module>

<inherits name='com.google.gwt.user.User'/>

            <inherits name='com.google.gwt.i18n.I18N’/>

            <inherits name='org.pojosoft.ria.gwt.Ria'/>

            <entry-point class='org.pojosoft.catalog.web.gwt.client.Home'/>

</module>

 

The “entry-point” defines the class that GWT first executes.

You will need to define a module file and create an entry point Java class for your application.  Each application’s entry point is different.  Easy RIA does not ship with a default entry point class implementation.  However, you can look at both the Catalog and TreeTable sample applications entry point classes for guidance.  You can also look at the entry point class for Open Enrollment LMS.

12    Step 10: Running the Application in Hosted Mode

A GWT application can be run in hosted mode or deployed mode.  Usually, you would use hosted mode during development (slower but more productive since hosted mode dynamically compiles client Java classes into JavaScript, allowing you to make code changes without restarting the GWT Development Shell).  Deployed mode is when the GWT application is compiled into JavaScript and the compiled JavaScript code is deployed and run in an application server.

There are 2 ways to run in hosted mode:

1.       Running hosted mode using the embedded Tomcat that comes with GWT.  We don’t use this approach.

2.       Running hosted mode using an external Tomcat instance.  We use this approach since it allows us to choose any version of Tomcat.  It also allows us to separate the GWT Development Shell from Tomcat, allowing the stop/start of GWT Development Shell and Tomcat to be independent of each other.

To run in hosted mode, download the latest 5.5.x version of Tomcat here.  Extract it to a directory.  Under $TOMCAT_HOME$/conf/Catalina/localhost, add a project.xml file (replace the word project with your project/application name) with the following content:

<?xml version="1.0" encoding="UTF-8"?>

<Context path="/project" docBase="D:\projects\pojosoft\ria\samples\treetable\webapp"/>

 

Replace path=“/project” with your project name.  For instance, for the Catalog project, this would be path=“/catalog”.  This is the web context path for your application.  The “docBase” attribute points to the webapp (root web directory) location of your GWT application.

Creating a project.xml file (note again that the word “project” must match the context path so name this xml file accordingly) essentially allows you to deploy a web application on Tomcat using your current project structure.  This is very convenient.

The Catalog sample application comes with a Windows command script for launching the GWT Development Shell using an external Tomcat instance.  The script, allowing with all the necessary files, is located under bin/gwt directory.  Remember to start Tomcat first before launching the GWT Development Shell.

13    Step 11:  Creating the WAR Distribution

The Catalog sample application’s Ant build.xml contains all the tasks necessary for development and distribution.  To build the WAR file, run “ant dist” and you’ll find the WAR file under the dist directory.  You can use this exact build file for your project.  Remember to modify the build.properties file if necessary.

14    Advanced Topics

We used Easy RIA to develop Open Enrollment LMS.  The framework comes well-tested and with many features.  Here are some of the more important features. 

14.1  I18N & L10N

Easy RIA is fully I18Ned and L10Ned.  DateTime, Currency, and Number values are displayed using the user’s Locale.  The sorting done on the client side (in the TreeTable) is performed using the user’s Locale.  Notice this line in Home.jsp of the Catalog sample application:

  <meta name="gwt:property" content="<%=localeProp%>">

This sets the locale property on the client side using the logged in user’s Locale ID.

You probably noticed that labels are used in the metadata xml files rather than hard-coded String literals.  This allows support for multiple languages.  The actual values for these label properties are stored in the various Application*.properties files under WEB-INF/classes.  If you need to support another language rather than the default English, you will need to create another set for your particular language.

14.2  Multiple Inheritance Collection

Easy RIA supports a typed-hierarchy of record collection.  For example, a collection of animal objects could be of type Dog, Cat, Bird, Lion, etc.  But all of them are of type Animal.

14.3  Custom Renderers & Listeners

As you probably know by now, Easy RIA supports custom renderers and listeners for use-cases where the framework needs to be extended.

14.4  Security

Easy RIA comes with a role-based security framework.  You can define roles and functions, create users, and then assign roles to users.  The menu items can be displayed or hidden based on the user’s roles.  Our Open Enrollment LMS uses all of these features. 

14.5  CSS

All the widgets used in Easy RIA are styled using CSS.  The name of the CSS file is ria.css.  We currently support only one theme.  Additional themes will be delivered in the near future.

Note that all of our custom GWT widgets are entirely written in Java.  There is no JavaScript code.