---------------- How to develop a primitive editor plugin in AXEL  ----------------

St├ęphane Sire
Last update: 2013-09-25

Plugin functionalities

Primitive editor plugins (or plugins) roles are :

* to generate a static view of their content into the DOM (what we call "the handle")
* to generate a view for editing (what we sometimes call "a device", this can be as simple as an input field)
* to convert data from and to XML (respectively XML loading and XML linearization)
* to manage user interaction with the view(s) (e.g. switching from static to editing view)

Plugin code should respect some conventions described in this document. They should implement a minimal API. A part of this API is provided as native methods with a default implementation which is inherited by the plugin per-construction.

Each plugin registers itself with the $axel.register method to the AXEL engine with the same identifier that is used to insert it inside a template with an <xt:use types="identifier".../> XTiger element.

While reading this document we recommend that you have a look at one or more plugin source files in the src/plugins folders (e.g. src/plugins/text.js).
Plugin API

The Plugin API is divided into different categories of methods :

- life cycle methods
- native construction methods
- native service methods
- other methods

Life cycle methods ("onGenerate", "onInit", "onAwake", "onLoad", "onSave") must be implemented to manage plugin instantiation and to read and write XML data to a target document.

Native construction methods are called by the AXEL processor to create the plugin instances. Three different execution flows lead to the creation of plugin instances. The first one is during template parsing and generation phases (i.e. when calling $axel.transform function to generate an editor from an XTiger XML template). At that time some private methods of the plugin API are called to read the plugin parameters and the plugin default content from the XTiger host node (i.e. the corresponding xt:use or xt:attribute element). The second flow leading to the creation of plugin instances is at interaction time, when the user click on a repeater to generate a new document slice. The third flow is when loading some XML content generates a new document slice. These last two flows use a special seeding mechanism to reconstruct the plugin instance initial state from the state that was saved during the first flow. 

Native service methods define some basic services common to all plugins. They give access to common state information such as the document the plugin instance belongs to, the plugin parameters, default content, optionality status, and so on.

The plugin registration process adds a default implementation of all the native methods to the plugin class unless you overwrite them.

Finally all the other methods are up to the plugin developer. It is however advised to look at existing plugins and to try to define similar methods with the same name so that more universal filters can be developped.

The filter API allows to filter any plugin method using a delegation pattern. However you should be careful when creating filters to call the original plugin method using the remapping mechanism so that it get executed.
Plugin registration

