On 7/7/05, Rick Reumann <[EMAIL PROTECTED]> wrote:
> I posted this in reply to Tony's post, but figured this stuff comes up
> all the time and to those new to Struts, this below might help....
> 
> You can accomplish 99% of what you want with this simple design

Ok, Rick, lets pull out and compare :-) But before I go into
presenting my approach, could you tell me, what happens in your app
when a user clicks Refresh? Or Back?

> For the sake of this discussion I'm going to assume you are using a
> DispatchAction. In case you don't know, a DispatchAction just lets you
> combine similar types of behaviors into one Controller... so rather than
> have an UpdateUserAction, DeleteUserAction, etc.. you have one Action...
> UserAction with methods in it update(..), delete(...), etc.

I will use my DialogAction, which subclasses DispatchAction, adding an
important feature to it: two-phase request processing aka
Redirect-after-Post. It helps to avoid resubmits, and makes the whole
process of input/change state/render view much easier.

> So typically here's what I do and it covers 'almost' all of my
> scenarios. I find it easier to work with examples and your "User" is a
> good example to work with. Lets say we want to CRUD (create, retrieve,
> update, delete) for a User.
> 
> 1) Step 1 Create  a UserActionForm
>     For simplicity it has just two main properties...
>     String userName;
>     Integer userID;

Fine with me. I use abstract items with ID, strvalue and numvalue.

>    Also though we are going to provide the dispatch (or action) as
>    a property to give it default value of "add"
>    String dispatch = "add"
>    This way if we go right to the page it'll have 'add' by default

My DialogAction or its subclass CRUDAction (thanks for this great
example, I have action exactly for this use case) does not have any
default values. If you navigate to the action, it simply shows current
data for current state. So, as you see, my actionform has session
scope to be stateful.

> 2) Step 2 Have a BusinessObject back reprsenting your User. I like to
> pass Business objects (not tied to Struts to my backend), so you'll have
> in this case a simple:
> 
>     "UserVO" (value object.. could just call it "User" but for this
> discussion seeing VO helps you understand what it is)
>     String userName;
>     Integer userID;
> 
>     //NOTE: it helps if the properties of the VO and the ActionForm have
> the same name. Not a requirement but makes things easier which I'll show

I don't care much about how VO/BO is mapped to actionform. Basically,
if you want values to be populated from request, and to be used in
JSP, either create setter/getters in the actionform, or set VO/BO as
nested property of the actionform.

