View source | Discuss this page | Page history | Printable version   
Main Page
Upload file
What links here
Recent changes

PDF Books
Add page
Show collection (0 pages)
Collections help


How to implement a new main view



This howto discusses how a new view can be added to Openbravo and made available through the menu and quick launch options.

A view is shown inside the tab of the multi-document-interface (MDI) in the main content area of the Openbravo user interface. This is illustrated in the screenshot below which shows several opened tabs, what's shown inside a tab is called a view.

Obuiapp main view mdi2.png


This howto uses the following terminology:

Presentation - Blogs

This blog contains a link to an interesting presentation/demo on how to develop a manual window:

Example Module

This howto is supported by an example module which shows example of the code shown and discussed in this howto.

The code of the example module can be downloaded from this mercurial repository:

The example module is available through the Central Repository (See 'Client Application Examples'), for more information see the Examples Client Application project page.

Bulbgraph.png   The example module also contains implementations of other howtos.
Bulbgraph.png   When implementing your own components it often makes sense to extend existing components. Make sure that your module then depends on the module providing the base types. This ensures that the javascript is loaded in the correct order. Most of the time it makes sense to add a dependency from your module to the Client 'User Interface Application/client.application' module

Main flow of view handling

A view is opened when a user makes a menu choice or uses quick launch (or through hyperlinks). To obtain and render the view the system goes through a number of steps:

The above flow also illustrates that view definitions can be provided in 2-ways to the system:

The first approach is the preferred approach, the easiest from an implementation point of view and will probably fit most usecases. The second approach is best if the view definition depends on dynamic information which is easier to incorporate on the server (using java) than on the client (using javascript).

Both implementation methods are discussed below in more detail.

Preferred: Implementing your view in Static javascript

Implementing a view as static javascript is the most straight forward method. Any Smartclient canvas is allowed to be used as a view. When implementing your view in a static javascript you should take the following steps:

Let's first start with a simple view and go through the steps to create it, define a view definition record and add it to the menu. Then an example of a more complex view is shown.

Create a javascript file with the class definition of the view

The implementation of the view needs to be done as an extension of Smartclient canvas class. The most common canvas to extend is the Layout. This is an example of a layout which contains one label as its content:

isc.defineClass("OBEXAPP_HelloWorldLabelView", isc.Layout).addProperties({
  labelContent: 'Label content should be passed in as a parameter',
  width: '100%',
  height: '100%',
  align: 'center',
  defaultLayoutAlign: 'center',
  initWidget: function() {
    this.children = [isc.Label.create({
      height: 1,
      width: 1,
      overflow: 'visible',
      align: "center",
      valign: "center",
      contents: this.labelContent})];
    this.Super("initWidget", arguments);

A short description of this source code:


Note: For screens that will appear in a standard UI tab, we recommend extending the OBBaseView javascript class instead of the Smartclient layout (isc.Layout):

isc.defineClass("OBEXAPP_HelloWorldLabelView", isc.OBBaseView).addProperties({....});

This will also take care of proper titles on screen refresh and back-button functionality (see Taking care of history/back button/page refresh section below).

The javascript file should be placed inside the module in a directory: web/[modulejavapackage]/js

Obuiapp static js location.png

The next step is to register the js file in Openbravo. This is done through a ComponentProvider. For more detailed information on the ComponentProvider concept visit this page.

The example module contains an example of a ComponentProvider, it has this method to tell Openbravo where to find the javascript file which contains the view implementation:

public List<String> getGlobalComponentResources() {
  final List<String> globalResources = new ArrayList<String>();
      "web/org.openbravo.client.application.examples/js/example-view-component.js", false));
      "web/org.openbravo.client.application.examples/js/example-grid-view.js", false));
      "web/org.openbravo.client.application.examples/js/example-simple-view.js", false));
  return globalResources;

Create a view implementation record - handle role access - Add to the menu

The js file is ready and registered, the next step is to make it accessible through the menu and quick launch. This requires three steps:

  1. create a view implementation record
  2. ensure that the correct roles have access
  3. create a menu entry and place the menu entry in the correct location

