views:

163

answers:

3

I'm trying to create a form using Zend_Form (Zend_Framework component). The users should be able to add arbitrary number of fields. Just as you can upload files with gmail GUI. Example:

[_____] [+]

After clicking on the [+] button:

[_____]
[_____] [+]

To get things complicated, I'd like to add field pairs, so it would look like this:

[_____] [_____]
[_____] [_____] [+]

How can I keep the availability of the easy built-in validation and assign methods while implementing this feature? Do I need a subform? Do I need something else? After submitting the form, the code should be able to map the fields into and array.

I'm afraid I must subclass Zend_Form or SubForm, but I want to be aware of the easiest/prettiest way.

A: 

Here's how to add ten input fields, each having Alpha validation:

$sub_test = new Zend_Form_SubForm();
$num_fields = 10;
for ( $i = 0; $i < $num_fields; $i++ ) {
  $element = $form->createElement('text', strval($i));
  $element->addValidator('Alpha'); // just to see in action
  $sub_test->addElement($element);
}
$form->addSubForm($sub_test, 'test'); // values mapped to $_POST['test'][0..9]

To determine how many to create/show, more work is necessary on your part, of course, but it seems straightforward.

  1. Look at $_POST['test'] to see how many values are filled in -- always include those.
  2. Look at another hidden variable to know how many total fields to show -- even those that are empty.
  3. etc.
Derek Illchuk
+1  A: 

The easiest approach to take here is to use a simple custom Zend Validator, that would parse input elements as the entire array PHP see it as so you are able to perform the following automatically.

  1. Validate each input element independently and display the error messages associated for each element.
  2. Return a parse-able array to rebuilt the form that was just submitted.

For the Zend_Validator

class Validate_InputArray extends Zend_Validate_Abstract
{
 const INVALID = 'invalid';
 const ERROR   = 'error';

 /**
  * Failed array elements used to regenerate same elements
  * on next form build
  *
  */
 protected $_elements = array();

    protected $_messageTemplates = array(
        self::INVALID => "Could not locate post element %value%",
  self::ERROR   => "YOUR ERROR MESSAGE|%value% is required"
    );

    public function __construct()
    {}

    public function isValid($element)
    {
        if (!$_POST[$element]) {
   $this->_error(self::INVALID);
   return false;
  }

  $elements = array();

  if (is_array($_POST[$element])) {

   $fail = false;

   foreach ($_POST[$element] as $k => $v) {
    if (!$this->_validateElement($v)) {
     $this->_error(self::ERROR);
     $elements[$k] = self::ERROR;
    }
   }

   $this->_setElements($elements);

   if ($fail) {
    return false;
   }

  } else {
   if (!$this->_validateElement($_POST[$element])) {

    $this->_error(self::ERROR);
    $elements[0] = self::ERROR;

    $this->_setElements($elements);

    return false;
   }
  }
    }

 protected function _setElements($elements)
 {
  $this->_elements = $elements;
  return $this;
 }

 public function getElements()
 {
  return $this->_elements;
 }

 private function _validateElement($value)
 {
  // do your validation here
  // return true/false
 }
}

Now the code using this to parse an input with a possible array as an a value and validate each element and regenerate the exact form submitted, with the identical arbitrary fields.

$fail = false;

if ($this->getRequest()->isPost()) {

 require 'Validate_InputArray.php';

 $validator = new Validate_InputArray();
 $elements  = array();

 if (!$validator->isValid($validator)) {

  $fail = true;

  foreach ($validator->getElements() as $k => $v) {
   $elements[$k] = $v;
  }
 }
}

if ($fail) {

 $messages = $validator->getMessages();

 foreach ($elements as $k => $v) {
  // Add custom methods here
  $element = $form->createElement('text', 'elementName[]');
        $element->addErrorMessages($messages[$k]);
 }

} else {
 $form->addElement('elementName[]');
}

This will allow you to validate any number of arbitrary input elements as needed without the need to sub-form, or worry about the need to re-add the form elements if and when a arbitrary element fails validation and needs to be rebuilt on the client side.

nwhiting
Thanks for the long answer, I'm going to try it in few days.
erenon
No problem at all, let me know how it goes ;)
nwhiting
A: 

Here is my own solution: I have created two hidden fields, one for the keys, one for the values. A javascript piece of code creates fake field pairs on deamand, and onChange, it maps all the fake fields into the hidden ones using JSON. It's easy to handle on server side, but the used javascript techique is not discrete.

erenon