> 3)   Create a "UserDispatchAction"
> 
>     This will have the following methods:
>     (all with signature public ActionForward methodName (ActionMapping
> mapping, ActionForm form, HttpServletRequest request,
> HttpServletResponse response)
> 
>     setUpForEdit(..)
>     setUpForAdd(..)
>     add(...)
>     edit(...)
>     delete(...)
>     getUsers(...)

I have generic CRUDAction, which has create(), duplicate(), edit(),
view() and delete() methods. And of course, getView(), which renders a
proper page.

>     Before I get to the setUpForEdit() lets just handle the others...
> 
>     In all cases you will be submitting either a form or a link 

DispatchAction and its subclasses accepts input as form submission
only (POST), because it detects input phase or render phase by request
type. I think, this is a reasonable limitation.

> but in
> the struts-config file this will map to our UserAction where we also
> include the name of our UserActionForm, so our UserActionForm is always
> populated.
> 
>    So our Add method in our Action...
> 
> //**ADD****
> public ActionForward add(ActionMapping mapping, ActionForm form,
> HttpServletRequest request, HttpServletResponse response) throws Exception {
> 
>    UserActionForm userForm = (UserActionForm)form;
>    UserVO user = new UserVO():
>    //copy our form properties into the VO
>    PropertyUtils.copyProperties( user, userForm );
>    ourBackendDelegate.addUser( user );
>    return mapping.findForward("to_form");
> }

The corresponding create() method in CRUDAction from Struts Dialogs
looks like this:

    public ActionForward onCreate (ActionMapping mapping,
                                   ActionForm form,
                                   HttpServletRequest request,
                                   HttpServletResponse response) 
           throws Exception {

        // Action Form must implement ICRUDForm
        ICRUDForm crudForm = (ICRUDForm) form;

        // Create new business object (item), and get back errors if any
        ActionMessages messages = crudForm.crudCreate();

        // Successfully created new object and initialized action form
        if ((messages == null) || messages.isEmpty()) {
            // Need to handle this "forward" object in struts-config.xml
            return mapping.findForward(CRUDConstants.MAPPING_ON_CREATE_SUCCESS);

        // On error save messages into session. Struts 1.2.6+ has
        // a special method for it; this code saves errors directly
        // to session for compatibility with older Struts versions.
        } else {
            HttpSession session = request.getSession();
            session.setAttribute(Globals.ERROR_KEY, messages);
            // Handle this "forward" object in struts-config.xml
            return mapping.findForward(CRUDConstants.MAPPING_ON_CREATE_FAILURE);
        }
    }

> // UPDATE and DELETE...
>   Same as above except for use of...
> 
>   ourBackendDelegate.deleteUser( user );
>   ourBackendDelegate.updateUser( user );

Yep, same here.

> // THE SET UP FOR EDIT
> 
>   Ok, this is the one you were asking about. Now you have to think about
> how you would get here? Typically you'd get to an edit page by clicking
> on a user record to say "Hey, I want to edit this guy's user
> information"  So imagine a case where we have a list of users and
> 'userID" for each in the list, they click on the link and they'll come
> to this method which will get our user and then forward them to a page
> to do the editing. The method will look like...
> 
> public ActionForward setUpForEdit(ActionMapping mapping, ActionForm
> form, HttpServletRequest request, HttpServletResponse response) throws
> Exception {
> 
>    UserActionForm userForm = (UserActionForm)form;
>    //userID is set in this form when the user clicked on the link
>    //lets get our real business object of this user, based on ID..
>    UserVO user = ourBackendDelegate.addUser( userForm.getUserID() );
>    //copy the other way this time, from UserVO to our Form...
>    PropertyUtils.copyProperties( userForm, user  );
>    //finally we are going to reuse or form for add and edit, so
>    //we will set up our dispatch/action parameter
>    userForm.setDispatch("edit");
>    return mapping.findForward("to_form");
> }

I guess in the above method you meant to write  
  UserVO user = ourBackendDelegate.getUser( userForm.getUserID() );

Anyway, my version does not differ from other handlers.
Data is loaded from the backend by crudForm.crudLoadForUpdate()
method. This method has to load existing data either directly into
action form or into nested BO/VO.

    public ActionForward onEdit (ActionMapping mapping,
                                 ActionForm form,
                                 HttpServletRequest request,
                                 HttpServletResponse response) throws
Exception {
        ICRUDForm crudForm = (ICRUDForm) form;
        ActionMessages messages = crudForm.crudLoadForUpdate();

        if ((messages == null) || messages.isEmpty()) {
            return mapping.findForward(CRUDConstants.MAPPING_ON_EDIT_SUCCESS);
        } else {
            HttpSession session = request.getSession();
            session.setAttribute(Globals.ERROR_KEY, messages);
            return mapping.findForward(CRUDConstants.MAPPING_ON_LOAD_FAILURE);
        }
    }

> //The get users - this will display our users on the page
> public ActionForward getUsers(ActionMapping mapping, ActionForm form,
> HttpServletRequest request, HttpServletResponse response) throws Exception {
>    //lets say pass in companyID
>    Integer companyID = Integer.valueOf( request.getParameter("companyID"));
> 
>    //get our list of users to display on page
>    List users = ourBackendDelegate.getUsers( companyID );
>    request.setAttribute("users", users );
> 
>    return mapping.findForward("to_display");
> }

"Get users" is the action not directly related to CRUD, so it is not
implemented in CRUDAction, which is a standard action class and can be
used directly, without modification. "Get users" is implemented in a
separate action.

> 4) sample config...
> 
> 
> <action path="/userMaintenance" type="com.foobar.UserAction"
>              name="userActionForm"
>              scope="request"
>              validate="false"
>              parameter="dispatch">
>                  <forward name="to_form" path="/userForm.jsp"/>
>                  <forward name="to_display" path="/displayUsers.jsp"/>
>          </action>

Here is mine, it is not users, but abstract items with ID, stringvalue
and numvalue. You see a lot of mappings for different occasions, but
most of them simply reload the same action, and can be replaced with
standard "ON-RELOAD".

      <!-- CRUD action helper: item list -->
      <action path="/cruditemlist"
              type      =
"net.sf.dialogs.samples.crudaction.ItemListActionSample"
              name      = "dummyForm">
              <forward name = "showlist" path="/crudaction-list.jsp"/>
      </action>

      <!-- CRUD action -->
      <action path="/crudaction"
              type      = "net.sf.dialogs.actions.crud.CRUDAction"
              name      = "crudform"
              scope     = "session"
              validate  = "false"
              parameter = "DIALOG-EVENT">

          <!-- Where to hand control over after input phase (POST) -->

          <forward name="ON-CREATE-SUCCESS" path="/crudaction.do"
redirect="true"/>
          <forward name="ON-DUPLICATE-SUCCESS" path="/crudaction.do"
redirect="true"/>

          <forward name="ON-PREVIEW" path="/crudaction.do" redirect="true"/>
          <forward name="ON-EDIT" path="/crudaction.do" redirect="true"/>
          <forward name="ON-LOAD-FAILURE" path="/erroraction.do"
redirect="true"/>

          <forward name="ON-DELETE-SUCCESS" path="/cruditemlist.do"
redirect="true"/>
          <forward name="ON-DELETE-FAILURE" path="/erroraction.do"
redirect="true"/>

          <forward name="ON-CANCEL" path="/cruditemlist.do" redirect="true"/>
          <forward name="ON-CLOSE" path="/cruditemlist.do" redirect="true"/>

          <forward name="ON-STORE-SUCCESS" path="/cruditemlist.do"
redirect="true"/>

          <forward name="ON-STORE-FAILURE" path="/crudaction.do"
