Jaffa Logo
 
SourceForge.net
Home Contact Us FAQs Site Map
Source Forge: Homepage Bugs @ Sourceforge Mailing Lists @ Sourceforge Task Manager @ Sourceforge CVS @ Sourceforge
Jaffa Site
Jaffa Runtime
Jaffa RAD
Sub-Projects
My First Widgets top

Overview

To illustrate how we create a widget I will use the example of the session explorer screen, as it uses a UserGrid Widget which is one of the more complex widgets. To use our architecture for each screen you need to have an Action Bean , Form Bean and a JSP (ala Struts). It is important to note that JAFFA, although utilizing some of the Struts Architecture, differs from it in some important ways.

We must import the Struts Tag Library and the Jaffa Portlet tags.

<%@ tag lib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ tag lib uri="/WEB-INF/jaffa-portlet.tld" prefix="Portlet" %>

The header tag must be added in the head block of the JSP, look at this code snippet for an example.

<Portlet:Header/>

This tag is very important as many of the Widgets utilize this tag for writing out necessary Javascript and CSS code for a page. It also handles some behind the scenes javascript code necessary for information gathering on the page and adds in the base ref, that all the files are relative to.

The next tag that needs to be added to the JSP is the FORM tag.

<Portlet:Form action="/jaffa_sessionExplorer">

This tag extends the struts form tag with a few powerful variations (note that struts tag that it extends has been modified and simplified) Unlike Struts JAFFA has a more powerful event handling capability. The following hidden fields are added to every rendered JSP.

<input type="hidden" name="componentId" value="">
<input type="hidden" name="eventId" value="">
<input type="hidden" id="dateTimeStamp" value="">

The componentID keeps track of the current component, The eventID stores the event that has been fired. For example on any given screen we could have unlimited event handlers. The dateTimeStamp field is used for caching the date and time that an  error message was raised on rendering the screen. This then is written to a cookie. If the back button is pressed the datetimestamp on the screen and the cooke are compared, if they match the error box will not be displayed again. If a form is posted and the dateTimeStamp does not match the one stored in the cookie .. an error screen will be displayed (if any error messages were raised).

The final tag that is necessary is the footer tag.

<Portlet:Footer/>

This tag will write out any registerd JS files for widgets on the screen.

You may wonder why we don't just write the necessary javascript code for the widgets when the widgets themselves are rendered. The reason for this is simple. If you have for example 5 edit boxes on a screen they all require the editbox.js file to be included on a page. If the script was written at the time of widget rendering you would get 5 duplicate references to the embedded editbox.js file. This would be a sloppy way to have code on your HTML page.

Then Close the form tag.

</Portlet:Form>

It is important to note that Form beans do NOT persist in JAFFA by default. They are request scope only. The reason for this is to allow reuse of the same screens. If you wish to store any persisted values you MUST use the UserSession or a Component Controller. You can change if you wish the scope in the struts-config.xml file to make them persistent, but in doing so you would not have the facility for re-use of the same screens. Jaffa also uses the struts-config.xml file. As a rule in JAFFA we use Global Forwards, as opposed to definitions per action. Now that we have established the basic differences between Struts and JAFFA lets look at building a basic example.

Adding a text widget top

fig 1 - Execution Sequence for basic widget with only use of form bean and component controller(optional)

Lets make a basic modification to the Session Explorer. In this example we shall add a text widget to the screen so that when the refresh button is pressed its value will increment by 1.

First lets modify the sessionExplorer.jsp by adding in the text widget

<Portlet:Form action="/jaffa_sessionExplorer">
  <Portlet:Text field="counterText"/>
  <Portlet:Button field="Refresh" label="Refresh" preprocess="false"/>
</Portlet:Form>

Now lets take the form bean and added a corresponding getter for the counterText field, add in this import statement.

import org.jaffa.presentation.portlet.session.ui.SessionExplorerComponent;

Now add in the getter.

public String getCounterText() {
  return ( (SessionExplorerComponent) getComponent() ).getCounter();
}

There is no corresponding form bean reference to the refresh button as it has no model.

Lets look now at the Component Controller lets add a variable counter.

private int counter = 0;

Add a method to increment the counter variable and return its value.

