Admin UI Framework Guide

From Zimbra :: Wiki

Jump to: navigation, search
Admin Article

Article Information

This article applies to the following ZCS versions.
  ZCS 6.0 Article  ZCS 6.0

Contents

Related articles

Extending Admin UI

Introduction

TBD

The Web Interface – Overview

In general, the functionality of the administration console can be described as viewing, editing and creating data objects. Example of data objects are Account records, Domain records, Server records. The Web UI exchanges data objects with a server via HTTP connection. These data objects are described in a format that can be parsed by JavaScript, such as XML Notation or JavaScript Object Notation (JSON).

The administration console follows Model-view-controller (MVC) design pattern. Data (M – model) is handled by Java beans inside a servlet container and stored in a database (LDAP and MySQL). Operations on the data (C – controller), such as read data from a database, write data to a database, send data to a client application, and receive data from a client application are handled by servlets that are running inside the servlet container. The presentation layer (V – view) of the admin console is an AJAX-based Web interface built on the same Zimbra AJAX Toolkit as the Zimbra mail UI.

The inner architecture of the Web interface also follows the MVC pattern, although not strictly. JavaScript classes in the Web interface can be grouped into three types: controllers, views and models. Models encapsulate data and provide methods for manipulating data. Views are responsible for defining and rendering the UI and Controllers glue views and models together.

The source tree of the admin web interface is organized in the following way. Top folders separate functionality areas, such as Account Management (“accounts” folder), Classes of Service Management (“cos” folder), Domain Management (“domains” folder), etc. Each top-level folder contains three sub folders: controller, model and view.

UI Framework Guide

Admin UI framework is a HTML/JavaScript framework that consists of JavaScript Classes, constants and functions. For a programmer familiar with traditional OO programming techniques commonly used in Java and C++ languages, object oriented JavaScript may seem somewhat odd at first. There are three main types of classes in the Admin Console UI Framework: Models, Views and Controllers. The Xforms framework, and these classes are described in the following sections.

Zimbra XForms Framework

Xforms UI framework facilitates rendering of UI elements, laying elements on the screen, feeding data from JavaScript objects (instances of model classes) to the UI elements and transferring user input to instances of models. Although, the name suggests that Xforms follows W3C Xforms specification, strictly speaking it does not do so. Therefore, even if you are familiar with other Xforms specification implementations – do not jump to any conclusions about Zimbra’s Xforms framework, it is a beast of its own kind (sorry Owen, it is). Designing a complicated user interface with Zimbra Xforms framework can be challenging, but the Xforms framework also makes things much easier once you know how to use it. Ultimately, a framework such as Xforms is essential when building a web-based user interface that has the main purpose of managing data objects on the server. Such framework allows you to draw forms quickly and consistently. UI engineers at Zimbra either hate it or love it.

Models

Models are classes that encapsulate data instances in the user interface. Some examples of the Model classes are:

accounts/model/ZaAccount.js

cos/model/ZaCos.js

domains/model/ZaDomain.js

(these are under jetty-x.x.x/webapps/zimbraAdmin/js/zimbraAdmin/ )

All Model classes inherit from ZaItem base class. Each model class has a constructor function with one argument – a reference to the main application controller. The constructor should call ZaItem constructor function with two arguments – the reference to the main application controller and the name of the class. Following is an example of a model constructor:

Code example

ZaItem.MY_TEST_CLASS =”MyTestClass”; // declare a string constant for referencing this class’ name

/**

*Create a constructor function with one argument

*@param app – reference to an instance of the main application controller.

**/

function MyTestClass(app) {

  //required statements

  if (arguments.length == 0) return; //precaution

/* call ZaItem constructor function with one additional argument and pass “this” as the context of the function call */

  ZaItem.call(this, app,”MyTestClass”); 

  this.type = ZaItem.MY_TEST_CLASS; //set this object’s type

  //optional statements

  this.attrs = []; //declare an container for this object’s attributes

  this._init(app); //call initialization methods

}

/* prototype-style declaration of inheritance */

MyTestClass.prototype = new ZaItem; 

MyTestClass.prototype.constructor = MyTestClass; 


Each model class consists of constants and methods. Constants usually correspond to LDAP attributes of data objects on the server. These constants look like following:

ZaAccount.A_office = “physicalDeliveryOfficeName”;

ZaAccount.A_street = “street”;

ZaAccount.A_zip = “postalCode”;

ZaAccount.A_state = “st”;

ZaAccount.A_mailDeliveryAddress = “zimbraMailDeliveryAddress”;

Methods of a model are divided into five areas:

  • Loading
  • Parsing
  • Creation
  • Modification
  • Removing

Each of these areas is implemented by a queue of methods. For example, an account may have three loading methods: method1 retrieves data from Zimbra Directory, method2 retrieves data from CRM database, and method3 retrieves data from posix account records. Each queue of methods is stored as a FIFO (First In – First Out) array of function references inside a static map. The following five static maps are available for storing method references:

      • ZaItem.loadMethods. Queues of methods that implement loading of a data instance from the server.
      • ZaItem.initMethods/Queues of methods that implement initialization.
      • ZaItem.createMethods. Queues of methods that implement creation of an instance on the server.
      • ZaItem.modifyMethods. Queues of methods that implement modification of a data instance on the server.
      • ZaItem.removeMethods. Queues of methods that implement removal of data instance from the server.

