Browse Source

Handle nested options in multi-checkbox widget

This makes converting from a multiple select box into
a multiple-checkbox widget simple as it works out of the box. This also
makes the behavior match the documentation. While this feature was
documented, it did not have any tests in 2.x and thus wasn't preserved
in the 3.x rewrite.

Refs #8385
Mark Story 10 years ago
parent
commit
709b54e9c9

+ 20 - 3
src/View/Widget/MultiCheckboxWidget.php

@@ -50,6 +50,8 @@ class MultiCheckboxWidget implements WidgetInterface
      * - `checkboxWrapper` Renders the containing div/element for
      *   a checkbox and its label. Accepts the `input`, and `label`
      *   variables.
+     * - `fieldset` Renders the fieldset for grouped inputs.
+     * - `legend` Renders the legend element for grouped inputs.
      *
      * @param \Cake\View\StringTemplate $templates Templates list.
      * @param \Cake\View\Widget\LabelWidget $label Label widget instance.
@@ -113,10 +115,26 @@ class MultiCheckboxWidget implements WidgetInterface
             'idPrefix' => null,
             'templateVars' => []
         ];
-        $out = [];
         $this->_idPrefix = $data['idPrefix'];
         $this->_clearIds();
+        return implode('', $this->_renderInputs($data, $context));
+    }
+
+    protected function _renderInputs($data, $context)
+    {
+        $out = [];
         foreach ($data['options'] as $key => $val) {
+            // Grouped inputs in a fieldset.
+            if (is_string($key) && is_array($val) && !isset($val['text'], $val['value'])) {
+                $inputs = $this->_renderInputs(['options' => $val] + $data, $context);
+                $legend = $this->_templates->format('legend', ['text' => $key]);
+                $out[] = $this->_templates->format('fieldset', [
+                    'content' => $legend . implode('', $inputs)
+                ]);
+                continue;
+            }
+
+            // Standard inputs.
             $checkbox = [
                 'value' => $key,
                 'text' => $val,
@@ -142,10 +160,9 @@ class MultiCheckboxWidget implements WidgetInterface
             if (empty($checkbox['id'])) {
                 $checkbox['id'] = $this->_id($checkbox['name'], $checkbox['value']);
             }
-
             $out[] = $this->_renderInput($checkbox, $context);
         }
-        return implode('', $out);
+        return $out;
     }
 
     /**

+ 120 - 0
tests/TestCase/View/Widget/MultiCheckboxWidgetTest.php

@@ -37,6 +37,8 @@ class MultiCheckboxWidgetTest extends TestCase
             'checkbox' => '<input type="checkbox" name="{{name}}" value="{{value}}"{{attrs}}>',
             'label' => '<label{{attrs}}>{{text}}</label>',
             'checkboxWrapper' => '<div class="checkbox">{{input}}{{label}}</div>',
+            'fieldset' => '<fieldset{{attrs}}>{{content}}</fieldset>',
+            'legend' => '<legend>{{text}}</legend>',
         ];
         $this->templates = new StringTemplate($templates);
         $this->context = $this->getMock('Cake\View\Form\ContextInterface');
@@ -366,4 +368,122 @@ class MultiCheckboxWidgetTest extends TestCase
         ];
         $this->assertHtml($expected, $result);
     }
+
+    /**
+     * Test render with groupings.
+     *
+     * @return void
+     */
+    public function testRenderGrouped()
+    {
+        $label = new LabelWidget($this->templates);
+        $input = new MultiCheckboxWidget($this->templates, $label);
+        $data = [
+            'name' => 'Tags[id]',
+            'options' => [
+                'Group 1' => [
+                    1 => 'CakePHP',
+                ],
+                'Group 2' => [
+                    2 => 'Development',
+                ]
+            ]
+        ];
+        $result = $input->render($data, $this->context);
+        $expected = [
+            '<fieldset',
+            '<legend', 'Group 1', '/legend',
+            ['div' => ['class' => 'checkbox']],
+            ['input' => [
+                'type' => 'checkbox',
+                'name' => 'Tags[id][]',
+                'value' => 1,
+                'id' => 'tags-id-1',
+            ]],
+            ['label' => ['for' => 'tags-id-1']],
+            'CakePHP',
+            '/label',
+            '/div',
+            '/fieldset',
+
+            '<fieldset',
+            '<legend', 'Group 2', '/legend',
+            ['div' => ['class' => 'checkbox']],
+            ['input' => [
+                'type' => 'checkbox',
+                'name' => 'Tags[id][]',
+                'value' => 2,
+                'id' => 'tags-id-2',
+            ]],
+            ['label' => ['for' => 'tags-id-2']],
+            'Development',
+            '/label',
+            '/div',
+            '/fieldset',
+        ];
+        $this->assertHtml($expected, $result);
+    }
+
+    /**
+     * Test render with partial groupings.
+     *
+     * @return void
+     */
+    public function testRenderPartialGrouped()
+    {
+        $label = new LabelWidget($this->templates);
+        $input = new MultiCheckboxWidget($this->templates, $label);
+        $data = [
+            'name' => 'Tags[id]',
+            'options' => [
+                1 => 'PHP',
+                'Group 1' => [
+                    2 => 'CakePHP',
+                ],
+                3 => 'Development',
+            ]
+        ];
+        $result = $input->render($data, $this->context);
+        $expected = [
+            ['div' => ['class' => 'checkbox']],
+            ['input' => [
+                'type' => 'checkbox',
+                'name' => 'Tags[id][]',
+                'value' => 1,
+                'id' => 'tags-id-1',
+            ]],
+            ['label' => ['for' => 'tags-id-1']],
+            'PHP',
+            '/label',
+            '/div',
+
+            '<fieldset',
+            '<legend', 'Group 1', '/legend',
+            ['div' => ['class' => 'checkbox']],
+            ['input' => [
+                'type' => 'checkbox',
+                'name' => 'Tags[id][]',
+                'value' => 2,
+                'id' => 'tags-id-2',
+            ]],
+            ['label' => ['for' => 'tags-id-2']],
+            'CakePHP',
+            '/label',
+            '/div',
+            '/fieldset',
+
+            ['div' => ['class' => 'checkbox']],
+            ['input' => [
+                'type' => 'checkbox',
+                'name' => 'Tags[id][]',
+                'value' => 3,
+                'id' => 'tags-id-3',
+            ]],
+            ['label' => ['for' => 'tags-id-3']],
+            'Development',
+            '/label',
+            '/div',
+        ];
+        $this->assertHtml($expected, $result);
+    }
 }