Edit/new view - Replicators, Switches and Transit Beans#

Having looked at how the default "list" view for the Recipe entity is put together, we're set up to look at the most complex view in this application, the "new Recipe" view, which is given the viewID recipe-edit. This view doubles as the view for creating a new entity as well as editing an existing one, with the minor differences taken care of with a UISwitch component.

To start with, as before, here is the "picture on the box" so we can see what result this view is aiming at:

The components tree for the recipe-show , as well as containing a UIReplicator component of the sort we saw before, also has a number of other components and bindings necessary to manage the input process. Here are the first dozen lines for us to look over:


<view>
  <component type="switch" id="headingswitch">
    <lvalue type="elref">#{viewParameters.mode}</lvalue>
    <rvalue type="java.lang.String">new</rvalue>
    <truecomponent type="output" id="new-recipe-heading"/>
    <falsecomponent type="output" id="edit-recipe-heading"/>
  </component>
  <component type="form" id="basic-form">
    <component type="replicator" id="reciperep">
      <valuebinding>#{viewParameters.entity}</valuebinding>
      <idstrategy type="idremap" basepath="#{Recipe}" idfield="ID"/>
      <elideparent>true</elideparent>
      <component type="branchcontainer" id="recipeline:">
        <component type="input" id="recipe-title">
          <valuebinding>#{*.title}</valuebinding>
        </component>

UISwitch#

The first interesting component is introduced by <component type="switch" which represents a component of type UISwitch (by standard RSF component serialization, the type parameter for a component is always the lower-cased component name omitting "UI"). This is another "template component" - like the replicator, UISwitch must never appear in the final component tree presented to the renderer, but instead is used to guide template component expansion operated by the XML view system.

Just as UIReplicator operates basic iteration semantics (iterating over an object list), UISwitch operates conditional branching, completing our basic programming capabilities in XML component trees. UISwitch accepts 4 arguments - firstly an "lvalue" and an "rvalue" which will form the basis of the comparison. The template expander will evaluate a boolean expression representing "lvalue == rvalue", and based on whether this expression is true or false, will choose one of the second two arguments, truecomponent or falsecomponent to be the one that finally enters the component tree.

So in this case, we are testing the mode field of the viewParameters request bean (this is a standard RSF bean representing the location of the current view) for being equal to the String "new". If it is, we emit the new-recipe-heading component (which is defined in the HTML template to contain the relevant view heading), if not, we emit edit-recipe-heading.

Remember that in the Java version of this app, this component layout will just be replaced by a simple genuine if statement choosing the right heading component.

Return of the Replicator#

This view, like the recipes list view from the previous page, also defines a replicator component inside its form, which looks at first sight a bit odd, since there is nothing obviously being repeated in this view. In this case, we are using the replicator just for its id rewriting function, based on the single viewParameters object which defines the entity of focus for the current view, rather than for any repetition as such. Given the definition above, we can see the idstrategy that we define

 <idstrategy type="idremap" basepath="#{Recipe}" idfield="ID"/>

will replace the wildcard paths in the replicated component with the field "ID" obtained from the value binding target from the replicator, which is #{viewParameters.entity} . That is, the total request EL containing the ID value is #{viewParameters.entity.ID} , and this value will be subsituted for "*" where it occurs in replicated components, afte prefixing with the base path of "#{Recipe}". If this all looks somewhat involved, recall that there has to be some price for pure XML-based programming of this sort! You will see how this logic is considerably simpler in the "pure-Java" version of this app we will present on the last page.

Dates and Transit Beans#

Everything else in this view is pretty straightforward, except for the handling of the "broken-up" date field that we can type into. In fact, usability requirements (as well as programming convenience!) are strongly recommending towards allowing free-form date entry fields these days, but we took the decision of exactly replicating the functionality of the original Ruby app, and although being somewhat more complex this nicely showcases the very powerful capabilities of RSF's "request bean programming model".

Firstly, here is the relevant stanza of recipe-edit.xml that deals with the date input:

  <component type="input" id="recipe-date-year">
    <valuebinding>#{datetransit.1.year}</valuebinding>
  </component>
  <component type="select" id="recipe-date-month">
    <optionlist>
      <valuebinding>#{monthBean.indexes}</valuebinding>
    </optionlist>
    <optionnames type="outputmany">
      <resolver type="elref">#{monthBean.names}</resolver>
    </optionnames>
    <selection type="input">
      <valuebinding>#{datetransit.1.month}</valuebinding>
    </selection>
  </component>
  <component type="input" id="recipe-date-day">
    <valuebinding>#{datetransit.1.day}</valuebinding>
  </component>
...
  <parameter type="elbinding">
    <valuebinding>#{*.date}</valuebinding>
    <rvalue type="elref">#{datetransit.1.date}</rvalue>
  </parameter>

To start with, recipe-date-year and recipe-date-day are just straightforward text input fields, but the first thing to notice is the slightly interesting EL target that they refer to, datetransit.

