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 custon form taglibs in detail.
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:
class form_controller 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.
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.
Using this methods, the developer is supported to implement custom form taglibs as described in chapter 7.
Since release 1.12 attribute whitelisting was 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:
class SpecialFormControlTag extends AbstractFormControl {
public function __construct() {
$this->attributeWhiteList[] = 'name';
$this->attributeWhiteList[] = 'accesskey';
}
}class form_controller extends BaseDocumentController {
public function transformContent(){
$form = &$this->getForm('MyForm');
if($form->isSent()){
echo 'Form was send');
}
if($form->isValid()){
echo 'Form is valid';
}
}
}class form_controller extends BaseDocumentController {
public function transformContent(){
$form = &$this->getForm('MyForm');
$searchField = &$form->getFormElementByName('searchterm');
if($searchField->isValid()){
echo 'Search field is valid';
}
}
}class form_controller extends BaseDocumentController {
public function transformContent(){
$form = &$this->getForm('MyForm');
$button = &$form->getFormElementByID('button');
if($button->isSent()){
echo 'Search button was clicked';
}
}
}<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="TextLengthValidator"
control="code|title|description"
button="send"
/>
<form:button name="send" value="Save" />
</html:form>class form_controller 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.
<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="TextLengthValidator"
control="code|title|description"
button="send"
/>
<form:button name="send" value="Save" />
</html:form>class form_controller 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.
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:
<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:
class search_controller 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).
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:
class form_controller 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();
}
}
}<html:form name="product-form">
<form:error name="error">
<error:placeholder name="ph1" />
</form:error>
...
</html:form>class form_controller 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');
}
}class edit_controller 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()
);
}
}
}<core:addtaglib
namespace="tools::form::taglib"
class="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>$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','...');<core:addtaglib
namespace="tools::form::taglib"
class="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>$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']);
}$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'));
}$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:
$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();
}In some cases it is necessary to generate forms dynamically. For this reason, the form taglib (HtmlFormTag) features the methods
<@controller namespace="..." class="select_controller" @>
<core:addtaglib
namespace="tools::form::taglib"
class="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>class select_controller 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','<br />');
}
// display form
$form->transformOnPlace();
}
}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:
// 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','<br />');
}$form->isValid()array(
'name' => 'current_name'
)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;
}
}$htmlCode .= $this->getAttributesAsString($this->__Attributes);$htmlCode .= $this->__getAttributesAsString($this->__Attributes);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:
<core:addtaglib
namespace="tools::form::taglib"
class="HtmlFormTag"
prefix="html"
name="form"
/>
<html:form name="CheckedForm">
...
<form:addtaglib namespace="..." class="HoneypotFormControlTag" prefix="form" name="honeypot" />
<form:honeypot name="check" />
...
</html:form>class HoneypotValidator extends TextFieldValidator {
public function validate($input){
if(empty($input)){
return true;
}
return false;
}
...
}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);
}
}$this->markControl($this->control);<core:addtaglib
namespace="tools::form::taglib"
class="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="HoneypotValidator"
control="check"
button="send"
/>
<form:button name="send" value="Send" />
</html:form>class GermanUmlautsFilter extends AbstractFormFilter {
public function filter($input){
return $input;
}
}class GermanUmlautsFilter extends AbstractFormFilter {
public function filter($input){
return str_replace(
array('ä','ö','ü','Ä','Ö','Ü','ß'),
array('ae','oe','ue','Ae','Oe','Ue','ss'),
$input
);
}
}<core:addtaglib
namespace="tools::form::taglib"
class="HtmlFormTag"
prefix="html"
name="form"
/>
<html:form name="UmlautsForm">
<form:text name="name" />
<form:area name="comment" />
<form:addfilter
namespace="..."
class="GermanUmlautsFilter"
control="name|comment"
button="send"
/>
<form:button name="send" value="Send" />
</html:form>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.
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.
In 1.17 the AbstractFormControl class has been added the
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.
For our further examples, we will use the following log-in form:
<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:
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
$checkbox = &$form->getFormElementByName('stay-logged-in');
$checkbox->hide();
$checkbox->markAsValid();or think about defining an optional validator (see Forms).
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:
<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:
stay-logged-in-label|passwordDefining 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:
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:
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:
class MyCustomFormTag extends AbstractFormControl {
...
public function onAfterAppend() {
$form = &$this->getParentObject();
$controls = $form->getFormElementsByTagName('form:checkbox');
foreach ($controls as $control) {
$control->hide();
}
...
}
...
}JetBRAINS supports the development of the APF with PHPStorm licenses and we feel confidential that PHPStorm strongly influences the APF's quality. Use PHPStorm!
Proud to useIntelligent PHP IDE for coding, testing and debugging with pleasure