3.3 Forms Developer Guide

From alfrescowiki

Jump to: navigation, search


NOTE: This document is a Developer Guide for the Forms Engine in Alfresco 3.3, for the latest documentation click here.

Contents

Introduction

This page describes the architecture and design of the Forms Engine and provides the relevant details to allow customizations to be implemented and plugged in.

Architecture

The Forms Engine consists of the following high level components:

High Level Forms Architecture.jpg


Before going into detail about each component it's probably worth examining the sequence of events for the generation and persistence of a form. The diagram below shows the sequence of events when generating a form for an item.

Form Generation Sequence.jpg


  1. The browser requests a page that contains the Form UI Component.
  2. The Form UI Component uses the ConfigService to look for XML form configuration for the item requested.
  3. If configuration was found for the item a list of fields to be displayed is determined and passed to the formdefinitions REST API, if no configuration is found the REST API is called without any parameters.
  4. The formdefinitions REST API calls the JavaScript FormService.
  5. The JavaScript FormService calls the Java FormService. To process the request an appropriate FormProcessor for the item "kind" is located and asked to generate a form definition. During this process one or more Filters may be called to further process the generated form. The REST API receives the form definition and generates a JSON representation.
  6. The Form UI component takes the JSON form definition and combines it with the form configuration retrieved earlier to produce a Form UI template model. The FreeMarker template is then processed to produce the HTML/JavaScript output that is sent back to the browser. The browser executes the returned HTML/JavaScript which instantiates the Forms Runtime object used to manage the generated form.


At this point the form is ready for the user to interact with, as they do the Forms Runtime constantly checks the validation rules enabling and disabling the submit button appropriately. The diagram below shows the sequence of events that occur when the user submits the form.

Form Persistence Sequence.jpg


  1. The user submits the form which triggers the Forms Runtime to send the form data to the web tier service proxy.
  2. The web tier service proxy passes the request onto the formprocessor REST API.
  3. The formprocessor REST API packages the form data and calls the JavaScript FormService.
  4. The JavaScript FormService calls the Java FormService. To process the request an appropriate FormProcessor for the item "kind" is located and asked to persist the form data. During this process one or more Filters may be called to further process the form data. The REST API then constructs an appropriate response.

Item Kind and Item Id

The forms engine has been designed to be generic and to allow the generation and persistence of forms for any type of data, the notion of an "item" is used to achieve this. An item has a kind and an id, this concept is key to the whole forms architecture.

The item kind, as the name suggests, describes the type of thing the form is for, for example an Alfresco node.

The item id represents a unique identifier for the kind of item the form is for, in the case of an Alfresco node, this would be a NodeRef.

As you'll see later on the item kind is used to locate an appropriate FormProcessor, which are used to generate and persist forms. This approach provides a pluggable architecture allowing other kinds of forms to be easily integrated.

Form UI Component

The Form UI component is responsible for requesting the form definition from the repository's FormsService and together with client side configuration, produce a forms runtime based standard HTML form, as shown in the diagram below.

Form UI Component.jpg

Usage

The Form UI component is a presentation web script that executes as a Spring Surf component in the web tier, as such it can be placed in a template via a region tag, for example:

<@region id="edit-metadata" scope="template" />

As with any Spring Surf component it is bound to the page using an XML file.

The Edit Metadata page in Share uses the form UI component, it's component binding is shown below as an example:

<component>
   <scope>template</scope>
   <region-id>edit-metadata</region-id>
   <source-id>edit-metadata</source-id>
   <url>/components/form</url>
   <properties>
      <itemKind>node</itemKind>
      <itemId>{nodeRef}</itemId>
      <mode>edit</mode>
      <submitType>json</submitType>
      <showCaption>true</showCaption>
      <showCancelButton>true</showCancelButton>
   </properties>
</component>

Being a presentation web script the form UI component has a URL to allow it be called and referenced in a component binding, it's URL is "/components/form". The form UI component accepts several properties, these can either be supplied by the component binding properties or via the URL. Property can also be populated at runtime, the example above shows how a "nodeRef" URL argument can be used as the "itemId" component binding property value.

The properties the form UI component supports are listed and explained below.

  • itemKind: The "kind" of item the form is for, the only supported kind currently is "node".
  • itemId: The identifier of the item the form is for, this will be different for each "kind" of item, for "node" it will be a NodeRef.
  • formId: The form configuration to lookup, refers the id attribute of the form element. If omitted the default form i.e. the form element without an id attribute is used.
  • mode: The mode the form will be rendered in, valid values are "view", "edit" and "create", the default is "edit".
  • submissionUrl: Provides a URL to submit the form to, if omitted the default URL returned from the FormService is used.
  • submitType: The "enctype" to use for the form submission, valid values are "multipart", "json" and "urlencoded", the default is "multipart".
  • destination: Provides a destination for any new items created by the form, when present a hidden field is generated with a name of alf_destination.
  • redirect: Provides a URL to redirect to after the form has been submitted. This only works for form submissions using the "multipart/form-data" encoding format. JSON form submissions will return the redirect parameter as part of the response so clients can perform the redirect is desired. When present a hidden field is generated with a name of alf_redirect.
  • showCaption: Determines whether the caption area above the form is displayed, by default this contains the "* Required Fields" message. If omitted the caption is not shown.
  • showCancelButton: Determines whether the Cancel button is displayed. If omitted the Cancel button is not shown.
  • showResetButton: Determines whether the Reset button is displayed. If omitted the Reset button is not shown.

