PageRenderTime 43ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/lithium-0.6/libraries/lithium/template/helper/Form.php

https://github.com/gwoo/framework-benchs
PHP | 353 lines | 190 code | 34 blank | 129 comment | 17 complexity | df0233f7b7a08bb1009391088f14c727 MD5 | raw file
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\template\helper;
  9. use \lithium\util\Set;
  10. use \lithium\util\Inflector;
  11. /**
  12. * A helper class to facilitate generating, processing and securing HTML forms. By default, `Form`
  13. * will simply generate HTML forms and widgets, but by creating a form with a _binding object_,
  14. * the helper can pre-fill form input values, render error messages, and introspect column types.
  15. *
  16. * For example:
  17. * {{{// In controller code:
  18. * $post = Post::find(1);
  19. * return compact('post');
  20. *
  21. * // In view code:
  22. * <?=$this->form->create($post); // Echoes a <form> tag and binds the helper to $post ?>
  23. * <?=$this->form->text('title'); // Echoes an <input /> element, pre-filled with $post's title ?>
  24. * <?=$this->form->submit('Update'); // Echoes a submit button with the title 'Update' ?>
  25. * <?=$this->form->end(); // Echoes a </form> tag & unbinds the form ?>
  26. * }}}
  27. */
  28. class Form extends \lithium\template\Helper {
  29. /**
  30. * String templates used by this helper.
  31. *
  32. * @var array
  33. */
  34. protected $_strings = array(
  35. 'button' => '<input type="{:type}"{:options} />',
  36. 'checkbox' => '<input type="checkbox" name="{:name}"{:options} />',
  37. 'checkbox-multi' => '<input type="checkbox" name="{:name}[]"{:options} />',
  38. 'checkbox-multi-end' => '',
  39. 'checkbox-multi-start' => '',
  40. 'error' => '<div{:options}>{:content}</div>',
  41. 'errors' => '{:content}',
  42. 'file' => '<input type="file" name="{:name}"{:options} />',
  43. 'form' => '<form action="{:url}"{:options}>',
  44. 'form-end' => '</form>',
  45. 'hidden' => '<input type="hidden" name="{:name}"{:options} />',
  46. 'input' => '<div{:wrap}>{:label}{:input}{:error}</div>',
  47. 'input-checkbox' => '<div{:wrap}>{:input}{:label}{:error}</div>',
  48. 'label' => '<label for="{:name}"{:options}>{:title}</label>',
  49. 'legend' => '<legend>{:content}</legend>',
  50. 'option-group' => '<optgroup label="{:label}"{:options}>',
  51. 'option-group-end' => '</optgroup>',
  52. 'password' => '<input type="password" name="{:name}"{:options} />',
  53. 'radio' => '<input type="radio" name="{:name}" id="{:id}"{:options} />{:label}',
  54. 'select-start' => '<select name="{:name}"{:options}>',
  55. 'select-multi-start' => '<select name="{:name}[]"{:options}>',
  56. 'select-empty' => '<option value=""{:options}>&nbsp;</option>',
  57. 'select-option' => '<option value="{:value}"{:options}>{:title}</option>',
  58. 'select-end' => '</select>',
  59. 'submit' => '<input type="submit" value="{:title}"{:options} />',
  60. 'submit-image' => '<input type="image" src="{:url}"{:options} />',
  61. 'text' => '<input type="text" name="{:name}"{:options} />',
  62. 'textarea' => '<textarea name="{:name}"{:options}>{:value}</textarea>',
  63. 'fieldset' => '<fieldset{:options}>{:content}</fieldset>',
  64. 'fieldset-start' => '<fieldset><legend>{:content}</legend>',
  65. 'fieldset-end' => '</fieldset>'
  66. );
  67. /**
  68. * Maps method names to template string names, allowing the default template strings to be set
  69. * permanently on a per-method basis.
  70. *
  71. * For example, if all text input fields should be wrapped in `<span />` tags, you can configure
  72. * the template string mappings per the following:
  73. *
  74. * {{{
  75. * $this->form->config(array('templates' => array(
  76. * 'text' => '<span><input type="text" name="{:name}"{:options} /></span>'
  77. * )));
  78. * }}}
  79. *
  80. * Alternatively, you can re-map one type as another. This is useful if, for example, you
  81. * include your own helper with custom form template strings which do not match the default
  82. * template string names.
  83. *
  84. * {{{
  85. * // Renders all password fields as text fields
  86. * $this->form->config(array('templates' => array('password' => 'text')));
  87. * }}}
  88. *
  89. * @var array
  90. * @see lithium\template\helper\Form::config()
  91. */
  92. protected $_templateMap = array(
  93. 'create' => 'form',
  94. 'end' => 'form-end'
  95. );
  96. /**
  97. * The data object or list of data objects to which the current form is bound. In order to
  98. * be a custom data object, a class must implement the following methods:
  99. *
  100. * - schema(): Returns an array defining the objects fields and their data types.
  101. * - data(): Returns an associative array of the data that this object represents.
  102. * - errors(): Returns an associatie array of validation errors for the current data set, where
  103. * the keys match keys from `schema()`, and the values are either strings (in cases
  104. * where a field only has one error) or an array (in case of multiple errors),
  105. *
  106. * For an example of how to implement these methods, see the `lithium\data\model\Record` object.
  107. *
  108. * @var mixed A single data object, a `Collection` of multiple data ovjects, or an array of data
  109. * objects/`Collection`s.
  110. * @see lithium\data\model\Record
  111. * @see lithium\template\helper\Form::create()
  112. */
  113. protected $_binding = null;
  114. public function __construct($config = array()) {
  115. $defaults = array(
  116. 'base' => array(), 'text' => array(), 'textarea' => array(),
  117. 'select' => array('multiple' => false)
  118. );
  119. parent::__construct((array) $config + $defaults);
  120. }
  121. /**
  122. * Allows you to configure a default set of options which are included on a per-method basis,
  123. * and configure method template overrides.
  124. *
  125. * To force all `<label />` elements to have a default `class` attribute value of `"foo"`,
  126. * simply do the following:
  127. *
  128. * {{{
  129. * $this->form->config(array('label' => array('class' => 'foo')));
  130. * }}}
  131. *
  132. * @param array $config An associative array where the keys are `Form` method names, and the
  133. * values are arrays of configuration options to be included in the `$options`
  134. * parameter of each method specified.
  135. * @return array Returns an array containing the currently set per-method configurations, and
  136. * an array of the currently set template overrides (in the `'templates'` array key).
  137. * @see lithium\template\helper\Form::$_templateMap
  138. */
  139. public function config($config = array()) {
  140. if (empty($config)) {
  141. return array('templates' => $this->_templateMap) + array_intersect_key(
  142. $this->_config, array('base' => '', 'text' => '', 'textarea' => '')
  143. );
  144. }
  145. if (isset($config['templates'])) {
  146. $this->_templateMap = $config['templates'] + $this->_templateMap;
  147. unset($config['templates']);
  148. }
  149. return ($this->_config = Set::merge($this->_config, $config)) + array(
  150. 'templates' => $this->_templateMap
  151. );
  152. }
  153. /**
  154. * Creates an HTML form, and optionally binds it to a data object which contains information on
  155. * how to render form fields, any data to pre-populate the form with, and any validation errors.
  156. * Typically, a data object will be a `Record` object returned from a `Model`, but you can
  157. * define your own custom objects as well. For more information on custom data objects, see
  158. * `lithium\template\helper\Form::$_binding`.
  159. *
  160. * @param object $binding
  161. * @param array $options
  162. * @return string Returns a `<form />` open tag with the `action` attribute defined by either
  163. * the `'action'` or `'url'` options (defaulting to the current page if none is
  164. * specified), the HTTP method is defined by the `'type'` option, and any HTML
  165. * attributes passed in `$options`.
  166. */
  167. public function create($binding = null, $options = array()) {
  168. $defaults = array(
  169. 'url' => null,
  170. 'type' => null,
  171. 'action' => $this->_context->request()->params['action'],
  172. 'method' => $binding ? ($binding->exists() ? 'put' : 'post') : 'post'
  173. );
  174. list(, $options, $template) = $this->_defaults(__FUNCTION__, null, $options);
  175. $options = (array) $options + $defaults;
  176. $_binding =& $this->_binding;
  177. $method = __METHOD__;
  178. $filter = function($self, $params, $chain) use ($template, $method, $defaults, &$_binding) {
  179. extract($params);
  180. $_binding = $binding;
  181. $append = '';
  182. if ($options['type'] == 'file') {
  183. if (strtolower($options['method']) == 'get') {
  184. $options['method'] = 'post';
  185. }
  186. $options['enctype'] = 'multipart/form-data';
  187. }
  188. unset($options['type']);
  189. if (!in_array(strtolower($options['method']), array('get', 'post'))) {
  190. $append .= $self->hidden('_method', array(
  191. 'name' => '_method', 'value' => strtoupper($options['method'])
  192. ));
  193. }
  194. $url = $options['url'] ?: array('action' => $options['action']);
  195. unset($options['url'], $options['action']);
  196. $options['method'] = strtoupper($options['method']);
  197. return $self->invokeMethod('_render', array(
  198. $method, $template, compact('url', 'options')
  199. ));
  200. };
  201. return $this->_filter(__METHOD__, compact('binding', 'options'), $filter);
  202. }
  203. public function end() {
  204. list(, $options, $template) = $this->_defaults(__FUNCTION__, null, array());
  205. $params = compact('options', 'template');
  206. $_binding =& $this->_binding;
  207. $_context =& $this->_context;
  208. $filter = function($self, $params, $chain) use (&$_binding, &$_context) {
  209. unset($_binding);
  210. return $_context->strings('form-end');
  211. };
  212. $result = $this->_filter(__METHOD__, $params, $filter);
  213. unset($this->_binding);
  214. $this->_binding = null;
  215. return $result;
  216. }
  217. /**
  218. * Generates an HTML `<input type="submit" />` object.
  219. *
  220. * @param string $title The title of the submit button.
  221. * @param array $options
  222. * @return string Returns a submit `<input />` tag with the given title and HTML attributes.
  223. */
  224. public function submit($title = null, $options = array()) {
  225. list($name, $options, $template) = $this->_defaults(__FUNCTION__, null, $options);
  226. return $this->_render(__METHOD__, $template, compact('title', 'options'));
  227. }
  228. public function textarea($name, $options = array()) {
  229. list($name, $options, $template) = $this->_defaults(__FUNCTION__, $name, $options);
  230. $value = isset($options['value']) ? $options['value'] : '';
  231. unset($options['value']);
  232. return $this->_render(__METHOD__, $template, compact('name', 'options', 'value'));
  233. }
  234. public function text($name, $options = array()) {
  235. list($name, $options, $template) = $this->_defaults(__FUNCTION__, $name, $options);
  236. return $this->_render(__METHOD__, $template, compact('name', 'options'));
  237. }
  238. /**
  239. * Generates a `<select />` list using the `$list` parameter for the `<option />` tags. The
  240. * default selection will be set to the value of `$options['value']`, if specified.
  241. *
  242. * For example: {{{
  243. * $this->form->select('colors', array(1 => 'red', 2 => 'green', 3 => 'blue'), array(
  244. * 'id' => 'Colors', 'value' => 2
  245. * ));
  246. * // Renders a '<select />' list with options 'red', 'green' and 'blue', with the 'green'
  247. * // option as the selection
  248. * }}}
  249. *
  250. * @param string $name The `name` attribute of the `<select />` element.
  251. * @param array $list An associative array of key/value pairs, which will be used to render the
  252. * list of options.
  253. * @param array $options Any HTML attributes that should be associated with the `<select />`
  254. * element. If the `'value'` key is set, this will be the value of the option
  255. * that is selected by default.
  256. * @return string Returns an HTML `<select />` element.
  257. */
  258. public function select($name, $list = array(), $options = array()) {
  259. $defaults = array('empty' => false, 'value' => null);
  260. list($name, $options, $template) = $this->_defaults(__FUNCTION__, $name, $options);
  261. $options += $defaults;
  262. $val = $options['value'];
  263. $empty = $options['empty'];
  264. unset($options['value'], $options['empty']);
  265. if ($empty) {
  266. $list = array('' => ($empty === true) ? '' : $empty) + $list;
  267. }
  268. $startTemplate = ($options['multiple']) ? 'select-multi-start' : 'select-start';
  269. $output = $this->_render(__METHOD__, $startTemplate, compact('name', 'options'));
  270. foreach ($list as $value => $title) {
  271. $selected = false;
  272. if (is_array($val) && in_array($value, $val)) {
  273. $selected = true;
  274. } elseif ($val == $value) {
  275. $selected = true;
  276. }
  277. $options = $selected ? array('selected' => true) : array();
  278. $output .= $this->_render(__METHOD__, 'select-option', compact(
  279. 'value', 'title', 'options'
  280. ));
  281. }
  282. return $output . $this->_context->strings('select-end');
  283. }
  284. public function checkbox($name, $options = array()) {
  285. list($name, $options, $template) = $this->_defaults(__FUNCTION__, $name, $options);
  286. if (!isset($options['checked'])) {
  287. $options['checked'] = isset($options['value']) ? $options['value'] : false;
  288. }
  289. unset($options['value']);
  290. return $this->_render(__METHOD__, $template, compact('name', 'options'));
  291. }
  292. public function password($name, $options = array()) {
  293. list($name, $options, $template) = $this->_defaults(__FUNCTION__, $name, $options);
  294. return $this->_render(__METHOD__, $template, compact('name', 'options'));
  295. }
  296. public function hidden($name, $options = array()) {
  297. list($name, $options, $template) = $this->_defaults(__FUNCTION__, $name, $options);
  298. return $this->_render(__METHOD__, $template, compact('name', 'options'));
  299. }
  300. public function label($name, $title = null, $options = array()) {
  301. $title = $title ?: Inflector::humanize($name);
  302. list($name, $options, $template) = $this->_defaults(__FUNCTION__, $name, $options);
  303. return $this->_render(__METHOD__, $template, compact('name', 'title', 'options'));
  304. }
  305. protected function _defaults($method, $name, $options) {
  306. $methodConfig = isset($this->_config[$method]) ? $this->_config[$method] : array();
  307. $options += $methodConfig + $this->_config['base'];
  308. if ($name && $this->_binding && (!isset($options['value']) || empty($options['value']))) {
  309. $options['value'] = $this->_binding->data($name);
  310. }
  311. if (isset($options['default'])) {
  312. $options['value'] = !empty($options['value']) ? $options['value'] : $options['default'];
  313. unset($options['default']);
  314. }
  315. $template = isset($this->_templateMap[$method]) ? $this->_templateMap[$method] : $method;
  316. return array($name, $options, $template);
  317. }
  318. }
  319. ?>