public String getCounter() {
    return Integer.toString(counter++);
}

Finally lets look at the action bean.

    return new FormKey(SessionExplorerForm.NAME, component.getComponentId());
}

This causes the screen to be re-rendered after the post has been executed. Now lets add to the screen scenario by introducing an editbox widget to allow the user to add the amount to increment by.

Adding an edit box
fig 2 - Execution Sequence for more complex widget, Involving the action bean and limited form bean

Lets go back to the JSP and add the following code beside the text widget to add the editbox widget

<Portlet:EditBox field="increment"/>

Now in the Form Bean add.

private EditBoxModel w_increment = null;
public void setIncrementWV(String value) {
    EditBoxController.updateModel(value, getIncrementWM());
}
public EditBoxModel getIncrementWM() {
    if (w_increment == null) {
       w_increment = (EditBoxModel) getWidgetCache().getModel("increment");
       if (w_increment == null) {
           if (getIncrement() != null)
               w_increment = new EditBoxModel( getIncrement() );
           else
               w_increment = new EditBoxModel();
           // .
           // Add custom code
           // .
           // .
           getWidgetCache().addModel("increment", w_increment);
       }
   }
   return w_increment;
}

When the screen does a post the setIncrementWv(String value) is called and sets whatever the current value in the editbox is to its associated model. It then fires the getIncrementWM() method and adds the widget model to the widget model cache. This enables the form bean to populate the stored value from the cache into the widget when the screen is rendered another time. Now lets add another 2 methods to the Form Bean.

public String getIncrement() {
    return Integer.toString(( (SessionExplorerComponent) getComponent() ).getIncrement());
}
public void setIncrement(String increment) {
    ( (SessionExplorerComponent) getComponent() ).setIncrement(increment);
}

These 2 methods get and set values into the component controller. But they need to be invoked in your code. If we wish to prepopulate a value on the first rendering of the form .. we can do this in the component controller and the getmethod will be interrogated from the getIncrementWM method. The setIncrement() method will be invoked form the doValidate() method. This method is used to handle validation logic for a screen. Say for example we have a validation check to see if some data is correct, if it failed we would want to update the widget model, but we wouldnt want to update the incorrect data to the component controller. An example of this would be a integer that has been entered with a decimal point. Lets add a doValidate with no validation logic.

public boolean doValidate(HttpServletRequest request) {
   boolean valid = true;
   String value = null;
   value = getIncrementWM().getValue();
   setIncrement(value);
   return valid;
}

Lets now turn our attention to the Action bean ... replace the code for the do_Refresh_Clicked() method.

public FormKey do_Refresh_Clicked() {
       SessionExplorerForm myForm = (SessionExplorerForm) form;
       myForm.doValidate(request);
       return new FormKey(SessionExplorerForm.NAME, component.getComponentId());
}

This is the point at which we fire the doValidate() method on the form bean which will inturn fire the setIncrement() method, that calls the Component Controller's setIncrement() method.

Finally lets add the following methods to the Component Controller(replacing the original getCounter() method).

private int increment = 0;
public String getCounter() {
       counter = counter + getIncrement();
       return Integer.toString(counter);
}
public int getIncrement() {
       return increment;
}
public void setIncrement(String value) {
       increment = Integer.parseInt(value);
}

 

Adding an user grid

fig 3 - Execution Sequence for most complex widget, involving full use of action bean, form bean and component Controller.

Lets go back to the JSP and add the following code beside the text widget to add the editbox widget

