Developing Custom Process Fields

Top  Previous  Next

MoreMotion Face contains wide variety of Process Fields that can be used to collect users input. A Process Field can wrap one or more standard HTML elements and act as a single user control.

It is always possible to develop a new type of Process Field that wraps any combination of standard HTML elements and provides special behaviours.

A Process Field is two folds. The first one is the definition of the combination of the HTML elements and their mo: attributes in the Page XSL Style Sheet and the other one is a Javascript function class that provides interface and custom methods.

An Example Custom Process Field: "CurrencyBox"

Is this section we will develop an custom Process Field called "CurrencyBox". Here is the required HTML code.

  <div mo:type="CurrencyBox" mo:name="PRICE" mo:needsInit="true" mo:iValue="120 EUR"
       mo:props="currencies:'USD,EUR,YTL', onCUnitChange:MyCUHandler, isCUnitReadOnly:false">
    <input type="text" name="__Value" onchange="CBoxMgr.valueChanged(this)"/>
    <select name="__CUnit" onchange="CBoxMgr.CUnitChanged(this)"/>
    <input type="text" name="pf_PRICE" />
  </div>

Since our Process Field is an complex element that wraps more than one standard HTML elements we need to enclose them with an HTML element that can contain other HTML elements in it. Our choice is a div element. We denote that div element is a Process Field node by attaching special mo: attributes to it.

mo:type attribute defines the Process Field type. There must be an accompanying Javascript function class with the name given with this attribute. So we have to provide CurrencyBox Javascript function class.

mo:name attribute defines the name of the Process Field. A Process Field can be accessed using its name.
e.g. var f = prec.getField("PRICE");

mo:needsInit defines if the Process Field needs initialization after the page loading. We've given true as the value since our Process Field needs it.

mo:iValue defines the initial value of the element. The value of this attribute should contain both the number value and the currency unit code.  e.g."120 USD" or "200 EUR". The methods of the Javascript function class will parse this value and assign the extracted parts to the nested elements during initialization.

mo:props defines the properties of the Process Field. We've defined three properties which are:

currencies

This property defines the currency unit codes that will be available for selection. If the codes need to be given dynamically then the definition can be currencies="{/root/currencies}".

onCUnitChange

This property defines the name of the handler function to be called when the user changes the currency unit.

isCUnitReadOny

In some cases we might want the user not to change the currency unit. This property can also be defined dynamically.

The nested elements

There are three HTML element nested within the div element; The figure below shows how they are organized.

CustomPF

Here is the Javascript function class CurrencyBox that extends the ProcessField base class. Since our Process Field has a mo:needsInit attribute the class should implement the init() method.

  function CurrencyBox(node) {
 
    this.base = ProcessField;  
    this.base(node);
    this.isCurrencyBox = true;
 
    this.wrapElements = function() {
      this.celm = this.node.getElementsByTagName("select")[0];
      var elms = this.node.getElementsByTagName("input");
      for (var i = 0; i < elms.length; i++) {
        var elm = elms[i];
        if (elm.name == "__Value") this.velm = elm;
        else if (elm.name.startsWith("pf_") || elm.name == this.name) c;
      }
    };
 
    this.wrapElements();
 
    this.init = function() {
      this.fillCUnits();
      this.setValue(this.getIValue());
    };
 
    this.fillCUnits = function() {
      var opts = this.celm.options;
      opts.length = 0;
      var currs = this.props.currencies.split(",");
      for (var i = 0; i < currs.length;i++) {
        var curr = currs[i];
        if (curr) opts[opts.length] = new Option( curr,curr );
      }
    };
 
    this.setValue = function(value) {
      this.velm.value = value.before(" ","");
      PMgr.setInitialOption(this.celm, value.after(" ") );
      this.selm.value = value;
    };
 
    this.getValue = function() {return this.selm.value;};
 
    this.onValueChange = function(node) {
      this.selm.value = this.velm.value + " " + this.celm.value;
      this.setModified();
    };
 
    this.onCUnitChange = function(node) {
      if (this.boolProp("isCUnitReadOnly")) {
        this.celm.value = this.selm.value.after(" ");
        return;
      }         
      if (this.props.onCUnitChange) {
        if (this.runHandler("onCUnitChange") == false) {
          this.celm.value = this.selm.value.after(" ");
          return;
        }
      }
      this.selm.value = this.velm.value + " " + this.celm.value;
      this.setModified();
    };
  };
 
  function CBoxManager() {
    this.valueChanged = function(node) {OMgr.getEnclosingObject(node).onValueChange()};
    this.CUnitChanged = function(node) {OMgr.getEnclosingObject(node).onCUnitChange()};
  };
  var CBoxMgr = new CBoxManager();          

Explanations

Wrapping the nested elements

During the object creation wrapElements()method is called to get the handles of the wrapped HTML elements.

The handles are in our case are:

velm : handle to the input element that keeps the number value

celm : handle to the element that lists the currencies

selm : handle to the hidden field that keeps the actual value to submit

