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.4, 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 is a UI based web script. It behaves like any other web script where it's logic is executed first followed by the rendering of it's FreeMarker template. The only difference with a UI web script is that a <scriptname>.get.head.ftl is also parsed, this contains all the dependencies (JavaScript and CSS) for the UI component being executed.
Since the 3.4 release the form UI component uses a Java backed web script. This makes it easier to extend the logic behind the web script and makes it a lot easier to troubleshoot issues during development. The form UI component is defined using the following Spring configuration.
<bean id='webscript.org.alfresco.components.form.form.get' class='org.alfresco.web.scripts.forms.FormUIGet' parent='webscript'>
<property name='configService' ref='web.config' />
</bean>
For a web script the form UI component 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), the list of 'forced fields', the item kind and the item id are all POSTed to the /api/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.4 release the forms engine supports four 'kinds' of item:
These are handled by the FormProcessor implementations, NodeFormProcessor, TypeFormProcessor, WorkflowFormProcessor and TaskFormProcessor.
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.
The WorkflowFormProcessor is responsible for generating a form definition given a Workflow Definition. Percreating form data using this Form Processor will create and start a new Workflow Instance of the type specified by the Workflow Definition.
The TaskFormProcessor is responsible for generating a form definition representing a Workflow Task and it's associated package. Persisting form data using this Form Processor will either update the properties associated with a task or end the task, depending on whether a transition is specified or not.
All four Form Processors 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 FormProcessors 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 FormCreationData SPI is shown below. FormCreationData is a simple DTO containing various data needed to generate Forms.
The itemData object associated with FieldCreationData is used by FieldProcessors to create the various Fields for a Form. Different FormProcessors will provide different types of itemData. For example, all of the standard Alfresco FormProcessors provide itemData of type ContentModelItemData.
public interface FormCreationData
{
Object getItemData();
boolean isForcedField(String fieldName);
Map<String, Object> getContext();
}
The Field SPI is shown below. A Field is a simple DTO which encapsulates all the data needed to display a field on a Form.
public interface Field
{
FieldDefinition getFieldDefinition();
String getFieldName();
Object getValue();
}
The FieldProcessor SPI is shown below. A FieldProcessor is responsible for generating a field with the specified name give the appropriate FormCreationData.
For details on creating custom FieldProcessors see the custom Form Processor section below.
public interface FieldProcessor
{
Field generateField(String fieldName, FormCreationData data);
}
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)
addSubmitElement: function(submitElement)
getFieldLabel: function(fieldId)
getFormData: function()
updateSubmitElements: function()
applyTabFix: 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.
The 'Alfresco.forms.validation.validDateTime' validation handler is used to signify whether an invalid date has been entered in a date picker control. The handler simply returns false if the 'invalid' CSS class is applied to the given field.
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 easily and integrated via a small amount of Spring configuration.
Typically you will do this to support a new 'kind' of form.
If you simply wish to add a few extra fields to a Form or want to support a new type of field you should probably consider using a Filter or FieldProcessor rather than implementing a new FormProcessor.
FormProcessors have two primary functions:
The FormProcessor interface has two other methods which are required by the FormService:
The isApplicable(Item) method is used to determine whether a FormProcessor is able to generate or persist Forms for a given Item.
The isActive() method is used to determine if a FormProcessor is currently active and available to generate or persist Forms.
Several extensible FormProcessor classes have been provided (shown in the diagram in the Form Service section) to facillitate the creation of new FormProcessors.
These classes are described in the following sections.
This is the top-level abstract FormProcessor implementation.
It does not provide implementations for the generate or persist methods, however it does provide implementations for the isActive and isApplicable methods.
It also provides useful methods to enable simple configuration of FormProcessors through Spring, including registering the FormProcessor with a supplied FormProcessorRegistry.
Customized FormProcessors will generally not directly extend this class as FilteredFormProcessor and ContentModelFormProcessor offer much more functionality and ease of use.
Some useful methods supplied by this class include:
Specifies a FormProcessorRegistry which this FormProcessor will register itself with.
Registers this FormProcessor with the specified FormProcessorRegistry. This method should be called as an 'init' method in Spring.
Sets the regular expression match pattern. This pattern is used by the isApplicable method to determine if this FormProcessor should be used to process a give Item kind.
Used to set the FormProcessor to active or inactive, effectively switching the FormProcessor on or off.
Subclasses of AbstractFormProcessor must implement the following abstract methods:
This is the method responsible for generating a Form representation given an item of the appropriate kind.
This is the method responsible for persisting a Form representation of an item of the appropriate kind.
This abstract class directly extends AbstractFormProcessor.
It provides partial implementations of the persist and generate methods.
It also provides support for the addition of Filters, providing pre- and post-process extension points on both the generate and persist methods.
The FilteredFormProcessor has two generic parameters, ItemType and PersistType.
ItemType is the type of item the FormProcessor can represent as a Form with the generate method. For example, the ItemType of TypeFormProcessor is TypeDefinition, while for NodeeFormProcessor it is NodeRef.
PersistType is the type of the object that is created when the persist method is called. This is often the same as the ItemType, but sometimes it differes. For example the TypeFormProcessor has NodeRef as its PersistType.
FilteredFormProcessor is well suited to be the parent of custom FormProcessors which do not make use of Alfresco-specific code such as QNames, NodeRefs, TypeDefinitions, etc.
The FilteredFormProcessor provides the following useful methods:
This implementation ensures that the befreGenerate and afterGenerate methods are called on all registered filters.
It also calls the internalGenerate method, immediately after the calls to beforeGenerate and before the calls to afterGenerate.
This method creates the Form Item URL and Form Item Type to the correct values.
It generates the FormCreationData and calls the method populateForm.
This method generates all the fields to be added and adds them to the Form, together with the associated field values.
If a list of field names is supplied then this is used to generate the fields by calling the generateSelectedFields method.
If no field names are supplied then a default list of fields is generated using the generateDefaultFields method.
This method makes use of FieldProcessors and the FieldProcessorRegistry to generate the specified fields.
The FieldProcessorRegistry selects the appropriate FieldProcessor to use to generate each field, based on the field name provided.
This method returns a list of fields used to generate a default Form when no field names are explicitly requested.
The last parameter of the method is a list of field names to exclude from the default Form.
This list of ignored fields can be set as a property on the FormProcessor (ignoredFields).
If this property is not explicitly set then the method getDefaultIgnoredFields is used instead.
This implementation ensures that the befrePersist and afterPersist methods are called on all registered filters.
It also calls the internalPersist method, immediately after the calls to befrePersist and before the calls to afterPersist.
This method sets the FilterRegistry which is used to get all the filters registered against this FormProcessor.
These Filters are called at various points in the persist and generate methods.
This method sets the list of field names to ignore when generating a default Form.
This method sets the FieldProcessorRegistry.
The FieldProcessorRegistry and the FieldProcessors registered with it are used to generate teh fields and their associated values.
Subclasses of FilteredFormRegistry must implement several abstract methods:
Converts the given Item into a typed object which must extend the type of the generic parameter ItemType.
For example, TypeFormProcessor gets the Item Id and looks up the corresponding TypeDefinition using the DictionaryService, while the NodeFormProcessor simply converts the Item Id to a new NodeRef.
Returns a String describing the type of the specified item, which is used to set the Form Item type.
For example, the TypeFormProcessor returns the prefixed String name of the TypeDefinition which is passed in, while the NodeFormProcessor simply returns the toString() value of the NodeRef.
Returns the URI location of the specified item.
Creates a data object used by the FormProcessor and FieldProcessors to create Fields.
This data object should contain all the information required to generate a Field for the give item.
Returns a default list of field names to be excluded from the default Form which is genereated.
Note that this default list can be overriden by setting the ignoredFields property.
Called from within the persist method, this method is responsible for actually persisting the Form and returning a representation of the persisted item.
Returns the Log used by the FormProcessor to log events.
ContentModelFormProcessor is an abstract class which directly extends FilteredFormProcessor.
This class has the same two generic parameters as FilteredFormProcessor, ItemType and PersistType.
The ContentModelFormProcessor is aware of Alfresco's content modelling capabilities and assumes that the Form properties and associations will be modelled using Alfresco's PropertyDefinition and AssociationDefinition classes.
The ContentModelFormProcessor assumes that the items it processes into Forms have a TypeDefinition associated with them.
This TypeDefinition is used to identify the PropertyDefinitions and AssociationDefinitions associated with that item.
In addition to the 'normal' properties and associations which are modelled in Alfresco's content model, there are also transient properties and associations.
These may be used in a variety of ways, for example to represent the size of a piece of content in the NodeFormProcessor, or to represent the list of available transitions in the TaskFormProcessor.
This class is an ideal parent for FormProcessors which interact with Alfresco's content model and repository.
At present the ContentModelFormProcessor does not improve the functionality of Form persistance over what was already provided by FilteredFormProcessor.
I.e. each FormProcessor extending ContentModelFormProcessor must implement an internalPersist method.
The ContentModelFormProcessor provides the following useful methods to facillitate Form generation:
This implementation returns an object of class ContentModelItemData which contains all the property and association definitions for the specified item type, including any applied aspects for that item.
It also contains the values for all properties, associations and transient properties/associations.
The implementation calls the following abstract methods: getBaseType, getPropertyValues, getAssociationValues, getTransientValues
This implementation uses the DefaultFieldBuilder to build a list of default fields for a Form where no field names were specified.
The DefaultFieldBuilder simply adds one field for every property and association definition in the FormCreationData, excluding those field names listed in the ignoredFields parameter.
Subclasses of ContentModelFormProcessor must implement the following abstract methods:
Returns the TypeDefinition assocciated with the item.
FOr example, TypeFormProcessor would simply return the item, as it is already a TypeDefinition.
The NodeFormProcessor would use the NodeService to find the TypeDefinition associated with the given NodeRef.
Returns a map from association QName to association value.
These association values are stored in the ContentModelItemData object created in makeItemData.
The TypeFormProcessor simply returns an empty map since TypeDefinitions have no association values.
The NodeFormProcessor, meanwhile, would use the NodeService to find all associations for which the item NodeRef is a source.
Returns a map from property QName to property value.
These property values are stored in the ContentModelItemData object created in makeItemData.
The TypeFormProcessor simply returns an empty map since TypeDefinitions have no property values.
The NodeFormProcessor, meanwhile, would use the NodeService to find all properties associated with the item NodeRef.
Returns a map from transient property/association name to transient property/association value.
These transient values are stored in the ContentModelItemData object created in makeItemData.
The TypeFormProcessor simply returns an empty map since TypeDefinitions have no transient values.
The NodeFormProcessor, meanwhile, may add values for the content size, mimetype and encoding if the item is a Content node.
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='baseFormProcessor' abstract='true' init-method='register'
class='org.alfresco.repo.forms.processor.node.ContentModelFormProcessor'>
<property name='processorRegistry' ref='formProcessorRegistry' />
<property name='nodeService' ref='NodeService' />
<property name='contentService' ref='ContentService' />
<property name='dictionaryService' ref='DictionaryService' />
<property name='fileFolderService' ref='FileFolderService' />
<property name='namespaceService' ref='NamespaceService' />
<property name='fieldProcessorRegistry' ref='fieldProcessorRegistry' />
</bean>
<bean id='nodeFormProcessor'
class='org.alfresco.repo.forms.processor.node.NodeFormProcessor'
parent='baseFormProcessor'>
<property name='filterRegistry' ref='nodeFilterRegistry' />
<property name='matchPattern'>
<value>node</value>
</property>
</bean>
<bean id='typeFormProcessor'
class='org.alfresco.repo.forms.processor.node.TypeFormProcessor'
parent='baseFormProcessor'>
<property name='filterRegistry' ref='typeFilterRegistry' />
<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='baseFormProcessor'>
<property name='filterRegistry' ref='typeFilterRegistry' />
<property name='matchPattern'>
<value>type</value>
</property>
<property name='active'>
<value>false</value>
</property>
</bean>
As a servlet filter allows an 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
and the Share tier Forms logging can be enabled with the following log4j statement:
log4j.logger.org.alfresco.web.scripts.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.
Ask for and offer help to other Alfresco Content Services Users and members of the Alfresco team.
Related links:
By using this site, you are agreeing to allow us to collect and use cookies as outlined in Alfresco’s Cookie Statement and Terms of Use (and you have a legitimate interest in Alfresco and our products, authorizing us to contact you in such methods). If you are not ok with these terms, please do not use this website.