Posts tagged "View"

View Master

LiveCycle Mosaic ES2.5 has some very useful features for allowing users to customize their environment.  Some of the most useful revolve around the view object and its related view context.  With a bit of creative programming we can take advantage of the view object and allow users to create their own, customized design.

In this post I’ll look at a fairly common request for a customizable Mosaic app and see how it can be done while using many of the view features.

The source code used in this post can be found here.

Scenario

I’ll start with a short description of what we want from the app:

  1. An IT Request application with four tiles to begin (more may be added later):
    • Request List – has a list of currently active requests
    • Request Details – contains details about a single request
    • Customer Details – contains details about the customer to which the request applies
    • Customer History – contains information about a customer’s previous requests
  2. When a user selects an item from the request list the other tiles will update with relevant request/customer information
  3. User’s can customize their app to include any or all of the tiles.
  4. User’s can open multiple copies of the work space to work on different requests. 
  5. User’s can save their customized work space
  6. If a user has a customized work space, then it should come up when they login.
  7. If a user does not have a customized work space then a default will be presented.  The default will include the Request List and the Request Details.
  8. When a user’s customized work space is opened then the last request they were working on should be selected

Okay, that should do for now.

The Tiles

First we need to build the four tiles that make up the bulk of the application.  I won’t go too much into the general tile development, but instead I’ll concentrate on the “special” things I need to do to make the other requirements work.

Basically these are small Flex apps that happen to use the Mosaic API.  In my case I built them as ModuleTile files since they are all built on the same version of the Flex SDK.   Since this is a sample, I’m just going to read the data from a couple of local XML files using an HTTP Service.  No need to get too fancy here.

  • The Request List (RequestList.mxml) will be a simple data grid that shows the list of requests from the XML file. 
  • The Request Details (RequestDetails.mxml) is a screen that shows details about a single request.  Its pretty much a form.
  • The Customer Details (CustomerDetails.mxml) is a simple display screen that shows information about a single customer.
  • The Customer History (CustomerHistory.mxml) is a list of a single customer’s previous requests.

The construction of these tiles is pretty strait forward MXML development.

Inter-tile Communication

Requirement: When a user selects an item from the request list the other tiles will update with relevant request/customer information.

Mosaic has several methods to exchange data between tiles (see video for more), but there are a few requirements that lend themselves to using the view context for inter-tile communication:

  • User’s can open multiple copies of the work space to work on different requests.  –  This means that tiles in the same work space need to share data, but they shouldn’t interfere with tiles in another work space.  We can equate a work space with a Mosaic view. Then we can read that as the data needs to be exchanged between tiles in the same view and not tiles in another view.  Coincidentally, this is exactly how view attribute data works.
  • When a user’s customized work space is opened then the last request they were working on should be selected. – This means that the data the user is looking at needs to be saved and reloaded when the view is opened.  Fortunately the view context data is saved when the view is saved.  The tile just needs to be built to make use of that data. The one restriction is that the view data must be a primitive type – complex data types cannot be saved.

Let’s look at how the use of the view context data attributes are coded to meet these requirements.

Sending Data

The Request List tile will be broadcasting data to the other tiles.  When a user clicks on an item from the data grid (id=”grid”) two data attributes will be set – the caseNumber and the customerName.  These will be picked up by the other tiles. 

To send view data to the other tiles in the current view you would use the parentView.context.setAttribute method.  The first parameter is the attribute name, the second is its value.  Here is the code from the Request List tile:

private function selectRecord():void{
    var caseNumber:String = grid.selectedItem.caseNumber;
    var customerName:String = grid.selectedItem.customer;
    parentView.context.setAttribute(“caseNumber”,caseNumber);
    parentView.context.setAttribute(“customerName”,customerName);               
}

Receiving Data

The other three tiles need to receive the data and then do something with it.  In this case they will get related XML data and put it on the screen.

To meet the requirements the tile must check for the data when it loads (thus meeting the requirement to open the last request).  It must also watch for changes to the data so the tile is updated when the user selects a different request.

To get the value of a view data attribute I will use the parentView.context.getAttribute method.  The only parameter is the attribute name and the result is the contents of the data.

Mosaic also has a listener that can watch for changes in a view context attribute. The  parentView.context.addAttributeWatcher function works using the standard Flex event model in that it will fire a function when the attribute data changes.  You can then use the getAttribute function to get the data.

The following code is from the Request Details tile, but the other two tiles use similar techniques for getting the view context data.  The init function is called on the creationComplete event of the tile:

import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.events.PropertyChangeEvent;
           
[Bindable] private var currentRequest:XML;           
[Bindable] private var caseNum:String;
           

private function init():void{   
    parentView.context.addAttributeWatcher(“caseNumber”,onChange);  //watch for changes to the data
    onChange();                                                     //check for data on load
}
           
private function onChange(event:PropertyChangeEvent=null):void{
    caseNum = parentView.context.getAttribute(“caseNumber”);   //get the data
    if (caseNum != null)                                       //if there is data, then do something with it
         HttpService.send();
}      

Adding Tiles to a View

Requirements: User’s can customize their app to include any or all of the tiles
                             User’s can open multiple copies of the work space to work on different requests.

This requirement implies that the application needs a way to allow the user to create a new view and to add available tile to that view as they want.  Creating a new view is easy, as Mosaic has a built in button on the view skin to add a new view (the “+” button in the default skin). 

Adding tiles to the view takes a bit more effort.  Mosaic 9.5 doesn’t have an out of the box way to do this, but it does provide the APIs so it can be coded.  I need somewhere to put this code and the user controls.  It doesn’t make much sense to put it in any of the four tiles, as the user may not have add that tile to their view.  For this example I’ll add another tile called Header (Header.mxml).

I’ll need something to hold a list of the available tiles.  To be more flexible, I will also add the catalog name to that list as well.  That will allow me to add tiles from other catalogs into the view later on.    I created a simple class TileInfo to hold the tile and catalog name for each tile.  Then I created an array collection of the TileInfo objects and I populate that array collection when the Header tile is initialized (creation complete):

private function init():void{               
    //set the list of tiles (and their catalogs) that a user can add
    tileCollection.addItem(new TileInfo(“RequestList”,”ViewDemo_Catalog”));
    tileMenuCollection.addItem(“Request List”);
               
    tileCollection.addItem(new TileInfo(“RequestDetails”,”ViewDemo_Catalog”));
    tileMenuCollection.addItem(“Request Details” );
               
    tileCollection.addItem(new TileInfo(“CustomerDetails”,”ViewDemo_Catalog”));
    tileMenuCollection.addItem(“Customer Details”);
               
    tileCollection.addItem(new TileInfo( “CustomerHistory”,”ViewDemo_Catalog”));
    tileMenuCollection.addItem(“Customer History”)

}   

One of Header’s jobs will be to have a control that a user can use to add tiles.  I used a simple drop down (bound to the array collection) with the tile names and a button for the user to add the selected tile.

headerControls

When the user clicks on the Add Tile button a function executes that will add the tile.  When the function fires the code must first do the following:

  1. Locate the proper catalog
  2. Locate the proper tile in the catalog
  3. Find the view and panel that the user is looking at
  4. Add the tile to the view

As part of step 3 we should make sure that there is both a view and panel into which we can put the tile.  If either of these are missing, we can pull a pre-configured one out of the catalog.  By creating a pre-configured view and panel template, the layout can be defined ahead of time.  This will save a bit of coding.  In this case I created a view template called addView and a panel template called addPanel in the catalog.

The button handler in the Header tile looks like:

protected function addTile_clickHandler(event:MouseEvent):void            {
                if (tileDropDown.selectedIndex != -1){
                    var tileInfo:TileInfo = tileCollection.getItemAt(tileDropDown.selectedIndex) as TileInfo ;
                   
                    //get the tile
                    var cat:ICatalog = mosaicApp.getCatalog(tileInfo.catalogNm);
                    var tileToAdd:ITile = cat.getTile(tileInfo.tileNm);
                   
                    var view:IView = this.currentView();
                    var panel:IPanel = this.currentPanel(view);                       
                   
                    panel.addTile(tileToAdd);
                }
            }

This calls two functions to find (or add) the current view (currentView) and panel (currentPanel).  To determine if a view or panel is the one that the user is using, I check each view/panel for the displayed flag (true means that it is the current one).

The following is the function for determining the current view.  The panel function is similar (you can see it in the source code).

                   /**
             * find the current view.  If not found, then load one from the catalog
             **/
            protected function currentView():IView{               
                var currentView:IView;
               
                //find the current view by looking at the displayed flag in each view
                var viewArray:Array = mosaicApp.views;
                for each (var searchView:IView in viewArray){
                    if (searchView.displayed){
                        //found view
                        currentView = searchView;
                        break;
                    }
                }   
               
                //not found, so go into the catalog and get a default view
                if (currentView == null){
                    var catalog:ICatalog = mosaicApp.getCatalog(“ViewDemo_Catalog”);
                    currentView = catalog.getView(“addView”);
                    mosaicApp.addView(currentView);
                    currentView.display();
                }
               
                return currentView;
            }
           
           