The naming conventions for naming the wrapped HTML elements and their Javascript handles

If there is only one HTML element wrapped by the Process Field then it means the value of the element is eligible for both displaying to the user and submitting to the server. In that case element name must be prefixed with "pf_" and name of the handle to it must be elm.
If there are more than one HTML elements wrapped by the Process Field then it means the value displayed to the user and the actual value received from and submitted to the server are different. In such cases usually there is only one hidden element that keeps the actual value. The name of this element must be prefixed with "pf_" and the name of the handle to it must be selm.
 
The other HTML elements are used to display/get value to/from the user in different formats. The names of these element should be prefixed with "__" (double underscores) which means their values are not significant and ignored on the server. We have also two this kind of elements and they are named as "__Value" and "__CUnit". The names of the handles to these kind of elements can be assigned freely.

 

Using a ProcessField outside ProcessBlocks

ProcessFields can be designed to be used both inside and outside ProcessBlocks. To use a Process Field outside ProcessBlocks "pf_" prefixes should not be used in the names of the elements so that they just become ordinary request parameters.

By taking into account that an element may or may not have a "pf_" prefix, the wrapElements() method detects it as follows:

if (elm.name.startsWith("pf_") || elm.name == this.name) this.selm = elm;   

 
The Fields and Methods to override

A Process Field Javascript function class should extend the ProcessField base class. This base class has the following fields and methods that can or must be overridden depending on the circumstances.

Fields

elm

The handle to the only wrapped HTML element.

selm

The handle to the hidden element that keeps the actual value

needsValueCloning

This flag that denotes the value of this field should be set additionally by calling the setValue() method after the ProcessRecord that this field resides in is cloned.        

 

Functions

getValue()

Should return the actual value. The value to return must be in the same format as the mo:iValue attribute value. 

setValue(value)

The method should parse (if necessary) the passed value and set all the wrapped HTML elements. The format of the passed value is the same as the format of the value returned by getValue() method.

validate()

This method should validate the users input. The validation errors must be notified with  notify() method. If this method is not overridden the validate() method of the base class is called to perform standard validations. Therefore, if there is nothing to validate then an empty method should be provided.

ajaxRefreshHandler(responseRecord,nodeNames,attrNames)

If this method is existing on the field object it is called by the AjaxMgr.refreshFields() and AjaxMgr.refreshBlocks() methods. nodeNames parameter should contain the names of the nodes in the ResponseRecord object that provide data for the field. attrNames parameter is supplied only by the AjaxMgr.refreshFields() method and it contains the names of the element attributes to refresh.

If this method is not provided then setValue() method of the Process Field is used which is sufficient for most of the cases. In case you need more specialized functionality for Ajax refreshing you should override this method.

An example overriding of this method exists in moremotion/face/Ajax/SuggestBox.js file.

init()

This method must be provided in case of your Process Field needs to be initialized. If the ProcessField element has a mo:needsInit="true" definition, this method is called by MoreMotion Face after the page is loaded.

clear()

This method should clear the value of the field.

appendRequestParams(acc)

This method is called before submitting the ProcessForm that the ProcessFeield is in with Ajax. The method in the base class uses the HTML form elements pointed with selm and elm handles. If your ProcessField has more than one request parameter then you  have to override this method.

An example overriding of this method exists in moremotion/face/Ajax/SuggestBox.js file.

setSubmitPrefix(prefix)

The request parameter names of the ProcessFields must have a prefix to identify their Process Blocks. This method is called before submitting (HTTP or Ajax) the ProcessForm. If your ProcessField has more than one request parameter then you have to override this method.

An example overriding of this method exists in moremotion/face/Ajax/SuggestBox.js file.

getProcessFieldNames(separator)

If your Process Field has more than one request parameter you have to override this method and return the names of the request parameters (without prefix) by delimiting each with the given separator character.

setRecordNumber(recnum)

Some ProcessFields need to know the record number that they reside in. e.g. CheckBox. The record number may change during manupulating the records of a ProcessBlock. If your ProcessField also need record number information then you have to implement this method.

 

 
Note:

The ProcesField base class has more fields and methods which are not mentioned here. Please refer to ProcessField in the MoreMotion Face API for more information.

 

Manager Classes

Process Field Javascript classes need to be instantiated before calling their methods. In an event procedure of an HTML element a definition can be made as follows:

  onchange="OMgr.getEnclosingObject(this).onValueChange();"

It is a little long, isn't it?

Therefore in our example we've provided a manager class and created a static object of it as follows.

  function CBoxManager() {
    this.valueChanged = function(node) {OMgr.getEnclosingObject(node).onValueChange()};
    this.CUnitChanged = function(node) {OMgr.getEnclosingObject(node).onCUnitChange()};
  };
  var CBoxMgr = new CBoxManager();

Now we can delegate the instantiation of the class to CBoxMgr and keep our event procedure simple as follows.

  onchange="CBoxMgr.valueChanged(this);"