Browse Source

Prototype an idea for nested label inputs.

Add a new label template and modify the existing templates to make it
easy (and the default) to have radio/checkboxes embedded in their
labels. Because bootstrap is pretty common this layout gets requested
pretty often for checkbox/radio inputs.

This still needs tests written/updated but I wanted to get some early
feedback on the idea and approach.
Mark Story 11 years ago
parent
commit
b66577f4f5

+ 21 - 10
src/View/Helper/FormHelper.php

@@ -79,7 +79,7 @@ class FormHelper extends Helper {
 		'templates' => [
 			'button' => '<button{{attrs}}>{{text}}</button>',
 			'checkbox' => '<input type="checkbox" name="{{name}}" value="{{value}}"{{attrs}}>',
-			'checkboxFormGroup' => '{{input}}{{label}}',
+			'checkboxFormGroup' => '{{label}}',
 			'checkboxWrapper' => '<div class="checkbox">{{input}}{{label}}</div>',
 			'dateWidget' => '{{year}}{{month}}{{day}}{{hour}}{{minute}}{{second}}{{meridian}}',
 			'error' => '<div class="error-message">{{content}}</div>',
@@ -96,13 +96,14 @@ class FormHelper extends Helper {
 			'inputContainer' => '<div class="input {{type}}{{required}}">{{content}}</div>',
 			'inputContainerError' => '<div class="input {{type}}{{required}} error">{{content}}{{error}}</div>',
 			'label' => '<label{{attrs}}>{{text}}</label>',
+			'nestedLabel' => '<label{{attrs}}>{{input}}{{text}}</label>',
 			'legend' => '<legend>{{text}}</legend>',
 			'option' => '<option value="{{value}}"{{attrs}}>{{text}}</option>',
 			'optgroup' => '<optgroup label="{{label}}"{{attrs}}>{{content}}</optgroup>',
 			'select' => '<select name="{{name}}"{{attrs}}>{{content}}</select>',
 			'selectMultiple' => '<select name="{{name}}[]" multiple="multiple"{{attrs}}>{{content}}</select>',
 			'radio' => '<input type="radio" name="{{name}}" value="{{value}}"{{attrs}}>',
-			'radioWrapper' => '{{input}}{{label}}',
+			'radioWrapper' => '{{label}}',
 			'textarea' => '<textarea name="{{name}}"{{attrs}}>{{value}}</textarea>',
 			'submitContainer' => '<div class="submit">{{content}}</div>',
 		]
@@ -701,7 +702,7 @@ class FormHelper extends Helper {
  * {{{
  * echo $this->Form->label('published', 'Publish', array(
  *   'for' => 'published',
- *   'input' => $this->text('published')
+ *   'input' => $this->text('published'),
  * ));
  * <label for="post-publish">Publish <input type="text" name="published"></label>
  * }}}
@@ -742,6 +743,9 @@ class FormHelper extends Helper {
 			'for' => $labelFor,
 			'text' => $text,
 		];
+		if (isset($options['input'])) {
+			return $this->widget('nestedLabel', $attrs);
+		}
 		return $this->widget('label', $attrs);
 	}
 
@@ -879,6 +883,9 @@ class FormHelper extends Helper {
  * - `error` - Control the error message that is produced. Set to `false` to disable any kind of error reporting (field
  *    error and error messages).
  * - `empty` - String or boolean to enable empty select box options.
+ * - `nestedInput` - Used with checkbox and radio inputs. Set to false to render inputs outside of label
+ *   elements. Can be set to true on any input to force the input inside the label. If you
+ *   enable this option for radio buttons you will also need to modify the default `radioWrapper` template.
  *
  * @param string $fieldName This should be "Modelname.fieldname"
  * @param array $options Each type of input takes different options.
@@ -892,7 +899,7 @@ class FormHelper extends Helper {
 			'error' => null,
 			'required' => null,
 			'options' => null,
-			'templates' => []
+			'templates' => [],
 		];
 		$options = $this->_parseOptions($fieldName, $options);
 		$options += ['id' => $this->_domId($fieldName)];
@@ -916,9 +923,12 @@ class FormHelper extends Helper {
 		}
 
 		$label = $options['label'];
-		if ($options['type'] !== 'radio') {
+		$nestedInput = false;
+		if (in_array($options['type'], ['radio', 'checkbox'], true)) {
+			$nestedInput = true;
 			unset($options['label']);
 		}
+		$nestedInput = isset($options['nestedInput']) ? $options['nestedInput'] : $nestedInput;
 
 		$input = $this->_getInput($fieldName, $options);
 		if ($options['type'] === 'hidden') {
@@ -928,7 +938,7 @@ class FormHelper extends Helper {
 			return $input;
 		}
 
-		$label = $this->_getLabel($fieldName, compact('input', 'label', 'error') + $options);
+		$label = $this->_getLabel($fieldName, compact('input', 'label', 'error', 'nestedInput') + $options);
 		$result = $this->_groupTemplate(compact('input', 'label', 'error', 'options'));
 		$result = $this->_inputContainerTemplate([
 			'content' => $result,
@@ -1213,11 +1223,12 @@ class FormHelper extends Helper {
 		} else {
 			$labelText = $label;
 		}
+		$options += ['id' => null, 'input' => null, 'nestedInput' => false];
 
-		$labelAttributes = [
-			'for' => isset($options['id']) ? $options['id'] : null,
-			'input' => isset($options['input']) ? $options['input'] : null
-		] + $labelAttributes;
+		$labelAttributes['for'] = $options['id'];
+		if ($options['nestedInput']) {
+			$labelAttributes['input'] = $options['input'];
+		}
 		return $this->label($fieldName, $labelText, $labelAttributes);
 	}
 

+ 8 - 1
src/View/Widget/Label.php

@@ -33,6 +33,13 @@ class Label implements WidgetInterface {
 	protected $_templates;
 
 /**
+ * The template to use.
+ *
+ * @var string
+ */
+	protected $_labelTemplate = 'label';
+
+/**
  * Constructor.
  *
  * This class uses the following template:
@@ -68,7 +75,7 @@ class Label implements WidgetInterface {
 			'escape' => true,
 		];
 
-		return $this->_templates->format('label', [
+		return $this->_templates->format($this->_labelTemplate, [
 			'text' => $data['escape'] ? h($data['text']) : $data['text'],
 			'input' => $data['input'],
 			'attrs' => $this->_templates->formatAttributes($data, ['text', 'input']),

+ 34 - 0
src/View/Widget/NestedLabel.php

@@ -0,0 +1,34 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.0.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\View\Widget;
+
+use Cake\View\Widget\Label;
+
+/**
+ * Form 'widget' for creating labels that contain their input.
+ *
+ * Generally this element is used by other widgets,
+ * and FormHelper itself.
+ */
+class NestedLabel extends Label {
+
+/**
+ * The template to use.
+ *
+ * @var string
+ */
+	protected $_labelTemplate = 'nestedLabel';
+
+}

+ 3 - 2
src/View/Widget/WidgetRegistry.php

@@ -48,8 +48,9 @@ class WidgetRegistry {
 		'checkbox' => ['Cake\View\Widget\Checkbox'],
 		'file' => ['Cake\View\Widget\File'],
 		'label' => ['Cake\View\Widget\Label'],
-		'multicheckbox' => ['Cake\View\Widget\MultiCheckbox', 'label'],
-		'radio' => ['Cake\View\Widget\Radio', 'label'],
+		'nestedLabel' => ['Cake\View\Widget\NestedLabel'],
+		'multicheckbox' => ['Cake\View\Widget\MultiCheckbox', 'nestedLabel'],
+		'radio' => ['Cake\View\Widget\Radio', 'nestedLabel'],
 		'select' => ['Cake\View\Widget\SelectBox'],
 		'textarea' => ['Cake\View\Widget\Textarea'],
 		'datetime' => ['Cake\View\Widget\DateTime', 'select'],