(sentence not complete)In each of these static maps a key is a name of the model class, e.g. “ZaAccount”, “ZaServer”, and a value is the array of function references, i.e. References to methods that implement loading of an Account instance from the server are stored in ZaItem.loadMethods[“ZaAccount”] array, and references to the methods that implement deletion of an Account instance from the server are stored in ZaItem.removeMethods[“ZaAccount”].

ZaItem.initMethods

Methods referenced in initMethods map are responsible for any post-constructor initialization. These methods are called from within ZaItem._init method which is called at the end of a constructor. ZaItem.initMethods is useful when extending an existing model class, because it allows adding statements that will be called at the end of the constructor call.

In the following example we add an initialization step to ZaServer object. The purpose of this additional step is to initialize two class members and assign default values. This example is the actual code from the com_zimbra_hsm admin extension which is available in the ZCS Network version. The purpose of the extension is to add tools for volume management to the Zimbra administration console.

Code Example

/**

* Declare the initialization method. Assume that ZaHSM constructor was 

* defined earlier.

* @param app – reference to an instance of the main application controller.

**/

ZaHSM.serverInit = function (app) {

  this.hsm = new Object();

  this.hsm.pollInterval = 500;

}

/**

* Add the new reference to the initialization method to the array of

* references in ZaItem.initMethods map under the key “ZaServer”.

**/

if(ZaItem.initMethods[”ZaServer”]) {

  ZaItem.initMethods[”ZaServer”].push(ZaHSM.serverInit);

}


ZaItem.loadMethods

As mentioned earlier, methods referenced in loadMethods map are responsible for loading data from the server. If you want to find out how these methods are called, look at ZaItem.load method in the ZaItem.js file. (jetty-x.x.x/webapps/zimbraAdmin/js/zimbraAdmin/common/ZaItem.js)

Code example

In the following example we create a function that loads some data from the server via a SOAP request. Note that this example assumes that there is a SOAP Handler for URN “mytestURN” that is aware of the SOAP command “MyTestRequest” and returns “MyTestResponse” document in JSON format; “MyTestRequest” SOAP command takes one argument “testArgument“.

   //declare the method

   MyTestClass.loadMethod = function(by, val) {

     if(!val)

       return;

     var soapDoc = AjxSoapDoc.create(”MyTestRequest”, “urn:myTestURN”, null);

     soapDoc.set(”testArgument”, “testValue”);

     var getAdditionalInfoCommand = new ZmCsfeCommand();

     var params = new Object();

     params.soapDoc = soapDoc;

     var resp =  getAdditionalInfoCommand.invoke(params).Body.MyTestResponse.myTestObj[0];

     this.initFromJS(resp);

   }

   /**

   *  add the method reference to the queue of method references in  

   * ZaItem.loadMethods map. “ZaAccount” key corresponds to methods that

   *  are called when loading a ZaAccount object

   **/

   if(ZaItem.loadMethods[”ZaAccount”]) {

     ZaItem.loadMethods[”ZaAccount”].push(MyTestClass.loadMethod);

   }

ZaItem.createMethods

Methods referenced in createMethods map are responsible for creating a data instance on the server. If you want to find out how these methods are called, look at ZaItem.create method in ZaItem.js file. In the ZCS administration console, every type of server object has a corresponding JavaScript class, e.g. Instances of Account objects stored in LDAP database on the server are represented as instanced of ZaAccount JavaScript class on the client side. Because multiple methods will be called in the UI when the UI creates an Account object on the server, ZaItem.create method creates an empty instance of ZaAccount JavaScript class before calling all the methods in ZaItem.createMethods[”ZaAccount”] queue. The same is true for other object types (ZaServer, ZaCos, ZaResource, ZaDomain, etc).

Code example

In the following example, we create a function that makes modifications to an Account object on the server after the object is created. This code example is the actual code from zimbra_samba extension that allows managing Samba accounts via Zimbra administration console.


/**

*Declare the function assuming that ZaSamAccount constructor was defined 

* earlier.

* @param tmpObj – a temporary javascript object that holds the data for the

* new instance of an Account object. TmpObj may or may not

* be an instance of ZaAccount JavaScript class.

* @param account – an instance of ZaAccount JavaScript class that was

* created in ZaItem.create method. At the time when

* ZaSamAccount.createMethod is called, account may already contain the data 

* from the Account object that was created on the server

* during the previous calls.

* @param app – reference to an instance of the main application controller.

**/

