Saturday, May 29, 2010

Tabbed Forms, Part 3

Part 1 in this series was how to create a simple tabbed form using subforms and ZendX. Part 2 explained how to configure the decorators such that additional elements could be added outside the tab panes, such as a submit button. But maybe you don't want to use subforms and would prefer to use display groups instead. This can easily be done and I will cover this in Part 3.

Here was the code we used to create the subforms and add some elements:
$subforms = array();
$subforms[0] = new ZendX_JQuery_Form;
$username = $form->createElement('text', 'username')
->setLabel('username');
$subforms[0]->addElement($username);

$subforms[1] = new ZendX_JQuery_Form;
$guestname = $form->createElement('text', 'guestname')
->setLabel('guestname');
$subforms[1]->addElement($guestname);
So first let's add the same elements to our form using display groups:
$username = $form->createElement('text', 'username')
->setLabel('username');
$form->addElement($username);
$form->addDisplayGroup(array('username'), 'Page 1');

$guestname = $form->createElement('text', 'guestname')
->setLabel('guestname');
$form->addElement($guestname);
$form->addDisplayGroup(array('guestname'), 'Page 2');
Now that we have our display groups configured, we can set up the same decorators that we previously used with our subforms:
foreach ($form->getDisplayGroups as $displayGroup) {
$displayGroup->setDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'div', 'class' => 'displayGroup')),
array('TabPane', array('jQueryParams' =>
array('containerId' => 'mainForm', 'title' => $displayGroup->getName()))),
'Form'
));
}
So with these modifications in place, here is the complete createTabbedForm function:
/**
* You must set the form id so that you can add your tabPanes to the tabContainer
*/
$form = new ZendX_JQuery_Form;
$form->setAttrib('id', 'mainForm');

/**
* Add your form elements first, before setting the decorators
*/
$form->addElement('submit', 'Submit');

/**
* Use the TabContainer View Helper
* Use both FormElements and SubformElements to render elements inside and outside the tabs
*/
$form->setDecorators(array(
array('decorator' => array('SubformElements'=>'FormElements')),
array('HtmlTag', array('tag' => 'div', 'id'=>'tabContainer', 'class'=>'mainForm')),
array('TabContainer', array('id'=>'tabContainer', 'style'=>'width: 530px;')),
'FormElements',
'Form'
));

/**
* Create the display groups and add some elements
*/
$username = $form->createElement('text', 'username')
->setLabel('username');
$form->addElement($username);
$form->addDisplayGroup(array('username'), 'Page 1');

$guestname = $form->createElement('text', 'guestname')
->setLabel('guestname');
$form->addElement($guestname);
$form->addDisplayGroup(array('guestname'), 'Page 2');

/**
* Set the decorators on the display groups to use TabPane View Helper
* Note that containerId is the same as the form id set earlier
*/
foreach ($form->getDisplayGroups() as $displayGroup) {
$displayGroup->setDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'div', 'class' => 'displayGroup')),
array('TabPane', array('jQueryParams' =>
array('containerId' => 'mainForm', 'title' => $displayGroup->getName()))),
'Form'
));
}

return $form;
Happy coding!

Friday, May 28, 2010

Tabbed Forms, Part 2

In the previous article, I described how to create a simple tabbed form. The code for this form contained submit elements on each page. In this article I will describe how to create a tabbed form with a submit element that is outside of the tabs.

If you remember from before, the form decorators were set up like this:
$form->setDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'div', 'id'=>'tabContainer', 'class'=>'mainForm')),
array('TabContainer', array('id'=>'tabContainer', 'style'=>'width: 530px;')),
'Form'
));
If you understand how decorators work, you know that they are rendered in reverse order. So the Form decorator generates the outtermost div, then the TabContainer decorator, then the HtmlTag decorator, and finally the FormElements. With this structure, all the form elements are rendered inside the tab container.

What we want is to render some additional elements outside the tab container, so we would like to use a decorator structure like this:
$form->setDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'div', 'id'=>'tabContainer', 'class'=>'mainForm')),
array('TabContainer', array('id'=>'tabContainer', 'style'=>'width: 530px;')),
'FormElements',
'Form'
));
Unfortunately, each decorator alias can only be used once in the chain. This note in the ZF documentation explains:

Internally, Zend_Form uses a decorator's class as the lookup mechanism when retrieving decorators. As a result, you cannot register multiple decorators of the same type; subsequent decorators will simply overwrite those that existed before. To get around this, you can use aliases. Instead of passing a decorator or decorator name as the first argument to addDecorator(), pass an array with a single element, with the alias pointing to the decorator object or name.

Well, that's a bit confusing, you might think the following decorator set would work, but it doesn't:
$form->setDecorators(array(
array('SubformElements'=>'FormElements'),
array('HtmlTag', array('tag' => 'div', 'id'=>'tabContainer', 'class'=>'mainForm')),
array('TabContainer', array('id'=>'tabContainer', 'style'=>'width: 530px;')),
'FormElements',
'Form'
));
This one does, though:
$form->setDecorators(array(
array('decorator' => array('SubformElements'=>'FormElements')),
array('HtmlTag', array('tag' => 'div', 'id'=>'tabContainer', 'class'=>'mainForm')),
array('TabContainer', array('id'=>'tabContainer', 'style'=>'width: 530px;')),
'FormElements',
'Form'
));
So here is the new code for our createTabbedForm function, with the submit button included outside the tab container:
/**
* You must set the form id so that you can add your tabPanes to the tabContainer
*/
$form = new ZendX_JQuery_Form;
$form->setAttrib('id', 'mainForm');

