zend framework 2/3 multicheckbox disable isEmpty errorMessage

489 Views Asked by At

By default a multicheckbox element triggers an error message when none of the options are selected:

 array (size=1)
  'checkbox' => 
    array (size=1)
      'isEmpty' => string 'Value is required and can't be empty' (length=36)

I found out that the message can be disabled using the function getInputFilterSpecification:

class MyForm extends Form  implements InputFilterProviderInterface{

  private $inputFilter;

  public function __construct($name = null){

    parent::__construct($name);


    $this->add([
                'name' => 'checkbox',
                'type' => 'checkbox',
                'attributes' => [],
                'options' => [
                             'value_options' =>[
                                                [
                                                'value' => '0',
                                                //'selected' => true,
                                                'label' => 'One',
                                                'label_attributes'=>[],
                                                'attributes' => [],
                                                ],

                                                [
                                                'value' => '1',
                                                'label' => 'Two',
                                                'label_attributes'=>[],
                                                'attributes' => [],
                                                ],
                                                [
                                                'value' => '2',
                                                'label' => 'Three',
                                                'label_attributes'=>[],
                                                'attributes' => [],
                                                ],
                             ],
                ],
    ]);


  }//construct

   public function getInputFilterSpecification() {
        return array(
         'checkbox'=>['required'=>false,],
        );  
    }


}//class

The only "issue" with this approach is that you have to specify name and the relative option for all the instances of the multicheckbox element; having 10 different multicheckboxes the inputfilter specifications would be something like:

public function getInputFilterSpecification() {
   return array(
     'checkbox1'=>['required'=>false,],
     'checkbox2'=>['required'=>false,],
     'checkbox3'=>['required'=>false,],
     ...   
   );  
}

...and ok it's not that bad but it seems pretty dumb since the element (and/or all the value options) accepts the attribute required but then is practically treated as if it was required and that makes no sense at all.

Where does that message come from? It seems a preassigned notEmpty validator but I haven't found any trace of it in the Zend's Mulicheckbox class or into the parent Checkbox class.

Isn't there really no better approach to do that?

1

There are 1 best solutions below

0
Gwen Hufschmid On

I found the answer on my own.

Zend's Multicheckbox element extends the Checkbox element which implements InputProviderInterface.

And in Checkbox (line 165) there is:

public function getInputSpecification()
{
    $spec = [
        'name' => $this->getName(),
        'required' => true,
    ];

    if ($validator = $this->getValidator()) {
        $spec['validators'] = [
            $validator,
        ];
    }

    return $spec;
}

That required parameter is retrieved from the Zend\InputFilter\Input class and if true (always true) a notEmpty validator is added.

