Zend Framework 2 form element error-class + custom ViewHelper to render form

6.3k views Asked by At

This is my third question this week (and overall) - hope I don't get banned here :D Anyway, searched around and couldn't find an exact explanation to solve my issue(s).

A. I've searched around and found a custom ViewHelper to render my forms. What it does is recursively get all fieldsets and when it gets to the element level, it goes like this:

    public function renderElement($element) {
        $html = '
            <div class="row">' . 
            '<label class="col-md-12" for="' . $element->getAttribute('id') . '">' . $element->getLabel() . '</label>' . 
            $this->view->formElement($element) . 
            $this->view->FormElementErrors($element) .
            '<div class="clearfix" style="height: 15px;"></div>';

        return $html . PHP_EOL;
    }

Form renders ok, except: 1) How can I add an error class to the form element? (like if I use formRow helper in my view, it automatically ads an 'input-error' class, while also keeping the initial class specified in my fieldset when creating the element - 'attributes' => array('class' => 'some-class')), so the element's class attribute becomes "some-class input-error" in case it's invalid. 2) How can I set a class for the 'ul' containing the error messages (the 'ul' rendered by $this->view->FormElementErrors($element))? Hope this is a one-liner and I don't have to go message-by-message and compose the html for the error messages list, but if not so be it (I don't know how to do that either).

B. Let's say that sometimes I don't use this custom ViewHelper to render my form. Zend's formRow view helper can be handy sometimes. This brings me to the following code in my view:

    echo $this->formRow($this->form->get('user_fieldset')->get('user_name'));

I've noticed this automatically adds 'input-error' class on my element (in case it's invalid) which is perfect, BUT how can I also tell formRow to give a class to the 'ul' that's displaying the error messages?

I'd go even further and ask how I can turn this:

    echo $this->formLabel($this->form->get('user_fieldset')->get('user_name'));
    echo $this->formInput($this->form->get('user_fieldset')->get('user_name'));
    echo $this->formElementErrors($this->form->get('user_fieldset')->get('user_name'), array('class' => 'form-validation-error'));

into something that ads an error-class to the element as well, not just to the error messages list, but if anyone answers to point A I think it's the same issue.

2

There are 2 answers

0
MrCroft On BEST ANSWER

I've managed to do it like this:

public function renderElement($element) {
    // FORM ROW
    $html = '<div class="form-group">';

    // LABEL
    $html .= '<label class="form-label" for="' . $element->getAttribute('id') . '">' . $element->getLabel() . '</label>';

    // ELEMENT
    /*
     - Check if element has error messages
     - If it does, add my error-class to the element's existing one(s),
       to style the element differently on error
    */
    if (count($element->getMessages()) > 0) {
        $classAttribute = ($element->hasAttribute('class') ? $element->getAttribute('class') . ' ' : '');
        $classAttribute .= 'input-error';
        /* 
         * Normally, here I would have added a space in my string (' input-error')
         * Logically, I would figure that if the element already has a class "cls"
         * and I would do $element->getAttribute('class') . 'another-class'
         * then the class attribute would become "clsanother-class"
         * BUT it seems that, even if I don't intentionally add a space in my string,
         * I still get "cls another-class" as the resulted concatenated string
         * I assume that when building the form, ZF2 automatically
         * adds spaces after attributes values? so you/it won't have to
         * consider that later, when you'd eventually need to add another
         * value to an attribute?
         */
        $element->setAttribute('class', $classAttribute);
    }
    $html .= $this->view->formElement($element);
    /*
     * Of course, you could decide/need to do things differently,
     * depending on the element's type

       switch ($element->getAttribute('type')) {
           case 'text':
           case 'email': {

               break;
           }
           default: {

           }
       }
     */
    // ERROR MESSAGES
    // Custom class (.form-validation-error) for the default html wrapper - <ul>
    $html .= $this->view->FormElementErrors($element, array('class' => 'form-validation-error'));

    $html .= '</div>'; # /.form-group
    $html .= '<div class="clearfix" style="height: 15px;"></div>';

    return $html . PHP_EOL;
}

