Localizing an ExtJS application is a hard task.
Structure of the application (client-side)
The Viewport is a specialized container representing the viewable application area (the browser viewport), it renders itself to the document body, and automatically sizes itself to the size of the browser viewport and manages window resizing.
LIME’s viewport uses the border layout, in the image below you can see how the viewport is structured.
This is the main view to represent a single button which allows the user to mark elements inside the document. Keep in mind that a TreeButton is just and ExtJS panel with some customization and thus needs to be divided into two parts: an always visible part (docked items) and a hide-able part (simple items or just items). Each TreeButton is structured as follows:
- Docked Items (Ext.Toolbar)
- Expander (Ext.Button)
- Name (Ext.Button)
- Children (Ext.Container)
- Widgets (Ext.Container)
- Docked Items (Ext.Toolbar)
Expander: is a real button and, if clicked, expands or collapses the children of the button which belongs to.
Name: this is the core of each TreeButton. If clicked performs a marking action on the currently selected element inside the editor and, if specified, creates a (or shows an already existing) widget. Plus, if children are present, shows the children container.
Children: this container holds all the children of the containing button which can contain whether other TreeButtons or other components. But why a container? Hiding a panel is computationally heavier than just hiding a whole container. In the first case a
hide() call is made for each panel we want to hide and a bunch of div elements must be deleted from the DOM while in the second case a single call is made and just one div element is deleted but the visible result is exactly the same. Plus using separated containers for the children and the widgets we’re able to isolate the first ones from the others.
Widgets: this is also a container but holds some widgets which are nothing more than ExtJS components. This way it is possible to render textfields, forms, images and even video or audio elements. The name “widget” comes from the powerful Ext method
Ext.widget() which can be used to instantiate a component on the fly.
This is the main menu for marking elements and is rendered through an Ext.Panel which contains other components. The first version of LIME provided a standard Ext.TreePanel for this purpose but after many failed attempts to use it for what we needed we decided to implement ourTreePanels inside the menu customizing the Ext.Panel view. Each button in the menu is a simple panel with some improvements needed to simulate a tree. A single button is called TreeButton and each of them includes an Expander (that is invisible on the leaves) and a Name (which represents the name related to that button). Both the Expander and the Name are Ext.Button. You might wonder why we used panels instead of a ready-to-use class from Ext. There are many reasons:
- TreePanels are implemented using an HTML table, not many div elements
- Each node is represented as a tr/td inside the table and when a node is hidden the related tr/td is removed from the DOM preventing the application to perform actions on hidden nodes
- It is not possible to append items (Ext components) to Ext.data.NodeInterface nodes since they are not components themselves and usingExt.renderTo is a less than ugly way to accomplish it
Using panels provides a complete customizable interface that allows to append any kind of component we want. This way we can easily add widgets to, for example, request to the user to insert particular data (a name, a date, etc.). To better interact with the view each TreeButtonprovides useful methods to show/hide children buttons, show/hide and create/destroy widgets and so on. See above for more details aboutTreeButtons.
Keep in mind that since the MarkingMenu and its TreeButton children are heavily linked it might be possible to integrate everything in a single customizable view provided as an ExtJS plugin.
This controller is responsible for the interaction with the marking buttons and for the initialization of the whole related view. The marking buttons (TreeButton) are built dinamically from the configuration files of the language plugin in use. There are some core methods which are reponsible for building the necessary components (Important: keep in mind that all the necessary data to create the buttons is directly taken from theLanguagesPlugin store!):
buildButtonsStructure(): retrieves the data from the store and start the creation process
getButtonConfig(name): scans the retrieved data and gets all the necessary configuration properties for the button with the name given as argument
createButton(name): uses the above method to get the configuration for the button (which name is given as argument) and instantiate it as an actual TreeButton. Then, if children are specified in the configuration, recursively creates other TreeButtons.
Each created button has its own id based on its own name. For example if n buttons with the same name (e.g. “preface”) exist the first one will have an id equal to preface0, the second will have it equal to preface1 and so on up to prefaceN with N = n-1. This is necessary to link each marked element to the button that was used to mark it. For this purpose the property
buttonsReference is fundamental.
Other methods are responsible for showing the children or the widgets of a given button. The behavior of this methods is based on two particular configuration properties (in the language plugin):
- sameLevelExpand: if there’s a hierarchy of marked elements which contains an element A inside of an element B and the button related to A is at the same level than the one related to B, both A and B button will be “expanded” if the A element is selected inside the editor and this property is set to true. Otherwise only the button related to the most specific element will be expanded.
- leaveExpanded: if true don’t collapse all the other buttons before expanding new ones.
Example: Structure of the elements
- — B
- — A (selected)
Value of the sameLevelExpand property:
true: buttons A and B are expanded
false: only button A is expanded
Notice: when a button is “expanded” it means its children (if there is any) are shown and the button is made visible to the user which means that if the button is nested inside a hierarchy all its ancestors are expanded too. Otherwise the button would remain invisible!
This controller manages the progress window which is used in all application.
The controller is listening of three application events which other controllers can fire if they need to show a progress window.
- progressStart, shows the progress window with the possibility to specify the title and the initial progress
- progressUpdate, updates the progress bar with the possibility to specify the loading text
- progressEnd, hides the progress window
The initial progress which can be passed on firing of progressStart event is a simple object which has two properties:
- text, the text to show inside the progress bar
- value, is a number between 0 and 1 that specifies the progress of the bar
This table represents all the interactions between the controllers according to the events fired by the application (global events). An ideal world would be the one where everything is completely independent but still many methods called in some controllers depends on other methods in other controllers (using
getController() method) which could lead to unexpected errors. Keep in mind that events related to single views are not reported since they are only used locally to those views.
(to be continued…)
This is a generic class containing utility methods that couldn’t be placed into the other custom classes.
This class contains informations about opened document.
This class contains several DOM utilities used in all application.
This class contains the static configuration, only constants.
This class contains methods used to interpret languagesPlugins configuration.
This class is the client side of parsing process and contains callbacks and utilities for document parsing.