Saving and Loading Views

Requirements:   User’s can save their customized work space
                                  If a user has a customized work space, then it should come up when they login. 
                                  If a user does not have a customized work space then a default will be presented.  The default will include the Request List and the Request Details.

The view skin has a control that allows users to save a view at any time:  clip_image002. Views can also be saved using the IView.save API.  Views are saved on the server and are tagged to the user’s account.  In this case I’ll let the user save the view using the built in controls.

Views can be loaded into an application either by using the organizer (added to the application’s xml file using the organizer element), or by using the API.  The organizer is an element added to the application’s xml definition.  It shows a list of the user’s saved Views and allows a user to add the view to the application at any time.

The requirement, however, state that the app should load the user’s view and if that does not exist, load the default view.   This will require loading the correct view using the API.  To add a view using the API you first have to find the view in the mosaicApp.userViews array, then add it to the application using the mosaicApp.addView method.   I could put the default view, with its panels and tiles, directly into the application’s XML file.  The problem with that is the default view will always load because Mosaic loads the application contents when the user accesses the app.    There will be no way to intercept it and show the user’s saved view.  In this case its better to open the view (default or saved) using the API. 

This means that the application’s XML file will not have any of the four tiles.  It will have the Header tile, which will contain the API calls to load the proper view. 

The application xml file looks like:

<?xml version=”1.0″ encoding=”UTF-8″?>
<app:Application name=”ITRequests” label=”IT Requests”
xmlns:view=”http://ns.adobe.com/Mosaic/View/1.0/”
xmlns:catalog=”http://ns.adobe.com/Mosaic/Catalog/1.0/”
xmlns:tile=”http://ns.adobe.com/Mosaic/Tile/1.0/”
xmlns:crx=”http://ns.adobe.com/Mosaic/CRXTypes/1.0/”
xmlns:app=”http://ns.adobe.com/Mosaic/Application/1.0/”
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=”http://ns.adobe.com/Mosaic/Application/1.0/ file:///C:/Adobe/Mosaic/Mosaic%209.5.0.1/docs/schemas/application.xsd”>

    <crx:Metadata>
        <crx:Description>ITRequests</crx:Description>
    </crx:Metadata>
    <app:Shell name=”ITRequests” label=”IT Requests”>
        <catalog:CatalogReference name=”cat” uri=”ViewDemo_Catalog”/>
        <view:Organizer visible=”false”/>
        <tile:TileReference catalog=”cat” name=”Header” label=”Header” width=”100%” height=”80″/>
        <view:ViewManager width=”100%” height=”100%”>
        </view:ViewManager>
    </app:Shell>
</app:Application>

Notice that I did add a ViewManager, this is necessary because Mosaic needs something to control the views that I will add later.

Now I need to add some code to the Header tile so it can load the proper view.  I’ll need to check to see if there are any saved views for the user.  If there are, then I add them to the application.  If not; then I’ll call another function to load the default view.  

           /**
             * check to see if the user has any saved views.  If so
             * bring them up.  If not, bring up the default
             */
            protected function getUserViews():void{               
                var userViews:Array = mosaicApp.userViews;
                for each (var view:IView in userViews ){
                    mosaicApp.addView(view);   
                    showDefaultBtn.visible = true;
                }
                if (userViews.length == 0){
                    showDefaultView();
                }
            }
            /**
             *  show the default view
             */
            protected function showDefaultView():void{
                var catalog:ICatalog = mosaicApp.getCatalog(“ViewDemo_Catalog”);
                var defaultView:IView = catalog.getView(“defaultView”);   //load a view template called defaultView from the catalog
                mosaicApp.addView(defaultView);
                showDefaultBtn.visible = false;
            }

To make sure this happens when the app first gets loaded, I’ll add a call to the getUserViews(); function to the Header’s init function.

I could have easily done this in one function, but I wanted user’s that have a saved view to “reload” the default one.  To do that I added a button (showDefaultBtn) that will fire the showDefaultView function.  If the default view is there, I want the button to be invisible.

Conclusion

By taking advantage of the Mosaic view features – inter-tile communication, view layout saving, view data saving and view related APIs – you can build a highly customizable application.  This will allow your users to have the freedom to set the system up the way they want.

The source code used in this post can be found here.