I'm not to fond of this, but I suppose there is no shorter way. I thought ZF2 shoud have something like:

if ($element->hasErrors()) { $element->addClass('some-class'); }

right out of the box. That's the answer I would have expected, that it would simply be a method I missed/couldn't find. But it turns out that ZF2 doesn't have quite anything in the whole world that you might need right out of the box, you end up having to write the (more or less) occasional helpers.

Anyway, if someone ever needs it here's the entire RenderForm view helper:

namespace User\View\Helper;

use Zend\View\Helper\AbstractHelper;

class RenderForm extends AbstractHelper {

    public function __invoke($form) {
        $form->prepare();
        $html = $this->view->form()->openTag($form) . PHP_EOL;
        $html .= $this->renderFieldsets($form->getFieldsets());
        $html .= $this->renderElements($form->getElements());
        $html .= $this->view->form()->closeTag($form) . PHP_EOL;
        return $html;
    }

    public function renderFieldsets($fieldsets) {

        foreach ($fieldsets as $fieldset) {
            if (count($fieldset->getFieldsets()) > 0) {
                $html = $this->renderFieldsets($fieldset->getFieldsets());
            } else {
                $html = '<fieldset>';
                    // You can use fieldset's name for the legend (if that's not inappropriate)
                    $html .= '<legend>' . ucfirst($fieldset->getName()) . '</legend>';
                    // or it's label (if you had set one)
                    // $html .= '<legend>' . ucfirst($fieldset->getLabel()) . '</legend>';
                    $html .= $this->renderElements($fieldset->getElements());
                $html .= '</fieldset>';
                // I actually never use the <fieldset> html tag.
                // Feel free to use anything you like, if you do have to
                // make grouping certain elements stand out to the user
            }
        }

        return $html;
    }

    public function renderElements($elements) {
        $html = '';
        foreach ($elements as $element) {
            $html .= $this->renderElement($element);
        }
        return $html;
    }

    public function renderElement($element) {
        // FORM ROW
        $html = '<div class="form-group">';

        // LABEL
        $html .= '<label class="form-label" for="' . $element->getAttribute('id') . '">' . $element->getLabel() . '</label>'; # add translation here

        // ELEMENT
        /*
         - Check if element has error messages
         - If it does, add my error-class to the element's existing one(s),
           to style the element differently on error
        */
        if (count($element->getMessages()) > 0) {
            $classAttribute = ($element->hasAttribute('class') ? $element->getAttribute('class') . ' ' : '');
            $classAttribute .= 'input-error';

            $element->setAttribute('class', $classAttribute);
        }
        $html .= $this->view->formElement($element);

        // ERROR MESSAGES
        $html .= $this->view->FormElementErrors($element, array('class' => 'form-validation-error'));


        $html .= '</div>'; # /.row
        $html .= '<div class="clearfix" style="height: 15px;"></div>';

        return $html . PHP_EOL;
    }

}

User is my module. I've created a 'viewhelper.config.php' in it's config folder:

return array(
    'invokables' => array(
        'renderForm' => 'User\View\Helper\RenderForm',
    ),
);

and in Module.php:

public function getViewHelperConfig() {
    return include __DIR__ . '/config/viewhelper.config.php';
}

Then, in your view simply call:

$this->renderForm($form);

Of course, if you don't have many view helpers, you could not create a separate config file just for that, leave Module.php alone and simply add:

'view_helpers' => array(
    'invokables' => array(
        'renderForm' => 'User\View\Helper\RenderForm',
    ),
),

to any configuration file.

0
Laxman On

I use getMessages() method to check if an element has an validation message. for eg.

<div class="form-group <?=($this->form->get('user_fieldset')->get('user_name')->getMessages())?'has-error':'';?>">
    ...
</div>

This question seems to be very old and you must have solved it by yourself. :)