Tags:
create new tag
, view all tags
-- PedroRio - 25 Jan 2011

XEO Library Web Components - Creating a complex viewer - Movement

To finalize the creation of the XEO Library viewers you'll create the most challenging viewer up until now. The Movement edit viewer is where librarians will create the movements of books (and returns) for a given user. When creating a new Movement, a librarian must be able to choose which user is going to take the books and select the list of books to take.

This viewer must also allow the librarian to show which books haven't been returned and also to return any of the current books which have not been yet returned. To achieve this you'll create a viewer with three inner tabs (one for adding books to a new movement, one for displaying books which haven't been yet returned and another one to return a set of books).

The set of operations within a movement (add books or returns books) should only be accessible whenever certain conditions are met. For instance: When a movement is first created (but not yet saved) you can add the books that the user will take home, but you cannot return books which the user has not yet taken. Similarly, when the movement is created you cannot add more books to the movement, but the user can return some (or all) of the books.

Whenever a user wants to return a book, the interface should only display books from the respective movement, and books which haven't previously been returned. A movement should be totally locked when all books are returned.

The first step will be to edit the LIB_Movement edit viewer to the following (also, create a LibMovementEditBean that extends the XEOEditBean in the org.example.viewer.beans package):

<?xml version="1.0" encoding="UTF-8"?>
<xvw:root xmlns:xvw="http://www.netgest.net/xeo/xvw" xmlns:xeo="http://www.netgest.net/xeo/xeo">
    <xvw:viewer beanClass="org.example.viewer.beans.LibMovementEditBean" beanId="viewBean">
        <xeo:formEdit renderToolBar="false">
        <xeo:editToolBar renderDestroyBtn="false">
        </xeo:editToolBar>
            <xvw:panel>
                <xvw:section label='Movement Details'>
                    <xvw:rows>
                        <xvw:row><xvw:cell><xvw:attribute objectAttribute="id"/></xvw:cell></xvw:row>
                        <xvw:row><xvw:cell><xvw:attribute objectAttribute="user"/></xvw:cell></xvw:row>
                        <xvw:row><xvw:cell><xvw:attribute objectAttribute="dueDate"/></xvw:cell></xvw:row>
                        <xvw:row><xvw:cell><xvw:attribute objectAttribute="fine"/></xvw:cell></xvw:row>
                        <xvw:row><xvw:cell><xvw:attribute objectAttribute="state"/></xvw:cell></xvw:row>
                    </xvw:rows>
                </xvw:section>                
                <xvw:tabs>
                    <xvw:tab label="Books in the movement"> <!-- Tab to add books to the movement and see the complete list of books of the movement -->
                    </xvw:tab>
                    <xvw:tab label='Books left to return'> <!-- Tab to list all the books which haven't been returned -->
                    </xvw:tab>
                    <xvw:tab label='List of Returns'> <!-- Tab for the list of returns -->
                    </xvw:tab>
                </xvw:tabs>
            </xvw:panel>
        </xeo:formEdit>
    </xvw:viewer>
</xvw:root>

In order to maintain a history of movements, you'll want to disable the remove button in the default toolBar. To do that, you'll need to tell its parent component ( xeo:formEdit) to not render the default toolbar and then add the xeo:editToolBar component as its child and tell it to not display the "Delete" button using renderDestroyBtn property set to false.

The xvw:rows, xvw:row, xvw:cell and xvw:attribute components are used to display the attributes such as the due date and the user (as well as the movement's identifier). First, let's add a bridge component to the first tab so that the librarian can add books to the movement (when it's first created). Add the following code to the first tab:

<xeo:bridge bridgeName='books'>
   <xvw:columns>
      <xvw:columnAttribute width="150" label="Books" dataField="SYS_CARDID"/>
    </xvw:columns>
</xeo:bridge>

If you preview it, it will be like depicted in figure LibMov.1.

Figure LibMov.1 - Lib_Movement edit viewer with first tab

Notice from figure LibMov.1 that you can directly create a new book (with the "New" button") from the list. This is something that will not be allowed and, to prevent that, we'll remove that button from the toolbar. Also, it's possible to open the edit viewer for a given book if you double click on a book in the list. That is also something that we don't want librarians to be doing. To prevent both things, you'll need to update the bridge component to the following:

<xeo:bridge bridgeName='books' renderToolBar="false" onRowDoubleClick="" >
  <xeo:bridgeToolBar renderCreateNewBtn="false" >
  </xeo:bridgeToolBar>
   <xvw:columns>
      <xvw:columnAttribute width="150" label="Books" dataField="SYS_CARDID"/>
    </xvw:columns>
</xeo:bridge>

Just like to disable the delete button in the xeo:editToolBar component, to change the xeo:bridgeToolBar component that comes by default with the xeo:bridge component you have to tell the xeo:bridge component to not render its toolbar and then add the component and set the properties there. Also, you can use the onRowDoubleClick property of the bridge component (actually it's inherited from the GridPanel component) to tell the component which bean method should be invoked when a row in the panel is double clicked. By using "" as the value for the property you make it that when a user double clicks the row, nothing will happened.

When a librarian creates a new movement he'll be able to add books to that movement, but once the librarian saves the movement and then opens it again, there's nothing stopping him from adding more books to the movement. In order to prevent that you'll use the disabled property to disable the toolbar under certain conditions. Change the bridgeToolbar component declaration to the following:

<xeo:bridgeToolBar renderCreateNewBtn="false" disabled="#{viewBean.movementInitated}" />

Open the LibMovementEditBean and create the getMovementInitiated method with the following code:

public boolean getMovementInitated(){
        
        try {
            if (!getXEOObject().exists())
                return false;
        } catch (boRuntimeException e) {
            log.severe(e);
        } 
        return true;
    }

The getMovementInitiated method determines if the movement was already saved and returns accordingly. If the instance is not saved (does not exist) then the toolbar should be enabled (thus, the method returns false). When the instance is already saved, the movement has been created and can't be changed (thus, the method returns true in that situation).

Next add to the second tab (labeled "Books left to return") a xeo:list component in order to display the list of books which haven't been returned. In the second tab put the following code:

<xeo:list targetList="#{viewBean.books}" renderToolBar="false" onRowDoubleClick="">
     <xvw:columns>
        <xvw:columnAttribute width="150" label="Books" dataField="title"/>
     </xvw:columns>
</xeo:list>   

For this tab, you'll only want to display the list of books to return and not permit any operations on the list, thus the renderToolBar property is false the onRowDoubleClick property is empty. Also, the targetList property will retrieve the list of books from the collection attribute, but only those who haven't been returned. Create the getBooks method in the bean, as follows:

public DataListConnector getBooks(){

            long boui = getXEOObject().getBoui();
            String xeoql = "select LIB_Movement.books where boui = " + boui + " and books.state = '0'";
            boObjectList result = ObjectListManager.list(getEboContext(), xeoql);
            return new XEOObjectListConnector(result);
}

This method will select all books from the LIB_Movement instance (that are in the books collection attribute) which have a state "0" (recall from the LIB_Lov file that state = '0' represents a books which is unavailable).

In the final tab you'll add a xeo:bridge component for the list of returns associated with the current movement. Include the following in the third tab:

<xeo:bridge bridgeName="returns" >
     <xvw:columns>
       <xvw:columnAttribute width="25" dataField="Id"/>
      <xvw:columnAttribute width="150" dataField="note"/>
   </xvw:columns>
</xeo:bridge>

In this xeo:bridge component you'll want to leave everything by default as the librarian should be able to create new LIB_Return instances with the books returned by the user. What you'll want, however, is customize the edit viewer for the LIB_Return Object Model, so that when you add a book instance to the books collection attribute the librarian only sees books not yet returned in movement.

Open the LIB_Return edit viewer and replace the xeo:bridge declaration with the following:

<xeo:bridge bridgeName='books' renderToolBar="false">
     <xvw:toolBar>
       <xvw:menu text='Add Books' serverAction="#{viewBean.openLookup}"></xvw:menu>
   </xvw:toolBar>
   <xvw:columns>
     <xvw:columnAttribute width="150" label="Book" dataField="SYS_CARDID"/>
  </xvw:columns>
</xeo:bridge>

In this xeo:bridge component the librarian should only see a button with "Add Books" as label, and shouldn't see the normal buttons in a xeo:bridge. The "Add books" button will open a special viewer designed to show only books of the movement that haven't been delivered yet. The edit viewer for a return (with the current settings) is depicted in figure LibMov.2

LibReturnEditViewer.png

Figure LibMov.2 - Edit viewer for the LIB_Return Object Model (with a special toolbar)

Create a LibReturnEditBean in the org.example.viewer.beans package and make it extend the XEOEditBean class (don't forget to make it the bean of the viewer by changing the beanClass property of the xvw:viewer component). Before continuing with this viewer, let's take a step back and create the viewer we want the "Add Books" button to open. The "Add Books" button should open a lookup viewer with the list of books not yet delivered from the movement.

Create a new lookup viewer named "ReturnBooks" using XEO Studio's Web Viewer wizard. Create a new java class in the org.example.viewer.beans package with the name LibBookReturnLookup and make it extend the XEOBaseLookupList bean. Replace the content of the newly created ReturnBooks viewer with the following:

<?xml version="1.0" encoding="UTF-8"?>
<xvw:root xmlns:xvw="http://www.netgest.net/xeo/xvw" xmlns:xeo="http://www.netgest.net/xeo/xeo">
    <xvw:viewer beanClass='org.example.viewer.beans.LibBookReturnLookup' beanId='viewBean'>
        <xeo:formLookupList>
            <xeo:lookupList targetList="#{viewBean.books}" >
                <xvw:columns>
                    <xvw:columnAttribute width="100" dataField="title"/>
                    <xvw:columnAttribute width="100" dataField="isbn"/>
                </xvw:columns>
            </xeo:lookupList>
        </xeo:formLookupList>
    </xvw:viewer>
</xvw:root>

Essentially you'll want to have a xeo:lookupList component whose datasource comes from the bean. In the LibBookReturnLookup class add the following:

A property (which will store the boui of the parent LIB_Movement instance:

private Long parentBoui;

A getter and a setter:

public void setParentBoui(Long val){
        parentBoui = val;
}
    
public Long getParentBoui(){
        return parentBoui;
}

An finally, the getBooks method (see the declaration of the xeo:baseLookupList component above):

public DataListConnector getBooks(){

        String boqlExpression = "select LIB_Book where state='0' and boui in (select LIB_Movement.books where boui = " +
        getParentBoui() + ")";
        
        boObjectList list = ObjectListManager.list(getEboContext(), boqlExpression);
        return new XEOObjectListConnector(list);

}

The XEOQL expression to retrieve the books is basically "select all book instances whose state is "Unavailable" and are part of the books collection of this particular movement". The main thing here is how to open the viewer and pass it the BOUI of the parent movement. For that let's return to the LIBReturnEditBean where you need to create the openLookup method, start by creating the method as follows:

public void openLookup(){ }

The first thing to do is create a XUIViewRoot (which is a representation of the viewer and all its component), and then get a reference to the bean it has (which will be a LibBookReturnLookp instance). In order to do that, add the following lines to the method:

XUIViewRoot viewRoot = getSessionContext().createChildView("viewers/LIB_Return/ReturnBooks.xvw");
LibBookReturnLookup bean = (LibBookReturnLookup) viewRoot.getBean("viewBean");

When a XUIViewRoot is created with the createView or createChildView method of a XUISessionContext instance, it receives the name of the viewer to create and while doing that it also instantiates a new instance of the java class (bean) associated to the viewer. Next add the two following lines:

WebXEO.XEOEditBean parentBean = getParentBean();
Long boui = parentBean.getXEOObject().getBoui();

In order to pass the BOUI of the LIB_Movement instance, you'll need to retrieve it from the LibMovemenEditBean. Fortunately when the LibReturnEditBean is created for the edit viewer of LIB_Return, the LibMovementEditBean is passed automatically as the LibReturnEditBean's parent and can be retrieved with the getParentBean method. Having access to the parent bean, you can now use the getXEOObject method which will return the boObject instance of the LIB_Movement, and with that the BOUI of the LIB_Movement instance. Next you'll have to set some attributes in the target bean, like the following:

bean.setParentBoui(boui);
bean.setMultiLookup(true);

First you need to use the setParentBoui method created in the bean to pass the BOUI to it and then you'll need to set the multiLookup property to true so that the librarian can choose more than one book. Next add the following:

bean.setParentBeanId("viewBean");
bean.setParentParentBeanId("viewBean");

The previous lines allow the LibBookReturnLookup bean to know the identifiers of its parent beans (respectively the first parent, LibReturnEditBean and the parent's parent LibMovementEditBean which it will be required for its normal operation. Next add the following:

WebXEO.GridPanel panel = (WebXEO.GridPanel) getViewRoot().findComponent(WebXEO.GridPanel.class);
bean.setParentComponentId(panel.getClientId());

In order for the lookup viewer to update the values in list of books for the LIB_Return edit, it will need to know which component it should update with the chosen books. The said component is a bridge component, but since a bridge is it self a gridPanel you can search the component tree for the only GridPanel component in that tree, using the findComponent method with the class of the component to find. Then set the parentComponentId with the identifier of that panel.

Finally, add the following lines:

bean.executeBoql("select LIB_Book where BOUI in (select LIB_Movement.books where boui = " + boui + ")");
        
getRequestContext().setViewRoot(viewRoot);
getRequestContext().renderResponse();

You'll need to make the bean execute the XEOQL expression one first time so that it can fetch results (after this, the getBooks method will be used). In the end you'll need to set the created XUIViewRoot as the current viewRoot and render the response. The full source code for this method is:

public void openLookup(){
        
        XUIViewRoot viewRoot = getSessionContext().createChildView("viewers/LIB_Return/ReturnBooks.xvw");
        LibBookReturnLookup bean = (LibBookReturnLookup) viewRoot.getBean("viewBean");
        
        WebXEO.XEOEditBean parentBean = getParentBean();
        Long boui = parentBean.getXEOObject().getBoui();
        
        bean.setParentBoui(boui);
        bean.setMultiLookup(true);
        
        bean.setParentBeanId("viewBean");
        bean.setParentParentBeanId("viewBean");
        
        WebXEO.GridPanel panel = (WebXEO.GridPanel) getViewRoot().findComponent(WebXEO.GridPanel.class);
        bean.setParentComponentId(panel.getClientId());
        bean.executeBoql("select LIB_Book where BOUI in (select LIB_Movement.books where boui = " + boui + ")");
        
        getRequestContext().setViewRoot(viewRoot);
        getRequestContext().renderResponse();
        
        
    }

The final effect will be something like depicted in figure LibMov.3.

LibMovementReturnFlow.png

Figure LibMov.3 - Flow of a librarian when returning a book.

Two final things that need to be changed is the onBeforeSave event in the LIB_Movement Object Model, and the disabledWhen behavior for the returns bridge (this last one could also be implemented using the components in the viewers). When the Movement is saved you need to verify if all books that have been returned have the correct state (and afterwards, if all books were returned the state of the movement must be updated to "closed"). The following code must be added as an else statement to the existing if (The comments through the code explain most of what it's being done):

else{ //If the Movement does exist
            
            //If it's closed, there's nothing else to do
            if ("1".equalsIgnoreCase(obj.getAttribute("state").getValueString()))
                return true;
            
            //Check if all returns have their books with the correct state
            bridgeHandler returnsCollection = obj.getBridge("returns");
            returnsCollection.beforeFirst();
            //Iterate through all returns to check if there are books which
            //haven't been updated
            while (returnsCollection.next()){
                boObject currentReturn = returnsCollection.getObject();
                //Get the books for the current return
                bridgeHandler booksOfReturnCollection = currentReturn.getBridge("books");
                booksOfReturnCollection.beforeFirst();
                //Iterate through them all
                while (booksOfReturnCollection.next()){
                    boObject book = booksOfReturnCollection.getObject();
                    //If there's a book that's unavailable (0) set it as available (1)
                    if ("0".equalsIgnoreCase(book.getAttribute("state").getValueString())){
                        book.getAttribute("state").setValueString("1");
                    }
                }
            }
            
            //Finally we must check from all the books in the movement, if there's anyone
            //which hasn't been returned
            bridgeHandler booksCollection = obj.getBridge("books");
            boolean closed = true;
            booksCollection.beforeFirst();
            while (booksCollection.next()){
                boObject currentBook = booksCollection.getObject();
                if ("0".equalsIgnoreCase(currentBook.getAttribute("state").getValueString())){
                    closed = false;
                    break;
                }
            }
            //If we don't find any book to return, the movement can be closed 
            if (closed){
                obj.getAttribute("state").setValueString("1");
            }
        }

Finally add a disableWhen condition to the returns attribute in LIB_Movement, choosing Java as the language and put the following code in the class.

public boolean returns_DisableWhen(boObject obj,bridgeHandler bridge) throws boRuntimeException {
        if (!obj.exists())
            return true;
        else
        {
            if ("1".equalsIgnoreCase(obj.getAttribute("state").getValueString()))
                return true;
        }
        return false;
    }

Essentially, when a LIB_Movement instance is not yet saved, the returns attribute should be disabled as there are no books to return yet. It should also be disabled when the movement is fully closed. It's only enabled when there are still books to return.

This concludes our tutorial about Viewers and the XEO Library, the last part of the tutorial will be about security in the XEO Library ( read more).

Topic attachments
I Attachment Action Size Date Who Comment
PNGpng LibMovementFirstTab.png manage 24.4 K 2011-01-25 - 12:24 PedroRio  
PNGpng LibMovementReturnFlow.png manage 78.8 K 2011-01-26 - 09:39 PedroRio  
PNGpng LibReturnEditViewer.png manage 36.2 K 2011-01-25 - 15:46 PedroRio  
Topic revision: r10 - 2011-04-04 - NicolauGrosskopf
 

No permission to view TWiki.WebTopBar

This site is powered by the TWiki collaboration platform Powered by Perl

No permission to view TWiki.WebBottomBar