Dzisiaj przedstawię wam szybki i prosty sposób na napisanie własnego dekoratora do Zend_Form. Dekorator ten opakuje przygotowany formularz w zakładki dostarczone przez jQuery.

Na początek musimy pobrać jQuery oraz jQuery UI w najnowszych wersjach. W przypadku jQuery UI nie ma znaczenia jaki temat (Theme) zostanie wybrany, ponieważ nie będziemy się dzisiaj zajmować upiększaniem formularza.

Do napisania mamy dwie klasy – formularz oraz dekorator. Dekorator będzie korzystał z helpera widoku, więc również jego będziemy musieli napisać. Na szczęście jQuery zadba o resztę, czyli o ostylowanie formularz i obsługę zakładek.

Zaczynamy od klasy formularza. Celowo pominąłem walidację oraz filtrowanie danych, by nie zaciemniać całości. W produkcyjnym kodzie należy filtrować i walidować dane!

<?php
class Form_Tabs extends Zend_Form
{
	public function init()
	{
		$text1 = new Zend_Form_Element_Text('field1');
		$text1->setLabel('Pole 1');

		$text2 = new Zend_Form_Element_Text('field2');
		$text2->setLabel('Pole 2');
		
		$text3 = new Zend_Form_Element_Text('field1');
		$text3->setLabel('Pole 3');

		$text4 = new Zend_Form_Element_Text('field2');
		$text4->setLabel('Pole 4');
		
		$text5 = new Zend_Form_Element_Text('field1');
		$text5->setLabel('Pole 5');

		$text6 = new Zend_Form_Element_Text('field2');
		$text6->setLabel('Pole 6');
		
		$submit = new Zend_Form_Element_Submit('btn');
		$submit->setLabel('Zapisz');
		
		
		$subform1 = new Zend_Form_SubForm();
		$subform2 = new Zend_Form_SubForm();
		$subform3 = new Zend_Form_SubForm();

		$subform1->addElement($text1);
		$subform1->addElement($text2);
		
		$subform2->addElement($text3);
		$subform2->addElement($text4);
		
		$subform3->addElement($text5);
		$subform3->addElement($text6);
		
		
		$this->addSubForm($subform1, 'subform1');
		$this->addSubForm($subform2, 'subform2');
		$this->addSubForm($subform3, 'subform3');
		
		$this->addElement($submit);

		$this->clearDecorators();

		$this->addDecorator('FormElements')
			 ->addDecorator('HtmlTag', array('tag' => 'div'))
			 ->addDecorator('Form');

		$this->setSubFormDecorators(array(
			array('FormElements'),
			array('HtmlTag', array('tag' => 'div')),
			array('Fieldset')
		));

		foreach($this->getSubForms() as $subform) {
			$subform->setElementDecorators(array(
				array('ViewHelper'),
				array('Errors'),
				array('Label'),
				array('HtmlTag', array('tag' => 'div', 'class' => 'element-group'))
			));
		}

		$submit->setDecorators(array(
			array('ViewHelper'),
			array('HtmlTag', array('tag' => 'div', 'class' => 'submit-group'))
		));
		
	}
}

Powyższa klasa jest standardowym formularzem stworzonym przy pomocy Zend_Form, wykorzystującym subform. Subform, podobnie jak display group, używa znacznika filedset do rozdzielenia od siebie poszczególnych części formularza. My wykorzystamy ten fakt, do stworzenia oddzielnych kontenerów na treść, która będzie obsługiwana przez zakładki. W tym celu musimy napisać odpowiedni dekorator oraz helper widoku.

Klasa dekoratora wymaga od nas dwóch rzeczy. Musi dziedziczyć po klasie Zend_Form_Decorator_Abstract oraz zawierać metodę render(), w której zawarta będzie logika naszego dekoratora. Tak naprawdę, to metoda render() przekazuje dane do odpowiedniego helpera widoku, a następnie zwraca odpowiedni kod HTML, w który udekorowany został konkretny element.

<?php
class App_Form_Decorator_FormTabs extends Zend_Form_Decorator_Abstract
{
	public function render($content) {
		$element = $this->getElement();

		if(!$element instanceof Zend_Form) {
			return $content;
		}

		if(null === $element->getView()) {
			return $content;
		}

		return $element->getView()->formTabs($content, 
											 $element->getName(), 
											 $element->getSubForms());
	}
}

Pozostało jeszcze napisać helper widoku.

<?php
class Zend_View_Helper_FormTabs extends Zend_View_Helper_Abstract
{
	public function formTabs($content, $formName, $subforms) {
		if(count($subforms) > 0) {
			$this->view->headScript()->appendFile('/js/jquery-1.3.2.min.js');
			$this->view->headScript()->appendFile('/js/jquery-ui-1.7.2.custom.min.js');
			$this->view->headLink()->appendStylesheet('/css/jquery-ui-1.7.2.custom.css');
			$out  = '<div id="tabs-'.$formName.'">';
			$out .= '<ul>';
			foreach($subforms as $subform) {
				$out .= '<li><a href="#fieldset-' . $subform->getName() . '">';
				$out .= $this->view->translate($subform->getName());
				$out .= '</a></li>';
			}
			$out .= '</ul>';
			$out .= $content;
			$out .= '</div>';
			$out .= '<script type="text/javascript">';
			$out .= '$("#tabs-' . $formName . '").tabs();';
			$out .= '</script>';
		}
		else {
			$out = $content;
		}

		return $out;
	}
}

W helperze tym dodajemy niezbędne biblioteki javascript, by nasze zakładki mogły w ogóle działać. Następnie tworzymy opakowanie dla zakładek oraz kontenerów. W pętli foreach tworzone są zakładki. Na koniec dodawany jest skrypt, który odpowiada za mechanizm zakładek.

Ostatnią rzeczą jaka pozostała do zrobienia, to dodanie nowego dekoratora do formularza i możemy cieszyć się formularzem z podziałem na zakładki.

$this->addDecorator('FormElements')
	 ->addDecorator('HtmlTag', array('tag' => 'div'))
	 ->addDecorator('Form')
	 ->addDecorator(new App_Form_Decorator_FormTabs());

Oczywiście same zakładki nie wyczerpują tematu. Można dodać walidację po stronie klienta poszczególnych elementów formularza (oczywiście po stronie serwera dane muszą być kolejny raz zwalidowane!). Inna modyfikacja może polegać na otwarciu zakładki, w której znajduje się element niepoprawnie zwalidowany po stronie serwera. Możliwości jest wiele.

Na zakończenie dodam, że w taki sam sposób można dekorować elementy formularza i dodać np do pola tekstowego datepicker-a lub colorpicker-a.