Usage of forms

The adventure PHP framework contains a comprehensive form support based on the taglib concept. The basics of forms and the available taglibs are described in the Forms chapter. This section now discusses the usage of forms, dynamic form generation, and implementation of custom form taglibs in detail.

1. Forms & controller

Forms are kind of template fragments, that exist within the APF DOM tree, but are not displayed at transformation. To display a form, a document controller is needed. The reason for it is simple: nearly every form is attached to operations which should be executed with sending the form (e.g. save data).

In order to display a form, the following controller code can be used:

PHP code
use APF\core\pagecontroller\BaseDocumentController; class FormController extends BaseDocumentController { public function transformContent(){ $form = &$this->getForm('Search'); $form->transformOnPlace(); } }

Thereby, the $form variable contains a reference on the HtmlFormTag instance, that encapsulates an APF form with all it's child elements. Having this reference, the form can be manipulated, filled, displayed, or adapted. Details can be taken from the subsequent chapters.

2. API of the form taglibs

As described within the previous section, a form can be referenced within a document controller easily. This section deals with the API of the form taglibs, that gives you an idea if the functionality included.

It is recommended to have the API documentation opened during development times. This eases work much, because each taglib has it's own API, that is described within the API documentation in detail.

The basic form element is defined within the AbstractFormControl class. There, the structure is defined as well as the basic functionality is included. Due to the fact, that all taglibs underly the page controller lifecycle each form element must implement the methods, a "normal" taglib must implement, too.

Form elements know about their validation status. Hence, each element can be queried it's status. Concerning the implementation, this means, that a form collects the status of all controls and returns the aggregated value executing the isValid() method.

Class AbstractFormControl implements the following methods:

  • isValid(): The function enables you to query the validity status from inside the form as well as from the outside.
  • markAsInvalid(): Marks a form element as invalid.
  • markAsValid(): Marks a form element as valid.
  • markAsSent(): Marks an element as sent (only relevant for forms and buttons).
  • isSent(): Checks, whether a form was sent or not.
  • isChecked(): Returns true, in case a checkbox is checked and false otherwise.
  • check(): Enables a checkbox.
  • uncheck(): Deactivates a checkbox.
  • getValue(): Returns the input of an element. Normally, this is just the content of the value attribute, except in special cases, as, for example, text areas, which store the user input as content of the tag.
  • setValue(): Sets the input of an element. Normally, this is just the content of the value attribute, except in special cases as for example text areas which store the user input as content of the tag.
  • isFilled(): Returns true in case a text field or text area was filled with text and false otherwise. This works only for text fields and text areas (form:text and form:area).
  • isSelected(): Returns true in case something was selected in a single or multi select and false otherwise. If something was selected which returns "" the method returns false too. This works only with single select or multi select (form:select and form:multiselect).
  • isVisible(): Returns the visibility status of the present form control. true indicates that the control is visible, false marks form controls that are not included to be displayed during the form generation.
  • hide(): Hides a form control from the form output.
  • show(): Sets the visibility status of a form control to true.
  • reset(): Resets a form control to it's initial state. Details can be taken from chapter 9.
  • addFilter(): Adds a filter to the current form control.
  • addValidator(): Adds a validator to the current form control.
  • getFilters(): Returns all filters registered with the current form control. This information can be used to e.g. setup Java-Script-based client-side filtering.
  • getValidators(): Returns all validators registered with the current form control. This information can be used to e.g. setup Java-Script-based client-side validation.
  • addAttribute(): Can be used to add content to an existing attribute. This is used when notifying invalid form elements.
  • notifyValidationListeners(): Notifies all listeners of a form element, that it is invalid.
  • presetValue(): This method provides presetting for form text fields. It can be directly used or overwritten for custom form elements.
  • setPlaceHolder(): This method implements a generic method to set place holder tags within sub tags of the form tag. Please note, that place holder tags must be named *:placeholder to be able to use the method.
  • addAttributeToWhitelist(): Adds the attribute applied to the method to the white list of the current form control. This causes the attribute to be included into the HTML source code.

Using this methods, the developer is supported to implement custom form taglibs as described in chapter 7.

White-listing has been introduced along with the client side form validator extension. This mechanism is responsible to only render XHTML compatible attributes to the HTML source code. Not suitable attributes - e.g. used for validation control - are ignored.

Creating custom form taglibs you must care about the attributes of your taglib. Hence, we recommend to add the desired attributes within the constructor of the taglib:

PHP code
class SpecialFormControlTag extends AbstractFormControl { public function __construct() { $this->attributeWhiteList[] = 'name'; $this->attributeWhiteList[] = 'accesskey'; } }

3. Validation

Validation is executed via the form:addvalidator, that attaches a validator to the desired form control. The validator is executed, when the click event of a form button is fired. Having a look at the timing, validation is executed within the onAfterAppend() method of each taglib. This is after the analysis phase (at this time, the onParseTime() of each taglib is executed) and the DOM tree is fully created by the page controller.

Within the onAfterAppend() method of the form:addvalidator tag, the validator is created and initialized with the necessary information (button and target form control) and given to the target element. The element itself executes the validator in case it is active and checks the result. In case of errors, the notify() method of the validator is called.

3.1. Validation status query

Since each form element knows about it's validation status within the new implementation, the form's isValid() method must query the status of each form element. Using a document controller, you can get the form status as follows:

PHP code
use APF\core\pagecontroller\BaseDocumentController; class FormController extends BaseDocumentController { public function transformContent(){ $form = &$this->getForm('MyForm'); if($form->isSent()){ echo 'Form was send'); } if($form->isValid()){ echo 'Form is valid'; } } }

Further, you can ask a single form element to return it's status using the subsequent PHP code:

PHP code
use APF\core\pagecontroller\BaseDocumentController; class FormController extends BaseDocumentController { public function transformContent(){ $form = &$this->getForm('MyForm'); $searchField = &$form->getFormElementByName('searchterm'); if($searchField->isValid()){ echo 'Search field is valid'; } } }

To decide, whether a button has been clicked, use the following code:

