Obsolete Pages{{Obsolete}}
The official documentation is at: http://docs.alfresco.com
NOTE: This document is a Developer Guide for the Forms Engine in Alfresco 3.3, for the latest documentation click here.
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.
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.
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.
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.
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.
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:
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.
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 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.
${error}${msg('form.not.present')}
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.
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.
public FormConfigElement getDefaultForm()
public FormConfigElement getForm(String id)
public DefaultControlsConfigElement getDefaultControls()
public ConstraintHandlersConfigElement getConstraintHandlers()
public DependenciesConfigElement getDependencies()
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)
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()
public String getTemplate()
public ControlParam[] getParams()
public List<ControlParam> getParamsAsList()
public String getType()
public String getValidationHandler()
public String getMessage()
public String getMessageId()
public String getEvent()
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()
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)
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)
public String[] getCss()
public String[] getJs()
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.
The following sections detail the URL, parameters, request and response payloads of the REST APIs provided by the form service.
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': '(.*[\\\'\\*\\\\\\>\\
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}'
}
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)
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:
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.
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:
NOTE: The context object is not exposed to the JavaScript API or REST API at the time of writing.
public Item getItem()
public String getSubmissionUrl()
public List<FieldDefinition> getFieldDefinitions()
public List<String> getFieldDefinitionNames()
public Collection<FieldGroup> getFieldGroups()
public FormData getFormData()
public String getName()
public String getLabel()
public String getDescription()
public String getBinding()
public String getDefaultValue()
public String getDataKeyName()
public FieldGroup getGroup()
public boolean isProtectedField()
public String getId()
public String getLabel()
public FieldGroup getParent()
public boolean isRepeating()
public boolean isMandatory()
NOTE: This class is reserved for future use.
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()
public String getName()
public Object getValue()
public boolean isFile()
public InputStream getInputStream()
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);
}
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);
}
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.
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()
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.
The 'Alfresco.forms.validation.mandatory' validation handler ensures that a value has been entered or selected for the field.
This handler has no arguments.
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]
}
The 'Alfresco.forms.validation.number' validation handler ensures the value entered for the field is a number.
This handler has no arguments.
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]
}
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]
}
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.
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.
The 'Alfresco.forms.validation.time' validation handler ensures the value entered for the field is a valid time.
This handler has no arguments.
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.
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.
There are multiple points in the Forms Engine stack where customizations can be applied, the most common ones are outlined below.
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.
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.
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).
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.
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.
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>
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.
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.
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.