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 preceeding 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 (since 1.17).
  • 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 (since 1.14).
  • 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 (since 1.14).
  • 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) (since 1.15).
  • 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) (since 1.15).
  • isVisible(): Returns the visibility status of the present form control. true indicates that the control is visable, false marks form controls that are not included to be displayed during the form generation (since 1.17).
  • hide(): Hides a form control from the form output (since 1.17).
  • show(): Sets the visibility status of a form control to true (since 1.17).
  • 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:addvalidator class="APF\tools\form\validator\TextLengthValidator" control="code|title|description" button="send" /> <form:button name="send" value="Save" /> </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:addvalidator class="APF\tools\form\validator\TextLengthValidator" control="code|title|description" button="send" /> <form:button name="send" value="Save" /> </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).
  • getFormElementsByObjectID(): Returns a form element selected by the APF DOM id. This can be used generating dynamic forms.

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"> <error: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(),
  • getFormElementByID() oder
  • getFormElementByObjectID()

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
<core:addtaglib class="APF\tools\form\taglib\HtmlFormTag" prefix="html" name="form" /> <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
<core:addtaglib class="APF\tools\form\taglib\HtmlFormTag" prefix="html" name="form" /> <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" @> <core:addtaglib class="APF\tools\form\taglib\HtmlFormTag" prefix="html" name="form" /> <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!) $currentElementID = $form->addFormElementBeforeMarker( 'fields', 'form:text', array('name' => $this->formElements[$CurrentType][$i]['name']) ); // configure further form element attributes $currentElement = &$form->getFormElementByObjectID($currentElementID); $currentElement->setAttribute('style','width: 200px;'); // add a line break $form->addFormContentBeforeMarker('fields',''); } // display form $form->transformOnPlace(); } }

6.3. Dynamic filters and validators

As of release Release 1.11 the filter and validation comcept was completely redesigned. This does also effect the dynamic form 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!) $currentElementID = $form->addFormElementBeforeMarker( 'fields', 'form:text', array('name' => $this->formElements[$CurrentType][$i]['name']) ); // configure further form element attributes $currentElement = &$form->getFormElementByObjectID($currentElementID); $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 taglibs to create reusable elements. This also continues with forms. Custom form controls can be added using the <form:addtaglib /> tag. This tag acts similar to the <core:addtaglib />, but is restricted to forms.

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 <form:addtaglib /> tag can be used to announce the tag to the form:

APF-Template
<core:addtaglib class="APF\tools\form\taglib\HtmlFormTag" prefix="html" name="form" /> <html:form name="CheckedForm"> ... <form:addtaglib namespace="..." class="HoneypotFormControlTag" prefix="form" name="honeypot" /> <form:honeypot name="check" /> ... </html:form>

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

7.2. Validators

Validators are defined separately and are attached as observer since release 1.11. 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 application 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 TextFieldValidator die Zeile

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="APF\tools\form\taglib\HtmlFormTag" prefix="html" name="form" /> <html:form name="CheckedForm"> <form:addtaglib namespace="..." class="HoneypotFormControlTag" prefix="form" name="honeypot" /> <form:honeypot name="check" /> <form:addvalidator namespace="..." class="APF\tools\form\validator\HoneypotValidator" control="check" button="send" /> <form:button name="send" value="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
<core:addtaglib class="APF\tools\form\taglib\HtmlFormTag" prefix="html" name="form" /> <html:form name="UmlautsForm"> <form:text name="name" /> <form:area name="comment" /> <form:addfilter class="...\GermanUmlautsFilter" control="name|comment" button="send" /> <form:button name="send" value="Send" /> </html:form>

7.4. MultiFileUpload

With release of APF version 1.16 you can use a new 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

Release 1.17 introduces a visibility status of form controls. 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

In 1.17 the AbstractFormControl class has been added the

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

methods. 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.3.

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.4 on the implementation of custom form controls respecting the visibility definition.

Please note, that controls set to invisible by hide() are included in validation and filtering anyhow. This may lead to hidden elements causing validation errors and the user cannot solve this because the control is not displayed.

In case you intent to influence the visibility of form controls within a controller, please mark the control as valid by

PHP-Code
$checkbox = &$form->getFormElementByName('stay-logged-in'); $checkbox->hide(); $checkbox->markAsValid();

or think about defining an optional validator (see Forms).

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. 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 />

8.4. 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 inidcates that the controls listed in chapter 8.3. 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->getParentObject(); $controls = $form->getFormElementsByTagName('form:checkbox'); foreach ($controls as $control) { $control->hide(); } ... } ... }

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.
There are no comments belonging to this article.