PHP code
use APF\core\pagecontroller\BaseDocumentController; class FormController extends BaseDocumentController { public function transformContent(){ $form = &$this->getForm('MyForm'); $button = &$form->getFormElementByID('button'); if($button->isSent()){ echo 'Search button was clicked'; } } }
To get an overview of the existent methods, please refer to the API documentation of the classes HtmlFormTag, AbstractFormControl and the dedicated form taglibs (e.g. TextFieldTag).

3.2. Display of validation messages

Introducing form validation, the fields containing wrong data are often marked with red borders and the user is presented a validation message. To achieve this, the taglibs <form:error /> (used for global error messages) and <form:listener /> can be used. As an alternative, you may use place holders, that are filled with the appropriate error messages within the controller code.

3.2.1. Form error

The <form:error /> displays it's content, in case the form is not valid - regardless which field is invalid. For this reason, the tag queries the form's status at transformation time:

Template:
APF template
<html:form name="product-form"> <form:error> Please fill in the mandatory fields! </form:error> <form:text name="code" minlength="5" maxlength="5"/> <form:text name="title" minlength="20"/> <form:area name="description" minlength="30"/> <form:button name="send" value="Save" /> <form:addvalidator class="APF\tools\form\validator\TextLengthValidator" control="code|title|description" button="send" /> </html:form>
Controller:
PHP code
use APF\core\pagecontroller\BaseDocumentController; class FormController extends BaseDocumentController { public function transformContent(){ $form = &$this->getForm('product-form'); $form->transformOnPlace(); } }

Within the <form:error /> tag, you may define further tags for formatting or content generation purposes as described in chapter display of form errors.

3.2.2. Listener

The <form:listener /> tag is aimed to display field specific errors. As noted in the introduction chapter, the notify() method is called, when a form control is considered invalid. This method then notifies all listener tags, that have registered on this event to display their content at transformation time.

In the below, field specific messages are displayed in case a desired field is marked invaluid:

Template:
APF template
<html:form name="product-form"> <form:listener control="code"> Please fill in a five letter product code! </form:listener> <form:text name="code" minlength="5" maxlength="5"/> <form:listener control="title"> Please fill in a title with at least 20 characters! </form:listener> <form:text name="title" minlength="20"/> <form:listener control="description"> Please provide a verbose product description! </form:listener> <form:area name="description" minlength="30"/> <form:button name="send" value="Save" /> <form:addvalidator class="APF\tools\form\validator\TextLengthValidator" control="code|title|description" button="send" /> </html:form>
Controller:
PHP code
use APF\core\pagecontroller\BaseDocumentController; class FormController extends BaseDocumentController { public function transformContent(){ $form = &$this->getForm('product-form'); $form->transformOnPlace(); } }

Within the <form:listener /> tag, you may define further tags for formatting or content generation purposes as described in chapter listener.

3.3. Manual validation within the document controller

In some kind of situation it may be easier or faster to do form validation within a document controller instead of writing a validator. In order to allow marking form controls a new form control method was introduced in Version 1.14.

As an example, the form field searchterm contained in the subsequent form should be validated using a custom rule within a document controller:

APF template
<html:form name="search"> <label for="searchterm">Search term:</label> <form:text id="searchterm" name="searchterm" value="" /> <form:button name="search" value="GO" /> </html:form>

The corresponding controller contains the following code to invalidate the search term field in case it contains an e:

PHP code
use APF\core\pagecontroller\BaseDocumentController; class SearchController extends BaseDocumentController { public function transformContent() { $form = &$this->getForm('search'); $searchTerm = &$form->getFormElementById('searchterm') if(stripos($searchTerm->getValue(), 'e') !== false) { $searchTerm->markAsInvalid(); $searchTerm->appendCssClass(AbstractFormValidator::$DEFAULT_MARKER_CLASS); } $form->transformOnPlace(); } }

Sending the form including any word that contains the letter e the searchterm field is technically and optically marked as invalid. The latter is done by adding the default validation marker css class. In case you want to apply your own css class you can do this by applying a hard-coded class name or by taking it from the form control's attribute list (e.g. valmarkerclass).

4. Handle form content

A frequent job with the handling of forms is the readout of values. To support this, the following methods can be used in the document controller:

  • getFormElementByID(): Returns a form element selected by id (Attribute: id).
  • getFormElementByName(): Returns a form element selected by name (Attribute: name).
  • getFormElementsByTagName(): Returns a list of form elements selected by tag name (z.B. form:text).

4.1. Select by name

In case you are facing the task to gather the values of the form described in chapter 3.2.2., you can use the subsequent controller code:

PHP code
use APF\core\pagecontroller\BaseDocumentController; class FormController extends BaseDocumentController { public function transformContent(){ $form = &$this->getForm('product-form'); if($form->isSent() && $form->isValid()){ $code = &$form->getFormElementByName('code'); echo 'product code: '.$code->getValue(); $title = &$form->getFormElementByName('title'); echo 'product title: '.$title->getValue(); $description = &$form->getFormElementByName('description'); echo 'product desc: '.$description->getValue(); } } }
Using text ares, the content of the tag is not contained in the value attribute but within the content of the tag. This is identical to the HTML tag definition.

Due to the fact, that an APF form is a DOM subtree containing form taglibs, a place holder within an error tag can be addressed with two steps. First, get the instance of the error tag, and second get the place holder's instance from the error tag. To have a dedicated example, consider the following form definition:

APF template
<html:form name="product-form"> <form:error name="error"> <html:placeholder name="ph1" /> </form:error> ... </html:form>

To fill the place holder within the <form:error /> tag use the following controller code:

PHP code
use APF\core\pagecontroller\BaseDocumentController; class FormController extends BaseDocumentController { public function transformContent(){ $form = &$this->getForm('product-form'); $error = &$form->getFormElementByName('error'); // simple way: $error->setPlaceHolder('ph1','My placeholder value'); // alternative way: $placeHolder = $error->getFormElementByName('ph1'); $placeHolder->setContent('My placeholder value'); } }
To select a form element by id, the same procedure can be used. Please ensure, to use the content of the id attribute of the desired form element in combination with the getFormElementByID() method.

4.2. Selection by tag

In some cases, it may be useful to select form elements by it's tag name. For this application case, the getFormElementsByTagName() method is available. It returns a list of form controls filtered by the given tag name.

In combination with the Generic o/r mapper the method can be used to read the content of the form elements and directly map them to a GenericDomainObject. Achieving this, you can use the following code:

PHP code
use APF\core\pagecontroller\BaseDocumentController; class EditController extends BaseDocumentController { public function transformContent(){ $form = &$this->getForm('...'); $textFields = &$form->getFormElementsByTagName('form:text'); $user = new GenericDomainObject('User'); $count = count($textFields); for($i = 0; $i < $count; $i++){ $user->setProperty( $textFields[$i]->getAttribute('name'), $textFields[$i]->getValue() ); } } }

Please note, that the method returns only those elements which exist as direct child nodes in all cases.

5. Manipulation of forms

The implementation of the form taglibs of the framework included several opportunities to manipulate form elements or their values. The next chapters thus describe solutions for the most frequent tasks.

5.1. Form presetting

To preset form controls (e.g. within an edit dialog) the

  • getFormElementByName(),
  • getFormElementByTagName(), or
  • getFormElementByID()

methods can be used to gather an instance of the desired form control. After that, you can take one of the standard method

  • getAttribute()

or

  • setAttribute()

respectively to retrieve or fill the content.

The subsequent code box defines a form, that is filled using the php code below:

Template:
APF template
<html:form name="UserEdit" method="post"> <strong>FirstName</strong>: <form:text name="FirstName" /> <br /> <strong>LastName</strong>: <form:text name="LastName" /> <br /> <br /> <form:button name="Edit" value="Save" /> <form:hidden name="userid" /> </html:form>
Controller:
PHP code
$form = &$this->getForm('UserEdit'); $userID = &$form->getFormElementByName('userid'); $userID->setAttribute('value','...'); $firstName = &$form->getFormElementByName('FirstName'); $fFirstName->setAttribute('value','...'); $lastName = &$form->getFormElementByName('LastName'); $lastName->setAttribute('value','...');

5.2. Pre-filling of form controls

Treatment of select and multi select fields is a bit different to normal form controls. But, the framework provides methods to ease pre-filling of select fields. The subsequent code is used to pre-fill a form, that contains select fields in addition to normal text fields:

APF template
<html:form name="UserCreate" method="post"> <strong>Salutation</strong>: <form:select name="Salutation" /> <br /> <strong>FirstName</strong>: <form:text name="FirstName" /> <br /> <strong>LastName</strong>: <form:text name="LastName" /> <br /> <br /> <strong>Groups</strong>: <br /> <form:multiselect name="Group" /> <br /> <br /> <form:button name="Edit" value="Save" /> <form:hidden name="userid" /> </html:form>

The below controller code prefills the form:

PHP code
$form = &$this->getForm('UserCreate'); $salutations = array(...); $salutation = &$form->getFormElementByName('Salutation'); for($i = 0; $i < count($salutations); $i++){ $salutation->addOption($salutations[$i]['DisplayName'], $salutations[$i]['Value']); } $groups = array(...); $group = &$form->getFormElementByName('Groups'); for($i = 0; $i < count($groups); $i++){ $group->addOption($groups[$i]['DisplayName'], $groups[$i]['Value']); }
In order to mark options as selected, please use the setOption2Selected() method.

The following code box gives presents an example taken from the User management module. The controller code first fills the select field and then preselects the desired option.

PHP code
$form = &$this->getForm('PermissionSetEdit'); // load permissions and fill the select field $allPermissions = $uM->loadPermissionList(); $permField = &$form->getFormElementByName('Permission[]'); for($i = 0; $i < count($allPermissions); $i++){ $permField->addOption($allPermissions[$i]->getProperty('DisplayName'),$allPermissions[$i]->getProperty('PermissionID')); } // preselect the options $selectedPermissions = $uM->loadPermissionsOfPermissionSet($permSet); for($i = 0; $i < count($selectedPermissions); $i++){ $permField->setOption2Selected($selectedPermissions[$i]->getProperty('PermissionID')); }

5.3. Readout of form controls

Readout of form controls is similar to prefilling. Please note, that simple and multi select fields are handled a little bit different. The following code example shows how the values can be read out from the form described in chapter 3.1:
PHP code
$form = &$this->getForm('UserEdit'); $userID = &$form->getFormElementByName('userid'); echo $userID->getValue(); $firstName = &$form->getFormElementByName('FirstName'); echo $firstName->getValue(); $lastName = &$form->getFormElementByName('LastName'); echo $lastName->getValue();

In order to get the selected options of simple and multi select fields, getSelectedOption() and getSelectedOptions() can be used. The subsequent code box describes how:

PHP code
$form = &$this->getForm('UserCreate'); $salutations = array(...); $salutation = &$form->getFormElementByName('Salutation'); for($i = 0; $i < count($salutations); $i++){ $salutation->addOption($salutations[$i]['Value'],$salutations[$i]['DisplayName']); } $option = &$salutation->getSelectedOption(); echo $option->getAttribute('value').', '.$option->getContent(); $groups = array(...); $group = &$form->getFormElementByName('Groups[]'); for($i = 0; $i < count($groups); $i++){ $group->addOption($groups[$i]['Value'],$groups[$i]['DisplayName']); } $selectedGroups = &$group->getSelectedOptions(); for($i = 0; $i < count($selectedGroups); $i++){ echo $selectedGroups[$i]->getAttribute('value').', '.$selectedGroups[$i]->getContent(); }
Analyzing dynamically filled select fields only works in case the field is filled with the desired options prior to reading the selected option(s). In case you do not fill the options, getSelectedOption() or getSelectedOptions() respectively will return an empty result (null). This is especially important, in case the form is evaluated after form submission. To get correct results, filling the select field must be independent of the state of the form.

6. Dynamic forms

In some cases it is necessary to generate forms dynamically. For this reason, the form taglib (HtmlFormTag) features the methods

  • addFormElement()
  • addFormContent()
  • addFormContentBeforeMarker()
  • addFormContentAfterMarker()
  • addFormElementBeforeMarker()
  • addFormElementAfterMarker()

The first two functions can be used to add content (i.e. plain text or html) or form elements at the end of the form. The latter ones are intended to add content or form at certain positions. For this reason, the <form:marker /> tag was introduced. The tag itself does not generate any output, but can be used for positioning purposes along with the "addForm*[Before|After]Marker()" methods.

The following chapters describe, how a dynamic form can be generated displaying form coordinate fields (triangle, square, ...). Depending on the type, the corresponding fields are displayed. If the type is set to "square", four fields are displayed ...

APF - dynamische Form generation; selection of the 'square' type

... in case of "triangle" three fields are presented:

APF - dynamische Form generation; selection of the 'triangle' type

6.1. Form definition

As already mentioned, the dynamic definition can be done in two flavours. While using addFormElement() and/or addFormContent(), no marker is needed. Instead, the following example uses marker, because the form already contains structural elements (e.g. table).

The following code box shows the form definition needed to display the form denoted above. To keep things simple, no CSS was added. Taking a closer look at the definition, you can see, that the form consists of a static select field, that defines the types available and a marker tag for positioning. Further, a document controller is specified, to add the desired form elements in front of the marker:

APF template
<@controller class="VENDOR\..\SelectController" @> <html:form name="type" method="post"> <table> <tr> <td> Please choose the desired form type: <form:select name="type"> <select:option value="triangle">triangle</select:option> <select:option value="square">square</select:option> </form:select> </td> <td> <form:button name="submit" value="send" /> </td> </tr> <tr> <td> <form:marker name="fields" /> </td> </tr> </table> </html:form>

6.2. Controller

The document controller is responsible for generating the form field, that depends on the type of the geometrical shape. For this reason, the constructor contains a definition of the form fields that should be displayed for a concrete type. Afterwards, the typ is read from the select field and the form is enhanced with additional content and fields. The following code box presents the implementation of the document controller needed for this functionality:

PHP code
use APF\core\pagecontroller\BaseDocumentController; class SelectController extends BaseDocumentController { // specify form element container private $formElements = array(); public function __construct(){ // define form elements for the triangle $this->formElements['triangle'][] = array('label' => 'coord 1','name' => 'coordone'); $this->formElements['triangle'][] = array('label' => 'coord 2','name' => 'coordtwo'); $this->formElements['triangle'][] = array('label' => 'coord 3','name' => 'coordthree'); // define form elements for the square $this->formElements['square'][] = array('label' => 'coord 1','name' => 'coordone'); $this->formElements['square'][] = array('label' => 'coord 2','name' => 'coordtwo'); $this->formElements['square'][] = array('label' => 'coord 3','name' => 'coordthree'); $this->formElements['square'][] = array('label' => 'coord 4','name' => 'coordfour'); } pubic function transformContent(){ // get form reference $form = &$this->getForm('type'); // get current decision $Select = &$form->getFormElementByName('type'); $Option = &$Select->getSelectedOption(); if($Option === null){ $CurrentType = 'triangle'; } else{ $CurrentType = $Option->getAttribute('value'); } // add form elements for($i = 0; $i < count($this->formElements[$CurrentType]); $i++){ // add label $form->addFormContentBeforeMarker('fields',$this->formElements[$CurrentType][$i]['label'].': '); // add text field (name attribute is present to enable validation and presetting!) $currentElement = &$form->addFormElementBeforeMarker( 'fields', 'form:text', array('name' => $this->formElements[$CurrentType][$i]['name']) ); // configure further form element attributes $currentElement->setAttribute('style','width: 200px;'); // add a line break $form->addFormContentBeforeMarker('fields',''); } // display form $form->transformOnPlace(); } }

6.3. Dynamic filters and validators

As described in chapter construction of validators and construction of filters validators and filters are attached to a form control as an Observer. Using dynamic forms, this can be simulated by imitating the <form:addvalidator /> and <form:addfilter /> tags. This means, that you can add a filter or validator using the addValidator() and addFilter() method respectively. The following code sample describes this approach in detail:

PHP code
// gather button instance of the form $button = &$form->getFormElementByName('submit'); // create dynamic form elements for($i = 0; $i < count($this->formElements[$CurrentType]); $i++){ // add label $form->addFormContentBeforeMarker('fields',$this->formElements[$CurrentType][$i]['label'].': '); // add text field (name attribute is present to enable validation and presetting!) $currentElement = &$form->addFormElementBeforeMarker( 'fields', 'form:text', array('name' => $this->formElements[$CurrentType][$i]['name']) ); // configure further form element attributes $currentElement->setAttribute('style','width: 200px;'); // add filter to the current element $filter = new NoSpecialCharactersFilter($currentElement,$button); $currentElement->addFilter($filter); // add validator to the current element $validator = new TextLengthValidator($currentElement,$button); $currentElement->addValidator($validator); // add a line break $form->addFormContentBeforeMarker('fields',''); }
Please respect the order of filters and validators defining dynamic forms. If you do not, undesirable validation effects may occure, because filters are executed after validators.
Please note that form validation checks using
PHP code
$form->isValid()
must be placed after the addition of dynamic validators and filters. Otherwise, wrong input is not detected.

6.4. Notes

An APF form element does need information about the name of itself already at creation time. If the element doesn't know it's own name, presetting and validation cannot be enabled. In order to use presetting and validation in combination with dynamic form elements, the addFormElement(), addFormElementBeforeMarker() and addFormElementBeforeMarker() functions possess a third parameter. This parameter expects an associative list of tag attributes, that are applied to the form object on creation time. Creating dynamic form elements, it is thus recommended to at least apply the name of the tag to the third argument:

PHP code
array( 'name' => 'current_name' )

Please note, that the tag attributes are also interesting for addressing the form objects after appending them to the form via the getFormElementByName() or getFormElementByID() methods.

7. Enhancement of form controls

The concept of the APF's page controller allows you to write own tags to create reusable elements. This also continues with forms. Custom form controls can be added using the <core:addtaglib /> tag.

The difference between a normal taglib and a form taglib is the fact, that each form taglib extends AbstractFormControl class or - effectively - implements the FormControl interface. Hence, the form control inherits an advanced tag definition and some more functionality to enable the <html:form /> tag to handle the control. The extra functionality mentioned in the last sentence merely consists of validation and filter handling code.

Please note, that the <html:form /> tag only treats tags as form elements in case they implement the FormControl interface. All other tags are not included in validation, filtering and requesting the current status.

This allows you to use any kind of tags within forms that are not part of the form's functionality (e.g. field labels.

The next three chapters describe the creation of custom form controls, validators and filters. Besides, implementation examples are presented.

7.1. Form controls

As mentioned above, an APF form control is defined by the abstract class AbstractFormControl. In order to implement a custom form element, the class' functionality must be enhanced.

As an example, a form element should be discussed that generates a hidden input field in order to protect a form. In the next chapter, we are going to implement the appropriate validator. In case, the control is filled, we consider the field invalid and assume, that it was filled by automated scripts. Otherwise, the field is defined to be valid. In various blog entries this kind of fields are also as called honeypot fields.

Let's start with the basics of the element. As described in the last sentence, the tag should generate a hidden text input field. To achieve this, the taglib must return the desired source code:

PHP code
class HoneypotFormControlTag extends AbstractFormControl { public function transform(){ $htmlCode = (string)'<input '; $htmlCode .= $this->getAttributesAsString($this->attributes); $htmlCode .= 'type="text" '; $htmlCode .= 'style="margin: 0px; padding: 0px; display: none; height: 0px; width: 0px;"'; $htmlCode .= ' />'; return $htmlCode; } }

Due to the fact, that validation and filter attachment as well as the method returning the validity status of the tag is already included in the AbstractFormControl class, the code printed above is all that you need by now.

In order to use the newly created form control, the <core:addtaglib /> tag can be used to announce the tag to the form:

APF template
<core:addtaglib class="VENDOR\..\HoneypotFormControlTag" prefix="form" name="honeypot" /> <html:form name="CheckedForm"> ... <form:honeypot name="check" /> ... </html:form>

The real functionality is now included in the honeypot field validator. See the next chapter for details.

7.2. Validators

Validators are defined separately and are attached as observer. In order to support this mechanism, a form control must be aware of validators. In case of the honey pot field, the functionality is already included in the tag, because it extends the base class AbstractFormControl. Thus, the validator is applied the content of the value attribute and is thus able to act as described in the introduction.

For convenience, we call the validator HoneypotValidator. In common sense, it is a text field validator and can thus use the TextFieldValidator class as a basis. This class derives from AbstractFormValidator, what is the bas class for all validators.

Each validator must implement the validate() method, that is applied the content to validate. In case of the HoneypotValidator the content of the text field must be empty to consider the control valid. If not, the field is marked as invalid. The following code can be used to implement the described functionality:

PHP code
class HoneypotValidator extends TextFieldValidator { public function validate($input){ if(empty($input)){ return true; } return false; } ... }

Due to the fact, that TextFieldValidator already contains the functionality that is necessary to notify a "real" text field and mark it with a special css class in case of errors. As of this use case, we do not need this functionality and though we have to suppress it. For this reason, we are going to overwrite the notify() method (defined within TextFieldValidator), that is called when a field failed validation:

PHP code
class HoneypotValidator extends TextFieldValidator { public function validate($input){ if(empty($input)){ return true; } return false; } public function notify(){ $this->control->markAsInvalid(); $this->notifyValidationListeners($this->control); } }

In contrast to the implementation of the TextFieldValidator class, the line

PHP code
$this->markControl($this->control);

is not included. This disables the optical marking of the form element using a custom css class.

The following code box describes the application of the validator together with the above honeypot field:

APF template
<core:addtaglib class="VENDOR\.. \HoneypotFormControlTag" prefix="form" name="honeypot" /> <html:form name="CheckedForm"> <form:honeypot name="check" /> <form:button name="send" value="Send" /> <form:addvalidator namespace="..." class="APF\tools\form\validator\HoneypotValidator" control="check" button="send" /> </html:form>

Thanks to the independent definition and implementation of the validator, it can also be applied to other text fields. These are all text fields and text areas shipped with the APF release.

7.3. Filter

Applying the honeypot field no filter is needed. Hence, this chapter describes a filter, that substitutes German umlauts with their phoneme equivalents (e.g. ä -> ae).

The GermanUmlautsFilter thus implements the AbstractFormFilter class that is the base class for all form filters just as the AbstractFormValidator is for validators. The abstract form filter class defines the interface for all concrete form filter implementations.

Further, each form filter implements filter() method. The function takes the content to filter as an argument and returns the filtered content.

The skeleton of the filter is as follows:

PHP code
class GermanUmlautsFilter extends AbstractFormFilter { public function filter($input){ return $input; } }

The functionality of the filter is a simple string replace call using the PHP standard function str_replace():

PHP code
class GermanUmlautsFilter extends AbstractFormFilter { public function filter($input){ return str_replace( array('ä','ö','ü','Ä','Ö','Ü','ß'), array('ae','oe','ue','Ae','Oe','Ue','ss'), $input ); } }

The application of the filter using a text field or a text area can be taken from the below code box:

APF template
<html:form name="UmlautsForm"> <form:text name="name" /> <form:area name="comment" /> <form:button name="send" value="Send" /> <form:addfilter class="...\GermanUmlautsFilter" control="name|comment" button="send" /> </html:form>

7.4. MultiFileUpload

Besides, you can use a tool for creating uploads offered with preview function as well as percentage progress indicator: the MultiFileUpload. For detailed information about this tool refer the documentation in the Wiki (German only): MultiFileUpload im Wiki.

8. Visibility of form controls

Form controls can be assigned a visibility status. This status is used during the transformation to decide whether a form control is displayed or not. This allows you to show or hide form controls within a (Document-)Controller.

In addition to the visibility status dependent form controls can be defined by each control. In case the control is requested to hide from the output the dependent fields will do so as well. Using this feature, you may create dynamic forms - with some limitation.

The following chapters describe the use cases and the respective implementation.

8.1. Manipulation of the visibility via controller

AbstractFormControl class defines the

  • hide()
  • show()
  • isVisible()

methods to define and manipulate visibility. Since all form controls are derived from this class, the visibility feature is theoretically present for all form controls. Please note the list of supported controls in chapter 8.4.

isVisible() can be used to query the current visibility status within a document controller or within a tag. show() defines the control as visible, hide() sets the form control to invisible.

By default, each control is visible.

For our further examples, we will use the following log-in form:

APF template
<html:form name="log-in"> <label for="user-name">User name:</label> <form:select name="user-name" id="user-name" /> <label for="password">Password:</label>: <form:text name="password" id="password" /> <label for="stay-logged-in">Stay logged-in?</label>: <form:checkbox name="stay-logged-in" id="stay-logged-in" /> <form:button name="log-in" value="Log-in" /> </html:form>

As you can take from the code bos the form contains one field for the user name, one for the password, as well as one field that notifies the underlying application to create a permanent log-in.

In order to hide the checkbox for the permanent log-in you may use the following controller code:

PHP code
use APF\core\pagecontroller\BaseDocumentController; class LoginController extends BaseDocumentController { public function transformContent() { $form = &$this->getForm('log-in'); $checkbox = &$form->getFormElementByName('stay-logged-in'); $checkbox->hide(); ... $form->transformOnPlace(); } }

When the form is displayed the checkbox is not visible anymore and users cannot interact with it.

Please note the hints in chapter 8.5 on the implementation of custom form controls respecting the visibility definition.

8.2. Hiding dependent controls

One problem of the code described in chapter 8.1. is: the label element describing the checkbox' meaning is still present. You can avoid that by defining the label as an APF form tag. It can then be referred to as a dependent control within the checkbox' tag definition using the dependent-controls attribute. In case you hide the checkbox the label tag will be hidden, too.

The tag definition of the form is now as follows:

APF template
<html:form name="log-in"> <label for="user-name">User name:</label> <form:select name="user-name" id="user-name" /> <label for="password">Password:</label>: <form:text name="password" id="password" /> <form:label name="stay-logged-in-label" for="stay-logged-in">Stay logged-in?</form:label>: <form:checkbox name="stay-logged-in" id="stay-logged-in" dependent-controls="stay-logged-in-label" /> <form:button name="log-in" value="Log-in" /> </html:form>

Defining dependent tags can be done recursively. In case you define the password field as a dependent control of the label tag, both the checkbox and the label plus the password field are hidden.

To define multiple fields as dependent controls, please specify a pipe-separated list (|) of control names. Example:

Code
stay-logged-in-label|password

8.3. Hide a group

A more sophisticated is to control visibility of form controls using groups. This allows showing and hiding of several form controls at the same time and let's you also control surrounding HTML code which is not possible with the approach described in chapter 8.2.

Defining a group you can use the <form:group /> tag. The tag itself does not generate any output but displays the form controls and HTML code defined therein.

The form setup can thus be updated as follows:

APF template
<html:form name="log-in"> <p> <label for="user-name">User name:</label> <form:select name="user-name" id="user-name" /> </p> <p> <label for="password">Password:</label>: <form:text name="password" id="password" /> </p> <form:group name="stay-logged-in-group"> <p> <form:label for="stay-logged-in">Stay logged-in?</form:label>: <form:checkbox name="stay-logged-in" id="stay-logged-in" /> </p> </form:group> <p> <form:button name="log-in" value="Log-in" /> </p> </html:form>
Please note, that the HTML markup has been added <p /> tags to demonstrates that you can also hide the HTML markup that wraps form controls.

Hiding the check box for activating the permanent log-in can be realized within the controller as follows:

PHP code
use APF\core\pagecontroller\BaseDocumentController; class LoginController extends BaseDocumentController { public function transformContent() { $form = &$this->getForm('log-in'); $group = &$form->getFormElementByName('stay-logged-in-group'); $group->hide(); ... $form->transformOnPlace(); } }

8.3. Supported controls

Defining the visibility state is not supported by all form control types. This is because elements like the <form:hidden /> tag do not generate visible output - even before 1.17 - and for elements such as the <form:error /> tag it doesn't make sense from a business requirements perspective.

Please note the following list of tags supporting the visibility definition:

  • <form:button />
  • <form:imagebutton />
  • <form:reset />
  • <form:checkbox />
  • <form:file />
  • <form:label />
  • <form:multiselect />
  • <form:select />
  • <form:password />
  • <form:radio />
  • <form:text />
  • <form:area />
  • <form:date />
  • <form:time />
  • <form:group />

8.5. Implementation of custom controls

The definition of the visibility definition is structurally defined within the AbstractFormControl class and is thus present to all form controls. As the behaviour of what should happen when a form control is marked hidden is managed by each form control individually you must take care of the visibility status implementing custom form controls - either respect the state or consciously ignore it.

The visibility status is held with the AbstractFormControl::$isVisible class variable. true means that the control is visible, false indicates that the controls listed in chapter 8.4 are not displayed.

To respect the visibility status with custom form controls, the subsequent code can be used as a template:

PHP code
class MyCustomFormTag extends AbstractFormControl { ... public function transform() { if ($this->isVisible) { return '...what ever your tag generates as its output...'; } return ''; } ... }

Another use case implementing custom form controls is hiding adjacent form controls directly within the tag code. This may be necessary when one control type defines a direct dependency to another type.

Hiding all check boxes within a given form can be implemented like this within a custom form tag:

PHP code
class MyCustomFormTag extends AbstractFormControl { ... public function onAfterAppend() { $form = &$this->getForm(); $controls = $form->getFormElementsByTagName('form:checkbox'); foreach ($controls as $control) { $control->hide(); } ... } ... }

9. Resetting forms

In case you intend to reset a form to it's initial state after it has been sent you may want to use the reset() method. This method is available for the entire form (HtmlForm::reset()) and for each form control separately (FormControl::reset()).

Calling reset() on the instance of a form each single form control (implementing the FormControl interface) is reset to it's initial state:

PHP code
$this->getForm('log-in')->reset();

To reset a certain form control you can also use reset():

PHP code
$this->getForm('log-in')->getFormElementByName('username')->reset();
Please be aware to re-implement the reset() method when creating custom form controls as the reset mechanism might be different to the standard implementation in AbstractFormControl.

10. Filling DTOs and Models

Please note that this feature is not available before version 3.2!

In order to read out form

APF template
<html:form name="address"> <form:text name="street" /> <form:text name="number" /> <form:text name="zip" /> <form:text name="city" /> <form:button name="submit" value="Weiter" /> </html:form>

and fill the respective values into a DTO or a Model you may use the follwing controller code:

PHP code
$model = new AddressModel(); $model->setStreet($form->getFormElementByName('street')->getValue()); $model->setNumber($form->getFormElementByName('number')->getValue()); $model->setZip($form->getFormElementByName('zip')->getValue()); $model->setCity($form->getFormElementByName('city')->getValue());

Especially for complex forms this leads to a huge amount of controller code. A more elgant approach is to delegate mapping to the form itself:

PHP code
$model = new AddressModel(); $form->fillModel($model);

The following chapters provide an introduction to this feature and contain in-depth use cases.

10.1. Concept

Assignment of values within method fillModel() is based on the assumption that the name of a form control corresponds to the name of the DTO or Model property.

This convention limits complexity of DTO or Model implementation and avoids creation of mapping tables (e.g. form field -> DTO/Model method).

Calling method fillModel() the form assigns values to properties of the DTO or Model based on form controls matching the name of the properties. In case a Model defines a property called $street the form evaluates corresponding field street to fill the Model property.

The mapping implementation is not restricted to properties with a public setter method but injects values based on PHP's Reflection API. Given DTO

PHP code
class AddressModel { private $street; ... }

can be easily filled from the following form:

APF template
<html:form name="address"> <form:text name="street" /> ... <form:button name="submit" value="Next" /> </html:form>
DTO or Model properties can be defined with any type of visibility. Mapping works for both private and protected properties as well as for public ones.

Since each form control complies with interface FormControl values can be retrieved by getValue(). Depending on implementation, this method either returns the value to be written to the DTO or Model or it's object representation (e.g. SelectBoxOptionTag for select box tags).

In case you are using a DTO or Model win conjunction with an O/R mapper (e.g. Generic o/r mapper) or any external API the value's data format must be form-independent (e.g. String or Integer). Translation of form-dependent object representations into simple data types is managed by so-called FormValueMapper.

In case a mapper implementation takes responsibility for a given form field - this is evaluated by applies() - the mappers' getValue() method is used within fillModel() to evaluate the form-independent value.

Within APF's default configuration there are already several mappers registered to handle all shipped field types:

  • StandardValueMapper (String-based fields as well as date and time selection components)
  • RadioButtonValueMapper (Radio buttons)
  • SelectBoxValueMapper (Select boxes)
  • MultiSelectBoxValueMapper (Multi select boxes)

Method fillModel() supports applying an optional list of fields to be processed during mapping. This is helpful in case a single DTO or Model is used for a multi-step form and not all fields are contained in the current step.

The list can also be used in case you intend to fill only certain fields or you want to prevent data getting overwritten.

In case the current step within a registration process only asky for your address filling the AddressModel can be limited to the current set of fields as follows:

PHP code
$model = new AddressModel(); $form->fillModel($model, ['street', 'number', 'ZIP', 'city']);
In case a FormException is emitted during filling a single property this is not stopping the mapping process. Catching all such exception guarantees that filling the model is completed in any case, however, error analysis my become more complex. During implementation decision has been taken to catch exceptions to allow easy re-use of existing models with forms.

10.2. Usage

This chapter talks about usage of fillModel() within your application.

10.2.1. Filling DTOs

The following form may be used to let users authenticate against a web site. It contains two fields for entering user name and password as well as a check box to store authentication permanently:

APF template
<html:form name="log-in"> <form:text name="user" /> <form:text name="pass" /> <form:checkbox name="stay_logged_in" value="yes" /> ... <form:button name="submit" value="Log-in" /> </html:form>

Assuming you are using an API that expects DTO class LoginData to authenticate a user, it is necessary to fill the respective DTO within the controller and pass it onto the API. Using fillModel() this is as follows:

PHP code
class LoginController extends BaseDocumentController { public function transformContent() { $form = $this->getForm('log-in'); if ($form->isSent() && $form->isValid()) { $model = new LoginData(); $form->fillModel($model); $this->executeLogIn($model); } } protected function executeLogIn(LoginData $data) { ... } }

Please find the structure of the DTO within the subsequent code box:

PHP code
class LoginData { private $user; private $pass; private $stay_logged_in; public function getUser() { return $this->user; } public function getPass() { return $this->pass; } public function getStayLoggedIn() { return $this->stay_logged_in; } }
Please note, that filling DTOs the name of the form field must exactly match the name of the DTO property. Further, not all characters are allows as part of property names in PHP classes (e.g. "-").
10.2.2. Filling multiple DTOs

Semantically, sign-up forms normally contain several types of data: personal data such as name and date of birth, home address, and electronic contact data such as e-mail and phone number.

In case the data model of your application or an API provides such kind of data segregation you can directly re-use existing DTOs or Models the same way as described in chapter 10.2.1.

The following code box describes a sign-up form with three parts: personal data, postal address, and e-mail as communication data:

APF template
<html:form name="sign-up"> <form:text name="firstName" /> <form:text name="lastName" /> <form:text name="street" /> <form:text name="number" /> <form:text name="zip" /> <form:text name="city" /> <form:text name="email" /> <form:button name="sign-up" value="Sign up"/> </html:form>

For this example, let's assume your application defines a DTO for each of the parts described above:

PHP code
class PersonalData { private $firstName; private $lastName; public function getFirstName() { return $this->firstName; } public function getLastName() { return $this->lastName; } } class PostalAddress { private $street; private $number; private $zip; private $city; public function getStreet() { return $this->street; } public function getNumber() { return $this->number; } public function getZip() { return $this->zip; } public function getCity() { return $this->city; } } class CommunicationData { private $email; public function getEmail() { return $this->email; } }

Filling above DTOs and conduct the sign-up can be easily achieved with the subsequent controller:

PHP code
class SignUpController extends BaseDocumentController { public function transformContent() { $form = $this->getForm('sign-up'); if ($form->isSent() && $form->isValid()) { $personal = new PersonalData(); $address = new PostalAddress(); $communication = new CommunicationData(); $form ->fillModel($personal) ->fillModel($address) ->fillModel($communication); $this->signUp($personal, $address, $communication); } } protected function signUp(PersonalData $personal, PostalAddress $address, CommunicationData $communication) { ... } }
10.2.3. Filling dedicated fields

Provided, ZIP code is already captured at the beginning of a sign-up process it has already been stored within the DTO or Model at an earlier stage. In case the user hits the sign-up form described in chapter 10.2.2 ZIP code should only be displayed but not written to the DTO or Model again.

This might be necessary in case ZIP code is essentiel to the sign-up and has been validated earlier in the process.

In case you want to store all data except ZIP within your DTO you may want to use the follwing controller code:

PHP code
$personal = new PersonalData(); $address = new PostalAddress(); $communication = new CommunicationData(); $form ->fillModel($personal) ->fillModel($address, ['street', 'number', 'city']) ->fillModel($communication);

Applying a list of fields to be mapped to the model the PostalAddress instance will only be filled with the content of street, number, and city. The content of the $zip property remains as-is.

With dynamic forms the list of available fields can be assembled as follows:
PHP code
$names = []; foreach ($form->getFormElementsByTagName('form:text') as &$field) { $names[] = $field->getAttribute('name'); } $model = new AddressModel(); $form->fillModel($model, names);

10.3. Extension

Writing custom form tags that are not returning the desired data format with getValue() it is necessary to introduce a custom translator (mapper) and to register it with the form tag (see concept in chapter 10.1). This approach has also been applied with APF's select field since getValue() returns an instance of the selected option (class SelectBoxOptionTag) just as getSelectedOption() does. However, the DTO or Model property requires the effective value.

Interface FormValueMapper describes the structure of the translator of form values for a dedicated form control into an independent (primitive) data format:

PHP code
interface FormValueMapper { public static function applies(FormControl $control); public static function getValue(FormControl $control); }

As an example for a custom FormValueMapper the following tag should be used displaying a select box of existing users:

PHP code
class UserSelectionTag extends AbstractFormControl { public function getValue() { return $this->getSelectedUser(); } /** * @return UmgtUser */ public function getSelectedUser() { $user = new UmgtUser(); $user->setObjectId($this->getSelectedUserId()); return $user; } /** * @return int */ protected function getSelectedUserId() { return ...; } }

Methods getValue() and getSelectedUser() return an instance of the currently selected user. This is helpful for controller implementation as the result can be directly used without conversion with components such as the UmgtManager (see ).

In case you are using a DTO or Model that can only handle simple data types you are required to write a mapper.

Implementation of method applies() defines for which field type the mapper is responsible for:

PHP code
class UserSelectionTagFormValueMapper implements FormValueMapper { public static function applies(FormControl $control) { return $control instanceof UserSelectionTag; } ... }

In this case, the mapper is only used with UserSelectionTag fields.

Please note, that inappropriately framed definitions of responsibility may lead to wrong results! Hence, please define exact conditions within applies().

Translation of the returned value is defined in method getValue():

PHP code
class UserSelectionTagFormValueMapper implements FormValueMapper { public static function applies(FormControl $control) { return $control instanceof UserSelectionTag; } public static function getValue(FormControl $control) { /* @var $control UserSelectionTag */ $user = $control->getSelectedUser(); return $user === null ? null : $user->getObjectId(); } }

Method getValue() evaluates the currently selected user and returns it's internal ID. In case no user is selected null is returned instead.

In order to make the form use the newly created mapper it must be registered with the form. This can be done with the following code snippet:

PHP code
HtmlFormTag::addFormValueMapper(UserSelectionTagFormValueMapper::class);
FormValueMapper can be registered once for the entire application such as tags (see Implementation of tags). For this reason, you can put your statements into your bootstrap file (e.g. index.php).

Using custom mappers it might be necessary to adapt the standard configuration. In case you intend to exclusively use UserSelectionTagFormValueMapper and StandardValueMapper please add the folowing code to your bootstrap file:

PHP code
HtmlFormTag::clearFormValueMappers(); HtmlFormTag::addFormValueMapper(UserSelectionTagFormValueMapper::class); HtmlFormTag::addFormValueMapper(StandardValueMapper::class);
The mapping mechanism described here can also be used in an inverted way. If you want to work with an object representation within your DTO or Model you can also create instances from simple data types with the following FormValueMapper implementation:
PHP code
class SelectBoxToUserFormValueMapper implements FormValueMapper { public static function applies(FormControl $control) { return $control instanceof SelectBoxTag && $control->getAttribute('user'); } public static function getValue(FormControl $control) { /* @var $control SelectBoxTag */ $option = $control->getSelectedOption(); if ($option === null) { return null; } $user = new UmgtUser(); $user->setObjectId($option->getValue()); return $user; } }
Your DTO or Model now contains a UmgtUser instance within the desired property.

Comments

Do you want to add a comment to the article above, or do you want to post additional hints? So please click here. Comments already posted can be found below.
« 1   »
Entries/Page: | 5 | 10 | 15 | 20 |
1
name 06.12.2016, 10:02:48
brand name cialis-Once-A-Day is the latest treatment for erectile dysfunction or impotence.
2
delivery 30.09.2016, 17:23:53
Physicians warned to be cautious in prescribing fast delivery viagra for erectile dysfunction.