datetransit is our first example of an exploder bean. The definition of datetransit, from this apps requestContext.xml file is as follows:

<!-- A "Date Transit" exploder to collect and focus a 
     "broken-up" date from fields in Date/Month/Year 
     into a full java.util.Date object. -->  
  <bean id="datetransit" parent="beanExploder">
    <property name="beanClass" 
       value="uk.org.ponder.dateutil.DMYTransit"/>
  </bean>

(see previous page for comments on parent bean definitions - in this case beanExploder is a standard RSF parent bean definition defined in blank-requestContext.xml). BeanExploders are provide similar functionality to RSF's One True Path entity beans, only generally for non-persistent beans - they take a bean "exemplar" (or more simply, just a class name) and "explode" this bean to create an infinite lazy collection of distinct instances initialized from it. Another way of thinking about this is that it is the request-scope equivalent to "defining a variable" - in this case, the path #{datetransit.1} refers to a particular (dynamically created at request scope) instance of a uk.org.ponder.dateutil.DMYTransit bean - if we were inputting two dates in this view we might call the second such instance #{datetransit.2} . These beans like all request beans are thrown away at the end of the request cycle and are very useful to maintain cleanly programming.

If you take a look at the definition of DMYTransit you can see that, in line with RSF philosophy it is a "completely clean bean" - it consists of nothing but getters and setters, has no framework dependencies (apart from exception types - this will be solved later), and could have been defined by anyone. It has three publically readable fields year, month and day, corresponding (in this case) to the three fields incoming from the UI, and has public accessor methods getDate and setDate which will render or accept the same date represented as a java.util.Date. In fact, this is a very simple and reusable basic component that performs type conversion of dates into a DMY "broken-up" form, and need not be used with RSF in particular.

The following picture shows the layout of the request beans (in red) together with the three components (green) involved in the submission:

Remember that this bean-wiring arrangement is encoded in the original component at render time, but not actually created until the POST is handled from the submission - this achieves one of the core RSF goals of allowing zero-server-state request handling.

Selection controls#

We decided to implement the "month" control with a dropdown. This is our first example of an RSF selection control. The key point to bear in mind is that RSF selection controls are represented as aggregates of simpler components, each some kind of UIBound - in total there are three: optionlist represents the "parent" list, from which choices are being made, optionnames represents the rendered names of these options in the UI, and selection represents the actual selection made by the user. In this example we initialise each of these three sub-components in a straightforward way - selection is simply bound to month field the datetransit.1 bean in the way one would expect. The other two fields are bound to the (application scope) bean monthbean, which we have defined in our applicationContext.xml as follows:

  <bean id="monthBean" class="uk.org.ponder.dateutil.MonthBean" 
    init-method="init"/>

The MonthBean bean is another simple dependency-free framework bean of the sort anyone could implement. In a more sophisticated application, you would make this a request bean, so that the locale parameter could be set in a request-aware way, appropriate for the current user. The bean simply does the work of exposing the list of Strings forming the "internal" month numbers, as well as querying the locale to find the corresponding month names.

Conclusion#

We have constructed a simple one-entity "CRUD" app with three basic views for listing, editing and showing the Recipe entities (we skipped treatment of the recipe-show view since it is straightforward).

In practice, you would you would have generated this application "logic" automatically using an XSL (coming soon, March 2006), but we have walked through it by hand here to get you set up for understanding and modifying these component trees for more advanced applications. At this point, we have arrived at the level of functionality of the end of page 3 of the Ruby version, showing off the ROR "scaffold" framework. We will now continue to follow this tutorial by adding a new entity to our data model, and customising all the views.

However, it's worth noting at this point one of the crucial benefits of RSF (as well as those inherited from such marvellous frameworks as Spring and Hibernate) - that of complete previewability. In contrast to the Ruby version of this app in which the view files are a mishmash of markup and code, the RSF template files (in your webapp at the path src\webapp\content\templates) are PURE HTML, which you can just fire up in your browser, even navigate relative links &c - in general, see exactly how your app will fit together, apart from any POST functionality. This can be a marvellously useful capability - especially on larger, more long-lived projects with a separate UI team and proper requirements cycle - the HTML-version of the app can be easily circulated for review, comment and redesign, even complete with any Javascript, CSS, local navigation &c. Ruby on Rails, with its focus on extracting maximum possible development speed from 1-man teams, cannot help here.


Head - Hibernate Cookbook
Page 1 - Query beans, templates and views
Page 2 - Switches, Replicators and transit beans
Page 3 - The main app - entity selectors and Javascript
Page 4 - Let's hear that one more time - this time in Java

Add new attachment

Only authorized users are allowed to upload new attachments.

List of attachments

Kind Attachment Name Size Version Date Modified Author Change note
png
cookbook-basic-recipe-edit.png 23.2 kB 1 19-Jul-2006 09:36 AntranigBasman
png
datetransit.png 32.5 kB 1 19-Jul-2006 09:36 AntranigBasman
« This page (revision-) was last changed on 19-Jul-2006 09:36 by UnknownAuthor