diff --git a/framework/assets/yii.activeForm.js b/framework/assets/yii.activeForm.js index a835be2..922bc92 100644 --- a/framework/assets/yii.activeForm.js +++ b/framework/assets/yii.activeForm.js @@ -35,14 +35,12 @@ validatingCssClass: 'validating', // the URL for performing AJAX-based validation. If not set, it will use the the form's action validationUrl: undefined, - // a callback that is called before validating every attribute - beforeValidateAttribute: undefined, - // a callback that is called after validating every attribute - afterValidateAttribute: undefined, - // a callback that is called before validating ALL attributes when submitting the form + // a callback that is called before submitting the form. The signature of the callback should be: + // function ($form) { ...return false to cancel submission...} + beforeSubmit: undefined, + // a callback that is called before validating each attribute. The signature of the callback should be: + // function ($form, attribute, messages) { ...return false to cancel the validation...} beforeValidate: undefined, - // a callback that is called after validating ALL attributes when submitting the form - afterValidate: undefined, // the GET parameter name indicating an AJAX-based validation ajaxVar: 'ajax' }; @@ -66,10 +64,6 @@ enableAjaxValidation: false, // function (attribute, value, messages), the client-side validation function. validate: undefined, - // callback called before validating the attribute - beforeValidate: undefined, - // callback called after validating an attribute. - afterValidate: undefined, // status of the input field, 0: empty, not entered before, 1: validated, 2: pending validation, 3: validating status: 0, // the value of the input @@ -122,6 +116,10 @@ }); }, + options: function() { + return this.data('yiiActiveForm').settings; + }, + submitForm: function () { var $form = this, data = $form.data('yiiActiveForm'); @@ -135,26 +133,24 @@ clearTimeout(data.settings.timer); } data.submitting = true; - if (!data.settings.beforeValidate || data.settings.beforeValidate($form)) { + if (!data.settings.beforeSubmit || data.settings.beforeSubmit($form)) { validate($form, function (messages) { var hasError = false; $.each(data.attributes, function () { hasError = updateInput($form, this, messages) || hasError; }); updateSummary($form, messages); - if (!data.settings.afterValidate || data.settings.afterValidate($form, messages, hasError)) { - if (!hasError) { - data.validated = true; - var $button = data.submitObject || $form.find(':submit:first'); - // TODO: if the submission is caused by "change" event, it will not work - if ($button.length) { - $button.click(); - } else { - // no submit button in the form - $form.submit(); - } - return; + if (!hasError) { + data.validated = true; + var $button = data.submitObject || $form.find(':submit:first'); + // TODO: if the submission is caused by "change" event, it will not work + if ($button.length) { + $button.click(); + } else { + // no submit button in the form + $form.submit(); } + return; } data.submitting = false; }, function () { @@ -235,25 +231,20 @@ if (data.submitting || $form.is(':hidden')) { return; } - if (!attribute.beforeValidate || attribute.beforeValidate($form, attribute)) { + $.each(data.attributes, function () { + if (this.status === 2) { + this.status = 3; + $form.find(this.container).addClass(data.settings.validatingCssClass); + } + }); + validate($form, function (messages) { + var hasError = false; $.each(data.attributes, function () { - if (this.status === 2) { - this.status = 3; - $form.find(this.container).addClass(data.settings.validatingCssClass); - } - }); - validate($form, function (messages) { - var hasError = false; - $.each(data.attributes, function () { - if (this.status === 2 || this.status === 3) { - hasError = updateInput($form, this, messages) || hasError; - } - }); - if (attribute.afterValidate) { - attribute.afterValidate($form, attribute, messages, hasError); + if (this.status === 2 || this.status === 3) { + hasError = updateInput($form, this, messages) || hasError; } }); - } + }); }, data.settings.validationDelay); }; @@ -271,15 +262,16 @@ $.each(data.attributes, function () { if (data.submitting || this.status === 2 || this.status === 3) { var msg = []; - if (this.validate) { - this.validate(this, getValue($form, this), msg); + if (!data.settings.beforeValidate || data.settings.beforeValidate($form, this, msg)) { + if (this.validate) { + this.validate(this, getValue($form, this), msg); + } if (msg.length) { messages[this.name] = msg; + } else if (this.enableAjaxValidation) { + needAjaxValidation = true; } } - if (this.enableAjaxValidation && !msg.length) { - needAjaxValidation = true; - } } }); diff --git a/framework/helpers/base/Html.php b/framework/helpers/base/Html.php index fd06226..6c875bb 100644 --- a/framework/helpers/base/Html.php +++ b/framework/helpers/base/Html.php @@ -896,6 +896,7 @@ class Html $attribute = static::getAttributeName($attribute); $label = isset($options['label']) ? $options['label'] : static::encode($model->getAttributeLabel($attribute)); $for = array_key_exists('for', $options) ? $options['for'] : static::getInputId($model, $attribute); + unset($options['label'], $options['for']); return static::label($label, $for, $options); } diff --git a/framework/widgets/ActiveField.php b/framework/widgets/ActiveField.php index bc7d696..55ff8e2 100644 --- a/framework/widgets/ActiveField.php +++ b/framework/widgets/ActiveField.php @@ -85,35 +85,12 @@ class ActiveField extends Component */ public $validationDelay; /** - * @var JsExpression|string a [[JsExpression]] object or a JavaScript expression string representing - * the callback that will be invoked BEFORE validating the attribute associated with this field on the client side. - * - * This callback is called after [[ActiveForm::beforeValidateAttribute]]. - * - * The signature of the callback should be like the following: - * - * ~~~ - * ~~~ - */ - public $beforeValidate; - /** - * @var JsExpression|string a [[JsExpression]] object or a JavaScript expression string representing - * the callback that will be invoked AFTER validating the attribute associated with this field on the client side. - * - * This callback is called before [[ActiveForm::afterValidateAttribute]]. - * - * The signature of the callback should be like the following: - * - * ~~~ - * ~~~ - */ - public $afterValidate; - /** * @var array the jQuery selectors for selecting the container, input and error tags. * The array keys should be "container", "input", and/or "error", and the array values * are the corresponding selectors. For example, `array('input' => '#my-input')`. * - * The selectors are used under the context of the form. + * The container selector is used under the context of the form, while the input and the error + * selectors are used under the context of the container. * * You normally do not need to set this property as the default selectors should work well for most cases. */ @@ -149,7 +126,9 @@ class ActiveField extends Component protected function getClientOptions() { - if ($this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation) { + $enableClientValidation = $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation; + $enableAjaxValidation = $this->enableAjaxValidation || $this->enableAjaxValidation === null && $this->form->enableAjaxValidation; + if ($enableClientValidation) { $attribute = Html::getAttributeName($this->attribute); $validators = array(); foreach ($this->model->getActiveValidators($attribute) as $validator) { @@ -164,19 +143,14 @@ class ActiveField extends Component } } - if ($this->enableAjaxValidation || $this->enableAjaxValidation === null && $this->form->enableAjaxValidation) { + if ($enableAjaxValidation) { $options['enableAjaxValidation'] = 1; } - if (isset($options['validate']) || isset($options['enableAjaxValidation'])) { + if ($enableClientValidation || $enableAjaxValidation) { $inputID = Html::getInputId($this->model, $this->attribute); $options['name'] = $inputID; - if ($this->model instanceof ActiveRecord && !$this->model->getIsNewRecord()) { - $option['status'] = 1; - } - $names = array( - 'enableAjaxValidation', 'validateOnChange', 'validateOnType', 'validationDelay', @@ -191,15 +165,6 @@ class ActiveField extends Component } else { $options['error'] = isset($this->errorOptions['tag']) ? $this->errorOptions['tag'] : 'span'; } - - foreach (array('beforeValidate', 'afterValidate') as $callback) { - $value = $this->$callback; - if ($value instanceof JsExpression) { - $options[$callback] = $value; - } elseif (is_string($value)) { - $options[$callback] = new JsExpression($value); - } - } return $options; } else { return array(); @@ -359,11 +324,29 @@ class ActiveField extends Component * * The rest of the options will be rendered as the attributes of the resulting tag. The values will * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * @param boolean $enclosedByLabel whether to enclose the radio within the label. + * If true, the method will still use [[template]] to layout the checkbox and the error message + * except that the radio is enclosed by the label tag. * @return string the generated radio button tag */ - public function radio($options = array()) + public function radio($options = array(), $enclosedByLabel = true) { - return $this->render(Html::activeRadio($this->model, $this->attribute, $options)); + if ($enclosedByLabel) { + $hidden = ''; + $radio = Html::activeRadio($this->model, $this->attribute, $options); + if (($pos = strpos($radio, '><')) !== false) { + $hidden = substr($radio, 0, $pos + 1); + $radio = substr($radio, $pos + 1); + } + $label = isset($this->labelOptions['label']) ? $this->labelOptions['label'] : Html::encode($this->model->getAttributeLabel($this->attribute)); + return $this->begin() . "\n" . $hidden . strtr($this->template, array( + '{input}' => Html::label("$radio $label", null, array('class' => 'radio')), + '{label}' => '', + '{error}' => $this->error(), + )) . "\n" . $this->end(); + } else { + return $this->render(Html::activeRadio($this->model, $this->attribute, $options)); + } } /** @@ -379,11 +362,29 @@ class ActiveField extends Component * * The rest of the options will be rendered as the attributes of the resulting tag. The values will * be HTML-encoded using [[encode()]]. If a value is null, the corresponding attribute will not be rendered. + * @param boolean $enclosedByLabel whether to enclose the checkbox within the label. + * If true, the method will still use [[template]] to layout the checkbox and the error message + * except that the checkbox is enclosed by the label tag. * @return string the generated checkbox tag */ - public function checkbox($options = array()) + public function checkbox($options = array(), $enclosedByLabel = true) { - return $this->render(Html::activeCheckbox($this->model, $this->attribute, $options)); + if ($enclosedByLabel) { + $hidden = ''; + $checkbox = Html::activeCheckbox($this->model, $this->attribute, $options); + if (($pos = strpos($checkbox, '><')) !== false) { + $hidden = substr($checkbox, 0, $pos + 1); + $checkbox = substr($checkbox, $pos + 1); + } + $label = isset($this->labelOptions['label']) ? $this->labelOptions['label'] : Html::encode($this->model->getAttributeLabel($this->attribute)); + return $this->begin() . "\n" . $hidden . strtr($this->template, array( + '{input}' => Html::label("$checkbox $label", null, array('class' => 'checkbox')), + '{label}' => '', + '{error}' => $this->error(), + )) . "\n" . $this->end(); + } else { + return $this->render(Html::activeCheckbox($this->model, $this->attribute, $options)); + } } /** diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php index a735982..7e284ba 100644 --- a/framework/widgets/ActiveForm.php +++ b/framework/widgets/ActiveForm.php @@ -106,51 +106,7 @@ class ActiveForm extends Widget */ public $ajaxVar = 'ajax'; /** - * @var JsExpression|string a [[JsExpression]] object or a JavaScript expression string representing - * the callback that will be invoked BEFORE validating EACH attribute on the client side. - * The signature of the callback should be like the following: - * - * ~~~ - * ~~~ - */ - public $beforeValidateAttribute; - /** - * @var JsExpression|string a [[JsExpression]] object or a JavaScript expression string representing - * the callback that will be invoked AFTER validating EACH attribute on the client side. - * The signature of the callback should be like the following: - * - * ~~~ - * ~~~ - */ - public $afterValidateAttribute; - /** - * @var JsExpression|string a [[JsExpression]] object or a JavaScript expression string representing - * the callback that will be invoked BEFORE validating ALL attributes on the client side when the validation - * is triggered by form submission (that is, [[validateOnSubmit]] is set true). - * - * This callback is called before [[beforeValidateAttribute]]. - * - * The signature of the callback should be like the following: - * - * ~~~ - * ~~~ - */ - public $beforeValidate; - /** - * @var JsExpression|string a [[JsExpression]] object or a JavaScript expression string representing - * the callback that will be invoked AFTER validating ALL attributes on the client side when the validation - * is triggered by form submission (that is, [[validateOnSubmit]] is set true). - * - * This callback is called after [[afterValidateAttribute]]. - * - * The signature of the callback should be like the following: - * - * ~~~ - * ~~~ - */ - public $afterValidate; - /** - * @var array list of attributes that need to be validated on the client side. Each element of the array + * @var array the client validation options for individual attributes. Each element of the array * represents the validation options for a particular attribute. * @internal */ @@ -201,21 +157,6 @@ class ActiveForm extends Widget if ($this->validationUrl !== null) { $options['validationUrl'] = Html::url($this->validationUrl); } - $callbacks = array( - 'beforeValidateAttribute', - 'afterValidateAttribute', - 'beforeValidate', - 'afterValidate', - ); - foreach ($callbacks as $callback) { - $value = $this->$callback; - if ($value instanceof JsExpression) { - $options[$callback] = $value; - } elseif (is_string($value)) { - $options[$callback] = new JsExpression($value); - } - } - return $options; }