User Interface

Dissecting and Customizing the Toolbar Component in Openbravo ERP

Toolbar save buttonFollowing the technical blog series where we looked at the Improvements to the Tree Grid View in Openbravo, now we shall look at the internal architecture of the Toolbar in Openbravo and how other modules can add a new button to the Toolbar. The Toolbar is the User Interface component that is going to be used the most in the Application. The toolbar consists of the basic options like New,Save, Delete,export data to CSV and also some special operations like Print Order, Clone order, etc. For the list of the available toolbar buttons, see this wiki.

Where can it be found?

The base of the toolbar buttons code resides in the org.openbravo.client.application module in the ob-toolbar.js file. It is located in the following location. modules/org.openbravo.client.application/web/org.openbravo.client.application/js/toolbar

Now coming to the file ob-toolbar.js, the smartclient component isc.ToolStrip is used for the area of the toolbars and each toolbar item is a button element. For each element we define a set of properties, an icon for display and add it to the OBToolbar toolstrip. Let us analyse this using the save button as an example.

Analysing the Save Button:

SAVE_BUTTON_PROPERTIES: {
  action: function() {
    this.focus();
    this.view.savingWithShortcut = true;
    this.view.saveRow();
    delete this.view.savingWithShortcut;
  },
  disabled: true,
  buttonType: 'save',
  sortPosition: 30,
  prompt: OB.I18N.getLabel('OBUIAPP_SaveRow'),
  updateState: function() {
    var view = this.view,
      form = view.viewForm,
      hasErrors = false,
      editRow;
    if (view.isShowingForm) {
      this.setDisabled(!(form.isNew &&form.allRequiredFieldsSet()) &&
        (form.isSaving || form.readOnly || !view.hasValidState() || form.hasErrors() ||
          !form.hasChanged || !form.allRequiredFieldsSet()));
    } else if (view.isEditingGrid) {
      form = view.viewGrid.getEditForm();
      editRow = view.viewGrid.getEditRow();
      hasErrors = view.viewGrid.rowHasErrors(editRow);
      this.setDisabled(!(form.isNew &&form.allRequiredFieldsSet()) && !hasErrors &&
        (form.isSaving || form.readOnly || !view.hasValidState() || form.hasErrors() ||
          !form.hasChanged || !form.allRequiredFieldsSet()));
    } else {
      this.setDisabled(true);
    }
  },
  keyboardShortcutId: 'ToolBar_Save'
},

The main components here are:

  1. Action Method:
    1. The action method is the method that is invoked when the button is clicked. Since the toolbar item is linked to the standard view, the standard items like the view, form and the grid can be used here. For more information on these standard items, refer here. In the save method, we have invoked the save method of the OBStandardView item.
    2. updateState method:
      1. This is the method that decides whether the button is enabled for the record in form view or grid view. Again here all the components of the grid and form are available and in the code, we can even find whether the current record is in form view or grid view too. Using this we can set the Disabled property of the button to true or false. Notice that this updateState method will be called many times during the normal workflow. For example, after a field value is changed, when form view is changed to grid view and vice versa. So it is advisable to keep the code minimal here.

Now these save button properties are added to the OBToolbar using the following code

OB.ToolbarRegistry.registerButton(isc.OBToolbar.SAVE_BUTTON_PROPERTIES.buttonType, isc.OBToolbarIconButton, isc.OBToolbar.SAVE_BUTTON_PROPERTIES, isc.OBToolbar.SAVE_BUTTON_PROPERTIES.sortPosition, <b>null</b>, <b>null</b>, <b>false</b>);</pre>
<h1>Adding custom buttons to the Toolbar</h1>
<pre>

Now that we have looked at how the standard buttons are added to the OBToolbar the next step is to understand how external modules can add their own buttons to the toolbar. There is already an example present in the same module that does this. The clone button that we see in Sales orders is a good example of this. The code for adding the clone order button to the OBToolbar is present in the ob-clone-order.js file. The code is pretty simple

OB.ToolbarUtils.createCloneButton(
  'org.openbravo.client.application.businesslogic.CloneOrderActionHandler',
  null, ['186', '294'], OB.I18N.getLabel('OBUIAPP_WantToCloneOrder'));

Here, the createCloneButton is another method defined in ob-toolbar.js

