Java & javascript experts

A button in the header of an Ext JS grid action column

In this post, we are going to create a button in the header of an Ext JS grid action column.

The facts

In order to display and manipulate large sets of data, there is a UI scheme that is often used by us developers: a grid to display the data, bound to a form to add or edit records.

There are several ways of doing this. For example, a toolbar above or beneath the grid:

What we are going to study here, is a completely integrated UI solution. It will include a button inside an action column header. While the cells of that column will contain the edition and deletion action icons.

That way you will have a lean and modern design, compared to the previous UI.

The issue

Ext JS grid columns already have the capability of having items in their the header via the items config :

xtype: 'grid',
  columns: [{
    items: [{
      xtype: 'button',
      iconCls: 'x-fa fa-plus',
      ui: 'default-toolbar'
   }]
  }, ...]
}

The issue here is that this way of doing it will not work for action columns xtype: 'actioncolumn'

Indeed, the items property for that kind of column is meant to set their cells icons configuration.

{
  xtype: 'grid',
  columns: [{
    xtype: 'actioncolumn',
    items: [{
      iconCls: 'x-fa fa-pencil' // config of icons in the column cells
      }, {
      iconCls: 'x-fa fa-trash'
      }]
   }, ...]
}

The solutions

  • Use a template column

The first solution would be to use a templatecolumn. This type of column easily helps us displaying an Ext JS template inside its cells.
In this case, it would really be straightforward to create the tags displaying those icons via a HTML template. The troubles we would have to face with the itemsconfig of an actioncolumn would then disappear:

{
  xtype: 'grid',
  columns: [{
    xtype: 'templatecolumn',
    items: [{
      xtype: 'button',
      iconCls: 'x-fa fa-plus',
      ui: 'default-toolbar'
    }],
    tpl: '<span class="x-fa fa-pencil"></span><span class="x-fa fa-trash"></span>'
  }]
}

Nevertheless, it might become painful to handle click on each icon individually, the icons enabled/disabled state or let them have their own tooltip all along the grid rows.
The actioncolumn component, via isActionDisabled, handler() or getTip(), facilitates those kind of interactions in a dynamic way. To get to this, you would have to manually code those different behaviours for a column of type templatecolumn.

  • Create a plugin for the action column

The second solution, which is cleaner and more modular, would be to create a plugin that may be applied to any actioncolumnof our application. This plugin will be provide a way to configure a button located in the header.
As a consequence we will keep all the features of the default actioncolumnwhile getting to the expected result.

  • How to create the plugin

An Ext JS class which extends Ext.AbstractPlugin:

Ext.define('MyApp.plugin.ActionColumnHeaderButtonPlugin', {
  extend: 'Ext.AbstractPlugin',
  alias: 'plugin.actioncolumnheaderbuttonplugin',
  actionColumnHeaderId: null,
  config: {
    buttonConfig: null
  }

The alias will let us easily identify the plugin we want to add later to our column.
We give it a property to also identify our button’s container inside the target column header.
Finally, we create a button configuration which will be implemented when we declare our plugin on the column.

  • How to initialize it

As for any plugin, by overriding the abstract function init(), we can get the component on which the plugin is set and interact with it.
We link the plugin to the component on which it is set.

this.setCmp(cmp);

Then, we listen to the viewready event of the grid. (We will talk later about what is happenning when this event is fired).

var ownerGrid = cmp.getView().ownerGrid;
ownerGrid.on('viewready', this.onViewReady, this);

Then we create a unique identifier based on the id of the column itself.

this.actionColumnHeaderId = cmp.getId() + '-actioncolumnheaderCt';

And finally, we inject a HTML block having the identifier created earlier as its id, inside the header of the column. That way we will easily have access to it among the different functions exposed by the plugin.

cmp.setText('<div id="' + this.actionColumnHeaderId + '"></div>;');

Below the complete code of our plugin init()function:

init: function (cmp) {
    this.setCmp(cmp);
    var ownerGrid = cmp.getView().ownerGrid;
    ownerGrid.on('viewready', this.onViewReady, this);
    this.actionColumnHeaderId = cmp.getId() + '-actioncolumnheaderCt';
    cmp.setText('<div id="' + this.actionColumnHeaderId + '"></div>');
}
  • Let’s add our button at the creation time of the grid

Once the grid is rendered, it fires a viewready event, which we make our plugin listen to.
When this event is fired, we create the button from the buttonConfig we set previously:

var button = Ext.create('Ext.button.Button', this.getButtonConfig());

Then we just inject this component inside the container we added at the plugin initialization:

button.render(view.el.down('#' + this.actionColumnHeaderId));

The complete code of our onViewReady() function:

onViewReady: function (view) {
  var button = Ext.create('Ext.button.Button', this.getButtonConfig());
  button.render(view.el.down('#' + this.actionColumnHeaderId));
}
  • Let’s set the plugin of our column
columns: [{
  xtype: 'actioncolumn',
  plugins: [{
    ptype: 'actioncolumnheaderbuttonplugin',
    buttonConfig: {
      iconCls: 'x-fa fa-plus',
      handler: 'onAddButtonClick'
    }
  }],
  items: [{
  iconCls: 'x-fa fa-pencil'
  }, {
  iconCls: 'x-fa fa-trash'
  }]
}]

This way, we will be able to have access to every property and function available for the differents items of the actioncolumn, while setting a button in its header.

You can get the whole (and a bit adjusted) code and its result by going to this fiddle.

Rowediting plugin customization in ExtJs

The facts

Among the plugins that the Ext JS ecosystem has to offer, one is a particularly useful one: the rowediting plugin. This plugin extends the functions of the grids by individually allowing the edition of each line as a whole.

This is an economic and user-friendly alternative to cellediting, the development of an edition form or the usage of the very restrictive property grid.

But how to customize this plugin? What about for instance modifying the layout of the action buttons by replacing text with icons?

The plugin is not open to such customization, this requires an override.

Implementation should therefore be done following these steps:

  • setting up the store, the grid and the different functionalities (adding items into the store for example)
  • applying the rowediting plugin to the grid
  • customizing of the plugin

As often in software development, that seems straightforward, but this required extra tricks.

What didn’t happen as expected:

  • a problem displaying the plugin buttons.
  • if the grid is empty, and items are manually added in its store, the edition of some lines is an issue since the buttons are partially hidden by the grid header.

Défaut d'affichage des boutons

 

Our solution

We were not the first trying to achieve this, the discussion here even led to a ticket on the Sencha side. The following thread on the Sencha forum was very helpful to solve the problems we faced: https://www.sencha.com/forum/showthread.php?305665-RowEditing-Buttons-not-visible

It only required the addition of a minHeight on the grid and everything was fine.

The customization of the buttons requires:

  • an override of the class constructor :  Ext.grid.RowEditorButtons
  • adding properties on the Ext.grid.RowEditor class override

Note that the useButtonText property is used to indicate that we don’t want to use the text, we want to display icons instead.

The saveBtnIconCls and cancelBtnIconCls properties are used to populate the iconCls property of the targeted button. These buttons are customizable as shown in the example below:

plugins: {
    rowediting: {
        clicksToMoveEditor: 1,
        autoCancel: false,
        useButtonText: false,
        saveBtnIconCls: 'fa fa-check green-color',
        cancelBtnIconCls: 'fa fa-times red-color'
    }
}

And the final rendering is:

Rowediting with buttons customization

The source code is available on our github here

Internationalization & Localization with Sencha Ext JS

Ext JS offers internationalization features out of the box, and the framework is easily extensible to cover additional requirements with a small amount of effort. Ext JS comes bundled with a localization package that supports over 40 languages ranging from Indonesian to Macedonian, and it’s easy to implement.

In this article, we’ll review solutions for handling some special circumstances and requirements that we’ve offered to our customers at Jnesis, but let’s first sum up what we know about internationalization.

The main focus is giving users the option to choose their preferred language for the application. This feature requires the application to be “translatable”, a feature that is often expressed with the words “i18n” or “internationalization.” “Translatable” meaning not only the possibility of translating into another language, but also considering all of the technical requirements attached to it.

Sencha provides a good internationalization and localization solution for the most common situations. It can also be extended, taking into consideration specific requirements of an enterprise environment.

How to Localize Ext JS Applications

You will find details on the Sencha recommended way to localize your applications in the Sencha localization documentation. There are also some additional recommendations in community member Saki’s blog post. In the following post, we will address how to:

  • Write language files (singleton or override) directly in the application, so they can be integrated afterwards into the production build
  • Generate, via Sencha Cmd, as many production builds of the same application as there are languages set in the configuration
  • Reload the entire application each time the language is changed
  • Easily load additional resources before starting the application
  • Use different languages in different parts of the application

 

Externalization of Sources

Enterprise developers view internationalization differently than the general developer because of their unique needs. Before choosing to adopt a framework and extend it, they must carefully assess their users’ actual needs. (Sencha Day 2015 presentation by Vincent Munier, Technical Manager at Jnesis.)

Companies of a certain size generally come to the same conclusion: multilingual management is a concern that spans beyond the development framework. Multilingualism is a general concern for large organizations, because their business processes require different languages at different stages of product development and company evolution. In some cases, where language requirements are changing regularly, translations should be done outside the application, mostly managed by third party software and loaded into the application via one or several web services.

The advantages of this approach are:

  • Changing the language doesn’t require the developer to rebuild the application – the change is applied instantly after reloading.
  • Translation doesn’t necessarily have to be done by the same people who write the code.

It could be argued that entrusting translation to people who do not know the code or how a JavaScript file is coded could pose some problems. However, it is perfectly conceivable to use a translation tier tool to translate content, one that can be used by non-programmers, and to load the data into the application.

As mentioned above, the official Ext JS solution (which can be found here: online localization guide) doesn’t allow any tier tools to be natively implemented.

It is important to introduce good practices for building internationalization into applications at the beginning of a project to avoid a painful refactoring phase.

The Elegant way

Let’s first follow Sencha localization guide and insights from Saki’s blog post: loading language data before the application main screen is built.

This solution is close to what was possible before Ext JS 5: adding dependencies to specific resources in the application’s “index.html” file.

 

To achieve that, let’s first store the localization values as variables of a “singleton” class, like that:

 

This will allow them to be accessed from anywhere in the application. How ? As simply as typing Jnesis.Labels.button or Jnesis.Labels.title

The advantage of this approach compared to the use of “override” (i.e what is recommended by Sencha) is that localized values will all be in one file, and easier to use, because each element is a variable, as explained above.

Once that is done, we simply have to override the default language file (« Jnesis.Labels ») with the translations in the selected language.

There are two options:

  • loading the data via web-services:

Where the server JSON response would look like this:

 

You then would have to parse data and override your default localization singleton with those values:

 

  • loading overriding Ext JS classes:

 

Then using Ext.Loader.loadScript function to process it and loading your Main view when it’s done.

 

We would then want to use the native inheritance mechanism to our advantage by defining a new property (“localized”, for example) in the base component, and generate the attribute to be translated. This attribute would be a simple JavaScript object with keys and values (remember the Jnesis.Labels.button and Jnesis.Labels.title from above).

Values can be updated only when the object is read, by defining the keys as strings:


Once that is done, you just have to override the “initComponent” method of the base class and evaluate the keys, so they can be used in any Ext JS component:


This solution works at any hierarchical level in the component’s declaration (container configuration or item configuration) in a transparent way.

To go further

The latter solution works with both Ext JS 5 and 6 (Classic toolkit). You will find below examples of possible adaptations made by Jnesis in the past:

  • Deporting the loading functions and their associated processing in a specific class like “Ext.mixin.Mashup”. It ensures that every necessary file or JSON data from web-services are loaded before the class to which the “mixin” is applied is stored in memory.
  • Ensuring compatibility with the Ext JS 6 Modern Toolkit. It has no “initComponent” function. Another option can be found in the “config” property and in the generation of the associated “apply” method.
  • Loading and interpreting the translation variables in a dynamic way without having to reload the entire application.

Please leave comments below, so we can share more information on these improvements.

The source code is available on our github here