Execution

The form UI component behaves like any other web script, it's JavaScript is executed first followed by the rendering of it's FreeMarker template.

The form UI component's JavaScript (form.get.js) is fairly complex. It's first job is to use the Config Service to retrieve the form configuration (this is fully explained in the next section), from that configuration a list of fields for the kind of item the form is for is determined. This list of fields (if there is one) together with the list of "forced fields", the item kind and item id is POSTed to the formdefinitions REST API. The REST API responds with a JSON object representing the form definition for the item i.e. the list of fields, their data types, their multiplicity, their constraints etc, the form definition structure is fully explained in a later section.

The form configuration initially retrieved (if present) is then essentially combined with the form definition retrieved from the REST API and the properties of the form ui component to produce the model the FreeMarker templates use to generate the form. The processing that takes place determines the following:

  • The mode the form is in
  • The URL to submit the form to
  • The format of the form data to submit i.e. "multipart/form-data" or "application/json"
  • Which buttons to display
  • The label to use for each field
  • Which control, represented by a FreeMarker template, to use for each field
  • The constraints each field has
  • The current value of each field

The model produced for a fairly simple form for an Alfresco node instance is shown below.

{
   "mode" : "edit", 
   "method" : "post",
   "enctype" : "multipart/form-data",
   "submissionUrl" : "/share/proxy/alfresco/api/node/workspace/SpacesStore/db8df439-c499-4b5b-8f87-1c8f23f2797c/formprocessor",
   "showCancelButton" : false,
   "showCaption" : false,
   "showResetButton" : false,
   "arguments" : 
   { 
      "itemId" : "workspace://SpacesStore/db8df439-c499-4b5b-8f87-1c8f23f2797c",
      "itemKind" : "node"
   },
   "structure" : 
   [ 
      { 
         "id" : "",
         "kind" : "set",
         "label" : "Default",
         "template" : null,
         "appearance" : null,
         "children" : 
         [ 
            { 
               "id" : "prop_cm_name",
               "kind" : "field"
            },
            { 
               "id" : "prop_cm_creator",
               "kind" : "field"
            },
            { 
               "id" : "prop_cm_modifier",
               "kind" : "field"
            }
         ]
      } 
   ],
   "fields" : 
   { 
      "prop_cm_creator" : 
      { 
         "configName" : "cm:creator",
         "control" : 
         { 
            "params" : {  },
            "template" : "/org/alfresco/components/form/controls/textfield.ftl"
         },
         "dataKeyName" : "prop_cm_creator",
         "dataType" : "text",
         "description" : "Who created this item",
         "disabled" : true,
         "id" : "prop_cm_creator",
         "kind" : "field",
         "label" : "Creator",
         "mandatory" : true,
         "name" : "prop_cm_creator",
         "repeating" : false,
         "type" : "property",
         "value" : "gavinc"
      },
      "prop_cm_modifier" : 
      { 
         "configName" : "cm:modifier",
         "control" : 
         { 
            "params" : {  },
            "template" : "/org/alfresco/components/form/controls/textfield.ftl"
         },
         "dataKeyName" : "prop_cm_modifier",
         "dataType" : "text",
         "description" : "Who last modified this item",
         "disabled" : true,
         "id" : "prop_cm_modifier",
         "kind" : "field",
         "label" : "Modifier",
         "mandatory" : true,
         "name" : "prop_cm_modifier",
         "repeating" : false,
         "type" : "property",
         "value" : "gavinc"
      },
      "prop_cm_name" : 
      { 
         "configName" : "cm:name",
         "control" : 
         { 
            "params" : {  },
            "template" : "/org/alfresco/components/form/controls/textfield.ftl"
         },
         "dataKeyName" : "prop_cm_name",
         "dataType" : "text",
         "description" : "Name",
         "disabled" : false,
         "id" : "prop_cm_name",
         "kind" : "field",
         "label" : "Name",
         "mandatory" : true,
         "name" : "prop_cm_name",
         "repeating" : false,
         "type" : "property",
         "value" : "test.txt"
      }
   },
   "constraints" : 
   [ 
      { 
         "constraintId" : "MANDATORY",
         "event" : "keyup",
         "fieldId" : "prop_cm_name",
         "params" : "{}",
         "validationHandler" : "Alfresco.forms.validation.mandatory"
      },
      { 
         "constraintId" : "REGEX",
         "event" : "keyup",
         "fieldId" : "prop_cm_name",
         "params" : "{}",
         "validationHandler" : "Alfresco.forms.validation.nodeName"
      }
   ],
   "data" : 
   { 
      "prop_cm_creator" : "gavinc",
      "prop_cm_modifier" : "gavinc",
      "prop_cm_name" : "test.txt"
   }
}