Ergo: there's no way to change that behaviour throug the element's options (and again: that's dumb).


Anyway, something can be done by using a custom element that extends multicheckbox (and that's actually what I'm already doing, for other reasons):

namespace Form\Form\Element;

use Zend\InputFilter\InputProviderInterface;
use Zend\ModuleManager\Feature\ViewHelperProviderInterface;
use Zend\Form\Element\MultiCheckbox;

class BootstrapMultiCheckbox extends MultiCheckbox implements  ViewHelperProviderInterface, InputProviderInterface{

  protected $attributes = [
                          'type' => 'bootstrapMultiCheckbox',
                          ];

  public function getViewHelperConfig(){return array('Form\Form\View\Helper\BootstrapMultiCheckboxHelper', null);}

  public function getInputSpecification(){
    $spec = [
            'name' => $this->getName(),
            'required' => false,
            ];

    if ($validator = $this->getValidator()) {

      $spec['validators'] = [$validator,];
    }

    return $spec;
  }

}

I just copied the getInputSpecification function from the original Checkbox element and set required to false. This way there's no need anymore to specify the required parameter into the form's getInputFilterSpecification function.

Obviously this solution makes sense if you're already using a custom version of multicheckbox, otherwise...make your own considerations.


UPDATE: handle the element's required attribute, practical use

Basically you will handle the required attribute this way (here for a custom Radio element):

//Form __construct:

$this->add([
                'name' => 'radio',
                'type' => BootstrapRadio::class,
                'attributes' => ['required' => true,],
                'options' => [
                             'value_options' =>[...],
                ],
    ]);

Then:

//BootstrapRadio Element:
public function getInputSpecification(){

    //If required==true, then the notEmpty validator will be added (see above)
    $required=$this->getAttribute('required');

    $spec = [
            'name' => $this->getName(),
            'required' => $required,
            ];

    if ($validator = $this->getValidator()) {

      $spec['validators'] = [$validator,];
    }

    return $spec;
  }

Alternatively you might want to assign the required attribute to a few options only (senseless for a radio element):

//Form __construct:

$this->add([
                'name' => 'checkbox',
                'type' => BootstrapMultiCheckbox::class,
                'options' => [
                             'value_options' =>[
                                                [
                                                'value' => '0',
                                                'label' => 'One',
                                                ],

                                                [
                                                'value' => '1',
                                                'label' => 'Two',
                                                'attributes' => ['required' => true,],
                                                ],
                                                [
                                                'value' => '2',
                                                'label' => 'Three',
                                                'attributes' => ['required' => true,],
                                                ],
                             ],
                ],
    ]);

Then:

//Custom element:
namespace Form\Form\Element;

use Zend\InputFilter\InputProviderInterface;
use Zend\ModuleManager\Feature\ViewHelperProviderInterface;
use Zend\Form\Element\MultiCheckbox;
use Zend\Validator\NotEmpty;

use Form\Form\Validator\BootstrapMulticheckboxValidator;

class BootstrapMultiCheckbox extends MultiCheckbox implements  ViewHelperProviderInterface, InputProviderInterface{

  protected $attributes = [
                          'type' => 'bootstrapMultiCheckbox',
                          ];

  protected $requiredValues=false;

  public function getViewHelperConfig(){return array('Form\Form\View\Helper\BootstrapMultiCheckboxHelper', null);}

  public function getInputSpecification(){


    $required=$this->setIsRequired();

    $spec = [
            'name' => $this->getName(),
            'required' => $required,
            ];

    if ($validator = $this->getValidator()) {

      $spec['validators'] = $this->requiredValues ? [$validator, new BootstrapMulticheckboxValidator($this->requiredValues)] : [$validator];
    }

    return $spec;
  }

  public function setIsRequired(){

    $value=$this->getValue();

    if(!$value && $this->getAttribute('required')){return true;}

    foreach ($this->getValueOptions() as $key => $option){
      if(isset($option['attributes']['required'])){$this->requiredValues[$key]=$option['value'];}
    }

    if(!$value && $this->requiredValues){return true;}

    return false;
  }

} 

And finally:

//custom validator
namespace Form\Form\Validator;

use Traversable;
use Zend\Stdlib\ArrayUtils;
use Zend\Validator\AbstractValidator;
use Zend\Validator\NotEmpty;

class BootstrapMulticheckboxValidator extends AbstractValidator
{

    const IS_EMPTY = 'isEmpty';

    protected $requiredValues;

    protected $messageTemplates = [
        self::IS_EMPTY => 'Value is required and can\'t be empty',

    ];

    public function __construct($requiredValues)
    {
      $this->requiredValues=$requiredValues;

      parent::__construct(null);
    }

    public function isValid($values)
    {

       foreach($this->requiredValues as $required){
         if(!in_array($required, $values)){
           $this->error(self::IS_EMPTY);
           return false;
         }
       }
       return true;
    }
} 

Note: depending on how you render your custom elements you might want to disable browser's validation by adding the novalidate attribute to your form:

class BootstrapForm extends Form  implements InputFilterProviderInterface{

  private $inputFilter;

  public function __construct($name = null){

    parent::__construct($name);

    $this->setAttribute('novalidate',true);

    $this->add([
                'name' => 'checkbox',
    [...]