This topic explains how to create new form logic in the XperienCentral Interactive Forms component. For developers, the most important parts of the Interactive Forms component are the following:
- Form logic (handler, router, form validator, element validator)
- Form element (also known as a form fragment)
- Form presentation (for the frontend and for the backend)
- Form manager
- Form engine
- Incorporation of AJAX on the frontend of the forms
Each of these parts of the Interactive Forms component is described in detail in the following parts of this topic.
In This Topic
Form Logic
Form Logic is a generic term for the following four concepts in Interactive Forms: handler, router, form validator and fragment validator. The screenshot below shows where new handlers (A), routers (B) and form validators (C) that you create will appear in the Interactive Forms user interface:
Form Fragment
The parts that make up a form are called form elements. When discussing form elements in connection with the API, we refer to form elements as form fragments. Both of these terms are used interchangeably in this topic. In the GUI of the Interactive Forms component, form elements are divided into three types:
- Elements that require input from the website visitor;
- Elements that contain zero or more other elements;
- Elements that don’t require input from the website visitor.
Presentation
Each part of a form has its own presentation. The presentation for Interactive Forms is handled using JSPs in the same way that the presentations in the rest of XperienCentral are handled.
Presentation Scopes
There are many scopes relevant for the Interactive Forms presentation. Presentations with the scope FormStep
are available as a presentation for each form step. Apart from this scope, there is also a scope for each sort of form fragment. For example, the form fragment with the name "Text" has the scope FormFragmentTextInput
. All default presentations can be found in the plugin nl.gx.forms.wmpformpresentation
.
Just like with elements on a page, if a form element has only one presentation with the scope belonging to that element, there will be no presentation option for that form element available to the editor. If a form element has more than one presentation within its scope, then the property "Presentation" appears at the top of the advanced properties of that form element. For example, by default the Text form element has two presentations and therefore the Presentation property has two options.
Frontend and Backend Presentation
The presentation in the form’s JSP(s) is used for rendering it in the frontend. For the backend, the "iafpanel" channel is used, which must be defined in the descriptor.xml
file that accompagnies the JSP. For example, for a text input fragment presentation, this would look similar to the following:
<channel> <name>iafpanel</name> <presentation>FormFragmentTextInput</presentation> </channel>
When the HTML output of a form element in the Interactive Forms component as shown in XperienCentral must be identical to the HTML output on the website, the value of the presentation
tag should be the same as the name of the presentation. This is what makes the WYSIWYG of the Interactive Forms component possible. Otherwise, the presentation
tag should be set to the name of IAF's official presentation for the corresponding scope.
Form Manager and Form Engine
The Form Manager is responsible for handling the Interactive Forms objects, that is, retrieving, storing and/or deleting objects (CRUD operations). The Form Engine deals with form operations such as creating form sessions and handling the form logic for a specific form.
AJAX on the Frontend
It is common to use AJAX in forms on the frontend of a website. For example, when a website visitor enters his or her postal code and house number into a field, you can automatically complete the street name and city. This is done without having to submit the complete form. AJAX performs this in the following ways:
- Using JavaScript in the browser, a call is made to the server using just the postal code and the house number;
- The server returns an XML string containing the city and street name;
- The JavaScript in the browser processes the XML response and fills in the city and street name fields with the returned information.
It’s possible to use AJAX on the frontend for forms. This process is briefly explained in Clientside routing: AJAX development using the JSON interface.
Architectural Overview
Entities
All of the concepts discussed in this part can be translated into entities stored in the JCR and interfaces in the API. XperienCentral editors use the Interactive Forms maintenance panel to manage these entities.
Form
The form is the top level entity within Interactive Forms. Each form can have multiple versions, which are basically version-controlled "wizards" in the sense that they can consist of multiple form steps. One of those versions is active while the other versions are in other states such as "development" or "inactive". The latter two have the same functional effect: each form version can have multiple form steps which are to be displayed and handled one at a time by the form element. The form element is a normal XperienCentral element that can be placed on a page. Optionally, a specific form step can be selected within the element. By using multiple form elements, you can spread the different steps of a form over multiple pages. Forms can also be ordered in categories.
Form Step
A form step is a presentation canvas for the website frontend. The form step is a container that contains one or more form fragments that can be added, removed and/or ordered in the user interface. A form step entity belongs to one specific form entity. Moreover, a form step is a form logic container, see Form Logic Container.
Form Section
A form section is conceptually the same as a form step, with the exception that they can be reused. Form sections can have multiple versions, just like forms. Form sections are placed on a form step. They can be used on multiple steps, versions and forms. Form sections are also referred to as sub forms. Form sections can also be ordered in categories.
Form Fragment (Container)
All visible components of a form step are form fragments. These can range from a simple text input to complex composite elements, for example a form section form fragment or an address fragment consisting of multiple fields. Complex form fragments can contain their own form logic. Form fragments typically have several default properties like the title, the identifier (their technical name), a precondition, a prefilling value and set of validators. Everything that can contain a form fragment is referred to as a FormFragmentContainer
. Examples of a container are a form step, a form fragment that renders a form section, a composite form fragment or a simple design fragment like a column fragment. All form fragments are of the component type FormFragmentComponentType
and can be generated by a plugin.
Form Logic Container
Apart from form fragments, form steps also contain form logic that can occur in multiple places, also known as form logicContainers
which are a special type of FormFragmentContainer
which always contain form fragments. Examples of such form logicContainers
are the form step, the form section and the form fragment that displays a form step. Form logic is typically a list of form logic components that are executed one after the other. However, they can also be grouped in a tree-like way using, for example, the conditional form logic component. These form logic components come in several varieties: handlers, validators, routers and form validators. Except for the validator, these components are basically identical. The difference is that validators cannot be assigned to a form logic container, but only directly to the form fragment they should validate.
Form logic can be executed both before rendering a form step and after. The former is referred to as prehandling and the latter is referred to as posthandling. Before rendering a form step, its tree of prehandling FormFragments
, FormFragmentContainers
and form logicContainers
is stepped through and the form logic components encountered are executed first. All form logic can return a routing result; if one is returned, execution stops. In the case of prehandling, by default the result has no effect on the execution process.
When a form step is submitted, the posthandling process is followed. In this case, the routing result is handled and the user is redirected accordingly. Form logic can be extended by installing a form logicProvider
plugin. Such form logicComponents
can be written in either Java, JavaScript or a combination of both. What actually happens when a form is rendered or submitted is much more complex (see The Form Engine for more details).
Language Labels
Interactive Forms has its own solution for providing language labels using language label containers. Most entities discussed above have their own set of language label containers. Within these containers, language-specific properties of these entities are stored. This typically involves the title and additional text shown to the user. Typically, there is a language label container for each supported locale that corresponds to the "metatagvalue" of the XperienCentral language (nl_NL or en_US).
The Interactive Forms API
The Interactive Forms API defines all interfaces that are used to communicate with the Interactive Forms component. Plugin developers only use these contracts for forms related services, to perform CRUD operations on forms, to submit forms and handle form operations. A complete overview of the packages can be found in the XperienCentral Javadoc. The following packages are the most used in the plugin development process:
nl.gx.forms.wmpformapi.api.form
An extension of the basic form interfaces in nl.gx.forms.wmpformapi.api
. Some interfaces extend the XperienCentral wrapper/presentable interface that allows the displaying of objects using the WM render mechanism. The basic form interfaces (nl.gx.forms.wmpformapi.api
) only contains getters — these form interfaces also contains create, delete and update (setter) methods.
nl.gx.forms.wmpformapi.api.logic
Contains interfaces that define the form logic components that can be assigned to a form step. The typical form logic components are form validators, form fragments validators, form routers, form handlers and combinations of these through the use of conditional form logic component (the if-then-else component).
nl.gx.forms.wmpformapi.api.manager
Form entities are managed by a shell around the XperienCentral EntityManager
, namely the FormManager
(wmpformmanager
). The FormManager
is designed to facilitate form maintenance actions like retrieving, adding, deleting forms or subforms (CRUD operations). Moreover, the FormManager
provides form-related information like categories, form validators, handlers and routers that are present in the XperienCentral platform. The FormManager
is the interface for the FormManagerService
. This service can be used by defining a service dependency in your plugin. This service is also used as the starting point for CRUD operations on forms, form steps, form fragments, form logic, and all other related objects in your plugin.
nl.gx.forms.wmpformapi.engine
The Interactive Forms component exposes three service interfaces, one of which is in the engine sub package of the forms API: the form engine service (FormEngineService
). The FormEngineService
enables the handling of forms, manages the form engine session and executes form logic components. This service is typically used in the form handler servlet that handles incoming form submits and the form element that handles the rendering of the form.
nl.gx.forms.wmpformapi.plugin.formfragmenttype
This package provides interfaces that enable plugin developers to create their own form fragments. A form fragment uses the FormFragmentComponent
to become a component, and is therefore able to be managed by the Component Manager. The FormFragmentComponent
interface ensures a unified way of creating form fragment instances through the createInstance()
method. The FormFragmentComponentType
interface introduces the component type that identifies the class of form fragment components. The FormFragmentComponentDefinition
defines the relationship between the component, component type, form fragment implementation class and more.
nl.gx.forms.wmpformapi.plugin.form logicprovidertype
This package provides interfaces that enable plugin developers to write their own form logic. The form logicProviderComponent
is the XperienCentral component that provides form logic (routers, handlers and validators). Form logic typically implements this interface. The form logicProviderComponentType
interface introduces the component type that identifies the class of form logic components. The form logicProviderComponentDefinition
defines the relation between the component, component type, form logic implementation class, and so forth. All form logic components typically implement the form logicProviderService
, which allows them to be executed by the form engine.
Form Fragment Java Interfaces
The following table shows the Java interfaces used by the Interactive Forms form fragments.
Form Fragment Name | Java Interface |
---|---|
Text | nl.gx.forms.wmpformfragments.api.FormFragmentTextInput |
Number | nl.gx.forms.wmpformfragments.api.FormFragmentNumberInput |
Date | nl.gx.forms.wmpformfragments.api.FormFragmentDateInput |
Password | nl.gx.forms.wmpformfragments.api.FormFragmentPasswordInput |
Textarea | nl.gx.forms.wmpformfragments.api.FormFragmentTextArea |
Radio | nl.gx.forms.wmpformfragments.api.FormFragmentRadioList |
Checkbox | nl.gx.forms.wmpformfragments.api.FormFragmentCheckboxList |
Dropdown | nl.gx.forms.wmpformfragments.api.FormFragmentDropDownList |
Upload | nl.gx.forms.wmpformfragments.api.FormFragmentFileUpload |
nl.gx.forms.wmpformfragments.api.FormFragmentEmailInput | |
Columns | nl.gx.forms.wmpformfragments.api.FormFragmentColumns |
Section | nl.gx.forms.wmpformfragments.api.FormFragmentSection |
Form Section | nl.gx.forms.wmpformfragments.api.form.FormFragmentFormSection |
Repeat | nl.gx.forms.wmpformfragments.api.form.FormFragmentRepeat |
Next Button | nl.gx.forms.wmpformfragments.api.FormFragmentButton |
Back Button | nl.gx.forms.wmpformfragments.api.FormFragmentBackButton |
Paragraph | nl.gx.forms.wmpformfragments.api.FormFragmentParagraph |
Overview | nl.gx.forms.wmpformfragments.api.FormFragmentOverview |
The Clientside JavaScript Framework
As soon as a form is rendered on the website, the entities discussed in the previously in this topic come in to play. The rendering of a form is handled by the FormElement
object. Rendering involves (among other things) retrieving the form, form fragments and form logic. The output of the rendering process is twofold: HTML based on the presentations coupled with the form and from fragments and JavaScript that is retrieved from form fragment preconditions and validations, that is, the clientside JavaScript framework.
Preconditions
The Interactive Forms component allows editors to define JavaScript preconditions with form fragments. Preconditions (Boolean logic expressions mostly based on identifiers of other fragments) define whether a form field is visible or hidden. These preconditions are handled on the clientside. Initially, a start state is calculated. Subsequent fragment actions are based on change events.
The standard JavaScript library of the Interactive Forms component (validation.js
) ensures that all form fields are bound to a change event. When a change event is thrown, the JavaScript logic will show (or hide) other form fields according to their identifier match. The repeater fragment (FormFragmentRepeat
) is a special case: JavaScript preconditions are generated automatically without editor interference. The actual JavaScript functions are rendered by a presentation JSP named showFormElementClienSideFramework.jspf
which is indirectly called from the Formelement
presentation using the code sample shown below. First, the showFormelement.jspf
retrieves the showFormElementClientsideFrameworkCall.jspf
:
<wm:link var="clientsideFrameworkLink" presentationName="showFormElementClientsideFrameworkCall" elementId="${element.id}"wmstepid="${param.wmstepid}"/> <script type="text/JavaScript" src="${clientsideFrameworkLink.url}"></script>
The scope in the descriptor file of the showFormelement.jspf
presentation is FormsElement
, not FormElement
.
Next, the showFormElementClientsideFrameworkCall.jspf
calls the showFormElementClienSideFramework.jspf
. The latter jspf retrieves a significant part of the JavaScript code by executing a getClientSideFrameWork()
method call from the FormElement.java
class using the code "${element.clientSideFrameWork}"
.
Validations
Validators are assigned to form fragments. Besides the default validators, editors can choose one or more validators for each form fragment. When the form (and thus form fragment on that form) is rendered, the JavaScript functions of these validators will be added to the clientside JavaScript framework. In order to accomplish this, the getJSFunction()
method in the form logicComponentDefinition
class is used.
The standard JavaScript library of the Interactive Forms component (validation.js) ensures that when a form is submitted the validation JavaScript functions will be executed. The submit is bound through the wmpform class of the form HTML tag (<form ... class="wmpform" method="post" enctype="multipart/form-data">
) using the following JavaScript code $(".wmpform").submit(function(event) {...}
. If a JavaScript validator fails, the submission of the form to the server will be cancelled.
Precondition JavaScript expressions are defined by the editor in the user interface. However, validator JavaScript functions are fixed to the logic component entity. Because there is no maintenance interface for these components in the Interactive Forms component, these functions should be shipped with the plugin. If a developer wants to add validator logic components to the XperienCentral framework, the JavaScript involved has to be known and referenced in the component definition in the Activator. This must also be done for the language labels that are mapped to the validation.
If the current clientside JavaScript framework architecture is not suited for a specific website implementation, the form element structure can be re-implemented. This procedure is not explained in this document however a discussion on how to extend the JavaScript framework using AJAX calls to the FormHandlerServlet for both form handling and validation appears in Clientside routing: AJAX development using the JSON interface.
Generate Your Own Plugin Using the Interactive Forms Archetypes
The Interactive Forms component is as an extensible framework. In order to generate the source for the Interactive forms plugins, follow the steps below. In the code samples below, settings.xml
refers to the XML settings file that normally resides in the root of your XperienCentral installation. Be sure that you refer to the correct path where settings.xml
resides on your system.
Execute the following command to generate the
helloworldfragment
plugin:mvn archetype:generate -DinteractiveMode=false -DarchetypeGroupId=nl.gx.forms.wmpformarchetypes -DarchetypeArtifactId=wmpformfragment-archetype -DarchetypeVersion=10.10.0 -DgroupId=com.gxwebmanager.helloworld -DartifactId=helloworld -Dclassprefix=HelloWorld -s <my_path_to_settings.xml>\settings.xml
Execute the following command to generate the
helloworldlogicprovider
plugin:mvn archetype:generate -DinteractiveMode=false -DarchetypeGroupId=nl.gx.forms.wmpformarchetypes -DarchetypeArtifactId=wmpformlogicprovider-archetype -DarchetypeVersion=10.10.0 -DgroupId=com.gx.webmanager.helloworld -DartifactId=helloworld -Dclassprefix=HelloWorld -s <my_path_to_settings.xml>\settings.xml
The helloworldformfragment Plugin
The fragment archetype generates the following files.
File | Description |
---|---|
Activator.java | The bundle activator |
CustomFragment.java | The interface representing the custom fragment |
CustomFragmentController.java | The controller of the fragment component |
CustomFragmentFBO.java | The form backing object (FBO) of the fragment component |
CustomFragmentImpl.java | The component and fragment implementation (Business Object) |
editCustomFragment.jspf | The JSP that renders the properties of the fragment in the right column of the maintenance panel |
| The US English language file |
| The Dutch language file |
| The Descriptor of the JSP rendering the fragment on the |
| The JSP that renders the fragment in both the Edit environment and the frontend of the website environment |
| The icon associated with the fragment that appears in the Insert menu |
The usage of the presentation JSPs is different than JSPs for custom elements because it is used in both the frontend and backend.
Developing a New Form Fragment Plugin
Now that the custom fragment plugin has been created, it can be modified to fit your design. If you want to try your plugin, build and deploy it to the XperienCentral platform.
- Open a Command prompt.
- Navigate to the folder where your plugin is located.
- Execute the following command. Be sure that you specify the correct path to your
settings.xml
file.mvn -s ..\..\XperienCentral9\settings.xml clean package
Copy the file
helloworldelement-1.0.0.jar
from thetarget
folder to the<xperiencentral-root>\work\deploy
folder.
The fragment is now available in XperienCentral. The custom fragment has to be activated first (this can be done by navigating to Maintain> Interactive Forms> Settings in the Interactive Forms user interface. After activation, the element appears in the Insert column.
Modifying the Presentation of the Form Fragment
In the component definition in the bundle Activator, the scope of the custom fragment is defined. If you want to add extra presentations to a fragment, the descriptor file belonging to the JSP must contain a scope tag that points to the correct scope. The descriptor file and JSP file can be saved in the same folder as the showCustomFragment.xml
file and showCustomFragment.jspf
file. The JSP is also used for rendering the fragment in the maintenance panel, therefore check the JSP to ensure that it is suitable for both environments.
Adding a Property Field to the Form Fragment
To add a property field to the fragment, perform the following steps:
- Add the property to the Business object (
CustomFragmentImpl.java
). - Add the property to the form backing object (
CustomFragmentFBO.java
). - Add a label to the language files in order to translate your property in the supported languages.
- Add the field to the properties JSP (
editCustomFragment.jspf
). - Add the field to the show/preview JSP (
showCustomFragment.jspf
).
These steps are identical to the steps that are described in Adding an Input Field to a Component.
Adding a default validation to the form fragment
The implementation class (for example, CustomFragmentImpl
) must contain the method getDefaultValidators()
. This method returns the validator(s) that you want to use by default. If the fragment is added to the form step in the Interactive Forms interface, this set of validators will be assigned. The validators that are retrieved by this method have to be present in the framework. Create Your Own Form Logic Components explains how to create a fragment validator. Suppose a fragment validator is present in the bundle activator:
form logicProviderComponentDefinitionImpl def = new form logicProviderComponentDefinitionImpl(false, form logicProviderComponentDefinition.FORM_LOGIC_PROVIDER_TYPE_FRAGMENTVALIDATOR, pluginConstants.MY_VALIDATOR_IDENTIFIER);
This validator can be retrieved using the same identifier:
FormManager formDAO = (FormManager) FrameworkFactory.getInstance().getFramework().getService(FormManager.class.getName()); // Add my validator FragmentValidatorDefinition myValidatorDef =formDAO.getFragmentValidatorDefinitionByIdentifier(com.gxXperienCentral.helloworld.pluginConstants.MY_VALIDATOR_IDENTIFIER); if (numberDef !=null) { result.add(new SimpleFragmentValidator(this, myValidatorDef)); }
Create your Own Form Logic Components
Form Logic is a generic term for handlers, routers, form validators and fragment validators. A form logic component implements both the form logicProvidercomponent
interface and the form logicProviderService
interface. Usage of these interfaces requires the implementation of the run()
method.
The return type of this method is a RoutingResult
. This result can be either a RoutingResult
or null
.
class implements form logicProvidercomponent,form logicProviderService { public RoutingResult run(FormScope scope, Map<String, Object> parameters,Map<String, Object> languageLabels) {...} }
If null
is returned, there is no routing result, therefore the next logic component in the queue will be handled. If a RoutingResult
is returned, execution within the handler queue is stopped and the routing result is handled. Because the form logicProvidercomponent
class is the action class of the logic component, the form logicProviderComponentDefinition
object will register the form logic component. This definition is added to the bundle Activator of a plugin.
Only one instance of the action class will be present within the XperienCentral framework. Therefore, this class should not keep track on any form specific session or user information, since it’s used by multiple forms. Instead, use the FormScope
which is present as a parameter of the run()
method.
When a form logic (provider) component (definition) is added to the XperienCentral framework (by deploying a plugin), a form logicComponentDefinition
object will be registered. In other words, the form logicComponentDefinition
object is the entity reference of the form logic component. The method getform logicProviderComponent()
returns the form logic provider component of this entity.
The subinterfaces of form logicComponentDefinition
are:
ConditionalComponentDefinition
FormHandlerDefinition
FormRouterDefinition
FormValidatorDefinition
FragmentValidatorDefinition
The Interactive Forms component uses these subinterfaces according to the form logicProviderType
that is assigned to the form logicProviderComponentDefinition
in the activator class. Registering a logic component in the bundle Activator is similar for all form logic component types. The following code sample shows how to define a fragment validator:
private form logicProviderComponentDefinition getBankAccountValidatorDefinition() { form logicProviderComponentDefinitionImpl def = new form logicProviderComponentDefinitionImpl(false, form logicProviderComponentDefinition. FORM_LOGIC_PROVIDER_TYPE_FRAGMENTVALIDAT OR, pluginConstants.BANKACCOUNT_VALIDATOR_IDENTIFIER); def.setId(pluginConstants.BANKACCOUNT_VALIDATOR_COMPONENT_ID); def.setName(pluginConstants.BANKACCOUNT_VALIDATOR_COMPONENT_NAME); def.setDescription(pluginConstants.BANKACCOUNT_VALIDATOR_COMPONENT_DESCR); def.setJsFilename("bankaccount_validator.js"); def.setTypeId(form logicProviderComponentType.class.getName()); def.setImplementationClassName(Simpleform logicComponent.class.getName()); def.setProperties(new Hashtable<String, String>()); def.addRequiredParameter(ParameterDefinition.PARAMETER_TYPE_FORMFRAGMENT_IDENTIFIER, nl.gx.forms.wmpformapi.pluginConstants.VALIDATOR_FRAGMENT_PARAMETER, nl.gx.forms.wmpformapi.pluginConstants.VALIDATOR_FRAGMENT_PARAMETER); def.setAllowedToRunClientside(true); def.addLanguageLabelMapping("INVALID_BANKACCOUNT", "invalid_bankaccount"); def.setTitleLanguageLabel("wmpform logicprovidersnl.validator.bankaccountvalidator.title"); return def; }
The constructor of the component definition requires a form logicProviderComponentDefinition
type as a (second) parameter. The form logicProviderComponentDefinition
defines four types:
FORM_LOGIC_PROVIDER_TYPE_FRAGMENTVALIDATOR
FORM_LOGIC_PROVIDER_TYPE_FORMVALIDATOR
FORM_LOGIC_PROVIDER_TYPE_ROUTER
FORM_LOGIC_PROVIDER_TYPE_HANDLER
Depending on the role of the form logic component, one of these types should be used. The last parameter is the (internal) identifier of the component. The first parameter connects to the ComponentDefinition
class of the XperienCentral framework and identifies the associated component as a component requiring a license.
setJsFilename
setJsFilename
Indicates which JavaScript file is the base for the JavaScript that will be used for the action of the form logic component. If the form logic component doesn't require JavaScript logic, this setter can be omitted. The function that is implemented in the .js
file should have the same name as the filename. For example: If the file is called bankaccount_validator.js
, the implemented JavaScript function should be function bankaccount_validator(parameters, languageLabels)
.
setImplementationClassName
The implementation class of this component is the Simpleform logicComponent
class. This class implements the form logicProviderComponent
class and has been used as a base or dummy form logic provider component. It is possible to use such a (empty) class in your plugin as the implementation class for a form logic provider that has only a JavaScript implementation. A dummy class should resemble the following:
package nl.gx.forms.yourform logicprovidertype.impl; import nl.gx.forms.wmpformapi.plugin.form logicprovidertype.form logicProviderComponent; import nl.gx.XperienCentral.plugin.foundation.ComponentBase; public class Simpleform logicComponent extends ComponentBase implements form logicProviderComponent { }
This class doesn't implement the form logicProviderService
interface. If no Java logic action (for example, validation action) is required, the form logicProviderService
doesn't have to be implemented.
addLanguageLabelMapping
The addLanguageLabelMapping()
method requires 2 parameters. The first parameter is the identifier that uses the Java/JavaScript code to retrieve the value if this label, for example, the language label can be retrieved from the JavaScript using the code "languageLabels["INVALID_BANKACCOUNT"]"
. The second parameter is the identifier as it appears in the language_labels_xx
.properties
files (where xx
is the locale value). These properties files have to be shipped with the plugin.
addRequiredParameter / addOptionalParameter
The addRequiredParameter()
method and addOptionalParameter()
method require 3 parameters. The first parameter refers to the parameter type. These types are:
File | Description |
---|---|
| Indicates a parameter is of type Boolean. |
| Indicates a parameter is of type FormFragment Identifier. |
| Indicates a parameter is of type FormFragment Value. |
| Indicates a parameter is of type LanguageLabel. |
| Indicates a parameter is of type Page. |
| Indicates a parameter if of type FormStep. |
| Indicates a parameter is of type String. |
| Indicates a parameter is of type Textarea. |
The second parameter refers to the name/identifier of this parameter. This identifier can be used as a reference in the run()
method of your form logicProviderComponent
class. In Form Validator the retrieval parameters values are described. The third parameter refers to the language label that will be used in the user interface. (It should be present in the language_labels_xx
.properties file.) For a fragment validator, one specific parameter has to be defined. It should be a required parameter of the PARAMETER_TYPE_FORMFRAGMENT_IDENTIFIER
type. For example:
def.addRequiredParameter(ParameterDefinition.PARAMETER_TYPE_FORMFRAGMENT_IDENTIFIER, nl.gx.forms.wmpformapi.pluginConstants.VALIDATOR_FRAGMENT_PARAMETER, nl.gx.forms.wmpformapi.pluginConstants.VALIDATOR_FRAGMENT_PARAMETER); }
This parameter allows the Interactive Forms component to assign a fragment validator to a form fragment. Next to this parameter, extra parameters can be defined.
setAllowedToRunClientside
setAllowedToRunclientside
determines whether the form logic (that is, the fragment validator) can run on the client side. This setting is merely for security purposes, for example when the JavaScript source code of a form logic component should not be exposed because it contains a business secret.
More on the Fragment Validator
Creating a simple (that is, JavaScript only) fragment validator definition is fairly straightforward. First, add a componentDefinition
to your bundle Activator. Next, implement a JavaScript validator function. Finally, implement a dummy form logicProviderComponent
class and, after deploying the plugin, the validator is ready to use.
As mentioned in the Architectural Overview, the validation JavaScript function(s) of a fragment will be retrieved by the JavaScript clientside framework by accessing the connected validators. These fragment validators can be both default validators and validators assigned by the editor. The validation is performed on the clientside (with JavaScript) when the form is submitted. At this point, two questions could arise: In what way is validation dealt with on the server side, and how can a fragment validator be assigned as a default validator to a specific fragment? Let's address the first question first. When the form is posted to the webserver, either a JavaScript or Java validation can be executed. The FormEngineService
will handle the validation of a fragment:
private void validateFormFragment(BasicFormFragment fragment, FormScope scope) throws FormManagerException { // Only continue if the fragment precondition was true! ... if (conditionResult) { if (fragment instanceof BasicFormInputFragment) { ... BasicFormInputFragment ssf = (BasicFormInputFragment) fragment; ... // getDefaultValidators() returns the set of validator instances that are always present for fields of the implemented type. List<Basicform logicComponent> defaultValidators = ssf.getDefaultValidators(); if (defaultValidators != null) { for (Basicform logicComponent validator : defaultValidators) { executeform logicComponent(validator, scope, fragment); } } ... //getCustomValidators() returns the set of validator instances that were configured by the user. List<Basicform logicComponent> customValidators = ssf.getCustomValidators(); if (customValidators != null) { for (Basicform logicComponent validator : customValidators) { executeform logicComponent(validator, scope, fragment); } } } }
In the code example above, the validateFormFragment()
method checks to see whether the precondition for the fragment is met. If so, it retrieves the validators that are assigned to the form fragment. The executeform logicComponent()
method that is called for a set of validators will retrieve and execute the validation action. This action could be either based on JavaScript or Java code. First, the JavaScript logic will be retrieved from the form logicComponentDefinition
. If no JavaScript is defined, the form logicProviderComponent
object is retrieved and the run()
method will be called. However, this is only done when the form logicProviderComponent
is an instance of the form logicProviderService
, therefore be sure that custom-defined form logicProviderComponent
implements the form logicProviderService
interface.
If neither a JavaScript source nor a Java implementation is available, the FormengineService
will log the (severe) message: No implementation found for form logic component. Additionally, a (custom) FormFagment
class should (indirectly) implement the BasicFormInputFragment
interface if you want to perform server side validation. This interface requires the implementation of the method getDefaultValidators()
. This particular method (again) could bring up the question, how can default Fragment validators be assigned to fragments?
If you implement the getDefaultValidators()
method, you determine yourself which validators are assigned by default. The HelloWorld plugin constructed from the fragment archetype uses a hardcoded mechanism to assign the default validators. In the getDefaultValidators()
method of the form fragment, a validator is directly retrieved from the Interactive Forms component using the FormManager
Service which uses the identifier as a reference.
This identifier is already introduced in the component definition of the validator in the bundle Activator:
form logicProviderComponentDefinitionImpl def =new form logicProviderComponentDefinitionImpl(false, form logicProviderComponentDefinition.FORM_LOGIC_PROVIDER_TYPE_FRAGMENTVALIDATOR,pluginConstants.HELLOWORLD_VALIDATOR_IDENTIFIER);
Thus, the same identifier allows you to retrieve this specific validator from the FormFragment
class:
FragmentValidatorDefinition myDefaultDef=myFormManager.getFragmentValidatorDefinitionByIdentifier(pluginConstants.HELLOWORLD_VALIDATOR_IDENTIFIER);
Form Validator
Unlike the Fragment Validator, which points to one specific fragment, a form validator can validate objects more freely. Editors assign a form validator to form steps. Setting the properties of the form validator is done on the canvas where the logic entities of a form step are visualized (the Handlers and Routers panel). In order to specify which objects have to be validated, the form validator must have one or more parameters.
To include a Form Validator in your plugin start with adding the definition type FORM_LOGIC_PROVIDER_TYPE_FORMVALIDATOR
in your bundle Activator. For example:
form logicProviderComponentDefinitionImpl def =new form logicProviderComponentDefinitionImpl(false, form logicProviderComponentDefinition.FORM_LOGIC_PROVIDER_TYPE_FORMVALIDATOR,pluginConstants.HELLOWORLD_MYFORMVALIDATOR_IDENTIFIER); def.setId(pluginConstants.DATEDAYRANGE_VALIDATOR_COMPONENT_ID); def.setName(pluginConstants.DATEDAYRANGE_VALIDATOR_COMPONENT_ID); def.setDescription(pluginConstants.DATEDAYRANGE_VALIDATOR_COMPONENT_DESCRIPTION); def.setTypeId(form logicProviderComponentType.class.getName()); def.setImplementationClassName(Simpleform logicComponent.class.getName()); def.setJsFilename("helloworld_formvalidator.js"); def.setProperties(new Hashtable<String,String>()); def.addRequiredParameter(ParameterDefinition.PARAMETER_TYPE_FORMFRAGMENT_IDENTIFIER "anyfragment","helloworldformvalidator.anyfragment"); def.addOptionalParameter(ParameterDefinition.PARAMETER_TYPE_STRING,"anyinteger","helloworldformvalidator.anyinteger"); def.setAllowedToRunClientside(false); def.addLanguageLabelMapping("ILLEGAL_VALUE","helloworldformvalidator.illegal_value"); def.addLanguageLabelMapping("VALUE_TO_BIG","helloworldformvalidator.fragment_value_max_exceeded"); def.setTitleLanguageLabel("helloworldformvalidator.title");
Given the definition above, the Interactive Forms component expects a JavaScript implementation of the validation action. Since the library file is named helloworld_formvalidator.js
, the JavaScript function that must be implemented must be named helloworld_formvalidator()
.
The following code example shows a way in which the parameters can be retrieved:
function helloworld_formvalidator(parameters, languageLabels) { var myValue = this[parameters.anyfragment].value; var nrDays = parseInt(parameters.anyinteger); if (myValue != undefined && myValue != "") { error = false; if (!myvalue.match(/[0-9]+\.?[0-9]*/)) { this[parameters.anyfragment].errors["ILLEGAL_VALUE"] = languageLabels["ILLEGAL_VALUE"]; error = true; } if (!error) { // now check the value if (nrDays != 'NaN') { if (nrDays >= myValue) { this[parameters.anyfragment].errors["VALUE_TO_BIG"]= languageLabels["VALUE_TO_BIG"]; } } } }
The code example shows that parameters are retrieved by referring to the identifier value of the parameter as defined in the component definition. This validator JavaScript function doesn't return anything. This is not needed because the only return value of a form logic component can be a RoutingResult. (The form engine service ignores any other return objects.) However, if the error container of the fragment contains a message, the form engine service will redirect it to the current form step and a possible RoutingResult of another logic component will be discarded.
Form Handler
The Form Logic components discussed so far have been limited to JavaScript implementations of the action logic. In this part the focus is on a Java implementation, more specifically a Java implementation of a Form Handler. Defining the form handler starts with a registration via the Activator class of your plugin. This class should register a form logicProviderComponentDefinition
object of the Handler Type (FORM_LOGIC_PROVIDER_TYPE_HANDLER
). For example:
form logicProviderComponentDefinitionImpl def = new form logicProviderComponentDefinitionImpl(false,form logicProviderComponentDefinition.FORM_LOGIC_PROVIDER_TYPE_HANDLER,"YOURIDENTIFIERHERE"); def.setTypeId(form logicProviderComponentType.class.getName()); def.setImplementationClassName(YOURHANDLERCLASS.class.getName()); def.addLanguageLabelMapping(YOURHANDLERCLASS.ERROR_SENDEMAIL_FAILED,"error_sendemail_failed"); def.addLanguageLabelMapping(YOURHANDLERCLASS.MESSAGE_SENDEMAIL_OK,"message_sendemail_ok"); def.setTitleLanguageLabel("helloworld.handler.YOURHANDLERCLASS.title");
Language labels needed in the code can be registered via calls to addLanguageLabelMapping()
in the bundle Activator. Parameters can be added to the handler by using either the call addRequiredParameter()
or addOptionalParameter()
in the bundle Activator:
def.addRequiredParameter( ParameterDefinition.PARAMETER_TYPE_STRING, //Type parameter YOURCONSTANTSCLASS.PARAM_URL, //Name "form logic.handler.addtosessionhandler.url"); // Titel of interface def.setTypeId(form logicProviderComponentType.class.getName()); def.setImplementationClassName(YOURHANDLERCLASS.class.getName()); def.addLanguageLabelMapping(YOURHANDLERCLASS.ERROR_SENDEMAIL_FAILED,"error_sendemail_failed"); def.addLanguageLabelMapping(YOURHANDLERCLASS.MESSAGE_SENDEMAIL_OK,"message_sendemail_ok"); def.setTitleLanguageLabel("helloworld.handler.YOURHANDLERCLASS.title");
These parameters can be used in the run()
method of the handler class.
public RoutingResult run(FormScope scope, Map<String, Object> parameters, Map<String, Object> languageLabels) { // All parameters enter the run() method through a Map object String urlParameter = (String) parameters.get(YOURCONSTANTSCLASS.PARAM_URL); … }
Pre-handler
A prehandler is a form logic component that has to be executed prior to rendering of the form. It will most likely be used to set values in the forms session that support the rendering of the form such as prefilling and possibly authorization. As descibed in Presentation of Form Entities, the form session is stored in the HTTP session. However, standard frontend XperienCentral JSPs do not have access to the HTTP session, therefore, the HTTP session is retrieved via a Server Side Include directive:
<wm:link var="ssiLink" fromFormSsi="true" ssiObjectId="${element.id}" ssiObjectClassName="nl.gx.forms.wmpformelement.api.FormElement" usehttpsession="true" passOn="wmformid,wmstepid,orgurl" />
Form Router
A key component of the form logic that implements a router is the routing result. The routing result object that is returned from the run() method of the form router has to have a specific routing result type. If the type is not known to the framework, the (warning) message Unknown RoutingResult type. Sending the user back to the referring page will be shown in the log (originating from the FormHandlerHelper
logger). The following Routing result types that are supported by the framework are shown in the table below:
File | Value | Description | Required Setter |
---|---|---|---|
| 0 | Indicates a routing to a specific URL. |
|
| 1 | Indicates a routing to a specific page. |
|
| 2 | Indicates a routing to a specific form step of the current form/form version. |
|
| 3 | Indicates a routing to the previous form step. | |
| 4 | Indicates a routing to the current form step. | |
| 5 | Indicates a routing to the next form step. | |
| 6 | Indicates a routing to a special page. |
|
| 7 | Indicates an error occured. |
Some routing result types require a setter to be executed. The properties that are set are used for normalizing the RoutingResult
. In this case, normalizing means that the RoutingResult
types are reduced to a subset after the router has been executed. However, not all RoutingResult
types are visible in the end result of the form routing action. For example, the RoutingResult
that is returned in case of clientside routing will be a normalized routing result.
Group | Internal Name | Normalized Result |
---|---|---|
1 |
|
|
1 |
|
|
1 |
|
|
2 |
|
|
2 |
|
|
2 |
|
|
2 |
|
|
3 |
|
|
Another aspect of the RoutingResult
object is setting the end of flow. The form element will automatically start and end a form session. However, if a router is navigates away from the page that renders the form, the form element code will not be executed and the form session will not be closed. If this is the case, the router itself has to notify the framework by setting the endOfFlow
flag.
public RoutingResult run(FormScope scope, Map<String, Object> parameters, Map<String, Object> languageLabels) { RoutingResult result = new RoutingResult(); result.setType(RoutingResult.ROUTE_TO_PAGE); result.setPage((Page) parameters.get(PARAM_PAGE)); result.setIsEndOfFlow(true); return result; }
Bundle activator:
private form logicProviderComponentDefinition getGoToPageRouterDefinition() { Class myClass = GoToPageRouter.class; form logicProviderComponentDefinitionImpl def = new form logicProviderComponentDefinitionImpl(false, form logicProviderComponentDefinition.FORM_LOGIC_PROVIDER_TYPE_ROUTER, "GoToPage"); def.setId(myClass.getName()); def.setName(myClass.getName()); def.setDescription(myClass.getName()); def.setTypeId(form logicProviderComponentType.class.getName()); def.setImplementationClassName(myClass.getName()); def.setProperties(new Hashtable<String, String>()); def.addRequiredParameter(ParameterDefinition.PARAMETER_TYPE_PAGE, GoToPageRouter.PARAM_PAGE, "page"); def.setTitleLanguageLabel("wmpform logic.router.goto_page_router.title"); return def; }
Advanced Development
Advanced Form Fragment Development
As for all plugin's, the bundle Activator defines the set of components via component definitions specified in the current plugin. The FormFragmentComponentDefinition
extends the general ComponentDefinition
class used for adding components through a plugin (for example a custom element component). The definition specification in the Activator is similar to other XperienCentral components:
FormFragmentComponentDefinitionImpl def = new FormFragmentComponentDefinitionImpl(false); def.setId(CustomFragmentImpl.class.getName()); def.setName(pluginConstants.plugin_ID + " " + CustomFragmentImpl.class.getName()); def.setDescription("Implements the " + pluginConstants.plugin_ID + " " + CustomFragmentImpl.class.getName()); def.setTypeId(FormFragmentComponentType.class.getName()); def.setProperties(new Hashtable<String, String>()); def.setInterfaceClassNames(new String[]{Component.class.getName(), FormFragmentComponent.class.getName()}); def.setInstanceClassName(CustomFragmentImpl.class.getName()); def.setImplementationClassName(CustomFragmentImpl.class.getName()); def.setControllerClassName(CustomFragmentController.class.getName()); def.setEntityClassNames(new String[]{CustomFragmentImpl.class.getName()}); def.setPersistenceManagerClassName(DefaultJcrPersistenceManager.class.getName()); def.setEntityFactoryClassName(DefaultJcrEntityFactoryImpl.class.getName()); def.setInterfaceClassName(CustomFragment.class.getName()); def.setIcon("img/customFragment.png"); def.setLanguageLabel("panel.wmpformfragments.customFragment"); def.setPresentationScope(presentationScope); def.setFragmentType(nl.gx.forms.wmpformapi.pluginConstants.FRAGMENT_TYPE_INPUT); def.setRanking(ranking); def.setWrapperClassNames(new String[]{CustomFragmentImpl.class.getName()}); ComponentDependency formDAODependency = createDependency(FormManager.class.getName()); ComponentDependency entityManagerDependency = createDependency(EntityManager.class.getName()); def.setDependencies(new ComponentDependency[]{ formDAODependency, entityManagerDependency}); return def;
Some setters, however, are specific to the Interactive Forms component, for example FragmentType
and Ranking
. The ranking is an arbitrary integer that is used to order the fragments in the form maintenance panel.
FormFragment Interface Types
The fragment type is one of the static strings defined in the pluginConstants file of the wmpformapi. Typically, of type static
, input
or group
.
Constants | Value | Description |
---|---|---|
| input | A fragment type that supports input. |
| group | A type of fragment that is used for combining multiple fragments (for example subform fragments). |
| static | A fragment type that doesn't support input, for example, a paragraph form fragment. |
Form fragments are grouped according to their types in the panel.
Form Fragment Ordering in the Interface
The ordering of the fragment components is defined to a ranking within a fragment type group. The standard set of fragments is summarized in the following table.
Name | Ranking | Type |
---|---|---|
| 10 |
|
| 20 |
|
| 30 |
|
| 40 |
|
| 50 |
|
| 60 |
|
| 70 |
|
| 80 |
|
| 90 |
|
| 100 |
|
| 10 |
|
| 20 |
|
| 30 |
|
| 40 |
|
| 10 |
|
| 20 |
|
| 30 |
|
| 40 |
|
Influence of the Fragment Presentation on the Backend
If you introduce new presentation(s) for existing or custom form fragments or form steps, keep the following in mind: In the XperienCentral platform, the Interactive Forms component uses the same presentation logic for both the frontend and the backend interfaces. Therefore, the JSP presentation files have to take into account events that might occur in backend. For example:
<forms:fragmentDiv fragment="${formFragment}" fragmentType="formsection"> <c:choose> <c:when test="${empty formFragment.formSection && mode == 'preview'}"> <fmt:message key="panel.wmpformfragments.overview.canvastext.noformsectionselected" /> </c:when> <c:when test="${empty formFragment.formSection.activeVersion && mode == 'preview'}"> <fmt:message key="panel.wmpformfragments.overview.canvastext.formsectioninactive" /> </c:when> <c:otherwise> <c:if test="${not empty formFragment.formSection.activeVersion}"> <wm:render object="${formFragment.formSection.activeVersion}" fromFragment="true" /> </c:if> </c:otherwise> </c:choose> </forms:fragmentDiv>
Form import/Export
Importing and exporting forms, form fragments and/or form logic entities is implemented as a standard component in the Interactive Forms component. If custom components are introduced, the FormManager
has to be able to find the entities that should be picked up during the export/import process. If specific custom properties are stored using the @Child
annotation, the import/export process will pick up these properties automatically.
The @Child
annotation can be used to indicate that the object should be stored as a reference property not as a child property of this entity. As a result, the physical location of the object will be such that there will be a parent-child relation between the entity and the object to which is referred to by the @Child
annotation. In the case of the JCR, this means that the node that corresponds to the entity is a parent node of the node that corresponds to the object of the property marked with the @Child
annotation.
Clientside Routing: AJAX Development Using the JSON Interface
In some projects, you need to obtain a form-post action without refreshing the complete page. Clientside routing combined with JavaScript AJAX functionality helps you (as a developer) to meet this requirement. When clientside routing is used, a typical JSON response looks like this:
{ "routingResult": { "type": "2", "step": "garage", "hasErrors": "false" } }
The first part always consists of the RoutingResult
and type. Also, the hasErrors
remark will be present in the response. Depending on the RoutingResult
type, the center of the response (that is, step) will have a different format. The following table contains a breakdown of the response according to RoutingResult
type:
RoutingResult type identifier | Type identifier | Value |
---|---|---|
| url |
|
| url |
|
| url |
|
| Step |
|
| Step |
|
| Step |
|
| Step |
|
| httpError |
|
| httpErrorMessagege |
|
The clientside JavaScript framework is not up to date when the form rendering is bypassed via the AJAX call. The getClientSideFrameWork()
method of the Formelement.java
class helps you retrieve the right JavaScript functions, for example after routing to a new step.
For a complete AJAX solution, the retrieval of form(steps) has to be implemented as well. Form HTML code can be retrieved based on a URL that includes elementId
and presentationid
(for example, http://mywebsite.com/MyPage.htm?elementId=418808&presentationid=415177&view=ajax ).
Presentation of Form Entities
If a custom form fragment is added, the presentation scope can be set in the component definition in the activator class of your plugin definition.setPresentationScope(presentationScope
. This presentation scope is the reference value that is used in the descriptor xml
file that comes with the presentation jspf
. If you only want to add presentations for standard form entities, for example, form, form step, email input fragment, and so forth, the presentation scope is fixed. The following table gives an overview of presentation scopes for form entities:
Entity | Name | Scope |
---|---|---|
|
| |
|
| |
| TextInput |
|
| NumberInput |
|
| DateInput |
|
| PasswordInput |
|
| EmailInput |
|
| TextArea |
|
| FileUpload |
|
| Columns |
|
| Section |
|
| Button |
|
| Overview |
|
| BackButton |
|
| RadioList |
|
| CheckboxList |
|
| DropDownList |
|
| FormSection |
|
| RepeatFormSection |
|
| Paragraph |
|
| RepeatFormSectionCountFragment |
|
The presentation of form entities is supported by a couple of tags. These tags are used throughout the standard presentation that is shipped with the Interactive Forms component. The following table describes these tags.
Tag | Description |
---|---|
| Renders the HTML form tag plus contains some required hidden fields. The |
| Renders the HTML of a single fragment calling other tags ( |
| If a precondition for a fragment is defined, the string |
| |
| |
| |
| A helper tag for displaying messages. |
| A helper tag for XML escaping. |
| The |
Form step presentation:
<forms:form element="${presentationcontext.element}" formStep="${formStep}">
Form fragment presentation:
<forms:fragment fragment="${formFragment}"> <input type="text" ${extra} name="${formFragment.nestedPath}" id="wmformfragment_${identifier}" value="<forms:fragmentValue nestedPath="${formFragment.nestedPath}" />" ${widthAttribute} ${maxLengthAttribute} /><p /> </forms:fragment>
- The HTML of the form (element) is not the only factor that affects the rendering for the frontend and the backend. The CSS also influences the appearance of the form. Without specific changes to your plugins, the form will use the CSS of the frontend on the frontend and the CSS of XperienCentral on the backend (in Interactive Forms). It is possible to align the used CSS on the backend with the used CSS on the frontend.
- With the default presentation, it is possible to perform certain actions with form elements. For example, to drag and drop each form element, or to drag other elements into the Columns element. When creating your own HTML for a form element, it is important to examine the default presentations of that element and use the same HTML structure as the default presentation.
The Form Engine
The form engine (service) handles the execution of a form. Actions like form session building/preparation are performed by this engine. Further, the form logic execution process is also performed by the form engine service by calling the run()
method of the attached handlers.
The Form Scope
The form values and other state variables are stored server-side in what is referred to as form scope. The form scope is a hierarchy of scopes; for each BasicFormFragmentContainer
, a new nested and named scope exists. This class represents a single node in such a scope, and is the interface that is used by most form logic components for accessing the scope. Information not found in the current scope is automatically looked up in the parent scope.
The Form Engine Session
The FormEngineSession
object is the root of the form engine session. Within this object is a tree of FormScope
, FragmentScope
and UploadFragmentScopes
that hold the submitted values and the state of the various parts of the form. Apart from these, this class holds the identifier of the last submitted step, a reference to the BasicFormVersion
, the last access time (for timeout), the FormSessionContext
and an identifier to the form engine session itself.
The FormElement
(Java implementation class) creates and ends the form session. When an interactive form is rendered on the frontend, the FormElement
.Java code is the entry point. The first thing the FormElement
does (in its render()
method), is set up the session. Through the FormEngineService
, a FormEngineRequestHelper
object will be asked to retrieve the FormEngineSession
. The getOrCreateFormEngineSession()
method of the RequestHelper
is used for this purpose. As a result, an existing session or new session will be returned and stored in the HTTPSession
. The session object contains information such as formIdentifier
, lastSteopIdentier
, lastAccessTime
, etc.
After setting up the FormSession
, the Formelement.java
object tries to execute the preparation of the current form step by means of the following code:
prepareFormFragmentContainer(formStep, getSession().getFormScope())
In this method call on the FormEngineService
, the current step identifier and formScope
are returned as parameters. The returned result of this method is a RoutingResult
object. However, the result is ignored because the prehandling action doesn't require a routing effect. The actual result of this method call is the prefilling of form fragments and the execution of logic components that are assigned as prehandlers.
The cleaning up of the form session is initialized as follows: when the presentation JSP of the formElement
(that is, showFormElement.jspf
) is processed, the last step of a form sequence will be deducted. Next, the endsession
tag is executed.
<c:if test="${isLastStep && empty element.formStep.form logic}"> <forms:endsession /> </c:if>
The endsession
tag wil set an endSession
variable with request scope.
<c:set var="endSession" value="true" scope="request" />
As the FormElement.java
code execution continues, this value is retrieved from the request and the session is closed (by calling the endFormSession()
method of the FormEngineRequestHelper
. This only happens when the session is not ended by the form handling process (that is, FormHandlerServlet
).
Language Setting During Form Rendering
When the render()
method of the form element object is called, the language is retrieved from the page version of the current page. The metaTagValue
of the language is then added to the request. While setting up the session, the metaTagValue
of the language is also added to the FormScope
via the setLocale()
method. The language value of the form scope is retrieved throughout the Interactive Forms component, for example when executing a form logic component and when building the clientside JavaScript framework.
FormEngineRequestHelper
The form engine service is decoupled from the servlet engine through the introduction of the FormEngineRequestHelper
. The form engine request helper communicates with the servlet API and the form engine service and manages the mapping of form engine sessions to HTTP sessions. In other words, the FormEngineService
is agnostic with respect to the interface from which it is approached; it has a clean Java API that is not explicitly coupled to external APIs. This makes it properly testable and much more flexible. However, the most common use case for the form engine service is for it to be used on a website via HTTP. In order to facilitate that use-case, this class provides the necessary coupling between the servlet API and the form engine.
FormEngine Servlet
The FormHandlerServlet
handles form POSTs
. Also, when receiving a GET
request, a dump of the active form engine sessions is shown, but only on the generator when the user is logged in in the backend.
Several use cases exist for POSTing
a form, but they all have the following in common:
- The identifier of the form step is in the request parameter
wmstepid
. - The ID of the form version is in the request parameter
wmformid
. - For each fragment on the step that is submitted, one or more values may be posted. These have names that are dot-expressions relative to the form step. The dot-expression for a fragment with identifier "fragment" on a form section that is rendered in a form section fragment with identifier "section" on a form step with identifier "step1", has dot-expression
section.fragment
, which corresponds to the server-side nested pathstep1.section.fragment
. - For each fragment that is submitted, the condition value indicating whether the fragment is rendered and handled can also be updated. This is done by submitting the boolean value of the condition (true or false) with a parameter with the name equal to the nested path described above, for example
step1.section.fragment.condition=true
.
For each request, the form engine session is updated to match the submitted values and conditions. Also, the last submitted step and the last access time are updated. What happens thereafter is controlled by several request parameters. The following use cases are defined:
Use Case 1: Default Form Posting
By default, the step is submitted and validated. If validation errors occur, the user is redirected back to the form in order to correct the error(s). If validation passes, the form engine checks whether the submitted values have caused preconditions for displaying fragments on this step to have changed from false to true. If that is the case, the user is also redirected back to the form in order to fill in the newly visible fragments. If no conditions have changed from false to true, the form is handled — All form logic components for the form step are recursively executed. If an error occurs, the user is redirected back to the page. If no error occurs, but a RoutingResult
is returned by the form logicComponents
, the RoutingResult
is honored and the proper redirect is sent. By default, the user is routed to the next form step. If no next step is available, the user is redirected to the step that was just submitted.
Use Case 2: Client-side Routing
This use case is similar to what happens in use case 1, but instead of redirecting the user, a JSON response describing the RoutingResult
is sent. For example:
script type="text/JavaScript" > $(document).ready(function() { $('.wmpform').submit(function() { $('#clientsideRouting').attr('value', 'true'); $.post('/web/pluginservlet/nl.gx.forms.wmpformengine.servlet', $('.wmpform').serialize()); }); }); </script>
The type that is returned is the RoutingResult
type as described in Clientside routing: AJAX development using the JSON interface. The JavaScript code that handles the JSON response should have a decision tree that maps these types. In order to activate client-side routing a parameter clientsideRouting=true
has to be present in the request. The following JQuery script prints the JSON response on the browser:
Use Case 3: Validate a Single Fragment
When the request parameter validateFragment
is present, only a single fragment is validated. That fragment is identified by the value of the validateFragment
which is a nested path as described above. The result of the validation is returned in the form of a JSON response. For example:
<pre> { "validationResult": { "fragment": "wmfragment_2", "messages": [ ], "errors": [ {"code": "MISSING", "message": "Dit veld is verplicht" } ] } } </pre>
Use Case 4: Go Back One Step
When a request parameter with name wmback
is present after updating the values, the user is redirected to the previous form step. The value of the parameter is irrelevant because it is typically a button (the value is the value of the button). This can be used in conjunction with clientsideRouting
.
Form Session Ending
If the FormHandlerServlet
is run in the default mode, the form logicComponents
belonging to the current step are executed. Some form logicComponents
(for example the GotoPageRouter
) will navigate out of the form context to another page. In these cases, the form session should be ended by the FormHandlerServlet
since the form is longer being rendered and therefore the formElement.java
code will not handle the session. These particular form logicComponents
should indicate that form session cleanup is needed by using the setIsEndOfFlow()
method of the RoutingResult
.
RoutingResult result = new RoutingResult(); ... result.setIsEndOfFlow(true); return result;
Next, while FormHandlerServlet
is analyzing the RoutingResult
, it will discover that isEndOfFlow() equals "true"
. The FormSession
will then be ended by retrieving the getRequestHelper
from the FormEngineService
and executing the endFormSession()
method.
Other Topics
Approaching Other Forms from the Frontend
It’s possible to retrieve information from other forms that are currently active (and thus available via the request object). In the following code example a form logic component is accessing other forms:
public RoutingResult run(FormScope scope, Map<String, Object> parameters,Map<String, Object> languageLabels) { FormEngineRequestHelper helper = getFormEngine().getRequestHelper(request); // Retrieve the form identifiers via the helper object Set<String> activeFormVersionIdentifiers = helper.getAllSessions(); for (String activeFormVersionIdentifier : activeFormVersionIdentifiers) { //each active form has its own session FormEngineSession formEngineSession = helper.getFormEngineSession(activeFormVersionIdentifier); //this session can be used to retrieve form information formEngineSession.getFormVersion() .getSteps(); }
As soon as the right step is retrieved, it could even be handled from the form logic component. Here’s how to alter a step fragment value and submit a step:
//fix the step that will be addressed formEngineSession.setLastStepIdentifier(step.getIdentifier()); // alter a value formEngineSession.getFormScope().setFragmentValue("step.identifier", "value"); // submit the form. RoutingResult result = formEngine.handleStepSubmit(formSession); }