The web script template (form.get.html.ftl) is then processed, it's passed the model shown above as the form model object. It first determines whether an error has occurred, displaying the error message if one has. If there is no error and the form model object exists a check is made for any configured custom templates for the current mode, if present the custom template is "included". If no custom template is defined the default rendering is executed, this consists of iterating around each set and field and "including" the FreeMarker template for each one.

The default template (form.get.html.ftl) is shown in it's entirety below. Note that a lot of the actual rendering is performed by FreeMarker macros defined in a file named form.lib.ftl, these should be used by custom templates whenever possible as they will then also benefit from any changes made in future releases.

<#import "form.lib.ftl" as formLib />

<#if error?exists>
   <div class="error">${error}</div>
<#elseif form?exists>
   <#assign formId=args.htmlid + "-form">
   <#assign formUI><#if args.formUI??>${args.formUI}<#else>true</#if></#assign>
      
   <#if form.viewTemplate?? && form.mode == "view">
      <#include "${form.viewTemplate}" />
   <#elseif form.editTemplate?? && form.mode == "edit">
      <#include "${form.editTemplate}" />
   <#elseif form.createTemplate?? && form.mode == "create">
      <#include "${form.createTemplate}" />
   <#else>
      <#if formUI == "true">
         <@formLib.renderFormsRuntime formId=formId />
      </#if>
      
      <@formLib.renderFormContainer formId=formId>
         <#list form.structure as item>
            <#if item.kind == "set">
               <@formLib.renderSet set=item />
            <#else>
               <@formLib.renderField field=form.fields[item.id] />
            </#if>
         </#list>
      </@>
   </#if>
<#else>
   <div class="form-container">${msg("form.not.present")}</div>
</#if>

Form Configuration

The ConfigService is used to determine the look and feel of the form for the requested item. The configuration determines what fields are shown, what control is used for each field, what validation handlers to use for each constraint present and more. The classes used to perform the configuration lookup are described in the diagram below.

Form Config Service.jpg

Lookup

The form UI component calls the ConfigService using the item id as the lookup context. The ConfigService then scans all the loaded configuration files looking for <config> sections that match, this is accomplished with evaluators. Any sections whose evaluators return "true" are added to the config lookup result set and combined if necessary.

Forms for Alfresco nodes are configured by type name and sometimes by aspect name. However, the item id for a node is it's NodeRef. The evaluators provided by the forms engine therefore have to retrieve metadata for the node in question and retrieve it's type name or the list of aspect's applied to the node.

The NodeTypeEvaluator is responsible for looking up the node's type i.e. cm:content, and determining if that matches the condition attribute provided for the <config> element.

The AspectEvaluator is responsible for looking up the list of aspects applied to the node and determining whether the aspect name provided in the condition attribute of the <config> element is present on the node.

The call to the ConfigService results in a FormsConfigElement object being returned. This object represents the combined configuration found in each matching section of each loaded configuration file. The FormsConfigElement object provides access to several other objects representing the various configurable areas, the API of each of these objects is described in the following sections.

FormsConfigElement API

public FormConfigElement getDefaultForm()

public FormConfigElement getForm(String id)

public DefaultControlsConfigElement getDefaultControls()

public ConstraintHandlersConfigElement getConstraintHandlers()

public DependenciesConfigElement getDependencies()

FormConfigElement API

public String getId()

public String getSubmissionURL()

public Map<String, FormSet> getSets()

public String[] getSetIDs()

public List<String> getSetIDsAsList()

public FormSet[] getRootSets()

public List<FormSet> getRootSetsAsList()

public Map<String, FormField> getFields()

public String[] getForcedFields()

public List<String> getForcedFieldsAsList()

public String[] getHiddenCreateFieldNames()

public String[] getHiddenEditFieldNames()

public String[] getHiddenViewFieldNames()

public String[] getVisibleCreateFieldNames()

public String[] getVisibleEditFieldNames()

public String[] getVisibleViewFieldNames()

public List<String> getHiddenCreateFieldNamesAsList()

public List<String> getHiddenEditFieldNamesAsList()

public List<String> getHiddenViewFieldNamesAsList()

public List<String> getVisibleCreateFieldNamesAsList()

public List<String> getVisibleEditFieldNamesAsList()

public List<String> getVisibleViewFieldNamesAsList()

public String[] getVisibleCreateFieldNamesForSet(String setId)

public String[] getVisibleEditFieldNamesForSet(String setId)

public String[] getVisibleViewFieldNamesForSet(String setId)

public String getCreateTemplate()

public String getEditTemplate()

public String getViewTemplate()

public String getFormTemplate(Mode m)

public boolean isFieldVisible(String fieldId, Mode m)

