phpcheckboxradio-buttonzend-framework2fieldset

Nesting Fieldsets under Radio or Checkbox items: Zend Framework 2


I would like to create a fieldset under each radio/checkbox item.

e.g

Which animals do you like:

[x] Cats

    [Fieldset of cat related questions]

[ ] Dogs

    [Fieldset of dog related questions]

...

I can create Fieldsets and Forms with ease, but nesting them under each item is causing me some headaches.

Ultimately I had in mind something like this:

 $this->add(array(
        'type' => 'Zend\Form\Element\Radio',
        'name' => 'profile_type',
        'options' => array(
            'label' => 'Which animals do you like:',
            'label_attributes' => array('class'=>'radio inline'),
            'value_options' => array(
                '1' =>'cats',

                'fieldsets' => array(
                 array(
                    'name' => 'sender',
                    'elements' => array(
                        array(
                            'name' => 'name',
                            'options' => array(
                                'label' => 'Your name',
                                ),
                            'type'  => 'Text'
                        ),
                        array(
                            'type' => 'Zend\Form\Element\Email',
                            'name' => 'email',
                            'options' => array(
                                'label' => 'Your email address',
                                ),
                        ),
                    ),
                )),
                '2'=>'dogs',

                'fieldsets' => array(
                    array(
                        'name' => 'sender',
                        'elements' => array(
                            array(
                                'name' => 'name',
                                'options' => array(
                                    'label' => 'Your name',
                                ),
                                'type'  => 'Text'
                            ),
                            array(
                                'type' => 'Zend\Form\Element\Email',
                                'name' => 'email',
                                'options' => array(
                                    'label' => 'Your email address',
                                ),
                            ),
                        ),
                    ))
            ),
        ),
        'attributes' => array(
            'value' => '1' //set checked to '1'
        )
    ));