The $axel.plugin.register method takes as parameter a plugin identifier, an option hash, a hash with a list of parameters with their default values and the plugin methods. 
$axel.plugin.register( 'identifier', { filterable: true, optional: true } | null, // option hash { key : 'value' }, // parameter hash _Editor // method hash );
The option hash declares a set of flags that defines whether the plugin is filterable (filterable=true) and wether it supports the basic XTiger option attribute of the XTiger host element (optional=true). The parameter hash defines the default values for the parameters which can be declared on the <xt:use> or <xt:attribute> XTiger elements inside the 'param' attribute. The plugin methods must be stored into a hash with the following structure : - life cycle methods are stored under first level keys of the hash ("onGenerate", "onInit", "onAwake", "onLoad", "onSave") - overwritten native plugin methods are stored under a first level "api" key that must contain a hash with the methods - all other methods are stored under a first level "methods" key that must contain a hash with the methods In all the plugin methods the this object will refer to a plugin instance. The plugin instance will be automatically created by the AXEL engine, so that you do not need to define the plugin constructor function. Note that it is always possible to use a closure to define more plugin private variables and methods. See the existing plugins.
Life cycle methods

There are 5 life cycle methods that must be implemented by each plugin class.

The plugin instance must create its HTML markup inside the <div> container passed as parameter. The method must pick up and return one HTML node from its representation that we call the handle. AXEL will add a special property to the DOM node (an expando property) of the handle to point to the Javascript plugin instance managing the handle.

When calling onGenerate the plugin instance has just been initialized from its host XTiger XML element, thus you may read the plugin default content or some plugin parameters with the getDefaultData or getParam methods. In general the purpose of the onGenerate method is only to create the HTML representation and nothing else. You should postpone any other operation to the onInit or onAwake method calls.

The plugin instance should initialize its content model with the default content provided from the template. It can also perform some specific initializations if it manages the XTiger option attribute which is set to "set" or "unset" by the template (note that alternatively you can "inherit" a default optional module when registering the plugin). Finally it can also make some adjustments to its view based on the values of the parameters which can be read with the getParam method.

Note that the final parameter (aReapeater) is defined if and only if the plugin instance is beeing created by repeating a slice in a repeater, which occurs either if the user has clicked on a "plus" control, or if s/he has loaded XML data from a file. When aRepeater is not defined, the plugin instance is beeing created from the template during the initial template transformation.

Typical behavior : initializes the handle with the default content from the template.

The plugin instance should register its event listeners in this method. This phase is disctinct from onInit to enable selective filtering.

Typical behavior : adds a 'click' event listener on the handle to trigger content editing.


The plugin instance should read the current point of the DOM data source and update its content with it.

Typical behavior : reads data and copies it to the handle, replacing any existing data.

The plugin instance should generates its XML content inside the DOM logger from its current state.

Typical behavior: copies the handle content as text in the DOM logger.
Native construction methods

Those methods should be considered as private inherited method. We just discuss them for documentation purpose although you can perfectly skip that section.

Note that these native methods create some instance properties that should be considered as reserved names (_param, _content, _option, _seed and _document, _key, _handle, _isModified).

The plugin instance parses its XTiger host element. This is called right after a plugin instantiation during the initial template transformation.

The default implementation initializes the default content (which can be read with getDefaultData), reads the optional option attribute (which can be read with getOption) and initializes the parameters (which can be read with getParam).

Note that the default _parseFromTemplate uses some conventions exploited by _parseFromSeed and makeSeed.

The plugin instance parses its seed. This is called after a plugin instantiation when repeating a slice in a repeater.

The seed is a temporary data structure that contains some information to instantiate a new plugin and which is created with the makeSeed method.

Returns the seed that would allow to create a new plugin instance. The seed MUST be an array where the element at index 0 is the factory method used to instantiate the plugin.
Native service methods

These methods are common to all plugins and have a default implementation unless overwritten when registering the plugin class.

Returns the parameter's value set on the xt:use (or xt:attribute) XTiger host element or its default value or undefined if it does not exist.

Returns a unique identifier associated with each plugin instance.

Returns the document the plugin instance belongs too.

Returns the DOM node that was identified as the handle by the plugin onGenerate method.

Returns the value of the optional "option" attribute that was set on the XTiger host element associated with the plugin instance or undefined. 

Note that the default native plugin methods does not manage the "option" attribute. You can however set the optional flag to true when registering a plugin class to inherit a default support (see below).

Returns true if the plugin instance supports the XTiger option attribute (option="set|unset") that makes the content optional.

Returns false by default.

Returns true if this plugin instance can receive focus from the keyboard manager. If this is the case, the plugin must implement a focus and an unfocus methods.

Returns false by default. 

Returns true if the content has been modified and is different from the default content.

Sets the modification state of the plugin's content. It is usually set to true if the content is no more equal to the default content, and false otherwise.

The plugin should take focus and switch to the editing view. Does nothing by default.

The plugin SHOULD release focus and switch to the static view. Does nothing by default.
Other frequently used methods

The following methods have been found useful when implementing different types of plugins. They are documented as you can use the same methods in similar cases, this will simplify the creation of universal filters. Some of these methods will probably be integrated into the native plugin API in future releases.

Sets the plugin instance content and configure the static view to display it. For instance, for a 'text' plugin, sets the data as the handle's text content.

Returns the current plugin instance content.

The plugin instance should switch to editing mode. For instance, for a 'text' plugin, grabs the corresponding device to display a text entry field instead of the handle.

The plugin instance should release any grabbed device and update its content with the current user input. It usually calls an update method for the last part.

The plugin instance should update its content with the new data. It usually checks the new data is different from the current content and call _setData, or call clear if the new data is empty or turns back to be the default content. In addition it usually sets itself (autoselection) if it is optional.

The plugin instance should resets it's content to the default content. It usually also unset itself if it is optional.
Methods of the native optional module

The default optional module is "inherited" if you set the optional flag to true when registering the plugin class. It adds a checkbox in front of the handle in the static view and adds some methods to manage optionality. 

Returns true if the plugin instance is optional and if it is checked. For instance this tells if the content should be output or not when saving XML content.

Checks the option checkbox and propagates change to the ancestor's repeaters.

Unchecks the option checkbox.
Plugin file skeleton

The code below can be used as a template for writing a new plugin.

The this object refer to the plugin instance in all the plugin methods below. The plugin instance will be automatically created by the AXEL engine, so you DO NOT NEED to define a plugin constructor function.

(function ($axel) { // you may use the closure to declare private objects and methods here var _Editor = { //////////////////////// // Life cycle methods // //////////////////////// onGenerate : function ( aContainer, aXTUse, aDocument ) { var handle; return handle; }; onInit : function ( aDefaultData, anOptionAttr, aRepeater ) { }, // Awakes the editor to DOM's events, registering the callbacks for them onAwake : function () { }, onLoad : function (aPoint, aDataSrc) { }, onSave : function (aLogger) { }, //////////////////////////////// // Overwritten plugin methods // //////////////////////////////// api : { isFocusable : function () { return true; }, // Request to take focus (from tab navigation manager) focus : function () { }, // Request to leave focus (fro tab navigation manager) unfocus : function () { }, }, ///////////////////////////// // Specific plugin methods // ///////////////////////////// methods : { } }; $axel.plugin.register( 'type', { filterable: true, optional: true }, { key : 'value' }, _Editor ); }($axel));