ZaSamAccount.createMethod = function(tmpObj, account, app) {

  if(tmpObj.attrs[ZaAccount.A_password] &&

     tmpObj.attrs[ZaAccount.A_password].length > 0) {

       var soapDoc = AjxSoapDoc.create(

         ”ModifyAccountRequest”, 

         “urn:zimbraAdmin”, 

         null);

       soapDoc.set(”id”, account.id);

       var attr = soapDoc.set(”a”, 

       ZaSambaUtil.hex_md4(tmpObj.attrs[ZaAccount.A_password]));

       attr.setAttribute(”n”, ZaSamAccount.A_sambaNTPassword);

       var modifyAccCommand = new ZmCsfeCommand();

       var params = new Object();

       params.soapDoc = soapDoc;

       resp = modifyAccCommand.invoke(params).Body.ModifyAccountResponse;

       account.initFromJS(resp.account[0]);

       account[ZaAccount.A2_confirmPassword] = null;

  }

}

/**

* add the method reference to the queue of method references in  

* ZaItem.createMethods map. “ZaAccount” key corresponds to methods that

*  are called when creatin an Account object on the server.

**/

if(ZaItem.createMethods[”ZaAccount”]) {

  ZaItem.createMethods[”ZaAccount”].push(ZaSamAccount.createMethod);

}

ZaItem.modifyMethods

Methods referenced in modifyMethods map are responsible for modifying a data instance on the server. If you want to find out how these methods are called, look at ZaItem.modify method in ZaItem.js file.

ZaItem.removeMethods

Methods referenced in removeMethods map are responsible for removing a data instance on the server. If you want to find out how these methods are called, look at ZaItem.remove method in ZaItem.js file.

myXModel

MyXModel is a static member of each model class. This member is a JavaScript object. It contains meta-data that describes the structure of the data encapsulated in the model class in the Xforms terms. MyXModel is used only by the Xforms engine to determine how to bind a data instance to form elements.

Communicating with a server via SOAP/JSON

As I mentioned earlier, model classes are responsible for communicating with a server. Although, there are cases when other types of objects send requests to a server and parse server responses. Communication with a server is done by sending SOAP requests and receiving responses in XML or JSON format. SOAP requests can be either synchronous or asynchronous. Zimbra AJAX Toolkit provides convenient APIs for sending and receiving SOAP/JSON requests via HTTP. The following example shows how to send a SOAP request to the server.

Code example

var soapDoc = 

  AjxSoapDoc.create("InstallCertRequest", "urn:zimbraAdmin", null);

soapDoc.getMethod().setAttribute("type", type);

soapDoc.set(ZaCert.A_validation_days, validation_days);

soapDoc.set(ZaCert.A_allserver, allserver);

var csfeParams = new Object();

csfeParams.soapDoc = soapDoc;

var reqMgrParams = {} ;

reqMgrParams.controller = app.getCurrentController();

reqMgrParams.busyMsg = com_zimbra_cert_manager.BUSY_INSTALL_CERT ;

ZaRequestMgr.invoke(csfeParams, reqMgrParams ) ;

Views

Views are classes that define layout of objects on the screen and tie data with UI elements. In the admin console, there are two basic types of views: list views and Xform views. List views extend DwtListView class from Zimbra AJAX Toolkit and display a list of objects in a table with an optional tool bar. Xform views are based on Zimbra Xform framework and usually are used to display properties of a selected data object.

Navigation panel (aka Overview Panel)

Overview panel contains the navigation tree. Nodes of the tree are created by the Overview Panel Controller.

List views

The main purpose of a list view is to display lists of objects in a table-like layout. A list view consists of three elements: a list, a tool bar, and a pop-up menu. These elements are created by an instance of a controller class. MVC-addicts may argue that this is a wrong way to encapsulate functionality, and the right way would be to have a list view class be responsible for creating all of its elements and laying them out on the screen. While I agree that, as I mentioned earlier, we did not follow the MVC framework too closely on the client side and the role of controller classes is somewhat blurry. When you write extensions to the administration console you might as well avoid controllers.

Examples of list view classes in ZCS administration console are ZaAccountListView, ZaServerListView, ZaDomainListView, etc. All list view classes in the administration UI inherit from ZaListView, which in term inherits from DwtListView. If you are adding your own list view class, it is recommended that you follow this inheritance pattern, although that is not required as long as your class implements the required methods. However, it is required that any view class inherits from DwtComposite class. Here is one way to think about it for those of you who are more familiar with languages like C++ and Java. If JavaScript had abstract classes and pure abstract classes like C++ or interfaces like Java, there would have been a common interface (pure abstract class), e.g. IlistView that every list view class had to implement. DwtListView would have been a default implementation of that interface and would extend DwtComposite class. Since JavaScript does not enforce inheritance natively, any such enforcement would have been artificial and limiting. Therefore, the framework does not enforce the inheritance, but only advises it.

Methods that a list view class has to implement:

  • getTabIcon
  • getTabTitle
  • _sortColumn
  • _setNoResultsHtml
  • _createItemHtml

Following is an example of a simple list view class.

Code example

In the following example, we create a list view class that overwrites two list view methods toString and getTitle.


/**

* @constructor

* @class ZaMyTestListView

* @param parent

**/

