PageRenderTime 124ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/yii/framework/web/form/CForm.php

https://github.com/joshuaswarren/weatherhub
PHP | 604 lines | 279 code | 38 blank | 287 comment | 39 complexity | 5c74a6d4b9d3349453d33bf655458231 MD5 | raw file
  1. <?php
  2. /**
  3. * CForm class file.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright Copyright &copy; 2008-2011 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. /**
  11. * CForm represents a form object that contains form input specifications.
  12. *
  13. * The main purpose of introducing the abstraction of form objects is to enhance the
  14. * reusability of forms. In particular, we can divide a form in two parts: those
  15. * that specify each individual form inputs, and those that decorate the form inputs.
  16. * A CForm object represents the former part. It relies on the rendering process to
  17. * accomplish form input decoration. Reusability is mainly achieved in the rendering process.
  18. * That is, a rendering process can be reused to render different CForm objects.
  19. *
  20. * A form can be rendered in different ways. One can call the {@link render} method
  21. * to get a quick form rendering without writing any HTML code; one can also override
  22. * {@link render} to render the form in a different layout; and one can use an external
  23. * view template to render each form element explicitly. In these ways, the {@link render}
  24. * method can be applied to all kinds of forms and thus achieves maximum reusability;
  25. * while the external view template keeps maximum flexibility in rendering complex forms.
  26. *
  27. * Form input specifications are organized in terms of a form element hierarchy.
  28. * At the root of the hierarchy, it is the root CForm object. The root form object maintains
  29. * its children in two collections: {@link elements} and {@link buttons}.
  30. * The former contains non-button form elements ({@link CFormStringElement},
  31. * {@link CFormInputElement} and CForm); while the latter mainly contains
  32. * button elements ({@link CFormButtonElement}). When a CForm object is embedded in the
  33. * {@link elements} collection, it is called a sub-form which can have its own {@link elements}
  34. * and {@link buttons} collections and thus form the whole form hierarchy.
  35. *
  36. * Sub-forms are mainly used to handle multiple models. For example, in a user
  37. * registration form, we can have the root form to collect input for the user
  38. * table while a sub-form to collect input for the profile table. Sub-form is also
  39. * a good way to partition a lengthy form into shorter ones, even though all inputs
  40. * may belong to the same model.
  41. *
  42. * Form input specifications are given in terms of a configuration array which is
  43. * used to initialize the property values of a CForm object. The {@link elements} and
  44. * {@link buttons} properties need special attention as they are the main properties
  45. * to be configured. To configure {@link elements}, we should give it an array like
  46. * the following:
  47. * <pre>
  48. * 'elements'=>array(
  49. * 'username'=>array('type'=>'text', 'maxlength'=>80),
  50. * 'password'=>array('type'=>'password', 'maxlength'=>80),
  51. * )
  52. * </pre>
  53. * The above code specifies two input elements: 'username' and 'password'. Note the model
  54. * object must have exactly the same attributes 'username' and 'password'. Each element
  55. * has a type which specifies what kind of input should be used. The rest of the array elements
  56. * (e.g. 'maxlength') in an input specification are rendered as HTML element attributes
  57. * when the input field is rendered. The {@link buttons} property is configured similarly.
  58. *
  59. * For more details about configuring form elements, please refer to {@link CFormInputElement}
  60. * and {@link CFormButtonElement}.
  61. *
  62. * @author Qiang Xue <qiang.xue@gmail.com>
  63. * @version $Id: CForm.php 3076 2011-03-14 13:16:43Z qiang.xue $
  64. * @package system.web.form
  65. * @since 1.1
  66. */
  67. class CForm extends CFormElement implements ArrayAccess
  68. {
  69. /**
  70. * @var string the title for this form. By default, if this is set, a fieldset may be rendered
  71. * around the form body using the title as its legend. Defaults to null.
  72. */
  73. public $title;
  74. /**
  75. * @var string the description of this form.
  76. */
  77. public $description;
  78. /**
  79. * @var string the submission method of this form. Defaults to 'post'.
  80. * This property is ignored when this form is a sub-form.
  81. */
  82. public $method='post';
  83. /**
  84. * @var mixed the form action URL (see {@link CHtml::normalizeUrl} for details about this parameter.)
  85. * Defaults to an empty string, meaning the current request URL.
  86. * This property is ignored when this form is a sub-form.
  87. */
  88. public $action='';
  89. /**
  90. * @var string the name of the class for representing a form input element. Defaults to 'CFormInputElement'.
  91. */
  92. public $inputElementClass='CFormInputElement';
  93. /**
  94. * @var string the name of the class for representing a form button element. Defaults to 'CFormButtonElement'.
  95. */
  96. public $buttonElementClass='CFormButtonElement';
  97. /**
  98. * @var array HTML attribute values for the form tag. When the form is embedded within another form,
  99. * this property will be used to render the HTML attribute values for the fieldset enclosing the child form.
  100. */
  101. public $attributes=array();
  102. /**
  103. * @var boolean whether to show error summary. Defaults to false.
  104. */
  105. public $showErrorSummary=false;
  106. /**
  107. * @var array the configuration used to create the active form widget.
  108. * The widget will be used to render the form tag and the error messages.
  109. * The 'class' option is required, which specifies the class of the widget.
  110. * The rest of the options will be passed to {@link CBaseController::beginWidget()} call.
  111. * Defaults to array('class'=>'CActiveForm').
  112. * @since 1.1.1
  113. */
  114. public $activeForm=array('class'=>'CActiveForm');
  115. private $_model;
  116. private $_elements;
  117. private $_buttons;
  118. private $_activeForm;
  119. /**
  120. * Constructor.
  121. * If you override this method, make sure you do not modify the method
  122. * signature, and also make sure you call the parent implementation.
  123. * @param mixed $config the configuration for this form. It can be a configuration array
  124. * or the path alias of a PHP script file that returns a configuration array.
  125. * The configuration array consists of name-value pairs that are used to initialize
  126. * the properties of this form.
  127. * @param CModel $model the model object associated with this form. If it is null,
  128. * the parent's model will be used instead.
  129. * @param mixed $parent the direct parent of this form. This could be either a {@link CBaseController}
  130. * object (a controller or a widget), or a {@link CForm} object.
  131. * If the former, it means the form is a top-level form; if the latter, it means this form is a sub-form.
  132. */
  133. public function __construct($config,$model=null,$parent=null)
  134. {
  135. $this->setModel($model);
  136. if($parent===null)
  137. $parent=Yii::app()->getController();
  138. parent::__construct($config,$parent);
  139. $this->init();
  140. }
  141. /**
  142. * Initializes this form.
  143. * This method is invoked at the end of the constructor.
  144. * You may override this method to provide customized initialization (such as
  145. * configuring the form object).
  146. */
  147. protected function init()
  148. {
  149. }
  150. /**
  151. * Returns a value indicating whether this form is submitted.
  152. * @param string $buttonName the name of the submit button
  153. * @param boolean $loadData whether to call {@link loadData} if the form is submitted so that
  154. * the submitted data can be populated to the associated models.
  155. * @return boolean whether this form is submitted.
  156. * @see loadData
  157. */
  158. public function submitted($buttonName='submit',$loadData=true)
  159. {
  160. $ret=$this->clicked($this->getUniqueId()) && $this->clicked($buttonName);
  161. if($ret && $loadData)
  162. $this->loadData();
  163. return $ret;
  164. }
  165. /**
  166. * Returns a value indicating whether the specified button is clicked.
  167. * @param string $name the button name
  168. * @return boolean whether the button is clicked.
  169. */
  170. public function clicked($name)
  171. {
  172. if(strcasecmp($this->getRoot()->method,'get'))
  173. return isset($_POST[$name]);
  174. else
  175. return isset($_GET[$name]);
  176. }
  177. /**
  178. * Validates the models associated with this form.
  179. * All models, including those associated with sub-forms, will perform
  180. * the validation. You may use {@link CModel::getErrors()} to retrieve the validation
  181. * error messages.
  182. * @return boolean whether all models are valid
  183. */
  184. public function validate()
  185. {
  186. $ret=true;
  187. foreach($this->getModels() as $model)
  188. $ret=$model->validate() && $ret;
  189. return $ret;
  190. }
  191. /**
  192. * Loads the submitted data into the associated model(s) to the form.
  193. * This method will go through all models associated with this form and its sub-forms
  194. * and massively assign the submitted data to the models.
  195. * @see submitted
  196. */
  197. public function loadData()
  198. {
  199. if($this->_model!==null)
  200. {
  201. $class=get_class($this->_model);
  202. if(strcasecmp($this->getRoot()->method,'get'))
  203. {
  204. if(isset($_POST[$class]))
  205. $this->_model->setAttributes($_POST[$class]);
  206. }
  207. else if(isset($_GET[$class]))
  208. $this->_model->setAttributes($_GET[$class]);
  209. }
  210. foreach($this->getElements() as $element)
  211. {
  212. if($element instanceof self)
  213. $element->loadData();
  214. }
  215. }
  216. /**
  217. * @return CForm the top-level form object
  218. */
  219. public function getRoot()
  220. {
  221. $root=$this;
  222. while($root->getParent() instanceof self)
  223. $root=$root->getParent();
  224. return $root;
  225. }
  226. /**
  227. * @return CActiveForm the active form widget associated with this form.
  228. * This method will return the active form widget as specified by {@link activeForm}.
  229. * @since 1.1.1
  230. */
  231. public function getActiveFormWidget()
  232. {
  233. if($this->_activeForm!==null)
  234. return $this->_activeForm;
  235. else
  236. return $this->getRoot()->_activeForm;
  237. }
  238. /**
  239. * @return CBaseController the owner of this form. This refers to either a controller or a widget
  240. * by which the form is created and rendered.
  241. */
  242. public function getOwner()
  243. {
  244. $owner=$this->getParent();
  245. while($owner instanceof self)
  246. $owner=$owner->getParent();
  247. return $owner;
  248. }
  249. /**
  250. * Returns the model that this form is associated with.
  251. * @param boolean $checkParent whether to return parent's model if this form doesn't have model by itself.
  252. * @return CModel the model associated with this form. If this form does not have a model,
  253. * it will look for a model in its ancestors.
  254. */
  255. public function getModel($checkParent=true)
  256. {
  257. if(!$checkParent)
  258. return $this->_model;
  259. $form=$this;
  260. while($form->_model===null && $form->getParent() instanceof self)
  261. $form=$form->getParent();
  262. return $form->_model;
  263. }
  264. /**
  265. * @param CModel $model the model to be associated with this form
  266. */
  267. public function setModel($model)
  268. {
  269. $this->_model=$model;
  270. }
  271. /**
  272. * Returns all models that are associated with this form or its sub-forms.
  273. * @return array the models that are associated with this form or its sub-forms.
  274. */
  275. public function getModels()
  276. {
  277. $models=array();
  278. if($this->_model!==null)
  279. $models[]=$this->_model;
  280. foreach($this->getElements() as $element)
  281. {
  282. if($element instanceof self)
  283. $models=array_merge($models,$element->getModels());
  284. }
  285. return $models;
  286. }
  287. /**
  288. * Returns the input elements of this form.
  289. * This includes text strings, input elements and sub-forms.
  290. * Note that the returned result is a {@link CFormElementCollection} object, which
  291. * means you can use it like an array. For more details, see {@link CMap}.
  292. * @return CFormElementCollection the form elements.
  293. */
  294. public function getElements()
  295. {
  296. if($this->_elements===null)
  297. $this->_elements=new CFormElementCollection($this,false);
  298. return $this->_elements;
  299. }
  300. /**
  301. * Configures the input elements of this form.
  302. * The configuration must be an array of input configuration array indexed by input name.
  303. * Each input configuration array consists of name-value pairs that are used to initialize
  304. * a {@link CFormStringElement} object (when 'type' is 'string'), a {@link CFormElement} object
  305. * (when 'type' is a string ending with 'Form'), or a {@link CFormInputElement} object in
  306. * all other cases.
  307. * @param array $elements the button configurations
  308. */
  309. public function setElements($elements)
  310. {
  311. $collection=$this->getElements();
  312. foreach($elements as $name=>$config)
  313. $collection->add($name,$config);
  314. }
  315. /**
  316. * Returns the button elements of this form.
  317. * Note that the returned result is a {@link CFormElementCollection} object, which
  318. * means you can use it like an array. For more details, see {@link CMap}.
  319. * @return CFormElementCollection the form elements.
  320. */
  321. public function getButtons()
  322. {
  323. if($this->_buttons===null)
  324. $this->_buttons=new CFormElementCollection($this,true);
  325. return $this->_buttons;
  326. }
  327. /**
  328. * Configures the buttons of this form.
  329. * The configuration must be an array of button configuration array indexed by button name.
  330. * Each button configuration array consists of name-value pairs that are used to initialize
  331. * a {@link CFormButtonElement} object.
  332. * @param array $buttons the button configurations
  333. */
  334. public function setButtons($buttons)
  335. {
  336. $collection=$this->getButtons();
  337. foreach($buttons as $name=>$config)
  338. $collection->add($name,$config);
  339. }
  340. /**
  341. * Renders the form.
  342. * The default implementation simply calls {@link renderBegin}, {@link renderBody} and {@link renderEnd}.
  343. * @return string the rendering result
  344. */
  345. public function render()
  346. {
  347. return $this->renderBegin() . $this->renderBody() . $this->renderEnd();
  348. }
  349. /**
  350. * Renders the open tag of the form.
  351. * The default implementation will render the open form tag.
  352. * @return string the rendering result
  353. */
  354. public function renderBegin()
  355. {
  356. if($this->getParent() instanceof self)
  357. return '';
  358. else
  359. {
  360. $options=$this->activeForm;
  361. if(isset($options['class']))
  362. {
  363. $class=$options['class'];
  364. unset($options['class']);
  365. }
  366. else
  367. $class='CActiveForm';
  368. $options['action']=$this->action;
  369. $options['method']=$this->method;
  370. if(isset($options['htmlOptions']))
  371. {
  372. foreach($this->attributes as $name=>$value)
  373. $options['htmlOptions'][$name]=$value;
  374. }
  375. else
  376. $options['htmlOptions']=$this->attributes;
  377. ob_start();
  378. $this->_activeForm=$this->getOwner()->beginWidget($class, $options);
  379. return ob_get_clean() . "<div style=\"visibility:hidden\">".CHtml::hiddenField($this->getUniqueID(),1)."</div>\n";
  380. }
  381. }
  382. /**
  383. * Renders the close tag of the form.
  384. * @return string the rendering result
  385. */
  386. public function renderEnd()
  387. {
  388. if($this->getParent() instanceof self)
  389. return '';
  390. else
  391. {
  392. ob_start();
  393. $this->getOwner()->endWidget();
  394. return ob_get_clean();
  395. }
  396. }
  397. /**
  398. * Renders the body content of this form.
  399. * This method mainly renders {@link elements} and {@link buttons}.
  400. * If {@link title} or {@link description} is specified, they will be rendered as well.
  401. * And if the associated model contains error, the error summary may also be displayed.
  402. * The form tag will not be rendered. Please call {@link renderBegin} and {@link renderEnd}
  403. * to render the open and close tags of the form.
  404. * You may override this method to customize the rendering of the form.
  405. * @return string the rendering result
  406. */
  407. public function renderBody()
  408. {
  409. $output='';
  410. if($this->title!==null)
  411. {
  412. if($this->getParent() instanceof self)
  413. {
  414. $attributes=$this->attributes;
  415. unset($attributes['name'],$attributes['type']);
  416. $output=CHtml::openTag('fieldset', $attributes)."<legend>".$this->title."</legend>\n";
  417. }
  418. else
  419. $output="<fieldset>\n<legend>".$this->title."</legend>\n";
  420. }
  421. if($this->description!==null)
  422. $output.="<div class=\"description\">\n".$this->description."</div>\n";
  423. if($this->showErrorSummary && ($model=$this->getModel(false))!==null)
  424. $output.=$this->getActiveFormWidget()->errorSummary($model)."\n";
  425. $output.=$this->renderElements()."\n".$this->renderButtons()."\n";
  426. if($this->title!==null)
  427. $output.="</fieldset>\n";
  428. return $output;
  429. }
  430. /**
  431. * Renders the {@link elements} in this form.
  432. * @return string the rendering result
  433. */
  434. public function renderElements()
  435. {
  436. $output='';
  437. foreach($this->getElements() as $element)
  438. $output.=$this->renderElement($element);
  439. return $output;
  440. }
  441. /**
  442. * Renders the {@link buttons} in this form.
  443. * @return string the rendering result
  444. */
  445. public function renderButtons()
  446. {
  447. $output='';
  448. foreach($this->getButtons() as $button)
  449. $output.=$this->renderElement($button);
  450. return $output!=='' ? "<div class=\"row buttons\">".$output."</div>\n" : '';
  451. }
  452. /**
  453. * Renders a single element which could be an input element, a sub-form, a string, or a button.
  454. * @param mixed $element the form element to be rendered. This can be either a {@link CFormElement} instance
  455. * or a string representing the name of the form element.
  456. * @return string the rendering result
  457. */
  458. public function renderElement($element)
  459. {
  460. if(is_string($element))
  461. {
  462. if(($e=$this[$element])===null && ($e=$this->getButtons()->itemAt($element))===null)
  463. return $element;
  464. else
  465. $element=$e;
  466. }
  467. if($element->getVisible())
  468. {
  469. if($element instanceof CFormInputElement)
  470. {
  471. if($element->type==='hidden')
  472. return "<div style=\"visibility:hidden\">\n".$element->render()."</div>\n";
  473. else
  474. return "<div class=\"row field_{$element->name}\">\n".$element->render()."</div>\n";
  475. }
  476. else if($element instanceof CFormButtonElement)
  477. return $element->render()."\n";
  478. else
  479. return $element->render();
  480. }
  481. return '';
  482. }
  483. /**
  484. * This method is called after an element is added to the element collection.
  485. * @param string $name the name of the element
  486. * @param CFormElement $element the element that is added
  487. * @param boolean $forButtons whether the element is added to the {@link buttons} collection.
  488. * If false, it means the element is added to the {@link elements} collection.
  489. */
  490. public function addedElement($name,$element,$forButtons)
  491. {
  492. }
  493. /**
  494. * This method is called after an element is removed from the element collection.
  495. * @param string $name the name of the element
  496. * @param CFormElement $element the element that is removed
  497. * @param boolean $forButtons whether the element is removed from the {@link buttons} collection
  498. * If false, it means the element is removed from the {@link elements} collection.
  499. */
  500. public function removedElement($name,$element,$forButtons)
  501. {
  502. }
  503. /**
  504. * Evaluates the visibility of this form.
  505. * This method will check the visibility of the {@link elements}.
  506. * If any one of them is visible, the form is considered as visible. Otherwise, it is invisible.
  507. * @return boolean whether this form is visible.
  508. */
  509. protected function evaluateVisible()
  510. {
  511. foreach($this->getElements() as $element)
  512. if($element->getVisible())
  513. return true;
  514. return false;
  515. }
  516. /**
  517. * Returns a unique ID that identifies this form in the current page.
  518. * @return string the unique ID identifying this form
  519. */
  520. protected function getUniqueId()
  521. {
  522. if(isset($this->attributes['id']))
  523. return 'yform_'.$this->attributes['id'];
  524. else
  525. return 'yform_'.sprintf('%x',crc32(serialize(array_keys($this->getElements()->toArray()))));
  526. }
  527. /**
  528. * Returns whether there is an element at the specified offset.
  529. * This method is required by the interface ArrayAccess.
  530. * @param mixed $offset the offset to check on
  531. * @return boolean
  532. */
  533. public function offsetExists($offset)
  534. {
  535. return $this->getElements()->contains($offset);
  536. }
  537. /**
  538. * Returns the element at the specified offset.
  539. * This method is required by the interface ArrayAccess.
  540. * @param integer $offset the offset to retrieve element.
  541. * @return mixed the element at the offset, null if no element is found at the offset
  542. */
  543. public function offsetGet($offset)
  544. {
  545. return $this->getElements()->itemAt($offset);
  546. }
  547. /**
  548. * Sets the element at the specified offset.
  549. * This method is required by the interface ArrayAccess.
  550. * @param integer $offset the offset to set element
  551. * @param mixed $item the element value
  552. */
  553. public function offsetSet($offset,$item)
  554. {
  555. $this->getElements()->add($offset,$item);
  556. }
  557. /**
  558. * Unsets the element at the specified offset.
  559. * This method is required by the interface ArrayAccess.
  560. * @param mixed $offset the offset to unset element
  561. */
  562. public function offsetUnset($offset)
  563. {
  564. $this->getElements()->remove($offset);
  565. }
  566. }