diff options
Diffstat (limited to 'framework/web/form/CForm.php')
| -rw-r--r-- | framework/web/form/CForm.php | 615 |
1 files changed, 615 insertions, 0 deletions
diff --git a/framework/web/form/CForm.php b/framework/web/form/CForm.php new file mode 100644 index 0000000..e974eaf --- /dev/null +++ b/framework/web/form/CForm.php @@ -0,0 +1,615 @@ +<?php +/** + * CForm class file. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @link http://www.yiiframework.com/ + * @copyright Copyright © 2008-2011 Yii Software LLC + * @license http://www.yiiframework.com/license/ + */ + +/** + * CForm represents a form object that contains form input specifications. + * + * The main purpose of introducing the abstraction of form objects is to enhance the + * reusability of forms. In particular, we can divide a form in two parts: those + * that specify each individual form inputs, and those that decorate the form inputs. + * A CForm object represents the former part. It relies on the rendering process to + * accomplish form input decoration. Reusability is mainly achieved in the rendering process. + * That is, a rendering process can be reused to render different CForm objects. + * + * A form can be rendered in different ways. One can call the {@link render} method + * to get a quick form rendering without writing any HTML code; one can also override + * {@link render} to render the form in a different layout; and one can use an external + * view template to render each form element explicitly. In these ways, the {@link render} + * method can be applied to all kinds of forms and thus achieves maximum reusability; + * while the external view template keeps maximum flexibility in rendering complex forms. + * + * Form input specifications are organized in terms of a form element hierarchy. + * At the root of the hierarchy, it is the root CForm object. The root form object maintains + * its children in two collections: {@link elements} and {@link buttons}. + * The former contains non-button form elements ({@link CFormStringElement}, + * {@link CFormInputElement} and CForm); while the latter mainly contains + * button elements ({@link CFormButtonElement}). When a CForm object is embedded in the + * {@link elements} collection, it is called a sub-form which can have its own {@link elements} + * and {@link buttons} collections and thus form the whole form hierarchy. + * + * Sub-forms are mainly used to handle multiple models. For example, in a user + * registration form, we can have the root form to collect input for the user + * table while a sub-form to collect input for the profile table. Sub-form is also + * a good way to partition a lengthy form into shorter ones, even though all inputs + * may belong to the same model. + * + * Form input specifications are given in terms of a configuration array which is + * used to initialize the property values of a CForm object. The {@link elements} and + * {@link buttons} properties need special attention as they are the main properties + * to be configured. To configure {@link elements}, we should give it an array like + * the following: + * <pre> + * 'elements'=>array( + * 'username'=>array('type'=>'text', 'maxlength'=>80), + * 'password'=>array('type'=>'password', 'maxlength'=>80), + * ) + * </pre> + * The above code specifies two input elements: 'username' and 'password'. Note the model + * object must have exactly the same attributes 'username' and 'password'. Each element + * has a type which specifies what kind of input should be used. The rest of the array elements + * (e.g. 'maxlength') in an input specification are rendered as HTML element attributes + * when the input field is rendered. The {@link buttons} property is configured similarly. + * + * For more details about configuring form elements, please refer to {@link CFormInputElement} + * and {@link CFormButtonElement}. + * + * @property CForm $root The top-level form object. + * @property CActiveForm $activeFormWidget The active form widget associated with this form. + * This method will return the active form widget as specified by {@link activeForm}. + * @property CBaseController $owner The owner of this form. This refers to either a controller or a widget + * by which the form is created and rendered. + * @property CModel $model The model associated with this form. If this form does not have a model, + * it will look for a model in its ancestors. + * @property array $models The models that are associated with this form or its sub-forms. + * @property CFormElementCollection $elements The form elements. + * @property CFormElementCollection $buttons The form elements. + * + * @author Qiang Xue <qiang.xue@gmail.com> + * @version $Id: CForm.php 3426 2011-10-25 00:01:09Z alexander.makarow $ + * @package system.web.form + * @since 1.1 + */ +class CForm extends CFormElement implements ArrayAccess +{ + /** + * @var string the title for this form. By default, if this is set, a fieldset may be rendered + * around the form body using the title as its legend. Defaults to null. + */ + public $title; + /** + * @var string the description of this form. + */ + public $description; + /** + * @var string the submission method of this form. Defaults to 'post'. + * This property is ignored when this form is a sub-form. + */ + public $method='post'; + /** + * @var mixed the form action URL (see {@link CHtml::normalizeUrl} for details about this parameter.) + * Defaults to an empty string, meaning the current request URL. + * This property is ignored when this form is a sub-form. + */ + public $action=''; + /** + * @var string the name of the class for representing a form input element. Defaults to 'CFormInputElement'. + */ + public $inputElementClass='CFormInputElement'; + /** + * @var string the name of the class for representing a form button element. Defaults to 'CFormButtonElement'. + */ + public $buttonElementClass='CFormButtonElement'; + /** + * @var array HTML attribute values for the form tag. When the form is embedded within another form, + * this property will be used to render the HTML attribute values for the fieldset enclosing the child form. + */ + public $attributes=array(); + /** + * @var boolean whether to show error summary. Defaults to false. + */ + public $showErrorSummary=false; + /** + * @var array the configuration used to create the active form widget. + * The widget will be used to render the form tag and the error messages. + * The 'class' option is required, which specifies the class of the widget. + * The rest of the options will be passed to {@link CBaseController::beginWidget()} call. + * Defaults to array('class'=>'CActiveForm'). + * @since 1.1.1 + */ + public $activeForm=array('class'=>'CActiveForm'); + + private $_model; + private $_elements; + private $_buttons; + private $_activeForm; + + /** + * Constructor. + * If you override this method, make sure you do not modify the method + * signature, and also make sure you call the parent implementation. + * @param mixed $config the configuration for this form. It can be a configuration array + * or the path alias of a PHP script file that returns a configuration array. + * The configuration array consists of name-value pairs that are used to initialize + * the properties of this form. + * @param CModel $model the model object associated with this form. If it is null, + * the parent's model will be used instead. + * @param mixed $parent the direct parent of this form. This could be either a {@link CBaseController} + * object (a controller or a widget), or a {@link CForm} object. + * If the former, it means the form is a top-level form; if the latter, it means this form is a sub-form. + */ + public function __construct($config,$model=null,$parent=null) + { + $this->setModel($model); + if($parent===null) + $parent=Yii::app()->getController(); + parent::__construct($config,$parent); + $this->init(); + } + + /** + * Initializes this form. + * This method is invoked at the end of the constructor. + * You may override this method to provide customized initialization (such as + * configuring the form object). + */ + protected function init() + { + } + + /** + * Returns a value indicating whether this form is submitted. + * @param string $buttonName the name of the submit button + * @param boolean $loadData whether to call {@link loadData} if the form is submitted so that + * the submitted data can be populated to the associated models. + * @return boolean whether this form is submitted. + * @see loadData + */ + public function submitted($buttonName='submit',$loadData=true) + { + $ret=$this->clicked($this->getUniqueId()) && $this->clicked($buttonName); + if($ret && $loadData) + $this->loadData(); + return $ret; + } + + /** + * Returns a value indicating whether the specified button is clicked. + * @param string $name the button name + * @return boolean whether the button is clicked. + */ + public function clicked($name) + { + if(strcasecmp($this->getRoot()->method,'get')) + return isset($_POST[$name]); + else + return isset($_GET[$name]); + } + + /** + * Validates the models associated with this form. + * All models, including those associated with sub-forms, will perform + * the validation. You may use {@link CModel::getErrors()} to retrieve the validation + * error messages. + * @return boolean whether all models are valid + */ + public function validate() + { + $ret=true; + foreach($this->getModels() as $model) + $ret=$model->validate() && $ret; + return $ret; + } + + /** + * Loads the submitted data into the associated model(s) to the form. + * This method will go through all models associated with this form and its sub-forms + * and massively assign the submitted data to the models. + * @see submitted + */ + public function loadData() + { + if($this->_model!==null) + { + $class=get_class($this->_model); + if(strcasecmp($this->getRoot()->method,'get')) + { + if(isset($_POST[$class])) + $this->_model->setAttributes($_POST[$class]); + } + else if(isset($_GET[$class])) + $this->_model->setAttributes($_GET[$class]); + } + foreach($this->getElements() as $element) + { + if($element instanceof self) + $element->loadData(); + } + } + + /** + * @return CForm the top-level form object + */ + public function getRoot() + { + $root=$this; + while($root->getParent() instanceof self) + $root=$root->getParent(); + return $root; + } + + /** + * @return CActiveForm the active form widget associated with this form. + * This method will return the active form widget as specified by {@link activeForm}. + * @since 1.1.1 + */ + public function getActiveFormWidget() + { + if($this->_activeForm!==null) + return $this->_activeForm; + else + return $this->getRoot()->_activeForm; + } + + /** + * @return CBaseController the owner of this form. This refers to either a controller or a widget + * by which the form is created and rendered. + */ + public function getOwner() + { + $owner=$this->getParent(); + while($owner instanceof self) + $owner=$owner->getParent(); + return $owner; + } + + /** + * Returns the model that this form is associated with. + * @param boolean $checkParent whether to return parent's model if this form doesn't have model by itself. + * @return CModel the model associated with this form. If this form does not have a model, + * it will look for a model in its ancestors. + */ + public function getModel($checkParent=true) + { + if(!$checkParent) + return $this->_model; + $form=$this; + while($form->_model===null && $form->getParent() instanceof self) + $form=$form->getParent(); + return $form->_model; + } + + /** + * @param CModel $model the model to be associated with this form + */ + public function setModel($model) + { + $this->_model=$model; + } + + /** + * Returns all models that are associated with this form or its sub-forms. + * @return array the models that are associated with this form or its sub-forms. + */ + public function getModels() + { + $models=array(); + if($this->_model!==null) + $models[]=$this->_model; + foreach($this->getElements() as $element) + { + if($element instanceof self) + $models=array_merge($models,$element->getModels()); + } + return $models; + } + + /** + * Returns the input elements of this form. + * This includes text strings, input elements and sub-forms. + * Note that the returned result is a {@link CFormElementCollection} object, which + * means you can use it like an array. For more details, see {@link CMap}. + * @return CFormElementCollection the form elements. + */ + public function getElements() + { + if($this->_elements===null) + $this->_elements=new CFormElementCollection($this,false); + return $this->_elements; + } + + /** + * Configures the input elements of this form. + * The configuration must be an array of input configuration array indexed by input name. + * Each input configuration array consists of name-value pairs that are used to initialize + * a {@link CFormStringElement} object (when 'type' is 'string'), a {@link CFormElement} object + * (when 'type' is a string ending with 'Form'), or a {@link CFormInputElement} object in + * all other cases. + * @param array $elements the button configurations + */ + public function setElements($elements) + { + $collection=$this->getElements(); + foreach($elements as $name=>$config) + $collection->add($name,$config); + } + + /** + * Returns the button elements of this form. + * Note that the returned result is a {@link CFormElementCollection} object, which + * means you can use it like an array. For more details, see {@link CMap}. + * @return CFormElementCollection the form elements. + */ + public function getButtons() + { + if($this->_buttons===null) + $this->_buttons=new CFormElementCollection($this,true); + return $this->_buttons; + } + + /** + * Configures the buttons of this form. + * The configuration must be an array of button configuration array indexed by button name. + * Each button configuration array consists of name-value pairs that are used to initialize + * a {@link CFormButtonElement} object. + * @param array $buttons the button configurations + */ + public function setButtons($buttons) + { + $collection=$this->getButtons(); + foreach($buttons as $name=>$config) + $collection->add($name,$config); + } + + /** + * Renders the form. + * The default implementation simply calls {@link renderBegin}, {@link renderBody} and {@link renderEnd}. + * @return string the rendering result + */ + public function render() + { + return $this->renderBegin() . $this->renderBody() . $this->renderEnd(); + } + + /** + * Renders the open tag of the form. + * The default implementation will render the open form tag. + * @return string the rendering result + */ + public function renderBegin() + { + if($this->getParent() instanceof self) + return ''; + else + { + $options=$this->activeForm; + if(isset($options['class'])) + { + $class=$options['class']; + unset($options['class']); + } + else + $class='CActiveForm'; + $options['action']=$this->action; + $options['method']=$this->method; + if(isset($options['htmlOptions'])) + { + foreach($this->attributes as $name=>$value) + $options['htmlOptions'][$name]=$value; + } + else + $options['htmlOptions']=$this->attributes; + ob_start(); + $this->_activeForm=$this->getOwner()->beginWidget($class, $options); + return ob_get_clean() . "<div style=\"visibility:hidden\">".CHtml::hiddenField($this->getUniqueID(),1)."</div>\n"; + } + } + + /** + * Renders the close tag of the form. + * @return string the rendering result + */ + public function renderEnd() + { + if($this->getParent() instanceof self) + return ''; + else + { + ob_start(); + $this->getOwner()->endWidget(); + return ob_get_clean(); + } + } + + /** + * Renders the body content of this form. + * This method mainly renders {@link elements} and {@link buttons}. + * If {@link title} or {@link description} is specified, they will be rendered as well. + * And if the associated model contains error, the error summary may also be displayed. + * The form tag will not be rendered. Please call {@link renderBegin} and {@link renderEnd} + * to render the open and close tags of the form. + * You may override this method to customize the rendering of the form. + * @return string the rendering result + */ + public function renderBody() + { + $output=''; + if($this->title!==null) + { + if($this->getParent() instanceof self) + { + $attributes=$this->attributes; + unset($attributes['name'],$attributes['type']); + $output=CHtml::openTag('fieldset', $attributes)."<legend>".$this->title."</legend>\n"; + } + else + $output="<fieldset>\n<legend>".$this->title."</legend>\n"; + } + + if($this->description!==null) + $output.="<div class=\"description\">\n".$this->description."</div>\n"; + + if($this->showErrorSummary && ($model=$this->getModel(false))!==null) + $output.=$this->getActiveFormWidget()->errorSummary($model)."\n"; + + $output.=$this->renderElements()."\n".$this->renderButtons()."\n"; + + if($this->title!==null) + $output.="</fieldset>\n"; + + return $output; + } + + /** + * Renders the {@link elements} in this form. + * @return string the rendering result + */ + public function renderElements() + { + $output=''; + foreach($this->getElements() as $element) + $output.=$this->renderElement($element); + return $output; + } + + /** + * Renders the {@link buttons} in this form. + * @return string the rendering result + */ + public function renderButtons() + { + $output=''; + foreach($this->getButtons() as $button) + $output.=$this->renderElement($button); + return $output!=='' ? "<div class=\"row buttons\">".$output."</div>\n" : ''; + } + + /** + * Renders a single element which could be an input element, a sub-form, a string, or a button. + * @param mixed $element the form element to be rendered. This can be either a {@link CFormElement} instance + * or a string representing the name of the form element. + * @return string the rendering result + */ + public function renderElement($element) + { + if(is_string($element)) + { + if(($e=$this[$element])===null && ($e=$this->getButtons()->itemAt($element))===null) + return $element; + else + $element=$e; + } + if($element->getVisible()) + { + if($element instanceof CFormInputElement) + { + if($element->type==='hidden') + return "<div style=\"visibility:hidden\">\n".$element->render()."</div>\n"; + else + return "<div class=\"row field_{$element->name}\">\n".$element->render()."</div>\n"; + } + else if($element instanceof CFormButtonElement) + return $element->render()."\n"; + else + return $element->render(); + } + return ''; + } + + /** + * This method is called after an element is added to the element collection. + * @param string $name the name of the element + * @param CFormElement $element the element that is added + * @param boolean $forButtons whether the element is added to the {@link buttons} collection. + * If false, it means the element is added to the {@link elements} collection. + */ + public function addedElement($name,$element,$forButtons) + { + } + + /** + * This method is called after an element is removed from the element collection. + * @param string $name the name of the element + * @param CFormElement $element the element that is removed + * @param boolean $forButtons whether the element is removed from the {@link buttons} collection + * If false, it means the element is removed from the {@link elements} collection. + */ + public function removedElement($name,$element,$forButtons) + { + } + + /** + * Evaluates the visibility of this form. + * This method will check the visibility of the {@link elements}. + * If any one of them is visible, the form is considered as visible. Otherwise, it is invisible. + * @return boolean whether this form is visible. + */ + protected function evaluateVisible() + { + foreach($this->getElements() as $element) + if($element->getVisible()) + return true; + return false; + } + + /** + * Returns a unique ID that identifies this form in the current page. + * @return string the unique ID identifying this form + */ + protected function getUniqueId() + { + if(isset($this->attributes['id'])) + return 'yform_'.$this->attributes['id']; + else + return 'yform_'.sprintf('%x',crc32(serialize(array_keys($this->getElements()->toArray())))); + } + + /** + * Returns whether there is an element at the specified offset. + * This method is required by the interface ArrayAccess. + * @param mixed $offset the offset to check on + * @return boolean + */ + public function offsetExists($offset) + { + return $this->getElements()->contains($offset); + } + + /** + * Returns the element at the specified offset. + * This method is required by the interface ArrayAccess. + * @param integer $offset the offset to retrieve element. + * @return mixed the element at the offset, null if no element is found at the offset + */ + public function offsetGet($offset) + { + return $this->getElements()->itemAt($offset); + } + + /** + * Sets the element at the specified offset. + * This method is required by the interface ArrayAccess. + * @param integer $offset the offset to set element + * @param mixed $item the element value + */ + public function offsetSet($offset,$item) + { + $this->getElements()->add($offset,$item); + } + + /** + * Unsets the element at the specified offset. + * This method is required by the interface ArrayAccess. + * @param mixed $offset the offset to unset element + */ + public function offsetUnset($offset) + { + $this->getElements()->remove($offset); + } +} |
