0

I'm currently doing some experiments with Zend forms and custom rendering and I was wondering how to handle form errors.

A concrete example:

let's say I want to render a fieldset with a radio element which has 3 options and only one is correct. The Idea is to wrap each option in some div in order to apply bootstrap classes, and then highlight the option (adding has-error class) when (custom) validation fails.

The rendering could be something like this (here directly into the view script):

foreach($form->getFieldsets() as  $fieldset) {

    echo "<fieldset><legend>".$fieldset->getName()."</legend>";

    foreach ($fieldset as $element) {

    $opt=$element->getOption('value_options');

        foreach ($opt as $key=>$val) {
            if($element->getValue()==$key){$check="checked='checked'";}else{$check="";}

            echo"<div class='form-group (HERE ADD `has-error` CLASS IF VALIDATION FAILS)'>".
                "<label class='custom-control'>".
                "<input type='radio' class='custom-control-input' name='".$element->getName()."' value='".$key."' ".$check.">".
                "<span class='custom-control-indicator'></span>".
                "<span class='custom-control-description'>*TEXT*</span>".
                "</label>".
                "</div>";
        }//3rd foreach
    }//2nd foreach
echo "</fieldset>
}//1st foreach

And here is the question: how to detect (after validation) if the element has an error in order to add a bootstrap error class?

I was thinking a possible solution could be:

   //custom validator

   [...]

   public function isValid($value, array $context = null)
    {
       $this->setValue($value);


       if ($value==1) {
           $this->error(self::MESSAGE1); //Your answer (radio with value=1) is wrong!
           $this->error(self::CODE1);  //1
           return false;
       }

       [...]
       return true;
   }

then:

//updated view script
$messages=$form->getMessages();

foreach($form->getFieldsets() as  $fieldset) {

    echo "<fieldset><legend>".$fieldset->getName()."</legend>";

    foreach ($fieldset as $element) {

    $opt=$element->getOption('value_options');

        foreach ($opt as $key=>$val) {
            if($element->getValue()==$key){$check="checked='checked'";}else{$check=null;}

            $field=$fieldset->getName();
            $radio=$element->getName();
            if(isset($messages[$fieldset][$radio]) && $messages[$fieldset][$radio]==$key){$class='has-error';}else{$class=null;}

            echo"<div class='form-group ".$class."'>".
                "<label class='custom-control'>".
                "<input type='radio' class='custom-control-input' name='".$element->getName()."' value='".$key."' ".$check.">".
                "<span class='custom-control-indicator'></span>".
                "<span class='custom-control-description'>*TEXT*</span>".
                "</label>".
                "</div>";
        }//3rd foreach
    }//2nd foreach
echo "</fieldset>
}//1st foreach

The only problem is that $element->getName() returns a string with like: "fieldsetName[elementName]" while $form->getMessages() returns a multidimensional array like fieldsetName=array(elementName=array(/*MESSAGES*/),);

A possible hack could be to add a custom attribute to the element definition into the form class.

I know that's pretty raw but it's just the concept. What do you think? Any better solution to detect errors?


EDIT:

Probably my first explaination wasn't very clear (my fault). For more clearness, THIS should be the (visual) expected output.

Obviously the idea pretty different since all the logic (filtering/validation/rendering/) should be handled by php/zend. Jquery should only submit the form asynchronously (ajax+serializeArray method) and then return the rendered view (using new viewmodel->setTerminal()). But tht's not the problem.

The problem is that the radio element extends Zend\Form\View\Helper\FormMultiCheckbox, then it's not possible to wrap the single options.

//Module/src/Form/ExampleForm
[...]
$this->add([
            'name' => 'radio_element',
            'type' => 'radio',
            'options' => [
                'label' => 'radio_element',
                'value_options' => [
                             1 => 'one',
                             2 => 'two',
                             3 => 'three',
                     ],
            ],

        ]);

[...]

//the view
echo "<div class='MY_CUSTOM_WRAPPER'>";
echo $this->formRadio($form->get('radio_element'));
echo "</div>";

//Output:
<div class='MY_CUSTOM_WRAPPER'>
<label><input type='radio' name='radio_element' value='1'>one</label>
<label><input type='radio' name='radio_element' value='2'>two</label>
<label><input type='radio' name='radio_element' value='3'>three</label>
</div>

The only way to wrap every single option (as far as I know) is to use a custom helper (In order to keep things simple I actually bypassed this step by acting directly into the view script).

And here is the real problem: since the three options ARE NOT ELEMENTS (but options of an element), I will need to:

  1. check the selected value and set an error message (custom validator)
  2. render the element and format the option associated with the error (and not the whole element aka all options).

My solution (updated):

//custom validator
public function isValid($value, array $context = null)
    {
       $this->setValue($value);

       if ($value==1) {
           $this->setMessage('Your answer is wrong because...',self::ERRDESC);

           $this->error(self::ERRNO); //defined above as %value% -> =1
           $this->error(self::ERRDESC);
           return false;
       }
         if ($value==2) {
           $this->setMessage('That\'s right!!!',self::ERRDESC);
           $this->setMessage("correct".$value,self::ERRNO);

           $this->error(self::ERRNO);
           $this->error(self::ERRDESC);
           return false;
       }
         if ($value==3) {
           $this->setMessage('That\'s really wrong! ',self::ERRDESC);

           $this->error(self::ERRNO);//defined above as %value% -> =3
           $this->error(self::ERRDESC);
           return false;
       }
       [...]
       return true;
    }

    //view script

    foreach($form->getFieldsets() as  $fieldset) {

        echo "<fieldset><legend>".$fieldset->getName()."</legend>";

        foreach ($fieldset as $element) {

        $opt=$element->getOption('value_options');

            $class2=null;
            foreach ($opt as $key=>$val) {

                if($element->getValue()==$key){$check="checked='checked'";}else{$check=null;}

                $class=null;
                if(isset($element->getMessages()['ERRNO'])){

                  switch($element->getMessages()['ERRNO']){
                    case $key: $class='is-invalid'; $class2='alert-danger';
                    break;
                    case "correct".$key: $class='is-valid'; $class2='alert-success';
                    break;
                  }
                }

                echo"<div class='custom-control custom-radio'>
                     <input type='radio' id='radio_".$key."' name='".$element->getName()."' class='custom-control-input ".$class."' value='".$key."' ".$check.">
                     <label class='custom-control-label' for='radio_".$key."'>$val</label>
                     </div>
                     ";

            }//3rd foreach (radio element's options)
            echo"<div class='alert $class2' role='alert'>";
            echo isset($element->getMessages()['ERRDESC']) ? $element->getMessages()['ERRDESC']:null;
            echo "</div>";
        }//2nd foreach (radio element)
    echo "</fieldset>";

    }//1st foreach (fieldset)

Do you know any better solution to get and format the signle radio options?

Gwen Hufschmid
  • 197
  • 1
  • 12
  • 1
    You are overthinking i guess. Why not can you check form messages? Element class has a method as "getMessages". Checkout documentation http://zendframework.github.io/zend-form/element/element/ – Mehmet SÖĞÜNMEZ Feb 17 '18 at 21:08
  • @MehmetSÖĞÜNMEZ actually I AM checking form messages but you made me realize that calling $element->getMessages() instead of $form->getMessages() solves the comparison issue since no keys are required to check the element's messages. I'll edit the code. – Gwen Hufschmid Feb 19 '18 at 09:15
  • could you solve your problem? Also your fiddle is blocked. – Mehmet SÖĞÜNMEZ Feb 28 '18 at 08:56

0 Answers0