<Portlet:UserGrid field="sessions" userGridId="sessionExplorer">
   <Portlet:UserGridColumn label="ID" dataType="CaseInsensitiveString" >
       <Portlet:Text field="JAFFA_ID"/>
   </Portlet:UserGridColumn>
   <Portlet:UserGridColumn label="User Name" dataType="CaseInsensitiveString" >
       <Portlet:Text field="USER_ID"/>
   </Portlet:UserGridColumn>
   <Portlet:UserGridColumn label="Remote Host" dataType="CaseInsensitiveString" >
       <Portlet:Text field="HOST"/>
   </Portlet:UserGridColumn>
   <Portlet:UserGridColumn label="Idle Time (Secs)" dataType="CaseInsensitiveString" >
       <Portlet:Text field="IDLE"/>
   </Portlet:UserGridColumn>
   <Portlet:UserGridColumn label="Components" dataType="CaseInsensitiveString" >
       <Portlet:Text field="COMPONENT_COUNT"/>
   </Portlet:UserGridColumn>
   <Portlet:UserGridColumn label="Session ID" dataType="CaseInsensitiveString" >
       <Portlet:Text field="HTTP_ID"/>
   </Portlet:UserGridColumn>
   <Portlet:UserGridColumn label="Created" dataType="CaseInsensitiveString" >
       <Portlet:Text field="CREATED"/>
   </Portlet:UserGridColumn>
   <Portlet:UserGridColumn label="Last Accessed" dataType="CaseInsensitiveString" >
       <Portlet:Text field="LAST_ACCESS"/>
   </Portlet:UserGridColumn>
   <Portlet:UserGridColumn label="Timeout (Secs)" dataType="CaseInsensitiveString" >
       <Portlet:Text field="TIMEOUT"/>
   </Portlet:UserGridColumn>
   <Portlet:UserGridColumn label="Action" dataType="CaseInsensitiveString" >
       <Portlet:Button field="View" label="[label.Jaffa.Widgets.Button.View]" />
   </Portlet:UserGridColumn>
</Portlet:UserGrid>

These are the widget tags on the session explorer that render the table with rows for different active sessions. Lets take a look more closely at.

This is the Outer Tag

<Portlet:UserGrid field="sessions" userGridId="sessionExplorer">

The field attribute is the name of the widget that will have a corresponding setter in the form bean. The userGridId is used for storing the widget specific configuration settings. This is the inner column tag with its unique col ID for this Usergrid.

<Portlet:UserGridColumn label="ID" dataType="CaseInsensitiveString">
   <Portlet:Text field="JAFFA_ID"/>
</Portlet:UserGridColumn>

As you can see within this column we have a text widget with a field name if "JAFFA_ID".

In the formbean we have the following code. This method returns the model for the UserGrid and adds the model to the UserSession cache.

public GridModel getSessionsWM() {
   if (w_sessions == null) {
       w_sessions = (GridModel) getWidgetCache().getModel("sessions");
       if (w_sessions == null) {
               w_sessions = getSessionsModel();
               getWidgetCache().addModel("sessions", w_sessions);
       }
   }
   return w_sessions;
}

This method will update any changed values in the model and set the cache

public void setSessionsWV(String value) {
   GridController.updateModel(value, getSessionsWM() );
}

This model returns and builds the model. It in this case is interrogating the SessionManager class and getting a list of available sessions. It then for each of the the sessions in the list, makes a grid model row and for each of the defined columns (these must match those defined in the JSP) it will add a model for the inner widgets.

private GridModel getSessionsModel() {
   GridModel model = new GridModel();
   UserSession[] sessionList = SessionManager.getSessions();
   long time = System.currentTimeMillis();
   for(int i=0; i<sessionList.length; i++) {
       UserSession u = sessionList[i];
       String sessId = u.getSessionId();
       HttpSession sess = (HttpSession) u.getHttpSession();
       GridModelRow row = model.newRow();
       row.addElement("JAFFA_ID",sessId);
       row.addElement("HTTP_ID",sess.getId());
       try {
       row.addElement("CREATED", Formatter.format(new DateTime(sess.getCreationTime())));
       row.addElement("TIMEOUT", sess.getMaxInactiveInterval() + "");
       row.addElement("LAST_ACCESS", Formatter.format(new DateTime(sess.getLastAccessedTime())));
       row.addElement("IDLE", ((time - sess.getLastAccessedTime())/1000) + "");
       } catch (java.lang.IllegalStateException e) {
       row.addElement("CREATED", "Invalidated");
       }
       row.addElement("COMPONENT_COUNT", (u.getComponents()==null? "0" : "" + u.getComponents().size()));
       row.addElement("USER_ID", u.getUserId());
       row.addElement("HOST", u.getUserHostAddr());
   }
   return model;
}


File: myFirstWidget.html, Last Modified: Tue Jul 1 2003 at 12:42:07pm. This site has been built using PPWIZARD