Custom rendering of FormCollection Elements

A problem with ZF2 forms is that you can’t actually configure the rendering of a form element. Same (stupid) story that was in ZF1 it’s in ZF2 as well.  If you want to change the rendering the only (idiot) solution is to extend the original rendering helper and that means override the render() function, put the whole code from the original render() function ( and edit it with your changes). The issue is you copy too much code from the framework itself and you should maintain it.

Problem: need to modify the rendering to adapt the HTML to be compatible for Bootstrap3.
Solution:

Extend FormCollection and FormRow. Only one thing to change for the extended FormCollection, just set the $defaultElementHelper:

<?php
namespace ApplicationFormViewHelper;

use ZendFormViewHelperFormCollection;

class FieldCollection extends FormCollection
{
    protected $defaultElementHelper = 'fieldRow';

}

The new FieldCollection will use the view helper called fieldRow:

<?php
namespace ApplicationFormViewHelper;

use ZendFormViewHelperFormRow;
use ZendFormElementInterface;

class FieldRow extends FormRow
{

public function render(ElementInterface $element)
    {
   //.... copy code from FormRow and modify with your changes
  }
}

To be able to call the new helpers, you need to register then as invokables in Module.php

  public function getViewHelperConfig()
    {
        return array(
            'invokables' => array(
                'fieldCollection' => 'ApplicationFormViewHelperFieldCollection',
                'fieldRow' => 'ApplicationFormViewHelperFieldRow'
            ),
           ....
}

My changes were mostly where the markup is created, so in my new FormRow renderer I have something like:

 $markup = '<div class="form-group">'. $labelOpen . $label. $labelClose .
                                    '<div class="col-sm-5"><div class="input-group">'.$elementString .'</div>'.'</div>'.'</div>' ;

and I also set a default class for the label element

 $labelAttributes = array('class'=>'control-label  col-sm-5');

Now in the HTML template we just use

<?php
    echo $this->fieldCollection($formElement);
?>

Personal project : Huzmet.ro

My personal project enters in a pre-beta phase . Currently the design is not ready yet , so it’s more about functionality .
Some tehnical details : made on Zend Framework , uses SphinxSE for searching . There’s no caching used yet , but will use ( most likely) memcached and file caching ( using Zend Cache ). There’s also a plan for using Gearman workers .
The project will start first for Romania , but multi-language is already implementend ( get-text translations) .
Testing is welcomed , especially testing with romanian texts/queries , but english is fine too .
Huzmet is a local services providers directory and more . And by that more one feature is that instead of searching you can make a shout , like a request , the shout is scanned and if matches are found among the providers , they get informed about your shout and they can contact you .
Huzmet.ro

non-input Zend Form Element

Simple case : let’s say you make a form for user to register . You have an email field , right ? Ok , now when the user is logged you have a page where he can modify password and other settings . You should normally want to display the email too , but you don’t want to be editable ( because emails should unique etc. ) .You could show the email separated from the form , but you don’t want that , you want it to be in the same area with the other fields .
One simple approach is to create a “dummy” element , one that can be populated by the populate() function , but actually don’t have an <input> . The way is to create an element that extends Zend_Form_Element or Zend_Form_Element_Xhtml which uses formNote helper . Normally this helper is used by hidden elements ( it’s a dead simple helper , you can check the source in Zend/View/Helper) , but you can use it in this case too :

class App_Form_Element_Xhtml extends Zend_Form_Element_Xhtml
{
public $helper = 'formNote';
}

Now ,you will proubably extend your form when editing ( I use a Base form with the fields and for Edit/Register I extend it with new ones ) . To get the email displayed , you need first to remove the email element ( text type ) and replace it with this one :

$this->removeElement('email');
$fakeEmail = new App_Form_Element_Xhtml('email',array(
                 'required' => false,
                'ignore'=> true,
		'label' => 'Email : ',
		));
$this->addElement($fakeEmail);

You need to add ignore to be true , otherwise $form->getValues() will return you a “” value form the email – this can be fixed by declaring getValue and setValue functions for the element , but setting ignore works too .
The last step is to re-order . If the email is the first you can use $fakeEmail->setOrder(-1) before adding .

Zend Forms

Zend Forms are a nice tool in ZF , but it’s a bit weird and looks bloated for new people . One thing is that it follows the decorator pattern and another is that you end up writing some code for a damn html form of several lines . Yet , it offers a validation system and if you have many forms in your project , the decorations might turn in a not bad thing .
After reading the tutorial , first thing you will want to do is to get rid of the “damn” dt/dd . The dt/dd combination was chosen to be default for a number of reasons ( some good ones ) . Yet designers might look weird at you ( even if dt/dd can be handled pretty well ) or you just want div tags or a damn table there .
How to do that ? well ,there are 2 ( no , 3) sollutions : one is to modify the decorators , second is to create your own elements ( maybe with their decorators too) … and last one is to use the ViewScript decorator ( basicly you template the form ) . The second , even if looks nice – to create your own element , sucks because you need to re-create all the input elements . And most of all sucks because you re-invent the wheel . Writing a decorator that pretty much does same thing as the default ones , sucks too.
ViewScript is a nice sollution , the problem I see here is that if you want to change your forms layout , you need to edit all those template files ( or maybe I’m wrong ). It’s a good sollution , but I want to use the normal path , if those zf people made the effort to build it .
So … after googling and googling , I have to say the docs are pretty bad . Even tutorials . They don’t explain exactly how to “reset” the damn decorating .
Let’s take it easly and explain how decorators apply ( and how forms works ) . It’s easy : you give an array which is processed … in the order you gave it . First decorator will be always the ViewHelper . This one renders your <input> element and nothing else ( along with the attributes you set for it ). The next decorator you declare will “embrace” the content you already have . If you have a label , it will be concated with the <input> element . If you have a HtmlTag , it will include your input or whatever is the current content. If you have another Html, it will embrace the new content , -that’s your previous HtmlTag which has the input side .  And so on . It’s like having boxes of different sizes in the order of first is the smallest , last is the biggest .  Pretty much same thing is when you add things in a form class , either they are elements or groups or decorations . There is one thing : you can define order here ( I’ll show later an example).

So , let’s say we want to have table instead of dt/dd .

$this->addElement('text','name',array(
			'filters' => array('StringTrim'),
			'validators' => array(array('StringLength',true,array(3,128))),
			'required' => true,
			'label' => 'Name :',
			'decorators' => array('ViewHelper',
						array(array('linebr' => 'HtmlTag'),'options'=> array('tag' => 'br', 'placement' => 'append','openOnly'=>true)),
						array('Errors'),
						array(array('data'=>'HtmlTag'),'options'=>array('tag'=>'td')),
						array('Label','options'=>array('tag'=>'td')),
						array(array('row'=>'HtmlTag'),'options'=>array('tag'=>'tr')))
			));	

This will render something like this :

<tr>
    <td>
        <label>Name:</label>
    </td>
    <td>
        <input type="text" name="name" id="name" value="">
       <br/>
       <ul><li>Error 1</li></ul>
    </td>
</tr>

Let’s recap : first we have the input element , rendered by ViewHelper , then we have a br line ( use openOnly to not create 2 br’s) – also note we use placement ( if we use prepend , the br will be pasted before the input ) , then we have Errors . Next comes a td tag which will embrace everything we have until now . Now it’s the Label time , instead of dt we have a td . By default it’s prepended . The last one is the tr tag that completes our row . Note that for multiple HtmlTag decorators you need to give them a key(or name , whatever).This is to create a new HtmlTag instance , otherwise the same decorator is applied – you can try removing the keys to see what will happen .
This looks ugly … if you do it for all your elements . But we’re in OO world so we can improve it. But first , let’s say you add several more elements .At the end you will add this :

$this->setDecorators(array('FormElements',array('HtmlTag', array('tag' => 'table')),'Form'));

This will render the table tags . Remember this must be added after you inserted all your elements , more likely after the submit button .
Ok , how can you make this spagetti be more nicer ? Your could use overwriting the default decoratos for elements , but I’m using another alternative : simply create a form with no elements , you create a method that returns the whole decorator array for the elements and you can have something like this :

$this->addElement('text','name',array(
	'filters' => array('StringTrim'),
	'validators' => array(array('StringLength',true,array(3,128))),
	'required' => true,
	'label' => 'Name :',
	'decorators' => $this->decorators()
));	

Looks a bit nicer now . Even more you can have a method :

public function loadDefaultDecorators()
{
      $this->setDecorators(array('FormElements',array('HtmlTag', array('tag' => 'table')),'Form'));
}

This is be the default decorator for the form , so you don’t need to add it in init() . Your form will extends this one instead of default Zend_Form .
So now you can create forms that extends this one and you will get html table output .
It’s very likely that you’ll have several types of decorators , one is for sure the submit button – because you don’t have a label there .

public function submitdecorator()
{
	return array('ViewHelper',
			array(array('data'=>'HtmlTag'),'options'=>array('tag'=>'td')),
			array(array('data2'=>'HtmlTag'),'options'=>array('tag'=>'td','placement'=>"prepend")),
			array(array('row'=>'HtmlTag'),'options'=>array('tag'=>'tr')));
}

Here you go . For submit button you do ‘decorators’ => $this->submitdecorator() . You can have a label too , but it will be ignored , because we didn’t defined the decorator for it . Note that for the second HtmlTag I used prepends , otherwise the cell with the submit button will be in left and we want it in right (to be aligned with the inputs , not the labels , but as you wish ) .

Ok, what’s next : using DisplayGroup . Let’s say you have you form table with several input elements , but you also have 2 checkboxes that you want to be displayed in the same cell .
First , you can’t use the decorator above , because it will render one element per row . Instead you want the 2 checkboxes to be rendered inside the cell and every checkbox to be inside a div for futher styling .

'decorators' => array(
			'ViewHelper',
			array('Label','options'=>array('placement'=>'append')),
			array('HtmlTag','options'=>array('tag'=>'div'))

This will be your decorator for each checkbox . Note that I put the label after the checkbox .
Next is to group them :

$this->addDisplayGroup(array('firstcheckbox','secondcheckbox'), 'nameofgroup',
		array(
			'description' => 'Bla bla bla :',
			'decorators'=>array('FormElements',
					  array(array('data'=>'HtmlTag'),'options'=>array('tag'=>'td')),
					  array('Description','options'=>array('tag'=>'td','placement'=>"prepend")),
				          array(array('row'=>'HtmlTag'),'options'=>array('tag'=>'tr'))
		)));		

‘firstcheckbox’ and ‘secondcheckbox’ are the names of your checkboxes .You always need to add there the elements names , otherwise they are not included in the group . I used Description for filling the left row .

Last thing : I said something about order of elements . How is useful . Let’s say you made a form … user data or something . Somewhere you have an admin are where as admin you can edit this data . Most likely you wil have several additions fields ( like if user is banned or something ) . You can extend the form you have . The problem is when you add new fields , they will be added AFTER the submit button . Why ? because elements are rendered in the order they are added , except if you set an order . By default , there is no order , it’s the normal iteration of the array with elements . But you can set it . The most simple way is to set a high value to the submit button – it will be rendered the last one . You can do it even in your admin form :

$this->getElement('mysubmit')->setOrder(100);

This way you don’t need to touch the initial form .