├── composer.assets.json
├── .phpunit-watcher.yml
├── config
└── tests.php
├── src
├── JqueryAsset.php
├── YiiAsset.php
├── ActiveFormAsset.php
├── GridViewAsset.php
├── ValidationAsset.php
├── PunycodeAsset.php
├── Validators
│ └── Client
│ │ ├── FilterValidator.php
│ │ ├── RegularExpressionValidator.php
│ │ ├── CaptchaClientValidator.php
│ │ ├── BooleanValidator.php
│ │ ├── RequiredValidator.php
│ │ ├── EmailValidator.php
│ │ ├── RangeValidator.php
│ │ ├── UrlValidator.php
│ │ ├── CompareValidator.php
│ │ ├── StringValidator.php
│ │ ├── IpValidator.php
│ │ ├── NumberValidator.php
│ │ ├── ImageValidator.php
│ │ └── FileValidator.php
├── GridViewClientScript.php
├── ActiveFormClientScript.php
└── assets
│ ├── yii.gridView.js
│ ├── yii.validation.js
│ ├── yii.js
│ └── yii.activeForm.js
├── package.json
├── README.md
├── LICENSE.md
├── .styleci.yml
├── composer.json
└── .travis.yml
/composer.assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "require": {
3 | "npm-asset/jquery": "3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable",
4 | "npm-asset/punycode": "1.3.*"
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/.phpunit-watcher.yml:
--------------------------------------------------------------------------------
1 | watch:
2 | directories:
3 | - src
4 | - tests
5 | fileMask: '*.php'
6 | notifications:
7 | passingTests: false
8 | failingTests: false
9 | phpunit:
10 | binaryPath: vendor/bin/phpunit
11 | timeout: 180
12 |
--------------------------------------------------------------------------------
/config/tests.php:
--------------------------------------------------------------------------------
1 | [
9 | '@root' => dirname(__DIR__, 1),
10 | '@public' => '@root/tests/public',
11 | '@basePath' => '@public/assets',
12 | '@baseUrl' => '/baseUrl',
13 | '@npm' => '@root/node_modules',
14 | ],
15 | ];
16 |
--------------------------------------------------------------------------------
/src/JqueryAsset.php:
--------------------------------------------------------------------------------
1 |
2 |
3 | This package is deprecated.
4 |
5 |
6 |
7 | ❌
8 |
9 |
10 | ---
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Yii Framework jQuery Extension
20 |
21 |
22 |
23 | This extension provides the [jQuery] for the [Yii framework].
24 |
25 | [jQuery]: https://jquery.com/
26 | [Yii Framework]: https://www.yiiframework.com/
27 |
28 | For license information check the [LICENSE](LICENSE.md)-file.
29 |
30 | Documentation is at [docs/guide/README.md](docs/guide/README.md).
31 |
32 | [](https://packagist.org/packages/yiisoft/yii-jquery)
33 | [](https://packagist.org/packages/yiisoft/yii-jquery)
34 | [](https://travis-ci.com/yiisoft/yii-jquery)
35 |
36 |
37 | Installation
38 | ------------
39 |
40 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
41 |
42 | ```
43 | php composer.phar require --prefer-dist yiisoft/yii-jquery
44 | ```
45 |
--------------------------------------------------------------------------------
/src/Validators/Client/FilterValidator.php:
--------------------------------------------------------------------------------
1 | getClientOptions($validator, $model, $attribute);
25 |
26 | return 'value = yii.validation.trim($form, attribute, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
27 | }
28 |
29 | /**
30 | * Returns the client-side validation options.
31 | *
32 | * @param \yii\validators\FilterValidator $validator the server-side validator.
33 | * @param \yii\base\Model $model the model being validated
34 | * @param string $attribute the attribute name being validated
35 | *
36 | * @return array the client-side validation options
37 | */
38 | public function getClientOptions($validator, $model, $attribute)
39 | {
40 | $options = [];
41 |
42 | if ($validator->skipOnEmpty) {
43 | $options['skipOnEmpty'] = 1;
44 | }
45 |
46 | return $options;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright © 2008 by Yii Software (https://www.yiiframework.com/)
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 |
8 | * Redistributions of source code must retain the above copyright
9 | notice, this list of conditions and the following disclaimer.
10 | * Redistributions in binary form must reproduce the above copyright
11 | notice, this list of conditions and the following disclaimer in
12 | the documentation and/or other materials provided with the
13 | distribution.
14 | * Neither the name of Yii Software nor the names of its
15 | contributors may be used to endorse or promote products derived
16 | from this software without specific prior written permission.
17 |
18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 | POSSIBILITY OF SUCH DAMAGE.
30 |
--------------------------------------------------------------------------------
/src/Validators/Client/RegularExpressionValidator.php:
--------------------------------------------------------------------------------
1 | getClientOptions($validator, $model, $attribute);
28 |
29 | return 'yii.validation.regularExpression(value, messages, ' . Json::htmlEncode($options) . ');';
30 | }
31 |
32 | /**
33 | * Returns the client-side validation options.
34 | *
35 | * @param \yii\validators\RegularExpressionValidator $validator the server-side validator.
36 | * @param \yii\base\Model $model the model being validated
37 | * @param string $attribute the attribute name being validated
38 | *
39 | * @return array the client-side validation options
40 | */
41 | public function getClientOptions($validator, $model, $attribute)
42 | {
43 | $pattern = Html::escapeJsRegularExpression($validator->pattern);
44 |
45 | $options = [
46 | 'pattern' => new JsExpression($pattern),
47 | 'not' => $validator->not,
48 | 'message' => $validator->formatMessage($validator->message, [
49 | 'attribute' => $model->getAttributeLabel($attribute),
50 | ]),
51 | ];
52 |
53 | if ($validator->skipOnEmpty) {
54 | $options['skipOnEmpty'] = 1;
55 | }
56 |
57 | return $options;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Validators/Client/CaptchaClientValidator.php:
--------------------------------------------------------------------------------
1 | getClientOptions($validator, $model, $attribute);
25 | return 'yii.validation.captcha(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
26 | }
27 |
28 | /**
29 | * Returns the client-side validation options.
30 | *
31 | * @param \Yiisoft\Yii\Captcha\CaptchaValidator $validator the server-side validator.
32 | * @param \yii\base\Model $model the model being validated
33 | * @param string $attribute the attribute name being validated
34 | *
35 | * @return array the client-side validation options
36 | */
37 | public function getClientOptions($validator, $model, $attribute)
38 | {
39 | $captcha = $validator->createCaptchaAction();
40 | $code = $captcha->getVerifyCode(false);
41 | $hash = $captcha->generateValidationHash($validator->caseSensitive ? $code : strtolower($code));
42 | $options = [
43 | 'hash' => $hash,
44 | 'hashKey' => 'yiiCaptcha/' . $captcha->getUniqueId(),
45 | 'caseSensitive' => $validator->caseSensitive,
46 | 'message' => $validator->formatMessage($validator->message, [
47 | 'attribute' => $model->getAttributeLabel($attribute),
48 | ]),
49 | ];
50 | if ($validator->skipOnEmpty) {
51 | $options['skipOnEmpty'] = 1;
52 | }
53 |
54 | return $options;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Validators/Client/BooleanValidator.php:
--------------------------------------------------------------------------------
1 | getClientOptions($validator, $model, $attribute);
25 | return 'yii.validation.boolean(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
26 | }
27 |
28 | /**
29 | * Returns the client-side validation options.
30 | *
31 | * @param \yii\validators\BooleanValidator $validator the server-side validator.
32 | * @param \yii\base\Model $model the model being validated
33 | * @param string $attribute the attribute name being validated
34 | *
35 | * @return array the client-side validation options
36 | */
37 | public function getClientOptions($validator, $model, $attribute)
38 | {
39 | $options = [
40 | 'trueValue' => $validator->trueValue,
41 | 'falseValue' => $validator->falseValue,
42 | 'message' => $validator->formatMessage($validator->message, [
43 | 'attribute' => $model->getAttributeLabel($attribute),
44 | 'true' => $validator->trueValue === true ? 'true' : $validator->trueValue,
45 | 'false' => $validator->falseValue === false ? 'false' : $validator->falseValue,
46 | ]),
47 | ];
48 | if ($validator->skipOnEmpty) {
49 | $options['skipOnEmpty'] = 1;
50 | }
51 | if ($validator->strict) {
52 | $options['strict'] = 1;
53 | }
54 |
55 | return $options;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Validators/Client/RequiredValidator.php:
--------------------------------------------------------------------------------
1 | getClientOptions($validator, $model, $attribute);
25 |
26 | return 'yii.validation.required(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
27 | }
28 |
29 | /**
30 | * Returns the client-side validation options.
31 | *
32 | * @param \yii\validators\RequiredValidator $validator the server-side validator.
33 | * @param \yii\base\Model $model the model being validated
34 | * @param string $attribute the attribute name being validated
35 | *
36 | * @return array the client-side validation options
37 | */
38 | public function getClientOptions($validator, $model, $attribute)
39 | {
40 | $options = [];
41 |
42 | if ($validator->requiredValue !== null) {
43 | $options['message'] = $validator->formatMessage($validator->message, [
44 | 'requiredValue' => $validator->requiredValue,
45 | ]);
46 | $options['requiredValue'] = $validator->requiredValue;
47 | } else {
48 | $options['message'] = $validator->message;
49 | }
50 |
51 | if ($validator->strict) {
52 | $options['strict'] = 1;
53 | }
54 |
55 | $options['message'] = $validator->formatMessage($options['message'], [
56 | 'attribute' => $model->getAttributeLabel($attribute),
57 | ]);
58 |
59 | return $options;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Validators/Client/EmailValidator.php:
--------------------------------------------------------------------------------
1 | enableIDN) {
30 | PunycodeAsset::register($view);
31 | }
32 |
33 | $options = $this->getClientOptions($validator, $model, $attribute);
34 |
35 | return 'yii.validation.email(value, messages, ' . Json::htmlEncode($options) . ');';
36 | }
37 |
38 | /**
39 | * Returns the client-side validation options.
40 | *
41 | * @param \yii\validators\EmailValidator $validator the server-side validator.
42 | * @param \yii\base\Model $model the model being validated
43 | * @param string $attribute the attribute name being validated
44 | *
45 | * @return array the client-side validation options
46 | */
47 | public function getClientOptions($validator, $model, $attribute)
48 | {
49 | $options = [
50 | 'pattern' => new JsExpression($validator->pattern),
51 | 'fullPattern' => new JsExpression($validator->fullPattern),
52 | 'allowName' => $validator->allowName,
53 | 'message' => $validator->formatMessage($validator->message, [
54 | 'attribute' => $model->getAttributeLabel($attribute),
55 | ]),
56 | 'enableIDN' => (bool)$validator->enableIDN,
57 | ];
58 |
59 | if ($validator->skipOnEmpty) {
60 | $options['skipOnEmpty'] = 1;
61 | }
62 |
63 | return $options;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/Validators/Client/RangeValidator.php:
--------------------------------------------------------------------------------
1 | range instanceof \Closure) {
25 | $validator->range = ($validator->range)($model, $attribute);
26 | }
27 |
28 | ValidationAsset::register($view);
29 |
30 | $options = $this->getClientOptions($validator, $model, $attribute);
31 |
32 | return 'yii.validation.range(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
33 | }
34 |
35 | /**
36 | * Returns the client-side validation options.
37 | *
38 | * @param \yii\validators\RangeValidator $validator the server-side validator.
39 | * @param \yii\base\Model $model the model being validated
40 | * @param string $attribute the attribute name being validated
41 | *
42 | * @return array the client-side validation options
43 | */
44 | public function getClientOptions($validator, $model, $attribute)
45 | {
46 | $range = [];
47 |
48 | foreach ($validator->range as $value) {
49 | $range[] = (string) $value;
50 | }
51 |
52 | $options = [
53 | 'range' => $range,
54 | 'not' => $validator->not,
55 | 'message' => $validator->formatMessage($validator->message, [
56 | 'attribute' => $model->getAttributeLabel($attribute),
57 | ]),
58 | ];
59 |
60 | if ($validator->skipOnEmpty) {
61 | $options['skipOnEmpty'] = 1;
62 | }
63 |
64 | if ($validator->allowArray) {
65 | $options['allowArray'] = 1;
66 | }
67 |
68 | return $options;
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/.styleci.yml:
--------------------------------------------------------------------------------
1 | preset: psr12
2 | risky: true
3 |
4 | version: 8
5 |
6 | finder:
7 | exclude:
8 | - docs
9 | - vendor
10 | - resources
11 | - views
12 | - public
13 | - templates
14 | not-name:
15 | - UnionCar.php
16 | - TimerUnionTypes.php
17 | - schema1.php
18 |
19 | enabled:
20 | - alpha_ordered_traits
21 | - array_indentation
22 | - array_push
23 | - combine_consecutive_issets
24 | - combine_consecutive_unsets
25 | - combine_nested_dirname
26 | - declare_strict_types
27 | - dir_constant
28 | - fully_qualified_strict_types
29 | - function_to_constant
30 | - hash_to_slash_comment
31 | - is_null
32 | - logical_operators
33 | - magic_constant_casing
34 | - magic_method_casing
35 | - method_separation
36 | - modernize_types_casting
37 | - native_function_casing
38 | - native_function_type_declaration_casing
39 | - no_alias_functions
40 | - no_empty_comment
41 | - no_empty_phpdoc
42 | - no_empty_statement
43 | - no_extra_block_blank_lines
44 | - no_short_bool_cast
45 | - no_superfluous_elseif
46 | - no_unneeded_control_parentheses
47 | - no_unneeded_curly_braces
48 | - no_unneeded_final_method
49 | - no_unset_cast
50 | - no_unused_imports
51 | - no_unused_lambda_imports
52 | - no_useless_else
53 | - no_useless_return
54 | - normalize_index_brace
55 | - php_unit_dedicate_assert
56 | - php_unit_dedicate_assert_internal_type
57 | - php_unit_expectation
58 | - php_unit_mock
59 | - php_unit_mock_short_will_return
60 | - php_unit_namespaced
61 | - php_unit_no_expectation_annotation
62 | - phpdoc_no_empty_return
63 | - phpdoc_no_useless_inheritdoc
64 | - phpdoc_order
65 | - phpdoc_property
66 | - phpdoc_scalar
67 | - phpdoc_separation
68 | - phpdoc_singular_inheritdoc
69 | - phpdoc_trim
70 | - phpdoc_trim_consecutive_blank_line_separation
71 | - phpdoc_type_to_var
72 | - phpdoc_types
73 | - phpdoc_types_order
74 | - print_to_echo
75 | - regular_callable_call
76 | - return_assignment
77 | - self_accessor
78 | - self_static_accessor
79 | - set_type_to_cast
80 | - short_array_syntax
81 | - short_list_syntax
82 | - simplified_if_return
83 | - single_quote
84 | - standardize_not_equals
85 | - ternary_to_null_coalescing
86 | - trailing_comma_in_multiline_array
87 | - unalign_double_arrow
88 | - unalign_equals
89 |
--------------------------------------------------------------------------------
/src/Validators/Client/UrlValidator.php:
--------------------------------------------------------------------------------
1 | enableIDN) {
30 | PunycodeAsset::register($view);
31 | }
32 |
33 | $options = $this->getClientOptions($validator, $model, $attribute);
34 |
35 | return 'yii.validation.url(value, messages, ' . Json::htmlEncode($options) . ');';
36 | }
37 |
38 | /**
39 | * Returns the client-side validation options.
40 | *
41 | * @param \yii\validators\UrlValidator $validator the server-side validator.
42 | * @param \yii\base\Model $model the model being validated
43 | * @param string $attribute the attribute name being validated
44 | *
45 | * @return array the client-side validation options
46 | */
47 | public function getClientOptions($validator, $model, $attribute)
48 | {
49 | if (strpos($validator->pattern, '{schemes}') !== false) {
50 | $pattern = str_replace('{schemes}', '(' . implode('|', $validator->validSchemes) . ')', $validator->pattern);
51 | } else {
52 | $pattern = $validator->pattern;
53 | }
54 |
55 | $options = [
56 | 'pattern' => new JsExpression($pattern),
57 | 'message' => $validator->formatMessage($validator->message, [
58 | 'attribute' => $model->getAttributeLabel($attribute),
59 | ]),
60 | 'enableIDN' => (bool) $validator->enableIDN,
61 | ];
62 |
63 | if ($validator->skipOnEmpty) {
64 | $options['skipOnEmpty'] = 1;
65 | }
66 |
67 | if ($validator->defaultScheme !== null) {
68 | $options['defaultScheme'] = $validator->defaultScheme;
69 | }
70 |
71 | return $options;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "yiisoft/yii-jquery",
3 | "type": "library",
4 | "description": "Yii Framework jQuery Extension",
5 | "keywords": [
6 | "yii",
7 | "jquery",
8 | "javascript",
9 | "client script"
10 | ],
11 | "license": "BSD-3-Clause",
12 | "support": {
13 | "source": "https://github.com/yiisoft/yii-jquery",
14 | "issues": "https://github.com/yiisoft/yii-jquery/issues",
15 | "forum": "https://www.yiiframework.com/forum/",
16 | "wiki": "https://www.yiiframework.com/wiki/",
17 | "irc": "irc://irc.freenode.net/yii"
18 | },
19 | "minimum-stability": "dev",
20 | "prefer-stable": true,
21 | "require": {
22 | "php": "^7.4|^8.0",
23 | "nyholm/psr7": "^1.0",
24 | "psr/event-dispatcher": "1.0.0",
25 | "yiisoft/assets": "^2.0",
26 | "yiisoft/html": "^2.0"
27 | },
28 | "require-dev": {
29 | "foxy/foxy": "^1.0",
30 | "phan/phan": "^3.0",
31 | "phpunit/phpunit": "^9.4",
32 | "roave/infection-static-analysis-plugin": "^1.5",
33 | "spatie/phpunit-watcher": "^1.23",
34 | "vimeo/psalm": "^4.2",
35 | "yiisoft/cache": "^1.0",
36 | "yiisoft/composer-config-plugin": "^1.0@dev",
37 | "yiisoft/di": "^1.0",
38 | "yiisoft/event-dispatcher": "^1.0",
39 | "yiisoft/log": "^1.0",
40 | "yiisoft/yii-dataview": "^3.0@dev"
41 | },
42 | "repositories": [
43 | {
44 | "type": "composer",
45 | "url": "https://asset-packagist.org"
46 | }
47 | ],
48 | "autoload": {
49 | "psr-4": {
50 | "Yiisoft\\Yii\\JQuery\\": "src"
51 | }
52 | },
53 | "autoload-dev": {
54 | "psr-4": {
55 | "Yiisoft\\Yii\\JQuery\\Tests\\": "tests"
56 | }
57 | },
58 | "extra": {
59 | "branch-alias": {
60 | "dev-master": "3.0.x-dev"
61 | },
62 | "config-plugin-options": {
63 | "source-directory": "config"
64 | },
65 | "config-plugin": {
66 | "tests": "tests.php"
67 | }
68 | },
69 | "config": {
70 | "sort-packages": true,
71 | "allow-plugins": {
72 | "infection/extension-installer": true,
73 | "composer/package-versions-deprecated": true
74 | }
75 | },
76 | "scripts": {
77 | "test": "phpunit --testdox --no-interaction",
78 | "test-watch": "phpunit-watcher watch"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/GridViewClientScript.php:
--------------------------------------------------------------------------------
1 | dataProvider($dataProvider)
22 | * ->asClientScript([
23 | * 'class' => Yiisoft\Yii\JQuery\GridViewClientScript::class
24 | * ])
25 | * ->columns([
26 | * 'id',
27 | * 'name',
28 | * 'created_at:datetime',
29 | * // ...
30 | * ]); ?>
31 | * ```
32 | *
33 | * @see \Yiisoft\Yii\DataView\GridView
34 | * @see GridViewAsset
35 | *
36 | * @property \Yiisoft\Yii\DataView\GridView $owner the owner of this behavior.
37 | */
38 | class GridViewClientScript extends Behavior
39 | {
40 | /**
41 | * @var string additional jQuery selector for selecting filter input fields.
42 | */
43 | public $filterSelector;
44 |
45 | /**
46 | * {@inheritdoc}
47 | */
48 | public function events()
49 | {
50 | return [
51 | RunEvent::BEFORE => 'beforeRun',
52 | ];
53 | }
54 |
55 | /**
56 | * Handles {@see \Yiisoft\Widget\Widget::EVENT_BEFORE_RUN} event, registering related client script.
57 | *
58 | * @param \Yiisoft\Widget\Event\BeforeRun $event event instance.
59 | */
60 | public function beforeRun(BeforeRun $event)
61 | {
62 | $id = $this->owner->options['id'];
63 | $options = Json::htmlEncode($this->getClientOptions());
64 | $view = $this->owner->getView();
65 |
66 | GridViewAsset::register($view);
67 | $view->registerJs("jQuery('#$id').yiiGridView($options);");
68 | }
69 |
70 | /**
71 | * Returns the options for the grid view JS widget.
72 | *
73 | * @return array the options
74 | */
75 | protected function getClientOptions()
76 | {
77 | $filterUrl = $this->owner->filterUrl ?? Yii::getApp()->request->url;
78 | $id = $this->owner->filterRowOptions['id'];
79 | $filterSelector = "#$id input, #$id select";
80 | if (isset($this->filterSelector)) {
81 | $filterSelector .= ', ' . $this->filterSelector;
82 | }
83 |
84 | return [
85 | 'filterUrl' => Url::to($filterUrl),
86 | 'filterSelector' => $filterSelector,
87 | ];
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Validators/Client/CompareValidator.php:
--------------------------------------------------------------------------------
1 | getClientOptions($validator, $model, $attribute);
26 |
27 | return 'yii.validation.compare(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
28 | }
29 |
30 | /**
31 | * Returns the client-side validation options.
32 | *
33 | * @param \yii\validators\CompareValidator $validator the server-side validator.
34 | * @param \yii\base\Model $model the model being validated
35 | * @param string $attribute the attribute name being validated
36 | *
37 | * @return array the client-side validation options
38 | */
39 | public function getClientOptions($validator, $model, $attribute)
40 | {
41 | $options = [
42 | 'operator' => $validator->operator,
43 | 'type' => $validator->type,
44 | ];
45 |
46 | if ($validator->compareValue !== null) {
47 | $options['compareValue'] = $validator->compareValue;
48 | $compareLabel = $compareValue = $compareValueOrAttribute = $validator->compareValue;
49 | } else {
50 | $compareAttribute = $validator->compareAttribute === null ? $attribute . '_repeat' : $validator->compareAttribute;
51 | $compareValue = $model->getAttributeLabel($compareAttribute);
52 | $options['compareAttribute'] = Html::getInputId($model, $compareAttribute);
53 | $compareLabel = $compareValueOrAttribute = $model->getAttributeLabel($compareAttribute);
54 | }
55 |
56 | if ($validator->skipOnEmpty) {
57 | $options['skipOnEmpty'] = 1;
58 | }
59 |
60 | $options['message'] = $validator->formatMessage($validator->message, [
61 | 'attribute' => $model->getAttributeLabel($attribute),
62 | 'compareAttribute' => $compareLabel,
63 | 'compareValue' => $compareValue,
64 | 'compareValueOrAttribute' => $compareValueOrAttribute,
65 | ]);
66 |
67 | return $options;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Validators/Client/StringValidator.php:
--------------------------------------------------------------------------------
1 | getClientOptions($validator, $model, $attribute);
25 |
26 | return 'yii.validation.string(value, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ');';
27 | }
28 |
29 | /**
30 | * Returns the client-side validation options.
31 | *
32 | * @param \yii\validators\StringValidator $validator the server-side validator.
33 | * @param \yii\base\Model $model the model being validated
34 | * @param string $attribute the attribute name being validated
35 | *
36 | * @return array the client-side validation options
37 | */
38 | public function getClientOptions($validator, $model, $attribute)
39 | {
40 | $label = $model->getAttributeLabel($attribute);
41 |
42 | $options = [
43 | 'message' => $validator->formatMessage($validator->message, [
44 | 'attribute' => $label,
45 | ]),
46 | ];
47 |
48 | if ($validator->min !== null) {
49 | $options['min'] = $validator->min;
50 | $options['tooShort'] = $validator->formatMessage($validator->tooShort, [
51 | 'attribute' => $label,
52 | 'min' => $validator->min,
53 | ]);
54 | }
55 |
56 | if ($validator->max !== null) {
57 | $options['max'] = $validator->max;
58 | $options['tooLong'] = $validator->formatMessage($validator->tooLong, [
59 | 'attribute' => $label,
60 | 'max' => $validator->max,
61 | ]);
62 | }
63 |
64 | if ($validator->length !== null) {
65 | $options['is'] = $validator->length;
66 | $options['notEqual'] = $validator->formatMessage($validator->notEqual, [
67 | 'attribute' => $label,
68 | 'length' => $validator->length,
69 | ]);
70 | }
71 |
72 | if ($validator->skipOnEmpty) {
73 | $options['skipOnEmpty'] = 1;
74 | }
75 |
76 | return $options;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Validators/Client/IpValidator.php:
--------------------------------------------------------------------------------
1 | getClientOptions($validator, $model, $attribute);
28 |
29 | return 'yii.validation.ip(value, messages, ' . Json::htmlEncode($options) . ');';
30 | }
31 |
32 | /**
33 | * Returns the client-side validation options.
34 | *
35 | * @param \yii\validators\IpValidator $validator the server-side validator.
36 | * @param \yii\base\Model $model the model being validated
37 | * @param string $attribute the attribute name being validated
38 | *
39 | * @return array the client-side validation options
40 | */
41 | public function getClientOptions($validator, $model, $attribute)
42 | {
43 | $messages = [
44 | 'ipv6NotAllowed' => $validator->ipv6NotAllowed,
45 | 'ipv4NotAllowed' => $validator->ipv4NotAllowed,
46 | 'message' => $validator->message,
47 | 'noSubnet' => $validator->noSubnet,
48 | 'hasSubnet' => $validator->hasSubnet,
49 | ];
50 |
51 | foreach ($messages as &$message) {
52 | $message = $validator->formatMessage($message, [
53 | 'attribute' => $model->getAttributeLabel($attribute),
54 | ]);
55 | }
56 |
57 | $options = [
58 | 'ipv4Pattern' => new JsExpression(Html::escapeJsRegularExpression($validator->ipv4Pattern)),
59 | 'ipv6Pattern' => new JsExpression(Html::escapeJsRegularExpression($validator->ipv6Pattern)),
60 | 'messages' => $messages,
61 | 'ipv4' => (bool) $validator->ipv4,
62 | 'ipv6' => (bool) $validator->ipv6,
63 | 'ipParsePattern' => new JsExpression(Html::escapeJsRegularExpression($validator->getIpParsePattern())),
64 | 'negation' => $validator->negation,
65 | 'subnet' => $validator->subnet,
66 | ];
67 |
68 | if ($validator->skipOnEmpty) {
69 | $options['skipOnEmpty'] = 1;
70 | }
71 |
72 | return $options;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/Validators/Client/NumberValidator.php:
--------------------------------------------------------------------------------
1 | getClientOptions($validator, $model, $attribute);
27 |
28 | return 'yii.validation.number(value, messages, ' . Json::htmlEncode($options) . ');';
29 | }
30 |
31 | /**
32 | * Returns the client-side validation options.
33 | *
34 | * @param \yii\validators\NumberValidator $validator the server-side validator.
35 | * @param \yii\base\Model $model the model being validated
36 | * @param string $attribute the attribute name being validated
37 | *
38 | * @return array the client-side validation options
39 | */
40 | public function getClientOptions($validator, $model, $attribute)
41 | {
42 | $label = $model->getAttributeLabel($attribute);
43 |
44 | $options = [
45 | 'pattern' => new JsExpression($validator->integerOnly ? $validator->integerPattern : $validator->numberPattern),
46 | 'message' => $validator->formatMessage($validator->message, [
47 | 'attribute' => $label,
48 | ]),
49 | ];
50 |
51 | if ($validator->min !== null) {
52 | // ensure numeric value to make javascript comparison equal to PHP comparison
53 | // https://github.com/yiisoft/yii2/issues/3118
54 | $options['min'] = is_string($validator->min) ? (float) $validator->min : $validator->min;
55 | $options['tooSmall'] = $validator->formatMessage($validator->tooSmall, [
56 | 'attribute' => $label,
57 | 'min' => $validator->min,
58 | ]);
59 | }
60 |
61 | if ($validator->max !== null) {
62 | // ensure numeric value to make javascript comparison equal to PHP comparison
63 | // https://github.com/yiisoft/yii2/issues/3118
64 | $options['max'] = is_string($validator->max) ? (float) $validator->max : $validator->max;
65 | $options['tooBig'] = $validator->formatMessage($validator->tooBig, [
66 | 'attribute' => $label,
67 | 'max' => $validator->max,
68 | ]);
69 | }
70 |
71 | if ($validator->skipOnEmpty) {
72 | $options['skipOnEmpty'] = 1;
73 | }
74 |
75 | return $options;
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/Validators/Client/ImageValidator.php:
--------------------------------------------------------------------------------
1 | getClientOptions($validator, $model, $attribute);
24 |
25 | return 'yii.validation.image(attribute, messages, ' . json_encode($options, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ', deferred);';
26 | }
27 |
28 | /**
29 | * Returns the client-side validation options.
30 | *
31 | * @param \yii\validators\ImageValidator $validator the server-side validator.
32 | * @param \yii\base\Model $model the model being validated
33 | * @param string $attribute the attribute name being validated
34 | *
35 | * @return array the client-side validation options
36 | */
37 | public function getClientOptions($validator, $model, $attribute)
38 | {
39 | $options = parent::getClientOptions($validator, $model, $attribute);
40 |
41 | $label = $model->getAttributeLabel($attribute);
42 |
43 | if ($validator->notImage !== null) {
44 | $options['notImage'] = $validator->formatMessage($validator->notImage, [
45 | 'attribute' => $label,
46 | ]);
47 | }
48 |
49 | if ($validator->minWidth !== null) {
50 | $options['minWidth'] = $validator->minWidth;
51 | $options['underWidth'] = $validator->formatMessage($validator->underWidth, [
52 | 'attribute' => $label,
53 | 'limit' => $validator->minWidth,
54 | ]);
55 | }
56 |
57 | if ($validator->maxWidth !== null) {
58 | $options['maxWidth'] = $validator->maxWidth;
59 | $options['overWidth'] = $validator->formatMessage($validator->overWidth, [
60 | 'attribute' => $label,
61 | 'limit' => $validator->maxWidth,
62 | ]);
63 | }
64 |
65 | if ($validator->minHeight !== null) {
66 | $options['minHeight'] = $validator->minHeight;
67 | $options['underHeight'] = $validator->formatMessage($validator->underHeight, [
68 | 'attribute' => $label,
69 | 'limit' => $validator->minHeight,
70 | ]);
71 | }
72 |
73 | if ($validator->maxHeight !== null) {
74 | $options['maxHeight'] = $validator->maxHeight;
75 | $options['overHeight'] = $validator->formatMessage($validator->overHeight, [
76 | 'attribute' => $label,
77 | 'limit' => $validator->maxHeight,
78 | ]);
79 | }
80 |
81 | return $options;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | env:
4 | global:
5 | - DEFAULT_COMPOSER_FLAGS="--prefer-dist --no-interaction --no-progress --optimize-autoloader"
6 | - TASK_STATIC_ANALYSIS=0
7 | - TASK_TESTS_COVERAGE=0
8 |
9 | matrix:
10 | include:
11 | - php: "7.4"
12 | env:
13 | - TASK_STATIC_ANALYSIS=0 # set to 1 to enable static analysis
14 | - TASK_TESTS_COVERAGE=1
15 |
16 | # faster builds on new travis setup not using sudo
17 | sudo: false
18 |
19 | # cache vendor dirs
20 | cache:
21 | directories:
22 | - $HOME/.composer/cache
23 |
24 | before_install:
25 | - |
26 | if [[ $TASK_TESTS_COVERAGE != 1 ]]; then
27 | # disable xdebug for performance reasons when code coverage is not needed
28 | phpenv config-rm xdebug.ini || echo "xdebug is not installed"
29 | fi
30 |
31 | install:
32 | - travis_retry composer self-update && composer --version
33 | - export PATH="$HOME/.composer/vendor/bin:$PATH"
34 | - travis_retry composer install $DEFAULT_COMPOSER_FLAGS
35 | - |
36 | if [ $TASK_STATIC_ANALYSIS == 1 ]; then
37 | pecl install ast
38 | fi
39 |
40 | before_script:
41 | - php --version
42 | - composer --version
43 | # enable code coverage
44 | - |
45 | if [ $TASK_TESTS_COVERAGE == 1 ]; then
46 | PHPUNIT_COVERAGE_FLAG="--coverage-clover=coverage.clover"
47 | fi
48 |
49 | script:
50 | - vendor/bin/phpunit --verbose $PHPUNIT_COVERAGE_FLAG
51 | - |
52 | if [ $TASK_STATIC_ANALYSIS == 1 ]; then
53 | composer phan
54 | fi
55 | - |
56 | if [ $TASK_STATIC_ANALYSIS == 1 ]; then
57 | cat analysis.txt
58 | fi
59 |
60 | after_script:
61 | - |
62 | if [ $TASK_TESTS_COVERAGE == 1 ]; then
63 | travis_retry wget https://scrutinizer-ci.com/ocular.phar
64 | php ocular.phar code-coverage:upload --format=php-clover coverage.clover
65 | fi
66 |
67 | # It should be generated for each repository
68 | # See https://github.com/yiisoft/yii-dev-tool/issues/14#issuecomment-561390952
69 | notifications:
70 | slack:
71 | -
72 | rooms:
73 | -
74 | secure: Qvx4x+h+s8HpOJRYElH5AG3csNFIsqUCcI8KTTflpP+LNDc6R4zhnV2538TrXNM4yZUT8e16x9wqurDs2/MNJOjgAUDhVlV9N2V/yDYEqyw3mvGr9MNIzDxRLlfpJugONhkO4+TL1go8FnBrXwCDpTvLxBo4HusbcSQYQxa6XtP6/KSfFmXh3feydRE95NKEUCJgIU9BPw9Mif99FrYBQGofsa78dR89tW55rfdNrz3daVOnH1iOyU7t+J9sx8JBklKOPawmKGttahbUxuChagiMdC8Onu1kEigo1FGKrif6o0Us2PXFoWvbMLhW4zgvKwSiCozB0FVIKRTBSM5VFGhRHxHaL3v82gicTxdfNyAG5TVAwgpoQoVQt6U27oFNU5LIpnqLM5mK4kDX/IExw/EtuQHZCNhXcM41PU0NJoBgLDyn0/dsPZsVSsQ5cwFvTkzMzXggHxnT2Zbh/HvbZiUfhGIf1FUrmd8XbBBsH2A7/Vt2FaC9EUTKQU5ii6/D84tgp2c0qvp7DjtDd9rrzXDpEdFdYqDcGmbFu5zr6hw2z6LFizIGmrkMNgJVaN1CH/R1Islps0Nm23dLZewjgHNIboR97duZrgCt3gBOx/gzl9tA8iL8kRMT9TU3WnD7Kv49mvyfsfVSVDds5nyle+FzVDxzj6g2g/Kq3AQBMPQ=
75 | on_success: always
76 | on_failure: never
77 | on_pull_requests: false
78 | -
79 | rooms:
80 | -
81 | secure: L9bTpHxLU5go+gBbAMdvyF0HZYij7G9ifn+JOtvPFkMavi5xl81d3QdWD6nc/67WSrhgdYJ8ApAwrisvAAyaNYwFBP1VC0BkwVcCXOQLpQKuS+hMankQNHauk3bdfclBU6UTLA8o6Yil7nF+NaUb/j3ElVwjHgiE5IQrHpPOWt3B9VeEFw64LX/oEfoRYYNbkMXMehRkKeWmgthLQ5MR+9KCutUVAWZlKuVFYoTyiXdYq6uk17V42/9XmPuVShY4VTdPfxUAQyXn16+usMfD2K5a75cAGKSSCgqgnDd5OzHh2D2JW6HyFl1RWAVcOrzUda+CUfQQPZbJFRg40m6Rf12YQMri4uszdSlyNEYXwRgxT7MvxNCOX/uwWyIb2D0d1qYs85qkkBIFrS7DBkx57GX3cNQnxUp2+ARvZnmsHlA10csJP0mq6jaJzPAR1mJuh9z26x9LEZ4fki07Rr1W1nvjwvizMDudBRhp3F+rzlWeq2/bX52twJ4pJhYFT6wLI5xjdufgyu9+tjCHPenN0EqeFMpaKxxBuKjfBRAgNZfeh6WP7FA/PurgHh4JvVGj6eDBqU5ZsE5ZguqZ9IzDHbL4EoFOrmzjiXmJkD0ZYIFlwhL841QILTn7R6A708N6ShAuWcJPyrnuPSWABs9sX3xVDXHDZI2OsYge3A5dI6U=
82 | on_success: never
83 | on_failure: always
84 | on_pull_requests: false
85 |
--------------------------------------------------------------------------------
/src/ActiveFormClientScript.php:
--------------------------------------------------------------------------------
1 | options(['id' => 'example-form')
19 | * ->asClientScript(\Yiisoft\Yii\JQuery\ActiveFormClientScript::class); ?>
20 | * ...
21 | *
22 | * ```
23 | */
24 | class ActiveFormClientScript extends \Yiisoft\Widgets\ActiveFormClientScript
25 | {
26 | /**
27 | * {@inheritdoc}
28 | */
29 | protected function defaultClientValidatorMap()
30 | {
31 | return [
32 | \Yiisoft\Validator\Rule\Boolean::class => \Yiisoft\Yii\JQuery\Validators\Client\BooleanValidator::class,
33 | \Yiisoft\Validator\Rule\CompareTo::class => \Yiisoft\Yii\JQuery\Validators\Client\CompareValidator::class,
34 | \Yiisoft\Validator\Rule\Email::class => \Yiisoft\Yii\JQuery\Validators\Client\EmailValidator::class,
35 | \Yiisoft\Validator\Rule\Ip::class => \Yiisoft\Yii\JQuery\Validators\Client\IpValidator::class,
36 | \Yiisoft\Validator\Rule\Number::class => \Yiisoft\Yii\JQuery\Validators\Client\NumberValidator::class,
37 | \Yiisoft\Validator\Rule\Required::class => \Yiisoft\Yii\JQuery\Validators\Client\RequiredValidator::class,
38 | \Yiisoft\Validator\Rule\Url::class => \Yiisoft\Yii\JQuery\Validators\Client\UrlValidator::class,
39 | ];
40 | }
41 |
42 | /**
43 | * {@inheritdoc}
44 | */
45 | protected function registerClientScript()
46 | {
47 | $id = $this->owner->options['id'];
48 | $options = Json::htmlEncode($this->getClientOptions());
49 | $attributes = Json::htmlEncode($this->attributes);
50 |
51 | ActiveFormAsset::register($view);
52 |
53 | $view->registerJs("jQuery('#$id').yiiActiveForm($attributes, $options);");
54 | }
55 |
56 | /**
57 | * {@inheritdoc}
58 | */
59 | protected function getFieldClientOptions($field)
60 | {
61 | $options = parent::getFieldClientOptions($field);
62 |
63 | // only get the options that are different from the default ones (set in yii.activeForm.js)
64 | return array_diff_assoc($options, [
65 | 'validateOnChange' => true,
66 | 'validateOnBlur' => true,
67 | 'validateOnType' => false,
68 | 'validationDelay' => 500,
69 | 'encodeError' => true,
70 | 'error' => '.help-block',
71 | 'updateAriaInvalid' => true,
72 | ]);
73 | }
74 |
75 | /**
76 | * {@inheritdoc}
77 | */
78 | protected function getClientOptions()
79 | {
80 | $options = parent::getClientOptions();
81 |
82 | // only get the options that are different from the default ones (set in yii.activeForm.js)
83 | return array_diff_assoc($options, [
84 | 'encodeErrorSummary' => true,
85 | 'errorSummary' => '.error-summary',
86 | 'validateOnSubmit' => true,
87 | 'errorCssClass' => 'has-error',
88 | 'successCssClass' => 'has-success',
89 | 'validatingCssClass' => 'validating',
90 | 'ajaxParam' => 'ajax',
91 | 'ajaxDataType' => 'json',
92 | 'scrollToError' => true,
93 | 'scrollToErrorOffset' => 0,
94 | 'validationStateOn' => ActiveForm::VALIDATION_STATE_ON_CONTAINER,
95 | ]);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/Validators/Client/FileValidator.php:
--------------------------------------------------------------------------------
1 | getClientOptions($validator, $model, $attribute);
28 |
29 | return 'yii.validation.file(attribute, messages, ' . Json::encode($options) . ');';
30 | }
31 |
32 | /**
33 | * Returns the client-side validation options.
34 | *
35 | * @param \yii\validators\FileValidator $validator the server-side validator.
36 | * @param \yii\base\Model $model the model being validated
37 | * @param string $attribute the attribute name being validated
38 | *
39 | * @return array the client-side validation options
40 | */
41 | public function getClientOptions($validator, $model, $attribute)
42 | {
43 | $label = $model->getAttributeLabel($attribute);
44 |
45 | $options = [];
46 |
47 | if ($validator->message !== null) {
48 | $options['message'] = $validator->formatMessage($validator->message, [
49 | 'attribute' => $label,
50 | ]);
51 | }
52 |
53 | $options['skipOnEmpty'] = $validator->skipOnEmpty;
54 |
55 | if (!$validator->skipOnEmpty) {
56 | $options['uploadRequired'] = $validator->formatMessage($validator->uploadRequired, [
57 | 'attribute' => $label,
58 | ]);
59 | }
60 |
61 | if ($validator->mimeTypes !== null) {
62 | $mimeTypes = [];
63 | foreach ($validator->mimeTypes as $mimeType) {
64 | $mimeTypes[] = new JsExpression(Html::escapeJsRegularExpression($validator->buildMimeTypeRegexp($mimeType)));
65 | }
66 | $options['mimeTypes'] = $mimeTypes;
67 | $options['wrongMimeType'] = $validator->formatMessage($validator->wrongMimeType, [
68 | 'attribute' => $label,
69 | 'mimeTypes' => implode(', ', $validator->mimeTypes),
70 | ]);
71 | }
72 |
73 | if ($validator->extensions !== null) {
74 | $options['extensions'] = $validator->extensions;
75 | $options['wrongExtension'] = $validator->formatMessage($validator->wrongExtension, [
76 | 'attribute' => $label,
77 | 'extensions' => implode(', ', $validator->extensions),
78 | ]);
79 | }
80 |
81 | if ($validator->minSize !== null) {
82 | $options['minSize'] = $validator->minSize;
83 | $options['tooSmall'] = $validator->formatMessage($validator->tooSmall, [
84 | 'attribute' => $label,
85 | 'limit' => $validator->minSize,
86 | 'formattedLimit' => Yii::$app->formatter->asShortSize($validator->minSize),
87 | ]);
88 | }
89 |
90 | if ($validator->maxSize !== null) {
91 | $options['maxSize'] = $validator->maxSize;
92 | $options['tooBig'] = $validator->formatMessage($validator->tooBig, [
93 | 'attribute' => $label,
94 | 'limit' => $validator->getSizeLimit(),
95 | 'formattedLimit' => Yii::$app->formatter->asShortSize($validator->getSizeLimit()),
96 | ]);
97 | }
98 |
99 | if ($validator->maxFiles !== null) {
100 | $options['maxFiles'] = $validator->maxFiles;
101 | $options['tooMany'] = $validator->formatMessage($validator->tooMany, [
102 | 'attribute' => $label,
103 | 'limit' => $validator->maxFiles,
104 | ]);
105 | }
106 |
107 | return $options;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/assets/yii.gridView.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Yii GridView widget.
3 | *
4 | * This is the JavaScript widget used by the yii\grid\GridView widget.
5 | */
6 | (function ($) {
7 | $.fn.yiiGridView = function (method) {
8 | if (methods[method]) {
9 | return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
10 | } else if (typeof method === 'object' || !method) {
11 | return methods.init.apply(this, arguments);
12 | } else {
13 | $.error('Method ' + method + ' does not exist in jQuery.yiiGridView');
14 | return false;
15 | }
16 | };
17 |
18 | var defaults = {
19 | filterUrl: undefined,
20 | filterSelector: undefined
21 | };
22 |
23 | var gridData = {};
24 |
25 | var gridEvents = {
26 | /**
27 | * beforeFilter event is triggered before filtering the grid.
28 | * The signature of the event handler should be:
29 | * function (event)
30 | * where
31 | * - event: an Event object.
32 | *
33 | * If the handler returns a boolean false, it will stop filter form submission after this event. As
34 | * a result, afterFilter event will not be triggered.
35 | */
36 | beforeFilter: 'beforeFilter',
37 | /**
38 | * afterFilter event is triggered after filtering the grid and filtered results are fetched.
39 | * The signature of the event handler should be:
40 | * function (event)
41 | * where
42 | * - event: an Event object.
43 | */
44 | afterFilter: 'afterFilter'
45 | };
46 |
47 | /**
48 | * Used for storing active event handlers and removing them later.
49 | * The structure of single event handler is:
50 | *
51 | * {
52 | * gridViewId: {
53 | * type: {
54 | * event: '...',
55 | * selector: '...'
56 | * }
57 | * }
58 | * }
59 | *
60 | * Used types:
61 | *
62 | * - filter, used for filtering grid with elements found by filterSelector
63 | * - checkRow, used for checking single row
64 | * - checkAllRows, used for checking all rows with according "Check all" checkbox
65 | *
66 | * event is the name of event, for example: 'change.yiiGridView'
67 | * selector is a jQuery selector for finding elements
68 | *
69 | * @type {{}}
70 | */
71 | var gridEventHandlers = {};
72 |
73 | var methods = {
74 | init: function (options) {
75 | return this.each(function () {
76 | var $e = $(this);
77 | var settings = $.extend({}, defaults, options || {});
78 | var id = $e.attr('id');
79 | if (gridData[id] === undefined) {
80 | gridData[id] = {};
81 | }
82 |
83 | gridData[id] = $.extend(gridData[id], {settings: settings});
84 |
85 | var filterEvents = 'change.yiiGridView keydown.yiiGridView';
86 | var enterPressed = false;
87 | initEventHandler($e, 'filter', filterEvents, settings.filterSelector, function (event) {
88 | if (event.type === 'keydown') {
89 | if (event.keyCode !== 13) {
90 | return; // only react to enter key
91 | } else {
92 | enterPressed = true;
93 | }
94 | } else {
95 | // prevent processing for both keydown and change events
96 | if (enterPressed) {
97 | enterPressed = false;
98 | return;
99 | }
100 | }
101 |
102 | methods.applyFilter.apply($e);
103 |
104 | return false;
105 | });
106 | });
107 | },
108 |
109 | applyFilter: function () {
110 | var $grid = $(this);
111 | var settings = gridData[$grid.attr('id')].settings;
112 | var data = {};
113 | $.each($(settings.filterSelector).serializeArray(), function () {
114 | if (!(this.name in data)) {
115 | data[this.name] = [];
116 | }
117 | data[this.name].push(this.value);
118 | });
119 |
120 | var namesInFilter = Object.keys(data);
121 |
122 | $.each(yii.getQueryParams(settings.filterUrl), function (name, value) {
123 | if (namesInFilter.indexOf(name) === -1 && namesInFilter.indexOf(name.replace(/\[\d*\]$/, '')) === -1) {
124 | if (!$.isArray(value)) {
125 | value = [value];
126 | }
127 | if (!(name in data)) {
128 | data[name] = value;
129 | } else {
130 | $.each(value, function (i, val) {
131 | if ($.inArray(val, data[name])) {
132 | data[name].push(val);
133 | }
134 | });
135 | }
136 | }
137 | });
138 |
139 | var pos = settings.filterUrl.indexOf('?');
140 | var url = pos < 0 ? settings.filterUrl : settings.filterUrl.substring(0, pos);
141 | var hashPos = settings.filterUrl.indexOf('#');
142 | if (pos >= 0 && hashPos >= 0) {
143 | url += settings.filterUrl.substring(hashPos);
144 | }
145 |
146 | $grid.find('form.gridview-filter-form').remove();
147 | var $form = $('', {
148 | action: url,
149 | method: 'get',
150 | 'class': 'gridview-filter-form',
151 | style: 'display:none'
152 | }).appendTo($grid);
153 | $.each(data, function (name, values) {
154 | $.each(values, function (index, value) {
155 | $form.append($('').attr({type: 'hidden', name: name, value: value}));
156 | });
157 | });
158 |
159 | var event = $.Event(gridEvents.beforeFilter);
160 | $grid.trigger(event);
161 | if (event.result === false) {
162 | return;
163 | }
164 |
165 | $form.submit();
166 |
167 | $grid.trigger(gridEvents.afterFilter);
168 | },
169 |
170 | setSelectionColumn: function (options) {
171 | var $grid = $(this);
172 | var id = $(this).attr('id');
173 | if (gridData[id] === undefined) {
174 | gridData[id] = {};
175 | }
176 | gridData[id].selectionColumn = options.name;
177 | if (!options.multiple || !options.checkAll) {
178 | return;
179 | }
180 | var checkAll = "#" + id + " input[name='" + options.checkAll + "']";
181 | var inputs = options['class'] ? "input." + options['class'] : "input[name='" + options.name + "']";
182 | var inputsEnabled = "#" + id + " " + inputs + ":enabled";
183 | initEventHandler($grid, 'checkAllRows', 'click.yiiGridView', checkAll, function () {
184 | $grid.find(inputs + ":enabled").prop('checked', this.checked);
185 | });
186 | initEventHandler($grid, 'checkRow', 'click.yiiGridView', inputsEnabled, function () {
187 | var all = $grid.find(inputs).length == $grid.find(inputs + ":checked").length;
188 | $grid.find("input[name='" + options.checkAll + "']").prop('checked', all);
189 | });
190 | },
191 |
192 | getSelectedRows: function () {
193 | var $grid = $(this);
194 | var data = gridData[$grid.attr('id')];
195 | var keys = [];
196 | if (data.selectionColumn) {
197 | $grid.find("input[name='" + data.selectionColumn + "']:checked").each(function () {
198 | keys.push($(this).parent().closest('tr').data('key'));
199 | });
200 | }
201 |
202 | return keys;
203 | },
204 |
205 | destroy: function () {
206 | var events = ['.yiiGridView', gridEvents.beforeFilter, gridEvents.afterFilter].join(' ');
207 | this.off(events);
208 |
209 | var id = $(this).attr('id');
210 | $.each(gridEventHandlers[id], function (type, data) {
211 | $(document).off(data.event, data.selector);
212 | });
213 |
214 | delete gridData[id];
215 |
216 | return this;
217 | },
218 |
219 | data: function () {
220 | var id = $(this).attr('id');
221 | return gridData[id];
222 | }
223 | };
224 |
225 | /**
226 | * Used for attaching event handler and prevent of duplicating them. With each call previously attached handler of
227 | * the same type is removed even selector was changed.
228 | * @param {jQuery} $gridView According jQuery grid view element
229 | * @param {string} type Type of the event which acts like a key
230 | * @param {string} event Event name, for example 'change.yiiGridView'
231 | * @param {string} selector jQuery selector
232 | * @param {function} callback The actual function to be executed with this event
233 | */
234 | function initEventHandler($gridView, type, event, selector, callback) {
235 | var id = $gridView.attr('id');
236 | var prevHandler = gridEventHandlers[id];
237 | if (prevHandler !== undefined && prevHandler[type] !== undefined) {
238 | var data = prevHandler[type];
239 | $(document).off(data.event, data.selector);
240 | }
241 | if (prevHandler === undefined) {
242 | gridEventHandlers[id] = {};
243 | }
244 | $(document).on(event, selector, callback);
245 | gridEventHandlers[id][type] = {event: event, selector: selector};
246 | }
247 | })(window.jQuery);
248 |
--------------------------------------------------------------------------------
/src/assets/yii.validation.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Yii validation module.
3 | *
4 | * This JavaScript module provides the validation methods for the built-in validators.
5 | */
6 |
7 | yii.validation = (function ($) {
8 | var pub = {
9 | isEmpty: function (value) {
10 | return value === null || value === undefined || ($.isArray(value) && value.length === 0) || value === '';
11 | },
12 |
13 | addMessage: function (messages, message, value) {
14 | messages.push(message.replace(/\{value\}/g, value));
15 | },
16 |
17 | required: function (value, messages, options) {
18 | var valid = false;
19 | if (options.requiredValue === undefined) {
20 | var isString = typeof value == 'string' || value instanceof String;
21 | if (options.strict && value !== undefined || !options.strict && !pub.isEmpty(isString ? $.trim(value) : value)) {
22 | valid = true;
23 | }
24 | } else if (!options.strict && value == options.requiredValue || options.strict && value === options.requiredValue) {
25 | valid = true;
26 | }
27 |
28 | if (!valid) {
29 | pub.addMessage(messages, options.message, value);
30 | }
31 | },
32 |
33 | // "boolean" is a reserved keyword in older versions of ES so it's quoted for IE < 9 support
34 | 'boolean': function (value, messages, options) {
35 | if (options.skipOnEmpty && pub.isEmpty(value)) {
36 | return;
37 | }
38 | var valid = !options.strict && (value == options.trueValue || value == options.falseValue)
39 | || options.strict && (value === options.trueValue || value === options.falseValue);
40 |
41 | if (!valid) {
42 | pub.addMessage(messages, options.message, value);
43 | }
44 | },
45 |
46 | string: function (value, messages, options) {
47 | if (options.skipOnEmpty && pub.isEmpty(value)) {
48 | return;
49 | }
50 |
51 | if (typeof value !== 'string') {
52 | pub.addMessage(messages, options.message, value);
53 | return;
54 | }
55 |
56 | if (options.is !== undefined && value.length != options.is) {
57 | pub.addMessage(messages, options.notEqual, value);
58 | return;
59 | }
60 | if (options.min !== undefined && value.length < options.min) {
61 | pub.addMessage(messages, options.tooShort, value);
62 | }
63 | if (options.max !== undefined && value.length > options.max) {
64 | pub.addMessage(messages, options.tooLong, value);
65 | }
66 | },
67 |
68 | file: function (attribute, messages, options) {
69 | var files = getUploadedFiles(attribute, messages, options);
70 | $.each(files, function (i, file) {
71 | validateFile(file, messages, options);
72 | });
73 | },
74 |
75 | image: function (attribute, messages, options, deferredList) {
76 | var files = getUploadedFiles(attribute, messages, options);
77 | $.each(files, function (i, file) {
78 | validateFile(file, messages, options);
79 |
80 | // Skip image validation if FileReader API is not available
81 | if (typeof FileReader === "undefined") {
82 | return;
83 | }
84 |
85 | var deferred = $.Deferred();
86 | pub.validateImage(file, messages, options, deferred, new FileReader(), new Image());
87 | deferredList.push(deferred);
88 | });
89 | },
90 |
91 | validateImage: function (file, messages, options, deferred, fileReader, image) {
92 | image.onload = function() {
93 | validateImageSize(file, image, messages, options);
94 | deferred.resolve();
95 | };
96 |
97 | image.onerror = function () {
98 | messages.push(options.notImage.replace(/\{file\}/g, file.name));
99 | deferred.resolve();
100 | };
101 |
102 | fileReader.onload = function () {
103 | image.src = this.result;
104 | };
105 |
106 | // Resolve deferred if there was error while reading data
107 | fileReader.onerror = function () {
108 | deferred.resolve();
109 | };
110 |
111 | fileReader.readAsDataURL(file);
112 | },
113 |
114 | number: function (value, messages, options) {
115 | if (options.skipOnEmpty && pub.isEmpty(value)) {
116 | return;
117 | }
118 |
119 | if (typeof value === 'string' && !options.pattern.test(value)) {
120 | pub.addMessage(messages, options.message, value);
121 | return;
122 | }
123 |
124 | if (options.min !== undefined && value < options.min) {
125 | pub.addMessage(messages, options.tooSmall, value);
126 | }
127 | if (options.max !== undefined && value > options.max) {
128 | pub.addMessage(messages, options.tooBig, value);
129 | }
130 | },
131 |
132 | range: function (value, messages, options) {
133 | if (options.skipOnEmpty && pub.isEmpty(value)) {
134 | return;
135 | }
136 |
137 | if (!options.allowArray && $.isArray(value)) {
138 | pub.addMessage(messages, options.message, value);
139 | return;
140 | }
141 |
142 | var inArray = true;
143 |
144 | $.each($.isArray(value) ? value : [value], function (i, v) {
145 | if ($.inArray(v, options.range) == -1) {
146 | inArray = false;
147 | return false;
148 | } else {
149 | return true;
150 | }
151 | });
152 |
153 | if (options.not === undefined) {
154 | options.not = false;
155 | }
156 |
157 | if (options.not === inArray) {
158 | pub.addMessage(messages, options.message, value);
159 | }
160 | },
161 |
162 | regularExpression: function (value, messages, options) {
163 | if (options.skipOnEmpty && pub.isEmpty(value)) {
164 | return;
165 | }
166 |
167 | if (!options.not && !options.pattern.test(value) || options.not && options.pattern.test(value)) {
168 | pub.addMessage(messages, options.message, value);
169 | }
170 | },
171 |
172 | email: function (value, messages, options) {
173 | if (options.skipOnEmpty && pub.isEmpty(value)) {
174 | return;
175 | }
176 |
177 | var valid = true,
178 | regexp = /^((?:"?([^"]*)"?\s)?)(?:\s+)?(?:()((.+)@([^>]+))(>?))$/,
179 | matches = regexp.exec(value);
180 |
181 | if (matches === null) {
182 | valid = false;
183 | } else {
184 | var localPart = matches[5],
185 | domain = matches[6];
186 |
187 | if (options.enableIDN) {
188 | localPart = punycode.toASCII(localPart);
189 | domain = punycode.toASCII(domain);
190 |
191 | value = matches[1] + matches[3] + localPart + '@' + domain + matches[7];
192 | }
193 |
194 | if (localPart.length > 64) {
195 | valid = false;
196 | } else if ((localPart + '@' + domain).length > 254) {
197 | valid = false;
198 | } else {
199 | valid = options.pattern.test(value) || (options.allowName && options.fullPattern.test(value));
200 | }
201 | }
202 |
203 | if (!valid) {
204 | pub.addMessage(messages, options.message, value);
205 | }
206 | },
207 |
208 | url: function (value, messages, options) {
209 | if (options.skipOnEmpty && pub.isEmpty(value)) {
210 | return;
211 | }
212 |
213 | if (options.defaultScheme && !/:\/\//.test(value)) {
214 | value = options.defaultScheme + '://' + value;
215 | }
216 |
217 | var valid = true;
218 |
219 | if (options.enableIDN) {
220 | var matches = /^([^:]+):\/\/([^\/]+)(.*)$/.exec(value);
221 | if (matches === null) {
222 | valid = false;
223 | } else {
224 | value = matches[1] + '://' + punycode.toASCII(matches[2]) + matches[3];
225 | }
226 | }
227 |
228 | if (!valid || !options.pattern.test(value)) {
229 | pub.addMessage(messages, options.message, value);
230 | }
231 | },
232 |
233 | trim: function ($form, attribute, options) {
234 | var $input = $form.find(attribute.input);
235 | var value = $input.val();
236 | if (!options.skipOnEmpty || !pub.isEmpty(value)) {
237 | value = $.trim(value);
238 | $input.val(value);
239 | }
240 |
241 | return value;
242 | },
243 |
244 | captcha: function (value, messages, options) {
245 | if (options.skipOnEmpty && pub.isEmpty(value)) {
246 | return;
247 | }
248 |
249 | // CAPTCHA may be updated via AJAX and the updated hash is stored in body data
250 | var hash = $('body').data(options.hashKey);
251 | hash = hash == null ? options.hash : hash[options.caseSensitive ? 0 : 1];
252 | var v = options.caseSensitive ? value : value.toLowerCase();
253 | for (var i = v.length - 1, h = 0; i >= 0; --i) {
254 | h += v.charCodeAt(i);
255 | }
256 | if (h != hash) {
257 | pub.addMessage(messages, options.message, value);
258 | }
259 | },
260 |
261 | compare: function (value, messages, options, $form) {
262 | if (options.skipOnEmpty && pub.isEmpty(value)) {
263 | return;
264 | }
265 |
266 | var compareValue,
267 | valid = true;
268 | if (options.compareAttribute === undefined) {
269 | compareValue = options.compareValue;
270 | } else {
271 | var attributes = $form.data('yiiActiveForm').attributes
272 | for (var i = attributes.length - 1; i >= 0; i--) {
273 | if (attributes[i].id === options.compareAttribute) {
274 | compareValue = $(attributes[i].input).val();
275 | }
276 | }
277 | }
278 |
279 | if (options.type === 'number') {
280 | value = parseFloat(value);
281 | compareValue = parseFloat(compareValue);
282 | }
283 | switch (options.operator) {
284 | case '==':
285 | valid = value == compareValue;
286 | break;
287 | case '===':
288 | valid = value === compareValue;
289 | break;
290 | case '!=':
291 | valid = value != compareValue;
292 | break;
293 | case '!==':
294 | valid = value !== compareValue;
295 | break;
296 | case '>':
297 | valid = value > compareValue;
298 | break;
299 | case '>=':
300 | valid = value >= compareValue;
301 | break;
302 | case '<':
303 | valid = value < compareValue;
304 | break;
305 | case '<=':
306 | valid = value <= compareValue;
307 | break;
308 | default:
309 | valid = false;
310 | break;
311 | }
312 |
313 | if (!valid) {
314 | pub.addMessage(messages, options.message, value);
315 | }
316 | },
317 |
318 | ip: function (value, messages, options) {
319 | if (options.skipOnEmpty && pub.isEmpty(value)) {
320 | return;
321 | }
322 |
323 | var negation = null,
324 | cidr = null,
325 | matches = new RegExp(options.ipParsePattern).exec(value);
326 | if (matches) {
327 | negation = matches[1] || null;
328 | value = matches[2];
329 | cidr = matches[4] || null;
330 | }
331 |
332 | if (options.subnet === true && cidr === null) {
333 | pub.addMessage(messages, options.messages.noSubnet, value);
334 | return;
335 | }
336 | if (options.subnet === false && cidr !== null) {
337 | pub.addMessage(messages, options.messages.hasSubnet, value);
338 | return;
339 | }
340 | if (options.negation === false && negation !== null) {
341 | pub.addMessage(messages, options.messages.message, value);
342 | return;
343 | }
344 |
345 | var ipVersion = value.indexOf(':') === -1 ? 4 : 6;
346 | if (ipVersion == 6) {
347 | if (!(new RegExp(options.ipv6Pattern)).test(value)) {
348 | pub.addMessage(messages, options.messages.message, value);
349 | }
350 | if (!options.ipv6) {
351 | pub.addMessage(messages, options.messages.ipv6NotAllowed, value);
352 | }
353 | } else {
354 | if (!(new RegExp(options.ipv4Pattern)).test(value)) {
355 | pub.addMessage(messages, options.messages.message, value);
356 | }
357 | if (!options.ipv4) {
358 | pub.addMessage(messages, options.messages.ipv4NotAllowed, value);
359 | }
360 | }
361 | }
362 | };
363 |
364 | function getUploadedFiles(attribute, messages, options) {
365 | // Skip validation if File API is not available
366 | if (typeof File === "undefined") {
367 | return [];
368 | }
369 |
370 | var files = $(attribute.input, attribute.$form).get(0).files;
371 | if (!files) {
372 | messages.push(options.message);
373 | return [];
374 | }
375 |
376 | if (files.length === 0) {
377 | if (!options.skipOnEmpty) {
378 | messages.push(options.uploadRequired);
379 | }
380 |
381 | return [];
382 | }
383 |
384 | if (options.maxFiles && options.maxFiles < files.length) {
385 | messages.push(options.tooMany);
386 | return [];
387 | }
388 |
389 | return files;
390 | }
391 |
392 | function validateFile(file, messages, options) {
393 | if (options.extensions && options.extensions.length > 0) {
394 | var index = file.name.lastIndexOf('.');
395 | var ext = !~index ? '' : file.name.substr(index + 1, file.name.length).toLowerCase();
396 |
397 | if (!~options.extensions.indexOf(ext)) {
398 | messages.push(options.wrongExtension.replace(/\{file\}/g, file.name));
399 | }
400 | }
401 |
402 | if (options.mimeTypes && options.mimeTypes.length > 0) {
403 | if (!validateMimeType(options.mimeTypes, file.type)) {
404 | messages.push(options.wrongMimeType.replace(/\{file\}/g, file.name));
405 | }
406 | }
407 |
408 | if (options.maxSize && options.maxSize < file.size) {
409 | messages.push(options.tooBig.replace(/\{file\}/g, file.name));
410 | }
411 |
412 | if (options.minSize && options.minSize > file.size) {
413 | messages.push(options.tooSmall.replace(/\{file\}/g, file.name));
414 | }
415 | }
416 |
417 | function validateMimeType(mimeTypes, fileType) {
418 | for (var i = 0, len = mimeTypes.length; i < len; i++) {
419 | if (new RegExp(mimeTypes[i]).test(fileType)) {
420 | return true;
421 | }
422 | }
423 |
424 | return false;
425 | }
426 |
427 | function validateImageSize(file, image, messages, options) {
428 | if (options.minWidth && image.width < options.minWidth) {
429 | messages.push(options.underWidth.replace(/\{file\}/g, file.name));
430 | }
431 |
432 | if (options.maxWidth && image.width > options.maxWidth) {
433 | messages.push(options.overWidth.replace(/\{file\}/g, file.name));
434 | }
435 |
436 | if (options.minHeight && image.height < options.minHeight) {
437 | messages.push(options.underHeight.replace(/\{file\}/g, file.name));
438 | }
439 |
440 | if (options.maxHeight && image.height > options.maxHeight) {
441 | messages.push(options.overHeight.replace(/\{file\}/g, file.name));
442 | }
443 | }
444 |
445 | return pub;
446 | })(jQuery);
447 |
--------------------------------------------------------------------------------
/src/assets/yii.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Yii JavaScript module.
3 | */
4 |
5 | /**
6 | * yii is the root module for all Yii JavaScript modules.
7 | * It implements a mechanism of organizing JavaScript code in modules through the function "yii.initModule()".
8 | *
9 | * Each module should be named as "x.y.z", where "x" stands for the root module (for the Yii core code, this is "yii").
10 | *
11 | * A module may be structured as follows:
12 | *
13 | * ```javascript
14 | * window.yii.sample = (function($) {
15 | * var pub = {
16 | * // whether this module is currently active. If false, init() will not be called for this module
17 | * // it will also not be called for all its child modules. If this property is undefined, it means true.
18 | * isActive: true,
19 | * init: function() {
20 | * // ... module initialization code goes here ...
21 | * },
22 | *
23 | * // ... other public functions and properties go here ...
24 | * };
25 | *
26 | * // ... private functions and properties go here ...
27 | *
28 | * return pub;
29 | * })(window.jQuery);
30 | * ```
31 | *
32 | * Using this structure, you can define public and private functions/properties for a module.
33 | * Private functions/properties are only visible within the module, while public functions/properties
34 | * may be accessed outside of the module. For example, you can access "yii.sample.isActive".
35 | *
36 | * You must call "yii.initModule()" once for the root module of all your modules.
37 | */
38 | window.yii = (function ($) {
39 | var pub = {
40 | /**
41 | * List of JS or CSS URLs that can be loaded multiple times via AJAX requests.
42 | * Each item may be represented as either an absolute URL or a relative one.
43 | * Each item may contain a wildcard matching character `*`, that means one or more
44 | * any characters on the position. For example:
45 | * - `/css/*.css` will match any file ending with `.css` in the `css` directory of the current web site
46 | * - `http*://cdn.example.com/*` will match any files on domain `cdn.example.com`, loaded with HTTP or HTTPS
47 | * - `/js/myCustomScript.js?realm=*` will match file `/js/myCustomScript.js` with defined `realm` parameter
48 | */
49 | reloadableScripts: [],
50 | /**
51 | * The selector for clickable elements that need to support confirmation and form submission.
52 | */
53 | clickableSelector: 'a, button, input[type="submit"], input[type="button"], input[type="reset"], ' +
54 | 'input[type="image"]',
55 | /**
56 | * The selector for changeable elements that need to support confirmation and form submission.
57 | */
58 | changeableSelector: 'select, input, textarea',
59 |
60 | /**
61 | * @return string|undefined the CSRF parameter name. Undefined is returned if CSRF validation is not enabled.
62 | */
63 | getCsrfParam: function () {
64 | return $('meta[name=csrf-param]').attr('content');
65 | },
66 |
67 | /**
68 | * @return string|undefined the CSRF token. Undefined is returned if CSRF validation is not enabled.
69 | */
70 | getCsrfToken: function () {
71 | return $('meta[name=csrf-token]').attr('content');
72 | },
73 |
74 | /**
75 | * Sets the CSRF token in the meta elements.
76 | * This method is provided so that you can update the CSRF token with the latest one you obtain from the server.
77 | * @param name the CSRF token name
78 | * @param value the CSRF token value
79 | */
80 | setCsrfToken: function (name, value) {
81 | $('meta[name=csrf-param]').attr('content', name);
82 | $('meta[name=csrf-token]').attr('content', value);
83 | },
84 |
85 | /**
86 | * Updates all form CSRF input fields with the latest CSRF token.
87 | * This method is provided to avoid cached forms containing outdated CSRF tokens.
88 | */
89 | refreshCsrfToken: function () {
90 | var token = pub.getCsrfToken();
91 | if (token) {
92 | $('form input[name="' + pub.getCsrfParam() + '"]').val(token);
93 | }
94 | },
95 |
96 | /**
97 | * Displays a confirmation dialog.
98 | * The default implementation simply displays a js confirmation dialog.
99 | * You may override this by setting `yii.confirm`.
100 | * @param message the confirmation message.
101 | * @param ok a callback to be called when the user confirms the message
102 | * @param cancel a callback to be called when the user cancels the confirmation
103 | */
104 | confirm: function (message, ok, cancel) {
105 | if (window.confirm(message)) {
106 | !ok || ok();
107 | } else {
108 | !cancel || cancel();
109 | }
110 | },
111 |
112 | /**
113 | * Handles the action triggered by user.
114 | * This method recognizes the `data-method` attribute of the element. If the attribute exists,
115 | * the method will submit the form containing this element. If there is no containing form, a form
116 | * will be created and submitted using the method given by this attribute value (e.g. "post", "put").
117 | * For hyperlinks, the form action will take the value of the "href" attribute of the link.
118 | * For other elements, either the containing form action or the current page URL will be used
119 | * as the form action URL.
120 | *
121 | * If the `data-method` attribute is not defined, the `href` attribute (if any) of the element
122 | * will be assigned to `window.location`.
123 | *
124 | * Starting from version 2.0.3, the `data-params` attribute is also recognized when you specify
125 | * `data-method`. The value of `data-params` should be a JSON representation of the data (name-value pairs)
126 | * that should be submitted as hidden inputs. For example, you may use the following code to generate
127 | * such a link:
128 | *
129 | * ```php
130 | * use yii\helpers\Html;
131 | * use yii\helpers\Json;
132 | *
133 | * echo Html::a('submit', ['site/foobar'], [
134 | * 'data' => [
135 | * 'method' => 'post',
136 | * 'params' => [
137 | * 'name1' => 'value1',
138 | * 'name2' => 'value2',
139 | * ],
140 | * ],
141 | * ]);
142 | * ```
143 | *
144 | * @param $e the jQuery representation of the element
145 | * @param event Related event
146 | */
147 | handleAction: function ($e, event) {
148 | var $form = $e.attr('data-form') ? $('#' + $e.attr('data-form')) : $e.closest('form'),
149 | method = !$e.data('method') && $form ? $form.attr('method') : $e.data('method'),
150 | action = $e.attr('href'),
151 | isValidAction = action && action !== '#',
152 | params = $e.data('params'),
153 | areValidParams = params && $.isPlainObject(params);
154 |
155 | if (method === undefined) {
156 | if (isValidAction) {
157 | window.location.assign(action);
158 | } else if ($e.is(':submit') && $form.length) {
159 | $form.trigger('submit');
160 | }
161 | return;
162 | }
163 |
164 | var oldMethod,
165 | oldAction,
166 | newForm = !$form.length;
167 | if (!newForm) {
168 | oldMethod = $form.attr('method');
169 | $form.attr('method', method);
170 | if (isValidAction) {
171 | oldAction = $form.attr('action');
172 | $form.attr('action', action);
173 | }
174 | } else {
175 | if (!isValidAction) {
176 | action = pub.getCurrentUrl();
177 | }
178 | $form = $('', {method: method, action: action});
179 | var target = $e.attr('target');
180 | if (target) {
181 | $form.attr('target', target);
182 | }
183 | if (!/(get|post)/i.test(method)) {
184 | $form.append($('', {name: '_method', value: method, type: 'hidden'}));
185 | method = 'post';
186 | $form.attr('method', method);
187 | }
188 | if (/post/i.test(method)) {
189 | var csrfParam = pub.getCsrfParam();
190 | if (csrfParam) {
191 | $form.append($('', {name: csrfParam, value: pub.getCsrfToken(), type: 'hidden'}));
192 | }
193 | }
194 | $form.hide().appendTo('body');
195 | }
196 |
197 | var activeFormData = $form.data('yiiActiveForm');
198 | if (activeFormData) {
199 | // Remember the element triggered the form submission. This is used by yii.activeForm.js.
200 | activeFormData.submitObject = $e;
201 | }
202 |
203 | if (areValidParams) {
204 | $.each(params, function (name, value) {
205 | $form.append($('').attr({name: name, value: value, type: 'hidden'}));
206 | });
207 | }
208 |
209 | $form.trigger('submit');
210 |
211 | $.when($form.data('yiiSubmitFinalizePromise')).done(function () {
212 | if (newForm) {
213 | $form.remove();
214 | return;
215 | }
216 |
217 | if (oldAction !== undefined) {
218 | $form.attr('action', oldAction);
219 | }
220 | $form.attr('method', oldMethod);
221 |
222 | if (areValidParams) {
223 | $.each(params, function (name) {
224 | $('input[name="' + name + '"]', $form).remove();
225 | });
226 | }
227 | });
228 | },
229 |
230 | getQueryParams: function (url) {
231 | var pos = url.indexOf('?');
232 | if (pos < 0) {
233 | return {};
234 | }
235 |
236 | var pairs = $.grep(url.substring(pos + 1).split('#')[0].split('&'), function (value) {
237 | return value !== '';
238 | });
239 | var params = {};
240 |
241 | for (var i = 0, len = pairs.length; i < len; i++) {
242 | var pair = pairs[i].split('=');
243 | var name = decodeURIComponent(pair[0].replace(/\+/g, '%20'));
244 | var value = decodeURIComponent(pair[1].replace(/\+/g, '%20'));
245 | if (!name.length) {
246 | continue;
247 | }
248 | if (params[name] === undefined) {
249 | params[name] = value || '';
250 | } else {
251 | if (!$.isArray(params[name])) {
252 | params[name] = [params[name]];
253 | }
254 | params[name].push(value || '');
255 | }
256 | }
257 |
258 | return params;
259 | },
260 |
261 | initModule: function (module) {
262 | if (module.isActive !== undefined && !module.isActive) {
263 | return;
264 | }
265 | if ($.isFunction(module.init)) {
266 | module.init();
267 | }
268 | $.each(module, function () {
269 | if ($.isPlainObject(this)) {
270 | pub.initModule(this);
271 | }
272 | });
273 | },
274 |
275 | init: function () {
276 | initCsrfHandler();
277 | initRedirectHandler();
278 | initAssetFilters();
279 | initDataMethods();
280 | },
281 |
282 | /**
283 | * Returns the URL of the current page without params and trailing slash. Separated and made public for testing.
284 | * @returns {string}
285 | */
286 | getBaseCurrentUrl: function () {
287 | return window.location.protocol + '//' + window.location.host;
288 | },
289 |
290 | /**
291 | * Returns the URL of the current page. Used for testing, you can always call `window.location.href` manually
292 | * instead.
293 | * @returns {string}
294 | */
295 | getCurrentUrl: function () {
296 | return window.location.href;
297 | }
298 | };
299 |
300 | function initCsrfHandler() {
301 | // automatically send CSRF token for all AJAX requests
302 | $.ajaxPrefilter(function (options, originalOptions, xhr) {
303 | if (!options.crossDomain && pub.getCsrfParam()) {
304 | xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken());
305 | }
306 | });
307 | pub.refreshCsrfToken();
308 | }
309 |
310 | function initRedirectHandler() {
311 | // handle AJAX redirection
312 | $(document).ajaxComplete(function (event, xhr) {
313 | var url = xhr && xhr.getResponseHeader('X-Redirect');
314 | if (url) {
315 | window.location.assign(url);
316 | }
317 | });
318 | }
319 |
320 | function initAssetFilters() {
321 | /**
322 | * Used for storing loaded scripts and information about loading each script if it's in the process of loading.
323 | * A single script can have one of the following values:
324 | *
325 | * - `undefined` - script was not loaded at all before or was loaded with error last time.
326 | * - `true` (boolean) - script was successfully loaded.
327 | * - object - script is currently loading.
328 | *
329 | * In case of a value being an object the properties are:
330 | * - `xhrList` - represents a queue of XHR requests sent to the same URL (related with this script) in the same
331 | * small period of time.
332 | * - `xhrDone` - boolean, acts like a locking mechanism. When one of the XHR requests in the queue is
333 | * successfully completed, it will abort the rest of concurrent requests to the same URL until cleanup is done
334 | * to prevent possible errors and race conditions.
335 | * @type {{}}
336 | */
337 | var loadedScripts = {};
338 |
339 | $('script[src]').each(function () {
340 | var url = getAbsoluteUrl(this.src);
341 | loadedScripts[url] = true;
342 | });
343 |
344 | $.ajaxPrefilter('script', function (options, originalOptions, xhr) {
345 | if (options.dataType == 'jsonp') {
346 | return;
347 | }
348 |
349 | var url = getAbsoluteUrl(options.url),
350 | forbiddenRepeatedLoad = loadedScripts[url] === true && !isReloadableAsset(url),
351 | cleanupRunning = loadedScripts[url] !== undefined && loadedScripts[url]['xhrDone'] === true;
352 |
353 | if (forbiddenRepeatedLoad || cleanupRunning) {
354 | xhr.abort();
355 | return;
356 | }
357 |
358 | if (loadedScripts[url] === undefined || loadedScripts[url] === true) {
359 | loadedScripts[url] = {
360 | xhrList: [],
361 | xhrDone: false
362 | };
363 | }
364 |
365 | xhr.done(function (data, textStatus, jqXHR) {
366 | // If multiple requests were successfully loaded, perform cleanup only once
367 | if (loadedScripts[jqXHR.yiiUrl]['xhrDone'] === true) {
368 | return;
369 | }
370 |
371 | loadedScripts[jqXHR.yiiUrl]['xhrDone'] = true;
372 |
373 | for (var i = 0, len = loadedScripts[jqXHR.yiiUrl]['xhrList'].length; i < len; i++) {
374 | var singleXhr = loadedScripts[jqXHR.yiiUrl]['xhrList'][i];
375 | if (singleXhr && singleXhr.readyState !== XMLHttpRequest.DONE) {
376 | singleXhr.abort();
377 | }
378 | }
379 |
380 | loadedScripts[jqXHR.yiiUrl] = true;
381 | }).fail(function (jqXHR, textStatus) {
382 | if (textStatus === 'abort') {
383 | return;
384 | }
385 |
386 | delete loadedScripts[jqXHR.yiiUrl]['xhrList'][jqXHR.yiiIndex];
387 |
388 | var allFailed = true;
389 | for (var i = 0, len = loadedScripts[jqXHR.yiiUrl]['xhrList'].length; i < len; i++) {
390 | if (loadedScripts[jqXHR.yiiUrl]['xhrList'][i]) {
391 | allFailed = false;
392 | }
393 | }
394 |
395 | if (allFailed) {
396 | delete loadedScripts[jqXHR.yiiUrl];
397 | }
398 | });
399 | // Use prefix for custom XHR properties to avoid possible conflicts with existing properties
400 | xhr.yiiIndex = loadedScripts[url]['xhrList'].length;
401 | xhr.yiiUrl = url;
402 |
403 | loadedScripts[url]['xhrList'][xhr.yiiIndex] = xhr;
404 | });
405 |
406 | $(document).ajaxComplete(function () {
407 | var styleSheets = [];
408 | $('link[rel=stylesheet]').each(function () {
409 | var url = getAbsoluteUrl(this.href);
410 | if (isReloadableAsset(url)) {
411 | return;
412 | }
413 |
414 | $.inArray(url, styleSheets) === -1 ? styleSheets.push(url) : $(this).remove();
415 | });
416 | });
417 | }
418 |
419 | function initDataMethods() {
420 | var handler = function (event) {
421 | var $this = $(this),
422 | method = $this.data('method'),
423 | message = $this.data('confirm'),
424 | form = $this.data('form');
425 |
426 | if (method === undefined && message === undefined && form === undefined) {
427 | return true;
428 | }
429 |
430 | if (message !== undefined) {
431 | $.proxy(pub.confirm, this)(message, function () {
432 | pub.handleAction($this, event);
433 | });
434 | } else {
435 | pub.handleAction($this, event);
436 | }
437 | event.stopImmediatePropagation();
438 | return false;
439 | };
440 |
441 | // handle data-confirm and data-method for clickable and changeable elements
442 | $(document).on('click.yii', pub.clickableSelector, handler)
443 | .on('change.yii', pub.changeableSelector, handler);
444 | }
445 |
446 | function isReloadableAsset(url) {
447 | for (var i = 0; i < pub.reloadableScripts.length; i++) {
448 | var rule = getAbsoluteUrl(pub.reloadableScripts[i]);
449 | var match = new RegExp("^" + escapeRegExp(rule).split('\\*').join('.+') + "$").test(url);
450 | if (match === true) {
451 | return true;
452 | }
453 | }
454 |
455 | return false;
456 | }
457 |
458 | // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
459 | function escapeRegExp(str) {
460 | return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
461 | }
462 |
463 | /**
464 | * Returns absolute URL based on the given URL
465 | * @param {string} url Initial URL
466 | * @returns {string}
467 | */
468 | function getAbsoluteUrl(url) {
469 | return url.charAt(0) === '/' ? pub.getBaseCurrentUrl() + url : url;
470 | }
471 |
472 | return pub;
473 | })(window.jQuery);
474 |
475 | window.jQuery(function () {
476 | window.yii.initModule(window.yii);
477 | });
478 |
--------------------------------------------------------------------------------
/src/assets/yii.activeForm.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Yii form widget.
3 | *
4 | * This is the JavaScript widget used by the yii\widgets\ActiveForm widget.
5 | */
6 | (function ($) {
7 |
8 | $.fn.yiiActiveForm = function (method) {
9 | if (methods[method]) {
10 | return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
11 | } else if (typeof method === 'object' || !method) {
12 | return methods.init.apply(this, arguments);
13 | } else {
14 | $.error('Method ' + method + ' does not exist on jQuery.yiiActiveForm');
15 | return false;
16 | }
17 | };
18 |
19 | var events = {
20 | /**
21 | * beforeValidate event is triggered before validating the whole form.
22 | * The signature of the event handler should be:
23 | * function (event, messages, deferreds)
24 | * where
25 | * - event: an Event object.
26 | * - messages: an associative array with keys being attribute IDs and values being error message arrays
27 | * for the corresponding attributes.
28 | * - deferreds: an array of Deferred objects. You can use deferreds.add(callback) to add a new deferred validation.
29 | *
30 | * If the handler returns a boolean false, it will stop further form validation after this event. And as
31 | * a result, afterValidate event will not be triggered.
32 | */
33 | beforeValidate: 'beforeValidate',
34 | /**
35 | * afterValidate event is triggered after validating the whole form.
36 | * The signature of the event handler should be:
37 | * function (event, messages, errorAttributes)
38 | * where
39 | * - event: an Event object.
40 | * - messages: an associative array with keys being attribute IDs and values being error message arrays
41 | * for the corresponding attributes.
42 | * - errorAttributes: an array of attributes that have validation errors. Please refer to attributeDefaults for the structure of this parameter.
43 | */
44 | afterValidate: 'afterValidate',
45 | /**
46 | * beforeValidateAttribute event is triggered before validating an attribute.
47 | * The signature of the event handler should be:
48 | * function (event, attribute, messages, deferreds)
49 | * where
50 | * - event: an Event object.
51 | * - attribute: the attribute to be validated. Please refer to attributeDefaults for the structure of this parameter.
52 | * - messages: an array to which you can add validation error messages for the specified attribute.
53 | * - deferreds: an array of Deferred objects. You can use deferreds.add(callback) to add a new deferred validation.
54 | *
55 | * If the handler returns a boolean false, it will stop further validation of the specified attribute.
56 | * And as a result, afterValidateAttribute event will not be triggered.
57 | */
58 | beforeValidateAttribute: 'beforeValidateAttribute',
59 | /**
60 | * afterValidateAttribute event is triggered after validating the whole form and each attribute.
61 | * The signature of the event handler should be:
62 | * function (event, attribute, messages)
63 | * where
64 | * - event: an Event object.
65 | * - attribute: the attribute being validated. Please refer to attributeDefaults for the structure of this parameter.
66 | * - messages: an array to which you can add additional validation error messages for the specified attribute.
67 | */
68 | afterValidateAttribute: 'afterValidateAttribute',
69 | /**
70 | * beforeSubmit event is triggered before submitting the form after all validations have passed.
71 | * The signature of the event handler should be:
72 | * function (event)
73 | * where event is an Event object.
74 | *
75 | * If the handler returns a boolean false, it will stop form submission.
76 | */
77 | beforeSubmit: 'beforeSubmit',
78 | /**
79 | * ajaxBeforeSend event is triggered before sending an AJAX request for AJAX-based validation.
80 | * The signature of the event handler should be:
81 | * function (event, jqXHR, settings)
82 | * where
83 | * - event: an Event object.
84 | * - jqXHR: a jqXHR object
85 | * - settings: the settings for the AJAX request
86 | */
87 | ajaxBeforeSend: 'ajaxBeforeSend',
88 | /**
89 | * ajaxComplete event is triggered after completing an AJAX request for AJAX-based validation.
90 | * The signature of the event handler should be:
91 | * function (event, jqXHR, textStatus)
92 | * where
93 | * - event: an Event object.
94 | * - jqXHR: a jqXHR object
95 | * - textStatus: the status of the request ("success", "notmodified", "error", "timeout", "abort", or "parsererror").
96 | */
97 | ajaxComplete: 'ajaxComplete',
98 | /**
99 | * afterInit event is triggered after yii activeForm init.
100 | * The signature of the event handler should be:
101 | * function (event)
102 | * where
103 | * - event: an Event object.
104 | */
105 | afterInit: 'afterInit'
106 | };
107 |
108 | // NOTE: If you change any of these defaults, make sure you update yii\widgets\ActiveForm::getClientOptions() as well
109 | var defaults = {
110 | // whether to encode the error summary
111 | encodeErrorSummary: true,
112 | // the jQuery selector for the error summary
113 | errorSummary: '.error-summary',
114 | // whether to perform validation before submitting the form.
115 | validateOnSubmit: true,
116 | // the container CSS class representing the corresponding attribute has validation error
117 | errorCssClass: 'has-error',
118 | // the container CSS class representing the corresponding attribute passes validation
119 | successCssClass: 'has-success',
120 | // the container CSS class representing the corresponding attribute is being validated
121 | validatingCssClass: 'validating',
122 | // the GET parameter name indicating an AJAX-based validation
123 | ajaxParam: 'ajax',
124 | // the type of data that you're expecting back from the server
125 | ajaxDataType: 'json',
126 | // the URL for performing AJAX-based validation. If not set, it will use the the form's action
127 | validationUrl: undefined,
128 | // whether to scroll to first visible error after validation.
129 | scrollToError: true,
130 | // offset in pixels that should be added when scrolling to the first error.
131 | scrollToErrorOffset: 0,
132 | // where to add validation class: container or input
133 | validationStateOn: 'container'
134 | };
135 |
136 | // NOTE: If you change any of these defaults, make sure you update yii\widgets\ActiveField::getClientOptions() as well
137 | var attributeDefaults = {
138 | // a unique ID identifying an attribute (e.g. "loginform-username") in a form
139 | id: undefined,
140 | // attribute name or expression (e.g. "[0]content" for tabular input)
141 | name: undefined,
142 | // the jQuery selector of the container of the input field
143 | container: undefined,
144 | // the jQuery selector of the input field under the context of the form
145 | input: undefined,
146 | // the jQuery selector of the error tag under the context of the container
147 | error: '.help-block',
148 | // whether to encode the error
149 | encodeError: true,
150 | // whether to perform validation when a change is detected on the input
151 | validateOnChange: true,
152 | // whether to perform validation when the input loses focus
153 | validateOnBlur: true,
154 | // whether to perform validation when the user is typing.
155 | validateOnType: false,
156 | // number of milliseconds that the validation should be delayed when a user is typing in the input field.
157 | validationDelay: 500,
158 | // whether to enable AJAX-based validation.
159 | enableAjaxValidation: false,
160 | // function (attribute, value, messages, deferred, $form), the client-side validation function.
161 | validate: undefined,
162 | // status of the input field, 0: empty, not entered before, 1: validated, 2: pending validation, 3: validating
163 | status: 0,
164 | // whether the validation is cancelled by beforeValidateAttribute event handler
165 | cancelled: false,
166 | // the value of the input
167 | value: undefined,
168 | // whether to update aria-invalid attribute after validation
169 | updateAriaInvalid: true
170 | };
171 |
172 |
173 | var submitDefer;
174 |
175 | var setSubmitFinalizeDefer = function($form) {
176 | submitDefer = $.Deferred();
177 | $form.data('yiiSubmitFinalizePromise', submitDefer.promise());
178 | };
179 |
180 | // finalize yii.js $form.submit
181 | var submitFinalize = function($form) {
182 | if(submitDefer) {
183 | submitDefer.resolve();
184 | submitDefer = undefined;
185 | $form.removeData('yiiSubmitFinalizePromise');
186 | }
187 | };
188 |
189 |
190 | var methods = {
191 | init: function (attributes, options) {
192 | return this.each(function () {
193 | var $form = $(this);
194 | if ($form.data('yiiActiveForm')) {
195 | return;
196 | }
197 |
198 | var settings = $.extend({}, defaults, options || {});
199 | if (settings.validationUrl === undefined) {
200 | settings.validationUrl = $form.attr('action');
201 | }
202 |
203 | $.each(attributes, function (i) {
204 | attributes[i] = $.extend({value: getValue($form, this)}, attributeDefaults, this);
205 | watchAttribute($form, attributes[i]);
206 | });
207 |
208 | $form.data('yiiActiveForm', {
209 | settings: settings,
210 | attributes: attributes,
211 | submitting: false,
212 | validated: false,
213 | options: getFormOptions($form)
214 | });
215 |
216 | /**
217 | * Clean up error status when the form is reset.
218 | * Note that $form.on('reset', ...) does work because the "reset" event does not bubble on IE.
219 | */
220 | $form.on('reset.yiiActiveForm', methods.resetForm);
221 |
222 | if (settings.validateOnSubmit) {
223 | $form.on('mouseup.yiiActiveForm keyup.yiiActiveForm', ':submit', function () {
224 | $form.data('yiiActiveForm').submitObject = $(this);
225 | });
226 | $form.on('submit.yiiActiveForm', methods.submitForm);
227 | }
228 | var event = $.Event(events.afterInit);
229 | $form.trigger(event);
230 | });
231 | },
232 |
233 | // add a new attribute to the form dynamically.
234 | // please refer to attributeDefaults for the structure of attribute
235 | add: function (attribute) {
236 | var $form = $(this);
237 | attribute = $.extend({value: getValue($form, attribute)}, attributeDefaults, attribute);
238 | $form.data('yiiActiveForm').attributes.push(attribute);
239 | watchAttribute($form, attribute);
240 | },
241 |
242 | // remove the attribute with the specified ID from the form
243 | remove: function (id) {
244 | var $form = $(this),
245 | attributes = $form.data('yiiActiveForm').attributes,
246 | index = -1,
247 | attribute = undefined;
248 | $.each(attributes, function (i) {
249 | if (attributes[i]['id'] == id) {
250 | index = i;
251 | attribute = attributes[i];
252 | return false;
253 | }
254 | });
255 | if (index >= 0) {
256 | attributes.splice(index, 1);
257 | unwatchAttribute($form, attribute);
258 | }
259 |
260 | return attribute;
261 | },
262 |
263 | // manually trigger the validation of the attribute with the specified ID
264 | validateAttribute: function (id) {
265 | var attribute = methods.find.call(this, id);
266 | if (attribute != undefined) {
267 | validateAttribute($(this), attribute, true);
268 | }
269 | },
270 |
271 | // find an attribute config based on the specified attribute ID
272 | find: function (id) {
273 | var attributes = $(this).data('yiiActiveForm').attributes,
274 | result = undefined;
275 | $.each(attributes, function (i) {
276 | if (attributes[i]['id'] == id) {
277 | result = attributes[i];
278 | return false;
279 | }
280 | });
281 | return result;
282 | },
283 |
284 | destroy: function () {
285 | return this.each(function () {
286 | $(this).off('.yiiActiveForm');
287 | $(this).removeData('yiiActiveForm');
288 | });
289 | },
290 |
291 | data: function () {
292 | return this.data('yiiActiveForm');
293 | },
294 |
295 | // validate all applicable inputs in the form
296 | validate: function (forceValidate) {
297 | if (forceValidate) {
298 | $(this).data('yiiActiveForm').submitting = true;
299 | }
300 |
301 | var $form = $(this),
302 | data = $form.data('yiiActiveForm'),
303 | needAjaxValidation = false,
304 | messages = {},
305 | deferreds = deferredArray(),
306 | submitting = data.submitting;
307 |
308 | if (submitting) {
309 | var event = $.Event(events.beforeValidate);
310 | $form.trigger(event, [messages, deferreds]);
311 |
312 | if (event.result === false) {
313 | data.submitting = false;
314 | submitFinalize($form);
315 | return;
316 | }
317 | }
318 |
319 | // client-side validation
320 | $.each(data.attributes, function () {
321 | this.$form = $form;
322 | if (!$(this.input).is(":disabled")) {
323 | this.cancelled = false;
324 | // perform validation only if the form is being submitted or if an attribute is pending validation
325 | if (data.submitting || this.status === 2 || this.status === 3) {
326 | var msg = messages[this.id];
327 | if (msg === undefined) {
328 | msg = [];
329 | messages[this.id] = msg;
330 | }
331 | var event = $.Event(events.beforeValidateAttribute);
332 | $form.trigger(event, [this, msg, deferreds]);
333 | if (event.result !== false) {
334 | if (this.validate) {
335 | this.validate(this, getValue($form, this), msg, deferreds, $form);
336 | }
337 | if (this.enableAjaxValidation) {
338 | needAjaxValidation = true;
339 | }
340 | } else {
341 | this.cancelled = true;
342 | }
343 | }
344 | }
345 | });
346 |
347 | // ajax validation
348 | $.when.apply(this, deferreds).always(function() {
349 | // Remove empty message arrays
350 | for (var i in messages) {
351 | if (0 === messages[i].length) {
352 | delete messages[i];
353 | }
354 | }
355 | if (needAjaxValidation && ($.isEmptyObject(messages) || data.submitting)) {
356 | var $button = data.submitObject,
357 | extData = '&' + data.settings.ajaxParam + '=' + $form.attr('id');
358 | if ($button && $button.length && $button.attr('name')) {
359 | extData += '&' + $button.attr('name') + '=' + $button.attr('value');
360 | }
361 | $.ajax({
362 | url: data.settings.validationUrl,
363 | type: $form.attr('method'),
364 | data: $form.serialize() + extData,
365 | dataType: data.settings.ajaxDataType,
366 | complete: function (jqXHR, textStatus) {
367 | $form.trigger(events.ajaxComplete, [jqXHR, textStatus]);
368 | },
369 | beforeSend: function (jqXHR, settings) {
370 | $form.trigger(events.ajaxBeforeSend, [jqXHR, settings]);
371 | },
372 | success: function (msgs) {
373 | if (msgs !== null && typeof msgs === 'object') {
374 | $.each(data.attributes, function () {
375 | if (!this.enableAjaxValidation || this.cancelled) {
376 | delete msgs[this.id];
377 | }
378 | });
379 | updateInputs($form, $.extend(messages, msgs), submitting);
380 | } else {
381 | updateInputs($form, messages, submitting);
382 | }
383 | },
384 | error: function () {
385 | data.submitting = false;
386 | submitFinalize($form);
387 | }
388 | });
389 | } else if (data.submitting) {
390 | // delay callback so that the form can be submitted without problem
391 | window.setTimeout(function () {
392 | updateInputs($form, messages, submitting);
393 | }, 200);
394 | } else {
395 | updateInputs($form, messages, submitting);
396 | }
397 | });
398 | },
399 |
400 | submitForm: function () {
401 | var $form = $(this),
402 | data = $form.data('yiiActiveForm');
403 |
404 | if (data.validated) {
405 | // Second submit's call (from validate/updateInputs)
406 | data.submitting = false;
407 | var event = $.Event(events.beforeSubmit);
408 | $form.trigger(event);
409 | if (event.result === false) {
410 | data.validated = false;
411 | submitFinalize($form);
412 | return false;
413 | }
414 | updateHiddenButton($form);
415 | return true; // continue submitting the form since validation passes
416 | } else {
417 | // First submit's call (from yii.js/handleAction) - execute validating
418 | setSubmitFinalizeDefer($form);
419 |
420 | if (data.settings.timer !== undefined) {
421 | clearTimeout(data.settings.timer);
422 | }
423 | data.submitting = true;
424 | methods.validate.call($form);
425 | return false;
426 | }
427 | },
428 |
429 | resetForm: function () {
430 | var $form = $(this);
431 | var data = $form.data('yiiActiveForm');
432 | // Because we bind directly to a form reset event instead of a reset button (that may not exist),
433 | // when this function is executed form input values have not been reset yet.
434 | // Therefore we do the actual reset work through setTimeout.
435 | window.setTimeout(function () {
436 | $.each(data.attributes, function () {
437 | // Without setTimeout() we would get the input values that are not reset yet.
438 | this.value = getValue($form, this);
439 | this.status = 0;
440 | var $container = $form.find(this.container),
441 | $input = findInput($form, this),
442 | $errorElement = data.settings.validationStateOn === 'input' ? $input : $container;
443 |
444 | $errorElement.removeClass(
445 | data.settings.validatingCssClass + ' ' +
446 | data.settings.errorCssClass + ' ' +
447 | data.settings.successCssClass
448 | );
449 | $container.find(this.error).html('');
450 | });
451 | $form.find(data.settings.errorSummary).hide().find('ul').html('');
452 | }, 1);
453 | },
454 |
455 | /**
456 | * Updates error messages, input containers, and optionally summary as well.
457 | * If an attribute is missing from messages, it is considered valid.
458 | * @param messages array the validation error messages, indexed by attribute IDs
459 | * @param summary whether to update summary as well.
460 | */
461 | updateMessages: function (messages, summary) {
462 | var $form = $(this);
463 | var data = $form.data('yiiActiveForm');
464 | $.each(data.attributes, function () {
465 | updateInput($form, this, messages);
466 | });
467 | if (summary) {
468 | updateSummary($form, messages);
469 | }
470 | },
471 |
472 | /**
473 | * Updates error messages and input container of a single attribute.
474 | * If messages is empty, the attribute is considered valid.
475 | * @param id attribute ID
476 | * @param messages array with error messages
477 | */
478 | updateAttribute: function(id, messages) {
479 | var attribute = methods.find.call(this, id);
480 | if (attribute != undefined) {
481 | var msg = {};
482 | msg[id] = messages;
483 | updateInput($(this), attribute, msg);
484 | }
485 | }
486 |
487 | };
488 |
489 | var watchAttribute = function ($form, attribute) {
490 | var $input = findInput($form, attribute);
491 | if (attribute.validateOnChange) {
492 | $input.on('change.yiiActiveForm', function () {
493 | validateAttribute($form, attribute, false);
494 | });
495 | }
496 | if (attribute.validateOnBlur) {
497 | $input.on('blur.yiiActiveForm', function () {
498 | if (attribute.status == 0 || attribute.status == 1) {
499 | validateAttribute($form, attribute, true);
500 | }
501 | });
502 | }
503 | if (attribute.validateOnType) {
504 | $input.on('keyup.yiiActiveForm', function (e) {
505 | if ($.inArray(e.which, [16, 17, 18, 37, 38, 39, 40]) !== -1 ) {
506 | return;
507 | }
508 | if (attribute.value !== getValue($form, attribute)) {
509 | validateAttribute($form, attribute, false, attribute.validationDelay);
510 | }
511 | });
512 | }
513 | };
514 |
515 | var unwatchAttribute = function ($form, attribute) {
516 | findInput($form, attribute).off('.yiiActiveForm');
517 | };
518 |
519 | var validateAttribute = function ($form, attribute, forceValidate, validationDelay) {
520 | var data = $form.data('yiiActiveForm');
521 |
522 | if (forceValidate) {
523 | attribute.status = 2;
524 | }
525 | $.each(data.attributes, function () {
526 | if (this.value !== getValue($form, this)) {
527 | this.status = 2;
528 | forceValidate = true;
529 | }
530 | });
531 | if (!forceValidate) {
532 | return;
533 | }
534 |
535 | if (data.settings.timer !== undefined) {
536 | clearTimeout(data.settings.timer);
537 | }
538 | data.settings.timer = window.setTimeout(function () {
539 | if (data.submitting || $form.is(':hidden')) {
540 | return;
541 | }
542 | $.each(data.attributes, function () {
543 | if (this.status === 2) {
544 | this.status = 3;
545 | $form.find(this.container).addClass(data.settings.validatingCssClass);
546 | }
547 | });
548 | methods.validate.call($form);
549 | }, validationDelay ? validationDelay : 200);
550 | };
551 |
552 | /**
553 | * Returns an array prototype with a shortcut method for adding a new deferred.
554 | * The context of the callback will be the deferred object so it can be resolved like ```this.resolve()```
555 | * @returns Array
556 | */
557 | var deferredArray = function () {
558 | var array = [];
559 | array.add = function(callback) {
560 | this.push(new $.Deferred(callback));
561 | };
562 | return array;
563 | };
564 |
565 | var buttonOptions = ['action', 'target', 'method', 'enctype'];
566 |
567 | /**
568 | * Returns current form options
569 | * @param $form
570 | * @returns object Object with button of form options
571 | */
572 | var getFormOptions = function ($form) {
573 | var attributes = {};
574 | for (var i = 0; i < buttonOptions.length; i++) {
575 | attributes[buttonOptions[i]] = $form.attr(buttonOptions[i]);
576 | }
577 |
578 | return attributes;
579 | };
580 |
581 | /**
582 | * Applies temporary form options related to submit button
583 | * @param $form the form jQuery object
584 | * @param $button the button jQuery object
585 | */
586 | var applyButtonOptions = function ($form, $button) {
587 | for (var i = 0; i < buttonOptions.length; i++) {
588 | var value = $button.attr('form' + buttonOptions[i]);
589 | if (value) {
590 | $form.attr(buttonOptions[i], value);
591 | }
592 | }
593 | };
594 |
595 | /**
596 | * Restores original form options
597 | * @param $form the form jQuery object
598 | */
599 | var restoreButtonOptions = function ($form) {
600 | var data = $form.data('yiiActiveForm');
601 |
602 | for (var i = 0; i < buttonOptions.length; i++) {
603 | $form.attr(buttonOptions[i], data.options[buttonOptions[i]] || null);
604 | }
605 | };
606 |
607 | /**
608 | * Updates the error messages and the input containers for all applicable attributes
609 | * @param $form the form jQuery object
610 | * @param messages array the validation error messages
611 | * @param submitting whether this method is called after validation triggered by form submission
612 | */
613 | var updateInputs = function ($form, messages, submitting) {
614 | var data = $form.data('yiiActiveForm');
615 |
616 | if (data === undefined) {
617 | return false;
618 | }
619 |
620 | if (submitting) {
621 | var errorAttributes = [];
622 | $.each(data.attributes, function () {
623 | if (!$(this.input).is(":disabled") && !this.cancelled && updateInput($form, this, messages)) {
624 | errorAttributes.push(this);
625 | }
626 | });
627 |
628 | $form.trigger(events.afterValidate, [messages, errorAttributes]);
629 |
630 | updateSummary($form, messages);
631 |
632 | if (errorAttributes.length) {
633 | if (data.settings.scrollToError) {
634 | var top = $form.find($.map(errorAttributes, function(attribute) {
635 | return attribute.input;
636 | }).join(',')).first().closest(':visible').offset().top - data.settings.scrollToErrorOffset;
637 | if (top < 0) {
638 | top = 0;
639 | } else if (top > $(document).height()) {
640 | top = $(document).height();
641 | }
642 | var wtop = $(window).scrollTop();
643 | if (top < wtop || top > wtop + $(window).height()) {
644 | $(window).scrollTop(top);
645 | }
646 | }
647 | data.submitting = false;
648 | } else {
649 | data.validated = true;
650 | if (data.submitObject) {
651 | applyButtonOptions($form, data.submitObject);
652 | }
653 | $form.submit();
654 | if (data.submitObject) {
655 | restoreButtonOptions($form);
656 | }
657 | }
658 | } else {
659 | $.each(data.attributes, function () {
660 | if (!this.cancelled && (this.status === 2 || this.status === 3)) {
661 | updateInput($form, this, messages);
662 | }
663 | });
664 | }
665 | submitFinalize($form);
666 | };
667 |
668 | /**
669 | * Updates hidden field that represents clicked submit button.
670 | * @param $form the form jQuery object.
671 | */
672 | var updateHiddenButton = function ($form) {
673 | var data = $form.data('yiiActiveForm');
674 | var $button = data.submitObject || $form.find(':submit:first');
675 | // TODO: if the submission is caused by "change" event, it will not work
676 | if ($button.length && $button.attr('type') == 'submit' && $button.attr('name')) {
677 | // simulate button input value
678 | var $hiddenButton = $('input[type="hidden"][name="' + $button.attr('name') + '"]', $form);
679 | if (!$hiddenButton.length) {
680 | $('').attr({
681 | type: 'hidden',
682 | name: $button.attr('name'),
683 | value: $button.attr('value')
684 | }).appendTo($form);
685 | } else {
686 | $hiddenButton.attr('value', $button.attr('value'));
687 | }
688 | }
689 | };
690 |
691 | /**
692 | * Updates the error message and the input container for a particular attribute.
693 | * @param $form the form jQuery object
694 | * @param attribute object the configuration for a particular attribute.
695 | * @param messages array the validation error messages
696 | * @return boolean whether there is a validation error for the specified attribute
697 | */
698 | var updateInput = function ($form, attribute, messages) {
699 | var data = $form.data('yiiActiveForm'),
700 | $input = findInput($form, attribute),
701 | hasError = false;
702 |
703 | if (!$.isArray(messages[attribute.id])) {
704 | messages[attribute.id] = [];
705 | }
706 |
707 | attribute.status = 1;
708 | if ($input.length) {
709 | hasError = messages[attribute.id].length > 0;
710 | var $container = $form.find(attribute.container);
711 | var $error = $container.find(attribute.error);
712 | updateAriaInvalid($form, attribute, hasError);
713 |
714 | var $errorElement = data.settings.validationStateOn === 'input' ? $input : $container;
715 |
716 | if (hasError) {
717 | if (attribute.encodeError) {
718 | $error.text(messages[attribute.id][0]);
719 | } else {
720 | $error.html(messages[attribute.id][0]);
721 | }
722 | $errorElement.removeClass(data.settings.validatingCssClass + ' ' + data.settings.successCssClass)
723 | .addClass(data.settings.errorCssClass);
724 | } else {
725 | $error.empty();
726 | $errorElement.removeClass(data.settings.validatingCssClass + ' ' + data.settings.errorCssClass + ' ')
727 | .addClass(data.settings.successCssClass);
728 | }
729 | attribute.value = getValue($form, attribute);
730 | }
731 |
732 | $form.trigger(events.afterValidateAttribute, [attribute, messages[attribute.id]]);
733 |
734 | return hasError;
735 | };
736 |
737 | /**
738 | * Updates the error summary.
739 | * @param $form the form jQuery object
740 | * @param messages array the validation error messages
741 | */
742 | var updateSummary = function ($form, messages) {
743 | var data = $form.data('yiiActiveForm'),
744 | $summary = $form.find(data.settings.errorSummary),
745 | $ul = $summary.find('ul').empty();
746 |
747 | if ($summary.length && messages) {
748 | $.each(data.attributes, function () {
749 | if ($.isArray(messages[this.id]) && messages[this.id].length) {
750 | var error = $('');
751 | if (data.settings.encodeErrorSummary) {
752 | error.text(messages[this.id][0]);
753 | } else {
754 | error.html(messages[this.id][0]);
755 | }
756 | $ul.append(error);
757 | }
758 | });
759 | $summary.toggle($ul.find('li').length > 0);
760 | }
761 | };
762 |
763 | var getValue = function ($form, attribute) {
764 | var $input = findInput($form, attribute);
765 | var type = $input.attr('type');
766 | if (type === 'checkbox' || type === 'radio') {
767 | var $realInput = $input.filter(':checked');
768 | if (!$realInput.length) {
769 | $realInput = $form.find('input[type=hidden][name="' + $input.attr('name') + '"]');
770 | }
771 |
772 | return $realInput.val();
773 | } else {
774 | return $input.val();
775 | }
776 | };
777 |
778 | var findInput = function ($form, attribute) {
779 | var $input = $form.find(attribute.input);
780 | if ($input.length && $input[0].tagName.toLowerCase() === 'div') {
781 | // checkbox list or radio list
782 | return $input.find('input');
783 | } else {
784 | return $input;
785 | }
786 | };
787 |
788 | var updateAriaInvalid = function ($form, attribute, hasError) {
789 | if (attribute.updateAriaInvalid) {
790 | $form.find(attribute.input).attr('aria-invalid', hasError ? 'true' : 'false');
791 | }
792 | }
793 | })(window.jQuery);
794 |
--------------------------------------------------------------------------------