Create a view implementation record

Goto the View Implementation Window, either through the menu (Application Dictionary > User Interface > View Implementation) or through Quick Launch (type in View Implementation). Create a new record, the most important thing is that the Name of the record should be the same as the class name defined in the js file (in our case: OBEXAPP_HelloWorldLabelView).

Bulbgraph.png   The field Name is the one who must match the class name defined in the .js file, the Classname of the view implementation is something different and can be left empty for this example

Obexapp view implementation.png

Role access

Then go to the Role window (General Setup > Security > Role) and check the View Implementation child tab to check that the intended roles have access:

Obexapp view implementation role.png

Menu Entry

The view implementation should be accessible from the menu and quick launch. This is done by entering a menu record and placing the menu entry in the correct location in the tree. Adding to the menu will automatically make the view available through quick launch.

Create a new menu record with the action set to 'Open View in MDI', the View Implementation combo will appear, there choose 'OBEXAPP_HelloWorldLabelView' (or the view name you have

Obexapp view implementation menu.png

Try it!

Log out and then log in again to refresh the menu (the menu is cached in the user session). Then access the new view through the menu or through quick launch. It should show something as illustrated below.

Obexapp view implementation result1.png

As you can see the text in the middle needs to be replaced. We can control this with so-called 'Menu Parameters'. This is explained in more detail in the next secion.

Working with menu parameters

Menu parameters allow you to re-use the same class definition in different menu entries but still ensure different behavior by passing them different parameters. In this example we will display a different text in the middle of the new simple view. Go to the menu window and select the menu entry added a few steps earlier. Then go to the child tab 'Menu Parameters' and add a record with name: labelContent and a value.

Obexapp view implementation menu parameter.png

Now log out and log in again, open the view through the menu (don't use any of the 'recent' links as they store the parameters also), now the result should be something like this:

Obexapp view implementation result2.png

Taking care of history/back button/page refresh

If you had implemented your view by extending the isc.Layout base class, try to refresh the complete page with the view opened. You will see that the view get's re-opened but that the tab title is wrong and that the content is wrong. When opening a view Openbravo will store information to be able to reconstruct the view when the page is refreshed or when the back/forward button in the browser is pressed.

The give Openbravo the correct information the view needs to implement a function (getBookMarkParams):

getBookMarkParams: function() {
  var result = {};
  result.viewId = this.getClassName();
  result.labelContent = this.labelContent;
  result.tabTitle = this.tabTitle;
  return result;

Some notes: the getBookMarkParams returns an object which is used to recreate the view instance when rebuilding the page, as you can see both the tab title and the labelContent are passed back (as well as the class name of the view).

For a complete overview of the api which Openbravo checks for additional functionality (opening a help view, preventing multiple tabs of the same type), see the View API section below.

Note: To avoid duplicating the above piece of code for your standard tabbed views, an OBBaseView wrapper class is available that includes all above actions. Hence, instead of extending the isc.Layout class, consider extending the OBBaseView, e.g.:

isc.defineClass("OBEXAPP_HelloWorldLabelView", isc.OBBaseView).addProperties({....});

A more elaborate view: grid with button, parameterized messages

In the previous step we created a simple view. This section shows a more complex example. It is part of the example module in the file example-grid-view.js. It shows a grid with a button which is enabled when selecting one or more records in the grid.

Obexapp view example grid.png

The source code of the test view is shown below. It start with the definition of the grid class and then defines the view itself (further down). The class name of the view itself is OBEXAPP_SimpleView. So to make use of this view, a view implementation record with this name (OBEXAPP_SimpleView) should be created and added to the menu.

// This javascript defines a grid and a layout containing the grid 
// together with a button to show the selected records
// This testgrid shows data from the product table
isc.defineClass('OBEXAPP_TestGrid', isc.OBGrid);
  dataSource: null,
  showFilterEditor: true,
  dataProperties: {
    useClientFiltering: false
  gridFields: [{
    name: 'name',
    // allow filtering on name
    canFilter: true,
    // filter automatically when the user types
    filterOnKeypress: true
    // description is a property of the product
    name: 'description',
    // allow filtering on description
    canFilter: true,
    // filter automatically when the user types
    filterOnKeypress: true
  setDataSource: function(ds) {
    // is called when the datasource is retrieved from the server
    this.Super('setDataSource', [ds, this.gridFields]);
  initWidget: function() {
    // get the datasource, if it is not yet defined
    // it is retrieved from the server and returned
    // Datasources refer to tables using the entity name
    OB.Datasource.get('Product', this, null, true);
    this.Super('initWidget', arguments);
isc.defineClass('OBEXAPP_SimpleView', isc.VLayout);
  // do some margins between the members
  membersMargin: 10,
  defaultLayoutAlign: 'center',
  initWidget: function() {
    // create a button which is enabled 
    var grid, btn;
    // create an instance of the grid
    grid = isc.OBEXAPP_TestGrid.create({
      // add logic to enable/disable the button when
      // the selected records changes
      selectionUpdated: function(record, recordList) {
        if (recordList && recordList.length > 0) {
        } else {
    // and create a button which refers to the grid
    btn = isc.OBFormButton.create({
        title: OB.I18N.getLabel('OBEXAPP_ClickMe'),
        // let it be enabled when more than one 
        disabled: true,
        action: function() {
          // show the number of selected records
          // illustrates the usage of a parameterized message
    // add the grid
    this.Super('initWidget', arguments);
  // the following three methods are related to the view handling
  // api of Openbravo
  isSameTab: function(viewId, params){
    // return false, allows this view to be opened many times
    return false;
  // just return the classname and nothing else to be bookmarked
  getBookMarkParams: function() {
    var result = {};
    result.viewId = this.getClassName();
    return result;
  // this view does not have a help view
  getHelpView: function(){

Note that the button click shows a parameterized message. A message can contain parameters (%0, %1) which are substituted with real values when showing the message.

Obexapp view example parameterized message.png

View API

Any Smartclient canvas can be used as an Openbravo view. When opening a view Openbravo will check if the view class/instance supports certain methods. If so then Openbravo will make use of these methods. The methods are reflected in the OBBaseView type (available from Openbravo 3.0MP3):

// = OBBaseView =
// A class which implements the view api.
isc.ClassFactory.defineClass('OBBaseView', isc.Layout);
  // ** {{{ OBBaseView.showsItself }}} **
  // If this boolean property is set to true then the Openbravo view manager
  // will not place the view instance in a tab in the main Multi-Document-Interface.
  // Instead it will call the show method on the instance. This makes 
  // it for example possible to define views which are implemented as 
  // popups instead of opened in the main MDI.
  showsItself: false,
  // ** {{{ OBBaseView.isSameTab() }}} **
  // Is called by the view manager when opening a view. The view manager
  // will first check if there is already a tab open by calling the 
  // isSameTab method on each opened view. If one of the views returns
  // true then the requested view is opened in that tab (effectively
  // replacing the current open view there). This is needed for cases
  // when a certain view may only be opened once.
  isSameTab: function(viewId, params){
    // a common implementation does this, this allows only 
    // one instance of certain view class to be open at one point 
    // in time.
    // return viewId === this.getClassName();
    // this will allow multiple tabs to be opened:
    return false;
  // ** {{{ OBBaseView.getBookMarkParams() }}} **
  // Is used to create a bookmarkable url in the browser's address bar.
  // For each opened view this method is called and the result is added
  // to the address bar. This makes it possible for the user to do 
  // back in the browser, to bookmark the url and to build history in the 
  // browser itself. 
  getBookMarkParams: function() {
    var result = {};
    result.viewId = this.getClassName();
    return result;
  // ** {{{ OBBaseView.getHelpView() }}} **
  // This method can return an object containing a view definition. 
  // If this method returns an object then a link is activated in the 
  // help pull-down in the top.
  getHelpView: function(){
    // an example of returning a view definition, the viewId contains
    // the help view classname, the tabTitle denotes the tab title of the
    // help view
//    return {
//        viewId: 'ClassicOBHelp',
//        tabTitle: this.tabTitle + ' - ' + OB.I18N.getLabel('UINAVBA_Help'),
//        windowId: this.windowId,
//        windowType: 'W',
//        windowName: this.tabTitle
//    };

Dynamically generated javascript

Instead of creating a static javascript file with a view definition it is also possible to use dynamically generated javascript. The javascript is generated at the first usage of the view definition by the user.

To create a dynamically generated view the following parts need to be implemented:

Each of these steps is described in more detail below.

The example module contains a Hello World component with a template (hello_world_view.js.ftl). This example creates a view with one clickable button.

Example template component structure2.png

Creating a component

A component is useful when you want to add runtime information to the view definition javascript when it gets generated.

If you don't have the requirement to use dynamic information in the generated javascript of your component then you don't need to implement a component (only a template). You can leave the class field empty in the view implementation record (see below).

The example module has a hello world component which provides the current logged in user to the template. The component can be found in the module's src directory. Here is the code:

package org.openbravo.client.application.examples;
import org.openbravo.client.kernel.BaseTemplateComponent;
import org.openbravo.dal.core.OBContext;
 * Provides a widget which shows a hello world message when clicked.
 * @author mtaal
public class HelloWorldComponent extends BaseTemplateComponent {
  public String getName() {
    return OBContext.getOBContext().getUser().getName();

Note: for simplicity we choose to get the user name on the server, this information is also available on the client in the global OB.User object (which contains information about the currently logged in user.

Creating a template

The template contains the actual javascript. A template consists of two parts:

  1. a template file (the template source) ending on ftl (a freemarker extension) which is located in the source tree (in the classpath).
  2. a record in the template table

The template source

To create the template for your view, create a ftl file in the source tree of your module. The ftl file should contain plain javascript with possible freemarker constructs to read information from the component.

As an example, the hello world template can be found in the org.openbravo.client.application.examples.templates package, it creates a button which can be clicked. The content of the template is this:

/* jslint */
isc.defineClass("OBAPPEX_HelloWorldButtonView", "Button").addProperties({
  title: "Click me",
  overflow: "visible",
  width: 100,
  layoutAlign: "center",
  showRollOver: false,
  showFocused: false,
  showDown: false,
  click: function() {
    isc.say(OB.I18N.getLabel('OBAPPEX_SayHello', ['${}']));

Some special things in this template source:

Template Record

The next step is to let Openbravo know that the template exists. This is done by registering the template in Openbravo in the Template table. The template maintenance function can be found here: Application Dictionary > User Interface > Template.

Obuiapp view template record.png

Some specifics:

For a description of the other fields of the template record, see here.

Register the View Implementation

To use the view implementation in a menu need to register it in Openbravo. This is done through the Application Dictionary > User Interface > View Implementation window.

Obuiapp register view implementation.png

The client, organization, module, description and active fields are standard Openbravo fields. The other fields have more specific meanings:

If the view is implemented as static javascript then the classname and template can remain empty. The name should correspond to the class name used when calling defineClass (for the example above the name is: OBAPPEX_HelloWorldLabelView).

The view implementation can be enabled or disabled by role through the View Role Access tab. When a new view is created it is automatically enabled for all roles.

Use the view implementation in a menu entry

The next step is to add the view as a menu item. This is done through General Setup > Application > Menu.

Obuiapp register view menu.png

When creating or updating a menu select as an action the Open View in MDI action and select a view implementation in the field which is shown.

Try it!

Now log out and log in again and find the menu entry in the menu, select it. You should see something like the below:

Obexapp dynamic javascript result.png

Retrieved from ""

This page has been accessed 30,855 times. This page was last modified on 26 April 2012, at 06:26. Content is available under Creative Commons Attribution-ShareAlike 2.5 Spain License.