* @link http://www.yiiframework.com/ * @copyright Copyright © 2008-2010 Yii Software LLC * @license http://www.yiiframework.com/license/ */ /** * CActiveForm represents an HTML form that can perform data validation via AJAX. * * CActiveForm renders the open and close form tags. In addition, it registers * necessary javascript code that can trigger AJAX validations when users change * the data in the relevant input fields. * * The goal of CActiveForm is to simplify the work of creating an HTML form that * can perform AJAX validation upon an input is changed by users. This may greatly * improve the user experience at entering data into a form. Because the validation * is done on the server side using the rules defined in the data model, no extra * javascript code needs to be written, and the validation result is consistent. * In case when the user turns off javascript in his browser, the traditional * validation via whole page submission still works. * * Using CActiveForm requires writing both the view code and the class code responding * to the AJAX validation requests. * * The following is a piece of sample view code: *
 * <php? $form = $this->beginWidget('CActiveForm'); ?>
 *
 * <?php echo $form->errorSummary($model); ?>
 *
 * <div class="row">
 *     <?php echo CHtml::activeLabelEx($model,'firstName'); ?>
 *     <?php echo CHtml::activeTextField($model,'firstName'); ?>
 *     <?php echo $form->error($model,'firstName'); ?>
 * </div>
 * <div class="row">
 *     <?php echo CHtml::activeLabelEx($model,'lastName'); ?>
 *     <?php echo CHtml::activeFileField($model,'lastName'); ?>
 *     <?php echo $form->error($model,'lastName'); ?>
 * </div>
 *
 * <php? $form = $this->endWidget(); ?>
 * 
* As we can see, the usage is very similar to {@link CHtml::error} and {@link CHtml::errorSummary}. * * To respond to the AJAX validation requests, we need the following class code: *
 * public function actionCreate()
 * {
 *     $model=new User;
 *     if(isset($_POST['ajax']))
 *     {
 *         echo CActiveForm::validate($model);
 *         return;
 *     }
 *     if(isset($_POST['User']))
 *     {
 *         $model->attributes=$_POST['User'];
 *         if($model->save())
 *             $this->redirect('index');
 *     }
 *     $this->render('create',array('model'=>$model));
 * }
 * 