redirect="true"/>
          <forward name="ON-INVALID-DATA" path="/crudaction.do"
redirect="true"/>

          <!-- Which page to load on the output phase or on refresh (GET) -->

          <!-- Edit Mode -->
          <forward name="CRUD-UI-MODE-EDITABLE" path="/crudaction-edit.jsp"/>
          <!-- Preview Mode -->
          <forward name="CRUD-UI-MODE-READONLY" path="/crudaction-noedit.jsp"/>
          <!-- Illegal mode, item is not active -->
          <forward name="CRUD-UI-MODE-INACTIVE"
path="/crudaction-notactive.jsp"/>

      </action>
      <action path="/erroraction" forward="/crudaction-error.jsp"/>

> 5) Sample form
> 
> <html:form action="/userMaintenance">
>      Name: <html:text property="name"/><br/>
>      <html:hidden property="userID"/>
>      <html:hidden property="dispatch"/>
> 
>      <html:submit value="${userForm.dispatch}"/>
>      <!-- above button logic can be done many ways, just for
>      simplicity i'm using the dispatch name-->
> <html:form>

Here is mine, this one is for edit. There is another for view, which
does not have edit fields.

<html:form action="/crudaction.do">
  <table border="1" width="300" >
    <tr>
        <td>Item ID</td>
        <td><bean:write name="crudform" property="itemId"/></td>
    </tr>
    <tr>
        <td>String value</td>
        <td><html:text name="crudform" property="stringValue"/></td>
    </tr>
    <tr>
        <td>Num value</td>
        <td><html:text name="crudform" property="intValue"/></td>
    </tr>
    <tr>
      <td colspan="2">
        <INPUT type="submit" size="10" name="DIALOG-EVENT-CANCEL"
value="Cancel">&nbsp;
        <INPUT type="submit" size="10" name="DIALOG-EVENT-DELETE"
value="Delete">&nbsp;
        <INPUT type="submit" size="10" name="DIALOG-EVENT-SAVE"
value="Save Changes">
      </td>
    </tr>
   </table>
</html:form>

> 6) Sample page displaying users lets them edit and delete them
> 
> <c:forEach items='${users}' var='users'>
>       <c:url var="editUrl" scope="page" value="/userMaintenance.do">
>          <c:param name="dispatch" value="setUpForEdit"/>
>          <c:param name="userID" value="${user.userID}"/>
>      </c:url>
>      <c:url var="removeUrl" scope="page" value="/userMaintenance.do">
>          <c:param name="dispatch" value="delete"/>
>          <c:param name="userID" value="${user.userID}"/>
>      </c:url>
>       ${user.name} <a href="${editUrl}">[ EDIT ]</a> <a
> href="${removeUrl}">[ DELETE ]</a>
> </c:forEach>

You mean the list? It does not directly relate to CRUD, does not it?
But here it is, for completeness:

<table border="1" width="100%">
  <tr>
    <th>ID</th>
    <th>Str Value</th>
    <th>Int Value</th>
    <th colspan="4">Operation</th>
  </tr>

  <logic:iterate id="item" name="crud-item-list"
type="net.sf.dialogs.samples.crudaction.business.BusinessObj">
  <html:form action="/crudaction.do">
    <tr>
      <td><bean:write name="item" property="itemId"/></td>
      <td><bean:write name="item" property="stringValue"/></td>
      <td><bean:write name="item" property="intValue"/></td>

      <td>
        <html:submit property="DIALOG-EVENT-INIT-DUPLICATE" 
                     value="Duplicate Item"/>
      </td>

      <td>
        <html:submit property="DIALOG-EVENT-INIT-VIEW" 
                     value="View Item"/>
      </td>

      <td>
        <html:submit property="DIALOG-EVENT-INIT-UPDATE" 
                     value="Update Item"/>
      </td>

      <td>
        <html:submit property="DIALOG-EVENT-DELETE" 
                     value="Delete Item"/>
      </td>
    </tr>
    <html:hidden name="item" property="itemId"/>
  </html:form>
  </logic:iterate>
</table>
<br/>
<html:form action="/crudaction.do">
        <html:submit property="DIALOG-EVENT-INIT-CREATE" 
                     value="Create New Item"/>
</html:form>

> Basically 90% of applications are doing something similar to the above.
> Presenting data to users and often letting them click on things to edit.
> Personally, this concept works well for me. There are several tweeks I
> have to do for more complex things, but this is the 'meat' of most
> applications.

This works for me too :-) Here is the live demo of above CRUD application:
http://www.superinterface.com/strutsdialog/cruditemlist.do

The app that you can play with, is not as sleek as it can be. If you
click Back, it will tell you that item you worked with, is not there
anymore. This is because I have to separate actions, one for list,
another for CRUD. I have a combo, where list and CRUD are served with
one action. This one takes only one slot in browser page history, so
no Back issues ever. I will deploy it when I get home from work.

Here is the class it was created with:
http://struts.sourceforge.net/strutsdialogs/crudaction.html

Michael.

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to