// ** {{{ OB.ToolbarUtils.createCloneButton(/*String*/ actionHandler, /*Object*/ requestParams,
//        /*Array[String]*/ tabIds, /*String*/ askMsg, /*Integer*/ sortOrder, /*Boolean*/ editRecordAfterClone,
//        /*String*/ buttonId, /*Boolean*/ overwriteIfExists, /*Array[String]*/ tabIdsToAvoid }}} **
// Automatically set up a clone button for the provided tabs
// Parameters:
// * {{{actionHandler}}}:  action handler which processes and returns the cloned record
// * {{{requestParams}}}: (Optional) aditional parameters to send to the action handler
// * {{{tabIds}}}: (Optional, all tabs will be included by default) array of tabIds where
//                 this button will be shown
// * {{{askMsg}}}: (Optional, 'OBUIAPP_WantToCloneRecord' by default) Text that will be
//                 displayed when the button be pressed.
// * {{{sortOrder}}}: (Optional, 'CLONE_BUTTON_PROPERTIES.sortPosition' by default)
//                    Position in the toolbar of the clone button.
// * {{{editRecordAfterClone}}}: (Optional, true by default) If the form edit view
//                               (of the cloned record) should be opened after clone it.
// * {{{buttonId}}}: (Optional, random by default) Don't set it unless you plan to do
//                   advanced coding with this button (as, for example, overwrite it later
//                   in another place).
// * {{{overwriteIfExists}}}: (Optional, false by default) To be able to overwrite a
//                             particular existing clone button. The buttonId should
//                             match with the overwritten one.
// * {{{tabIdsToAvoid}}}: (Optional, no tabs to avoid by default) array of tabIds where
//                        this button should not be shown
OB.ToolbarUtils.createCloneButton = function(actionHandler, requestParams,
  tabIds, askMsg, sortOrder, editRecordAfterClone, buttonId,
  overwriteIfExists, tabIdsToAvoid) {
  var cloneButtonProps = isc.addProperties({}, isc.OBToolbar.CLONE_BUTTON_PROPERTIES);

  if (!askMsg) {
    askMsg = OB.I18N.getLabel('OBUIAPP_WantToCloneRecord');
  }
  if (!sortOrder) {
    sortOrder = isc.OBToolbar.CLONE_BUTTON_PROPERTIES.sortPosition;
  }
  if (editRecordAfterClone !== false) {
    editRecordAfterClone = true;
  }
  if (!buttonId) {
    buttonId = cloneButtonProps.buttonType + '_' + OB.Utilities.generateRandomString(8);
  }
  if (overwriteIfExists !== true) {
    overwriteIfExists = false;
  }

  cloneButtonProps.action = function() {
    var view = this.view,
      callback;

    callback = function(ok) {
      if (!requestParams) {
        requestParams = {};
      }
      requestParams.recordId = view.viewGrid.getSelectedRecord().id;
      requestParams.tabId = view.tabId;
      requestParams.windowId = view.windowId;

      if (ok) {
        OB.RemoteCallManager.call(actionHandler, {}, requestParams,
          function(rpcResponse, data, rpcRequest) {
            var recordIndex = view.viewGrid.getRecordIndex(view.viewGrid
                .getSelectedRecord()) + 1,
              recordsData = view.viewGrid.getDataSource().recordsFromObjects(data)[0];
            view.viewGrid.addToCacheData(recordsData, recordIndex);
            view.viewGrid.scrollToRow(recordIndex);
            view.viewGrid.markForRedraw();
            if (view.viewGrid.getEditRow()) {
              view.viewGrid.endEditing();
            }
            view.viewGrid.doSelectSingleRecord(recordIndex);
            if (editRecordAfterClone) {
              view.editRecord(view.viewGrid.getRecord(recordIndex),
                false);
            }
          });
      }
    };
    isc.ask(askMsg, callback);
  };

  OB.ToolbarRegistry.registerButton(buttonId, isc.OBToolbarIconButton, cloneButtonProps,
    sortOrder, tabIds, tabIdsToAvoid, overwriteIfExists);
};

Here, besides the logics similar to the save button, the key code to note is the OB.ToolbarRegistry.registerButton call. This is the code that will add your custom button to the OBToolbar based on the other parameters.

I have used the clone button already present in the Application to explain this, but there is another excellent example of how to do it with a detailed description here.

This concludes our brief overview of the technical architecture behind the Toolbar component in Openbravo ERP. In case you want a review of some other technical aspect of Openbravo ERP, please leave it in the comments section and we will try to do it.

Happy Working !!!

Previous post

Improvements To Tree Grid View in Openbravo

Next post

Creating virtual fields using Client Class in the Openbravo ERP Platform

No Comment

Leave a reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>