* The first if-statement is the main extra code that responds to the AJAX validation requests. * The rest of the code is as usual, displaying a user creation page and saving the user model. * * There are some limitations of CActiveForm. First, it does not work with file uploads. * Second, it should not be used to perform validations that may cause server-side state change. * For example, it is not suitable to perform CAPTCHA validation done by {@link CCaptchAction} * because each validation request will increase the test times by one. Third, it is not designed * to work with tabular data input for the moment. * * Sometimes, we may want to limit the AJAX validation to certain model attributes only. * This can be achieved by setting the model with a scenario that is specific for AJAX validation. * Then only list those attributes that need AJAX validation in the scenario in {@link CModel::rules()} declaration. * * @author Qiang Xue * @version $Id$ * @package system.web.widgets * @since 1.1.0 */ class CActiveForm extends CWidget { /** * @var mixed the form action URL (see {@link normalizeUrl} for details about this parameter.) * If not set, the current page URL is used. */ public $action=''; /** * @var string the form submission method. This should be either 'post' or 'get'. * Defaults to 'post'. */ public $method='post'; /** * @var array additional HTML attributes that should be rendered for the form tag. */ public $htmlOptions=array(); /** * @var array the options to be passed to the javascript validation plugin. * The following options are supported: * * * Some of the above options may be overridden in individual calls of {@link error()}. * They include: validateOnChange, errorLabelCssClass, successLabelCssClass, * errorInputCssClass, successInputCssClass, errorMessageCssClass, successMessageCssClass and successMessage. */ public $options=array(); private $_attributes=array(); private $_summary; /** * Initializes the widget. * This renders the form open tag. */ public function init() { $this->htmlOptions['id']=$this->id; echo CHtml::beginForm($this->action, $this->method, $this->htmlOptions); } /** * Runs the widget. * This registers the necessary javascript code and renders the form close tag. */ public function run() { echo CHtml::endForm(); if(empty($this->_attributes)) return; $options=$this->options; $options['attributes']=array_values($this->_attributes); if($this->_summary!==null) $options['summaryID']=$this->_summary; $options=CJavaScript::encode($options); Yii::app()->clientScript->registerCoreScript('yiiactiveform'); $id=$this->id; Yii::app()->clientScript->registerScript(__CLASS__.'#'.$id,"\$('#$id').yiiactiveform($options);"); } /** * Displays the first validation error for a model attribute. * This is similar to {@link CHtml::error} except that it registers the model attribute * so that if its value is changed by users, an AJAX validation may be triggered. * @param CModel the data model * @param string the attribute name * @param array additional HTML attributes to be rendered in the container div tag. * Besides all those options available in {@link CHtml::error}, the following options are recognized in addition: * * These options override the corresponding options as declared in {@link options} for this * particular model attribute. For more details about these options, please refer to {@link options}. * @return string the validation result (error display or success message). * @see CHtml::error */ public function error($model,$attribute,$htmlOptions=array()) { $inputID=isset($htmlOptions['inputID']) ? $htmlOptions['inputID'] : CHtml::activeId($model,$attribute); if(!isset($htmlOptions['id'])) $htmlOptions['id']=$inputID.'_em_'; $option=array('inputID'=>$inputID, 'errorID'=>$htmlOptions['id']); $optionNames=array( 'validateOnChange', 'errorLabelCssClass', 'successLabelCssClass', 'errorInputCssClass', 'successInputCssClass', 'errorMessageCssClass', 'successMessageCssClass', 'successMessage', ); foreach($optionNames as $name) { if(isset($htmlOptions[$name])) { $option[$name]=$htmlOptions[$name]; unset($htmlOptions[$name]); } } if($model instanceof CActiveRecord && !$model->isNewRecord) $option['validated']=true; if(!isset($htmlOptions['class']) && (isset($option['errorMessageCssClass']) || isset($this->options['errorMessageCssClass']))) { $class=isset($option['errorMessageCssClass']) ? $option['errorMessageCssClass'] : $this->options['errorMessageCssClass']; if($class!==false) $htmlOptions['class']=$option['errorMessageCssClass']; } $html=CHtml::error($model,$attribute,$htmlOptions); if($html==='') { $htmlOptions['style']=isset($htmlOptions['style']) ? rtrim($htmlOptions['style'],';').';display:none' : 'display:none'; $html=CHtml::tag('div',$htmlOptions,''); } $this->_attributes[$inputID]=$option; return $html; } /** * Displays a summary of validation errors for one or several models. * This method is very similar to {@link CHtml::errorSummary} except that it also works * when AJAX validation is performed. * @param mixed the models whose input errors are to be displayed. This can be either * a single model or an array of models. * @param string a piece of HTML code that appears in front of the errors * @param string a piece of HTML code that appears at the end of the errors * @param array additional HTML attributes to be rendered in the container div tag. * @return string the error summary. Empty if no errors are found. * @see CHtml::errorSummary */ public function errorSummary($models,$header=null,$footer=null,$htmlOptions=array()) { if(!isset($htmlOptions['id'])) $htmlOptions['id']=$this->id.'_es_'; $html=CHtml::errorSummary($models,$header,$footer,$htmlOptions); if($html==='') { if($header===null) $header='

'.Yii::t('yii','Please fix the following input errors:').'

'; if(!isset($htmlOptions['class'])) $htmlOptions['class']=CHtml::$errorSummaryCss; $htmlOptions['style']=isset($htmlOptions['style']) ? rtrim($htmlOptions['style'],';').';display:none' : 'display:none'; $html=CHtml::tag('div',$htmlOptions,$header."\n".$footer); } $this->_summary=$htmlOptions['id']; return $html; } /** * Validates one or several models and returns the results in JSON format. * This is a helper method that simplies the way of writing AJAX validation code. * @param mixed a single model instance or an array of models. * @param boolean whether to load the data from $_POST array in this method. * If this is true, the model will be populated from $_POST[ModelClass]. * @return string the JSON representation of the validation error messages. */ public static function validate($models, $loadInput=true) { $result=array(); if(!is_array($models)) $models=array($models); foreach($models as $model) { if($loadInput && isset($_POST[get_class($model)])) $model->attributes=$_POST[get_class($model)]; $model->validate(); foreach($model->getErrors() as $attribute=>$errors) $result[CHtml::activeId($model,$attribute)]=$errors; } return CJavaScript::encode($result); } }