From e4ee20260ded280922782bfb92b612887f764786 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 21 Sep 2017 18:03:55 +0300 Subject: [PATCH 01/14] JQuery usage removed from `yii\web\View` --- framework/web/View.php | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/framework/web/View.php b/framework/web/View.php index df007d58bdf..31d6e401e4d 100644 --- a/framework/web/View.php +++ b/framework/web/View.php @@ -68,12 +68,12 @@ class View extends \yii\base\View const POS_END = 3; /** * The location of registered JavaScript code block. - * This means the JavaScript code block will be enclosed within `jQuery(document).ready()`. + * This means the JavaScript code block will be executed when HTML document composition is ready. */ const POS_READY = 4; /** * The location of registered JavaScript code block. - * This means the JavaScript code block will be enclosed within `jQuery(window).load()`. + * This means the JavaScript code block will be executed when HTML page is completely loaded. */ const POS_LOAD = 5; /** @@ -430,23 +430,18 @@ public function registerCssFile($url, $options = [], $key = null) * * - [[POS_HEAD]]: in the head section * - [[POS_BEGIN]]: at the beginning of the body section - * - [[POS_END]]: at the end of the body section - * - [[POS_LOAD]]: enclosed within jQuery(window).load(). - * Note that by using this position, the method will automatically register the jQuery js file. - * - [[POS_READY]]: enclosed within jQuery(document).ready(). This is the default value. - * Note that by using this position, the method will automatically register the jQuery js file. + * - [[POS_END]]: at the end of the body section. This is the default value. + * - [[POS_LOAD]]: executed when HTML page is completely loaded. + * - [[POS_READY]]: executed when HTML document composition is ready. * * @param string $key the key that identifies the JS code block. If null, it will use * $js as the key. If two JS code blocks are registered with the same key, the latter * will overwrite the former. */ - public function registerJs($js, $position = self::POS_READY, $key = null) + public function registerJs($js, $position = self::POS_END, $key = null) { $key = $key ?: md5($js); $this->js[$position][$key] = $js; - if ($position === self::POS_READY || $position === self::POS_LOAD) { - JqueryAsset::register($this); - } } /** @@ -579,11 +574,11 @@ protected function renderBodyEndHtml($ajaxMode) $lines[] = Html::script(implode("\n", $this->js[self::POS_END]), ['type' => 'text/javascript']); } if (!empty($this->js[self::POS_READY])) { - $js = "jQuery(function ($) {\n" . implode("\n", $this->js[self::POS_READY]) . "\n});"; + $js = "document.addEventListener('DOMContentLoaded', function(event) {\n" . implode("\n", $this->js[self::POS_READY]) . "\n});"; $lines[] = Html::script($js, ['type' => 'text/javascript']); } if (!empty($this->js[self::POS_LOAD])) { - $js = "jQuery(function ($) {\n$(window).on('load', function () {\n" . implode("\n", $this->js[self::POS_LOAD]) . "\n});\n});"; + $js = "window.addEventListener('load', function (event) {\n" . implode("\n", $this->js[self::POS_LOAD]) . "\n});"; $lines[] = Html::script($js, ['type' => 'text/javascript']); } } From de48158ebc11ae8103b7f63291d70a2a33075dea Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 21 Sep 2017 18:19:25 +0300 Subject: [PATCH 02/14] assets moved to 'jquery' --- framework/captcha/CaptchaAsset.php | 6 ++---- framework/{ => captcha}/assets/yii.captcha.js | 0 framework/{widgets => jquery}/ActiveFormAsset.php | 7 ++----- framework/{grid => jquery}/GridViewAsset.php | 7 ++----- framework/{web => jquery}/JqueryAsset.php | 4 +++- framework/{validators => jquery}/ValidationAsset.php | 7 ++----- framework/{web => jquery}/YiiAsset.php | 11 +++++------ framework/{ => jquery}/assets/yii.activeForm.js | 0 framework/{ => jquery}/assets/yii.gridView.js | 0 framework/{ => jquery}/assets/yii.js | 0 framework/{ => jquery}/assets/yii.validation.js | 0 11 files changed, 16 insertions(+), 26 deletions(-) rename framework/{ => captcha}/assets/yii.captcha.js (100%) rename framework/{widgets => jquery}/ActiveFormAsset.php (84%) rename framework/{grid => jquery}/GridViewAsset.php (86%) rename framework/{web => jquery}/JqueryAsset.php (90%) rename framework/{validators => jquery}/ValidationAsset.php (86%) rename framework/{web => jquery}/YiiAsset.php (79%) rename framework/{ => jquery}/assets/yii.activeForm.js (100%) rename framework/{ => jquery}/assets/yii.gridView.js (100%) rename framework/{ => jquery}/assets/yii.js (100%) rename framework/{ => jquery}/assets/yii.validation.js (100%) diff --git a/framework/captcha/CaptchaAsset.php b/framework/captcha/CaptchaAsset.php index 8c0578e7528..180a48a27a5 100644 --- a/framework/captcha/CaptchaAsset.php +++ b/framework/captcha/CaptchaAsset.php @@ -8,7 +8,7 @@ namespace yii\captcha; use yii\web\AssetBundle; -use yii\web\YiiAsset; +use yii\jquery\YiiAsset; /** * This asset bundle provides the javascript files needed for the [[Captcha]] widget. @@ -21,13 +21,11 @@ class CaptchaAsset extends AssetBundle /** * @inheritdoc */ - public $sourcePath = '@yii/assets'; - + public $sourcePath = '@yii/captcha/assets'; /** * @inheritdoc */ public $js = ['yii.captcha.js',]; - /** * @inheritdoc */ diff --git a/framework/assets/yii.captcha.js b/framework/captcha/assets/yii.captcha.js similarity index 100% rename from framework/assets/yii.captcha.js rename to framework/captcha/assets/yii.captcha.js diff --git a/framework/widgets/ActiveFormAsset.php b/framework/jquery/ActiveFormAsset.php similarity index 84% rename from framework/widgets/ActiveFormAsset.php rename to framework/jquery/ActiveFormAsset.php index 79d1a383993..295b4967258 100644 --- a/framework/widgets/ActiveFormAsset.php +++ b/framework/jquery/ActiveFormAsset.php @@ -5,10 +5,9 @@ * @license http://www.yiiframework.com/license/ */ -namespace yii\widgets; +namespace yii\jquery; use yii\web\AssetBundle; -use yii\web\YiiAsset; /** * @author Qiang Xue @@ -19,13 +18,11 @@ class ActiveFormAsset extends AssetBundle /** * @inheritdoc */ - public $sourcePath = '@yii/assets'; - + public $sourcePath = '@yii/jquery/assets'; /** * @inheritdoc */ public $js = ['yii.activeForm.js']; - /** * @inheritdoc */ diff --git a/framework/grid/GridViewAsset.php b/framework/jquery/GridViewAsset.php similarity index 86% rename from framework/grid/GridViewAsset.php rename to framework/jquery/GridViewAsset.php index 71fc6851337..7363b6784b5 100644 --- a/framework/grid/GridViewAsset.php +++ b/framework/jquery/GridViewAsset.php @@ -5,10 +5,9 @@ * @license http://www.yiiframework.com/license/ */ -namespace yii\grid; +namespace yii\jquery; use yii\web\AssetBundle; -use yii\web\YiiAsset; /** * This asset bundle provides the javascript files for the [[GridView]] widget. @@ -21,13 +20,11 @@ class GridViewAsset extends AssetBundle /** * @inheritdoc */ - public $sourcePath = '@yii/assets'; - + public $sourcePath = '@yii/jquery/assets'; /** * @inheritdoc */ public $js = ['yii.gridView.js']; - /** * @inheritdoc */ diff --git a/framework/web/JqueryAsset.php b/framework/jquery/JqueryAsset.php similarity index 90% rename from framework/web/JqueryAsset.php rename to framework/jquery/JqueryAsset.php index 0ad585b706d..86af878031f 100644 --- a/framework/web/JqueryAsset.php +++ b/framework/jquery/JqueryAsset.php @@ -5,7 +5,9 @@ * @license http://www.yiiframework.com/license/ */ -namespace yii\web; +namespace yii\jquery; + +use yii\web\AssetBundle; /** * This asset bundle provides the [jQuery](http://jquery.com/) JavaScript library. diff --git a/framework/validators/ValidationAsset.php b/framework/jquery/ValidationAsset.php similarity index 86% rename from framework/validators/ValidationAsset.php rename to framework/jquery/ValidationAsset.php index 69feb43524e..7482fce11ca 100644 --- a/framework/validators/ValidationAsset.php +++ b/framework/jquery/ValidationAsset.php @@ -5,10 +5,9 @@ * @license http://www.yiiframework.com/license/ */ -namespace yii\validators; +namespace yii\jquery; use yii\web\AssetBundle; -use yii\web\YiiAsset; /** * This asset bundle provides the javascript files for client validation. @@ -21,13 +20,11 @@ class ValidationAsset extends AssetBundle /** * @inheritdoc */ - public $sourcePath = '@yii/assets'; - + public $sourcePath = '@yii/jquery/assets'; /** * @inheritdoc */ public $js = ['yii.validation.js']; - /** * @inheritdoc */ diff --git a/framework/web/YiiAsset.php b/framework/jquery/YiiAsset.php similarity index 79% rename from framework/web/YiiAsset.php rename to framework/jquery/YiiAsset.php index 275eb373864..45fee9b2205 100644 --- a/framework/web/YiiAsset.php +++ b/framework/jquery/YiiAsset.php @@ -5,7 +5,9 @@ * @license http://www.yiiframework.com/license/ */ -namespace yii\web; +namespace yii\jquery; + +use yii\web\AssetBundle; /** * This asset bundle provides the base JavaScript files for the Yii Framework. @@ -15,17 +17,14 @@ */ class YiiAsset extends AssetBundle { - /** * @inheritdoc */ - public $sourcePath = '@yii/assets'; - + public $sourcePath = '@yii/jquery/assets'; /** * @inheritdoc */ - public $js = ['yii.js',]; - + public $js = ['yii.js']; /** * @inheritdoc */ diff --git a/framework/assets/yii.activeForm.js b/framework/jquery/assets/yii.activeForm.js similarity index 100% rename from framework/assets/yii.activeForm.js rename to framework/jquery/assets/yii.activeForm.js diff --git a/framework/assets/yii.gridView.js b/framework/jquery/assets/yii.gridView.js similarity index 100% rename from framework/assets/yii.gridView.js rename to framework/jquery/assets/yii.gridView.js diff --git a/framework/assets/yii.js b/framework/jquery/assets/yii.js similarity index 100% rename from framework/assets/yii.js rename to framework/jquery/assets/yii.js diff --git a/framework/assets/yii.validation.js b/framework/jquery/assets/yii.validation.js similarity index 100% rename from framework/assets/yii.validation.js rename to framework/jquery/assets/yii.validation.js From 530d22c181945f4e3a46c8336c5b21db0b74bc0f Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 21 Sep 2017 18:27:29 +0300 Subject: [PATCH 03/14] `GridViewClientScript` extracted --- framework/grid/GridView.php | 39 ---------- framework/jquery/GridViewClientScript.php | 94 +++++++++++++++++++++++ 2 files changed, 94 insertions(+), 39 deletions(-) create mode 100644 framework/jquery/GridViewClientScript.php diff --git a/framework/grid/GridView.php b/framework/grid/GridView.php index fc7f07de0b3..8c20aabd69d 100644 --- a/framework/grid/GridView.php +++ b/framework/grid/GridView.php @@ -12,8 +12,6 @@ use yii\base\InvalidConfigException; use yii\base\Model; use yii\helpers\Html; -use yii\helpers\Json; -use yii\helpers\Url; use yii\i18n\Formatter; use yii\widgets\BaseListView; @@ -215,10 +213,6 @@ class GridView extends BaseListView * as GET parameters to this URL. */ public $filterUrl; - /** - * @var string additional jQuery selector for selecting filter input fields - */ - public $filterSelector; /** * @var string whether the filters should be displayed in the grid view. Valid values include: * @@ -278,20 +272,6 @@ public function init() $this->initColumns(); } - /** - * Runs the widget. - */ - public function run() - { - $id = $this->options['id']; - $options = Json::htmlEncode($this->getClientOptions()); - $view = $this->getView(); - GridViewAsset::register($view); - $view->registerJs("jQuery('#$id').yiiGridView($options);"); - - return parent::run(); - } - /** * Renders validator errors of filter model. * @return string the rendering result. @@ -318,25 +298,6 @@ public function renderSection($name) } } - /** - * Returns the options for the grid view JS widget. - * @return array the options - */ - protected function getClientOptions() - { - $filterUrl = isset($this->filterUrl) ? $this->filterUrl : Yii::$app->request->url; - $id = $this->filterRowOptions['id']; - $filterSelector = "#$id input, #$id select"; - if (isset($this->filterSelector)) { - $filterSelector .= ', ' . $this->filterSelector; - } - - return [ - 'filterUrl' => Url::to($filterUrl), - 'filterSelector' => $filterSelector, - ]; - } - /** * Renders the data models for the grid view. */ diff --git a/framework/jquery/GridViewClientScript.php b/framework/jquery/GridViewClientScript.php new file mode 100644 index 00000000000..c5e74b7669a --- /dev/null +++ b/framework/jquery/GridViewClientScript.php @@ -0,0 +1,94 @@ + $dataProvider, + * 'as clientScript' => [ + * 'class' => yii\jquery\GridViewClientScript::class + * ], + * 'columns' => [ + * 'id', + * 'name', + * 'created_at:datetime', + * // ... + * ], + * ]) ?> + * ``` + * + * @see \yii\grid\GridView + * @see GridViewAsset + * + * @property \yii\grid\GridView $owner the owner of this behavior. + * + * @author Qiang Xue + * @author Paul Klimov + * @since 2.1 + */ +class GridViewClientScript extends Behavior +{ + /** + * @var string additional jQuery selector for selecting filter input fields. + */ + public $filterSelector; + + + /** + * @inheritdoc + */ + public function events() + { + return [ + Widget::EVENT_BEFORE_RUN => 'beforeRun' + ]; + } + + /** + * Handles [[Widget::EVENT_BEFORE_RUN]] event, registering related client script. + * @param \yii\base\Event $event event instance. + */ + public function beforeRun($event) + { + $id = $this->owner->options['id']; + $options = Json::htmlEncode($this->getClientOptions()); + $view = $this->owner->getView(); + GridViewAsset::register($view); + $view->registerJs("jQuery('#$id').yiiGridView($options);"); + } + + /** + * Returns the options for the grid view JS widget. + * @return array the options + */ + protected function getClientOptions() + { + $filterUrl = isset($this->filterUrl) ? $this->filterUrl : Yii::$app->request->url; + $id = $this->owner->filterRowOptions['id']; + $filterSelector = "#$id input, #$id select"; + if (isset($this->filterSelector)) { + $filterSelector .= ', ' . $this->filterSelector; + } + + return [ + 'filterUrl' => Url::to($filterUrl), + 'filterSelector' => $filterSelector, + ]; + } +} \ No newline at end of file From f2cad0c641e2af4adbc58da78c2c3e5cf02cfe9e Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 21 Sep 2017 18:29:26 +0300 Subject: [PATCH 04/14] `PunycodeAsset` moved to `jquery` --- framework/{validators => jquery}/PunycodeAsset.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename framework/{validators => jquery}/PunycodeAsset.php (94%) diff --git a/framework/validators/PunycodeAsset.php b/framework/jquery/PunycodeAsset.php similarity index 94% rename from framework/validators/PunycodeAsset.php rename to framework/jquery/PunycodeAsset.php index 1f65b8d6400..ce70def3b7b 100644 --- a/framework/validators/PunycodeAsset.php +++ b/framework/jquery/PunycodeAsset.php @@ -5,7 +5,7 @@ * @license http://www.yiiframework.com/license/ */ -namespace yii\validators; +namespace yii\jquery; use yii\web\AssetBundle; From 18d2e85975f7ea979e39a419a7ca3d9592425079 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 21 Sep 2017 19:22:36 +0300 Subject: [PATCH 05/14] `ActiveFormClientScript` extracted --- framework/jquery/ActiveFormClientScript.php | 111 +++++++++ framework/jquery/GridViewClientScript.php | 2 +- framework/widgets/ActiveField.php | 100 +------- framework/widgets/ActiveFieldEvent.php | 38 +++ framework/widgets/ActiveForm.php | 105 +++----- framework/widgets/ActiveFormClientScript.php | 214 +++++++++++++++++ .../jquery/ActiveFormClientScriptTest.php | 225 ++++++++++++++++++ .../jquery/GridViewClientScriptTest.php | 78 ++++++ tests/framework/widgets/ActiveFieldTest.php | 122 +--------- tests/framework/widgets/ActiveFormTest.php | 39 +-- 10 files changed, 723 insertions(+), 311 deletions(-) create mode 100644 framework/jquery/ActiveFormClientScript.php create mode 100644 framework/widgets/ActiveFieldEvent.php create mode 100644 framework/widgets/ActiveFormClientScript.php create mode 100644 tests/framework/jquery/ActiveFormClientScriptTest.php create mode 100644 tests/framework/jquery/GridViewClientScriptTest.php diff --git a/framework/jquery/ActiveFormClientScript.php b/framework/jquery/ActiveFormClientScript.php new file mode 100644 index 00000000000..81df2eff8f5 --- /dev/null +++ b/framework/jquery/ActiveFormClientScript.php @@ -0,0 +1,111 @@ + 'example-form', + * 'as clientScript' => \yii\jquery\ActiveFormClientScript::class, + * // ... + * ]); ?> + * ... + * + * ``` + * + * @see \yii\widgets\ActiveForm + * @see \yii\widgets\ActiveFormClientScriptBehavior + * + * @author Paul Klimov + * @since 2.1 + */ +class ActiveFormClientScript extends \yii\widgets\ActiveFormClientScript +{ + /** + * @inheritdoc + */ + public $clientValidatorMap = [ + \yii\validators\BooleanValidator::class => \yii\jquery\validators\client\BooleanValidator::class, + \yii\validators\CompareValidator::class => \yii\jquery\validators\client\CompareValidator::class, + \yii\validators\EmailValidator::class => \yii\jquery\validators\client\EmailValidator::class, + \yii\validators\FilterValidator::class => \yii\jquery\validators\client\FilterValidator::class, + \yii\validators\IpValidator::class => \yii\jquery\validators\client\IpValidator::class, + \yii\validators\NumberValidator::class => \yii\jquery\validators\client\NumberValidator::class, + \yii\validators\RangeValidator::class => \yii\jquery\validators\client\RangeValidator::class, + \yii\validators\RegularExpressionValidator::class => \yii\jquery\validators\client\RegularExpressionValidator::class, + \yii\validators\RequiredValidator::class => \yii\jquery\validators\client\RequiredValidator::class, + \yii\validators\StringValidator::class => \yii\jquery\validators\client\StringValidator::class, + \yii\validators\UrlValidator::class => \yii\jquery\validators\client\UrlValidator::class, + \yii\validators\ImageValidator::class => \yii\jquery\validators\client\ImageValidator::class, + \yii\validators\FileValidator::class => \yii\jquery\validators\client\FileValidator::class, + \yii\captcha\CaptchaValidator::class => \yii\captcha\CaptchaClientValidator::class, + ]; + + + /** + * @inheritdoc + */ + protected function registerClientScript() + { + $id = $this->owner->options['id']; + $options = Json::htmlEncode($this->getClientOptions()); + $attributes = Json::htmlEncode($this->attributes); + $view = $this->owner->getView(); + ActiveFormAsset::register($view); + $view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);"); + } + + /** + * @inheritdoc + */ + protected function getFieldClientOptions($field) + { + $options = parent::getFieldClientOptions($field); + + // only get the options that are different from the default ones (set in yii.activeForm.js) + return array_diff_assoc($options, [ + 'validateOnChange' => true, + 'validateOnBlur' => true, + 'validateOnType' => false, + 'validationDelay' => 500, + 'encodeError' => true, + 'error' => '.help-block', + 'updateAriaInvalid' => true, + ]); + } + + /** + * @inheritdoc + */ + protected function getClientOptions() + { + $options = parent::getClientOptions(); + + // only get the options that are different from the default ones (set in yii.activeForm.js) + return array_diff_assoc($options, [ + 'encodeErrorSummary' => true, + 'errorSummary' => '.error-summary', + 'validateOnSubmit' => true, + 'errorCssClass' => 'has-error', + 'successCssClass' => 'has-success', + 'validatingCssClass' => 'validating', + 'ajaxParam' => 'ajax', + 'ajaxDataType' => 'json', + 'scrollToError' => true, + 'scrollToErrorOffset' => 0, + ]); + } +} \ No newline at end of file diff --git a/framework/jquery/GridViewClientScript.php b/framework/jquery/GridViewClientScript.php index c5e74b7669a..ec32c997aa0 100644 --- a/framework/jquery/GridViewClientScript.php +++ b/framework/jquery/GridViewClientScript.php @@ -79,7 +79,7 @@ public function beforeRun($event) */ protected function getClientOptions() { - $filterUrl = isset($this->filterUrl) ? $this->filterUrl : Yii::$app->request->url; + $filterUrl = isset($this->owner->filterUrl) ? $this->owner->filterUrl : Yii::$app->request->url; $id = $this->owner->filterRowOptions['id']; $filterSelector = "#$id input, #$id select"; if (isset($this->filterSelector)) { diff --git a/framework/widgets/ActiveField.php b/framework/widgets/ActiveField.php index fa7f7757159..01967704dfb 100644 --- a/framework/widgets/ActiveField.php +++ b/framework/widgets/ActiveField.php @@ -13,7 +13,6 @@ use yii\base\Model; use yii\helpers\ArrayHelper; use yii\helpers\Html; -use yii\web\JsExpression; /** * ActiveField represents a form input field within an [[ActiveForm]]. @@ -226,12 +225,7 @@ public function render($content = null) */ public function begin() { - if ($this->form->enableClientScript) { - $clientOptions = $this->getClientOptions(); - if (!empty($clientOptions)) { - $this->form->attributes[] = $clientOptions; - } - } + $this->form->beforeFieldRender($this); $inputID = $this->getInputId(); $attribute = Html::getAttributeName($this->attribute); @@ -256,7 +250,9 @@ public function begin() */ public function end() { - return Html::endTag(ArrayHelper::keyExists('tag', $this->options) ? $this->options['tag'] : 'div'); + $html = Html::endTag(ArrayHelper::keyExists('tag', $this->options) ? $this->options['tag'] : 'div'); + $this->form->afterFieldRender($this); + return $html; } /** @@ -743,98 +739,22 @@ protected function adjustLabelFor($options) } } - /** - * Returns the JS options for the field. - * @return array the JS options. - */ - protected function getClientOptions() - { - $attribute = Html::getAttributeName($this->attribute); - if (!in_array($attribute, $this->model->activeAttributes(), true)) { - return []; - } - - $clientValidation = $this->isClientValidationEnabled(); - $ajaxValidation = $this->isAjaxValidationEnabled(); - - if ($clientValidation) { - $validators = []; - foreach ($this->model->getActiveValidators($attribute) as $validator) { - /* @var $validator \yii\validators\Validator */ - $js = $validator->clientValidateAttribute($this->model, $attribute, $this->form->getView()); - if ($validator->enableClientValidation && $js != '') { - if ($validator->whenClient !== null) { - $js = "if (({$validator->whenClient})(attribute, value)) { $js }"; - } - $validators[] = $js; - } - } - } - - if (!$ajaxValidation && (!$clientValidation || empty($validators))) { - return []; - } - - $options = []; - - $inputID = $this->getInputId(); - $options['id'] = Html::getInputId($this->model, $this->attribute); - $options['name'] = $this->attribute; - - $options['container'] = isset($this->selectors['container']) ? $this->selectors['container'] : ".field-$inputID"; - $options['input'] = isset($this->selectors['input']) ? $this->selectors['input'] : "#$inputID"; - if (isset($this->selectors['error'])) { - $options['error'] = $this->selectors['error']; - } elseif (isset($this->errorOptions['class'])) { - $options['error'] = '.' . implode('.', preg_split('/\s+/', $this->errorOptions['class'], -1, PREG_SPLIT_NO_EMPTY)); - } else { - $options['error'] = isset($this->errorOptions['tag']) ? $this->errorOptions['tag'] : 'span'; - } - - $options['encodeError'] = !isset($this->errorOptions['encode']) || $this->errorOptions['encode']; - if ($ajaxValidation) { - $options['enableAjaxValidation'] = true; - } - foreach (['validateOnChange', 'validateOnBlur', 'validateOnType', 'validationDelay'] as $name) { - $options[$name] = $this->$name === null ? $this->form->$name : $this->$name; - } - - if (!empty($validators)) { - $options['validate'] = new JsExpression('function (attribute, value, messages, deferred, $form) {' . implode('', $validators) . '}'); - } - - if ($this->addAriaAttributes === false) { - $options['updateAriaInvalid'] = false; - } - - // only get the options that are different from the default ones (set in yii.activeForm.js) - return array_diff_assoc($options, [ - 'validateOnChange' => true, - 'validateOnBlur' => true, - 'validateOnType' => false, - 'validationDelay' => 500, - 'encodeError' => true, - 'error' => '.help-block', - 'updateAriaInvalid' => true, - ]); - } - /** * Checks if client validation enabled for the field. - * @return bool + * @return bool whether client validation enabled for the field. * @since 2.0.11 */ - protected function isClientValidationEnabled() + public function isClientValidationEnabled() { return $this->enableClientValidation || $this->enableClientValidation === null && $this->form->enableClientValidation; } /** - * Checks if ajax validation enabled for the field. - * @return bool + * Checks if AJAX validation enabled for the field. + * @return bool whether AJAX validation enabled for the field. * @since 2.0.11 */ - protected function isAjaxValidationEnabled() + public function isAjaxValidationEnabled() { return $this->enableAjaxValidation || $this->enableAjaxValidation === null && $this->form->enableAjaxValidation; } @@ -844,7 +764,7 @@ protected function isAjaxValidationEnabled() * @return string the input id. * @since 2.0.7 */ - protected function getInputId() + public function getInputId() { return $this->_inputId ?: Html::getInputId($this->model, $this->attribute); } diff --git a/framework/widgets/ActiveFieldEvent.php b/framework/widgets/ActiveFieldEvent.php new file mode 100644 index 00000000000..f93930332d7 --- /dev/null +++ b/framework/widgets/ActiveFieldEvent.php @@ -0,0 +1,38 @@ + + * @since 2.1 + */ +class ActiveFieldEvent extends Event +{ + /** + * @var ActiveField related active field instance + */ + public $field; + + + /** + * Constructor. + * @param ActiveField $field the active field associated with this event. + * @param array $config name-value pairs that will be used to initialize the object properties + */ + public function __construct($field, $config = []) + { + $this->field = $field; + parent::__construct($config); + } +} \ No newline at end of file diff --git a/framework/widgets/ActiveForm.php b/framework/widgets/ActiveForm.php index d1ac4d5f0ea..07deea2a781 100644 --- a/framework/widgets/ActiveForm.php +++ b/framework/widgets/ActiveForm.php @@ -26,6 +26,17 @@ */ class ActiveForm extends Widget { + /** + * @event ActiveFieldEvent an event raised right before rendering an ActiveField. + * @since 2.1.0 + */ + const EVENT_BEFORE_FIELD_RENDER = 'beforeFieldRender'; + /** + * @event ActionEvent an event raised right after rendering an ActiveField. + * @since 2.1.0 + */ + const EVENT_AFTER_FIELD_RENDER = 'afterFieldRender'; + /** * @var array|string the form action URL. This parameter will be processed by [[\yii\helpers\Url::to()]]. * @see method for specifying the HTTP method for this form. @@ -106,14 +117,6 @@ class ActiveForm extends Widget * If [[ActiveField::enableAjaxValidation]] is set, its value will take precedence for that input field. */ public $enableAjaxValidation = false; - /** - * @var bool whether to hook up `yii.activeForm` JavaScript plugin. - * This property must be set `true` if you want to support client validation and/or AJAX validation, or if you - * want to take advantage of the `yii.activeForm` plugin. When this is `false`, the form will not generate - * any JavaScript. - * @see registerClientScript - */ - public $enableClientScript = true; /** * @var array|string the URL for performing AJAX-based validation. This property will be processed by * [[Url::to()]]. Please refer to [[Url::to()]] for more details on how to configure this property. @@ -164,12 +167,6 @@ class ActiveForm extends Widget * @since 2.0.11 */ public $scrollToErrorOffset = 0; - /** - * @var array the client validation options for individual attributes. Each element of the array - * represents the validation options for a particular attribute. - * @internal - */ - public $attributes = []; /** * @var ActiveField[] the ActiveField objects that are currently active @@ -204,67 +201,11 @@ public function run() $content = ob_get_clean(); $html = Html::beginForm($this->action, $this->method, $this->options); $html .= $content; - - if ($this->enableClientScript) { - $this->registerClientScript(); - } - $html .= Html::endForm(); return $html; } - /** - * This registers the necessary JavaScript code. - * @since 2.0.12 - */ - public function registerClientScript() - { - $id = $this->options['id']; - $options = Json::htmlEncode($this->getClientOptions()); - $attributes = Json::htmlEncode($this->attributes); - $view = $this->getView(); - ActiveFormAsset::register($view); - $view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);"); - } - - /** - * Returns the options for the form JS widget. - * @return array the options. - */ - protected function getClientOptions() - { - $options = [ - 'encodeErrorSummary' => $this->encodeErrorSummary, - 'errorSummary' => '.' . implode('.', preg_split('/\s+/', $this->errorSummaryCssClass, -1, PREG_SPLIT_NO_EMPTY)), - 'validateOnSubmit' => $this->validateOnSubmit, - 'errorCssClass' => $this->errorCssClass, - 'successCssClass' => $this->successCssClass, - 'validatingCssClass' => $this->validatingCssClass, - 'ajaxParam' => $this->ajaxParam, - 'ajaxDataType' => $this->ajaxDataType, - 'scrollToError' => $this->scrollToError, - 'scrollToErrorOffset' => $this->scrollToErrorOffset, - ]; - if ($this->validationUrl !== null) { - $options['validationUrl'] = Url::to($this->validationUrl); - } - - // only get the options that are different from the default ones (set in yii.activeForm.js) - return array_diff_assoc($options, [ - 'encodeErrorSummary' => true, - 'errorSummary' => '.error-summary', - 'validateOnSubmit' => true, - 'errorCssClass' => 'has-error', - 'successCssClass' => 'has-success', - 'validatingCssClass' => 'validating', - 'ajaxParam' => 'ajax', - 'ajaxDataType' => 'json', - 'scrollToError' => true, - 'scrollToErrorOffset' => 0, - ]); - } - /** * Generates a summary of the validation errors. * If there is no validation error, an empty error summary markup will still be generated, but it will be hidden. @@ -440,4 +381,28 @@ public static function validateMultiple($models, $attributes = null) return $result; } + + /** + * This method is invoked right before an ActiveField is rendered. + * The method will trigger the [[EVENT_BEFORE_FIELD_RENDER]] event. + * @param ActiveField $field active field to be rendered. + * @since 2.1.0 + */ + public function beforeFieldRender($field) + { + $event = new ActiveFieldEvent($field); + $this->trigger(self::EVENT_BEFORE_FIELD_RENDER, $event); + } + + /** + * This method is invoked right after an ActiveField is rendered. + * The method will trigger the [[EVENT_AFTER_FIELD_RENDER]] event. + * @param ActiveField $field active field to be rendered. + * @since 2.1.0 + */ + public function afterFieldRender($field) + { + $event = new ActiveFieldEvent($field); + $this->trigger(self::EVENT_AFTER_FIELD_RENDER, $event); + } } diff --git a/framework/widgets/ActiveFormClientScript.php b/framework/widgets/ActiveFormClientScript.php new file mode 100644 index 00000000000..12b767e8b13 --- /dev/null +++ b/framework/widgets/ActiveFormClientScript.php @@ -0,0 +1,214 @@ + + * @since 2.1 + */ +abstract class ActiveFormClientScript extends Behavior +{ + /** + * @var array client validator class map in format: [server-side-validator-class => client-side-validator]. + * Client side validator should be specified as an instance of [[\yii\validators\client\ClientValidator]] or + * its DI compatible configuration. + * + * Class map respects validators inheritance, e.g. if you specify map for `ParentValidator` it will be used for + * `ChildValidator` in case it extends `ParentValidator`. In case maps for both `ParentValidator` and `ChildValidator` + * are specified the first value will take precedence. + * + * For example: + * + * ```php + * [ + * \yii\validators\BooleanValidator::class => \yii\jquery\validators\client\BooleanValidator::class, + * \yii\validators\ImageValidator::class => \yii\jquery\validators\client\ImageValidator::class, + * \yii\validators\FileValidator::class => \yii\jquery\validators\client\FileValidator::class, + * ] + * ``` + */ + public $clientValidatorMap = []; + + /** + * @var array the client validation options for individual attributes. Each element of the array + * represents the validation options for a particular attribute. + */ + protected $attributes = []; + + + /** + * @inheritdoc + */ + public function events() + { + return [ + Widget::EVENT_AFTER_RUN => 'afterRun', + ActiveForm::EVENT_BEFORE_FIELD_RENDER => 'beforeFieldRender', + ]; + } + + /** + * Handles [[Widget::EVENT_AFTER_RUN]] event, registering related client script. + * @param \yii\base\Event $event event instance. + */ + public function afterRun($event) + { + $this->registerClientScript(); + } + + /** + * Handles [[ActiveForm::EVENT_BEFORE_FIELD_RENDER]] event. + * @param ActiveFieldEvent $event event instance. + */ + public function beforeFieldRender($event) + { + $clientOptions = $this->getFieldClientOptions($event->field); + if (!empty($clientOptions)) { + $this->attributes[] = $clientOptions; + } + } + + /** + * Registers underlying client script including JavaScript code, which supports form validation. + */ + abstract protected function registerClientScript(); + + /** + * Returns the JS options for the field. + * @param ActiveField $field active field instance. + * @return array the JS options. + */ + protected function getFieldClientOptions($field) + { + $attribute = Html::getAttributeName($field->attribute); + if (!in_array($attribute, $field->model->activeAttributes(), true)) { + return []; + } + + $clientValidation = $field->isClientValidationEnabled(); + $ajaxValidation = $field->isAjaxValidationEnabled(); + + if ($clientValidation) { + $validators = []; + foreach ($field->model->getActiveValidators($attribute) as $validator) { + /* @var $validator \yii\validators\Validator */ + if (!$validator->enableClientValidation) { + continue; + } + + $js = $validator->clientValidateAttribute($field->model, $attribute, $field->form->getView()); + if ($js == '') { + $js = $this->buildClientValidator($validator, $field->model, $attribute, $field->form->getView()); + } + + if ($js != '') { + if ($validator->whenClient !== null) { + $js = "if (({$validator->whenClient})(attribute, value)) { $js }"; + } + $validators[] = $js; + } + } + } + + if (!$ajaxValidation && (!$clientValidation || empty($validators))) { + return []; + } + + $options = []; + + $inputID = $field->getInputId(); + $options['id'] = Html::getInputId($field->model, $field->attribute); + $options['name'] = $field->attribute; + + $options['container'] = isset($field->selectors['container']) ? $field->selectors['container'] : ".field-$inputID"; + $options['input'] = isset($field->selectors['input']) ? $field->selectors['input'] : "#$inputID"; + if (isset($field->selectors['error'])) { + $options['error'] = $field->selectors['error']; + } elseif (isset($field->errorOptions['class'])) { + $options['error'] = '.' . implode('.', preg_split('/\s+/', $field->errorOptions['class'], -1, PREG_SPLIT_NO_EMPTY)); + } else { + $options['error'] = isset($field->errorOptions['tag']) ? $field->errorOptions['tag'] : 'span'; + } + + $options['encodeError'] = !isset($field->errorOptions['encode']) || $field->errorOptions['encode']; + if ($ajaxValidation) { + $options['enableAjaxValidation'] = true; + } + foreach (['validateOnChange', 'validateOnBlur', 'validateOnType', 'validationDelay'] as $name) { + $options[$name] = $field->$name === null ? $field->form->$name : $field->$name; + } + + if (!empty($validators)) { + $options['validate'] = new JsExpression("function (attribute, value, messages, deferred, \$form) {" . implode('', $validators) . '}'); + } + + if ($field->addAriaAttributes === false) { + $options['updateAriaInvalid'] = false; + } + + return $options; + } + + /** + * Returns the options for the form JS widget. + * @return array the options. + */ + protected function getClientOptions() + { + $options = [ + 'encodeErrorSummary' => $this->owner->encodeErrorSummary, + 'errorSummary' => '.' . implode('.', preg_split('/\s+/', $this->owner->errorSummaryCssClass, -1, PREG_SPLIT_NO_EMPTY)), + 'validateOnSubmit' => $this->owner->validateOnSubmit, + 'errorCssClass' => $this->owner->errorCssClass, + 'successCssClass' => $this->owner->successCssClass, + 'validatingCssClass' => $this->owner->validatingCssClass, + 'ajaxParam' => $this->owner->ajaxParam, + 'ajaxDataType' => $this->owner->ajaxDataType, + 'scrollToError' => $this->owner->scrollToError, + 'scrollToErrorOffset' => $this->owner->scrollToErrorOffset, + ]; + if ($this->owner->validationUrl !== null) { + $options['validationUrl'] = Url::to($this->owner->validationUrl); + } + + return $options; + } + + /** + * Builds the JavaScript needed for performing client-side validation for given validator. + * @param \yii\validators\Validator $validator validator to be built. + * @param \yii\base\Model $model the data model being validated. + * @param string $attribute the name of the attribute to be validated. + * @param \yii\web\View $view the view object that is going to be used to render views or view files + * containing a model form with validator applied. + * @return string|null client-side validation JavaScript code, `null` - if given validator is not supported. + */ + protected function buildClientValidator($validator, $model, $attribute, $view) + { + foreach ($this->clientValidatorMap as $serverSideValidatorClass => $clientSideValidator) { + if ($validator instanceof $serverSideValidatorClass) { + /* @var $clientValidator \yii\validators\client\ClientValidator */ + $clientValidator = Yii::createObject($clientSideValidator); + return $clientValidator->build($validator, $model, $attribute, $view); + } + } + return null; + } +} \ No newline at end of file diff --git a/tests/framework/jquery/ActiveFormClientScriptTest.php b/tests/framework/jquery/ActiveFormClientScriptTest.php new file mode 100644 index 00000000000..34323482629 --- /dev/null +++ b/tests/framework/jquery/ActiveFormClientScriptTest.php @@ -0,0 +1,225 @@ +mockWebApplication([ + 'components' => [ + 'assetManager' => [ + 'basePath' => '@testWebRoot/assets', + 'baseUrl' => '@testWeb/assets', + 'bundles' => [ + ActiveFormAsset::class => [ + 'sourcePath' => null, + 'basePath' => null, + 'baseUrl' => 'http://example.com/assets', + 'depends' => [], + ], + ], + ], + ], + ]); + + Yii::setAlias('@testWeb', '/'); + Yii::setAlias('@testWebRoot', '@yiiunit/data/web'); + + $this->helperModel = new DynamicModel(['attributeName']); + ob_start(); + $this->helperForm = ActiveForm::begin([ + 'action' => '/something', + 'as clientScript' => ActiveFormClientScript::class + ]); + ActiveForm::end(); + ob_end_clean(); + + $this->activeField = new ActiveField(); + $this->activeField->form = $this->helperForm; + $this->activeField->model = $this->helperModel; + $this->activeField->attribute = $this->attributeName; + } + + /** + * @return array client options of current [[activeField]] instance. + */ + protected function getActiveFieldClientOptions() + { + // invoke protected method : + return $this->invokeMethod($this->activeField->form->getBehavior('clientScript'), 'getFieldClientOptions', [$this->activeField]); + } + + // Tests : + + public function testRegisterClientScript() + { + $model = new DynamicModel(['name']); + $model->addRule(['name'], 'required'); + + $mockedView = $this->createMock(View::class); + $mockedView + ->expects($this->once()) + ->method('registerJs') + ->with($this->matches("jQuery('#test-form').yiiActiveForm([], {\"validateOnSubmit\":false});")); + $mockedView + ->expects($this->once()) + ->method('registerAssetBundle') + ->willReturn(true); + + ob_start(); + ob_implicit_flush(false); + + $form = ActiveForm::begin([ + 'id' => 'test-form', + 'action' => '/something', + 'view' => $mockedView, + 'validateOnSubmit' => false, + 'as clientScript' => ActiveFormClientScript::class, + ]); + $form->field($model, 'name'); + $form::end(); + + ob_get_clean(); + } + + public function testGetClientOptionsWithActiveAttributeInScenario() + { + $this->activeField->model->addRule($this->attributeName, TestValidator::class); + $this->activeField->form->enableClientValidation = false; + + // expected empty + $actualValue = $this->getActiveFieldClientOptions(); + $this->assertEmpty($actualValue); + + } + + public function testGetClientOptionsClientValidation() + { + $this->activeField->model->addRule($this->attributeName, TestValidator::class); + $this->activeField->enableClientValidation = true; + $actualValue = $this->getActiveFieldClientOptions(); + $expectedJsExpression = "function (attribute, value, messages, deferred, \$form) {return true;}"; + $this->assertEquals($expectedJsExpression, $actualValue['validate']); + + $this->assertTrue(!isset($actualValue['validateOnChange'])); + $this->assertTrue(!isset($actualValue['validateOnBlur'])); + $this->assertTrue(!isset($actualValue['validateOnType'])); + $this->assertTrue(!isset($actualValue['validationDelay'])); + $this->assertTrue(!isset($actualValue['enableAjaxValidation'])); + + $this->activeField->validateOnChange = $expectedValidateOnChange = false; + $this->activeField->validateOnBlur = $expectedValidateOnBlur = false; + $this->activeField->validateOnType = $expectedValidateOnType = true; + $this->activeField->validationDelay = $expectedValidationDelay = 100; + $this->activeField->enableAjaxValidation = $expectedEnableAjaxValidation = true; + + $actualValue = $this->getActiveFieldClientOptions(); + + $this->assertTrue($expectedValidateOnChange === $actualValue['validateOnChange']); + $this->assertTrue($expectedValidateOnBlur === $actualValue['validateOnBlur']); + $this->assertTrue($expectedValidateOnType === $actualValue['validateOnType']); + $this->assertTrue($expectedValidationDelay === $actualValue['validationDelay']); + $this->assertTrue($expectedEnableAjaxValidation === $actualValue['enableAjaxValidation']); + } + + public function testGetClientOptionsValidatorWhenClientSet() + { + $this->activeField->enableAjaxValidation = true; + $this->activeField->model->addRule($this->attributeName, TestValidator::class); + + foreach($this->activeField->model->validators as $validator) { + $validator->whenClient = "function (attribute, value) { return 'yii2' == 'yii2'; }"; // js + } + + $actualValue = $this->getActiveFieldClientOptions(); + $expectedJsExpression = "function (attribute, value, messages, deferred, \$form) {if ((function (attribute, value) " + . "{ return 'yii2' == 'yii2'; })(attribute, value)) { return true; }}"; + + $this->assertEquals($expectedJsExpression, $actualValue['validate']->expression); + } + + /** + * @link https://github.com/yiisoft/yii2/issues/7627 + */ + public function testGetClientOptionsWithCustomInputId() + { + $this->activeField->model->addRule($this->attributeName, TestValidator::class); + $this->activeField->inputOptions['id'] = 'custom-input-id'; + $this->activeField->textInput(); + $actualValue = $this->getActiveFieldClientOptions(); + + $this->assertArraySubset([ + 'id' => 'dynamicmodel-attributename', + 'name' => $this->attributeName, + 'container' => '.field-custom-input-id', + 'input' => '#custom-input-id', + ], $actualValue); + + $this->activeField->textInput(['id' => 'custom-textinput-id']); + $actualValue = $this->getActiveFieldClientOptions(); + + $this->assertArraySubset([ + 'id' => 'dynamicmodel-attributename', + 'name' => $this->attributeName, + 'container' => '.field-custom-textinput-id', + 'input' => '#custom-textinput-id', + ], $actualValue); + } +} + +class TestValidator extends \yii\validators\Validator +{ + public function clientValidateAttribute($object, $attribute, $view) + { + return "return true;"; + } + + public function setWhenClient($js) + { + $this->whenClient = $js; + } +} \ No newline at end of file diff --git a/tests/framework/jquery/GridViewClientScriptTest.php b/tests/framework/jquery/GridViewClientScriptTest.php new file mode 100644 index 00000000000..1a2ec573f52 --- /dev/null +++ b/tests/framework/jquery/GridViewClientScriptTest.php @@ -0,0 +1,78 @@ +mockWebApplication([ + 'components' => [ + 'assetManager' => [ + 'basePath' => '@testWebRoot/assets', + 'baseUrl' => '@testWeb/assets', + 'bundles' => [ + GridViewAsset::class => [ + 'sourcePath' => null, + 'basePath' => null, + 'baseUrl' => 'http://example.com/assets', + 'depends' => [], + ], + ], + ], + ], + ]); + + Yii::setAlias('@testWeb', '/'); + Yii::setAlias('@testWebRoot', '@yiiunit/data/web'); + } + + public function testRegisterClientScript() + { + $row = ['id' => 1, 'name' => 'Name1', 'value' => 'Value1', 'description' => 'Description1',]; + + GridView::widget([ + 'id' => 'test-grid', + 'dataProvider' => new ArrayDataProvider( + [ + 'allModels' => [ + $row, + ], + ] + ), + 'filterUrl' => 'http://example.com/filter', + 'as clientScript' => [ + 'class' => GridViewClientScript::class + ], + ]); + + $this->assertTrue(Yii::$app->assetManager->bundles[GridViewAsset::class] instanceof GridViewAsset); + $this->assertNotEmpty(Yii::$app->view->js[View::POS_END]); + $js = reset(Yii::$app->view->js[View::POS_END]); + $this->assertContains("jQuery('#test-grid').yiiGridView(", $js); + } +} \ No newline at end of file diff --git a/tests/framework/widgets/ActiveFieldTest.php b/tests/framework/widgets/ActiveFieldTest.php index f42336ade19..a126278d1b0 100644 --- a/tests/framework/widgets/ActiveFieldTest.php +++ b/tests/framework/widgets/ActiveFieldTest.php @@ -34,8 +34,15 @@ class ActiveFieldTest extends \yiiunit\TestCase * @var ActiveForm */ private $helperForm; + /** + * @var string + */ private $attributeName = 'attributeName'; + + /** + * {@inheritdoc} + */ protected function setUp() { parent::setUp(); @@ -50,7 +57,7 @@ protected function setUp() $this->helperModel = new ActiveFieldTestModel(['attributeName']); ob_start(); - $this->helperForm = ActiveForm::begin(['action' => '/something', 'enableClientScript' => false]); + $this->helperForm = ActiveForm::begin(['action' => '/something']); ActiveForm::end(); ob_end_clean(); @@ -343,76 +350,6 @@ public function testListBox() $this->assertEqualsWithoutLE($expectedValue, $this->activeField->parts['{input}']); } - public function testGetClientOptionsReturnEmpty() - { - // setup: we want the real deal here! - $this->activeField->setClientOptionsEmpty(false); - - // expected empty - $actualValue = $this->activeField->getClientOptions(); - $this->assertEmpty($actualValue); - } - - public function testGetClientOptionsWithActiveAttributeInScenario() - { - $this->activeField->setClientOptionsEmpty(false); - - $this->activeField->model->addRule($this->attributeName, 'yiiunit\framework\widgets\TestValidator'); - $this->activeField->form->enableClientValidation = false; - - // expected empty - $actualValue = $this->activeField->getClientOptions(); - $this->assertEmpty($actualValue); - } - - public function testGetClientOptionsClientValidation() - { - $this->activeField->setClientOptionsEmpty(false); - - $this->activeField->model->addRule($this->attributeName, 'yiiunit\framework\widgets\TestValidator'); - $this->activeField->enableClientValidation = true; - $actualValue = $this->activeField->getClientOptions(); - $expectedJsExpression = 'function (attribute, value, messages, deferred, $form) {return true;}'; - $this->assertEquals($expectedJsExpression, $actualValue['validate']); - - $this->assertNotTrue(isset($actualValue['validateOnChange'])); - $this->assertNotTrue(isset($actualValue['validateOnBlur'])); - $this->assertNotTrue(isset($actualValue['validateOnType'])); - $this->assertNotTrue(isset($actualValue['validationDelay'])); - $this->assertNotTrue(isset($actualValue['enableAjaxValidation'])); - - $this->activeField->validateOnChange = $expectedValidateOnChange = false; - $this->activeField->validateOnBlur = $expectedValidateOnBlur = false; - $this->activeField->validateOnType = $expectedValidateOnType = true; - $this->activeField->validationDelay = $expectedValidationDelay = 100; - $this->activeField->enableAjaxValidation = $expectedEnableAjaxValidation = true; - - $actualValue = $this->activeField->getClientOptions(); - - $this->assertSame($expectedValidateOnChange, $actualValue['validateOnChange']); - $this->assertSame($expectedValidateOnBlur, $actualValue['validateOnBlur']); - $this->assertSame($expectedValidateOnType, $actualValue['validateOnType']); - $this->assertSame($expectedValidationDelay, $actualValue['validationDelay']); - $this->assertSame($expectedEnableAjaxValidation, $actualValue['enableAjaxValidation']); - } - - public function testGetClientOptionsValidatorWhenClientSet() - { - $this->activeField->setClientOptionsEmpty(false); - $this->activeField->enableAjaxValidation = true; - $this->activeField->model->addRule($this->attributeName, 'yiiunit\framework\widgets\TestValidator'); - - foreach ($this->activeField->model->validators as $validator) { - $validator->whenClient = "function (attribute, value) { return 'yii2' == 'yii2'; }"; // js - } - - $actualValue = $this->activeField->getClientOptions(); - $expectedJsExpression = 'function (attribute, value, messages, deferred, $form) {if ((function (attribute, value) ' - . "{ return 'yii2' == 'yii2'; })(attribute, value)) { return true; }}"; - - $this->assertEquals($expectedJsExpression, $actualValue['validate']->expression); - } - /** * @see https://github.com/yiisoft/yii2/issues/8779 */ @@ -422,36 +359,6 @@ public function testEnctype() $this->assertEquals('multipart/form-data', $this->activeField->form->options['enctype']); } - /** - * @link https://github.com/yiisoft/yii2/issues/7627 - */ - public function testGetClientOptionsWithCustomInputId() - { - $this->activeField->setClientOptionsEmpty(false); - - $this->activeField->model->addRule($this->attributeName, 'yiiunit\framework\widgets\TestValidator'); - $this->activeField->inputOptions['id'] = 'custom-input-id'; - $this->activeField->textInput(); - $actualValue = $this->activeField->getClientOptions(); - - $this->assertArraySubset([ - 'id' => 'activefieldtestmodel-attributename', - 'name' => $this->attributeName, - 'container' => '.field-custom-input-id', - 'input' => '#custom-input-id', - ], $actualValue); - - $this->activeField->textInput(['id' => 'custom-textinput-id']); - $actualValue = $this->activeField->getClientOptions(); - - $this->assertArraySubset([ - 'id' => 'activefieldtestmodel-attributename', - 'name' => $this->attributeName, - 'container' => '.field-custom-textinput-id', - 'input' => '#custom-textinput-id', - ], $actualValue); - } - public function testAriaAttributes() { $this->activeField->addAriaAttributes = true; @@ -612,19 +519,6 @@ public function getClientOptions() } } -class TestValidator extends \yii\validators\Validator -{ - public function clientValidateAttribute($object, $attribute, $view) - { - return 'return true;'; - } - - public function setWhenClient($js) - { - $this->whenClient = $js; - } -} - class TestInputWidget extends InputWidget { /** diff --git a/tests/framework/widgets/ActiveFormTest.php b/tests/framework/widgets/ActiveFormTest.php index a3d746c4b5d..8f8a017a26e 100644 --- a/tests/framework/widgets/ActiveFormTest.php +++ b/tests/framework/widgets/ActiveFormTest.php @@ -29,7 +29,7 @@ public function testBooleanAttributes() $model = new DynamicModel(['name']); ob_start(); - $form = ActiveForm::begin(['action' => '/something', 'enableClientScript' => false]); + $form = ActiveForm::begin(['action' => '/something']); ActiveForm::end(); ob_end_clean(); @@ -63,7 +63,7 @@ public function testIssue5356() $model = new DynamicModel(['categories']); $model->categories = 1; ob_start(); - $form = ActiveForm::begin(['action' => '/something', 'enableClientScript' => false]); + $form = ActiveForm::begin(['action' => '/something']); ActiveForm::end(); ob_end_clean(); @@ -87,7 +87,7 @@ public function testOutputBuffering() $model = new DynamicModel(['name']); - $form = ActiveForm::begin(['id' => 'someform', 'action' => '/someform', 'enableClientScript' => false]); + $form = ActiveForm::begin(['id' => 'someform', 'action' => '/someform']); echo "\n" . $form->field($model, 'name') . "\n"; ActiveForm::end(); @@ -107,37 +107,4 @@ public function testOutputBuffering() HTML , $content); } - - public function testRegisterClientScript() - { - $this->mockWebApplication(); - $_SERVER['REQUEST_URI'] = 'http://example.com/'; - - $model = new DynamicModel(['name']); - $model->addRule(['name'], 'required'); - - $mockedView = $this->createMock(View::class); - $mockedView - ->expects($this->once()) - ->method('registerJs') - ->with($this->matches("jQuery('#w0').yiiActiveForm([], {\"validateOnSubmit\":false});")); - $mockedView - ->expects($this->once()) - ->method('registerAssetBundle') - ->willReturn(true); - - Widget::$counter = 0; - ob_start(); - ob_implicit_flush(false); - - $form = ActiveForm::begin(['view' => $mockedView, 'validateOnSubmit' => false]); - $form->field($model, 'name'); - $form::end(); - - // Disable clientScript will not call `View->registerJs()` - $form = ActiveForm::begin(['view' => $mockedView, 'enableClientScript' => false]); - $form->field($model, 'name'); - $form::end(); - ob_get_clean(); - } } From dc0188438ad89a8155f99edef0d4e47da3e83564 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 21 Sep 2017 19:51:59 +0300 Subject: [PATCH 06/14] client validators extracted --- framework/jquery/ActiveFormClientScript.php | 2 +- framework/jquery/GridViewClientScript.php | 2 +- .../validators/client/BooleanValidator.php | 61 ++++++++++ .../client/CaptchaClientValidator.php | 60 ++++++++++ .../validators/client/CompareValidator.php | 72 +++++++++++ .../validators/client/EmailValidator.php | 65 ++++++++++ .../validators/client/FileValidator.php | 112 ++++++++++++++++++ .../validators/client/FilterValidator.php | 50 ++++++++ .../validators/client/ImageValidator.php | 86 ++++++++++++++ .../jquery/validators/client/IpValidator.php | 76 ++++++++++++ .../validators/client/NumberValidator.php | 78 ++++++++++++ .../validators/client/RangeValidator.php | 67 +++++++++++ .../client/RegularExpressionValidator.php | 61 ++++++++++ .../validators/client/RequiredValidator.php | 62 ++++++++++ .../validators/client/StringValidator.php | 78 ++++++++++++ .../jquery/validators/client/UrlValidator.php | 72 +++++++++++ framework/validators/BooleanValidator.php | 35 ------ framework/validators/CompareValidator.php | 46 ------- framework/validators/EmailValidator.php | 37 ------ framework/validators/FileValidator.php | 86 +------------- framework/validators/FilterValidator.php | 28 ----- framework/validators/ImageValidator.php | 60 ---------- framework/validators/IpValidator.php | 51 +------- framework/validators/NumberValidator.php | 52 -------- framework/validators/RangeValidator.php | 41 ------- .../validators/RegularExpressionValidator.php | 35 ------ framework/validators/RequiredValidator.php | 36 ------ framework/validators/StringValidator.php | 52 -------- framework/validators/UrlValidator.php | 44 ------- framework/validators/Validator.php | 20 +--- .../validators/client/ClientValidator.php | 30 +++++ 31 files changed, 1036 insertions(+), 621 deletions(-) create mode 100644 framework/jquery/validators/client/BooleanValidator.php create mode 100644 framework/jquery/validators/client/CaptchaClientValidator.php create mode 100644 framework/jquery/validators/client/CompareValidator.php create mode 100644 framework/jquery/validators/client/EmailValidator.php create mode 100644 framework/jquery/validators/client/FileValidator.php create mode 100644 framework/jquery/validators/client/FilterValidator.php create mode 100644 framework/jquery/validators/client/ImageValidator.php create mode 100644 framework/jquery/validators/client/IpValidator.php create mode 100644 framework/jquery/validators/client/NumberValidator.php create mode 100644 framework/jquery/validators/client/RangeValidator.php create mode 100644 framework/jquery/validators/client/RegularExpressionValidator.php create mode 100644 framework/jquery/validators/client/RequiredValidator.php create mode 100644 framework/jquery/validators/client/StringValidator.php create mode 100644 framework/jquery/validators/client/UrlValidator.php create mode 100644 framework/validators/client/ClientValidator.php diff --git a/framework/jquery/ActiveFormClientScript.php b/framework/jquery/ActiveFormClientScript.php index 81df2eff8f5..2daad14dae2 100644 --- a/framework/jquery/ActiveFormClientScript.php +++ b/framework/jquery/ActiveFormClientScript.php @@ -30,7 +30,7 @@ * @see \yii\widgets\ActiveFormClientScriptBehavior * * @author Paul Klimov - * @since 2.1 + * @since 2.1.0 */ class ActiveFormClientScript extends \yii\widgets\ActiveFormClientScript { diff --git a/framework/jquery/GridViewClientScript.php b/framework/jquery/GridViewClientScript.php index ec32c997aa0..801b46483da 100644 --- a/framework/jquery/GridViewClientScript.php +++ b/framework/jquery/GridViewClientScript.php @@ -40,7 +40,7 @@ * * @author Qiang Xue * @author Paul Klimov - * @since 2.1 + * @since 2.1.0 */ class GridViewClientScript extends Behavior { diff --git a/framework/jquery/validators/client/BooleanValidator.php b/framework/jquery/validators/client/BooleanValidator.php new file mode 100644 index 00000000000..8a73c4599f9 --- /dev/null +++ b/framework/jquery/validators/client/BooleanValidator.php @@ -0,0 +1,61 @@ + + * @since 2.1.0 + */ +class BooleanValidator extends ClientValidator +{ + /** + * @inheritdoc + */ + public function build($validator, $model, $attribute, $view) + { + ValidationAsset::register($view); + $options = $this->getClientOptions($validator, $model, $attribute); + return 'yii.validation.boolean(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; + } + + /** + * Returns the client-side validation options. + * @param \yii\validators\BooleanValidator $validator the server-side validator. + * @param \yii\base\Model $model the model being validated + * @param string $attribute the attribute name being validated + * @return array the client-side validation options + */ + public function getClientOptions($validator, $model, $attribute) + { + $options = [ + 'trueValue' => $validator->trueValue, + 'falseValue' => $validator->falseValue, + 'message' => $validator->formatMessage($validator->message, [ + 'attribute' => $model->getAttributeLabel($attribute), + 'true' => $validator->trueValue === true ? 'true' : $validator->trueValue, + 'false' => $validator->falseValue === false ? 'false' : $validator->falseValue, + ]), + ]; + if ($validator->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + if ($validator->strict) { + $options['strict'] = 1; + } + + return $options; + } +} \ No newline at end of file diff --git a/framework/jquery/validators/client/CaptchaClientValidator.php b/framework/jquery/validators/client/CaptchaClientValidator.php new file mode 100644 index 00000000000..21d7ee30b31 --- /dev/null +++ b/framework/jquery/validators/client/CaptchaClientValidator.php @@ -0,0 +1,60 @@ + + * @since 2.1.0 + */ +class CaptchaClientValidator extends ClientValidator +{ + /** + * @inheritdoc + */ + public function build($validator, $model, $attribute, $view) + { + ValidationAsset::register($view); + $options = $this->getClientOptions($validator, $model, $attribute); + return 'yii.validation.captcha(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; + } + + /** + * Returns the client-side validation options. + * @param \yii\captcha\CaptchaValidator $validator the server-side validator. + * @param \yii\base\Model $model the model being validated + * @param string $attribute the attribute name being validated + * @return array the client-side validation options + */ + public function getClientOptions($validator, $model, $attribute) + { + $captcha = $validator->createCaptchaAction(); + $code = $captcha->getVerifyCode(false); + $hash = $captcha->generateValidationHash($validator->caseSensitive ? $code : strtolower($code)); + $options = [ + 'hash' => $hash, + 'hashKey' => 'yiiCaptcha/' . $captcha->getUniqueId(), + 'caseSensitive' => $validator->caseSensitive, + 'message' => $validator->formatMessage($validator->message, [ + 'attribute' => $model->getAttributeLabel($attribute), + ]), + ]; + if ($validator->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + + return $options; + } +} \ No newline at end of file diff --git a/framework/jquery/validators/client/CompareValidator.php b/framework/jquery/validators/client/CompareValidator.php new file mode 100644 index 00000000000..cd257b6c8f6 --- /dev/null +++ b/framework/jquery/validators/client/CompareValidator.php @@ -0,0 +1,72 @@ + + * @since 2.1.0 + */ +class CompareValidator extends ClientValidator +{ + /** + * @inheritdoc + */ + public function build($validator, $model, $attribute, $view) + { + ValidationAsset::register($view); + $options = $this->getClientOptions($validator, $model, $attribute); + return 'yii.validation.compare(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; + } + + /** + * Returns the client-side validation options. + * @param \yii\validators\CompareValidator $validator the server-side validator. + * @param \yii\base\Model $model the model being validated + * @param string $attribute the attribute name being validated + * @return array the client-side validation options + */ + public function getClientOptions($validator, $model, $attribute) + { + $options = [ + 'operator' => $validator->operator, + 'type' => $validator->type, + ]; + + if ($validator->compareValue !== null) { + $options['compareValue'] = $validator->compareValue; + $compareLabel = $compareValue = $compareValueOrAttribute = $validator->compareValue; + } else { + $compareAttribute = $validator->compareAttribute === null ? $attribute . '_repeat' : $validator->compareAttribute; + $compareValue = $model->getAttributeLabel($compareAttribute); + $options['compareAttribute'] = Html::getInputId($model, $compareAttribute); + $compareLabel = $compareValueOrAttribute = $model->getAttributeLabel($compareAttribute); + } + + if ($validator->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + + $options['message'] = $validator->formatMessage($validator->message, [ + 'attribute' => $model->getAttributeLabel($attribute), + 'compareAttribute' => $compareLabel, + 'compareValue' => $compareValue, + 'compareValueOrAttribute' => $compareValueOrAttribute, + ]); + + return $options; + } +} \ No newline at end of file diff --git a/framework/jquery/validators/client/EmailValidator.php b/framework/jquery/validators/client/EmailValidator.php new file mode 100644 index 00000000000..67f18dd7555 --- /dev/null +++ b/framework/jquery/validators/client/EmailValidator.php @@ -0,0 +1,65 @@ + + * @since 2.1.0 + */ +class EmailValidator extends ClientValidator +{ + /** + * @inheritdoc + */ + public function build($validator, $model, $attribute, $view) + { + /* @var $validator \yii\validators\EmailValidator */ + ValidationAsset::register($view); + if ($validator->enableIDN) { + PunycodeAsset::register($view); + } + $options = $this->getClientOptions($validator, $model, $attribute); + return 'yii.validation.email(value, messages, ' . Json::htmlEncode($options) . ');'; + } + + /** + * Returns the client-side validation options. + * @param \yii\validators\EmailValidator $validator the server-side validator. + * @param \yii\base\Model $model the model being validated + * @param string $attribute the attribute name being validated + * @return array the client-side validation options + */ + public function getClientOptions($validator, $model, $attribute) + { + $options = [ + 'pattern' => new JsExpression($validator->pattern), + 'fullPattern' => new JsExpression($validator->fullPattern), + 'allowName' => $validator->allowName, + 'message' => $validator->formatMessage($validator->message, [ + 'attribute' => $model->getAttributeLabel($attribute), + ]), + 'enableIDN' => (bool)$validator->enableIDN, + ]; + if ($validator->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + + return $options; + } +} \ No newline at end of file diff --git a/framework/jquery/validators/client/FileValidator.php b/framework/jquery/validators/client/FileValidator.php new file mode 100644 index 00000000000..63c6fdd0a9e --- /dev/null +++ b/framework/jquery/validators/client/FileValidator.php @@ -0,0 +1,112 @@ + + * @since 2.1.0 + */ +class FileValidator extends ClientValidator +{ + /** + * @inheritdoc + */ + public function build($validator, $model, $attribute, $view) + { + ValidationAsset::register($view); + $options = $this->getClientOptions($validator, $model, $attribute); + return 'yii.validation.file(attribute, messages, ' . Json::encode($options) . ');'; + } + + /** + * Returns the client-side validation options. + * @param \yii\validators\FileValidator $validator the server-side validator. + * @param \yii\base\Model $model the model being validated + * @param string $attribute the attribute name being validated + * @return array the client-side validation options + */ + public function getClientOptions($validator, $model, $attribute) + { + $label = $model->getAttributeLabel($attribute); + + $options = []; + if ($validator->message !== null) { + $options['message'] = $validator->formatMessage($validator->message, [ + 'attribute' => $label, + ]); + } + + $options['skipOnEmpty'] = $validator->skipOnEmpty; + + if (!$validator->skipOnEmpty) { + $options['uploadRequired'] = $validator->formatMessage($validator->uploadRequired, [ + 'attribute' => $label, + ]); + } + + if ($validator->mimeTypes !== null) { + $mimeTypes = []; + foreach ($validator->mimeTypes as $mimeType) { + $mimeTypes[] = new JsExpression(Html::escapeJsRegularExpression($validator->buildMimeTypeRegexp($mimeType))); + } + $options['mimeTypes'] = $mimeTypes; + $options['wrongMimeType'] = $validator->formatMessage($validator->wrongMimeType, [ + 'attribute' => $label, + 'mimeTypes' => implode(', ', $validator->mimeTypes), + ]); + } + + if ($validator->extensions !== null) { + $options['extensions'] = $validator->extensions; + $options['wrongExtension'] = $validator->formatMessage($validator->wrongExtension, [ + 'attribute' => $label, + 'extensions' => implode(', ', $validator->extensions), + ]); + } + + if ($validator->minSize !== null) { + $options['minSize'] = $validator->minSize; + $options['tooSmall'] = $validator->formatMessage($validator->tooSmall, [ + 'attribute' => $label, + 'limit' => $validator->minSize, + 'formattedLimit' => Yii::$app->formatter->asShortSize($validator->minSize), + ]); + } + + if ($validator->maxSize !== null) { + $options['maxSize'] = $validator->maxSize; + $options['tooBig'] = $validator->formatMessage($validator->tooBig, [ + 'attribute' => $label, + 'limit' => $validator->getSizeLimit(), + 'formattedLimit' => Yii::$app->formatter->asShortSize($validator->getSizeLimit()), + ]); + } + + if ($validator->maxFiles !== null) { + $options['maxFiles'] = $validator->maxFiles; + $options['tooMany'] = $validator->formatMessage($validator->tooMany, [ + 'attribute' => $label, + 'limit' => $validator->maxFiles, + ]); + } + + return $options; + } +} \ No newline at end of file diff --git a/framework/jquery/validators/client/FilterValidator.php b/framework/jquery/validators/client/FilterValidator.php new file mode 100644 index 00000000000..134b81a0ebe --- /dev/null +++ b/framework/jquery/validators/client/FilterValidator.php @@ -0,0 +1,50 @@ + + * @since 2.1.0 + */ +class FilterValidator extends ClientValidator +{ + /** + * @inheritdoc + */ + public function build($validator, $model, $attribute, $view) + { + ValidationAsset::register($view); + $options = $this->getClientOptions($validator, $model, $attribute); + return 'value = yii.validation.trim($form, attribute, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; + } + + /** + * Returns the client-side validation options. + * @param \yii\validators\FilterValidator $validator the server-side validator. + * @param \yii\base\Model $model the model being validated + * @param string $attribute the attribute name being validated + * @return array the client-side validation options + */ + public function getClientOptions($validator, $model, $attribute) + { + $options = []; + if ($validator->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + + return $options; + } +} \ No newline at end of file diff --git a/framework/jquery/validators/client/ImageValidator.php b/framework/jquery/validators/client/ImageValidator.php new file mode 100644 index 00000000000..08003f81745 --- /dev/null +++ b/framework/jquery/validators/client/ImageValidator.php @@ -0,0 +1,86 @@ + + * @since 2.1.0 + */ +class ImageValidator extends FileValidator +{ + /** + * @inheritdoc + */ + public function build($validator, $model, $attribute, $view) + { + ValidationAsset::register($view); + $options = $this->getClientOptions($validator, $model, $attribute); + return 'yii.validation.image(attribute, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ', deferred);'; + } + + /** + * Returns the client-side validation options. + * @param \yii\validators\ImageValidator $validator the server-side validator. + * @param \yii\base\Model $model the model being validated + * @param string $attribute the attribute name being validated + * @return array the client-side validation options + */ + public function getClientOptions($validator, $model, $attribute) + { + $options = parent::getClientOptions($validator, $model, $attribute); + + $label = $model->getAttributeLabel($attribute); + + if ($validator->notImage !== null) { + $options['notImage'] = $validator->formatMessage($validator->notImage, [ + 'attribute' => $label, + ]); + } + + if ($validator->minWidth !== null) { + $options['minWidth'] = $validator->minWidth; + $options['underWidth'] = $validator->formatMessage($validator->underWidth, [ + 'attribute' => $label, + 'limit' => $validator->minWidth, + ]); + } + + if ($validator->maxWidth !== null) { + $options['maxWidth'] = $validator->maxWidth; + $options['overWidth'] = $validator->formatMessage($validator->overWidth, [ + 'attribute' => $label, + 'limit' => $validator->maxWidth, + ]); + } + + if ($validator->minHeight !== null) { + $options['minHeight'] = $validator->minHeight; + $options['underHeight'] = $validator->formatMessage($validator->underHeight, [ + 'attribute' => $label, + 'limit' => $validator->minHeight, + ]); + } + + if ($validator->maxHeight !== null) { + $options['maxHeight'] = $validator->maxHeight; + $options['overHeight'] = $validator->formatMessage($validator->overHeight, [ + 'attribute' => $label, + 'limit' => $validator->maxHeight, + ]); + } + + return $options; + } +} \ No newline at end of file diff --git a/framework/jquery/validators/client/IpValidator.php b/framework/jquery/validators/client/IpValidator.php new file mode 100644 index 00000000000..96c24cfbf84 --- /dev/null +++ b/framework/jquery/validators/client/IpValidator.php @@ -0,0 +1,76 @@ + + * @since 2.1.0 + */ +class IpValidator extends ClientValidator +{ + /** + * @inheritdoc + */ + public function build($validator, $model, $attribute, $view) + { + ValidationAsset::register($view); + $options = $this->getClientOptions($validator, $model, $attribute); + return 'yii.validation.ip(value, messages, ' . Json::htmlEncode($options) . ');'; + } + + /** + * Returns the client-side validation options. + * @param \yii\validators\IpValidator $validator the server-side validator. + * @param \yii\base\Model $model the model being validated + * @param string $attribute the attribute name being validated + * @return array the client-side validation options + */ + public function getClientOptions($validator, $model, $attribute) + { + $messages = [ + 'ipv6NotAllowed' => $validator->ipv6NotAllowed, + 'ipv4NotAllowed' => $validator->ipv4NotAllowed, + 'message' => $validator->message, + 'noSubnet' => $validator->noSubnet, + 'hasSubnet' => $validator->hasSubnet, + ]; + foreach ($messages as &$message) { + $message = $validator->formatMessage($message, [ + 'attribute' => $model->getAttributeLabel($attribute), + ]); + } + + $options = [ + 'ipv4Pattern' => new JsExpression(Html::escapeJsRegularExpression($validator->ipv4Pattern)), + 'ipv6Pattern' => new JsExpression(Html::escapeJsRegularExpression($validator->ipv6Pattern)), + 'messages' => $messages, + 'ipv4' => (bool) $validator->ipv4, + 'ipv6' => (bool) $validator->ipv6, + 'ipParsePattern' => new JsExpression(Html::escapeJsRegularExpression($validator->getIpParsePattern())), + 'negation' => $validator->negation, + 'subnet' => $validator->subnet, + ]; + if ($validator->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + + return $options; + } +} \ No newline at end of file diff --git a/framework/jquery/validators/client/NumberValidator.php b/framework/jquery/validators/client/NumberValidator.php new file mode 100644 index 00000000000..a5fbc48c5e3 --- /dev/null +++ b/framework/jquery/validators/client/NumberValidator.php @@ -0,0 +1,78 @@ + + * @since 2.1.0 + */ +class NumberValidator extends ClientValidator +{ + /** + * @inheritdoc + */ + public function build($validator, $model, $attribute, $view) + { + ValidationAsset::register($view); + $options = $this->getClientOptions($validator, $model, $attribute); + return 'yii.validation.number(value, messages, ' . Json::htmlEncode($options) . ');'; + } + + /** + * Returns the client-side validation options. + * @param \yii\validators\NumberValidator $validator the server-side validator. + * @param \yii\base\Model $model the model being validated + * @param string $attribute the attribute name being validated + * @return array the client-side validation options + */ + public function getClientOptions($validator, $model, $attribute) + { + $label = $model->getAttributeLabel($attribute); + + $options = [ + 'pattern' => new JsExpression($validator->integerOnly ? $validator->integerPattern : $validator->numberPattern), + 'message' => $validator->formatMessage($validator->message, [ + 'attribute' => $label, + ]), + ]; + + if ($validator->min !== null) { + // ensure numeric value to make javascript comparison equal to PHP comparison + // https://github.com/yiisoft/yii2/issues/3118 + $options['min'] = is_string($validator->min) ? (float) $validator->min : $validator->min; + $options['tooSmall'] = $validator->formatMessage($validator->tooSmall, [ + 'attribute' => $label, + 'min' => $validator->min, + ]); + } + if ($validator->max !== null) { + // ensure numeric value to make javascript comparison equal to PHP comparison + // https://github.com/yiisoft/yii2/issues/3118 + $options['max'] = is_string($validator->max) ? (float) $validator->max : $validator->max; + $options['tooBig'] = $validator->formatMessage($validator->tooBig, [ + 'attribute' => $label, + 'max' => $validator->max, + ]); + } + if ($validator->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + + return $options; + } +} \ No newline at end of file diff --git a/framework/jquery/validators/client/RangeValidator.php b/framework/jquery/validators/client/RangeValidator.php new file mode 100644 index 00000000000..2a436703e3c --- /dev/null +++ b/framework/jquery/validators/client/RangeValidator.php @@ -0,0 +1,67 @@ + + * @since 2.1.0 + */ +class RangeValidator extends ClientValidator +{ + /** + * @inheritdoc + */ + public function build($validator, $model, $attribute, $view) + { + /* @var $validator \yii\validators\RangeValidator */ + if ($validator->range instanceof \Closure) { + $validator->range = call_user_func($validator->range, $model, $attribute); + } + ValidationAsset::register($view); + $options = $validator->getClientOptions($model, $attribute); + return 'yii.validation.range(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; + } + + /** + * Returns the client-side validation options. + * @param \yii\validators\RangeValidator $validator the server-side validator. + * @param \yii\base\Model $model the model being validated + * @param string $attribute the attribute name being validated + * @return array the client-side validation options + */ + public function getClientOptions($validator, $model, $attribute) + { + $range = []; + foreach ($validator->range as $value) { + $range[] = (string) $value; + } + $options = [ + 'range' => $range, + 'not' => $validator->not, + 'message' => $validator->formatMessage($validator->message, [ + 'attribute' => $model->getAttributeLabel($attribute), + ]), + ]; + if ($validator->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + if ($validator->allowArray) { + $options['allowArray'] = 1; + } + + return $options; + } +} \ No newline at end of file diff --git a/framework/jquery/validators/client/RegularExpressionValidator.php b/framework/jquery/validators/client/RegularExpressionValidator.php new file mode 100644 index 00000000000..b43fded191d --- /dev/null +++ b/framework/jquery/validators/client/RegularExpressionValidator.php @@ -0,0 +1,61 @@ + + * @since 2.1.0 + */ +class RegularExpressionValidator extends ClientValidator +{ + /** + * @inheritdoc + */ + public function build($validator, $model, $attribute, $view) + { + ValidationAsset::register($view); + $options = $this->getClientOptions($validator, $model, $attribute); + return 'yii.validation.regularExpression(value, messages, ' . Json::htmlEncode($options) . ');'; + } + + /** + * Returns the client-side validation options. + * @param \yii\validators\RegularExpressionValidator $validator the server-side validator. + * @param \yii\base\Model $model the model being validated + * @param string $attribute the attribute name being validated + * @return array the client-side validation options + */ + public function getClientOptions($validator, $model, $attribute) + { + $pattern = Html::escapeJsRegularExpression($validator->pattern); + + $options = [ + 'pattern' => new JsExpression($pattern), + 'not' => $validator->not, + 'message' => $validator->formatMessage($validator->message, [ + 'attribute' => $model->getAttributeLabel($attribute), + ]), + ]; + if ($validator->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + + return $options; + } +} \ No newline at end of file diff --git a/framework/jquery/validators/client/RequiredValidator.php b/framework/jquery/validators/client/RequiredValidator.php new file mode 100644 index 00000000000..726a6d41d69 --- /dev/null +++ b/framework/jquery/validators/client/RequiredValidator.php @@ -0,0 +1,62 @@ + + * @since 2.1.0 + */ +class RequiredValidator extends ClientValidator +{ + /** + * @inheritdoc + */ + public function build($validator, $model, $attribute, $view) + { + ValidationAsset::register($view); + $options = $this->getClientOptions($validator, $model, $attribute); + return 'yii.validation.required(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; + } + + /** + * Returns the client-side validation options. + * @param \yii\validators\RequiredValidator $validator the server-side validator. + * @param \yii\base\Model $model the model being validated + * @param string $attribute the attribute name being validated + * @return array the client-side validation options + */ + public function getClientOptions($validator, $model, $attribute) + { + $options = []; + if ($validator->requiredValue !== null) { + $options['message'] = $validator->formatMessage($validator->message, [ + 'requiredValue' => $validator->requiredValue, + ]); + $options['requiredValue'] = $validator->requiredValue; + } else { + $options['message'] = $validator->message; + } + if ($validator->strict) { + $options['strict'] = 1; + } + + $options['message'] = $validator->formatMessage($options['message'], [ + 'attribute' => $model->getAttributeLabel($attribute), + ]); + + return $options; + } +} \ No newline at end of file diff --git a/framework/jquery/validators/client/StringValidator.php b/framework/jquery/validators/client/StringValidator.php new file mode 100644 index 00000000000..d0101d29ed5 --- /dev/null +++ b/framework/jquery/validators/client/StringValidator.php @@ -0,0 +1,78 @@ + + * @since 2.1.0 + */ +class StringValidator extends ClientValidator +{ + /** + * @inheritdoc + */ + public function build($validator, $model, $attribute, $view) + { + ValidationAsset::register($view); + $options = $this->getClientOptions($validator, $model, $attribute); + return 'yii.validation.string(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; + } + + /** + * Returns the client-side validation options. + * @param \yii\validators\StringValidator $validator the server-side validator. + * @param \yii\base\Model $model the model being validated + * @param string $attribute the attribute name being validated + * @return array the client-side validation options + */ + public function getClientOptions($validator, $model, $attribute) + { + $label = $model->getAttributeLabel($attribute); + + $options = [ + 'message' => $validator->formatMessage($validator->message, [ + 'attribute' => $label, + ]), + ]; + + if ($validator->min !== null) { + $options['min'] = $validator->min; + $options['tooShort'] = $validator->formatMessage($validator->tooShort, [ + 'attribute' => $label, + 'min' => $validator->min, + ]); + } + if ($validator->max !== null) { + $options['max'] = $validator->max; + $options['tooLong'] = $validator->formatMessage($validator->tooLong, [ + 'attribute' => $label, + 'max' => $validator->max, + ]); + } + if ($validator->length !== null) { + $options['is'] = $validator->length; + $options['notEqual'] = $validator->formatMessage($validator->notEqual, [ + 'attribute' => $label, + 'length' => $validator->length, + ]); + } + if ($validator->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + + return $options; + } +} \ No newline at end of file diff --git a/framework/jquery/validators/client/UrlValidator.php b/framework/jquery/validators/client/UrlValidator.php new file mode 100644 index 00000000000..32d42d03274 --- /dev/null +++ b/framework/jquery/validators/client/UrlValidator.php @@ -0,0 +1,72 @@ + + * @since 2.1.0 + */ +class UrlValidator extends ClientValidator +{ + /** + * @inheritdoc + */ + public function build($validator, $model, $attribute, $view) + { + /* @var $validator \yii\validators\UrlValidator */ + ValidationAsset::register($view); + if ($validator->enableIDN) { + PunycodeAsset::register($view); + } + $options = $this->getClientOptions($validator, $model, $attribute); + return 'yii.validation.url(value, messages, ' . Json::htmlEncode($options) . ');'; + } + + /** + * Returns the client-side validation options. + * @param \yii\validators\UrlValidator $validator the server-side validator. + * @param \yii\base\Model $model the model being validated + * @param string $attribute the attribute name being validated + * @return array the client-side validation options + */ + public function getClientOptions($validator, $model, $attribute) + { + if (strpos($validator->pattern, '{schemes}') !== false) { + $pattern = str_replace('{schemes}', '(' . implode('|', $validator->validSchemes) . ')', $validator->pattern); + } else { + $pattern = $validator->pattern; + } + + $options = [ + 'pattern' => new JsExpression($pattern), + 'message' => $validator->formatMessage($validator->message, [ + 'attribute' => $model->getAttributeLabel($attribute), + ]), + 'enableIDN' => (bool) $validator->enableIDN, + ]; + if ($validator->skipOnEmpty) { + $options['skipOnEmpty'] = 1; + } + if ($validator->defaultScheme !== null) { + $options['defaultScheme'] = $validator->defaultScheme; + } + + return $options; + } +} \ No newline at end of file diff --git a/framework/validators/BooleanValidator.php b/framework/validators/BooleanValidator.php index 2a6830ad400..d0daa3f461e 100644 --- a/framework/validators/BooleanValidator.php +++ b/framework/validators/BooleanValidator.php @@ -67,39 +67,4 @@ protected function validateValue($value) return null; } - - /** - * @inheritdoc - */ - public function clientValidateAttribute($model, $attribute, $view) - { - ValidationAsset::register($view); - $options = $this->getClientOptions($model, $attribute); - - return 'yii.validation.boolean(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; - } - - /** - * @inheritdoc - */ - public function getClientOptions($model, $attribute) - { - $options = [ - 'trueValue' => $this->trueValue, - 'falseValue' => $this->falseValue, - 'message' => $this->formatMessage($this->message, [ - 'attribute' => $model->getAttributeLabel($attribute), - 'true' => $this->trueValue === true ? 'true' : $this->trueValue, - 'false' => $this->falseValue === false ? 'false' : $this->falseValue, - ]), - ]; - if ($this->skipOnEmpty) { - $options['skipOnEmpty'] = 1; - } - if ($this->strict) { - $options['strict'] = 1; - } - - return $options; - } } diff --git a/framework/validators/CompareValidator.php b/framework/validators/CompareValidator.php index 5eb5fc019fa..6f6440d7fc0 100644 --- a/framework/validators/CompareValidator.php +++ b/framework/validators/CompareValidator.php @@ -9,7 +9,6 @@ use Yii; use yii\base\InvalidConfigException; -use yii\helpers\Html; /** * CompareValidator compares the specified attribute value with another value. @@ -219,49 +218,4 @@ protected function compareValues($operator, $type, $value, $compareValue) return false; } } - - /** - * @inheritdoc - */ - public function clientValidateAttribute($model, $attribute, $view) - { - ValidationAsset::register($view); - $options = $this->getClientOptions($model, $attribute); - - return 'yii.validation.compare(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; - } - - /** - * @inheritdoc - */ - public function getClientOptions($model, $attribute) - { - $options = [ - 'operator' => $this->operator, - 'type' => $this->type, - ]; - - if ($this->compareValue !== null) { - $options['compareValue'] = $this->compareValue; - $compareLabel = $compareValue = $compareValueOrAttribute = $this->compareValue; - } else { - $compareAttribute = $this->compareAttribute === null ? $attribute . '_repeat' : $this->compareAttribute; - $compareValue = $model->getAttributeLabel($compareAttribute); - $options['compareAttribute'] = Html::getInputId($model, $compareAttribute); - $compareLabel = $compareValueOrAttribute = $model->getAttributeLabel($compareAttribute); - } - - if ($this->skipOnEmpty) { - $options['skipOnEmpty'] = 1; - } - - $options['message'] = $this->formatMessage($this->message, [ - 'attribute' => $model->getAttributeLabel($attribute), - 'compareAttribute' => $compareLabel, - 'compareValue' => $compareValue, - 'compareValueOrAttribute' => $compareValueOrAttribute, - ]); - - return $options; - } } diff --git a/framework/validators/EmailValidator.php b/framework/validators/EmailValidator.php index bd613f6162c..230aeebbbdd 100644 --- a/framework/validators/EmailValidator.php +++ b/framework/validators/EmailValidator.php @@ -9,8 +9,6 @@ use Yii; use yii\base\InvalidConfigException; -use yii\helpers\Json; -use yii\web\JsExpression; /** * EmailValidator validates that the attribute value is a valid email address. @@ -103,39 +101,4 @@ protected function validateValue($value) return $valid ? null : [$this->message, []]; } - - /** - * @inheritdoc - */ - public function clientValidateAttribute($model, $attribute, $view) - { - ValidationAsset::register($view); - if ($this->enableIDN) { - PunycodeAsset::register($view); - } - $options = $this->getClientOptions($model, $attribute); - - return 'yii.validation.email(value, messages, ' . Json::htmlEncode($options) . ');'; - } - - /** - * @inheritdoc - */ - public function getClientOptions($model, $attribute) - { - $options = [ - 'pattern' => new JsExpression($this->pattern), - 'fullPattern' => new JsExpression($this->fullPattern), - 'allowName' => $this->allowName, - 'message' => $this->formatMessage($this->message, [ - 'attribute' => $model->getAttributeLabel($attribute), - ]), - 'enableIDN' => (bool) $this->enableIDN, - ]; - if ($this->skipOnEmpty) { - $options['skipOnEmpty'] = 1; - } - - return $options; - } } diff --git a/framework/validators/FileValidator.php b/framework/validators/FileValidator.php index b2975202207..1754951c0d5 100644 --- a/framework/validators/FileValidator.php +++ b/framework/validators/FileValidator.php @@ -9,9 +9,6 @@ use Yii; use yii\helpers\FileHelper; -use yii\helpers\Html; -use yii\helpers\Json; -use yii\web\JsExpression; use yii\http\UploadedFile; /** @@ -378,87 +375,6 @@ protected function validateExtension($file) return true; } - /** - * @inheritdoc - */ - public function clientValidateAttribute($model, $attribute, $view) - { - ValidationAsset::register($view); - $options = $this->getClientOptions($model, $attribute); - return 'yii.validation.file(attribute, messages, ' . Json::encode($options) . ');'; - } - - /** - * @inheritdoc - */ - public function getClientOptions($model, $attribute) - { - $label = $model->getAttributeLabel($attribute); - - $options = []; - if ($this->message !== null) { - $options['message'] = $this->formatMessage($this->message, [ - 'attribute' => $label, - ]); - } - - $options['skipOnEmpty'] = $this->skipOnEmpty; - - if (!$this->skipOnEmpty) { - $options['uploadRequired'] = $this->formatMessage($this->uploadRequired, [ - 'attribute' => $label, - ]); - } - - if ($this->mimeTypes !== null) { - $mimeTypes = []; - foreach ($this->mimeTypes as $mimeType) { - $mimeTypes[] = new JsExpression(Html::escapeJsRegularExpression($this->buildMimeTypeRegexp($mimeType))); - } - $options['mimeTypes'] = $mimeTypes; - $options['wrongMimeType'] = $this->formatMessage($this->wrongMimeType, [ - 'attribute' => $label, - 'mimeTypes' => implode(', ', $this->mimeTypes), - ]); - } - - if ($this->extensions !== null) { - $options['extensions'] = $this->extensions; - $options['wrongExtension'] = $this->formatMessage($this->wrongExtension, [ - 'attribute' => $label, - 'extensions' => implode(', ', $this->extensions), - ]); - } - - if ($this->minSize !== null) { - $options['minSize'] = $this->minSize; - $options['tooSmall'] = $this->formatMessage($this->tooSmall, [ - 'attribute' => $label, - 'limit' => $this->minSize, - 'formattedLimit' => Yii::$app->formatter->asShortSize($this->minSize), - ]); - } - - if ($this->maxSize !== null) { - $options['maxSize'] = $this->maxSize; - $options['tooBig'] = $this->formatMessage($this->tooBig, [ - 'attribute' => $label, - 'limit' => $this->getSizeLimit(), - 'formattedLimit' => Yii::$app->formatter->asShortSize($this->getSizeLimit()), - ]); - } - - if ($this->maxFiles !== null) { - $options['maxFiles'] = $this->maxFiles; - $options['tooMany'] = $this->formatMessage($this->tooMany, [ - 'attribute' => $label, - 'limit' => $this->maxFiles, - ]); - } - - return $options; - } - /** * Builds the RegExp from the $mask. * @@ -466,7 +382,7 @@ public function getClientOptions($model, $attribute) * @return string the regular expression * @see mimeTypes */ - private function buildMimeTypeRegexp($mask) + public function buildMimeTypeRegexp($mask) { return '/^' . str_replace('\*', '.*', preg_quote($mask, '/')) . '$/'; } diff --git a/framework/validators/FilterValidator.php b/framework/validators/FilterValidator.php index cedf241fd1e..1ca6593e564 100644 --- a/framework/validators/FilterValidator.php +++ b/framework/validators/FilterValidator.php @@ -78,32 +78,4 @@ public function validateAttribute($model, $attribute) $model->$attribute = call_user_func($this->filter, $value); } } - - /** - * @inheritdoc - */ - public function clientValidateAttribute($model, $attribute, $view) - { - if ($this->filter !== 'trim') { - return null; - } - - ValidationAsset::register($view); - $options = $this->getClientOptions($model, $attribute); - - return 'value = yii.validation.trim($form, attribute, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; - } - - /** - * @inheritdoc - */ - public function getClientOptions($model, $attribute) - { - $options = []; - if ($this->skipOnEmpty) { - $options['skipOnEmpty'] = 1; - } - - return $options; - } } diff --git a/framework/validators/ImageValidator.php b/framework/validators/ImageValidator.php index cf4e34be27e..b4c2816f98f 100644 --- a/framework/validators/ImageValidator.php +++ b/framework/validators/ImageValidator.php @@ -158,64 +158,4 @@ protected function validateImage($image) return null; } - - /** - * @inheritdoc - */ - public function clientValidateAttribute($model, $attribute, $view) - { - ValidationAsset::register($view); - $options = $this->getClientOptions($model, $attribute); - return 'yii.validation.image(attribute, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ', deferred);'; - } - - /** - * @inheritdoc - */ - public function getClientOptions($model, $attribute) - { - $options = parent::getClientOptions($model, $attribute); - - $label = $model->getAttributeLabel($attribute); - - if ($this->notImage !== null) { - $options['notImage'] = $this->formatMessage($this->notImage, [ - 'attribute' => $label, - ]); - } - - if ($this->minWidth !== null) { - $options['minWidth'] = $this->minWidth; - $options['underWidth'] = $this->formatMessage($this->underWidth, [ - 'attribute' => $label, - 'limit' => $this->minWidth, - ]); - } - - if ($this->maxWidth !== null) { - $options['maxWidth'] = $this->maxWidth; - $options['overWidth'] = $this->formatMessage($this->overWidth, [ - 'attribute' => $label, - 'limit' => $this->maxWidth, - ]); - } - - if ($this->minHeight !== null) { - $options['minHeight'] = $this->minHeight; - $options['underHeight'] = $this->formatMessage($this->underHeight, [ - 'attribute' => $label, - 'limit' => $this->minHeight, - ]); - } - - if ($this->maxHeight !== null) { - $options['maxHeight'] = $this->maxHeight; - $options['overHeight'] = $this->formatMessage($this->overHeight, [ - 'attribute' => $label, - 'limit' => $this->maxHeight, - ]); - } - - return $options; - } } diff --git a/framework/validators/IpValidator.php b/framework/validators/IpValidator.php index 6f1896a8c49..138ffb1381f 100644 --- a/framework/validators/IpValidator.php +++ b/framework/validators/IpValidator.php @@ -9,9 +9,6 @@ use Yii; use yii\base\InvalidConfigException; -use yii\helpers\Html; -use yii\helpers\Json; -use yii\web\JsExpression; /** * The validator checks if the attribute value is a valid IPv4/IPv6 address or subnet. @@ -522,7 +519,7 @@ private function getIpVersion($ip) * Used to get the Regexp pattern for initial IP address parsing. * @return string */ - private function getIpParsePattern() + public function getIpParsePattern() { return '/^(' . preg_quote(static::NEGATION_CHAR, '/') . '?)(.+?)(\/(\d+))?$/'; } @@ -579,50 +576,4 @@ private function ip2bin($ip) return $result; } - - /** - * @inheritdoc - */ - public function clientValidateAttribute($model, $attribute, $view) - { - ValidationAsset::register($view); - $options = $this->getClientOptions($model, $attribute); - - return 'yii.validation.ip(value, messages, ' . Json::htmlEncode($options) . ');'; - } - - /** - * @inheritdoc - */ - public function getClientOptions($model, $attribute) - { - $messages = [ - 'ipv6NotAllowed' => $this->ipv6NotAllowed, - 'ipv4NotAllowed' => $this->ipv4NotAllowed, - 'message' => $this->message, - 'noSubnet' => $this->noSubnet, - 'hasSubnet' => $this->hasSubnet, - ]; - foreach ($messages as &$message) { - $message = $this->formatMessage($message, [ - 'attribute' => $model->getAttributeLabel($attribute), - ]); - } - - $options = [ - 'ipv4Pattern' => new JsExpression(Html::escapeJsRegularExpression($this->ipv4Pattern)), - 'ipv6Pattern' => new JsExpression(Html::escapeJsRegularExpression($this->ipv6Pattern)), - 'messages' => $messages, - 'ipv4' => (bool) $this->ipv4, - 'ipv6' => (bool) $this->ipv6, - 'ipParsePattern' => new JsExpression(Html::escapeJsRegularExpression($this->getIpParsePattern())), - 'negation' => $this->negation, - 'subnet' => $this->subnet, - ]; - if ($this->skipOnEmpty) { - $options['skipOnEmpty'] = 1; - } - - return $options; - } } diff --git a/framework/validators/NumberValidator.php b/framework/validators/NumberValidator.php index e438c048a4d..0154aed64b7 100644 --- a/framework/validators/NumberValidator.php +++ b/framework/validators/NumberValidator.php @@ -8,9 +8,7 @@ namespace yii\validators; use Yii; -use yii\helpers\Json; use yii\helpers\StringHelper; -use yii\web\JsExpression; /** * NumberValidator validates that the attribute value is a number. @@ -117,54 +115,4 @@ protected function validateValue($value) return null; } - - /** - * @inheritdoc - */ - public function clientValidateAttribute($model, $attribute, $view) - { - ValidationAsset::register($view); - $options = $this->getClientOptions($model, $attribute); - - return 'yii.validation.number(value, messages, ' . Json::htmlEncode($options) . ');'; - } - - /** - * @inheritdoc - */ - public function getClientOptions($model, $attribute) - { - $label = $model->getAttributeLabel($attribute); - - $options = [ - 'pattern' => new JsExpression($this->integerOnly ? $this->integerPattern : $this->numberPattern), - 'message' => $this->formatMessage($this->message, [ - 'attribute' => $label, - ]), - ]; - - if ($this->min !== null) { - // ensure numeric value to make javascript comparison equal to PHP comparison - // https://github.com/yiisoft/yii2/issues/3118 - $options['min'] = is_string($this->min) ? (float) $this->min : $this->min; - $options['tooSmall'] = $this->formatMessage($this->tooSmall, [ - 'attribute' => $label, - 'min' => $this->min, - ]); - } - if ($this->max !== null) { - // ensure numeric value to make javascript comparison equal to PHP comparison - // https://github.com/yiisoft/yii2/issues/3118 - $options['max'] = is_string($this->max) ? (float) $this->max : $this->max; - $options['tooBig'] = $this->formatMessage($this->tooBig, [ - 'attribute' => $label, - 'max' => $this->max, - ]); - } - if ($this->skipOnEmpty) { - $options['skipOnEmpty'] = 1; - } - - return $options; - } } diff --git a/framework/validators/RangeValidator.php b/framework/validators/RangeValidator.php index bbc344ada59..cd8bd486af8 100644 --- a/framework/validators/RangeValidator.php +++ b/framework/validators/RangeValidator.php @@ -98,45 +98,4 @@ public function validateAttribute($model, $attribute) } parent::validateAttribute($model, $attribute); } - - /** - * @inheritdoc - */ - public function clientValidateAttribute($model, $attribute, $view) - { - if ($this->range instanceof \Closure) { - $this->range = call_user_func($this->range, $model, $attribute); - } - - ValidationAsset::register($view); - $options = $this->getClientOptions($model, $attribute); - - return 'yii.validation.range(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; - } - - /** - * @inheritdoc - */ - public function getClientOptions($model, $attribute) - { - $range = []; - foreach ($this->range as $value) { - $range[] = (string) $value; - } - $options = [ - 'range' => $range, - 'not' => $this->not, - 'message' => $this->formatMessage($this->message, [ - 'attribute' => $model->getAttributeLabel($attribute), - ]), - ]; - if ($this->skipOnEmpty) { - $options['skipOnEmpty'] = 1; - } - if ($this->allowArray) { - $options['allowArray'] = 1; - } - - return $options; - } } diff --git a/framework/validators/RegularExpressionValidator.php b/framework/validators/RegularExpressionValidator.php index fab5c51b45c..49ba13cda26 100644 --- a/framework/validators/RegularExpressionValidator.php +++ b/framework/validators/RegularExpressionValidator.php @@ -9,9 +9,6 @@ use Yii; use yii\base\InvalidConfigException; -use yii\helpers\Html; -use yii\helpers\Json; -use yii\web\JsExpression; /** * RegularExpressionValidator validates that the attribute value matches the specified [[pattern]]. @@ -59,36 +56,4 @@ protected function validateValue($value) return $valid ? null : [$this->message, []]; } - - /** - * @inheritdoc - */ - public function clientValidateAttribute($model, $attribute, $view) - { - ValidationAsset::register($view); - $options = $this->getClientOptions($model, $attribute); - - return 'yii.validation.regularExpression(value, messages, ' . Json::htmlEncode($options) . ');'; - } - - /** - * @inheritdoc - */ - public function getClientOptions($model, $attribute) - { - $pattern = Html::escapeJsRegularExpression($this->pattern); - - $options = [ - 'pattern' => new JsExpression($pattern), - 'not' => $this->not, - 'message' => $this->formatMessage($this->message, [ - 'attribute' => $model->getAttributeLabel($attribute), - ]), - ]; - if ($this->skipOnEmpty) { - $options['skipOnEmpty'] = 1; - } - - return $options; - } } diff --git a/framework/validators/RequiredValidator.php b/framework/validators/RequiredValidator.php index a8c620a6f89..95977a3e05b 100644 --- a/framework/validators/RequiredValidator.php +++ b/framework/validators/RequiredValidator.php @@ -82,40 +82,4 @@ protected function validateValue($value) 'requiredValue' => $this->requiredValue, ]]; } - - /** - * @inheritdoc - */ - public function clientValidateAttribute($model, $attribute, $view) - { - ValidationAsset::register($view); - $options = $this->getClientOptions($model, $attribute); - - return 'yii.validation.required(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; - } - - /** - * @inheritdoc - */ - public function getClientOptions($model, $attribute) - { - $options = []; - if ($this->requiredValue !== null) { - $options['message'] = $this->formatMessage($this->message, [ - 'requiredValue' => $this->requiredValue, - ]); - $options['requiredValue'] = $this->requiredValue; - } else { - $options['message'] = $this->message; - } - if ($this->strict) { - $options['strict'] = 1; - } - - $options['message'] = $this->formatMessage($options['message'], [ - 'attribute' => $model->getAttributeLabel($attribute), - ]); - - return $options; - } } diff --git a/framework/validators/StringValidator.php b/framework/validators/StringValidator.php index e01bfe64041..794e6bd4913 100644 --- a/framework/validators/StringValidator.php +++ b/framework/validators/StringValidator.php @@ -147,56 +147,4 @@ protected function validateValue($value) return null; } - - /** - * @inheritdoc - */ - public function clientValidateAttribute($model, $attribute, $view) - { - ValidationAsset::register($view); - $options = $this->getClientOptions($model, $attribute); - - return 'yii.validation.string(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');'; - } - - /** - * @inheritdoc - */ - public function getClientOptions($model, $attribute) - { - $label = $model->getAttributeLabel($attribute); - - $options = [ - 'message' => $this->formatMessage($this->message, [ - 'attribute' => $label, - ]), - ]; - - if ($this->min !== null) { - $options['min'] = $this->min; - $options['tooShort'] = $this->formatMessage($this->tooShort, [ - 'attribute' => $label, - 'min' => $this->min, - ]); - } - if ($this->max !== null) { - $options['max'] = $this->max; - $options['tooLong'] = $this->formatMessage($this->tooLong, [ - 'attribute' => $label, - 'max' => $this->max, - ]); - } - if ($this->length !== null) { - $options['is'] = $this->length; - $options['notEqual'] = $this->formatMessage($this->notEqual, [ - 'attribute' => $label, - 'length' => $this->length, - ]); - } - if ($this->skipOnEmpty) { - $options['skipOnEmpty'] = 1; - } - - return $options; - } } diff --git a/framework/validators/UrlValidator.php b/framework/validators/UrlValidator.php index 6f237a2155d..f216f7f1f1b 100644 --- a/framework/validators/UrlValidator.php +++ b/framework/validators/UrlValidator.php @@ -9,8 +9,6 @@ use Yii; use yii\base\InvalidConfigException; -use yii\helpers\Json; -use yii\web\JsExpression; /** * UrlValidator validates that the attribute value is a valid http or https URL. @@ -107,46 +105,4 @@ protected function validateValue($value) return [$this->message, []]; } - - /** - * @inheritdoc - */ - public function clientValidateAttribute($model, $attribute, $view) - { - ValidationAsset::register($view); - if ($this->enableIDN) { - PunycodeAsset::register($view); - } - $options = $this->getClientOptions($model, $attribute); - - return 'yii.validation.url(value, messages, ' . Json::htmlEncode($options) . ');'; - } - - /** - * @inheritdoc - */ - public function getClientOptions($model, $attribute) - { - if (strpos($this->pattern, '{schemes}') !== false) { - $pattern = str_replace('{schemes}', '(' . implode('|', $this->validSchemes) . ')', $this->pattern); - } else { - $pattern = $this->pattern; - } - - $options = [ - 'pattern' => new JsExpression($pattern), - 'message' => $this->formatMessage($this->message, [ - 'attribute' => $model->getAttributeLabel($attribute), - ]), - 'enableIDN' => (bool) $this->enableIDN, - ]; - if ($this->skipOnEmpty) { - $options['skipOnEmpty'] = 1; - } - if ($this->defaultScheme !== null) { - $options['defaultScheme'] = $this->defaultScheme; - } - - return $options; - } } diff --git a/framework/validators/Validator.php b/framework/validators/Validator.php index 2082ab4b424..4cc9ea93662 100644 --- a/framework/validators/Validator.php +++ b/framework/validators/Validator.php @@ -329,8 +329,6 @@ protected function validateValue($value) /** * Returns the JavaScript needed for performing client-side validation. * - * Calls [[getClientOptions()]] to generate options array for client-side validation. - * * You may override this method to return the JavaScript validation code if * the validator can support client-side validation. * @@ -364,20 +362,6 @@ public function clientValidateAttribute($model, $attribute, $view) return null; } - /** - * Returns the client-side validation options. - * This method is usually called from [[clientValidateAttribute()]]. You may override this method to modify options - * that will be passed to the client-side validation. - * @param \yii\base\Model $model the model being validated - * @param string $attribute the attribute name being validated - * @return array the client-side validation options - * @since 2.0.11 - */ - public function getClientOptions($model, $attribute) - { - return []; - } - /** * Returns a value indicating whether the validator is active for the given scenario and attribute. * @@ -435,13 +419,13 @@ public function isEmpty($value) } /** - * Formats a mesage using the I18N, or simple strtr if `\Yii::$app` is not available. + * Formats a message using the I18N, or simple strtr if `\Yii::$app` is not available. * @param string $message * @param array $params * @since 2.0.12 * @return string */ - protected function formatMessage($message, $params) + public function formatMessage($message, $params) { if (Yii::$app !== null) { return \Yii::$app->getI18n()->format($message, $params, Yii::$app->language); diff --git a/framework/validators/client/ClientValidator.php b/framework/validators/client/ClientValidator.php new file mode 100644 index 00000000000..9b45b0baa92 --- /dev/null +++ b/framework/validators/client/ClientValidator.php @@ -0,0 +1,30 @@ + + * @since 2.1.0 + */ +abstract class ClientValidator extends BaseObject +{ + /** + * Builds the JavaScript needed for performing client-side validation for given validator. + * @param \yii\validators\Validator $validator validator to be built. + * @param \yii\base\Model $model the data model being validated. + * @param string $attribute the name of the attribute to be validated. + * @param \yii\web\View $view the view object that is going to be used to render views or view files + * containing a model form with validator applied. + * @return string|null client-side validation JavaScript code, `null` - if given validator is not supported. + */ + abstract public function build($validator, $model, $attribute, $view); +} \ No newline at end of file From 701ac690490ca4e4ee87e0c7984cc28d4797a51e Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Thu, 21 Sep 2017 20:00:33 +0300 Subject: [PATCH 07/14] UPGRADE added --- framework/UPGRADE.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md index d8bfbe8f581..70a2504ee80 100644 --- a/framework/UPGRADE.md +++ b/framework/UPGRADE.md @@ -133,6 +133,13 @@ Upgrade from Yii 2.0.x If you are using `yii\web\Request::resolve()` or `yii\web\UrlManager::parseRequest()` directly, make sure that all potential exceptions are handled correctly or set `yii\web\UrlNormalizer::$normalizer` to `false` to disable normalizer. * `yii\base\InvalidParamException` was renamed to `yii\base\InvalidArgumentException`. +* Classes `yii\widgets\ActiveForm`, `yii\widgets\ActiveField`, `yii\grid\GridView`, `yii\web\View` have been refactored + to be more generic without including any 'JQuery' support and client-side processing (validation, automatic submit etc.). + You should use widget behaviors from `yii\jquery\*` package to make old code function as before. E.g. attach `yii\jquery\ActiveFormClientScript` + to `yii\widgets\ActiveForm`, `yii\jquery\GridViewClientScript` `yii\grid\GridView` and so on. +* Assets `yii\web\JqueryAsset`, `yii\web\YiiAsset`, `yii\validators\ValidationAsset` have been moved under `yii\jquery\*` + namespace. Make sure you refer to the new full-qualified names of this classes. +* Default script position for the `yii\web\View::registerJs()` changed to `View::POS_END`. Upgrade from Yii 2.0.12 From fbaa7fb5897779a34b51a4efc022f8b2fb76c612 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Fri, 22 Sep 2017 13:16:41 +0300 Subject: [PATCH 08/14] fix unit tests --- tests/framework/captcha/CaptchaTest.php | 2 +- .../client/BooleanValidatorTest.php | 49 ++++++++++++ .../validators/client/NumberValidatorTest.php | 78 +++++++++++++++++++ .../validators/BooleanValidatorTest.php | 19 ----- .../validators/NumberValidatorTest.php | 42 ---------- 5 files changed, 128 insertions(+), 62 deletions(-) create mode 100644 tests/framework/jquery/validators/client/BooleanValidatorTest.php create mode 100644 tests/framework/jquery/validators/client/NumberValidatorTest.php diff --git a/tests/framework/captcha/CaptchaTest.php b/tests/framework/captcha/CaptchaTest.php index f55286d6d29..8d600175105 100644 --- a/tests/framework/captcha/CaptchaTest.php +++ b/tests/framework/captcha/CaptchaTest.php @@ -9,7 +9,7 @@ use yii\captcha\Captcha; use yii\web\AssetManager; -use yii\web\JqueryAsset; +use yii\jquery\JqueryAsset; use yiiunit\TestCase; class CaptchaTest extends TestCase diff --git a/tests/framework/jquery/validators/client/BooleanValidatorTest.php b/tests/framework/jquery/validators/client/BooleanValidatorTest.php new file mode 100644 index 00000000000..c830fd55c0f --- /dev/null +++ b/tests/framework/jquery/validators/client/BooleanValidatorTest.php @@ -0,0 +1,49 @@ + true, + 'falseValue' => false, + 'strict' => true, + ]); + + $clientValidator = new BooleanValidator(); + + $model = new FakedValidationModel(); + $model->attrA = true; + $model->attrB = '1'; + $model->attrC = '0'; + $model->attrD = []; + + $this->assertEquals( + 'yii.validation.boolean(value, messages, {"trueValue":true,"falseValue":false,"message":"attrB must be either \"true\" or \"false\".","skipOnEmpty":1,"strict":1});', + $clientValidator->build($validator, $model, 'attrB', new ViewStub()) + ); + } +} + +class ViewStub extends View +{ + public function registerAssetBundle($name, $position = null) + { + } +} \ No newline at end of file diff --git a/tests/framework/jquery/validators/client/NumberValidatorTest.php b/tests/framework/jquery/validators/client/NumberValidatorTest.php new file mode 100644 index 00000000000..9a451714d75 --- /dev/null +++ b/tests/framework/jquery/validators/client/NumberValidatorTest.php @@ -0,0 +1,78 @@ + ['yii\jquery\ValidationAsset' => true]]); + $clientValidator = new NumberValidator(); + $model = new FakedValidationModel(); + + $js = $clientValidator->build( + new \yii\validators\NumberValidator([ + 'min' => 5, + 'max' => 10, + ]), + $model, + 'attr_number', + $view + ); + $this->assertContains('"min":5', $js); + $this->assertContains('"max":10', $js); + + $js = $clientValidator->build( + new \yii\validators\NumberValidator([ + 'min' => '5', + 'max' => '10', + ]), + $model, + 'attr_number', + $view + ); + $this->assertContains('"min":5', $js); + $this->assertContains('"max":10', $js); + + $js = $clientValidator->build( + new \yii\validators\NumberValidator([ + 'min' => 5.65, + 'max' => 13.37, + ]), + $model, + 'attr_number', + $view + ); + $this->assertContains('"min":5.65', $js); + $this->assertContains('"max":13.37', $js); + + $js = $clientValidator->build( + new \yii\validators\NumberValidator([ + 'min' => '5.65', + 'max' => '13.37', + ]), + $model, + 'attr_number', + $view + ); + $this->assertContains('"min":5.65', $js); + $this->assertContains('"max":13.37', $js); + } +} \ No newline at end of file diff --git a/tests/framework/validators/BooleanValidatorTest.php b/tests/framework/validators/BooleanValidatorTest.php index 9835422ebf0..69c9574ca8e 100644 --- a/tests/framework/validators/BooleanValidatorTest.php +++ b/tests/framework/validators/BooleanValidatorTest.php @@ -8,7 +8,6 @@ namespace yiiunit\framework\validators; use yii\validators\BooleanValidator; -use yii\web\View; use yiiunit\data\validators\models\FakedValidationModel; use yiiunit\TestCase; @@ -77,23 +76,5 @@ public function testErrorMessage() $validator->validate('someIncorrectValue', $errorMessage); $this->assertEquals('the input value must be either "true" or "false".', $errorMessage); - - $obj = new FakedValidationModel(); - $obj->attrA = true; - $obj->attrB = '1'; - $obj->attrC = '0'; - $obj->attrD = []; - - $this->assertEquals( - 'yii.validation.boolean(value, messages, {"trueValue":true,"falseValue":false,"message":"attrB must be either \"true\" or \"false\".","skipOnEmpty":1,"strict":1});', - $validator->clientValidateAttribute($obj, 'attrB', new ViewStub()) - ); - } -} - -class ViewStub extends View -{ - public function registerAssetBundle($name, $position = null) - { } } diff --git a/tests/framework/validators/NumberValidatorTest.php b/tests/framework/validators/NumberValidatorTest.php index b26c8c8ad04..9732c9f4bfe 100644 --- a/tests/framework/validators/NumberValidatorTest.php +++ b/tests/framework/validators/NumberValidatorTest.php @@ -249,48 +249,6 @@ public function testEnsureCustomMessageIsSetOnValidateAttribute() $this->assertSame('attr_number is to small.', $msgs[0]); } - /** - * @see https://github.com/yiisoft/yii2/issues/3118 - */ - public function testClientValidateComparison() - { - $val = new NumberValidator([ - 'min' => 5, - 'max' => 10, - ]); - $model = new FakedValidationModel(); - $js = $val->clientValidateAttribute($model, 'attr_number', new View(['assetBundles' => ['yii\validators\ValidationAsset' => true]])); - $this->assertContains('"min":5', $js); - $this->assertContains('"max":10', $js); - - $val = new NumberValidator([ - 'min' => '5', - 'max' => '10', - ]); - $model = new FakedValidationModel(); - $js = $val->clientValidateAttribute($model, 'attr_number', new View(['assetBundles' => ['yii\validators\ValidationAsset' => true]])); - $this->assertContains('"min":5', $js); - $this->assertContains('"max":10', $js); - - $val = new NumberValidator([ - 'min' => 5.65, - 'max' => 13.37, - ]); - $model = new FakedValidationModel(); - $js = $val->clientValidateAttribute($model, 'attr_number', new View(['assetBundles' => ['yii\validators\ValidationAsset' => true]])); - $this->assertContains('"min":5.65', $js); - $this->assertContains('"max":13.37', $js); - - $val = new NumberValidator([ - 'min' => '5.65', - 'max' => '13.37', - ]); - $model = new FakedValidationModel(); - $js = $val->clientValidateAttribute($model, 'attr_number', new View(['assetBundles' => ['yii\validators\ValidationAsset' => true]])); - $this->assertContains('"min":5.65', $js); - $this->assertContains('"max":13.37', $js); - } - public function testValidateObject() { $val = new NumberValidator(); From dc72df36903895595968ff01d58914229066034c Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Fri, 22 Sep 2017 13:19:23 +0300 Subject: [PATCH 09/14] fix JS unit tests --- tests/js/tests/yii.activeForm.test.js | 4 ++-- tests/js/tests/yii.captcha.test.js | 2 +- tests/js/tests/yii.gridView.test.js | 4 ++-- tests/js/tests/yii.test.js | 2 +- tests/js/tests/yii.validation.test.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/js/tests/yii.activeForm.test.js b/tests/js/tests/yii.activeForm.test.js index 39815995b2e..15e77925a75 100644 --- a/tests/js/tests/yii.activeForm.test.js +++ b/tests/js/tests/yii.activeForm.test.js @@ -6,8 +6,8 @@ var fs = require('fs'); var vm = require('vm'); describe('yii.activeForm', function () { - var yiiActiveFormPath = 'framework/assets/yii.activeForm.js'; - var yiiPath = 'framework/assets/yii.js'; + var yiiActiveFormPath = 'framework/jquery/assets/yii.activeForm.js'; + var yiiPath = 'framework/jquery/assets/yii.js'; var jQueryPath = 'vendor/bower-asset/jquery/dist/jquery.js'; var $; var $activeForm; diff --git a/tests/js/tests/yii.captcha.test.js b/tests/js/tests/yii.captcha.test.js index 9fdd1d1d95f..615750b63df 100644 --- a/tests/js/tests/yii.captcha.test.js +++ b/tests/js/tests/yii.captcha.test.js @@ -7,7 +7,7 @@ var fs = require('fs'); var vm = require('vm'); describe('yii.captcha', function () { - var yiiCaptchaPath = 'framework/assets/yii.captcha.js'; + var yiiCaptchaPath = 'framework/captcha/assets/yii.captcha.js'; var jQueryPath = 'vendor/bower-asset/jquery/dist/jquery.js'; var $; var $captcha; diff --git a/tests/js/tests/yii.gridView.test.js b/tests/js/tests/yii.gridView.test.js index 6b173f0cd09..fcf22530eee 100644 --- a/tests/js/tests/yii.gridView.test.js +++ b/tests/js/tests/yii.gridView.test.js @@ -7,8 +7,8 @@ var fs = require('fs'); var vm = require('vm'); describe('yii.gridView', function () { - var yiiGridViewPath = 'framework/assets/yii.gridView.js'; - var yiiPath = 'framework/assets/yii.js'; + var yiiGridViewPath = 'framework/jquery/assets/yii.gridView.js'; + var yiiPath = 'framework/jquery/assets/yii.js'; var jQueryPath = 'vendor/bower-asset/jquery/dist/jquery.js'; var $; var $gridView; diff --git a/tests/js/tests/yii.test.js b/tests/js/tests/yii.test.js index daaa1e1c160..cad921e0ec9 100644 --- a/tests/js/tests/yii.test.js +++ b/tests/js/tests/yii.test.js @@ -19,7 +19,7 @@ var StringUtils = { }; describe('yii', function () { - var yiiPath = 'framework/assets/yii.js'; + var yiiPath = 'framework/jquery/assets/yii.js'; var jQueryPath = 'vendor/bower-asset/jquery/dist/jquery.js'; var pjaxPath = 'vendor/bower-asset/yii2-pjax/jquery.pjax.js'; var sandbox; diff --git a/tests/js/tests/yii.validation.test.js b/tests/js/tests/yii.validation.test.js index 7e1312260ee..2a9c451b3f7 100644 --- a/tests/js/tests/yii.validation.test.js +++ b/tests/js/tests/yii.validation.test.js @@ -57,7 +57,7 @@ describe('yii.validation', function () { }; } - var path = 'framework/assets/yii.validation.js'; + var path = 'framework/jquery/assets/yii.validation.js'; if (code === undefined) { code = fs.readFileSync(path); From 8edbcb577a5fd468970cf5a149e5456bc2b970e6 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Fri, 22 Sep 2017 14:11:53 +0300 Subject: [PATCH 10/14] path to captcha client validator fixed --- framework/jquery/ActiveFormClientScript.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/jquery/ActiveFormClientScript.php b/framework/jquery/ActiveFormClientScript.php index 2daad14dae2..fc14f990825 100644 --- a/framework/jquery/ActiveFormClientScript.php +++ b/framework/jquery/ActiveFormClientScript.php @@ -51,7 +51,7 @@ class ActiveFormClientScript extends \yii\widgets\ActiveFormClientScript \yii\validators\UrlValidator::class => \yii\jquery\validators\client\UrlValidator::class, \yii\validators\ImageValidator::class => \yii\jquery\validators\client\ImageValidator::class, \yii\validators\FileValidator::class => \yii\jquery\validators\client\FileValidator::class, - \yii\captcha\CaptchaValidator::class => \yii\captcha\CaptchaClientValidator::class, + \yii\captcha\CaptchaValidator::class => \yii\jquery\validators\client\CaptchaClientValidator::class, ]; From eef2e872f927e0941ab3c01923b07e219c85d3ec Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Sun, 24 Sep 2017 13:38:37 +0300 Subject: [PATCH 11/14] `ActiveFormClientScript::defaultClientValidatorMap()` added --- framework/jquery/ActiveFormClientScript.php | 44 ++++++++++---------- framework/widgets/ActiveFormClientScript.php | 37 ++++++++++++++-- 2 files changed, 56 insertions(+), 25 deletions(-) diff --git a/framework/jquery/ActiveFormClientScript.php b/framework/jquery/ActiveFormClientScript.php index fc14f990825..50e18a18572 100644 --- a/framework/jquery/ActiveFormClientScript.php +++ b/framework/jquery/ActiveFormClientScript.php @@ -35,28 +35,30 @@ class ActiveFormClientScript extends \yii\widgets\ActiveFormClientScript { /** - * @inheritdoc + * {@inheritdoc} */ - public $clientValidatorMap = [ - \yii\validators\BooleanValidator::class => \yii\jquery\validators\client\BooleanValidator::class, - \yii\validators\CompareValidator::class => \yii\jquery\validators\client\CompareValidator::class, - \yii\validators\EmailValidator::class => \yii\jquery\validators\client\EmailValidator::class, - \yii\validators\FilterValidator::class => \yii\jquery\validators\client\FilterValidator::class, - \yii\validators\IpValidator::class => \yii\jquery\validators\client\IpValidator::class, - \yii\validators\NumberValidator::class => \yii\jquery\validators\client\NumberValidator::class, - \yii\validators\RangeValidator::class => \yii\jquery\validators\client\RangeValidator::class, - \yii\validators\RegularExpressionValidator::class => \yii\jquery\validators\client\RegularExpressionValidator::class, - \yii\validators\RequiredValidator::class => \yii\jquery\validators\client\RequiredValidator::class, - \yii\validators\StringValidator::class => \yii\jquery\validators\client\StringValidator::class, - \yii\validators\UrlValidator::class => \yii\jquery\validators\client\UrlValidator::class, - \yii\validators\ImageValidator::class => \yii\jquery\validators\client\ImageValidator::class, - \yii\validators\FileValidator::class => \yii\jquery\validators\client\FileValidator::class, - \yii\captcha\CaptchaValidator::class => \yii\jquery\validators\client\CaptchaClientValidator::class, - ]; - + protected function defaultClientValidatorMap() + { + return [ + \yii\validators\BooleanValidator::class => \yii\jquery\validators\client\BooleanValidator::class, + \yii\validators\CompareValidator::class => \yii\jquery\validators\client\CompareValidator::class, + \yii\validators\EmailValidator::class => \yii\jquery\validators\client\EmailValidator::class, + \yii\validators\FilterValidator::class => \yii\jquery\validators\client\FilterValidator::class, + \yii\validators\IpValidator::class => \yii\jquery\validators\client\IpValidator::class, + \yii\validators\NumberValidator::class => \yii\jquery\validators\client\NumberValidator::class, + \yii\validators\RangeValidator::class => \yii\jquery\validators\client\RangeValidator::class, + \yii\validators\RegularExpressionValidator::class => \yii\jquery\validators\client\RegularExpressionValidator::class, + \yii\validators\RequiredValidator::class => \yii\jquery\validators\client\RequiredValidator::class, + \yii\validators\StringValidator::class => \yii\jquery\validators\client\StringValidator::class, + \yii\validators\UrlValidator::class => \yii\jquery\validators\client\UrlValidator::class, + \yii\validators\ImageValidator::class => \yii\jquery\validators\client\ImageValidator::class, + \yii\validators\FileValidator::class => \yii\jquery\validators\client\FileValidator::class, + \yii\captcha\CaptchaValidator::class => \yii\jquery\validators\client\CaptchaClientValidator::class, + ]; + } /** - * @inheritdoc + * {@inheritdoc} */ protected function registerClientScript() { @@ -69,7 +71,7 @@ protected function registerClientScript() } /** - * @inheritdoc + * {@inheritdoc} */ protected function getFieldClientOptions($field) { @@ -88,7 +90,7 @@ protected function getFieldClientOptions($field) } /** - * @inheritdoc + * {@inheritdoc} */ protected function getClientOptions() { diff --git a/framework/widgets/ActiveFormClientScript.php b/framework/widgets/ActiveFormClientScript.php index 12b767e8b13..7f5f3580b44 100644 --- a/framework/widgets/ActiveFormClientScript.php +++ b/framework/widgets/ActiveFormClientScript.php @@ -26,7 +26,7 @@ abstract class ActiveFormClientScript extends Behavior { /** - * @var array client validator class map in format: [server-side-validator-class => client-side-validator]. + * @var array client validator class map in format: `[server-side-validator-class => client-side-validator]`. * Client side validator should be specified as an instance of [[\yii\validators\client\ClientValidator]] or * its DI compatible configuration. * @@ -34,15 +34,21 @@ abstract class ActiveFormClientScript extends Behavior * `ChildValidator` in case it extends `ParentValidator`. In case maps for both `ParentValidator` and `ChildValidator` * are specified the first value will take precedence. * + * Result of [[defaultClientValidatorMap()]] method will be merged into this field at behavior initialization. + * In order to disable mapping for pre-defined validator use `false` value. + * * For example: * * ```php * [ * \yii\validators\BooleanValidator::class => \yii\jquery\validators\client\BooleanValidator::class, * \yii\validators\ImageValidator::class => \yii\jquery\validators\client\ImageValidator::class, - * \yii\validators\FileValidator::class => \yii\jquery\validators\client\FileValidator::class, + * \yii\validators\FileValidator::class => false, // disable client validation for `FileValidator` * ] * ``` + * + * You should use this field only in case particular client script does not provide any default mapping or + * in case you wish to override this mapping. */ public $clientValidatorMap = []; @@ -54,7 +60,20 @@ abstract class ActiveFormClientScript extends Behavior /** - * @inheritdoc + * {@inheritdoc} + */ + public function init() + { + parent::init(); + + $this->clientValidatorMap = array_merge( + $this->defaultClientValidatorMap(), + $this->clientValidatorMap + ); + } + + /** + * {@inheritdoc} */ public function events() { @@ -203,7 +222,7 @@ protected function getClientOptions() protected function buildClientValidator($validator, $model, $attribute, $view) { foreach ($this->clientValidatorMap as $serverSideValidatorClass => $clientSideValidator) { - if ($validator instanceof $serverSideValidatorClass) { + if ($clientSideValidator !== false && $validator instanceof $serverSideValidatorClass) { /* @var $clientValidator \yii\validators\client\ClientValidator */ $clientValidator = Yii::createObject($clientSideValidator); return $clientValidator->build($validator, $model, $attribute, $view); @@ -211,4 +230,14 @@ protected function buildClientValidator($validator, $model, $attribute, $view) } return null; } + + /** + * Returns default client validator map, which will be merged with [[clientValidatorMap]] at [[init()]]. + * Child class may override this method providing validator map specific for particular client script. + * @return array client validator class map in format: `[server-side-validator-class => client-side-validator]`. + */ + protected function defaultClientValidatorMap() + { + return []; + } } \ No newline at end of file From b79f53e9ed178914f5e98a1774fa036185236e5a Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Sun, 24 Sep 2017 13:53:06 +0300 Subject: [PATCH 12/14] UPGRADE note extended --- framework/UPGRADE.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/framework/UPGRADE.md b/framework/UPGRADE.md index 70a2504ee80..4d34e915242 100644 --- a/framework/UPGRADE.md +++ b/framework/UPGRADE.md @@ -136,9 +136,19 @@ Upgrade from Yii 2.0.x * Classes `yii\widgets\ActiveForm`, `yii\widgets\ActiveField`, `yii\grid\GridView`, `yii\web\View` have been refactored to be more generic without including any 'JQuery' support and client-side processing (validation, automatic submit etc.). You should use widget behaviors from `yii\jquery\*` package to make old code function as before. E.g. attach `yii\jquery\ActiveFormClientScript` - to `yii\widgets\ActiveForm`, `yii\jquery\GridViewClientScript` `yii\grid\GridView` and so on. + to `yii\widgets\ActiveForm`, `yii\jquery\GridViewClientScript` to `yii\grid\GridView` and so on. +* Fields `$enableClientScript` and `$attributes` have been removed from `yii\widgets\ActiveForm`. Make sure + you do not use them or specify them during `ActiveForm::begin()` invocation. +* Field `yii\grid\GridView::$filterSelector` has been removed. Make sure you do not use it or specify it during + `GridView::widget()` invocation. Use `yii\jquery\GridViewClientScript::$filterSelector` instead. +* Method `getClientOptions()` has been removed from `yii\validators\Validator` and all its descendants. + All implementations of `clientValidateAttribute()` around built-in validators now return `null`. + Use classes from `yii\jquery\validators\client\*` for building client validation (JavaScript) code. * Assets `yii\web\JqueryAsset`, `yii\web\YiiAsset`, `yii\validators\ValidationAsset` have been moved under `yii\jquery\*` namespace. Make sure you refer to the new full-qualified names of this classes. +* Methods `yii\validators\Validator::formatMessage()`, `yii\validators\IpValidator::getIpParsePattern()` and + `yii\validators\FileValidator::buildMimeTypeRegexp()` have been made `public`. Make sure you use correct + access level specification in case you override these methods. * Default script position for the `yii\web\View::registerJs()` changed to `View::POS_END`. From 65488682f80e91108301a423a45a56576ed525c5 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Sat, 30 Dec 2017 19:50:48 +0200 Subject: [PATCH 13/14] `CompareValidator` adjusted --- framework/validators/CompareValidator.php | 1 - 1 file changed, 1 deletion(-) diff --git a/framework/validators/CompareValidator.php b/framework/validators/CompareValidator.php index 484aa0868e3..6f6440d7fc0 100644 --- a/framework/validators/CompareValidator.php +++ b/framework/validators/CompareValidator.php @@ -9,7 +9,6 @@ use Yii; use yii\base\InvalidConfigException; -use yii\helpers\Html; /** * CompareValidator compares the specified attribute value with another value. From 5469ce756bee1c0d2d5c4eecf715b8b9bc490d65 Mon Sep 17 00:00:00 2001 From: Klimov Paul Date: Sat, 30 Dec 2017 20:14:20 +0200 Subject: [PATCH 14/14] `View::registerCsrfMetaTags()` restored --- framework/web/View.php | 21 +++++++++++++++++++++ tests/framework/web/ViewTest.php | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/framework/web/View.php b/framework/web/View.php index 31d6e401e4d..5b90d2849d2 100644 --- a/framework/web/View.php +++ b/framework/web/View.php @@ -370,6 +370,27 @@ public function registerLinkTag($options, $key = null) } } + /** + * Registers CSRF meta tags. + * They are rendered dynamically to retrieve a new CSRF token for each request. + * + * ```php + * $view->registerCsrfMetaTags(); + * ``` + * + * The above code will result in `` + * and `` added to the page. + * + * Note: Hidden CSRF input of ActiveForm will be automatically refreshed by calling `window.yii.refreshCsrfToken()` + * from `yii.js`. + * + * @since 2.0.13 + */ + public function registerCsrfMetaTags() + { + $this->metaTags['csrf_meta_tags'] = $this->renderDynamic('return yii\helpers\Html::csrfMetaTags();'); + } + /** * Registers a CSS code block. * @param string $css the content of the CSS code block to be registered diff --git a/tests/framework/web/ViewTest.php b/tests/framework/web/ViewTest.php index 8e47cb086e5..0cef4158a1b 100644 --- a/tests/framework/web/ViewTest.php +++ b/tests/framework/web/ViewTest.php @@ -65,7 +65,7 @@ public function testRegisterCssFileWithAlias() $this->assertContains('', $html); } - public function testRegisterregisterCsrfMetaTags() + public function testRegisterCsrfMetaTags() { $this->mockWebApplication([ 'components' => [