Solution

  • Ok I managed to get round this issue using a bit of a hack, but it works.

    TestForm.php (note the additional attributes in the elements ('parent' and 'childOf')

    class TestForm extends Form
    {
    
    public $user_agent;
    public $user_ip;
    
    
    public function __construct($name = null)
    {
        // we want to ignore the name passed
        parent::__construct('profile');
        $this->setAttributes(array(
            'method'=>'post',
            'class'=>'form-horizontal',
        ));
    
        $this->add(array(
            'type' => 'Zend\Form\Element\Radio',
            'name' => 'profile_type',
            'options' => array(
                'label' => 'Which animals do you like:',
                'label_attributes' => array('class'=>'radio inline'),
                'value_options' => array(
                    array('label' =>'cats', 'value'=>1, 'parent'=>'cat'),
                    array('label'=>'dogs', 'value'=>2, 'parent'=>'dog'),
                ),
            ),
            'attributes' => array(
                'value' => '1' //set checked to '1'
            )
        ));
    
        $this->add(array(
            'type'  => 'Text',
            'name' => 'name',
            'attributes' => array(
                'childOf' => 'cat',
            ),
            'options' => array(
                'label' => 'Your name',
                ),
        ));
    
        $this->add(array(
            'type' => 'Zend\Form\Element\Email',
            'name' => 'email',
            'attributes' => array(
                'childOf' => 'dog',
            ),
            'options' => array(
                'label' => 'Your email address',
            ),
        ));
    }
    }
    

    Then extended Zend\Form\View\Helper\FormMultiCheckbox and overwrote the RenderOptions Method (look out for the new optionSpec 'parent') this basically creates a tag e.g.{#cat#} after the parent element:

    protected function renderOptions(MultiCheckboxElement $element, array $options, array $selectedOptions,
                                     array $attributes)
    {
        $escapeHtmlHelper = $this->getEscapeHtmlHelper();
        $labelHelper      = $this->getLabelHelper();
        $labelClose       = $labelHelper->closeTag();
        $labelPosition    = $this->getLabelPosition();
        $globalLabelAttributes = $element->getLabelAttributes();
        $closingBracket   = $this->getInlineClosingBracket();
    
        if (empty($globalLabelAttributes)) {
            $globalLabelAttributes = $this->labelAttributes;
        }
    
        $combinedMarkup = array();
        $count          = 0;
    
        foreach ($options as $key => $optionSpec) {
            $count++;
            if ($count > 1 && array_key_exists('id', $attributes)) {
                unset($attributes['id']);
            }
    
            $value           = '';
            $parent          = '';
            $label           = '';
            $inputAttributes = $attributes;
            $labelAttributes = $globalLabelAttributes;
            $selected        = isset($inputAttributes['selected']) && $inputAttributes['type'] != 'radio' && $inputAttributes['selected'] != false ? true : false;
            $disabled        = isset($inputAttributes['disabled']) && $inputAttributes['disabled'] != false ? true : false;
    
            if (is_scalar($optionSpec)) {
                $optionSpec = array(
                    'label' => $optionSpec,
                    'value' => $key
                );
            }
    
            if (isset($optionSpec['value'])) {
                $value = $optionSpec['value'];
            }
            if (isset($optionSpec['parent'])) {
                $parent = $optionSpec['parent'];
            }
            if (isset($optionSpec['label'])) {
                $label = $optionSpec['label'];
            }
            if (isset($optionSpec['selected'])) {
                $selected = $optionSpec['selected'];
            }
            if (isset($optionSpec['disabled'])) {
                $disabled = $optionSpec['disabled'];
            }
            if (isset($optionSpec['label_attributes'])) {
                $labelAttributes = (isset($labelAttributes))
                    ? array_merge($labelAttributes, $optionSpec['label_attributes'])
                    : $optionSpec['label_attributes'];
            }
            if (isset($optionSpec['attributes'])) {
                $inputAttributes = array_merge($inputAttributes, $optionSpec['attributes']);
            }
    
            if (in_array($value, $selectedOptions)) {
                $selected = true;
            }
    
            $inputAttributes['value']    = $value;
            $inputAttributes['checked']  = $selected;
            $inputAttributes['disabled'] = $disabled;
    
            $input = sprintf(
                '<input %s%s',
                $this->createAttributesString($inputAttributes),
                $closingBracket
            );
    
            if (null !== ($translator = $this->getTranslator())) {
                $label = $translator->translate(
                    $label, $this->getTranslatorTextDomain()
                );
            }
    
            $tag = ($parent != '')? "{#*".$parent."*#}": "";
    
            $label     = $escapeHtmlHelper($label);
            $labelOpen = $labelHelper->openTag($labelAttributes);
            $template  = $labelOpen . '%s%s%s' . $labelClose;
            switch ($labelPosition) {
                case self::LABEL_PREPEND:
                    $markup = sprintf($template, $label, $input, $tag);
                    break;
                case self::LABEL_APPEND:
                default:
                    $markup = sprintf($template, $input, $label, $tag);
                    break;
            }
            $combinedMarkup[] = $markup;
        }
    
        return implode($this->getSeparator(), $combinedMarkup);
    }
    

    Extended and overwrote Zend\Form\View\Helper\FormRow render method this is the same except for the return of the method:

        ..... more code .....
        $child_of = $element->getAttribute('childOf');
        if($child_of != '')
        {
            return array($child_of => sprintf('<div class="control-group%s">%s</div>', $status_type, $markup));
        }
        return sprintf('<div class="control-group%s">%s</div>', $status_type, $markup);
    

    And finally extended and overwrote the render method of Zend\Form\View\Helper\FormCollection and changed the element foreach loop, basically overwriting the tags if the element is an array, and therefore has a ChildOf tag. Then a clean up of tags:

    foreach ($element->getIterator() as $elementOrFieldset) {
            if ($elementOrFieldset instanceof FieldsetInterface) {
                $markup .= $fieldsetHelper($elementOrFieldset);
            } elseif ($elementOrFieldset instanceof ElementInterface) {
                $elementString =  $elementHelper($elementOrFieldset);
                if(!is_array($elementString))
                {
                    $markup .= $elementString;
                }
                // is child of another element
                else
                {
    
                    foreach($elementString as $key => $value)
                    {
                        $match = "{#*".$key."*#}";
                        $replacement = $value.$match;
                        $markup = str_replace($match, $replacement, $markup);
                    }
                }
            }
        }
        $pattern = '/[{#\*]+[a-z0-0A-Z]*[\*#}]+/';
        $markup = preg_replace($pattern, '', $markup);
    

    This (although ugly) produces the desired results, also because we are just playing with the rendering, validation and form creation are untouched.

    All the best, Aborgrove