public boolean isFieldHidden(String fieldId, Mode m)

public boolean isFieldVisibleInMode(String fieldId, String modeString)

public boolean isFieldHiddenInMode(String fieldId, String modeString)

public boolean isFieldForced(String fieldId)

FormField API

public Control getControl()

public Map<String, String> getAttributes()

public String getId()

public String getLabel()

public String getLabelId()

public String getDescription()

public String getDescriptionId()

public String getHelpText()

public String getHelpTextId()

public Map<String, ConstraintHandlerDefinition> getConstraintDefinitionMap()

public String getSet()

public boolean isReadOnly()

public boolean isMandatory()

Control API

public String getTemplate()

public ControlParam[] getParams()

public List<ControlParam> getParamsAsList()

ConstraintHandlerDefinition API

public String getType()

public String getValidationHandler()

public String getMessage()

public String getMessageId()

public String getEvent()

FormSet API

public String getSetId()

public String getParentId()

public String getAppearance()

public String getLabel()

public String getLabelId()

public String getTemplate()

public FormSet getParent()

public FormSet[] getChildren()

public List<FormSet> getChildrenAsList()

ConstraintHandlersConfigElement API

public String[] getItemNames()

public List<String> getItemNamesAsList()

public Map<String, ConstraintHandlerDefinition> getItems()

String[] getConstraintTypes()

List<String> getConstraintTypesAsList()

String getValidationHandlerFor(String type)

String getMessageFor(String type)

String getMessageIdFor(String type)

String getEventFor(String type)

DefaultControlsConfigElement API

public String[] getItemNames()

public List<String> getItemNamesAsList()

public Map<String, Control> getItems()

public String getTemplateFor(String dataType)

public ControlParam[] getControlParamsFor(String dataType)

public List<ControlParam> getControlParamsAsListFor(String dataType)

DependenciesConfigElement API

public String[] getCss()

public String[] getJs()

Form Service

The Form Service is responsible for returning a data model representing the data a form should display for an item and for saving the contents of a form into a format appropriate for the item.

This approach allows multiple "kinds" of item to be represented in a uniform way, allowing a single UI form component to provide a consistent user experience and single point of configuration/customization. Performing the form model generation on the server allows the functionality to be shared across multiple clients.

The diagram below shows the classes that make up the Form Service and the sections following the diagram detail each layer of the Form Service.

Form Service.jpg

REST API

The following sections detail the URL, parameters, request and response payloads of the REST APIs provided by the form service.

Form Definition

The form definition REST API is exposed as the following URL:

POST /api/formdefinitions


The API expects a JSON object within the POST body:

{
   "itemKind" : item kind,
   "itemId" : item id,
   "fields" : [array of field id's],
   "force" : [array of field id's]
}


The default response content type is "application/json", a typical JSON response for the form definition API is shown below, using an Alfresco node as an example:

{
   "data":
   {
      "item": "\/api\/node\/workspace\/SpacesStore\/db8df439-c499-4b5b-8f87-1c8f23f2797c",
      "submissionUrl": "\/api\/node\/workspace\/SpacesStore\/db8df439-c499-4b5b-8f87-1c8f23f2797c\/formprocessor",
      "type": "cm:content",
      "definition":
      {
         "fields":
         [
            {
               "name": "cm:name",
               "label": "Name",
               "description": "Name",
               "protectedField": false,
               "dataKeyName": "prop_cm_name",
               "type": "property",
               "dataType": "text",
               "constraints": 
               [
                  {
                     "type": "REGEX",
                     "parameters": 
                     {
                        "expression": "(.*[\\\"\\*\\\\\\>\\<\\?\\/\\:\\|]+.*)|(.*[\\.]?.*[\\.]+$)|(.*[ ]+$)",
                        "requiresMatch": false
                     }
                  }
               ],
               "mandatory": true,
               "repeating": false
            },
            {
               "name": "cm:creator",
               "label": "Creator",
               "description": "Who created this item",
               "protectedField": true,
               "dataKeyName": "prop_cm_creator",
               "type": "property",
               "dataType": "text",
               "mandatory": true,
               "repeating": false
            },
            {
               "name": "cm:modifier",
               "label": "Modifier",
               "description": "Who last modified this item",
               "protectedField": true,
               "dataKeyName": "prop_cm_modifier",
               "type": "property",
               "dataType": "text",
               "mandatory": true,
               "repeating": false
            }
         ]
      },
      "formData":
      {
         "prop_cm_modifier": "gavinc",
         "prop_cm_creator": "gavinc",
         "prop_cm_name": "test.txt"
      }
   }
}

Form Persistence

The persistence REST API is exposed as the following URL:

POST /api/{item_kind}/{item_id}/formprocessor


The API can accept a form submission using both "multipart/form-data" and JSON.

multipart/form-data is obviously posted using the defined standard, when submitting JSON the structure expected is shown below:

{
   "field_id": "field_value"
}

for example...

{
   "prop_cm_name": "test.txt",
   "prop_cm_title": "test.txt",
   "prop_cm_description": ""
}


The default response content type is "application/json". However, if an "alf_redirect" field_id is provided the REST API will respond with a HTTP 301 Redirect.

The JSON response for the formprocessor API is fairly simple, the structure of the response is shown below:

{
   "redirect": "${redirect}",
   "persistedObject": "${persistedObject?string}",
   "message": "${message}"
}

JavaScript API

The JavaScript API is a wrapper around the Java API. ScriptFormService provides the implementation for the JavaScript API. It is exposed as a 'dataDictionaryService' object in the script model.

The public methods are shown below.

public ScriptForm getForm(String itemKind, String itemId)

public ScriptForm getForm(String itemKind, String itemId, String[] fields)

public ScriptForm getForm(String itemKind, String itemId, String[] fields, String[] forcedFields)

public Object saveForm(String itemKind, String itemId, Object postData)

Java API

The FormService is responsible for selecting an appropriate FormProcessor for the item being processed and either generating a form definition or persisting the supplied form data.

As of the 3.3 release the forms engine supports two "kinds" of item:

  • node
  • type

These are handled by two FormProcessor implementations, NodeFormProcessor and TypeFormProcessor.

NodeFormProcessor is responsible for generating a form definition given an Alfresco NodeRef and persisting the provided form data back to the node represented by the NodeRef.

TypeFormProcessor is responsible for generating a form definition given an Alfresco content model type name i.e. "cm:content" and creating a new instance of the given type in the repository using the provided form data.

NodeFormProcessor and TypeFormProcessor both (indirectly) extend FilteredFormProcessor. FilteredFormProcessor provides custom hook points via the Front Controller pattern, very similar to the approach used by Servlet Filters.

The API of the key objects mentioned above are described in the following sections.

FormService API

public Form getForm(Item item)

public Form getForm(Item item, Map<String, Object> context)

public Form getForm(Item item, List<String> fields)

public Form getForm(Item item, List<String> fields, Map<String, Object> context)

public Form getForm(Item item, List<String> fields, List<String> forcedFields)

public Form getForm(Item item, List<String> fields, List<String> forcedFields, Map<String, Object> context)

public Object saveForm(Item item, FormData data)


The various getForm methods take the following parameters:

  • item: An object representing the item to generate a form for.
  • context: A Map of objects representing contextual data that may be required to generate the form.
  • fields: List of field ids to include in the form.
  • forcedFields: List of field ids that must be included if at all possible. An example might be a property defined on an aspect, if the aspect is not applied to the node a field definition for the property will not be returned, if the field is in the "forced" list it would indicate that server needs to try and find the property on an aspect in the content model.

NOTE: The context object is not exposed to the JavaScript API or REST API at the time of writing.

Form API

public Item getItem()

public String getSubmissionUrl()

public List<FieldDefinition> getFieldDefinitions()

public List<String> getFieldDefinitionNames()

public Collection<FieldGroup> getFieldGroups()

public FormData getFormData()

FieldDefinition API

public String getName()

public String getLabel()

public String getDescription()

public String getBinding()

public String getDefaultValue()

public String getDataKeyName()

public FieldGroup getGroup()

public boolean isProtectedField()

FieldGroup API

public String getId()

public String getLabel()

public FieldGroup getParent()

public boolean isRepeating()

public boolean isMandatory()

NOTE: This class is reserved for future use.

FormData API

public boolean hasFieldData(String fieldName)

public FieldData getFieldData(String fieldName)

public void addFieldData(String fieldName, Object fieldValue)

public void addFieldData(FormField field)

public void addFieldData(String fieldName, Object fieldValue, boolean overwrite)

public void removeFieldData(String fieldName)

public Set<String> getFieldNames()

public int getNumberOfFields()

FieldData API

public String getName()

public Object getValue()

public boolean isFile()

public InputStream getInputStream()

FormProcessor SPI

The FormProcessor SPI is shown below, for details on creating custom FormProcessor's see the custom Form Processor section below.

public interface FormProcessor
{
    public boolean isApplicable(Item item);

    public boolean isActive();

    public Form generate(Item item, List<String> fields, List<String> forcedFields, Map<String, Object> context);

    public Object persist(Item item, FormData data);
}

Filter SPI

The Filter SPI is shown below, for details on creating custom Filter's see the custom Filter section below.

public interface Filter<ItemType, PersistType>
{
   public boolean isActive();
    
   public void beforeGenerate(ItemType item, List<String> fields, List<String> forcedFields, Form form, Map<String, Object> context);
    
   public void afterGenerate(ItemType item, List<String> fields, List<String> forcedFields, Form form, Map<String, Object> context);
    
   public void beforePersist(ItemType item, FormData data);
    
   public void afterPersist(ItemType item, FormData data, PersistType persistedObject);
}

Forms Runtime

The forms runtime (forms-runtime.js) is responsible for the user interaction of a form. It manages all input, client-side validation, events and form submission.

It consists of a small lightweight JavaScript library. An unobtrusive JavaScript pattern is used whereby behaviour is added to the HTML form elements upon page load.

API

The forms runtime defines the JavaScript Alfresco.forms.Form object, it's API is shown below.

init: function()

setValidateOnSubmit: function(validate)

setValidateAllOnSubmit: function(validateAll)

setSubmitElements: function(submitElements)

setErrorContainer: function(container)

setShowSubmitStateDynamically: function(showState, showErrors)

setAJAXSubmit: function(ajaxSubmit, callbacks)

setSubmitAsJSON: function(submitAsJSON)

setAjaxSubmitMethod: function(ajaxSubmitMethod)

addValidation: function(fieldId, validationHandler, validationArgs, when, message)

addError: function(msg, field)

getFieldLabel: function(fieldId)

updateSubmitElements: function()

Validation Handlers

A validation handler is a small JavaScript function that gets called by the forms runtime when a field value needs to be validated.

The interface for a validation handler is shown below.

/**
 * Validation handler for a field.
 * 
 * @param field {object} The element representing the field the validation is for
 * @param args {object} Object containing arguments for the handler
 * @param event {object} The event that caused this handler to be called, maybe null
 * @param form {object} The forms runtime class instance the field is being managed by
 * @param silent {boolean} Determines whether the user should be informed upon failure
 * @param message {string} Message to display when validation fails, maybe null
 * @static
 */
function handler-name(field, args, event, form, silent, message)

The definition of the built in "mandatory" validation handler is shown below.

Alfresco.forms.validation.mandatory = function mandatory(field, args, event, form, silent, message)

The field parameter is usually the HTML DOM element representing the field's value, this is normally an HTML input DOM element so that the value property can be accessed. The structure of the args parameter is totally dependent on the handler being implemented, by default these will be the parameters of the constraint defined on the field. All the other parameters are self sufficiently described above in the code documentation.

The handler is responsible for taking the value from the field and using the args to calculate whether the current value is valid or not returning true if it is valid and false if it is not.

The built-in validation handlers are described in the sections below.

mandatory

The "Alfresco.forms.validation.mandatory" validation handler ensures that a value has been entered or selected for the field.

This handler has no arguments.

length

The "Alfresco.forms.validation.length" validation handler ensures the value entered for the field has more than the minimum number of characters and less than the maximum number of characters.

The handler accepts the following argument object:

{
   min: [int],
   minLength: [int],
   max: [int],
   maxLength: [int],
   crop: [boolean]
}

number

The "Alfresco.forms.validation.number" validation handler ensures the value entered for the field is a number.

This handler has no arguments.

numberRange

The "Alfresco.forms.validation.numberRange" validation handler ensures the value entered for the field is more than the minimum and less than the maximum.

The handler accepts the following argument object:

{
   min: [int],
   minValue: [int],
   max: [int]
   maxValue: [int]
}

regexMatch

The "Alfresco.forms.validation.regexMatch" validation handler ensures the value of the field matches the provided regular expression, to test the regular expression pattern does NOT match the field's value set the match argument to "false"

{
   pattern: [regexp],
   match: [boolean]
}

email

The "Alfresco.forms.validation.email" validation handler ensures the value entered for the field is a valid email address, in terms of syntax.

This handler has no arguments.

url

The "Alfresco.forms.validation.url" validation handler ensures the value entered for the field is a valid URL, in terms of syntax.

This handler has no arguments.

time

The "Alfresco.forms.validation.time" validation handler ensures the value entered for the field is a valid time.

This handler has no arguments.

nodeName

The "Alfresco.forms.validation.nodeName" validation handler ensures the value entered for the field is a valid string to use for an Alfresco node.

This handler has no arguments.

nodeRef

The "Alfresco.forms.validation.nodeRef" validation handler ensures the value entered for the field is a valid Alfresco NodeRef in termsof syntax (it doesn't check whether the NodeRef actually points to a live node).

This handler has no arguments.

Customization

There are multiple points in the Forms Engine stack where customizations can be applied, the most common ones are outlined below.

Controls

Probably the most common customization will be adding new controls. A control is classed as the label for the field and UI the user interacts with in order to set and/or edit the value for the field.

A control is defined as a Freemarker template snippet i.e. it just includes the markup to define the control. Refer to the Configuring Forms section for details on specifying any dependencies the control has.

As you'd expect a model is available representing the field and form being generated, represented by a field and form object, respectively.

The structure of the form object is detailed in the Form UI Component section.

The structure of the field object is shown below (using the cm:name property as an example):

{
   kind : "field",
   id : "prop_cm_name",
   configName : "cm:name",
   name : "prop_cm_name",
   dataType : "d:text",
   type : "property",
   label : "Name",
   description : "Name",
   mandatory : true
   disabled : false,
   repeating : false,        
   dataKeyName : "prop_cm_name",
   value : "plain-content.txt",
   control:
   {
      params: {},
      template : "controls/textfield.ftl"
   }
} 

Although the id property provides a unique identifier for the field it is only scoped to the current form. If there are multiple forms on the page containing the same field this id will not be unique. The remaining model object provided to the control to mention is fieldHtmlId, the value of the property should be used as the id for the control as this is guaranteed to be unique for the page. An excerpt from the built-in textfield control is shown below to demonstrate it's use.

<input id="${fieldHtmlId}" type="text" name="${field.name}" tabindex="0" ..... />

The state of the disabled property must always be adhered to when implementing controls as this is driven from the field definition returned from the FormService and from the read-only attribute in the form configuration. If this is set to "true" the control should never allow the value to be edited.

The control is also responsible for rendering an appropriate UI representation for the mode the form is currently in. The form mode can be retrieved from the mode property. A pattern used by most the out-of-the-box controls is shown below.

<#if form.mode == "view">
   // view representation goes here...
<#else>
   // edit and create representation goes here...
</#if>

The final rule for controls is that they MUST supply the fields current value in a DOM element that has a value property and the id property set to the value of fieldHtmlId Freemarker variable. For advanced controls i.e. association, date, period etc. this usually means a hidden form field.

Some custom controls can be found in the examples provided with the Forms Development Kit.

Form Templates

The out-of-the-box templates that generate the form UI are fairly limited in terms of layout, without sets fields are just rendered from top to bottom in a single column.

It is possible via configuration to specify an alternative Freemarker template to use for the form in each mode using the view-form, edit-form and create-form elements. If present the Form UI Component will use the custom template instead of the default one.

Custom templates should be placed outside the web application, for Tomcat installations (presuming it has been configured to) this means the <tomcat>/shared/classes/alfresco/web-extension/site-webscripts folder.

The custom template has full access to the form model object introduced in the Form UI Component section.

The custom form template is free to use as much or as little of the supplied form model as it wants, the only caveat is that the generated form UI must ensure that any fields that need to be disabled are rendered as such. It is also recommended that the custom template use the FreeMarker macros provided by form.lib.ftl to reduce the amount the markup required and to protect against future changes.

An example custom form template, that can also be found in the Forms Development Kit, is shown below as an example (the renderSetWithColumns macro is not shown for brevity).

<#import "/org/alfresco/components/form/form.lib.ftl" as formLib />

<#if formUI == "true">
   <@formLib.renderFormsRuntime formId=formId />
</#if>

<@formLib.renderFormContainer formId=formId>
   <#list form.structure as item>
      <#if item.kind == "set">
         <@renderSetWithColumns set=item />
      <#else>
         <@formLib.renderField field=form.fields[item.id] />
      </#if>
   </#list>
</@>

Set Templates

Sets can be used to group fields and have them rendered within a standard HTML fieldset or within a headed panel.

The ability to independently control the layout of a set of fields is required, this can be achieved by configuring a custom set template.

Custom templates should be placed outside the web application, for Tomcat installations (presuming it has been configured to) this means the <tomcat>/shared/classes/alfresco/web-extension/site-webscripts folder.

The custom set template is provided with a set model object, the structure of which is shown below.

{ 
   "id" : "",
   "kind" : "set",
   "label" : "Default",
   "template" : null,
   "appearance" : null,
   "children" : 
   [ 
      { 
         "id" : "prop_cm_name",
         "kind" : "field"
      },
      { 
         "id" : "prop_cm_creator",
         "kind" : "field"
      },
      { 
         "id" : "prop_cm_modifier",
         "kind" : "field"
      }
   ]
} 

The set template is responsible for rendering all the fields and any child sets present. By configuring the set appearance to be "" the custom set template can also render the set 'chrome'.

Two examples of custom set templates can be found in the Forms Development Kit.

Form Processor

Custom FormProcessor implementations can be implemented and integrated easily via a small amount of Spring configuration. You will typically want to do this to support a new "kind" of form.

A FormProcessor implementation is responsible for registering itself with the FormProcessorRegistry and for providing some way to determine whether it is applicable for the item being processed.

There are several FormProcessor classes that can be extended (as shown in the diagram in the Form Service section), which one you choose depends on the features required.

The base class is AbstractFormProcessor, all FormProcessor implementations should at the very least extend this class. It handles all the low level requirements mentioned above using a regular expression match to determine applicability. All built-in FormProcessor implementations extend this class indirectly.

Next in the class hierarchy is the FilteredFormProcessor. This implementation provides a Filter mechanism similar to that provided by Servlet filters. Again, all the built-in FormProcessor implementations extend this class.

The final abstract FormProcessor is the ContentModelFormProcessor. This implementation provides functionality focused towards the Alfresco content model. NodeFormProcessor and TypeFormProcessor both extend this class. However, it has been recognized that the ContentModelFormProcessor implementation does not lend itself very well to re-use, but does provide some useful generic code to build form definitions, this class is therefore targeted for re-factoring in the next release.

If you find yourself wanting to extend ContentModelFormProcessor, NodeFormProcessor or TypeFormProcessor, it is more than likely that you can achieve what you want using a Filter. Adding a Filter is the recommended approach if you are just "tweaking" an existing FormProcessor, these are discussed in the next section.

Configuration

If you do decide you need to replace one of the built-in FormProcessor implementations you can reconfigure the Spring bean definition (found in form-services-context.xml), the default configuration is shown below.

<bean id="nodeFormProcessor" class="org.alfresco.repo.forms.processor.node.NodeFormProcessor" parent="filteredFormProcessor">
   <property name="filterRegistry" ref="nodeFilterRegistry" />
   <property name="nodeService" ref="NodeService" />
   <property name="fileFolderService" ref="FileFolderService" />
   <property name="dictionaryService" ref="DictionaryService" />
   <property name="namespaceService" ref="NamespaceService" />
   <property name="contentService" ref="ContentService" />
   <property name="matchPattern">
      <value>node</value>
   </property>
</bean>
    
<bean id="typeFormProcessor" class="org.alfresco.repo.forms.processor.node.TypeFormProcessor" parent="filteredFormProcessor">
   <property name="filterRegistry" ref="typeFilterRegistry" />
   <property name="nodeService" ref="NodeService" />
   <property name="fileFolderService" ref="FileFolderService" />
   <property name="dictionaryService" ref="DictionaryService" />
   <property name="namespaceService" ref="NamespaceService" />
   <property name="contentService" ref="ContentService" />
   <property name="matchPattern">
      <value>type</value>
   </property>
</bean>


The configuration above also gives an indication of what configuration you'll require when registering a new FormProcessor implementation.

Finally, it is unlikely you'll ever need to, but it is possible to disable a built-in FormProcessor by setting the active property to "false". The configuration below shows how could disable the TypeFormProcessor.

<bean id="typeFormProcessor" class="org.alfresco.repo.forms.processor.node.TypeFormProcessor" parent="filteredFormProcessor">
   <property name="filterRegistry" ref="typeFilterRegistry" />
   <property name="nodeService" ref="NodeService" />
   <property name="fileFolderService" ref="FileFolderService" />
   <property name="dictionaryService" ref="DictionaryService" />
   <property name="namespaceService" ref="NamespaceService" />
   <property name="contentService" ref="ContentService" />
   <property name="matchPattern">
      <value>type</value>
   </property>
   <property name="active">
      <value>false</value>
   </property>
</bean>

Form Filter

As a servlet filter allows a HTTP request and response to be manipulated a form filter allows a form definition to be manipulated before and/or after generation and form data to be manipulated before and/or after being persisted.

Form filters are typically used to add custom processing to a FormProcessor, for example, calculated fields could be added after the default set of fields have been processed, a unique identifier could be generated for a field before it's persisted or an XML rendition of the metadata could be generated after the form data is persisted, as you can see this provides a very extensible mechanism.

A filter registry is associated with each FilteredFormProcessor implementation, each registered Filter is then called (within the same transaction) for each request processed by the FormProcessor. It is the responsibility of the Filter to determine whether it is applicable for the request. The order the Filters are executed is not guaranteed.

Configuration

To register a new Filter (for the NodeFormProcessor) the following Spring configuration needs to be defined.

<bean id="yourCustomFilter" class="your.CustomFilter" parent="baseFormFilter">
   <property name="filterRegistry" ref="nodeFilterRegistry" />
</bean>


A real world example of FormFilters in use can be found within the DOD5015 Records Management Module. They are used to handle custom metadata that can be added to record types dynamically and generating default unique identifiers, demonstrating the use of afterGenerate and afterPersist, respectively. The source code can be found in root/modules/dod-5015/source/java/org/alfresco/module/org_alfresco_module_dod5015/forms.

Debugging & Testing

As a majority of the Forms Engine stack is based on Web Scripts all the information pertaining to Web Script Administration is valid here.

Any changes made to custom config files, custom controls and custom templates (as long as they are outside the core WEB-INF classloader) can be reloaded via the Refresh button on the Web Script index page.

Logging can also be enabled, for Web Scripts logging can be enabled with the following log4j statements:

log4j.logger.org.springframework.extensions.webscripts.ScriptLogger=debug
log4j.logger.org.alfresco.repo.jscript.ScriptLogger=debug

and FormService logging can be enabled with the following log4j statements:

log4j.logger.org.alfresco.repo.forms=debug
log4j.logger.org.alfresco.web.config.forms=debug

A Forms Development Kit (FDK) is also available mainly containing examples at the time of writing. The test form page that also shipped in previous releases of Share has now been moved to the FDK and renamed to the Form Console. The Form Console allows your custom forms to be tested quickly and in isolation.

Finally, a colleague wrote a useful blog post recently providing development tips for Share most of which are also applicable to forms development.

Personal tools
Download and go
© 2014 Alfresco Software, Inc. All Rights Reserved. Legal | Privacy | Accessibility