Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Hacky start to building a selectbox input widget.
I think splitting FormHelper into smaller parts will make the code more manageable and extensible. I know for sure that a 3000 line class is neither of the above, so smaller classes can't be worse. This change also puts a stub in place for the Context class which will eventually contain all the relevant context for building forms from the ORM and other sources like the request. Hopefully this class can insulate the rest of the input libraries from the ORM and request objects.
- Loading branch information
Showing
3 changed files
with
339 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<?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 CakePHP(tm) v3.0 | ||
* @license http://www.opensource.org/licenses/mit-license.php MIT License | ||
*/ | ||
namespace Cake\View\Input; | ||
|
||
/** | ||
* Form input generation context. | ||
* | ||
* Coaleseces request data, form entities. Also provides methods | ||
* for checking if fields are required, and schema introspection. | ||
* | ||
* The context class insulates the FormHelper and Input classes | ||
* from various ORM implementations making them ORM independent. | ||
*/ | ||
class Context { | ||
|
||
protected $_requestData; | ||
protected $_entities; | ||
protected $_schema; | ||
|
||
public function __construct($requestData = [], $entities = [], $schema = []) { | ||
$this->_requestData = $requestData; | ||
$this->_entities = $entities; | ||
$this->_schema = $schema; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,154 @@ | ||
<?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 CakePHP(tm) v3.0 | ||
* @license http://www.opensource.org/licenses/mit-license.php MIT License | ||
*/ | ||
namespace Cake\View\Input; | ||
|
||
use Cake\View\Input\Context; | ||
use Cake\View\StringTemplate; | ||
|
||
/** | ||
* Input widget class for generating a selectbox. | ||
*/ | ||
class SelectBox { | ||
|
||
/** | ||
* Minimized attributes | ||
* | ||
* @var array | ||
*/ | ||
protected $_minimizedAttributes = array( | ||
'compact', 'checked', 'declare', 'readonly', 'disabled', 'selected', | ||
'defer', 'ismap', 'nohref', 'noshade', 'nowrap', 'multiple', 'noresize', | ||
'autoplay', 'controls', 'loop', 'muted', 'required', 'novalidate', 'formnovalidate' | ||
); | ||
|
||
/** | ||
* Format to attribute | ||
* | ||
* @var string | ||
*/ | ||
protected $_attributeFormat = '%s="%s"'; | ||
|
||
/** | ||
* Format to attribute | ||
* | ||
* @var string | ||
*/ | ||
protected $_minimizedAttributeFormat = '%s="%s"'; | ||
|
||
protected $_templates; | ||
protected $_context; | ||
|
||
public function __construct($templates, $context) { | ||
$this->_templates = $templates; | ||
$this->_context = $context; | ||
} | ||
|
||
public function render($data) { | ||
if (empty($data['name'])) { | ||
throw new \RuntimeException('Cannot make inputs with empty name attributes.'); | ||
} | ||
$options = $this->_renderOptions($data); | ||
$name = $data['name']; | ||
unset($data['name'], $data['options'], $data['empty'], $data['value']); | ||
$attrs = $this->_parseAttributes($data); | ||
return $this->_templates->format('select', [ | ||
'name' => $name, | ||
'attrs' => $attrs, | ||
'content' => implode('', $options), | ||
]); | ||
} | ||
|
||
protected function _renderOptions($data) { | ||
$out = []; | ||
if (!empty($data['empty'])) { | ||
// TODO | ||
} | ||
if (empty($data['options'])) { | ||
return $out; | ||
} | ||
|
||
$selected = isset($data['value']) ? $data['value'] : null; | ||
$selectedArray = is_array($selected); | ||
|
||
foreach ($data['options'] as $key => $val) { | ||
$template = 'option'; | ||
$strict = !is_numeric($key); | ||
|
||
if ( | ||
isset($selected) && | ||
(!$selectedArray && (string)$key === (string)$selected) || | ||
($selectedArray && in_array((string)$key, $selected, $strict)) | ||
) { | ||
$template = 'optionSelected'; | ||
} | ||
|
||
$out[] = $this->_templates->format($template, [ | ||
'name' => $key, | ||
'value' => $val | ||
]); | ||
} | ||
return $out; | ||
} | ||
|
||
protected function _parseAttributes($options, $exclude = null) { | ||
$insertBefore = ' '; | ||
$options = (array)$options + array('escape' => true); | ||
|
||
if (!is_array($exclude)) { | ||
$exclude = array(); | ||
} | ||
|
||
$exclude = array('escape' => true) + array_flip($exclude); | ||
$escape = $options['escape']; | ||
$attributes = array(); | ||
|
||
foreach ($options as $key => $value) { | ||
if (!isset($exclude[$key]) && $value !== false && $value !== null) { | ||
$attributes[] = $this->_formatAttribute($key, $value, $escape); | ||
} | ||
} | ||
$out = implode(' ', $attributes); | ||
return $out ? $insertBefore . $out : ''; | ||
} | ||
|
||
/** | ||
* Formats an individual attribute, and returns the string value of the composed attribute. | ||
* Works with minimized attributes that have the same value as their name such as 'disabled' and 'checked' | ||
* | ||
* @param string $key The name of the attribute to create | ||
* @param string $value The value of the attribute to create. | ||
* @param boolean $escape Define if the value must be escaped | ||
* @return string The composed attribute. | ||
* @deprecated This method will be moved to HtmlHelper in 3.0 | ||
*/ | ||
protected function _formatAttribute($key, $value, $escape = true) { | ||
if (is_array($value)) { | ||
$value = implode(' ', $value); | ||
} | ||
if (is_numeric($key)) { | ||
return sprintf($this->_minimizedAttributeFormat, $value, $value); | ||
} | ||
$truthy = array(1, '1', true, 'true', $key); | ||
$isMinimized = in_array($key, $this->_minimizedAttributes); | ||
if ($isMinimized && in_array($value, $truthy, true)) { | ||
return sprintf($this->_minimizedAttributeFormat, $key, $key); | ||
} | ||
if ($isMinimized) { | ||
return ''; | ||
} | ||
return sprintf($this->_attributeFormat, $key, ($escape ? h($value) : $value)); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
<?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 CakePHP(tm) v3.0 | ||
* @license http://www.opensource.org/licenses/mit-license.php MIT License | ||
*/ | ||
namespace Cake\Test\TestCase\View\Input; | ||
|
||
use Cake\TestSuite\TestCase; | ||
use Cake\View\Input\Context; | ||
use Cake\View\Input\SelectBox; | ||
use Cake\View\StringTemplate; | ||
|
||
/** | ||
* SelectBox test case | ||
*/ | ||
class SelectBoxTest extends TestCase { | ||
|
||
public function setUp() { | ||
parent::setUp(); | ||
$templates = [ | ||
'select' => '<select name="{{name}}" {{attrs}}>{{content}}</select>', | ||
'selectMultiple' => '<select name="{{name}}" multiple="multiple" {{attrs}}>{{content}}</select>', | ||
'option' => '<option value="{{name}}">{{value}}</option>', | ||
'optionSelected' => '<option value="{{name}}" selected="selected">{{value}}</option>', | ||
'optgroup' => '<optgroup label="{{label}}">{{content}}</optgroup>', | ||
]; | ||
$this->templates = new StringTemplate(); | ||
$this->templates->add($templates); | ||
} | ||
|
||
/** | ||
* test render no options | ||
* | ||
* @return void | ||
*/ | ||
public function testRenderNoOptions() { | ||
$context = new Context(); | ||
$select = new SelectBox($this->templates, $context); | ||
$data = [ | ||
'id' => 'BirdName', | ||
'name' => 'Birds[name]', | ||
'options' => [] | ||
]; | ||
$result = $select->render($data); | ||
$expected = [ | ||
'select' => ['name' => 'Birds[name]', 'id' => 'BirdName'], | ||
'/select' | ||
]; | ||
$this->assertTags($result, $expected); | ||
} | ||
|
||
/** | ||
* test simple rendering | ||
* | ||
* @return void | ||
*/ | ||
public function testRenderSimple() { | ||
$context = new Context(); | ||
$select = new SelectBox($this->templates, $context); | ||
$data = [ | ||
'id' => 'BirdName', | ||
'name' => 'Birds[name]', | ||
'options' => ['a' => 'Albatross', 'b' => 'Budgie'] | ||
]; | ||
$result = $select->render($data); | ||
$expected = [ | ||
'select' => ['name' => 'Birds[name]', 'id' => 'BirdName'], | ||
['option' => ['value' => 'a']], 'Albatross', '/option', | ||
['option' => ['value' => 'b']], 'Budgie', '/option', | ||
'/select' | ||
]; | ||
$this->assertTags($result, $expected); | ||
} | ||
|
||
/** | ||
* test rendering with a selected value | ||
* | ||
* @return void | ||
*/ | ||
public function testRenderSelected() { | ||
$this->markTestIncomplete('Not done'); | ||
} | ||
|
||
/** | ||
* test rendering a multi select | ||
* | ||
* @return void | ||
*/ | ||
public function testRenderMultipleSelect() { | ||
$this->markTestIncomplete('Not done'); | ||
} | ||
|
||
/** | ||
* test rendering multi select & selected values | ||
* | ||
* @return void | ||
*/ | ||
public function testRenderMultipleSelected() { | ||
$this->markTestIncomplete('Not done'); | ||
} | ||
|
||
/** | ||
* test rendering with option groups | ||
* | ||
* @return void | ||
*/ | ||
public function testRenderOptionGroups() { | ||
$this->markTestIncomplete('Not done'); | ||
} | ||
|
||
/** | ||
* test rendering option groups and selected values | ||
* | ||
* @return void | ||
*/ | ||
public function testRenderOptionGroupsSelected() { | ||
$this->markTestIncomplete('Not done'); | ||
} | ||
|
||
/** | ||
* test rendering a disabled element | ||
* | ||
* @return void | ||
*/ | ||
public function testRenderDisabled() { | ||
$this->markTestIncomplete('Not done'); | ||
} | ||
|
||
/** | ||
* test rendering with an empty value | ||
* | ||
* @return void | ||
*/ | ||
public function testRenderEmptyOption() { | ||
$this->markTestIncomplete('Not done'); | ||
} | ||
|
||
} |