ZaMyTestListView= function(parent) {

  ZaListView.call(this, parent, null, DwtControl.ABSOLUTE_STYLE);

}

ZaMyTestListView.prototype = new ZaListView;

ZaMyTestListView.prototype.constructor = ZaMyTestListView;

ZaMyTestListView.prototype.toString = function() {

  return “ZaMyTestListView”;

}

ZaMyTestListView.prototype.getTitle = function () {

  return “My Test View”;

}

Form views

Most form views display properties of a selected data object such as a server or an account. Therefore, form views consist of a tool bar, a tab bar and several HTML forms placed on tab cards. Similar to list views, form view elements are created by an instance of a controller class. All form views inherit from ZaTabView. If you are creating your own form views, you are not required to inherit your view classes from ZaTabView, but it is required that any view class inherits from DwtComposite class. In order to build forms quickly and consistently, we created a special framework for rendering and handling forms and called it Xforms in analogy to W3C Xforms proposal.

The following methods are common for all classes that implement form views: setObject, getTitle and myXFormModifier.setObject method takes an instance of a model class as its argument, caches the data of the instance in a member variable and passes the data to the Xforms form that is displayed on the view. In order to construct a form, we have to provide the Xforms framework with two meta-data objects, one that describes the data model for the form and another that describes the form elements and layout. The object that describes the data model is myXModel object described in section [#_toc155 #3.2.6.myXModel]. The object that describes form elements and layout is accessed by getMyXForm method of a view class. Default implementation of getMyXForm method is available in ZaTabView class, and it is not recommended to overwrite this method. The getMyXForm method follows the mechanism similar to the mechanism of initializing, loading, and modifying data instances as described in section [#_toc23 #3.2.Models]. There is a static map ZaTabView.XformModifiers where keys are names of the view classes and values are arrays of references to methods that define elements of a form, e.g. All methods that define form elements for a form that displays an account object are referenced in ZaTabView.XformModifiers[“ZaAccountXFormView”] array.

Following is an example of a simple form view class. This is an actual form view class for a view that manages Samba Domains in zimbra_samba admin extension.

Code example


/**

* @class ZaSambaDomainXFormView

* @contructor

* @param parent

* @param app

**/

function ZaSambaDomainXFormView (parent, app) {

  /* call the parent constructor, “ZaSambaDomainXFormView” is the name of 

  the CSS class for this view */

  ZaTabView.call(this, parent, app,”ZaSambaDomainXFormView”);

  /* initialize the Xform */

  this.initForm(ZaSambaDomain.myXModel,this.getMyXForm()); 

  /* assign a reference to the main application controller to the Xform 

  instance, so that elements on the form can access the main application 

  controller */

  this._localXForm.setController(this._app); 

}

ZaSambaDomainXFormView.prototype = new ZaTabView();

ZaSambaDomainXFormView.prototype.constructor = ZaSambaDomainXFormView;

ZaTabView.XformModifiers[”ZaSambaDomainXFormView”] = new Array();

/**

* @param entry – an instance of  ZaSambaDomain class, that contains data 

* for a Samba Domain object

**/

ZaSambaDomainXFormView.prototype.setObject = function (entry) {

  this._containedObject.attrs = new Object();

  /* create an object to store the local cache of the data */

  this._containedObject = new ZaSambaDomain(this._app); 

  /**

  * BEGIN saving local copy of the data

  **/

  if(entry.id)

    this._containedObject.id = entry.id;

  if(entry.name)

    this._containedObject.name = entry.name;

  for (var a in entry.attrs) {

    if(entry.attrs[a] instanceof Array) {

      this._containedObject.attrs[a] = new Array();

      var cnt = entry.attrs[a].length;

      for (var ix = 0; ix < cnt; ix++) {

        this._containedObject.attrs[a][ix]=entry.attrs[a][ix];

      }

    } else {

      this._containedObject.attrs[a] = entry.attrs[a];

    }

  }

  /**

  * END saving local copy of the data

  **/

  /**

  * initialize the variable which is not part of  ZaSambaDomain class, but 

  * is used by the form

  **/

  if(!entry[ZaModel.currentTab])

    this._containedObject[ZaModel.currentTab] = “1”;

  else

    this._containedObject[ZaModel.currentTab] = entry[ZaModel.currentTab];

  //pass the data to the form

  this._localXForm.setInstance(this._containedObject);

  //trigger form redraw

  this.updateTab();

}

ZaSambaDomainXFormView.prototype.getTitle = function () {

  return “Samba Domains”;

}

/**

* Define a static method which defines form elements

* @param xFormObject – a reference to the object that contains definitions 

* of form elements. This object was created in 

* ZaTabView.prototype.getMyXForm prior to calling this method

**/

ZaSambaDomainXFormView.myXFormModifier = function(xFormObject) {

  xFormObject.tableCssStyle=”width:100%;overflow:auto;”;

  xFormObject.items = [

    {type:_GROUP_, 

      cssClass:”ZmSelectedHeaderBg”, 

      colSpan: “*”, 

      id:”xform_header”,

      items: [

        {type:_GROUP_,

          numCols:4,

          colSizes:[”32px”,”350px”,”100px”,”250px”],

          items: [

            {type:_AJX_IMAGE_, src:”Domain_32”, label:null},

            {type:_OUTPUT_, 

              ref:ZaSambaDomain.A_sambaDomainName, 

              label:null,cssClass:”AdminTitle”, rowSpan:2},

            {type:_OUTPUT_, ref:ZaSambaDomain.A_sambaSID, label:”sambaSID”}

          ]

        }

      ],

      cssStyle:”padding-top:5px; padding-left:2px; padding-bottom:5px”

    },

    {type:_TAB_BAR_, 

      ref:ZaModel.currentTab,

      choices:[

        {value:1, label:ZaMsg.Domain_Tab_General}

      ],

      cssClass:”ZaTabBar”, id:”xform_tabbar”

    },

    {type:_SWITCH_,

      items:[

        {type:_ZATABCASE_, 

          relevant:”instance[ZaModel.currentTab] == 1”,

          colSizes:[”250px”,”*”],

          items:[

            {ref:ZaSambaDomain.A_sambaDomainName, 

              type:_TEXTFIELD_,

              label:ZaMsg.Domain_DomainName,

                   onChange:ZaTabView.onFormFieldChanged},

            {ref:ZaSambaDomain.A_sambaSID, 

              type:_TEXTFIELD_,

              label:”sambaSID”, width:300,

                   onChange:ZaTabView.onFormFieldChanged},

            {ref:ZaSambaDomain.A_sambaAlgorithmicRidBase, 

              type:_TEXTFIELD_,

              label:”sambaAlgorithmicRidBase”, 

              cssClass:”admin_xform_number_input”,

              onChange:ZaTabView.onFormFieldChanged}]

        }

]

         }

  ];

}

/* put the reference to the method defined above into the static map with the key that corresponds to this view */

ZaTabView.XformModifiers[”ZaSambaDomainXFormView”].push(ZaSambaDomainXFormView.myXFormModifier); 

ZaTabView.XFormModifiers

Methods referenced in ZaTabView.XformModifiers map are responsible for defining meta-data that describes form elements and form layout in Xforms syntax. If you want to find out how these methods are called, look at ZaTabView.getMyXForm method in ZaTabView.js file.

ZaTabView.getMyXForm vs. ZaItem.myXModel

A question you may have after reading the previous section is how does meta data returned by ZaTabView.getMyXForm interact with meta data in ZaItem.myXModel. Lets look at an example. Let’s say, we have an Account object to be rendered. ZaItem.myXModel will say that the account has a name, which is a string, and a quota which is an integer. The ZaItem.myXModel may also say that an account may have a Class of Service, which is an ID of a Class of Service object, and if the quota attribute of the account is NULL, than it defaults to the quota attribute in the account’s class of service. Metadata returned by ZaTabView.getMyXForm will say that the account’s name is to be rendered as an editable text box, and Class of Service is to be rendered as a drop down with three values to select from.

Controllers

If you have read the sections about Models and Views, then by now you are probably wondering how Views get the Data and how user actions translate into data changes. This is exactly what controllers’ role is. In the administration console, controllers are the glue between the views and the data. Controllers pass data from Models to Views and vice versa. There are four types of controllers in the administration console: list view controllers, form view controllers, main application controller, and overview panel controller. The last two controllers are singletons, and therefore there is only one instance of the main application controller and one instance of the overview panel controller per application. The base class for all controllers is ZaController. This class provides default implementations of several methods and holds maps of references to methods (similar to ZaTabView.XformModifiers, ZaItem.loadMethods and other maps of references discussed previously). The following maps of method references are important when extending existing views and creating new views:

  • ZaController.initToolbarMethods
  • ZaController.initPopupMenuMethods

ZaController.initToolbarMethods

Methods referenced in ZaController.initToolbarMethods map are responsible for adding buttons to toolbars. Similar to all other reference maps, keys in the map are names of controllers and values are arrays of references to methods. Methods referenced in this map are called by ZaController.prototype._initToolbar

Following is an example of using to add buttons to the toolbar. This example is taken from com_zimbra_cert_manager extension.

Code example

/**

* Method that adds button descriptors to this view’s toolbar

**/

ZaCertsServerListController.initToolbarMethod = function () {

  this._toolbarOperations.push(

    new ZaOperation(ZaOperation.VIEW,

    com_zimbra_cert_manager.TBB_view_cert,

    com_zimbra_cert_manager.TBB_view_cert_tt, 

    “ViewCertificate”, 

    “ViewCertificate”, 

     new AjxListener(

       this,

       ZaCertsServerListController.prototype.viewCertListener)

     )

  );

  this._toolbarOperations.push(

    new ZaOperation(ZaOperation.NEW,

    com_zimbra_cert_manager.TBB_launch_cert_wizard,

    com_zimbra_cert_manager.TBB_launch_cert_wizard_tt,

    “InstallCertificate”,

    “InstallCertificate”,

    new AjxListener(this,

      ZaCertsServerListController.prototype._newCertListener)

    )

  );

  this._toolbarOperations.push(new ZaOperation(ZaOperation.NONE));

  this._toolbarOperations.push(

    new ZaOperation(ZaOperation.HELP, 

      com_zimbra_cert_manager.TBB_Help, 

      com_zimbra_cert_manager.TBB_Help_tt,

      “Help”,

      “Help”, 

      new AjxListener(this, this._helpButtonListener)

    )

  );

}

//add method reference to the map

ZaController.initToolbarMethods[”ZaCertsServerListController”].push(

  ZaCertsServerListController.initToolbarMethod);

ZaController.initPopupMenuMethods

This map is very similar to the initToolbarMethods map. Methods referenced in this map define items that appear in the popup menu. Most of the times, items in the popup menu mirror toolbar buttons. Methods referenced in this map are called by ZaController.prototype._initPopupMenu.

_createUI method

When you are adding a new view to the administration console, there are two methods that you have to implement: _createUI, and show. _createUI method is responsible for instantiating elements of the view and calling the view manager to lay out the elements on the screen.

Following is an example of _createUI method of a form view controller class in com_zimbra_cert_manager extension which implements certificate management. In this example, _createUI method creates a toolbar, and a form view.

Code example

ZaCertViewController.prototype._createUI = function () {

  try {

    //map of elements that constitute the view

    var elements = new Object();

    //instantiate the form view

    this._contentView = new ZaCertView( this._container, this._app );

    /* initialize toolbar buttons, this calls 

    * ZaController.prototype._initToolbar method which will iterate 

    * through all the methods referenced in

    * ZaController.initToolbarMethods[”ZaCertViewController”] array */

    this._initToolbar();

    if(this._toolbarOperations && this._toolbarOperations.length) {

      //instantiate the toolbar

      this._toolbar = new ZaToolBar(

        this._container,    

        this._toolbarOperations

      );

      //add the toolbar to the map

      elements[ZaAppViewMgr.C_TOOLBAR_TOP] = this._toolbar;

    }

    //add the xform view to the map

    elements[ZaAppViewMgr.C_APP_CONTENT] = this._contentView;

    var tabParams = {

      openInNewTab: true,

      tabId: this.getContentViewId(),

      tab: this.getMainTab()

    }

    //call view manager to layout all the elements on the screen     

    this._app.createView(this.getContentViewId(), elements, tabParams);

    this._UICreated = true;

    this._app._controllers[this.getContentViewId ()] = this ;

  } catch (ex) {

      this._handleException(ex, 

        “ZaCertViewController.prototype._createUI”, 

        null, 

        false

      );

      return;

       }

}

Following is an example of _createUI method of a list view controller class in com_zimbra_cert_manager extension. In this example, _createUI method creates a toolbar, a popup menu, and a list view.

Code example

ZaCertsServerListController.prototype._createUI = function () {

  try {

    var elements = new Object();

    this._contentView = new ZaCertsServerListView(this._container);

  /* this will call ZaController.prototype._initToolbar method, which will 

  * iterate through methods referenced in 

  * ZaController.initToolbarMethods[“ZaCertsServerListController”] array */

    this._initToolbar();

    if(this._toolbarOperations && this._toolbarOperations.length) {

      this._toolbar = new ZaToolBar(

        this._container, 

        this._toolbarOperations

      );

      elements[ZaAppViewMgr.C_TOOLBAR_TOP] = this._toolbar;

}

/* this will call ZaController.prototype._initPopupMenu method, which will * iterate through methods referenced in 

* ZaController.initPopupMenuMethods[“ZaCertsServerListController”] array */

    this._initPopupMenu();

    if(this._popupOperations && this._popupOperations.length) {

      this._actionMenu = 

        new ZaPopupMenu(

          this._contentView, 

            “ActionMenu”, 

            null, 

            this._popupOperations

        );

    }

    elements[ZaAppViewMgr.C_APP_CONTENT] = this._contentView;

    var tabParams = {

      openInNewTab: false,

      tabId: this.getContentViewId(),

      tab: this.getMainTab()

    }

    //call view manager to layout all the elements on the screen

    this._app.createView(this.getContentViewId(), elements, tabParams);

    this._contentView.addSelectionListener(

      new AjxListener(this, this._listSelectionListener)

    );

    this._contentView.addActionListener(

      new AjxListener(this, this._listActionListener)

    );

    this._removeConfirmMessageDialog = 

      new ZaMsgDialog(

        this._app.getAppCtxt().getShell(), 

        null, 

        [DwtDialog.YES_BUTTON, DwtDialog.NO_BUTTON], 

        this._app

      );

    this._UICreated = true;

    this._app._controllers[this.getContentViewId ()] = this ;

  } catch (ex) {

      this._handleException(

        ex, 

        “ZaCertsServerListController.prototype._createUI”, 

        null, 

        false

      );

      return;

  }

    }  

show method

This method is called to show a view. E.g.: when a user selects “Servers” node in the navigation tree, an instance of ZaOverviewPanelController will call ZaServerListController.prorotype.show method. This method’s responsibility is to call _createUI method, pass the data to view, and call the view manager to make the view visible. The method has two arguments. In case of list view controller, the first argument is the list of objects to be displayed in the list view, and the second argument is a Boolean flag that tells whether to create a new tab for the view.

Following is an example of show method of a list view controller class in com_zimbra_cert_manager extension.

Code example

/**

* @param list {ZaItemList} a list of ZaServer {@link ZaServer} objects

* @param openInNewTab {Boolean} a flag that indicates whether to open the

* view in the new tab

**/

ZaCertsServerListController.prototype.show = function(list, openInNewTab) {

  if (!this._UICreated) {

    //create the UI components

    this._createUI();

  } 

  //pass the data to the view

  if (list != null)

    this._contentView.set(list.getVector());

  //tell the view manager to show the view

  this._app.pushView(this.getContentViewId());

  if (list != null)

    this._list = list;

  this.changeActionsState();

}

In case of form view controller, the first argument is the object to be shown on the form, and the second argument is the id of the tab where the view will be displayed.

Following is an example of show method of a form view controller class in com_zimbra_cert_manager extension.


Code example


ZaCertViewController.prototype.show = function(certs, targetServerId) {

  if (!this._UICreated) {

    this._createUI();

  } 

  if (certs != null) {

    this._contentView.set(certs, targetServerId);

  }

  this._app.pushView(this.getContentViewId());

}

Handling exceptions

ZaController class has a helper method for handling and reporting exceptions: ZaController.prototype._handleException method. This method allows the caller to display an error message, restart the application and call a function after the application is restarted.The method takes five arguments:

  1. Exception object
  2. Name of the function where the exception was caught (String value) or a reference to the function which should be executed after the application is restarted
  3. Array of arguments for the function that will be executed when the application is restarted
  4. Boolean flag that indicates whether the application needs to be restarted
  5. The object to be used as the context of the function to be called after the application is restarted

Only the first argument is required, and the rest of the arguments are optional. It is generally not recommended to force the application to restart when an exception is caught. In fact, there is only one place in the code of Zimbra Administration Console that uses TRUE for the fourth argument to ZaController.prototype._handleException call.

When the fourth argument (boolean flag) is FALSE, _handleException method will show an error dialog with the details of the exception. For well-known exceptions, such as authentication failure, network error, the dialog will have a customized user-friendly message. If the server's stack trace is available for the exception it will be displayed in the error dialog.

List view controllers

As the title suggests, list view controllers control list views. The responsibilities of a list view controller are:

    • instantiate and initialize the elements: toolbar, popup menu, list view
    • capture and dispatch user-generated events: button click in the toolbar, element selection in the list view, element selection in the popup menu, and double click in the list view
    • control the state of the buttons in the toolbar and elements in the popup menu. E.g.: determine whether a particular button should be enabled or disabled and whether a particular element in the popup menu should be enabled or disabled
    • handle some of the events generated by changes in data. E.g.: redraw the list view when an object is deleted from the view

The framework of the administration console provides a base class for list view controllers: ZaListViewController which inherits from ZaController class. ZaListViewController holds some additional method maps and provides default implementations for methods commonly used by list view controllers.

Form view controllers

Form view controllers control XForm views. The responsibilities of a form view controller are:

  • instantiate and initialize the elements: toolbar and form view
  • cache the data instance and pass it on to the form view
  • capture and dispatch button clicks in the toolbar
  • control the state of buttons in the toolbar. E.g.: enable “save” button if the user changes anything on the form
  • determine what data is modified and what data is unchanged, and call ZaItem.modify method to submit the changes to server

Main application controller

The main application controller is implemented by ZaApp class. The only thing you really need to know about ZaApp is that it holds references to the instances of all other controllers and provides several utility methods.

Overview panel controller

ZaOverviewPanelController instantiates ZaOverviewPanel class, creates nodes in the navigation panel and handles selection of the nodes.

Message dialogs

Base class for all message dialogs in Zimbra Ajax Framework is DwtMessageDialog. Zimbra administration console extends DwtMessageDialog with ZaMsgDialog, you must Use ZaMsgDialog to create an instance of a message dialog in Admin UI. Generally you would not have to create your own instance of ZaMsgDialog, because Admin UI framework provides four singleton instances of ZaMsgDialog which cover most scenarios for using a message dialog: msgDialog, errorDialog, confirmMessageDialog and confirmMessageDialog2. In order to maintain Admin UI performance and avoid increasing its memory footprint you should always consider using these instances for showing a message dialog to a user. These instances can be accessed via ZaApp class:

  • ZaApp.getInstance().dialogs["errorDialog"]
  • ZaApp.getInstance().dialogs["confirmMessageDialog"]
  • ZaApp.getInstance().dialogs["confirmMessageDialog2"]
  • ZaApp.getInstance().dialogs["msgDialog"]

Important methods of ZaMsgDialog

To define what message is shown on the dialog:

<code>
setMessage(String msgStr, contant style, String title);
</code>

parameter style can take one of the following values:

  • DwtMessageDialog.CRITICAL_STYLE
  • DwtMessageDialog.INFO_STYLE
  • DwtMessageDialog.WARNING_STYLE

To define callbacks for buttons in the dialog:

<code>
registerCallback(constant buttonId, AjxCallback func, Object obj, Array args) 
</code>

buttonId identifies the button to which the callback identified bu AjxCallback will be assigned. Default callbacks for all buttons hide the dialog.

  • msgDialog and errorDialog have one button:
    • DwtDialog.OK_BUTTON
  • confirmMessageDialog2 has two buttons:
    • DwtDialog.YES_BUTTON
    • DwtDialog.NO_BUTTON
  • confirmMessageDialog has three buttons:
    • DwtDialog.YES_BUTTON
    • DwtDialog.NO_BUTTON
    • DwtDialog.CANCEL_BUTTON

To show a message dialog:

<code>popup()</code>

To hide a message dialog:

<code>popdown()</code>

Examples of using message dialogs in Admin UI

Yes/No/Cancel confirmation dialog

This code will show a message "Do you want to save current changes?" with three buttons: "Yes", "No", and "Cancel", and will assign custom callbacks to "Yes" and "No" buttons.

<code>
ZaApp.getInstance().dialogs["confirmMessageDialog"].setMessage(ZaMsg.Q_SAVE_CHANGES, DwtMessageDialog.INFO_STYLE);
ZaApp.getInstance().dialogs["confirmMessageDialog"].registerCallback(DwtDialog.YES_BUTTON, this.validateChanges, this, args);
ZaApp.getInstance().dialogs["confirmMessageDialog"].registerCallback(DwtDialog.NO_BUTTON, this.discardAndGoAway, this, args);
ZaApp.getInstance().dialogs["confirmMessageDialog"].popup();
</code>

Yes/No confirmation dialog

This code will show a message "You have modified theme properties. In order for the changes to have effect, you need to flush server theme cache. Would you like to flush theme cache now?" with two buttons "Yes" and "No", and will assign custom callbacks to "Yes" and "No" buttons.

<code>
ZaApp.getInstance().dialogs["confirmMessageDialog2"].setMessage(ZaMsg.Domain_flush_cache_q, DwtMessageDialog.INFO_STYLE);
ZaApp.getInstance().dialogs["confirmMessageDialog2"].registerCallback(DwtDialog.YES_BUTTON, this.openFlushCacheDlg, this, [serverList]);		
ZaApp.getInstance().dialogs["confirmMessageDialog2"].registerCallback(DwtDialog.NO_BUTTON, this.closeCnfrmDelDlg, this, null);				
ZaApp.getInstance().dialogs["confirmMessageDialog2"].popup();             			
</code>

Error dialog

Error dialog is implemented by ZaErrorDialog which extends ZaMsgDialog. Error dialog adds expandable "Details" section to display technical details of an error such as stack trace or a message that came from server. ZaController class has a public method to show an error dialog:

<code>popupErrorDialog(String msg, AjxException ex, constant style)</code>
.

Following is an example of showing an error dialog with an exception object:

<code>
this.popupErrorDialog(ZaMsg.SERVER_ERROR, ex,DwtMessageDialog.CRITICAL_STYLE);
</code>

Acceptable values for style argument are

  • DwtMessageDialog.CRITICAL_STYLE
  • DwtMessageDialog.INFO_STYLE
  • DwtMessageDialog.WARNING_STYLE

Default value is DwtMessageDialog.CRITICAL_STYLE

XForm Dialogs

XForm dialogs are similar to message dalogs. The only difference is that an XForm dialog uses XForm’s framework to draw the contents of the dialog.

Wizard Dialogs

Wizard dialog is a form of XForm dialog. In addition to functionality provided by an XForm dialog, a Wizard dialog also provides methods for navigating a step-by-step wizard.

Application tabs

Starting with version 5.0, Zimbra Administration Console facilitates an application tab infrastructure to allow opening views in a separate tab. Each tab has a view and controller instance associated with it. These instances are identified by an internal unique ID. When the tab is closed, associated view, data and controller instances are destroyed. There is a main tab which can't be closed and always holds the view of the first level tree items in the overview panel. Here are the examples to create the views in the controllers. This is the same code that you have seen previously in section #_createUI_method


1. To create a view in the main tab: 
var tabParams = { 
openInNewTab: false, 
tabId: this.getContentViewId(), 
tab: this.getMainTab() 
} 
this._app.createView(this.getContentViewId(), elements, tabParams) ; 

2. To create a view in the new tab:


var tabParams = { 
openInNewTab: true, 
tabId: this.getContentViewId() 
} 
this._app.createView(this.getContentViewId(), elements, tabParams) ; 

We suggest creating list views or the first level tree item views in the main tab and other item views in the new tab.

Application Skins

TBD


Verified Against: unknown Date Created: 11/21/2007
Article ID: http://wiki.zimbra.com/index.php?title=Admin_UI_Framework_Guide Date Modified: 07/28/2010
Personal tools