/**
* Add your form elements first, before setting the decorators
*/
$form->addElement('submit', 'Submit');

/**
* Use the TabContainer View Helper
* Use both FormElements and SubformElements to render elements inside and outside the tabs
*/
$form->setDecorators(array(
array('decorator' => array('SubformElements'=>'FormElements')),
array('HtmlTag', array('tag' => 'div', 'id'=>'tabContainer', 'class'=>'mainForm')),
array('TabContainer', array('id'=>'tabContainer', 'style'=>'width: 530px;')),
'FormElements',
'Form'
));

/**
* Create the subforms and add some elements
*/
$subforms = array();
$subforms[0] = new ZendX_JQuery_Form;
$username = $form->createElement('text', 'username')
->setLabel('username');
$subforms[0]->addElement($username);

$subforms[1] = new ZendX_JQuery_Form;
$guestname = $form->createElement('text', 'guestname')
->setLabel('guestname');
$subforms[1]->addElement($guestname);

/**
* Set the decorators on the subforms to use TabPane View Helper
* Note that containerId is the same as the form id set earlier
*/
foreach ($subforms as $pageno=>$subform) {
$subform->setAttrib('id', 'subForm');
$subform->setDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'div', 'class' => 'subForm')),
array('TabPane', array('jQueryParams' =>
array('containerId' => 'mainForm', 'title' => 'Page '.$pageno))),
'Form'
));
$form->addSubform($subform, 'subform_'.$pageno);
}

return $form;
Happy coding!

Monday, May 24, 2010

Tabbed Forms, Part 1

If you want to create a form with jQuery-style tabbed pages, the component to extend isn't in the standard Zend Framework but in ZendX, the Zend Framework Extensions Library. If you download the full version of the Zend Framework, ZendX can be found in the /extras/library folder. The component to use is ZendX_JQuery_Form.

The Zend Framework documentation does have a segment on using the ZendX Tab Decorator. This is pretty good, but it combines the Tab Decorator with other jQuery decorators and leaves out the jQuery includes and call that are needed in the view. So this example is just a very simple but complete tabbed form.

Basically, what we are doing is to add each of the form elements to a subform instead of the main form. Each subform then becomes a tab. On the main form you will set the decorators to use the TabContainer view helper, and then on the subforms the decorators will use the TabPane view helper. The form_id on the main form is used by the TabPanes to register them with the correct TabContainer. So here is a simple createTabbedForm function:
/**
* You must set the form id so that you can add your tabPanes to the tabContainer
* Set the decorator to use the TabContainer View Helper
*/
$form = new ZendX_JQuery_Form;
$form->setAttrib('id', 'mainForm');
$form->setDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'div', 'id'=>'tabContainer', 'class'=>'mainForm')),
array('TabContainer', array('id'=>'tabContainer', 'style'=>'width: 530px;')),
'Form'
));

/**
* Create the subforms and add some elements
*/
$subforms = array();
$subforms[0] = new ZendX_JQuery_Form;
$username = $form->createElement('text', 'username')
->setLabel('username');
$subforms[0]->addElement($username);

$subforms[1] = new ZendX_JQuery_Form;
$guestname = $form->createElement('text', 'guestname')
->setLabel('guestname');
$subforms[1]->addElement($guestname);

/**
* Set the decorators on the subforms to use TabPane View Helper
* Note that containerId is the same as the form id set earlier
*/
foreach ($subforms as $pageno=>$subform) {
$subform->setAttrib('id', 'subForm');
$subform->setDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'div')),
array('TabPane', array('jQueryParams' =>
array('containerId' => 'mainForm', 'title' => 'Page '.$pageno))),
'Form'
));
/**
* Add a submit button to each subform
*/
$subform->addElement('submit', 'Submit');
$form->addSubform($subform, 'subform_'.$pageno);
}

return $form;

Now just put this into a controller and call it from the action:
class ExampleController extends Zend_Controller_Action {

public function init() {
$this->_helper->layout->disableLayout();
}

public function createTabbedForm() {
// insert from above
}

public function tabbedformAction() {
$this->view->form = $this->createTabbedForm();
}

}

Finally, you will create your view, tabbedform.phtml, to display the form. As mentioned, there are some jQuery includes and calls that are needed here. Basically, you need the jQuery library, the jQuery UI core library, and the jQuery UI tabs library, as well as some jQuery themes to render the display. We can use the HeadScript and HeadLink view helpers to easily create the correct HTML. Here is what you need:
// jQuery themes list:  http://www.stemkoski.com/jquery-ui-1-7-2-themes-list-at-google-code/
$this->headLink()
->appendStylesheet('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.1/themes/base/jquery-ui.css')
->appendStylesheet('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.1/themes/base/jquery-ui.tabs.css');

// jQuery UI versions: http://code.google.com/apis/ajaxlibs/documentation/#jqueryUI
$this->headScript()
->appendFile('http://ajax.googleapis.com/ajax/libs/jquery/1.4.1/jquery.min.js')
->appendFile('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.1/jquery-ui.min.js')
->appendFile('http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.1/jquery-ui.tabs.js')
->appendScript('$(function() { $("#tabContainer").tabs(); });');


echo $this->headLink();
echo $this->headScript();
echo $this->form;

That's it! Using this code should quickly and easily get you a tabbed form that you can now modify to your heart's desire.

Happy coding!