From d3b0b3da904a4487102232a1edf0f7b4c96e0aa3 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 3 Aug 2016 19:00:55 +0600 Subject: [PATCH 001/247] Get data from a model only if an attribute exists --- src/MultipleInput.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 0962453..c4bdbd6 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -10,6 +10,7 @@ use Yii; use yii\base\Model; +use yii\helpers\ArrayHelper; use yii\widgets\InputWidget; use yii\db\ActiveRecordInterface; use unclead\widgets\renderers\TableRenderer; @@ -127,7 +128,10 @@ protected function initData() } if ($this->model instanceof Model) { - foreach ((array)$this->model->{$this->attribute} as $index => $value) { + $data = $this->model->hasProperty($this->attribute) + ? ArrayHelper::getValue($this->model, $this->attribute, []) + : []; + foreach ((array) $data as $index => $value) { $this->data[$index] = $value; } } From e0720a17bbe8a67e1c480ce9f85f27fe16b2525f Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 3 Aug 2016 19:15:26 +0600 Subject: [PATCH 002/247] Added ability to set custom renderer --- CHANGELOG.md | 4 +- src/MultipleInput.php | 21 ++++++--- src/TabularInput.php | 21 ++++++--- .../BaseRenderer.php | 13 +++--- src/renderers/RendererInterface.php | 43 +++++++++++++++++++ src/renderers/TableRenderer.php | 1 - 6 files changed, 83 insertions(+), 20 deletions(-) rename src/{components => renderers}/BaseRenderer.php (97%) create mode 100644 src/renderers/RendererInterface.php diff --git a/CHANGELOG.md b/CHANGELOG.md index a75b0df..d8b39b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,11 @@ Yii2 multiple input change log ============================== -1.3.2 in development +1.4.0 in development -------------------- +- #94: added ability to set custom renderer (unclead, bokodi-dev) + 1.3.1 ----- diff --git a/src/MultipleInput.php b/src/MultipleInput.php index c4bdbd6..5f69578 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -14,6 +14,7 @@ use yii\widgets\InputWidget; use yii\db\ActiveRecordInterface; use unclead\widgets\renderers\TableRenderer; +use unclead\widgets\renderers\RendererInterface; /** @@ -23,9 +24,9 @@ */ class MultipleInput extends InputWidget { - const POS_HEADER = TableRenderer::POS_HEADER; - const POS_ROW = TableRenderer::POS_ROW; - const POS_FOOTER = TableRenderer::POS_FOOTER; + const POS_HEADER = RendererInterface::POS_HEADER; + const POS_ROW = RendererInterface::POS_ROW; + const POS_FOOTER = RendererInterface::POS_FOOTER; /** * @var ActiveRecordInterface[]|array[] input data @@ -101,6 +102,12 @@ class MultipleInput extends InputWidget */ public $columnClass; + /** + * @var string the name of renderer class. Defaults to `unclead\widgets\renderers\TableRenderer`. + * @since 1.4 + */ + public $rendererClass; + /** * Initialization. * @@ -183,14 +190,18 @@ private function createRenderer() 'context' => $this, ]; - if (!is_null($this->removeButtonOptions)) { + if ($this->removeButtonOptions !== null) { $config['removeButtonOptions'] = $this->removeButtonOptions; } - if (!is_null($this->addButtonOptions)) { + if ($this->addButtonOptions !== null) { $config['addButtonOptions'] = $this->addButtonOptions; } + if (!$this->rendererClass) { + $this->rendererClass = TableRenderer::className(); + } + return new TableRenderer($config); } } \ No newline at end of file diff --git a/src/TabularInput.php b/src/TabularInput.php index 5b95373..e55796c 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -14,6 +14,7 @@ use yii\db\ActiveRecordInterface; use yii\bootstrap\Widget; use unclead\widgets\renderers\TableRenderer; +use unclead\widgets\renderers\RendererInterface; /** * Class TabularInput @@ -21,9 +22,9 @@ */ class TabularInput extends Widget { - const POS_HEADER = TableRenderer::POS_HEADER; - const POS_ROW = TableRenderer::POS_ROW; - const POS_FOOTER = TableRenderer::POS_FOOTER; + const POS_HEADER = RendererInterface::POS_HEADER; + const POS_ROW = RendererInterface::POS_ROW; + const POS_FOOTER = RendererInterface::POS_FOOTER; /** * @var array @@ -95,6 +96,12 @@ class TabularInput extends Widget */ public $columnClass; + /** + * @var string the name of renderer class. Defaults to `unclead\widgets\renderers\TableRenderer`. + * @since 1.4 + */ + public $rendererClass; + /** * Initialization. * @@ -142,14 +149,18 @@ private function createRenderer() 'context' => $this, ]; - if (!is_null($this->removeButtonOptions)) { + if ($this->removeButtonOptions !== null) { $config['removeButtonOptions'] = $this->removeButtonOptions; } - if (!is_null($this->addButtonOptions)) { + if ($this->addButtonOptions !== null) { $config['addButtonOptions'] = $this->addButtonOptions; } + if (!$this->rendererClass) { + $this->rendererClass = TableRenderer::className(); + } + return new TableRenderer($config); } } \ No newline at end of file diff --git a/src/components/BaseRenderer.php b/src/renderers/BaseRenderer.php similarity index 97% rename from src/components/BaseRenderer.php rename to src/renderers/BaseRenderer.php index 087c0da..f4063db 100644 --- a/src/components/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -6,7 +6,7 @@ * @license https://github.com/unclead/yii2-multiple-input/blob/master/LICENSE.md */ -namespace unclead\widgets\components; +namespace unclead\widgets\renderers; use Yii; use yii\helpers\Html; @@ -20,17 +20,14 @@ use unclead\widgets\MultipleInput; use unclead\widgets\TabularInput; use unclead\widgets\assets\MultipleInputAsset; +use unclead\widgets\components\BaseColumn; /** * Class BaseRenderer - * @package unclead\widgets\components + * @package unclead\widgets\renderers */ -abstract class BaseRenderer extends Object +abstract class BaseRenderer extends Object implements RendererInterface { - const POS_HEADER = 'header'; - const POS_ROW = 'row'; - const POS_FOOTER = 'footer'; - /** * @var string the ID of the widget */ @@ -116,7 +113,7 @@ abstract class BaseRenderer extends Object private $indexPlaceholder; /** - * @param $context + * @inheritdoc */ public function setContext($context) { diff --git a/src/renderers/RendererInterface.php b/src/renderers/RendererInterface.php new file mode 100644 index 0000000..d8cb34a --- /dev/null +++ b/src/renderers/RendererInterface.php @@ -0,0 +1,43 @@ + Date: Wed, 3 Aug 2016 19:30:31 +0600 Subject: [PATCH 003/247] #94 update docs --- docs/configuration.md | 3 +++ docs/usage.md | 12 ++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index f9e67fd..2643822 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -27,6 +27,9 @@ This can be `MultipleInput::POS_HEADER`, `MultipleInput::POS_ROW` or `MultipleIn **columnClass** *string*: the name of column class. You can specify your own class to extend base functionality. Defaults to `unclead\widgets\MultipleInputColumn` for `MultipleInput` and `unclead\widgets\TabularColumn` for `TabularInput`. +**rendererClass** *string*: the name of renderer class. You can specify your own class to extend base functionality. +Defaults to `unclead\widgets\renderers\TableRenderer`. + **columns** *array*: the row columns configuration where you can set the properties which is described below **rowOptions** *array|\Closure*: the HTML attributes for the table body rows. This can be either an array diff --git a/docs/usage.md b/docs/usage.md index 96a4962..c0b33b8 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -2,11 +2,11 @@ > You can find source code of examples [here](./examples/) -- [Input with one column](#one-column) -- [Input with multiple column in each row](#multiple-columns) -- [Tabular input](#tabular) +- [One column](#one-column) +- [Multiple columns](#multiple-columns-example) +- [Tabular input](#tabular-input) -##Input with one column +##One column ![Single column example](./images/single-column.gif?raw=true) @@ -32,7 +32,7 @@ use unclead\widgets\MultipleInput; You can find more detail about this use case [here](multiple_input_single.md) -##Input with multiple column in each row +##Multiple columns ![Multiple columns example](./images/multiple-column.gif?raw=true) @@ -105,7 +105,7 @@ use unclead\widgets\MultipleInput; You can find more detail about this use case [here](multiple_input_multiple.md) -##Tabular input +##Tabular input For example you want to manage some models via tabular input. In this case you can use `TabularInput` widget which is based on `MultipleInput` widget. Use the following code for this purpose: From a2023505dd671b174fdfc28c267800bce7cca24c Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Thu, 4 Aug 2016 21:08:38 +0300 Subject: [PATCH 004/247] #94 using of a renderer class for instantiating an object --- src/MultipleInput.php | 8 +++----- src/TabularInput.php | 4 +++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 5f69578..0696a88 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -198,10 +198,8 @@ private function createRenderer() $config['addButtonOptions'] = $this->addButtonOptions; } - if (!$this->rendererClass) { - $this->rendererClass = TableRenderer::className(); - } - - return new TableRenderer($config); + $config['class'] = $this->rendererClass ?: TableRenderer::className(); + + return Yii::createObject($config); } } \ No newline at end of file diff --git a/src/TabularInput.php b/src/TabularInput.php index e55796c..20a0d8b 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -161,6 +161,8 @@ private function createRenderer() $this->rendererClass = TableRenderer::className(); } - return new TableRenderer($config); + $config['class'] = $this->rendererClass ?: TableRenderer::className(); + + return Yii::createObject($config); } } \ No newline at end of file From af12e6916d212942422fa58df0bcb0c0e84e4c57 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 5 Aug 2016 21:14:42 +0300 Subject: [PATCH 005/247] Added first tests --- .gitignore | 3 +- composer.json | 6 +- composer.lock | 1702 ++++++++++++++++++++++++++++++ phpunit.xml.dist | 22 + src/MultipleInput.php | 1 + tests/unit/MultipleInputTest.php | 57 + tests/unit/TestCase.php | 9 + tests/unit/bootstrap.php | 12 + tests/unit/data/TestModel.php | 24 + 9 files changed, 1834 insertions(+), 2 deletions(-) create mode 100644 composer.lock create mode 100644 phpunit.xml.dist create mode 100644 tests/unit/MultipleInputTest.php create mode 100644 tests/unit/TestCase.php create mode 100644 tests/unit/bootstrap.php create mode 100644 tests/unit/data/TestModel.php diff --git a/.gitignore b/.gitignore index 766e96f..945dd9f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea/* -/node_modules \ No newline at end of file +node_modules/ +vendor/ \ No newline at end of file diff --git a/composer.json b/composer.json index 02967ec..a84fff7 100644 --- a/composer.json +++ b/composer.json @@ -25,10 +25,14 @@ "php": ">=5.4.0", "yiisoft/yii2": "*" }, + "require-dev": { + "phpunit/phpunit": "5.4.*" + }, "autoload": { "psr-4": { "unclead\\widgets\\examples\\": "examples/", - "unclead\\widgets\\": "src/" + "unclead\\widgets\\": "src/", + "unclead\\multipleinput\\tests\\": "tests/" } } } diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..7aa8236 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1702 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "0c37169e6d126cfea4070e3b6703de94", + "content-hash": "d7051054c08c885a9347ca56fb769211", + "packages": [ + { + "name": "bower-asset/jquery", + "version": "2.2.4", + "source": { + "type": "git", + "url": "https://github.com/jquery/jquery-dist.git", + "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/c0185ab7c75aab88762c5aae780b9d83b80eda72", + "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72", + "shasum": "" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": "dist/jquery.js", + "bower-asset-ignore": [ + "package.json" + ] + }, + "license": [ + "MIT" + ], + "keywords": [ + "browser", + "javascript", + "jquery", + "library" + ] + }, + { + "name": "bower-asset/jquery.inputmask", + "version": "3.2.7", + "source": { + "type": "git", + "url": "https://github.com/RobinHerbots/jquery.inputmask.git", + "reference": "5a72c563b502b8e05958a524cdfffafe9987be38" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/5a72c563b502b8e05958a524cdfffafe9987be38", + "reference": "5a72c563b502b8e05958a524cdfffafe9987be38", + "shasum": "" + }, + "require": { + "bower-asset/jquery": ">=1.7" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": [ + "./dist/inputmask/inputmask.js" + ], + "bower-asset-ignore": [ + "**/*", + "!dist/*", + "!dist/inputmask/*", + "!dist/min/*", + "!dist/min/inputmask/*", + "!extra/bindings/*", + "!extra/dependencyLibs/*", + "!extra/phone-codes/*" + ] + }, + "license": [ + "http://opensource.org/licenses/mit-license.php" + ], + "description": "jquery.inputmask is a jquery plugin which create an input mask.", + "keywords": [ + "form", + "input", + "inputmask", + "jquery", + "mask", + "plugins" + ] + }, + { + "name": "bower-asset/punycode", + "version": "v1.3.2", + "source": { + "type": "git", + "url": "https://github.com/bestiejs/punycode.js.git", + "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3", + "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3", + "shasum": "" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": "punycode.js", + "bower-asset-ignore": [ + "coverage", + "tests", + ".*", + "component.json", + "Gruntfile.js", + "node_modules", + "package.json" + ] + } + }, + { + "name": "bower-asset/yii2-pjax", + "version": "v2.0.6", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/jquery-pjax.git", + "reference": "60728da6ade5879e807a49ce59ef9a72039b8978" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/60728da6ade5879e807a49ce59ef9a72039b8978", + "reference": "60728da6ade5879e807a49ce59ef9a72039b8978", + "shasum": "" + }, + "require": { + "bower-asset/jquery": ">=1.8" + }, + "type": "bower-asset-library", + "extra": { + "bower-asset-main": "./jquery.pjax.js", + "bower-asset-ignore": [ + ".travis.yml", + "Gemfile", + "Gemfile.lock", + "CONTRIBUTING.md", + "vendor/", + "script/", + "test/" + ] + }, + "license": [ + "MIT" + ] + }, + { + "name": "cebe/markdown", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/cebe/markdown.git", + "reference": "54a2c49de31cc44e864ebf0500a35ef21d0010b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/cebe/markdown/zipball/54a2c49de31cc44e864ebf0500a35ef21d0010b2", + "reference": "54a2c49de31cc44e864ebf0500a35ef21d0010b2", + "shasum": "" + }, + "require": { + "lib-pcre": "*", + "php": ">=5.4.0" + }, + "require-dev": { + "cebe/indent": "*", + "facebook/xhprof": "*@dev", + "phpunit/phpunit": "4.1.*" + }, + "bin": [ + "bin/markdown" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "cebe\\markdown\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Creator" + } + ], + "description": "A super fast, highly extensible markdown parser for PHP", + "homepage": "https://github.com/cebe/markdown#readme", + "keywords": [ + "extensible", + "fast", + "gfm", + "markdown", + "markdown-extra" + ], + "time": "2015-03-06 05:28:07" + }, + { + "name": "ezyang/htmlpurifier", + "version": "v4.8.0", + "source": { + "type": "git", + "url": "https://github.com/ezyang/htmlpurifier.git", + "reference": "d0c392f77d2f2a3dcf7fcb79e2a1e2b8804e75b2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/d0c392f77d2f2a3dcf7fcb79e2a1e2b8804e75b2", + "reference": "d0c392f77d2f2a3dcf7fcb79e2a1e2b8804e75b2", + "shasum": "" + }, + "require": { + "php": ">=5.2" + }, + "type": "library", + "autoload": { + "psr-0": { + "HTMLPurifier": "library/" + }, + "files": [ + "library/HTMLPurifier.composer.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL" + ], + "authors": [ + { + "name": "Edward Z. Yang", + "email": "admin@htmlpurifier.org", + "homepage": "http://ezyang.com" + } + ], + "description": "Standards compliant HTML filter written in PHP", + "homepage": "http://htmlpurifier.org/", + "keywords": [ + "html" + ], + "time": "2016-07-16 12:58:58" + }, + { + "name": "yiisoft/yii2", + "version": "2.0.9", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-framework.git", + "reference": "2b75151ea60e1fd820046416eee2e89c3dda1133" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2b75151ea60e1fd820046416eee2e89c3dda1133", + "reference": "2b75151ea60e1fd820046416eee2e89c3dda1133", + "shasum": "" + }, + "require": { + "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", + "bower-asset/jquery.inputmask": "~3.2.2", + "bower-asset/punycode": "1.3.*", + "bower-asset/yii2-pjax": "~2.0.1", + "cebe/markdown": "~1.0.0 | ~1.1.0", + "ext-ctype": "*", + "ext-mbstring": "*", + "ezyang/htmlpurifier": "~4.6", + "lib-pcre": "*", + "php": ">=5.4.0", + "yiisoft/yii2-composer": "~2.0.4" + }, + "bin": [ + "yii" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com", + "homepage": "http://www.yiiframework.com/", + "role": "Founder and project lead" + }, + { + "name": "Alexander Makarov", + "email": "sam@rmcreative.ru", + "homepage": "http://rmcreative.ru/", + "role": "Core framework development" + }, + { + "name": "Maurizio Domba", + "homepage": "http://mdomba.info/", + "role": "Core framework development" + }, + { + "name": "Carsten Brandt", + "email": "mail@cebe.cc", + "homepage": "http://cebe.cc/", + "role": "Core framework development" + }, + { + "name": "Timur Ruziev", + "email": "resurtm@gmail.com", + "homepage": "http://resurtm.com/", + "role": "Core framework development" + }, + { + "name": "Paul Klimov", + "email": "klimov.paul@gmail.com", + "role": "Core framework development" + }, + { + "name": "Dmitry Naumenko", + "email": "d.naumenko.a@gmail.com", + "role": "Core framework development" + } + ], + "description": "Yii PHP Framework Version 2", + "homepage": "http://www.yiiframework.com/", + "keywords": [ + "framework", + "yii2" + ], + "time": "2016-07-11 13:36:42" + }, + { + "name": "yiisoft/yii2-composer", + "version": "2.0.4", + "source": { + "type": "git", + "url": "https://github.com/yiisoft/yii2-composer.git", + "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/7452fd908a5023b8bb5ea1b123a174ca080de464", + "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1.0" + }, + "type": "composer-plugin", + "extra": { + "class": "yii\\composer\\Plugin", + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "yii\\composer\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Qiang Xue", + "email": "qiang.xue@gmail.com" + } + ], + "description": "The composer plugin for Yii extension installer", + "keywords": [ + "composer", + "extension installer", + "yii2" + ], + "time": "2016-02-06 00:49:24" + } + ], + "packages-dev": [ + { + "name": "doctrine/instantiator", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/doctrine/instantiator.git", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", + "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "shasum": "" + }, + "require": { + "php": ">=5.3,<8.0-DEV" + }, + "require-dev": { + "athletic/athletic": "~0.1.8", + "ext-pdo": "*", + "ext-phar": "*", + "phpunit/phpunit": "~4.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Marco Pivetta", + "email": "ocramius@gmail.com", + "homepage": "http://ocramius.github.com/" + } + ], + "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", + "homepage": "https://github.com/doctrine/instantiator", + "keywords": [ + "constructor", + "instantiate" + ], + "time": "2015-06-14 21:17:01" + }, + { + "name": "myclabs/deep-copy", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/myclabs/DeepCopy.git", + "reference": "a8773992b362b58498eed24bf85005f363c34771" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/a8773992b362b58498eed24bf85005f363c34771", + "reference": "a8773992b362b58498eed24bf85005f363c34771", + "shasum": "" + }, + "require": { + "php": ">=5.4.0" + }, + "require-dev": { + "doctrine/collections": "1.*", + "phpunit/phpunit": "~4.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "DeepCopy\\": "src/DeepCopy/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Create deep copies (clones) of your objects", + "homepage": "https://github.com/myclabs/DeepCopy", + "keywords": [ + "clone", + "copy", + "duplicate", + "object", + "object graph" + ], + "time": "2015-11-20 12:04:31" + }, + { + "name": "phpdocumentor/reflection-common", + "version": "1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionCommon.git", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "description": "Common reflection classes used by phpdocumentor to reflect the code structure", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "FQSEN", + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "time": "2015-12-27 11:43:31" + }, + { + "name": "phpdocumentor/reflection-docblock", + "version": "3.1.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd", + "reference": "9270140b940ff02e58ec577c237274e92cd40cdd", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0@dev", + "phpdocumentor/type-resolver": "^0.2.0", + "webmozart/assert": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "time": "2016-06-10 09:48:41" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "0.2", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443", + "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443", + "shasum": "" + }, + "require": { + "php": ">=5.5", + "phpdocumentor/reflection-common": "^1.0" + }, + "require-dev": { + "mockery/mockery": "^0.9.4", + "phpunit/phpunit": "^5.2||^4.8.24" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": [ + "src/" + ] + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + } + ], + "time": "2016-06-10 07:14:17" + }, + { + "name": "phpspec/prophecy", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/phpspec/prophecy.git", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", + "reference": "58a8137754bc24b25740d4281399a4a3596058e0", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.3|^7.0", + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", + "sebastian/comparator": "^1.1", + "sebastian/recursion-context": "^1.0" + }, + "require-dev": { + "phpspec/phpspec": "^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-0": { + "Prophecy\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Konstantin Kudryashov", + "email": "ever.zet@gmail.com", + "homepage": "http://everzet.com" + }, + { + "name": "Marcello Duarte", + "email": "marcello.duarte@gmail.com" + } + ], + "description": "Highly opinionated mocking framework for PHP 5.3+", + "homepage": "https://github.com/phpspec/prophecy", + "keywords": [ + "Double", + "Dummy", + "fake", + "mock", + "spy", + "stub" + ], + "time": "2016-06-07 08:13:47" + }, + { + "name": "phpunit/php-code-coverage", + "version": "4.0.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-code-coverage.git", + "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5f3f7e736d6319d5f1fc402aff8b026da26709a3", + "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0", + "phpunit/php-file-iterator": "~1.3", + "phpunit/php-text-template": "~1.2", + "phpunit/php-token-stream": "^1.4.2", + "sebastian/code-unit-reverse-lookup": "~1.0", + "sebastian/environment": "^1.3.2 || ^2.0", + "sebastian/version": "~1.0|~2.0" + }, + "require-dev": { + "ext-xdebug": ">=2.1.4", + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-dom": "*", + "ext-xdebug": ">=2.4.0", + "ext-xmlwriter": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", + "homepage": "https://github.com/sebastianbergmann/php-code-coverage", + "keywords": [ + "coverage", + "testing", + "xunit" + ], + "time": "2016-07-26 14:39:29" + }, + { + "name": "phpunit/php-file-iterator", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-file-iterator.git", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "FilterIterator implementation that filters files based on a list of suffixes.", + "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", + "keywords": [ + "filesystem", + "iterator" + ], + "time": "2015-06-21 13:08:43" + }, + { + "name": "phpunit/php-text-template", + "version": "1.2.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-text-template.git", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Simple template engine.", + "homepage": "https://github.com/sebastianbergmann/php-text-template/", + "keywords": [ + "template" + ], + "time": "2015-06-21 13:50:34" + }, + { + "name": "phpunit/php-timer", + "version": "1.0.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-timer.git", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", + "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4|~5" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Utility class for timing", + "homepage": "https://github.com/sebastianbergmann/php-timer/", + "keywords": [ + "timer" + ], + "time": "2016-05-12 18:03:57" + }, + { + "name": "phpunit/php-token-stream", + "version": "1.4.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/php-token-stream.git", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Wrapper around PHP's tokenizer extension.", + "homepage": "https://github.com/sebastianbergmann/php-token-stream/", + "keywords": [ + "tokenizer" + ], + "time": "2015-09-15 10:49:45" + }, + { + "name": "phpunit/phpunit", + "version": "5.4.8", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit.git", + "reference": "3132365e1430c091f208e120b8845d39c25f20e6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3132365e1430c091f208e120b8845d39c25f20e6", + "reference": "3132365e1430c091f208e120b8845d39c25f20e6", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "ext-json": "*", + "ext-pcre": "*", + "ext-reflection": "*", + "ext-spl": "*", + "myclabs/deep-copy": "~1.3", + "php": "^5.6 || ^7.0", + "phpspec/prophecy": "^1.3.1", + "phpunit/php-code-coverage": "^4.0.1", + "phpunit/php-file-iterator": "~1.4", + "phpunit/php-text-template": "~1.2", + "phpunit/php-timer": "^1.0.6", + "phpunit/phpunit-mock-objects": "^3.2", + "sebastian/comparator": "~1.1", + "sebastian/diff": "~1.2", + "sebastian/environment": "^1.3 || ^2.0", + "sebastian/exporter": "~1.2", + "sebastian/global-state": "~1.0", + "sebastian/object-enumerator": "~1.0", + "sebastian/resource-operations": "~1.0", + "sebastian/version": "~1.0|~2.0", + "symfony/yaml": "~2.1|~3.0" + }, + "conflict": { + "phpdocumentor/reflection-docblock": "3.0.2" + }, + "suggest": { + "phpunit/php-invoker": "~1.1" + }, + "bin": [ + "phpunit" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "The PHP Unit Testing framework.", + "homepage": "https://phpunit.de/", + "keywords": [ + "phpunit", + "testing", + "xunit" + ], + "time": "2016-07-26 14:48:00" + }, + { + "name": "phpunit/phpunit-mock-objects", + "version": "3.2.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", + "reference": "b13d0d9426ced06958bd32104653526a6c998a52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/b13d0d9426ced06958bd32104653526a6c998a52", + "reference": "b13d0d9426ced06958bd32104653526a6c998a52", + "shasum": "" + }, + "require": { + "doctrine/instantiator": "^1.0.2", + "php": "^5.6 || ^7.0", + "phpunit/php-text-template": "^1.2", + "sebastian/exporter": "^1.2" + }, + "conflict": { + "phpunit/phpunit": "<5.4.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4" + }, + "suggest": { + "ext-soap": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sb@sebastian-bergmann.de", + "role": "lead" + } + ], + "description": "Mock Object library for PHPUnit", + "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", + "keywords": [ + "mock", + "xunit" + ], + "time": "2016-06-12 07:37:26" + }, + { + "name": "sebastian/code-unit-reverse-lookup", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", + "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Looks up which function or method a line of code belongs to", + "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "time": "2016-02-13 06:45:14" + }, + { + "name": "sebastian/comparator", + "version": "1.2.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/comparator.git", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", + "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/diff": "~1.2", + "sebastian/exporter": "~1.2" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides the functionality to compare PHP values for equality", + "homepage": "http://www.github.com/sebastianbergmann/comparator", + "keywords": [ + "comparator", + "compare", + "equality" + ], + "time": "2015-07-26 15:48:44" + }, + { + "name": "sebastian/diff", + "version": "1.4.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", + "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff" + ], + "time": "2015-12-08 07:14:41" + }, + { + "name": "sebastian/environment", + "version": "1.3.7", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/environment.git", + "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4e8f0da10ac5802913afc151413bc8c53b6c2716", + "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides functionality to handle HHVM/PHP environments", + "homepage": "http://www.github.com/sebastianbergmann/environment", + "keywords": [ + "Xdebug", + "environment", + "hhvm" + ], + "time": "2016-05-17 03:18:57" + }, + { + "name": "sebastian/exporter", + "version": "1.2.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/exporter.git", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", + "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "ext-mbstring": "*", + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Volker Dusch", + "email": "github@wallbash.com" + }, + { + "name": "Bernhard Schussek", + "email": "bschussek@2bepublished.at" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides the functionality to export PHP variables for visualization", + "homepage": "http://www.github.com/sebastianbergmann/exporter", + "keywords": [ + "export", + "exporter" + ], + "time": "2016-06-17 09:04:28" + }, + { + "name": "sebastian/global-state", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/global-state.git", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", + "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.2" + }, + "suggest": { + "ext-uopz": "*" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Snapshotting of global state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", + "keywords": [ + "global state" + ], + "time": "2015-10-12 03:26:01" + }, + { + "name": "sebastian/object-enumerator", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/object-enumerator.git", + "reference": "d4ca2fb70344987502567bc50081c03e6192fb26" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/d4ca2fb70344987502567bc50081c03e6192fb26", + "reference": "d4ca2fb70344987502567bc50081c03e6192fb26", + "shasum": "" + }, + "require": { + "php": ">=5.6", + "sebastian/recursion-context": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "~5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Traverses array structures and object graphs to enumerate all referenced objects", + "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "time": "2016-01-28 13:25:10" + }, + { + "name": "sebastian/recursion-context", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/recursion-context.git", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", + "reference": "913401df809e99e4f47b27cdd781f4a258d58791", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "~4.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Jeff Welch", + "email": "whatthejeff@gmail.com" + }, + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Adam Harvey", + "email": "aharvey@php.net" + } + ], + "description": "Provides functionality to recursively process PHP variables", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "time": "2015-11-11 19:50:13" + }, + { + "name": "sebastian/resource-operations", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/resource-operations.git", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", + "shasum": "" + }, + "require": { + "php": ">=5.6.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + } + ], + "description": "Provides a list of PHP built-in functions that operate on resources", + "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "time": "2015-07-28 20:34:47" + }, + { + "name": "sebastian/version", + "version": "2.0.0", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/version.git", + "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", + "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de", + "role": "lead" + } + ], + "description": "Library that helps with managing the version number of Git-hosted PHP projects", + "homepage": "https://github.com/sebastianbergmann/version", + "time": "2016-02-04 12:56:52" + }, + { + "name": "symfony/yaml", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "1819adf2066880c7967df7180f4f662b6f0567ac" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/1819adf2066880c7967df7180f4f662b6f0567ac", + "reference": "1819adf2066880c7967df7180f4f662b6f0567ac", + "shasum": "" + }, + "require": { + "php": ">=5.5.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2016-07-17 14:02:08" + }, + { + "name": "webmozart/assert", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/webmozart/assert.git", + "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/webmozart/assert/zipball/30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", + "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phpunit/phpunit": "^4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "Webmozart\\Assert\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Bernhard Schussek", + "email": "bschussek@gmail.com" + } + ], + "description": "Assertions to validate method input/output with nice error messages.", + "keywords": [ + "assert", + "check", + "validate" + ], + "time": "2015-08-24 13:29:44" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.0" + }, + "platform-dev": [] +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..fd3fa65 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,22 @@ + + + + + ./tests + ./vendor + + + + + ./tests/unit/ + + + + + + \ No newline at end of file diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 0696a88..d093a89 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -138,6 +138,7 @@ protected function initData() $data = $this->model->hasProperty($this->attribute) ? ArrayHelper::getValue($this->model, $this->attribute, []) : []; + foreach ((array) $data as $index => $value) { $this->data[$index] = $value; } diff --git a/tests/unit/MultipleInputTest.php b/tests/unit/MultipleInputTest.php new file mode 100644 index 0000000..8fb5cd3 --- /dev/null +++ b/tests/unit/MultipleInputTest.php @@ -0,0 +1,57 @@ + $model, + 'attribute' => 'email', + ]); + + $expected = [ + ['name' => 'email', 'type' => MultipleInputColumn::TYPE_TEXT_INPUT] + ]; + + $this->assertEquals($expected, $widget->columns); + } + + public function testInitData() + { + $model = new TestModel(); + + $dataExample = [ + ['email' => 'test@example.com'], + ['email' => 'test@example.com'], + ]; + + $widget = new MultipleInput([ + 'model' => $model, + 'attribute' => 'email', + 'data' => $dataExample + ]); + + $this->assertEquals($dataExample, $widget->data); + + $model->email = ['test@example.com']; + + $widget = new MultipleInput([ + 'model' => $model, + 'attribute' => 'email', + ]); + + $this->assertEquals($model->email, $widget->data); + } +} \ No newline at end of file diff --git a/tests/unit/TestCase.php b/tests/unit/TestCase.php new file mode 100644 index 0000000..9e05bcd --- /dev/null +++ b/tests/unit/TestCase.php @@ -0,0 +1,9 @@ + Date: Sun, 4 Sep 2016 22:18:43 +0300 Subject: [PATCH 006/247] Respect "addButtonPosition" option when rendering the button --- src/MultipleInput.php | 4 ++-- src/TabularInput.php | 4 ++-- src/renderers/BaseRenderer.php | 3 +++ src/renderers/TableRenderer.php | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 0696a88..e6bcf2f 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -76,9 +76,9 @@ class MultipleInput extends InputWidget public $min; /** - * @var string|array position of add button. By default button is rendered in the row. + * @var string|array position of add button. */ - public $addButtonPosition = self::POS_ROW; + public $addButtonPosition; /** * @var array|\Closure the HTML attributes for the table body rows. This can be either an array diff --git a/src/TabularInput.php b/src/TabularInput.php index 20a0d8b..a3c2262 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -69,9 +69,9 @@ class TabularInput extends Widget public $models; /** - * @var string|array position of add button. By default button is rendered in the row. + * @var string|array position of add button. */ - public $addButtonPosition = self::POS_ROW; + public $addButtonPosition; /** * @var array|\Closure the HTML attributes for the table body rows. This can be either an array diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index f4063db..725057d 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -182,6 +182,9 @@ private function prepareLimit() private function prepareButtons() { + if ($this->addButtonPosition === null || $this->addButtonPosition === []) { + $this->addButtonPosition = $this->min === 0 ? self::POS_HEADER : self::POS_ROW; + } if (!is_array($this->addButtonPosition)) { $this->addButtonPosition = (array) $this->addButtonPosition; } diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index efd9ec7..6aad0e7 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -59,7 +59,7 @@ public function renderHeader() } if ($this->limit === null || ($this->limit >= 1 && $this->limit !== $this->min)) { - $button = $this->min === 0 || $this->isAddButtonPositionHeader() ? $this->renderAddButton() : ''; + $button = $this->isAddButtonPositionHeader() ? $this->renderAddButton() : ''; $cells[] = Html::tag('th', $button, [ 'class' => 'list-cell__button' From 7f3d8e9d75b62d6c23b762424308e311a19bf77d Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 4 Sep 2016 22:18:59 +0300 Subject: [PATCH 007/247] Prepare to release --- CHANGELOG.md | 5 +++-- README.md | 2 +- UPGRADE.md | 5 +++++ composer.json | 2 +- package.json | 2 +- 5 files changed, 11 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d8b39b0..0ed3355 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ Yii2 multiple input change log ============================== -1.4.0 in development --------------------- +1.4.0 +----- - #94: added ability to set custom renderer (unclead, bokodi-dev) +- #97: Respect `addButtonPosition` when rendering the button (unclead) 1.3.1 ----- diff --git a/README.md b/README.md index 1a52fca..cc5fdba 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v1.3.1. Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v1.4.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/UPGRADE.md b/UPGRADE.md index 0566f67..5f9a61a 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -8,6 +8,11 @@ if you want to upgrade from version A to version C and there is version B between A and C, you need to following the instructions for both A and B. +Upgrade from 1.3 to 1.4 +- In scope of #97 was changed a behavior of rendering add button. The button renders now depends on option `addButtonPosition` and now this +option is not set by default. +----------------------- + Upgrade from 1.2 to 1.3 ----------------------- diff --git a/composer.json b/composer.json index 02967ec..6bb3d1e 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "1.3.1", + "version": "1.4.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 693f6cb..9789fc2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "1.3.1", + "version": "1.4.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From edff08e0169274dee63ecf7c62d3be224fd1e9d8 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 16 Sep 2016 09:04:14 +0300 Subject: [PATCH 008/247] Respect "defaultValue" if it is set and current value is empty --- CHANGELOG.md | 5 +++++ examples/views/tabular-input.php | 3 ++- src/components/BaseColumn.php | 11 ++++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ed3355..abdb06f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Yii2 multiple input change log ============================== +1.4.1 +===== + +- #99: Respect "defaultValue" if it is set and current value is empty (unclead) + 1.4.0 ----- diff --git a/examples/views/tabular-input.php b/examples/views/tabular-input.php index 4c6fdc4..b921a02 100644 --- a/examples/views/tabular-input.php +++ b/examples/views/tabular-input.php @@ -19,7 +19,7 @@ ]) ?> $models, + 'models' => [], 'attributeOptions' => [ 'enableAjaxValidation' => true, 'enableClientValidation' => false, @@ -36,6 +36,7 @@ 'name' => 'title', 'title' => 'Title', 'type' => TabularColumn::TYPE_TEXT_INPUT, + 'defaultValue' => 'Test', 'enableError' => true ], [ diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 317ef25..dd55626 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -16,6 +16,7 @@ use yii\helpers\ArrayHelper; use yii\helpers\Html; use yii\helpers\Inflector; +use unclead\widgets\renderers\BaseRenderer; /** * Class BaseColumn. @@ -179,6 +180,7 @@ protected function prepareValue() if ($this->value instanceof \Closure) { $value = call_user_func($this->value, $data); } else { + $value = null; if ($data instanceof ActiveRecordInterface ) { $value = $data->getAttribute($this->name); } elseif ($data instanceof Model) { @@ -187,13 +189,20 @@ protected function prepareValue() $value = ArrayHelper::getValue($data, $this->name, null); } elseif(is_string($data) || is_numeric($data)) { $value = $data; - }else { + } + + if ($this->isEmpty($value) && $this->defaultValue !== null) { $value = $this->defaultValue; } } return $value; } + protected function isEmpty($value) + { + return $value === null || $value === [] || $value === ''; + } + /** * Returns element id. * From ac564a7f4afaada04e57aa11c637d2890b114d45 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 16 Sep 2016 09:06:30 +0300 Subject: [PATCH 009/247] Release 1.4.1 --- README.md | 2 +- composer.json | 2 +- docs/usage.md | 2 +- package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cc5fdba..29f89e6 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v1.4.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v1.4.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 6bb3d1e..447ea4d 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "1.4.0", + "version": "1.4.1", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/docs/usage.md b/docs/usage.md index c0b33b8..cf8be26 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -3,7 +3,7 @@ > You can find source code of examples [here](./examples/) - [One column](#one-column) -- [Multiple columns](#multiple-columns-example) +- [Multiple columns](#multiple-columns) - [Tabular input](#tabular-input) ##One column diff --git a/package.json b/package.json index 9789fc2..214c40f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "1.4.0", + "version": "1.4.1", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 157a7ddd8ae4f89cb632f6e0366f9a1df22d276c Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 16 Sep 2016 09:07:41 +0300 Subject: [PATCH 010/247] Fixed example --- examples/views/tabular-input.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/views/tabular-input.php b/examples/views/tabular-input.php index b921a02..08f768a 100644 --- a/examples/views/tabular-input.php +++ b/examples/views/tabular-input.php @@ -19,7 +19,7 @@ ]) ?> [], + 'models' => $models, 'attributeOptions' => [ 'enableAjaxValidation' => true, 'enableClientValidation' => false, From a08491f89dc9255ed1afcdf384847a7265962839 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 24 Sep 2016 19:47:39 +0300 Subject: [PATCH 011/247] Respect default value when column is a custom widget --- src/components/BaseColumn.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index dd55626..2597a13 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -477,6 +477,7 @@ protected function renderWidget($type, $name, $value, $options) { $model = $this->getModel(); if ($model instanceof Model) { + $model->{$this->name} = $value; $widgetOptions = [ 'model' => $model, 'attribute' => $this->name, From 53521f8cdf6f00316b138f1a9963d2a4a4a61ec4 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 1 Oct 2016 21:14:42 +0300 Subject: [PATCH 012/247] Change namespace --- README.md | 2 +- UPGRADE.md | 8 +++++++- composer.json | 4 ++-- docs/configuration.md | 4 ++-- docs/multiple_input_multiple.md | 4 ++-- docs/multiple_input_single.md | 4 ++-- docs/tabular_input.md | 12 ++++++------ docs/usage.md | 4 ++-- examples/actions/EmbeddedInputAction.php | 6 +++--- examples/actions/MultipleInputAction.php | 6 +++--- examples/actions/TabularInputAction.php | 6 +++--- examples/models/ExampleModel.php | 4 ++-- examples/models/Item.php | 4 ++-- examples/views/embedded-input.php | 6 +++--- examples/views/multiple-input.php | 6 +++--- examples/views/tabular-input.php | 6 +++--- src/MultipleInput.php | 10 +++++----- src/MultipleInputColumn.php | 6 +++--- src/TabularColumn.php | 6 +++--- src/TabularInput.php | 12 ++++++------ src/assets/MultipleInputAsset.php | 4 ++-- src/components/BaseColumn.php | 5 +++-- src/renderers/BaseRenderer.php | 12 ++++++------ src/renderers/RendererInterface.php | 4 ++-- src/renderers/TableRenderer.php | 6 +++--- 25 files changed, 79 insertions(+), 72 deletions(-) diff --git a/README.md b/README.md index cc5fdba..4f9db91 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ For example you want to have an ability of entering several emails of user on pr In this case you can use yii2-multiple-input widget like in the following code ```php -use unclead\widgets\MultipleInput; +use yii\multipleinput\MultipleInput; ... diff --git a/UPGRADE.md b/UPGRADE.md index 5f9a61a..f878c7f 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -8,10 +8,16 @@ if you want to upgrade from version A to version C and there is version B between A and C, you need to following the instructions for both A and B. +Upgrade from 1.4 to 2.0 +----------------------- + +- Change namespace prefix `unclead\widgets\` to `yii\multipleinput\`. + Upgrade from 1.3 to 1.4 +----------------------- - In scope of #97 was changed a behavior of rendering add button. The button renders now depends on option `addButtonPosition` and now this option is not set by default. ------------------------ + Upgrade from 1.2 to 1.3 ----------------------- diff --git a/composer.json b/composer.json index 6bb3d1e..ed3d349 100644 --- a/composer.json +++ b/composer.json @@ -27,8 +27,8 @@ }, "autoload": { "psr-4": { - "unclead\\widgets\\examples\\": "examples/", - "unclead\\widgets\\": "src/" + "yii\\multipleinput\\examples\\": "examples/", + "yii\\multipleinput\\": "src/" } } } diff --git a/docs/configuration.md b/docs/configuration.md index 2643822..1a08966 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -25,10 +25,10 @@ This can be `MultipleInput::POS_HEADER`, `MultipleInput::POS_ROW` or `MultipleIn **allowEmptyList** *boolean*: whether to allow the empty list **columnClass** *string*: the name of column class. You can specify your own class to extend base functionality. -Defaults to `unclead\widgets\MultipleInputColumn` for `MultipleInput` and `unclead\widgets\TabularColumn` for `TabularInput`. +Defaults to `yii\multipleinput\MultipleInputColumn` for `MultipleInput` and `yii\multipleinput\TabularColumn` for `TabularInput`. **rendererClass** *string*: the name of renderer class. You can specify your own class to extend base functionality. -Defaults to `unclead\widgets\renderers\TableRenderer`. +Defaults to `yii\multipleinput\renderers\TableRenderer`. **columns** *array*: the row columns configuration where you can set the properties which is described below diff --git a/docs/multiple_input_multiple.md b/docs/multiple_input_multiple.md index b04d267..807fe21 100644 --- a/docs/multiple_input_multiple.md +++ b/docs/multiple_input_multiple.md @@ -36,8 +36,8 @@ Then we have to use `MultipleInput` widget for rendering form field in the view ```php use yii\bootstrap\ActiveForm; -use unclead\widgets\MultipleInput; -use unclead\widgets\examples\models\ExampleModel; +use yii\multipleinput\MultipleInput; +use yii\multipleinput\examples\models\ExampleModel; use yii\helpers\Html; /* @var $this \yii\base\View */ diff --git a/docs/multiple_input_single.md b/docs/multiple_input_single.md index f39f193..5095122 100644 --- a/docs/multiple_input_single.md +++ b/docs/multiple_input_single.md @@ -22,8 +22,8 @@ Then we have to use `MultipleInput` widget for rendering form field in the view ```php use yii\bootstrap\ActiveForm; -use unclead\widgets\MultipleInput; -use unclead\widgets\examples\models\ExampleModel; +use yii\multipleinput\MultipleInput; +use yii\multipleinput\examples\models\ExampleModel; use yii\helpers\Html; /* @var $this \yii\base\View */ diff --git a/docs/tabular_input.md b/docs/tabular_input.md index 621ea7d..9ca5237 100644 --- a/docs/tabular_input.md +++ b/docs/tabular_input.md @@ -7,7 +7,7 @@ In this case you can use `yii2-multiple-input` widget for supporting tabular inp Our test model can looks like as the following snippet ```php -namespace unclead\widgets\examples\models; +namespace yii\multipleinput\examples\models; use Yii; use yii\base\Model; @@ -16,7 +16,7 @@ use vova07\fileapi\behaviors\UploadBehavior; /** * Class Item - * @package unclead\widgets\examples\models + * @package yii\multipleinput\examples\models */ class Item extends Model { @@ -57,9 +57,9 @@ Then we have to use `TabularInput` widget for rendering form field in the view f 'title', 'title' => 'Title', - 'type' => \unclead\widgets\MultipleInputColumn::TYPE_TEXT_INPUT, + 'type' => \yii\multipleinput\MultipleInputColumn::TYPE_TEXT_INPUT, ], [ 'name' => 'description', @@ -135,7 +135,7 @@ Your action can looks like the following code ```php /** * Class TabularInputAction - * @package unclead\widgets\examples\actions + * @package yii\multipleinput\examples\actions */ class TabularInputAction extends Action { diff --git a/docs/usage.md b/docs/usage.md index c0b33b8..2c5707b 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -14,7 +14,7 @@ For example you want to have an ability of entering several emails of user on pr In this case you can use yii2-multiple-input widget like in the following code ```php -use unclead\widgets\MultipleInput; +use yii\multipleinput\MultipleInput; ... @@ -41,7 +41,7 @@ For example you keep some data in json format in attribute of model. Imagine tha On the edit page you want to be able to manage this schedule and you can you yii2-multiple-input widget like in the following code ```php -use unclead\widgets\MultipleInput; +use yii\multipleinput\MultipleInput; ... diff --git a/examples/actions/EmbeddedInputAction.php b/examples/actions/EmbeddedInputAction.php index caf5b07..3ade172 100644 --- a/examples/actions/EmbeddedInputAction.php +++ b/examples/actions/EmbeddedInputAction.php @@ -1,16 +1,16 @@ Date: Sat, 1 Oct 2016 22:33:41 +0300 Subject: [PATCH 013/247] Renamed limit to max --- CHANGELOG.md | 6 ++++++ README.md | 6 +++--- UPGRADE.md | 1 + docs/configuration.md | 2 +- docs/multiple_input_multiple.md | 2 +- docs/multiple_input_single.md | 4 ++-- docs/tips.md | 6 +++--- docs/usage.md | 4 ++-- examples/views/multiple-input.php | 4 ++-- src/MultipleInput.php | 8 ++++---- src/TabularInput.php | 8 ++++---- src/assets/src/js/jquery.multipleInput.js | 4 ++-- src/renderers/BaseRenderer.php | 22 +++++++++++----------- src/renderers/TableRenderer.php | 8 ++++---- 14 files changed, 46 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index abdb06f..95415bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ Yii2 multiple input change log ============================== +2.0 +=== + +- Renamed `limit` option to `max` +- Changed namespace from `unclead\widgets` to `yii\multipleinout` + 1.4.1 ===== diff --git a/README.md b/README.md index 8f7ef30..7bf2e50 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v1.4.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). @@ -41,10 +41,10 @@ use yii\multipleinput\MultipleInput; field($model, 'emails')->widget(MultipleInput::className(), [ - 'limit' => 6, + 'max' => 6, + 'min' => 2, // should be at least 2 rows 'allowEmptyList' => false, 'enableGuessTitle' => true, - 'min' => 2, // should be at least 2 rows 'addButtonPosition' => MultipleInput::POS_HEADER // show add button in the header ]) ->label(false); diff --git a/UPGRADE.md b/UPGRADE.md index f878c7f..f7670c6 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -11,6 +11,7 @@ for both A and B. Upgrade from 1.4 to 2.0 ----------------------- +- Rename option `limit` to `max` - Change namespace prefix `unclead\widgets\` to `yii\multipleinput\`. Upgrade from 1.3 to 1.4 diff --git a/docs/configuration.md b/docs/configuration.md index 1a08966..f98af7b 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -4,7 +4,7 @@ Widget support the following options that are additionally recognized over and a ##Base options -**limit** *integer*: rows limit. If not set will defaul to unlimited +**max** *integer*: maximux number of rows. If not set will default to unlimited **min** *integer*: minimum number of rows. Set to `0` if you need the empty list in case you don't have any data diff --git a/docs/multiple_input_multiple.md b/docs/multiple_input_multiple.md index 807fe21..3c4bcfe 100644 --- a/docs/multiple_input_multiple.md +++ b/docs/multiple_input_multiple.md @@ -53,7 +53,7 @@ use yii\helpers\Html; ]);?> field($model, 'schedule')->widget(MultipleInput::className(), [ - 'limit' => 4, + 'max' => 4, 'columns' => [ [ 'name' => 'user_id', diff --git a/docs/multiple_input_single.md b/docs/multiple_input_single.md index 5095122..1be6cb7 100644 --- a/docs/multiple_input_single.md +++ b/docs/multiple_input_single.md @@ -39,14 +39,14 @@ use yii\helpers\Html; ]);?> field($model, 'emails')->widget(MultipleInput::className(), [ - 'limit' => 4, + 'max' => 4, ]); ?> 'btn btn-success']);?> ``` -Options `limit` means that user able to input only 4 emails +Options `max` means that user able to input only 4 emails For validation emails you can use the following code diff --git a/docs/tips.md b/docs/tips.md index 6d2d51d..7b48563 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -16,7 +16,7 @@ how you can use those options: ```php echo $form->field($model, 'emails')->widget(MultipleInput::className(), [ - 'limit' => 5, + 'max' => 5, 'addButtonOptions' => [ 'class' => 'btn btn-success', 'label' => 'add' // also you can use html code @@ -36,7 +36,7 @@ In some cases you need to have the ability to delete all rows in the list. For t ```php echo $form->field($model, 'emails')->widget(MultipleInput::className(), [ - 'limit' => 5, + 'max' => 5, 'allowEmptyList' => true ]) ->label(false); @@ -53,7 +53,7 @@ In this case you can use `enableGuessTitle` option like in the example below: ```php echo $form->field($model, 'emails')->widget(MultipleInput::className(), [ - 'limit' => 5, + 'max' => 5, 'allowEmptyList' => true, 'enableGuessTitle' => true ]) diff --git a/docs/usage.md b/docs/usage.md index 22b2c26..6dca381 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -20,7 +20,7 @@ use yii\multipleinput\MultipleInput; field($model, 'emails')->widget(MultipleInput::className(), [ - 'limit' => 6, + 'max' => 6, 'allowEmptyList' => false, 'enableGuessTitle' => true, 'min' => 2, // should be at least 2 rows @@ -46,7 +46,7 @@ use yii\multipleinput\MultipleInput; ... field($model, 'schedule')->widget(MultipleInput::className(), [ - 'limit' => 4, + 'max' => 4, 'columns' => [ [ 'name' => 'user_id', diff --git a/examples/views/multiple-input.php b/examples/views/multiple-input.php index 12a921d..9a3974c 100644 --- a/examples/views/multiple-input.php +++ b/examples/views/multiple-input.php @@ -24,7 +24,7 @@

Single column

field($model, 'emails')->widget(MultipleInput::className(), [ - 'limit' => 6, + 'max' => 6, 'allowEmptyList' => false, 'columns' => [ [ @@ -47,7 +47,7 @@

Multiple columns

field($model, 'schedule')->widget(MultipleInput::className(), [ - 'limit' => 4, + 'max' => 4, 'allowEmptyList' => true, 'rowOptions' => function($model) { $options = []; diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 7b0ae8f..fd94d4b 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -39,9 +39,9 @@ class MultipleInput extends InputWidget public $columns = []; /** - * @var integer inputs limit + * @var integer maximum number of rows */ - public $limit; + public $max; /** * @var array client-side attribute options, e.g. enableAjaxValidation. You may use this property in case when @@ -179,12 +179,12 @@ private function createRenderer() $config = [ 'id' => $this->options['id'], 'columns' => $this->columns, - 'limit' => $this->limit, + 'min' => $this->min, + 'max' => $this->max, 'attributeOptions' => $this->attributeOptions, 'data' => $this->data, 'columnClass' => $this->columnClass !== null ? $this->columnClass : MultipleInputColumn::className(), 'allowEmptyList' => $this->allowEmptyList, - 'min' => $this->min, 'addButtonPosition' => $this->addButtonPosition, 'rowOptions' => $this->rowOptions, 'context' => $this, diff --git a/src/TabularInput.php b/src/TabularInput.php index 4435986..367d01a 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -32,9 +32,9 @@ class TabularInput extends Widget public $columns = []; /** - * @var integer inputs limit + * @var integer maximum number of rows */ - public $limit; + public $max; /** * @var int minimum number of rows @@ -138,12 +138,12 @@ private function createRenderer() $config = [ 'id' => $this->options['id'], 'columns' => $this->columns, - 'limit' => $this->limit, + 'min' => $this->min, + 'max' => $this->max, 'attributeOptions' => $this->attributeOptions, 'data' => $this->models, 'columnClass' => $this->columnClass !== null ? $this->columnClass : TabularColumn::className(), 'allowEmptyList' => $this->allowEmptyList, - 'min' => $this->min, 'rowOptions' => $this->rowOptions, 'addButtonPosition' => $this->addButtonPosition, 'context' => $this, diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 95a1927..6594013 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -54,7 +54,7 @@ // string that collect js templates of widgets which uses in the columns jsTemplates: [], // how many row has to renders - limit: 1, + max: 1, // minimum number of rows min: 1, attributeOptions: {}, @@ -177,7 +177,7 @@ template = settings.template, inputList = $wrapper.children('.multiple-input-list').first(); - if (settings.limit != null && getCurrentIndex($wrapper) >= settings.limit) { + if (settings.max != null && getCurrentIndex($wrapper) >= settings.max) { return; } diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 927a028..ef65f1d 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -44,9 +44,9 @@ abstract class BaseRenderer extends Object implements RendererInterface public $columns = []; /** - * @var int inputs limit + * @var int maximum number of rows */ - public $limit; + public $max; /** * @var int minimum number of rows. @@ -125,7 +125,7 @@ public function init() parent::init(); $this->prepareMinOption(); - $this->prepareLimit(); + $this->prepareMaxOption(); $this->prepareColumnClass(); $this->prepareButtons(); $this->prepareIndexPlaceholder(); @@ -164,19 +164,19 @@ private function prepareMinOption() } } - private function prepareLimit() + private function prepareMaxOption() { - if ($this->limit === null) { - $this->limit = PHP_INT_MAX; + if ($this->max === null) { + $this->max = PHP_INT_MAX; } - if ($this->limit < 1) { - $this->limit = 1; + if ($this->max < 1) { + $this->max = 1; } // Maximum number of rows cannot be less then minimum number. - if ($this->limit < $this->min) { - $this->limit = $this->min; + if ($this->max < $this->min) { + $this->max = $this->min; } } @@ -257,7 +257,7 @@ protected function registerAssets() 'id' => $this->id, 'template' => $template, 'jsTemplates' => $jsTemplates, - 'limit' => $this->limit, + 'max' => $this->max, 'min' => $this->min, 'attributeOptions' => $this->attributeOptions, 'indexPlaceholder' => $this->getIndexPlaceholder() diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index d553168..ea6456e 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -58,7 +58,7 @@ public function renderHeader() $cells[] = $this->renderHeaderCell($column); } - if ($this->limit === null || ($this->limit >= 1 && $this->limit !== $this->min)) { + if ($this->max === null || ($this->max >= 1 && $this->max !== $this->min)) { $button = $this->isAddButtonPositionHeader() ? $this->renderAddButton() : ''; $cells[] = Html::tag('th', $button, [ @@ -140,8 +140,8 @@ protected function renderBody() if ($this->data) { $cnt = count($this->data); - if ($this->min === $this->limit && $cnt < $this->limit) { - $cnt = $this->limit; + if ($this->min === $this->max && $cnt < $this->max) { + $cnt = $this->max; } $indices = array_keys($this->data); @@ -183,7 +183,7 @@ private function renderRowContent($index = null, $item = null) } } - if ($this->limit !== $this->min) { + if ($this->max !== $this->min) { $cells[] = $this->renderActionColumn($index); } From 07ca184ec8d88cfda7cb9faa90684d480ecbfdaf Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 1 Oct 2016 22:35:05 +0300 Subject: [PATCH 014/247] Fixed typo --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index f98af7b..3ebef6e 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -4,7 +4,7 @@ Widget support the following options that are additionally recognized over and a ##Base options -**max** *integer*: maximux number of rows. If not set will default to unlimited +**max** *integer*: maximum number of rows. If not set will default to unlimited **min** *integer*: minimum number of rows. Set to `0` if you need the empty list in case you don't have any data From 8f370af9ae390025a6300e1521b4b384055b83d6 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 9 Oct 2016 12:01:50 +0300 Subject: [PATCH 015/247] Adjustments for correct work with AR relations --- src/MultipleInput.php | 7 +++ src/MultipleInputColumn.php | 19 ++++++-- src/assets/src/js/jquery.multipleInput.js | 2 +- src/assets/src/js/jquery.multipleInput.min.js | 2 +- src/renderers/BaseRenderer.php | 46 +++++++++---------- 5 files changed, 48 insertions(+), 28 deletions(-) diff --git a/src/MultipleInput.php b/src/MultipleInput.php index fd94d4b..2c9ace3 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -108,6 +108,12 @@ class MultipleInput extends InputWidget */ public $rendererClass; + /** + * @var bool whether the widget is embedded or not. + * @internal this property is used for internal purposes. Do not use it in your code. + */ + public $isEmbedded; + /** * Initialization. * @@ -117,6 +123,7 @@ public function init() { $this->guessColumns(); $this->initData(); + parent::init(); } diff --git a/src/MultipleInputColumn.php b/src/MultipleInputColumn.php index 91e455d..bfb49b0 100644 --- a/src/MultipleInputColumn.php +++ b/src/MultipleInputColumn.php @@ -49,13 +49,20 @@ public function getElementName($index, $withPrefix = true) if (is_null($index)) { $index = '{' . $this->renderer->getIndexPlaceholder() . '}'; } - + $elementName = $this->isRendererHasOneColumn() ? '[' . $this->name . '][' . $index . ']' : '[' . $index . '][' . $this->name . ']'; - $prefix = $withPrefix ? $this->getInputNamePrefix() : ''; - + if (!$withPrefix) { + return $elementName; + } + + $prefix = $this->getInputNamePrefix(); + if ($this->context->isEmbedded && strpos($prefix, $this->context->name) === false) { + $prefix = $this->context->name; + } + return $prefix . $elementName; } @@ -147,6 +154,12 @@ protected function renderWidget($type, $name, $value, $options) $options['model'] = $model; $options['attribute'] = $attribute; + + // Remember current name and mark the widget as embedded to prevent + // generation of wrong prefix in case when column is associated with AR relation + // @see https://github.com/unclead/yii2-multiple-input/issues/92 + $options['name'] = $name; + $options['isEmbedded'] = true; } return parent::renderWidget($type, $name, $value, $options); diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 6594013..65b8a3f 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -138,7 +138,7 @@ // wait for initialization of ActiveForm a second // If after a second system could not detect ActiveForm it means // that widget is used without ActiveForm and we should just complete initialization of the widget - if (i > 10) { + if (form.length === 0 || i > 10) { $wrapper.data('multipleInput').currentIndex = getCurrentIndex($wrapper); isActiveFormEnabled = false; diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index c4c192f..2262106 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,template:null,jsTemplates:[],limit:1,min:1,attributeOptions:{},indexPlaceholder:"multiple_index"},n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1},l=!1,r={init:function(r){var p=t.extend(!0,{},i,r||{}),f=t("#"+p.id),c=f.closest("form"),s=this.selector.replace("#","");f.data("multipleInput",{settings:p,currentIndex:0,attributeDefaults:{}}),f.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),u(t(this))}),f.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),a(t(this))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof c.data("yiiActiveForm")){var e=c.yiiActiveForm("find",s),i=[];"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),c.yiiActiveForm("remove",s));var r=t.extend({},n,p.attributeOptions);t.each(r,function(t,e){"undefined"==typeof i[t]&&(i[t]=e)}),f.data("multipleInput").attributeDefaults=i,f.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),f.data("multipleInput").currentIndex=d(f),l=!0,clearInterval(h),f.trigger(m)}else v++;v>10&&(f.data("multipleInput").currentIndex=d(f),l=!1,clearInterval(h),f.trigger(m))},100)},add:function(e){a(t(this),e)},remove:function(e){var i=null;i=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),u(i)},clear:function(){t(".js-input-remove").each(function(){u(t(this))})}},a=function(i,n){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,p=u.template,c=r.children(".multiple-input-list").first();if(!(null!=u.limit&&d(r)>=u.limit)){if(p=p.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(p).hide().appendTo(c).fadeIn(300),n instanceof Object){var s=[];for(var v in n)n.hasOwnProperty(v)&&s.push(n[v]);n=s}var m;for(var h in u.jsTemplates)m=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(m);var I=0;t(p).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=f(e),a=t("#"+r);if(n){var u=n[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var p=a.find('option[value="'+u+'"]');p.length&&a.val(u)}}l&&o(e),I++}),r.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);r.trigger(g)}},u=function(i){var n=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=n.data("multipleInput"),u=a.settings;if(d(n)>u.min){var o=t.Event(e.beforeDeleteRow);if(n.trigger(o,[r]),o.result===!1)return;l&&r.find("input, select, textarea").each(function(){p(t(this))}),r.fadeOut(300,function(){t(this).remove(),o=t.Event(e.afterDeleteRow),n.trigger(o)})}},o=function(e){var i=f(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput");r.yiiActiveForm("add",t.extend({},a.attributeDefaults,{id:i,input:"#"+i,container:".field-"+i}))}}},p=function(){var e=f(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},f=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},d=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,template:null,jsTemplates:[],max:1,min:1,attributeOptions:{},indexPlaceholder:"multiple_index"},n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1},l=!1,r={init:function(r){var p=t.extend(!0,{},i,r||{}),f=t("#"+p.id),c=f.closest("form"),s=this.selector.replace("#","");f.data("multipleInput",{settings:p,currentIndex:0,attributeDefaults:{}}),f.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),u(t(this))}),f.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),a(t(this))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof c.data("yiiActiveForm")){var e=c.yiiActiveForm("find",s),i=[];"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),c.yiiActiveForm("remove",s));var r=t.extend({},n,p.attributeOptions);t.each(r,function(t,e){"undefined"==typeof i[t]&&(i[t]=e)}),f.data("multipleInput").attributeDefaults=i,f.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),f.data("multipleInput").currentIndex=d(f),l=!0,clearInterval(h),f.trigger(m)}else v++;(0===c.length||v>10)&&(f.data("multipleInput").currentIndex=d(f),l=!1,clearInterval(h),f.trigger(m))},100)},add:function(e){a(t(this),e)},remove:function(e){var i=null;i=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),u(i)},clear:function(){t(".js-input-remove").each(function(){u(t(this))})}},a=function(i,n){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,p=u.template,c=r.children(".multiple-input-list").first();if(!(null!=u.max&&d(r)>=u.max)){if(p=p.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(p).hide().appendTo(c).fadeIn(300),n instanceof Object){var s=[];for(var v in n)n.hasOwnProperty(v)&&s.push(n[v]);n=s}var m;for(var h in u.jsTemplates)m=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(m);var I=0;t(p).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=f(e),a=t("#"+r);if(n){var u=n[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var p=a.find('option[value="'+u+'"]');p.length&&a.val(u)}}l&&o(e),I++}),r.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);r.trigger(x)}},u=function(i){var n=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=n.data("multipleInput"),u=a.settings;if(d(n)>u.min){var o=t.Event(e.beforeDeleteRow);if(n.trigger(o,[r]),o.result===!1)return;l&&r.find("input, select, textarea").each(function(){p(t(this))}),r.fadeOut(300,function(){t(this).remove(),o=t.Event(e.afterDeleteRow),n.trigger(o)})}},o=function(e){var i=f(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput");r.yiiActiveForm("add",t.extend({},a.attributeDefaults,{id:i,input:"#"+i,container:".field-"+i}))}}},p=function(){var e=f(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},f=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},d=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index ef65f1d..b650bdb 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -111,7 +111,7 @@ abstract class BaseRenderer extends Object implements RendererInterface * @var string */ private $indexPlaceholder; - + /** * @inheritdoc */ @@ -248,10 +248,29 @@ protected function registerAssets() { $view = $this->context->getView(); MultipleInputAsset::register($view); - - $jsBefore = $this->collectJsTemplates(); + + $view = $this->context->getView(); + + $jsBefore= []; + if (is_array($view->js) && array_key_exists(View::POS_READY, $view->js)) { + foreach ($view->js[View::POS_READY] as $key => $js) { + $jsBefore[$key] = $js; + } + } + $template = $this->prepareTemplate(); - $jsTemplates = $this->collectJsTemplates($jsBefore); + + $jsTemplates = []; + if (is_array($view->js) && array_key_exists(View::POS_READY, $view->js)) { + foreach ($view->js[View::POS_READY] as $key => $js) { + if (array_key_exists($key, $jsBefore)) { + continue; + } + + $jsTemplates[$key] = $js; + unset($view->js[View::POS_READY][$key]); + } + } $options = Json::encode([ 'id' => $this->id, @@ -272,25 +291,6 @@ protected function registerAssets() */ abstract protected function prepareTemplate(); - - protected function collectJsTemplates($except = []) - { - $view = $this->context->getView(); - $output = []; - if (is_array($view->js) && array_key_exists(View::POS_READY, $view->js)) { - foreach ($view->js[View::POS_READY] as $key => $js) { - if (array_key_exists($key, $except)) { - continue; - } - if (preg_match('/^[^{]+{' . $this->getIndexPlaceholder() . '}.*$/m', $js) === 1) { - $output[$key] = $js; - unset($view->js[View::POS_READY][$key]); - } - } - } - return $output; - } - /** * @return mixed */ From 9d4967cc39697af393f662206d8a90cbda6929d2 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 9 Oct 2016 12:03:47 +0300 Subject: [PATCH 016/247] Update CHANGELOG --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95415bf..3c8f103 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Yii2 multiple input change log - Renamed `limit` option to `max` - Changed namespace from `unclead\widgets` to `yii\multipleinout` +- #92: Adjustments for correct work with AR relations 1.4.1 ===== From 3d7e746e5a9766ecc0b14acf04a1a3584b794ed7 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 9 Oct 2016 19:32:56 +0300 Subject: [PATCH 017/247] Fixed namespace --- composer.json | 3 ++- tests/unit/MultipleInputTest.php | 10 +++++----- tests/unit/TestCase.php | 2 +- tests/unit/data/TestModel.php | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/composer.json b/composer.json index 2d6018b..f5c8852 100644 --- a/composer.json +++ b/composer.json @@ -31,7 +31,8 @@ "autoload": { "psr-4": { "yii\\multipleinput\\examples\\": "examples/", - "yii\\multipleinput\\": "src/" + "yii\\multipleinput\\": "src/", + "yii\\multipleinput\\tests\\": "tests/" } } } diff --git a/tests/unit/MultipleInputTest.php b/tests/unit/MultipleInputTest.php index 8fb5cd3..1e39633 100644 --- a/tests/unit/MultipleInputTest.php +++ b/tests/unit/MultipleInputTest.php @@ -1,14 +1,14 @@ Date: Sun, 9 Oct 2016 21:01:57 +0300 Subject: [PATCH 018/247] Pass the model to embedded widget when prepare a template --- src/MultipleInputColumn.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MultipleInputColumn.php b/src/MultipleInputColumn.php index bfb49b0..918dd50 100644 --- a/src/MultipleInputColumn.php +++ b/src/MultipleInputColumn.php @@ -138,7 +138,7 @@ protected function renderWidget($type, $name, $value, $options) // Extend options in case of rendering embedded MultipleInput // We have to pass to the widget an original model and an attribute to be able get a first error from model // for embedded widget. - if ($type === MultipleInput::className() && strpos($name, $this->renderer->getIndexPlaceholder()) === false) { + if ($type === MultipleInput::className()) { $model = $this->context->model; // in case of embedding level 2 and more From 6d612c8eeacb58bc525bf01f06dc789d93766a2b Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 9 Oct 2016 22:03:58 +0300 Subject: [PATCH 019/247] added method to set value of an particular option --- CHANGELOG.md | 1 + docs/javascript.md | 51 ++++++++++++++++--- src/assets/src/js/jquery.multipleInput.js | 17 +++++++ src/assets/src/js/jquery.multipleInput.min.js | 2 +- 4 files changed, 64 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c8f103..64aa441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Yii2 multiple input change log - Renamed `limit` option to `max` - Changed namespace from `unclead\widgets` to `yii\multipleinout` - #92: Adjustments for correct work with AR relations +- Enh #104: Added method to set value of an particular option 1.4.1 ===== diff --git a/docs/javascript.md b/docs/javascript.md index 203872e..fed9b48 100644 --- a/docs/javascript.md +++ b/docs/javascript.md @@ -27,15 +27,54 @@ jQuery('#multiple-input').on('afterInit', function(){ #JavaScript operations -Dynamically operations in widget: - - `add`: adding new row, **param** *object*: object with values for inputs, can be filled with '}); +``` + +####remove + +Remove row with specified index. + +Inout arguments: +- *integer* - row number for removing, if not specified then removes last row. + +Example: + +```js $('#multiple-input').multipleInput('remove', 2); +``` + +####clear + +Remove all rows + +```js $('#multiple-input').multipleInput('clear'); -``` \ No newline at end of file +``` + +####option + +Get or set a particular option + +Input arguments: +- *string* - a name of an option +- *mixed* - a value of an option (optional). If specified will be used as a new value of an option; + +Example: + +```js +$('#multiple-input').multipleInput('option', 'max'); +$('#multiple-input').multipleInput('option', 'max', 10); + +``` + + diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 65b8a3f..113eda9 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -167,6 +167,23 @@ $('.js-input-remove').each(function () { removeInput($(this)); }); + }, + + option: function(name, value) { + value = value || null; + + var data = $(this).data('multipleInput'), + settings = data.settings; + if (value === null) { + if (!settings.hasOwnProperty(name)) { + throw new Error('Option "' + name + '" does not exist'); + } + return settings[name]; + } else if (settings.hasOwnProperty(name)) { + settings[name] = value; + data.settings = settings; + $(this).data('multipleInput', data); + } } }; diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 2262106..0828afb 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,template:null,jsTemplates:[],max:1,min:1,attributeOptions:{},indexPlaceholder:"multiple_index"},n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1},l=!1,r={init:function(r){var p=t.extend(!0,{},i,r||{}),f=t("#"+p.id),c=f.closest("form"),s=this.selector.replace("#","");f.data("multipleInput",{settings:p,currentIndex:0,attributeDefaults:{}}),f.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),u(t(this))}),f.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),a(t(this))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof c.data("yiiActiveForm")){var e=c.yiiActiveForm("find",s),i=[];"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),c.yiiActiveForm("remove",s));var r=t.extend({},n,p.attributeOptions);t.each(r,function(t,e){"undefined"==typeof i[t]&&(i[t]=e)}),f.data("multipleInput").attributeDefaults=i,f.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),f.data("multipleInput").currentIndex=d(f),l=!0,clearInterval(h),f.trigger(m)}else v++;(0===c.length||v>10)&&(f.data("multipleInput").currentIndex=d(f),l=!1,clearInterval(h),f.trigger(m))},100)},add:function(e){a(t(this),e)},remove:function(e){var i=null;i=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),u(i)},clear:function(){t(".js-input-remove").each(function(){u(t(this))})}},a=function(i,n){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,p=u.template,c=r.children(".multiple-input-list").first();if(!(null!=u.max&&d(r)>=u.max)){if(p=p.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(p).hide().appendTo(c).fadeIn(300),n instanceof Object){var s=[];for(var v in n)n.hasOwnProperty(v)&&s.push(n[v]);n=s}var m;for(var h in u.jsTemplates)m=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(m);var I=0;t(p).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=f(e),a=t("#"+r);if(n){var u=n[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var p=a.find('option[value="'+u+'"]');p.length&&a.val(u)}}l&&o(e),I++}),r.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);r.trigger(x)}},u=function(i){var n=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=n.data("multipleInput"),u=a.settings;if(d(n)>u.min){var o=t.Event(e.beforeDeleteRow);if(n.trigger(o,[r]),o.result===!1)return;l&&r.find("input, select, textarea").each(function(){p(t(this))}),r.fadeOut(300,function(){t(this).remove(),o=t.Event(e.afterDeleteRow),n.trigger(o)})}},o=function(e){var i=f(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput");r.yiiActiveForm("add",t.extend({},a.attributeDefaults,{id:i,input:"#"+i,container:".field-"+i}))}}},p=function(){var e=f(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},f=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},d=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,template:null,jsTemplates:[],max:1,min:1,attributeOptions:{},indexPlaceholder:"multiple_index"},n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1},l=!1,r={init:function(r){var p=t.extend(!0,{},i,r||{}),s=t("#"+p.id),d=s.closest("form"),c=this.selector.replace("#","");s.data("multipleInput",{settings:p,currentIndex:0,attributeDefaults:{}}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),u(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),a(t(this))});var m=0,v=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i=[];"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c));var r=t.extend({},n,p.attributeOptions);t.each(r,function(t,e){"undefined"==typeof i[t]&&(i[t]=e)}),s.data("multipleInput").attributeDefaults=i,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=f(s),l=!0,clearInterval(h),s.trigger(v)}else m++;(0===d.length||m>10)&&(s.data("multipleInput").currentIndex=f(s),l=!1,clearInterval(h),s.trigger(v))},100)},add:function(e){a(t(this),e)},remove:function(e){var i=null;i=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),u(i)},clear:function(){t(".js-input-remove").each(function(){u(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},a=function(i,n){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,p=u.template,d=r.children(".multiple-input-list").first();if(!(null!=u.max&&f(r)>=u.max)){if(p=p.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(p).hide().appendTo(d).fadeIn(300),n instanceof Object){var c=[];for(var m in n)n.hasOwnProperty(m)&&c.push(n[m]);n=c}var v;for(var h in u.jsTemplates)v=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(v);var I=0;t(p).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=s(e),a=t("#"+r);if(n){var u=n[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var p=a.find('option[value="'+u+'"]');p.length&&a.val(u)}}l&&o(e),I++}),r.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);r.trigger(g)}},u=function(i){var n=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=n.data("multipleInput"),u=a.settings;if(f(n)>u.min){var o=t.Event(e.beforeDeleteRow);if(n.trigger(o,[r]),o.result===!1)return;l&&r.find("input, select, textarea").each(function(){p(t(this))}),r.fadeOut(300,function(){t(this).remove(),o=t.Event(e.afterDeleteRow),n.trigger(o)})}},o=function(e){var i=s(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput");r.yiiActiveForm("add",t.extend({},a.attributeDefaults,{id:i,input:"#"+i,container:".field-"+i}))}}},p=function(){var e=s(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},f=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file From 03c1d8516b3121cf0129eefaa94899941064d8e9 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 9 Oct 2016 22:05:54 +0300 Subject: [PATCH 020/247] Prepare to release 2.0.0 --- composer.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index f5c8852..9861a34 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "1.4.1", + "version": "2.0.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 214c40f..2dab28c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "1.4.1", + "version": "2.0.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From f75a68a4034f5bc28ac1a945cd6abf55e0314885 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 9 Oct 2016 22:12:40 +0300 Subject: [PATCH 021/247] Update README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7bf2e50..bb75b71 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,13 @@ The preferred way to install this extension is through [composer](http://getcomp Either run ``` -php composer.phar require unclead/yii2-multiple-input "~1.0" +php composer.phar require unclead/yii2-multiple-input "~2.0" ``` or add ``` -"unclead/yii2-multiple-input": "~1.0" +"unclead/yii2-multiple-input": "~2.0" ``` to the require section of your `composer.json` file. From 8bfc504d087489c9028036f919e3fcde7a31e952 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Tue, 11 Oct 2016 09:32:15 +0300 Subject: [PATCH 022/247] Use unclead as vendor name because yii is reserved --- CHANGELOG.md | 11 ++++++++--- README.md | 4 ++-- UPGRADE.md | 5 +++++ composer.json | 8 ++++---- docs/configuration.md | 4 ++-- docs/multiple_input_multiple.md | 4 ++-- docs/multiple_input_single.md | 4 ++-- docs/tabular_input.md | 12 ++++++------ docs/usage.md | 4 ++-- examples/actions/EmbeddedInputAction.php | 6 +++--- examples/actions/MultipleInputAction.php | 6 +++--- examples/actions/TabularInputAction.php | 6 +++--- examples/models/ExampleModel.php | 4 ++-- examples/models/Item.php | 4 ++-- examples/views/embedded-input.php | 6 +++--- examples/views/multiple-input.php | 6 +++--- examples/views/tabular-input.php | 6 +++--- package.json | 2 +- src/MultipleInput.php | 10 +++++----- src/MultipleInputColumn.php | 6 +++--- src/TabularColumn.php | 6 +++--- src/TabularInput.php | 12 ++++++------ src/assets/MultipleInputAsset.php | 4 ++-- src/components/BaseColumn.php | 6 +++--- src/renderers/BaseRenderer.php | 12 ++++++------ src/renderers/RendererInterface.php | 4 ++-- src/renderers/TableRenderer.php | 6 +++--- tests/unit/MultipleInputTest.php | 10 +++++----- tests/unit/TestCase.php | 2 +- tests/unit/data/TestModel.php | 4 ++-- 30 files changed, 97 insertions(+), 87 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 64aa441..c63e7d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,16 @@ Yii2 multiple input change log ============================== -2.0 -=== +2.0.1 +===== + +- Bug #104: Change vendor name in namespace from yii to unclead + +2.0.0 +===== - Renamed `limit` option to `max` -- Changed namespace from `unclead\widgets` to `yii\multipleinout` +- Changed namespace from `unclead\widgets` to `yii\multipleinput` - #92: Adjustments for correct work with AR relations - Enh #104: Added method to set value of an particular option diff --git a/README.md b/README.md index bb75b71..63dd908 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v2.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.0.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). @@ -35,7 +35,7 @@ For example you want to have an ability of entering several emails of user on pr In this case you can use yii2-multiple-input widget like in the following code ```php -use yii\multipleinput\MultipleInput; +use unclead\multipleinput\MultipleInput; ... diff --git a/UPGRADE.md b/UPGRADE.md index f7670c6..7c80e6d 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -8,6 +8,11 @@ if you want to upgrade from version A to version C and there is version B between A and C, you need to following the instructions for both A and B. +Upgrade from 2.0.0 tp 2.0.1 +--------------------------- + +- Change namespace prefix `yii\multipleinout\` to `unclead\multipleinput\`. + Upgrade from 1.4 to 2.0 ----------------------- diff --git a/composer.json b/composer.json index 9861a34..d1fc00f 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.0.0", + "version": "2.0.1", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", @@ -30,9 +30,9 @@ }, "autoload": { "psr-4": { - "yii\\multipleinput\\examples\\": "examples/", - "yii\\multipleinput\\": "src/", - "yii\\multipleinput\\tests\\": "tests/" + "unclead\\multipleinput\\examples\\": "examples/", + "unclead\\multipleinput\\": "src/", + "unclead\\multipleinput\\tests\\": "tests/" } } } diff --git a/docs/configuration.md b/docs/configuration.md index 3ebef6e..4cd1e46 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -25,10 +25,10 @@ This can be `MultipleInput::POS_HEADER`, `MultipleInput::POS_ROW` or `MultipleIn **allowEmptyList** *boolean*: whether to allow the empty list **columnClass** *string*: the name of column class. You can specify your own class to extend base functionality. -Defaults to `yii\multipleinput\MultipleInputColumn` for `MultipleInput` and `yii\multipleinput\TabularColumn` for `TabularInput`. +Defaults to `unclead\multipleinput\MultipleInputColumn` for `MultipleInput` and `unclead\multipleinput\TabularColumn` for `TabularInput`. **rendererClass** *string*: the name of renderer class. You can specify your own class to extend base functionality. -Defaults to `yii\multipleinput\renderers\TableRenderer`. +Defaults to `unclead\multipleinput\renderers\TableRenderer`. **columns** *array*: the row columns configuration where you can set the properties which is described below diff --git a/docs/multiple_input_multiple.md b/docs/multiple_input_multiple.md index 3c4bcfe..a3225a7 100644 --- a/docs/multiple_input_multiple.md +++ b/docs/multiple_input_multiple.md @@ -36,8 +36,8 @@ Then we have to use `MultipleInput` widget for rendering form field in the view ```php use yii\bootstrap\ActiveForm; -use yii\multipleinput\MultipleInput; -use yii\multipleinput\examples\models\ExampleModel; +use unclead\multipleinput\MultipleInput; +use unclead\multipleinput\examples\models\ExampleModel; use yii\helpers\Html; /* @var $this \yii\base\View */ diff --git a/docs/multiple_input_single.md b/docs/multiple_input_single.md index 1be6cb7..0c86496 100644 --- a/docs/multiple_input_single.md +++ b/docs/multiple_input_single.md @@ -22,8 +22,8 @@ Then we have to use `MultipleInput` widget for rendering form field in the view ```php use yii\bootstrap\ActiveForm; -use yii\multipleinput\MultipleInput; -use yii\multipleinput\examples\models\ExampleModel; +use unclead\multipleinput\MultipleInput; +use unclead\multipleinput\examples\models\ExampleModel; use yii\helpers\Html; /* @var $this \yii\base\View */ diff --git a/docs/tabular_input.md b/docs/tabular_input.md index 9ca5237..10ea779 100644 --- a/docs/tabular_input.md +++ b/docs/tabular_input.md @@ -7,7 +7,7 @@ In this case you can use `yii2-multiple-input` widget for supporting tabular inp Our test model can looks like as the following snippet ```php -namespace yii\multipleinput\examples\models; +namespace unclead\multipleinput\examples\models; use Yii; use yii\base\Model; @@ -16,7 +16,7 @@ use vova07\fileapi\behaviors\UploadBehavior; /** * Class Item - * @package yii\multipleinput\examples\models + * @package unclead\multipleinput\examples\models */ class Item extends Model { @@ -57,9 +57,9 @@ Then we have to use `TabularInput` widget for rendering form field in the view f 'title', 'title' => 'Title', - 'type' => \yii\multipleinput\MultipleInputColumn::TYPE_TEXT_INPUT, + 'type' => \unclead\multipleinput\MultipleInputColumn::TYPE_TEXT_INPUT, ], [ 'name' => 'description', @@ -135,7 +135,7 @@ Your action can looks like the following code ```php /** * Class TabularInputAction - * @package yii\multipleinput\examples\actions + * @package unclead\multipleinput\examples\actions */ class TabularInputAction extends Action { diff --git a/docs/usage.md b/docs/usage.md index 6dca381..1d5d887 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -14,7 +14,7 @@ For example you want to have an ability of entering several emails of user on pr In this case you can use yii2-multiple-input widget like in the following code ```php -use yii\multipleinput\MultipleInput; +use unclead\multipleinput\MultipleInput; ... @@ -41,7 +41,7 @@ For example you keep some data in json format in attribute of model. Imagine tha On the edit page you want to be able to manage this schedule and you can you yii2-multiple-input widget like in the following code ```php -use yii\multipleinput\MultipleInput; +use unclead\multipleinput\MultipleInput; ... diff --git a/examples/actions/EmbeddedInputAction.php b/examples/actions/EmbeddedInputAction.php index 3ade172..58bfb3b 100644 --- a/examples/actions/EmbeddedInputAction.php +++ b/examples/actions/EmbeddedInputAction.php @@ -1,16 +1,16 @@ Date: Tue, 11 Oct 2016 09:33:52 +0300 Subject: [PATCH 023/247] Fixed typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c63e7d7..387d91f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Yii2 multiple input change log 2.0.1 ===== -- Bug #104: Change vendor name in namespace from yii to unclead +- Bug #105: Change vendor name in namespace from yii to unclead 2.0.0 ===== From 3c8d88daf0a3818a2b5dc4100b4eb91b9ed543a9 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Tue, 11 Oct 2016 09:35:04 +0300 Subject: [PATCH 024/247] Fixed typo --- UPGRADE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADE.md b/UPGRADE.md index 7c80e6d..6fdc1bf 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -11,7 +11,7 @@ for both A and B. Upgrade from 2.0.0 tp 2.0.1 --------------------------- -- Change namespace prefix `yii\multipleinout\` to `unclead\multipleinput\`. +- Change namespace prefix `yii\multipleinput\` to `unclead\multipleinput\`. Upgrade from 1.4 to 2.0 ----------------------- From 156e38f93b94b77722e2f2a0b1c53f17df1232e5 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Tue, 11 Oct 2016 18:47:44 +0300 Subject: [PATCH 025/247] Support of client validation --- CHANGELOG.md | 7 ++- README.md | 2 +- composer.json | 2 +- docs/tips.md | 47 +++++++++++++++++++ examples/models/ExampleModel.php | 1 + examples/views/tabular-input.php | 5 ++ package.json | 2 +- src/MultipleInput.php | 13 +++++ src/TabularInput.php | 12 +++++ src/assets/src/js/jquery.multipleInput.js | 44 +++++++++-------- src/assets/src/js/jquery.multipleInput.min.js | 2 +- src/components/BaseColumn.php | 9 ++++ src/renderers/BaseRenderer.php | 47 ++++++++++++++++++- 13 files changed, 164 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 387d91f..c90287e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,15 @@ Yii2 multiple input change log ============================== +2.1.0 +===== + +- Enh #37: Support of client validation + 2.0.1 ===== -- Bug #105: Change vendor name in namespace from yii to unclead +- Bug #105: Change vendor name in namespace from yii to unclead to respect Yii recommendations 2.0.0 ===== diff --git a/README.md b/README.md index 63dd908..e3873b7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v2.0.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.1.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index d1fc00f..c635253 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.0.1", + "version": "2.1.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/docs/tips.md b/docs/tips.md index 7b48563..88f4f77 100644 --- a/docs/tips.md +++ b/docs/tips.md @@ -7,6 +7,7 @@ - [Use of a widget's placeholder](#using-placeholder) - [Custom index of the row](#custom-index) - [Embedded MultipleInput widget](#embedded) + - [Client validation](#client-validation) ##How to customize buttons @@ -182,3 +183,49 @@ echo MultipleInput::widget([ ``` But in this case you have to pass `attributeOptions` to the widget otherwise you will not be able to use ajax or client side validation of data. + +### Client validation + +Apart of ajax validation you can use client validation but in this case you MUST set property `form`. +Also ensure that you set `enableClientValidation` to `true` value in property `attributeOptions`. If you want to use client validation +for particular column you can use `attributeOptions` property for this column. An example of using client validation is listed below: + +```php + $models, + 'form' => $form, + 'attributeOptions' => [ + 'enableAjaxValidation' => true, + 'enableClientValidation' => false, + 'validateOnChange' => false, + 'validateOnSubmit' => true, + 'validateOnBlur' => false, + ], + 'columns' => [ + [ + 'name' => 'id', + 'type' => TabularColumn::TYPE_HIDDEN_INPUT + ], + [ + 'name' => 'title', + 'title' => 'Title', + 'type' => TabularColumn::TYPE_TEXT_INPUT, + 'attributeOptions' => [ + 'enableClientValidation' => true, + 'validateOnChange' => true, + ], + 'enableError' => true + ], + [ + 'name' => 'description', + 'title' => 'Description', + ], + ], +]) ?> + +``` + +In the example above we use client validation for column `title` and ajax validation for column `description`. +As you can noticed we also enabled `validateOnChange` for column `title` thus you can use all client-side options from the `ActiveField` class. + + diff --git a/examples/models/ExampleModel.php b/examples/models/ExampleModel.php index 9d64b87..2fbc1ec 100644 --- a/examples/models/ExampleModel.php +++ b/examples/models/ExampleModel.php @@ -93,6 +93,7 @@ public function rules() { return [ ['title', 'required'], + ['title', 'string', 'min' => 5], ['emails', 'validateEmails'], ['phones', 'validatePhones'], ['schedule', 'validateSchedule'], diff --git a/examples/views/tabular-input.php b/examples/views/tabular-input.php index 66cbfa2..a50b8de 100644 --- a/examples/views/tabular-input.php +++ b/examples/views/tabular-input.php @@ -27,6 +27,7 @@ 'validateOnSubmit' => true, 'validateOnBlur' => false, ], + 'form' => $form, 'columns' => [ [ 'name' => 'id', @@ -36,6 +37,10 @@ 'name' => 'title', 'title' => 'Title', 'type' => TabularColumn::TYPE_TEXT_INPUT, + 'attributeOptions' => [ + 'enableClientValidation' => true, + 'validateOnChange' => true, + ], 'defaultValue' => 'Test', 'enableError' => true ], diff --git a/package.json b/package.json index 6fb7230..0185d10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.0.1", + "version": "2.1.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 04a89c1..d79afd9 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -9,8 +9,10 @@ namespace unclead\multipleinput; use Yii; +use yii\base\InvalidConfigException; use yii\base\Model; use yii\helpers\ArrayHelper; +use yii\widgets\ActiveForm; use yii\widgets\InputWidget; use yii\db\ActiveRecordInterface; use unclead\multipleinput\renderers\TableRenderer; @@ -114,6 +116,12 @@ class MultipleInput extends InputWidget */ public $isEmbedded; + /** + * @var ActiveForm an instance of ActiveForm which you have to pass in case of using client validation + * @since 2.1 + */ + public $form; + /** * Initialization. * @@ -121,6 +129,10 @@ class MultipleInput extends InputWidget */ public function init() { + if ($this->form !== null && !$this->form instanceof ActiveForm) { + throw new InvalidConfigException('Property "form" must be an instance of yii\widgets\ActiveForm'); + } + $this->guessColumns(); $this->initData(); @@ -196,6 +208,7 @@ private function createRenderer() 'addButtonPosition' => $this->addButtonPosition, 'rowOptions' => $this->rowOptions, 'context' => $this, + 'form' => $this->form ]; if ($this->removeButtonOptions !== null) { diff --git a/src/TabularInput.php b/src/TabularInput.php index 4c85105..052cd5a 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -13,6 +13,7 @@ use yii\base\Model; use yii\db\ActiveRecordInterface; use yii\bootstrap\Widget; +use yii\widgets\ActiveForm; use unclead\multipleinput\renderers\TableRenderer; use unclead\multipleinput\renderers\RendererInterface; @@ -102,6 +103,12 @@ class TabularInput extends Widget */ public $rendererClass; + /** + * @var ActiveForm an instance of ActiveForm which you have to pass in case of using client validation + * @since 2.1 + */ + public $form; + /** * Initialization. * @@ -113,6 +120,10 @@ public function init() throw new InvalidConfigException('You must specify "models"'); } + if ($this->form !== null && !$this->form instanceof ActiveForm) { + throw new InvalidConfigException('Property "form" must be an instance of yii\widgets\ActiveForm'); + } + foreach ($this->models as $model) { if (!$model instanceof Model) { throw new InvalidConfigException('Model has to be an instance of yii\base\Model'); @@ -147,6 +158,7 @@ private function createRenderer() 'rowOptions' => $this->rowOptions, 'addButtonPosition' => $this->addButtonPosition, 'context' => $this, + 'form' => $this->form ]; if ($this->removeButtonOptions !== null) { diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 113eda9..0335ad5 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -57,15 +57,8 @@ max: 1, // minimum number of rows min: 1, - attributeOptions: {}, - indexPlaceholder: 'multiple_index', - }; - - var defaultAttributeOptions = { - enableAjaxValidation: false, - validateOnBlur: false, - validateOnChange: false, - validateOnType: false + attributes: {}, + indexPlaceholder: 'multiple_index' }; var isActiveFormEnabled = false; @@ -79,8 +72,7 @@ $wrapper.data('multipleInput', { settings: settings, - currentIndex: 0, - attributeDefaults: {} + currentIndex: 0 }); @@ -100,27 +92,32 @@ var intervalID = setInterval(function () { if (typeof form.data('yiiActiveForm') === 'object') { var attribute = form.yiiActiveForm('find', id), - attributeDefaults = []; - + defaultAttributeOptions = { + enableAjaxValidation: false, + validateOnBlur: false, + validateOnChange: false, + validateOnType: false, + validationDelay: 500 + }; + + // fetch default attribute options from active from attribute if (typeof attribute === 'object') { $.each(attribute, function (key, value) { if (['id', 'input', 'container'].indexOf(key) == -1) { - attributeDefaults[key] = value; + defaultAttributeOptions[key] = value; } }); form.yiiActiveForm('remove', id); } - var attributeOptions = $.extend({}, defaultAttributeOptions, settings.attributeOptions); - - $.each(attributeOptions, function (key, value) { - if (typeof attributeDefaults[key] === 'undefined') { - attributeDefaults[key] = value; - } + // append default options to option from settings + $.each(settings.attributes, function (attribute, attributeOptions) { + attributeOptions = $.extend({}, defaultAttributeOptions, attributeOptions); + settings.attributes[attribute] = attributeOptions; }); - $wrapper.data('multipleInput').attributeDefaults = attributeDefaults; + $wrapper.data('multipleInput').settings = settings; $wrapper.find('.multiple-input-list').find('input, select, textarea').each(function () { addAttribute($(this)); @@ -318,9 +315,10 @@ return; } - var data = wrapper.data('multipleInput'); - form.yiiActiveForm('add', $.extend({}, data.attributeDefaults, { + var bareID = id.replace(/-\d/, '').replace(/-\d-/, ''); + + form.yiiActiveForm('add', $.extend({}, data.settings.attributes[bareID], { 'id': id, 'input': '#' + id, 'container': '.field-' + id diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 0828afb..5cace5d 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,template:null,jsTemplates:[],max:1,min:1,attributeOptions:{},indexPlaceholder:"multiple_index"},n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1},l=!1,r={init:function(r){var p=t.extend(!0,{},i,r||{}),s=t("#"+p.id),d=s.closest("form"),c=this.selector.replace("#","");s.data("multipleInput",{settings:p,currentIndex:0,attributeDefaults:{}}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),u(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),a(t(this))});var m=0,v=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i=[];"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c));var r=t.extend({},n,p.attributeOptions);t.each(r,function(t,e){"undefined"==typeof i[t]&&(i[t]=e)}),s.data("multipleInput").attributeDefaults=i,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=f(s),l=!0,clearInterval(h),s.trigger(v)}else m++;(0===d.length||m>10)&&(s.data("multipleInput").currentIndex=f(s),l=!1,clearInterval(h),s.trigger(v))},100)},add:function(e){a(t(this),e)},remove:function(e){var i=null;i=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),u(i)},clear:function(){t(".js-input-remove").each(function(){u(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},a=function(i,n){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,p=u.template,d=r.children(".multiple-input-list").first();if(!(null!=u.max&&f(r)>=u.max)){if(p=p.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(p).hide().appendTo(d).fadeIn(300),n instanceof Object){var c=[];for(var m in n)n.hasOwnProperty(m)&&c.push(n[m]);n=c}var v;for(var h in u.jsTemplates)v=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(v);var I=0;t(p).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=s(e),a=t("#"+r);if(n){var u=n[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var p=a.find('option[value="'+u+'"]');p.length&&a.val(u)}}l&&o(e),I++}),r.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);r.trigger(g)}},u=function(i){var n=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=n.data("multipleInput"),u=a.settings;if(f(n)>u.min){var o=t.Event(e.beforeDeleteRow);if(n.trigger(o,[r]),o.result===!1)return;l&&r.find("input, select, textarea").each(function(){p(t(this))}),r.fadeOut(300,function(){t(this).remove(),o=t.Event(e.afterDeleteRow),n.trigger(o)})}},o=function(e){var i=s(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput");r.yiiActiveForm("add",t.extend({},a.attributeDefaults,{id:i,input:"#"+i,container:".field-"+i}))}}},p=function(){var e=s(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},f=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return l[e]?l[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):l.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,template:null,jsTemplates:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,l={init:function(l){var u=t.extend(!0,{},i,l||{}),p=t("#"+u.id),d=p.closest("form"),c=this.selector.replace("#","");p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),r(t(this))});var f=0,m=t.Event(e.afterInit),v=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(v),p.trigger(m)}else f++;(0===d.length||f>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(v),p.trigger(m))},100)},add:function(e){r(t(this),e)},remove:function(e){var i=null;i=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},r=function(i,l){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,d=u.template,c=r.children(".multiple-input-list").first();if(!(null!=u.max&&s(r)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),l instanceof Object){var f=[];for(var m in l)l.hasOwnProperty(m)&&f.push(l[m]);l=f}var v;for(var h in u.jsTemplates)v=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(v);var I=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=p(e),a=t("#"+r);if(l){var u=l[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),I++}),r.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);r.trigger(g)}},a=function(i){var l=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=l.data("multipleInput"),o=a.settings;if(s(l)>o.min){var p=t.Event(e.beforeDeleteRow);if(l.trigger(p,[r]),p.result===!1)return;n&&r.find("input, select, textarea").each(function(){u(t(this))}),r.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),l.trigger(p)})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),o=i.replace(/-\d/,"").replace(/-\d-/,"");r.yiiActiveForm("add",t.extend({},a.settings.attributes[o],{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index baca4bb..f4a1204 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -110,6 +110,15 @@ abstract class BaseColumn extends Object * @var mixed the context of using a column. It is an instance of widget(MultipleInput or TabularInput). */ public $context; + + /** + * @var array client-side options of the attribute, e.g. enableAjaxValidation. + * You can use this property for custom configuration of the column (attribute). + * By default, the column will use options which are defined on widget level. + * + * @since 2.1 + */ + public $attributeOptions = []; /** * @var Model|ActiveRecordInterface|array diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index f86cbc6..670ff5b 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -9,6 +9,7 @@ namespace unclead\multipleinput\renderers; use Yii; +use yii\helpers\ArrayHelper; use yii\helpers\Html; use yii\helpers\Json; use yii\base\InvalidConfigException; @@ -17,6 +18,7 @@ use yii\base\Object; use yii\db\ActiveRecordInterface; use yii\web\View; +use yii\widgets\ActiveForm; use unclead\multipleinput\MultipleInput; use unclead\multipleinput\TabularInput; use unclead\multipleinput\assets\MultipleInputAsset; @@ -112,6 +114,11 @@ abstract class BaseRenderer extends Object implements RendererInterface */ private $indexPlaceholder; + /** + * @var ActiveForm the instance of `ActiveForm` class. + */ + public $form; + /** * @inheritdoc */ @@ -221,6 +228,14 @@ protected function initColumns() 'context' => $this->context ], $column); + if (!is_array($this->addButtonOptions)) { + $this->addButtonOptions = [$this->addButtonOptions]; + } + + if (!array_key_exists('attributeOptions', $definition)) { + $definition['attributeOptions'] = $this->attributeOptions; + } + $this->columns[$i] = Yii::createObject($definition); } } @@ -278,7 +293,7 @@ protected function registerAssets() 'jsTemplates' => $jsTemplates, 'max' => $this->max, 'min' => $this->min, - 'attributeOptions' => $this->attributeOptions, + 'attributes' => $this->prepareJsAttributes(), 'indexPlaceholder' => $this->getIndexPlaceholder() ]); @@ -327,4 +342,34 @@ private function prepareIndexPlaceholder() { $this->indexPlaceholder = 'multiple_index_' . $this->id; } + + /** + * Prepares attributes options for client side. + * + * @return array + */ + protected function prepareJsAttributes() + { + $attributes = []; + foreach ($this->columns as $column) { + $model = $column->getModel(); + if ($this->form instanceof ActiveForm && $model instanceof Model) { + $field = $this->form->field($model, $column->name); + foreach ($column->attributeOptions as $name => $value) { + if ($field->hasProperty($name)) { + $field->$name = $value; + } + } + $field->render(''); + $attributeOptions = array_pop($this->form->attributes); + $attributeOptions = ArrayHelper::merge($attributeOptions, $column->attributeOptions); + } else { + $attributeOptions = $column->attributeOptions; + } + $inputID = str_replace(['-0', '-0-'], '', $column->getElementId(0)); + $attributes[$inputID] = $attributeOptions; + } + + return $attributes; + } } From 13d8ff00dd672badc1c77ab7a0aef55e2f202f9a Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 12 Oct 2016 11:38:00 +0300 Subject: [PATCH 026/247] Passing a deleted row to the event --- docs/javascript.md | 3 ++- examples/views/multiple-input.php | 3 ++- src/assets/src/js/jquery.multipleInput.js | 2 +- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/javascript.md b/docs/javascript.md index fed9b48..86e611f 100644 --- a/docs/javascript.md +++ b/docs/javascript.md @@ -20,8 +20,9 @@ jQuery('#multiple-input').on('afterInit', function(){ // For TableRenderer it is tr.multiple-input-list__item console.log('calls on before remove row event.'); return confirm('Are you sure you want to delete row?') -}).on('afterDeleteRow', function(){ +}).on('afterDeleteRow', function(e, row){ console.log('calls on after remove row event'); + console.log(row); }); ``` diff --git a/examples/views/multiple-input.php b/examples/views/multiple-input.php index 4f3d495..40ceab7 100644 --- a/examples/views/multiple-input.php +++ b/examples/views/multiple-input.php @@ -164,8 +164,9 @@ console.log(item); console.log('calls on before remove row event'); return confirm('Are you sure you want to delete row?') - }).on('afterDeleteRow', function(){ + }).on('afterDeleteRow', function(e, item){ console.log('calls on after remove row event'); + console.log('User_id:' + item.find('.list-cell__user_id').find('select').first().val()); }); JS; diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 0335ad5..c19ac6a 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -282,7 +282,7 @@ $(this).remove(); event = $.Event(events.afterDeleteRow); - $wrapper.trigger(event); + $wrapper.trigger(event, [$toDelete]); }); } }; diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 5cace5d..75d317d 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return l[e]?l[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):l.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,template:null,jsTemplates:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,l={init:function(l){var u=t.extend(!0,{},i,l||{}),p=t("#"+u.id),d=p.closest("form"),c=this.selector.replace("#","");p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),r(t(this))});var f=0,m=t.Event(e.afterInit),v=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(v),p.trigger(m)}else f++;(0===d.length||f>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(v),p.trigger(m))},100)},add:function(e){r(t(this),e)},remove:function(e){var i=null;i=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},r=function(i,l){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,d=u.template,c=r.children(".multiple-input-list").first();if(!(null!=u.max&&s(r)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),l instanceof Object){var f=[];for(var m in l)l.hasOwnProperty(m)&&f.push(l[m]);l=f}var v;for(var h in u.jsTemplates)v=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(v);var I=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=p(e),a=t("#"+r);if(l){var u=l[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),I++}),r.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);r.trigger(g)}},a=function(i){var l=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=l.data("multipleInput"),o=a.settings;if(s(l)>o.min){var p=t.Event(e.beforeDeleteRow);if(l.trigger(p,[r]),p.result===!1)return;n&&r.find("input, select, textarea").each(function(){u(t(this))}),r.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),l.trigger(p)})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),o=i.replace(/-\d/,"").replace(/-\d-/,"");r.yiiActiveForm("add",t.extend({},a.settings.attributes[o],{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return l[e]?l[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):l.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,template:null,jsTemplates:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,l={init:function(l){var u=t.extend(!0,{},i,l||{}),p=t("#"+u.id),d=p.closest("form"),c=this.selector.replace("#","");p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),r(t(this))});var f=0,m=t.Event(e.afterInit),v=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(v),p.trigger(m)}else f++;(0===d.length||f>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(v),p.trigger(m))},100)},add:function(e){r(t(this),e)},remove:function(e){var i=null;i=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},r=function(i,l){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,d=u.template,c=r.children(".multiple-input-list").first();if(!(null!=u.max&&s(r)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),l instanceof Object){var f=[];for(var m in l)l.hasOwnProperty(m)&&f.push(l[m]);l=f}var v;for(var h in u.jsTemplates)v=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(v);var I=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=p(e),a=t("#"+r);if(l){var u=l[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),I++}),r.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);r.trigger(g)}},a=function(i){var l=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=l.data("multipleInput"),o=a.settings;if(s(l)>o.min){var p=t.Event(e.beforeDeleteRow);if(l.trigger(p,[r]),p.result===!1)return;n&&r.find("input, select, textarea").each(function(){u(t(this))}),r.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),l.trigger(p,[r])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),o=i.replace(/-\d/,"").replace(/-\d-/,"");r.yiiActiveForm("add",t.extend({},a.settings.attributes[o],{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file From b74299e76e4725d5d2ed91f2416ad793ae8b7536 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 12 Oct 2016 11:39:05 +0300 Subject: [PATCH 027/247] Prepare to release --- CHANGELOG.md | 4 ++++ README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c90287e..ced43ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ Yii2 multiple input change log ============================== +2.1.1 + +- Enh: Passing a deleted row to the event + 2.1.0 ===== diff --git a/README.md b/README.md index e3873b7..564a439 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v2.1.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.1.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index c635253..ebeec59 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.1.0", + "version": "2.1.1", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 0185d10..d35dc85 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.1.0", + "version": "2.1.1", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 56c937d860193e8e56d3bf0cb8010a33ae53533c Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 12 Oct 2016 11:42:14 +0300 Subject: [PATCH 028/247] update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ced43ee..5794fb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ Yii2 multiple input change log ============================== 2.1.1 +===== - Enh: Passing a deleted row to the event From 03d8d457f2044e4c5c0d6c8b48be95f2a6214932 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Tue, 15 Nov 2016 20:37:55 +0300 Subject: [PATCH 029/247] Fixed preparation of js attributes --- src/renderers/BaseRenderer.php | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 670ff5b..d812d1a 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -353,6 +353,7 @@ protected function prepareJsAttributes() $attributes = []; foreach ($this->columns as $column) { $model = $column->getModel(); + $inputID = str_replace(['-0', '-0-'], '', $column->getElementId(0)); if ($this->form instanceof ActiveForm && $model instanceof Model) { $field = $this->form->field($model, $column->name); foreach ($column->attributeOptions as $name => $value) { @@ -362,12 +363,14 @@ protected function prepareJsAttributes() } $field->render(''); $attributeOptions = array_pop($this->form->attributes); - $attributeOptions = ArrayHelper::merge($attributeOptions, $column->attributeOptions); + if (isset($attributeOptions['name']) && $attributeOptions['name'] === $column->name) { + $attributes[$inputID] = ArrayHelper::merge($attributeOptions, $column->attributeOptions); + } else { + array_push($this->form->attributes, $attributeOptions); + } } else { - $attributeOptions = $column->attributeOptions; + $attributes[$inputID] = $column->attributeOptions; } - $inputID = str_replace(['-0', '-0-'], '', $column->getElementId(0)); - $attributes[$inputID] = $attributeOptions; } return $attributes; From 6a15c99727d4057a51c5f694845cf1b5e03ec017 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Tue, 15 Nov 2016 20:40:58 +0300 Subject: [PATCH 030/247] update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5794fb3..bd03641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Yii2 multiple input change log ============================== +2.2.0 in development +==================== + +- #104: Fixed preparation of js attributes (Choate, unclead) + 2.1.1 ===== From dd153976b5083c372b01163de9df338faa9e77f2 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 27 Nov 2016 22:37:54 +0300 Subject: [PATCH 031/247] Using the ID of a widget instead of ID which can be passed via options property --- CHANGELOG.md | 5 +++-- src/MultipleInput.php | 2 +- src/TabularInput.php | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bd03641..5db6fa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ Yii2 multiple input change log ============================== -2.2.0 in development -==================== +2.2.0 +===== - #104: Fixed preparation of js attributes (Choate, unclead) +- #109: Using the ID of a widget instead of ID which can be passed via options property (unclead) 2.1.1 ===== diff --git a/src/MultipleInput.php b/src/MultipleInput.php index d79afd9..1eb3781 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -197,7 +197,7 @@ public function run() private function createRenderer() { $config = [ - 'id' => $this->options['id'], + 'id' => $this->getId(), 'columns' => $this->columns, 'min' => $this->min, 'max' => $this->max, diff --git a/src/TabularInput.php b/src/TabularInput.php index 052cd5a..087ef3d 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -147,7 +147,7 @@ public function run() private function createRenderer() { $config = [ - 'id' => $this->options['id'], + 'id' => $this->getId(), 'columns' => $this->columns, 'min' => $this->min, 'max' => $this->max, From c339ee727457434f3b48681e6371de0e0fe988ac Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 27 Nov 2016 23:01:55 +0300 Subject: [PATCH 032/247] Revert dd153976b5083c372b01163de9df338faa9e77f2 --- CHANGELOG.md | 1 - src/MultipleInput.php | 2 +- src/TabularInput.php | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5db6fa3..c1b6e02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,6 @@ Yii2 multiple input change log ===== - #104: Fixed preparation of js attributes (Choate, unclead) -- #109: Using the ID of a widget instead of ID which can be passed via options property (unclead) 2.1.1 ===== diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 1eb3781..d79afd9 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -197,7 +197,7 @@ public function run() private function createRenderer() { $config = [ - 'id' => $this->getId(), + 'id' => $this->options['id'], 'columns' => $this->columns, 'min' => $this->min, 'max' => $this->max, diff --git a/src/TabularInput.php b/src/TabularInput.php index 087ef3d..052cd5a 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -147,7 +147,7 @@ public function run() private function createRenderer() { $config = [ - 'id' => $this->getId(), + 'id' => $this->options['id'], 'columns' => $this->columns, 'min' => $this->min, 'max' => $this->max, From 448d811a46b953a5281373c754eadb7a730cd103 Mon Sep 17 00:00:00 2001 From: pvlg Date: Thu, 1 Dec 2016 04:56:29 +0200 Subject: [PATCH 033/247] Not removed from index 0 $('#multiple-input').multipleInput('remove', 0); remove the last element --- src/assets/src/js/jquery.multipleInput.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index c19ac6a..8b180b2 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -151,7 +151,7 @@ remove: function (index) { var row = null; - if (index) { + if (index != undefined) { row = $(this).find('.js-input-remove:eq(' + index + ')'); } else { row = $(this).find('.js-input-remove').last(); @@ -367,4 +367,4 @@ String.prototype.replaceAll = function (search, replace) { return this.split(search).join(replace); }; -})(window.jQuery); \ No newline at end of file +})(window.jQuery); From 27e5ee112ab0c57adbc8ea0ff3491464fe2eaa5b Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Thu, 1 Dec 2016 10:53:10 +0300 Subject: [PATCH 034/247] Undate CHANGELOG and generated minified version --- CHANGELOG.md | 5 +++++ src/assets/src/js/jquery.multipleInput.min.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c1b6e02..9a50fc3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Yii2 multiple input change log ============================== +2.2.1 in development +==================== + +- Fixed removal of row with index 0 via js api method (pvlg) + 2.2.0 ===== diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 75d317d..d9228fb 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return l[e]?l[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):l.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,template:null,jsTemplates:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,l={init:function(l){var u=t.extend(!0,{},i,l||{}),p=t("#"+u.id),d=p.closest("form"),c=this.selector.replace("#","");p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),r(t(this))});var f=0,m=t.Event(e.afterInit),v=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(v),p.trigger(m)}else f++;(0===d.length||f>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(v),p.trigger(m))},100)},add:function(e){r(t(this),e)},remove:function(e){var i=null;i=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},r=function(i,l){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,d=u.template,c=r.children(".multiple-input-list").first();if(!(null!=u.max&&s(r)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),l instanceof Object){var f=[];for(var m in l)l.hasOwnProperty(m)&&f.push(l[m]);l=f}var v;for(var h in u.jsTemplates)v=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(v);var I=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=p(e),a=t("#"+r);if(l){var u=l[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),I++}),r.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);r.trigger(g)}},a=function(i){var l=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=l.data("multipleInput"),o=a.settings;if(s(l)>o.min){var p=t.Event(e.beforeDeleteRow);if(l.trigger(p,[r]),p.result===!1)return;n&&r.find("input, select, textarea").each(function(){u(t(this))}),r.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),l.trigger(p,[r])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),o=i.replace(/-\d/,"").replace(/-\d-/,"");r.yiiActiveForm("add",t.extend({},a.settings.attributes[o],{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return l[e]?l[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):l.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,template:null,jsTemplates:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,l={init:function(l){var u=t.extend(!0,{},i,l||{}),p=t("#"+u.id),d=p.closest("form"),c=this.selector.replace("#","");p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),r(t(this))});var f=0,v=t.Event(e.afterInit),m=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(m),p.trigger(v)}else f++;(0===d.length||f>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(m),p.trigger(v))},100)},add:function(e){r(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},r=function(i,l){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,d=u.template,c=r.children(".multiple-input-list").first();if(!(null!=u.max&&s(r)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),l instanceof Object){var f=[];for(var v in l)l.hasOwnProperty(v)&&f.push(l[v]);l=f}var m;for(var h in u.jsTemplates)m=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(m);var I=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=p(e),a=t("#"+r);if(l){var u=l[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),I++}),r.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);r.trigger(g)}},a=function(i){var l=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=l.data("multipleInput"),o=a.settings;if(s(l)>o.min){var p=t.Event(e.beforeDeleteRow);if(l.trigger(p,[r]),p.result===!1)return;n&&r.find("input, select, textarea").each(function(){u(t(this))}),r.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),l.trigger(p,[r])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),o=i.replace(/-\d/,"").replace(/-\d-/,"");r.yiiActiveForm("add",t.extend({},a.settings.attributes[o],{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file From 93af3f951477d1200a457d3ffd00b4822781dd46 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Thu, 1 Dec 2016 11:06:55 +0300 Subject: [PATCH 035/247] prepare to release 2.2.1 --- CHANGELOG.md | 4 ++-- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a50fc3..b4bb187 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ Yii2 multiple input change log ============================== -2.2.1 in development -==================== +2.2.1 +===== - Fixed removal of row with index 0 via js api method (pvlg) diff --git a/README.md b/README.md index 564a439..909e75c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v2.1.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.2.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index ebeec59..4f20c17 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.1.1", + "version": "2.2.1", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index d35dc85..4ae5839 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.1.1", + "version": "2.2.1", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From ea4dca33026a49180e568c826388f7fb03a3993c Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Thu, 1 Dec 2016 11:08:34 +0300 Subject: [PATCH 036/247] changed the version --- CHANGELOG.md | 7 ++----- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4bb187..2ab47ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,12 @@ Yii2 multiple input change log ============================== -2.2.1 -===== - -- Fixed removal of row with index 0 via js api method (pvlg) - 2.2.0 ===== - #104: Fixed preparation of js attributes (Choate, unclead) +- Fixed removal of row with index 0 via js api method (pvlg) + 2.1.1 ===== diff --git a/README.md b/README.md index 909e75c..f1391af 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v2.2.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.2.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 4f20c17..c1d1b21 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.2.1", + "version": "2.2.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 4ae5839..b14d61d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.2.1", + "version": "2.2.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 8c880a89d27dc96b871169ce670501bc6d66187b Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Tue, 13 Dec 2016 14:54:33 +0300 Subject: [PATCH 037/247] update dependency --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c1d1b21..f177e36 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "yiisoft/yii2": "*" }, "require-dev": { - "phpunit/phpunit": "5.4.*" + "phpunit/phpunit": "5.7.*" }, "autoload": { "psr-4": { From 8f02e286cf83b5c5a131413d4e8329186c6363a5 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Mon, 19 Dec 2016 16:49:51 +0300 Subject: [PATCH 038/247] Prevent errors when call widget without options --- src/assets/src/js/jquery.multipleInput.js | 5 +++++ src/assets/src/js/jquery.multipleInput.min.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 8b180b2..f87d215 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -65,6 +65,11 @@ var methods = { init: function (options) { + if (typeof options !== 'object') { + console.error('Options must be an object'); + return; + } + var settings = $.extend(true, {}, defaultOptions, options || {}), $wrapper = $('#' + settings.id), form = $wrapper.closest('form'), diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index d9228fb..2b9c8c7 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return l[e]?l[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):l.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,template:null,jsTemplates:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,l={init:function(l){var u=t.extend(!0,{},i,l||{}),p=t("#"+u.id),d=p.closest("form"),c=this.selector.replace("#","");p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),r(t(this))});var f=0,v=t.Event(e.afterInit),m=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(m),p.trigger(v)}else f++;(0===d.length||f>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(m),p.trigger(v))},100)},add:function(e){r(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},r=function(i,l){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,d=u.template,c=r.children(".multiple-input-list").first();if(!(null!=u.max&&s(r)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),l instanceof Object){var f=[];for(var v in l)l.hasOwnProperty(v)&&f.push(l[v]);l=f}var m;for(var h in u.jsTemplates)m=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(m);var I=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=p(e),a=t("#"+r);if(l){var u=l[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),I++}),r.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);r.trigger(g)}},a=function(i){var l=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=l.data("multipleInput"),o=a.settings;if(s(l)>o.min){var p=t.Event(e.beforeDeleteRow);if(l.trigger(p,[r]),p.result===!1)return;n&&r.find("input, select, textarea").each(function(){u(t(this))}),r.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),l.trigger(p,[r])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),o=i.replace(/-\d/,"").replace(/-\d-/,"");r.yiiActiveForm("add",t.extend({},a.settings.attributes[o],{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return l[e]?l[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):l.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,template:null,jsTemplates:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,l={init:function(l){if("object"!=typeof l)return void console.error("Options must be an object");var u=t.extend(!0,{},i,l||{}),p=t("#"+u.id),d=p.closest("form"),c=this.selector.replace("#","");p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),r(t(this))});var f=0,v=t.Event(e.afterInit),m=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(m),p.trigger(v)}else f++;(0===d.length||f>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(m),p.trigger(v))},100)},add:function(e){r(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},r=function(i,l){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,d=u.template,c=r.children(".multiple-input-list").first();if(!(null!=u.max&&s(r)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),l instanceof Object){var f=[];for(var v in l)l.hasOwnProperty(v)&&f.push(l[v]);l=f}var m;for(var h in u.jsTemplates)m=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(m);var I=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=p(e),a=t("#"+r);if(l){var u=l[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),I++}),r.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);r.trigger(g)}},a=function(i){var l=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=l.data("multipleInput"),o=a.settings;if(s(l)>o.min){var p=t.Event(e.beforeDeleteRow);if(l.trigger(p,[r]),p.result===!1)return;n&&r.find("input, select, textarea").each(function(){u(t(this))}),r.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),l.trigger(p,[r])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),o=i.replace(/-\d/,"").replace(/-\d-/,"");r.yiiActiveForm("add",t.extend({},a.settings.attributes[o],{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file From 53b7381acd1ab0225ebf49ab79e0359fb7ecae01 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 28 Jan 2017 21:44:10 +0300 Subject: [PATCH 039/247] Fixed #109 and #107 --- CHANGELOG.md | 6 ++++ README.md | 2 +- composer.json | 2 +- package.json | 2 +- src/MultipleInput.php | 13 +++++-- src/TabularInput.php | 2 +- src/assets/src/js/jquery.multipleInput.js | 35 +++++++++++++++---- src/assets/src/js/jquery.multipleInput.min.js | 2 +- src/renderers/BaseRenderer.php | 1 + src/renderers/TableRenderer.php | 2 +- 10 files changed, 52 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ab47ff..46ce416 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,12 @@ Yii2 multiple input change log ============================== +2.3.0 +===== + +- #107: render a hidden input when `MultipleInput` is used for active field +- #109: respect ID when using a widget's placeholder + 2.2.0 ===== diff --git a/README.md b/README.md index f1391af..bc1015c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v2.2.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.3.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index f177e36..e8f5f38 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.2.0", + "version": "2.3.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index b14d61d..eb54faa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.2.0", + "version": "2.3.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" diff --git a/src/MultipleInput.php b/src/MultipleInput.php index d79afd9..4ae8178 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -12,6 +12,7 @@ use yii\base\InvalidConfigException; use yii\base\Model; use yii\helpers\ArrayHelper; +use yii\helpers\Html; use yii\widgets\ActiveForm; use yii\widgets\InputWidget; use yii\db\ActiveRecordInterface; @@ -188,7 +189,15 @@ protected function guessColumns() */ public function run() { - return $this->createRenderer()->render(); + $content = ''; + if ($this->hasModel()) { + $content .= Html::hiddenInput(Html::getInputName($this->model, $this->attribute), null, [ + 'id' => Html::getInputId($this->model, $this->attribute) + ]); + } + $content .= $this->createRenderer()->render(); + + return $content; } /** @@ -197,7 +206,7 @@ public function run() private function createRenderer() { $config = [ - 'id' => $this->options['id'], + 'id' => $this->getId(), 'columns' => $this->columns, 'min' => $this->min, 'max' => $this->max, diff --git a/src/TabularInput.php b/src/TabularInput.php index 052cd5a..087ef3d 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -147,7 +147,7 @@ public function run() private function createRenderer() { $config = [ - 'id' => $this->options['id'], + 'id' => $this->getId(), 'columns' => $this->columns, 'min' => $this->min, 'max' => $this->max, diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index f87d215..3306328 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -48,16 +48,37 @@ }; var defaultOptions = { + /** + * the ID of widget + */ id: null, - // the template of row + /** + * the ID of related input in case of using widget for an active field + */ + inputId: null, + /** + * the template of row + */ template: null, - // string that collect js templates of widgets which uses in the columns + /** + * string that collect js templates of widgets which uses in the columns + */ jsTemplates: [], - // how many row has to renders + /** + * how many row are allowed to render + */ max: 1, - // minimum number of rows + /** + * a minimum number of rows + */ min: 1, + /** + * active form options of attributes + */ attributes: {}, + /** + * default prefix of a widget's placeholder + */ indexPlaceholder: 'multiple_index' }; @@ -73,7 +94,7 @@ var settings = $.extend(true, {}, defaultOptions, options || {}), $wrapper = $('#' + settings.id), form = $wrapper.closest('form'), - id = this.selector.replace('#', ''); + inputId = settings.inputId; $wrapper.data('multipleInput', { settings: settings, @@ -96,7 +117,7 @@ var intervalID = setInterval(function () { if (typeof form.data('yiiActiveForm') === 'object') { - var attribute = form.yiiActiveForm('find', id), + var attribute = form.yiiActiveForm('find', inputId), defaultAttributeOptions = { enableAjaxValidation: false, validateOnBlur: false, @@ -113,7 +134,7 @@ } }); - form.yiiActiveForm('remove', id); + form.yiiActiveForm('remove', inputId); } // append default options to option from settings diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 2b9c8c7..b0b0118 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return l[e]?l[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):l.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,template:null,jsTemplates:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,l={init:function(l){if("object"!=typeof l)return void console.error("Options must be an object");var u=t.extend(!0,{},i,l||{}),p=t("#"+u.id),d=p.closest("form"),c=this.selector.replace("#","");p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),r(t(this))});var f=0,v=t.Event(e.afterInit),m=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(m),p.trigger(v)}else f++;(0===d.length||f>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(m),p.trigger(v))},100)},add:function(e){r(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},r=function(i,l){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,d=u.template,c=r.children(".multiple-input-list").first();if(!(null!=u.max&&s(r)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),l instanceof Object){var f=[];for(var v in l)l.hasOwnProperty(v)&&f.push(l[v]);l=f}var m;for(var h in u.jsTemplates)m=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(m);var I=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=p(e),a=t("#"+r);if(l){var u=l[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),I++}),r.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);r.trigger(g)}},a=function(i){var l=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=l.data("multipleInput"),o=a.settings;if(s(l)>o.min){var p=t.Event(e.beforeDeleteRow);if(l.trigger(p,[r]),p.result===!1)return;n&&r.find("input, select, textarea").each(function(){u(t(this))}),r.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),l.trigger(p,[r])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),o=i.replace(/-\d/,"").replace(/-\d-/,"");r.yiiActiveForm("add",t.extend({},a.settings.attributes[o],{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return l[e]?l[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):l.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,l={init:function(l){if("object"!=typeof l)return void console.error("Options must be an object");var u=t.extend(!0,{},i,l||{}),p=t("#"+u.id),d=p.closest("form"),c=u.inputId;p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),r(t(this))});var f=0,v=t.Event(e.afterInit),m=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(m),p.trigger(v)}else f++;(0===d.length||f>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(m),p.trigger(v))},100)},add:function(e){r(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},r=function(i,l){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,d=u.template,c=r.children(".multiple-input-list").first();if(!(null!=u.max&&s(r)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),l instanceof Object){var f=[];for(var v in l)l.hasOwnProperty(v)&&f.push(l[v]);l=f}var m;for(var h in u.jsTemplates)m=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(m);var I=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=p(e),a=t("#"+r);if(l){var u=l[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),I++}),r.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);r.trigger(g)}},a=function(i){var l=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=l.data("multipleInput"),o=a.settings;if(s(l)>o.min){var p=t.Event(e.beforeDeleteRow);if(l.trigger(p,[r]),p.result===!1)return;n&&r.find("input, select, textarea").each(function(){u(t(this))}),r.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),l.trigger(p,[r])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),o=i.replace(/-\d/,"").replace(/-\d-/,"");r.yiiActiveForm("add",t.extend({},a.settings.attributes[o],{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index d812d1a..0c5c00f 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -289,6 +289,7 @@ protected function registerAssets() $options = Json::encode([ 'id' => $this->id, + 'inputId' => $this->context->options['id'], 'template' => $template, 'jsTemplates' => $jsTemplates, 'max' => $this->max, diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index d06eda2..21812d9 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -39,7 +39,7 @@ protected function internalRender() $content = Html::tag('table', implode("\n", $content), $options); - return Html::tag( 'div', $content, [ + return Html::tag('div', $content, [ 'id' => $this->id, 'class' => 'multiple-input' ]); From a8700f31b1a6d17dd699505f8dc200f5eecd5338 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 1 Feb 2017 21:47:30 +0300 Subject: [PATCH 040/247] Fixed ajax validation for embedded fields --- examples/views/embedded-input.php | 4 ++-- src/MultipleInput.php | 4 ++-- src/assets/src/js/jquery.multipleInput.js | 21 ++++++++++++++++--- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 4 files changed, 23 insertions(+), 8 deletions(-) diff --git a/examples/views/embedded-input.php b/examples/views/embedded-input.php index aff0775..ba33087 100644 --- a/examples/views/embedded-input.php +++ b/examples/views/embedded-input.php @@ -11,14 +11,14 @@ /* @var $model ExampleModel */ $commonAttributeOptions = [ - 'enableAjaxValidation' => false, + 'enableAjaxValidation' => true, 'enableClientValidation' => false, 'validateOnChange' => false, 'validateOnSubmit' => true, 'validateOnBlur' => false, ]; -$enableActiveForm = false; +$enableActiveForm = true; ?> hasModel()) { + if ($this->hasModel() && $this->isEmbedded === false) { $content .= Html::hiddenInput(Html::getInputName($this->model, $this->attribute), null, [ 'id' => Html::getInputId($this->model, $this->attribute) ]); diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 3306328..7670e5d 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -341,10 +341,25 @@ return; } - var data = wrapper.data('multipleInput'); - var bareID = id.replace(/-\d/, '').replace(/-\d-/, ''); + var data = wrapper.data('multipleInput'), + attributeOptions = {}; + + // try to find options for embedded attribute at first. + // For example the id of new input is example-1-field-0. + // We remove last index and check whether attribute with such id exists or not. + var bareId = id.replace(/-\d-([^\d]+)$/, '-$1'); + if (data.settings.attributes.hasOwnProperty(bareId)) { + attributeOptions = data.settings.attributes[bareId]; + } else { + // fallback in case of using flatten widget - just remove all digital indexes and check whether attribute + // exists or not. + bareId = bareId.replaceAll(/-\d-/, '-').replaceAll(/-\d/, ''); + if (data.settings.attributes.hasOwnProperty(bareId)) { + attributeOptions = data.settings.attributes[bareId]; + } + } - form.yiiActiveForm('add', $.extend({}, data.settings.attributes[bareID], { + form.yiiActiveForm('add', $.extend({}, attributeOptions, { 'id': id, 'input': '#' + id, 'container': '.field-' + id diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index b0b0118..b042a55 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return l[e]?l[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):l.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,l={init:function(l){if("object"!=typeof l)return void console.error("Options must be an object");var u=t.extend(!0,{},i,l||{}),p=t("#"+u.id),d=p.closest("form"),c=u.inputId;p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),r(t(this))});var f=0,v=t.Event(e.afterInit),m=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(m),p.trigger(v)}else f++;(0===d.length||f>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(m),p.trigger(v))},100)},add:function(e){r(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},r=function(i,l){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,d=u.template,c=r.children(".multiple-input-list").first();if(!(null!=u.max&&s(r)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),l instanceof Object){var f=[];for(var v in l)l.hasOwnProperty(v)&&f.push(l[v]);l=f}var m;for(var h in u.jsTemplates)m=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(m);var I=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=p(e),a=t("#"+r);if(l){var u=l[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),I++}),r.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);r.trigger(g)}},a=function(i){var l=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=l.data("multipleInput"),o=a.settings;if(s(l)>o.min){var p=t.Event(e.beforeDeleteRow);if(l.trigger(p,[r]),p.result===!1)return;n&&r.find("input, select, textarea").each(function(){u(t(this))}),r.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),l.trigger(p,[r])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),o=i.replace(/-\d/,"").replace(/-\d-/,"");r.yiiActiveForm("add",t.extend({},a.settings.attributes[o],{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return l[e]?l[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):l.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,l={init:function(l){if("object"!=typeof l)return void console.error("Options must be an object");var u=t.extend(!0,{},i,l||{}),p=t("#"+u.id),d=p.closest("form"),c=u.inputId;p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),r(t(this))});var f=0,v=t.Event(e.afterInit),m=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(m),p.trigger(v)}else f++;(0===d.length||f>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(m),p.trigger(v))},100)},add:function(e){r(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},r=function(i,l){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,d=u.template,c=r.children(".multiple-input-list").first();if(!(null!=u.max&&s(r)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),l instanceof Object){var f=[];for(var v in l)l.hasOwnProperty(v)&&f.push(l[v]);l=f}var m;for(var h in u.jsTemplates)m=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(m);var g=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=p(e),a=t("#"+r);if(l){var u=l[g];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),g++}),r.data("multipleInput").currentIndex++;var I=t.Event(e.afterAddRow);r.trigger(I)}},a=function(i){var l=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=l.data("multipleInput"),o=a.settings;if(s(l)>o.min){var p=t.Event(e.beforeDeleteRow);if(l.trigger(p,[r]),p.result===!1)return;n&&r.find("input, select, textarea").each(function(){u(t(this))}),r.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),l.trigger(p,[r])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),o={},u=i.replace(/-\d-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),r.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file From 6f91f6c0386a7262f86e2475899d47638963aa48 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 1 Feb 2017 21:49:31 +0300 Subject: [PATCH 041/247] release 2.3.1 --- CHANGELOG.md | 5 +++++ README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46ce416..d01068d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Yii2 multiple input change log ============================== +2.3.1 +===== + +- Fixed ajax validation for embedded fields + 2.3.0 ===== diff --git a/README.md b/README.md index bc1015c..39905b9 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v2.3.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.3.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index e8f5f38..ee3ea50 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.3.0", + "version": "2.3.1", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index eb54faa..4c67e34 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.3.0", + "version": "2.3.1", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 35815f26c8de2e0cf793fa83bfd5259c98c4d29f Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 3 Feb 2017 09:41:16 +0300 Subject: [PATCH 042/247] patch: set default value for property `isEmbedded` --- README.md | 2 +- composer.json | 2 +- package.json | 2 +- src/MultipleInput.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 39905b9..8df7c70 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v2.3.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.3.2 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index ee3ea50..5e15a9f 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.3.1", + "version": "2.3.2", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 4c67e34..45815c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.3.1", + "version": "2.3.2", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 8d57914..77f6312 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -115,7 +115,7 @@ class MultipleInput extends InputWidget * @var bool whether the widget is embedded or not. * @internal this property is used for internal purposes. Do not use it in your code. */ - public $isEmbedded; + public $isEmbedded = false; /** * @var ActiveForm an instance of ActiveForm which you have to pass in case of using client validation From a919c4f7c8980ecf7ce8f7440aee9dedfb0e2c25 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 3 Feb 2017 11:07:02 +0300 Subject: [PATCH 043/247] Do not render row if data is empty --- src/MultipleInput.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 77f6312..637d8bd 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -159,6 +159,10 @@ protected function initData() ? ArrayHelper::getValue($this->model, $this->attribute, []) : []; + if (!is_array($data) && empty($data)) { + return; + } + foreach ((array) $data as $index => $value) { $this->data[$index] = $value; } From e31042c55c510b725d83f18c8a2af7f7acb34c54 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 4 Feb 2017 23:22:22 +0300 Subject: [PATCH 044/247] Implemented ListRenderer --- CHANGELOG.md | 5 + docs/images/list-rederer.jpg | Bin 0 -> 46980 bytes docs/images/table-renderer.jpg | Bin 0 -> 32715 bytes docs/renderers.md | 27 ++- src/assets/src/css/multiple-input.css | 21 +- src/renderers/ListRenderer.php | 288 ++++++++++++++++++++++++++ src/renderers/TableRenderer.php | 2 +- 7 files changed, 333 insertions(+), 10 deletions(-) create mode 100644 docs/images/list-rederer.jpg create mode 100644 docs/images/table-renderer.jpg create mode 100644 src/renderers/ListRenderer.php diff --git a/CHANGELOG.md b/CHANGELOG.md index d01068d..95dbedd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Yii2 multiple input change log ============================== +2.4.0 +===== + +- Implemented `ListRenderer` + 2.3.1 ===== diff --git a/docs/images/list-rederer.jpg b/docs/images/list-rederer.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5480716aac4397bb2da923a793fa86724f047f76 GIT binary patch literal 46980 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 literal 0 HcmV?d00001 diff --git a/docs/images/table-renderer.jpg b/docs/images/table-renderer.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0ea01df7d3e253daf10a50257bee729cc02f59f1 GIT binary patch literal 32715 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 literal 0 HcmV?d00001 diff --git a/docs/renderers.md b/docs/renderers.md index 201d857..a7cc7ce 100644 --- a/docs/renderers.md +++ b/docs/renderers.md @@ -1,5 +1,28 @@ ##Renderers -> Section is under development +Currently widget supports two type of renderers -Currently widget supports only `TableRenderer` which renders content in table format. +###TableRenderer +![Table renderer](./images/table-renderer.jpg?raw=true) + +This renderer is enabled by default. + +###ListRenderer +![List renderer](./images/list-renderer.jpg?raw=true) + +To enable this renderer you have to use an option `rendererClass` +```php +field($model, 'schedule')->widget(MultipleInput::className(), [ + 'rendererClass' => \unclead\multipleinput\renderers\ListRenderer::className(), + 'max' => 4, + 'allowEmptyList' => true, + 'rowOptions' => function($model) { + $options = []; + + if ($model['priority'] > 1) { + $options['class'] = 'danger'; + } + return $options; + }, +``` \ No newline at end of file diff --git a/src/assets/src/css/multiple-input.css b/src/assets/src/css/multiple-input.css index 9640449..c7e55a0 100644 --- a/src/assets/src/css/multiple-input.css +++ b/src/assets/src/css/multiple-input.css @@ -6,7 +6,6 @@ display: inline-block; width: 80%; } - .multiple-input-list.no-buttons .multiple-input-list__input { display: block; width: 100%; @@ -14,28 +13,36 @@ table.multiple-input-list { margin: 0; } -table.multiple-input-list tbody tr > td { +table.multiple-input-list.table-renderer tbody tr > td { border: 0 !important; } -table.multiple-input-list tr > td:first-child { +table.multiple-input-list.table-renderer tr > td:first-child { padding-left: 0; } -table.multiple-input-list tr > td:last-child { +table.multiple-input-list.table-renderer tr > td:last-child { padding-right: 0; } table.multiple-input-list tr > th { border-bottom: 1px solid #dddddd; } -.multiple-input-list .form-group { +.multiple-input-list.table-renderer .form-group { margin: 0 !important; } -.multiple-input-list__item .label { +.multiple-input-list.list-renderer .form-group { + margin-bottom: 10px !important; +} +.multiple-input-list.table-renderer .multiple-input-list__item .label { display: block; font-size: 13px; } -.multiple-input-list .list-cell__button { +.multiple-input-list.table-renderer .list-cell__button { width: 40px; } +.multiple-input-list.list-renderer .list-cell__button { + width: 70px; + text-align: right; + padding-right: 15px; +} .multiple-input-list__item .radio, .multiple-input-list__item .checkbox { margin: 7px 0 7px 0; diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php new file mode 100644 index 0000000..d89a2d2 --- /dev/null +++ b/src/renderers/ListRenderer.php @@ -0,0 +1,288 @@ +renderHeader(); + $content[] = $this->renderBody(); + $content[] = $this->renderFooter(); + + $options = []; + Html::addCssClass($options, 'multiple-input-list list-renderer table form-horizontal'); + + $content = Html::tag('table', implode("\n", $content), $options); + + return Html::tag('div', $content, [ + 'id' => $this->id, + 'class' => 'multiple-input' + ]); + } + + /** + * Renders the header. + * + * @return string + */ + public function renderHeader() + { + if ($this->min !== 0 || !$this->isAddButtonPositionHeader()) { + return ''; + } + + $button = $this->isAddButtonPositionHeader() ? $this->renderAddButton() : ''; + + $content = []; + $content[] = Html::tag('td', ' '); + $content[] = Html::tag('td', $button, [ + 'class' => 'list-cell__button', + ]); + + return Html::tag('thead', Html::tag('tr', implode("\n", $content))); + } + + /** + * Renders the footer. + * + * @return string + */ + public function renderFooter() + { + if (!$this->isAddButtonPositionFooter()) { + return ''; + } + + $cells = []; + $cells[] = Html::tag('td', ' '); + $cells[] = Html::tag('td', $this->renderAddButton(), [ + 'class' => 'list-cell__button' + ]); + + return Html::tag('tfoot', Html::tag('tr', implode("\n", $cells))); + } + + /** + * Renders the body. + * + * @return string + * @throws \yii\base\InvalidConfigException + * @throws \yii\base\InvalidParamException + */ + protected function renderBody() + { + $rows = []; + + if ($this->data) { + $cnt = count($this->data); + if ($this->min === $this->max && $cnt < $this->max) { + $cnt = $this->max; + } + + $indices = array_keys($this->data); + + for ($i = 0; $i < $cnt; $i++) { + $index = ArrayHelper::getValue($indices, $i, $i); + $item = ArrayHelper::getValue($this->data, $index, null); + $rows[] = $this->renderRowContent($index, $item); + } + } elseif ($this->min > 0) { + for ($i = 0; $i < $this->min; $i++) { + $rows[] = $this->renderRowContent($i); + } + } + + return Html::tag('tbody', implode("\n", $rows)); + } + + /** + * Renders the row content. + * + * @param int $index + * @param ActiveRecordInterface|array $item + * @return mixed + * @throws InvalidConfigException + */ + private function renderRowContent($index = null, $item = null) + { + $elements = []; + foreach ($this->columns as $column) { + /* @var $column BaseColumn */ + $column->setModel($item); + $elements[] = $this->renderCellContent($column, $index); + } + + $content = []; + $content[] = Html::tag('td', implode("\n", $elements)); + if ($this->max !== $this->min) { + $content[] = $this->renderActionColumn($index); + } + + $content = Html::tag('tr', implode("\n", $content), $this->prepareRowOptions($index, $item)); + + if ($index !== null) { + $content = str_replace('{' . $this->getIndexPlaceholder() . '}', $index, $content); + } + + return $content; + } + + /** + * Prepares the row options. + * + * @param int $index + * @param ActiveRecordInterface|array $item + * @return array + */ + protected function prepareRowOptions($index, $item) + { + if (is_callable($this->rowOptions)) { + $options = call_user_func($this->rowOptions, $item, $index, $this->context); + } else { + $options = $this->rowOptions; + } + + Html::addCssClass($options, 'multiple-input-list__item'); + + return $options; + } + + /** + * Renders the cell content. + * + * @param BaseColumn $column + * @param int|null $index + * @return string + */ + public function renderCellContent($column, $index) + { + $id = $column->getElementId($index); + $name = $column->getElementName($index); + $input = $column->renderInput($name, [ + 'id' => $id + ]); + + if ($column->isHiddenInput()) { + return $input; + } + + $hasError = false; + $error = ''; + + if ($index !== null) { + $error = $column->getFirstError($index); + $hasError = !empty($error); + } + + if ($column->enableError) { + $input .= "\n" . $column->renderError($error); + } + + $wrapperOptions = [ + 'class' => 'field-' . $id + ]; + + if ($hasError) { + Html::addCssClass($wrapperOptions, 'has-error'); + } + + $input = Html::tag('div', $input, $wrapperOptions); + + $content = Html::beginTag('div', ['class' => 'form-group list-cell__' . $column->name]); + $content .= Html::label($column->title, $id, [ + 'class' => 'col-sm-2 control-label' . (empty($column->title) ? ' sr-only' : '') + ]); + $content .= Html::tag('div', $input, ['class' => 'col-sm-10']); + $content .= Html::endTag('div'); + + return $content; + } + + /** + * Renders the action column. + * + * @param null|int $index + * @return string + * @throws \Exception + */ + private function renderActionColumn($index = null) + { + return Html::tag('td', $this->getActionButton($index), [ + 'class' => 'list-cell__button', + ]); + } + + private function getActionButton($index) + { + if ($index === null || $this->min === 0) { + return $this->renderRemoveButton(); + } + + $index++; + if ($index < $this->min) { + return ''; + } elseif ($index === $this->min) { + return $this->isAddButtonPositionRow() ? $this->renderAddButton() : ''; + } else { + return $this->renderRemoveButton(); + } + } + + private function renderAddButton() + { + $options = [ + 'class' => 'btn multiple-input-list__btn js-input-plus', + ]; + Html::addCssClass($options, $this->addButtonOptions['class']); + + return Html::tag('div', $this->addButtonOptions['label'], $options); + } + + /** + * Renders remove button. + * + * @return string + * @throws \Exception + */ + private function renderRemoveButton() + { + $options = [ + 'class' => 'btn multiple-input-list__btn js-input-remove', + ]; + Html::addCssClass($options, $this->removeButtonOptions['class']); + + return Html::tag('div', $this->removeButtonOptions['label'], $options); + } + + /** + * Returns template for using in js. + * + * @return string + */ + protected function prepareTemplate() + { + return $this->renderRowContent(); + } +} diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 21812d9..a714039 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -35,7 +35,7 @@ protected function internalRender() $content[] = $this->renderFooter(); $options = []; - Html::addCssClass($options, 'multiple-input-list table table-condensed'); + Html::addCssClass($options, 'multiple-input-list table table-condensed table-renderer'); $content = Html::tag('table', implode("\n", $content), $options); From 878932f89c7800606106c2d580711b1cd9823d0b Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 4 Feb 2017 23:23:05 +0300 Subject: [PATCH 045/247] Fixed image name --- docs/images/{list-rederer.jpg => list-renderer.jpg} | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/images/{list-rederer.jpg => list-renderer.jpg} (100%) diff --git a/docs/images/list-rederer.jpg b/docs/images/list-renderer.jpg similarity index 100% rename from docs/images/list-rederer.jpg rename to docs/images/list-renderer.jpg From b8edc05ee9c25cee6587fa49b89b2c073db83142 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 4 Feb 2017 23:25:02 +0300 Subject: [PATCH 046/247] release 2.4.0 --- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8df7c70..56d074f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v2.3.2 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.4.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 5e15a9f..96b3d5a 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.3.2", + "version": "2.4.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 45815c0..724827b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.3.2", + "version": "2.4.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 3c2074b8e9522a4ae6c3cc78e320f1296b73e50c Mon Sep 17 00:00:00 2001 From: Hiren Bhut Date: Sat, 11 Feb 2017 15:20:48 +0530 Subject: [PATCH 047/247] Update UPGRADE.md --- UPGRADE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/UPGRADE.md b/UPGRADE.md index 6fdc1bf..1d6f584 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -8,7 +8,7 @@ if you want to upgrade from version A to version C and there is version B between A and C, you need to following the instructions for both A and B. -Upgrade from 2.0.0 tp 2.0.1 +Upgrade from 2.0.0 to 2.0.1 --------------------------- - Change namespace prefix `yii\multipleinput\` to `unclead\multipleinput\`. @@ -40,4 +40,4 @@ After installing version 1.1.0 you have to rename js events following the next s - Event `init` rename to `afterInit` - Event `addNewRow` rename to `afterAddRow` -- Event `removeRow` rename to `afterDeleteRow` \ No newline at end of file +- Event `removeRow` rename to `afterDeleteRow` From a1528a68106162563daf91348120073c1eb1dc0e Mon Sep 17 00:00:00 2001 From: Hiren Bhut Date: Tue, 14 Feb 2017 18:00:19 +0530 Subject: [PATCH 048/247] type mistake --- docs/configuration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 4cd1e46..22b94cc 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -12,7 +12,7 @@ Widget support the following options that are additionally recognized over and a you use widget without a model, since in this case widget is not able to detect client-side options automatically **addButtonPosition** *integer|array*: the position(s) of `add` button. -This can be `MultipleInput::POS_HEADER`, `MultipleInput::POS_ROW` or `MultipleInput:PO_FOOTER` +This can be `MultipleInput::POS_HEADER`, `MultipleInput::POS_ROW` or `MultipleInput:POS_FOOTER` **addButtonOptions** *array*: the HTML options for `add` button. Can contains `class` and `label` keys @@ -106,4 +106,4 @@ For using widget as column input you may use the following code: 'mask' => '999-999-99-99' ] ] -``` \ No newline at end of file +``` From b7aa8c2e1e1094c986e0d4595369847c8e6f8aa8 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Mon, 20 Feb 2017 22:45:22 +0300 Subject: [PATCH 049/247] #127: don't use an ID for hidden input --- CHANGELOG.md | 5 +++++ README.md | 2 +- UPGRADE.md | 5 +++++ composer.json | 2 +- examples/views/multiple-input.php | 1 + package.json | 2 +- src/MultipleInput.php | 4 +--- 7 files changed, 15 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95dbedd..523b2e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Yii2 multiple input change log ============================== +2.5.0 +===== + +- #127: fixed js actions + 2.4.0 ===== diff --git a/README.md b/README.md index 56d074f..0d7369d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v2.4.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.5.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/UPGRADE.md b/UPGRADE.md index 6fdc1bf..b3fc558 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -8,6 +8,11 @@ if you want to upgrade from version A to version C and there is version B between A and C, you need to following the instructions for both A and B. +Upgrade from 2.2.0 tp 2.3.0 +--------------------------- + +- Ensure that you set `id` option in case you are using js actions, otherwise you old code won't work. + Upgrade from 2.0.0 tp 2.0.1 --------------------------- diff --git a/composer.json b/composer.json index 96b3d5a..b293cea 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.4.0", + "version": "2.5.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/examples/views/multiple-input.php b/examples/views/multiple-input.php index 40ceab7..1a32baf 100644 --- a/examples/views/multiple-input.php +++ b/examples/views/multiple-input.php @@ -47,6 +47,7 @@

Multiple columns

field($model, 'schedule')->widget(MultipleInput::className(), [ + 'id' => 'examplemodel-schedule', 'max' => 4, 'allowEmptyList' => true, 'rowOptions' => function($model) { diff --git a/package.json b/package.json index 724827b..3d89e38 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.4.0", + "version": "2.5.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 637d8bd..9706a7e 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -195,9 +195,7 @@ public function run() { $content = ''; if ($this->hasModel() && $this->isEmbedded === false) { - $content .= Html::hiddenInput(Html::getInputName($this->model, $this->attribute), null, [ - 'id' => Html::getInputId($this->model, $this->attribute) - ]); + $content .= Html::hiddenInput(Html::getInputName($this->model, $this->attribute), null); } $content .= $this->createRenderer()->render(); From 5256627be47dc0bc8d1013003c4b445d07967c39 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Mon, 20 Feb 2017 22:47:48 +0300 Subject: [PATCH 050/247] typo --- UPGRADE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/UPGRADE.md b/UPGRADE.md index 6fdebb4..c9fa17c 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -11,7 +11,7 @@ for both A and B. Upgrade from 2.2.0 tp 2.3.0 --------------------------- -- Ensure that you set `id` option in case you are using js actions, otherwise you old code won't work. +- Ensure that you set `id` option in case you are using js actions, otherwise your old code won't work. Upgrade from 2.0.0 tp 2.0.1 --------------------------- From 5a3300e06329c00d7b875edc7acc0eb42f42fe59 Mon Sep 17 00:00:00 2001 From: Nikolya Date: Mon, 6 Mar 2017 19:49:49 +0200 Subject: [PATCH 051/247] Sorting + FIX (hasProperty to hasAttribute) --- src/MultipleInput.php | 11 +- src/TabularInput.php | 9 +- src/assets/MultipleInputAsset.php | 2 +- src/assets/MultipleInputSortableAsset.php | 33 ++ src/assets/src/css/multiple-input.min.css | 1 + src/assets/src/css/sorting.css | 33 ++ src/assets/src/css/sorting.min.css | 1 + src/assets/src/js/jquery-sortable.js | 693 ++++++++++++++++++++++ src/assets/src/js/jquery-sortable.min.js | 1 + src/renderers/BaseRenderer.php | 13 + 10 files changed, 793 insertions(+), 4 deletions(-) create mode 100644 src/assets/MultipleInputSortableAsset.php create mode 100644 src/assets/src/css/multiple-input.min.css create mode 100644 src/assets/src/css/sorting.css create mode 100644 src/assets/src/css/sorting.min.css create mode 100644 src/assets/src/js/jquery-sortable.js create mode 100644 src/assets/src/js/jquery-sortable.min.js diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 9706a7e..0abcde4 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -123,6 +123,12 @@ class MultipleInput extends InputWidget */ public $form; + /** + * @var bool allow sorting. + * @internal this property is used when need to allow sorting rows. + */ + public $sortable = false; + /** * Initialization. * @@ -155,7 +161,7 @@ protected function initData() } if ($this->model instanceof Model) { - $data = $this->model->hasProperty($this->attribute) + $data = $this->model->hasAttribute($this->attribute) ? ArrayHelper::getValue($this->model, $this->attribute, []) : []; @@ -219,7 +225,8 @@ private function createRenderer() 'addButtonPosition' => $this->addButtonPosition, 'rowOptions' => $this->rowOptions, 'context' => $this, - 'form' => $this->form + 'form' => $this->form, + 'sortable' => $this->sortable ]; if ($this->removeButtonOptions !== null) { diff --git a/src/TabularInput.php b/src/TabularInput.php index 087ef3d..3a37781 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -109,6 +109,12 @@ class TabularInput extends Widget */ public $form; + /** + * @var bool allow sorting. + * @internal this property is used when need to allow sorting rows. + */ + public $sortable = false; + /** * Initialization. * @@ -158,7 +164,8 @@ private function createRenderer() 'rowOptions' => $this->rowOptions, 'addButtonPosition' => $this->addButtonPosition, 'context' => $this, - 'form' => $this->form + 'form' => $this->form, + 'sortable' => $this->sortable ]; if ($this->removeButtonOptions !== null) { diff --git a/src/assets/MultipleInputAsset.php b/src/assets/MultipleInputAsset.php index 95cb4ed..4c1dded 100644 --- a/src/assets/MultipleInputAsset.php +++ b/src/assets/MultipleInputAsset.php @@ -17,7 +17,7 @@ class MultipleInputAsset extends AssetBundle { public $css = [ - 'css/multiple-input.css' + YII_DEBUG ? 'css/multiple-input.css' : 'css/multiple-input.min.css' ]; public $js = []; diff --git a/src/assets/MultipleInputSortableAsset.php b/src/assets/MultipleInputSortableAsset.php new file mode 100644 index 0000000..2cd1378 --- /dev/null +++ b/src/assets/MultipleInputSortableAsset.php @@ -0,0 +1,33 @@ +td{border:0!important}table.multiple-input-list.table-renderer tr>td:first-child{padding-left:0}table.multiple-input-list.table-renderer tr>td:last-child{padding-right:0}table.multiple-input-list tr>th{border-bottom:1px solid #ddd}.multiple-input-list.table-renderer .form-group{margin:0!important}.multiple-input-list.list-renderer .form-group{margin-bottom:10px!important}.multiple-input-list.table-renderer .multiple-input-list__item .label{display:block;font-size:13px}.multiple-input-list.table-renderer .list-cell__button{width:40px}.multiple-input-list.list-renderer .list-cell__button{width:70px;text-align:right;padding-right:15px}.multiple-input-list__item .checkbox,.multiple-input-list__item .radio{margin:7px 0}.multiple-input-list__item .checkbox-list .checkbox,.multiple-input-list__item .radio-list .radio{margin:0}.multiple-input-list .multiple-input-list{margin-top:-5px} \ No newline at end of file diff --git a/src/assets/src/css/sorting.css b/src/assets/src/css/sorting.css new file mode 100644 index 0000000..e496690 --- /dev/null +++ b/src/assets/src/css/sorting.css @@ -0,0 +1,33 @@ +.multiple-input-list__item, +.dragging, +.dragging * { + cursor: move !important; +} + +.dragged { + position: absolute + top: 0 + opacity: .5 + z-index: 2000 +} + +tr.placeholder { + display: block; + background: red; + position: relative; + margin: 0; + padding: 0; + border: none; +} + +tr.placeholder:before { + content: ""; + position: absolute; + width: 0; + height: 0; + border: 5px solid transparent; + border-left-color: red; + margin-top: -5px; + left: -5px; + border-right: none; +} \ No newline at end of file diff --git a/src/assets/src/css/sorting.min.css b/src/assets/src/css/sorting.min.css new file mode 100644 index 0000000..ea7f145 --- /dev/null +++ b/src/assets/src/css/sorting.min.css @@ -0,0 +1 @@ +.dragging,.dragging *,.multiple-input-list__item{cursor:move!important}.dragged{position:absolute top: 0 opacity: .5 z-index: 2000}tr.placeholder{display:block;background:red;position:relative;margin:0;padding:0;border:none}tr.placeholder:before{content:"";position:absolute;width:0;height:0;border:5px solid transparent;border-left-color:red;margin-top:-5px;left:-5px;border-right:none} \ No newline at end of file diff --git a/src/assets/src/js/jquery-sortable.js b/src/assets/src/js/jquery-sortable.js new file mode 100644 index 0000000..5e93508 --- /dev/null +++ b/src/assets/src/js/jquery-sortable.js @@ -0,0 +1,693 @@ +/* =================================================== + * jquery-sortable.js v0.9.13 + * http://johnny.github.com/jquery-sortable/ + * =================================================== + * Copyright (c) 2012 Jonas von Andrian + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ========================================================== */ + +!function ( $, window, pluginName, undefined){ + var eventNames, + containerDefaults = { + // If true, items can be dragged from this container + drag: true, + // If true, items can be droped onto this container + drop: true, + // Exclude items from being draggable, if the + // selector matches the item + exclude: "", + // If true, search for nested containers within an item.If you nest containers, + // either the original selector with which you call the plugin must only match the top containers, + // or you need to specify a group (see the bootstrap nav example) + nested: true, + // If true, the items are assumed to be arranged vertically + vertical: true + }, // end container defaults + groupDefaults = { + // This is executed after the placeholder has been moved. + // $closestItemOrContainer contains the closest item, the placeholder + // has been put at or the closest empty Container, the placeholder has + // been appended to. + afterMove: function ($placeholder, container, $closestItemOrContainer) { + }, + // The exact css path between the container and its items, e.g. "> tbody" + containerPath: "", + // The css selector of the containers + containerSelector: "ol, ul", + // Distance the mouse has to travel to start dragging + distance: 0, + // Time in milliseconds after mousedown until dragging should start. + // This option can be used to prevent unwanted drags when clicking on an element. + delay: 0, + // The css selector of the drag handle + handle: "", + // The exact css path between the item and its subcontainers. + // It should only match the immediate items of a container. + // No item of a subcontainer should be matched. E.g. for ol>div>li the itemPath is "> div" + itemPath: "", + // The css selector of the items + itemSelector: "li", + // The class given to "body" while an item is being dragged + bodyClass: "dragging", + // The class giving to an item while being dragged + draggedClass: "dragged", + // Check if the dragged item may be inside the container. + // Use with care, since the search for a valid container entails a depth first search + // and may be quite expensive. + isValidTarget: function ($item, container) { + return true + }, + // Executed before onDrop if placeholder is detached. + // This happens if pullPlaceholder is set to false and the drop occurs outside a container. + onCancel: function ($item, container, _super, event) { + }, + // Executed at the beginning of a mouse move event. + // The Placeholder has not been moved yet. + onDrag: function ($item, position, _super, event) { + $item.css(position) + }, + // Called after the drag has been started, + // that is the mouse button is being held down and + // the mouse is moving. + // The container is the closest initialized container. + // Therefore it might not be the container, that actually contains the item. + onDragStart: function ($item, container, _super, event) { + $item.css({ + height: $item.outerHeight(), + width: $item.outerWidth() + }) + $item.addClass(container.group.options.draggedClass) + $("body").addClass(container.group.options.bodyClass) + }, + // Called when the mouse button is being released + onDrop: function ($item, container, _super, event) { + $item.removeClass(container.group.options.draggedClass).removeAttr("style") + $("body").removeClass(container.group.options.bodyClass) + }, + // Called on mousedown. If falsy value is returned, the dragging will not start. + // Ignore if element clicked is input, select or textarea + onMousedown: function ($item, _super, event) { + if (!event.target.nodeName.match(/^(input|select|textarea)$/i)) { + event.preventDefault() + return true + } + }, + // The class of the placeholder (must match placeholder option markup) + placeholderClass: "placeholder", + // Template for the placeholder. Can be any valid jQuery input + // e.g. a string, a DOM element. + // The placeholder must have the class "placeholder" + placeholder: '
  • ', + // If true, the position of the placeholder is calculated on every mousemove. + // If false, it is only calculated when the mouse is above a container. + pullPlaceholder: true, + // Specifies serialization of the container group. + // The pair $parent/$children is either container/items or item/subcontainers. + serialize: function ($parent, $children, parentIsContainer) { + var result = $.extend({}, $parent.data()) + + if(parentIsContainer) + return [$children] + else if ($children[0]){ + result.children = $children + } + + delete result.subContainers + delete result.sortable + + return result + }, + // Set tolerance while dragging. Positive values decrease sensitivity, + // negative values increase it. + tolerance: 0 + }, // end group defaults + containerGroups = {}, + groupCounter = 0, + emptyBox = { + left: 0, + top: 0, + bottom: 0, + right:0 + }, + eventNames = { + start: "touchstart.sortable mousedown.sortable", + drop: "touchend.sortable touchcancel.sortable mouseup.sortable", + drag: "touchmove.sortable mousemove.sortable", + scroll: "scroll.sortable" + }, + subContainerKey = "subContainers" + + /* + * a is Array [left, right, top, bottom] + * b is array [left, top] + */ + function d(a,b) { + var x = Math.max(0, a[0] - b[0], b[0] - a[1]), + y = Math.max(0, a[2] - b[1], b[1] - a[3]) + return x+y; + } + + function setDimensions(array, dimensions, tolerance, useOffset) { + var i = array.length, + offsetMethod = useOffset ? "offset" : "position" + tolerance = tolerance || 0 + + while(i--){ + var el = array[i].el ? array[i].el : $(array[i]), + // use fitting method + pos = el[offsetMethod]() + pos.left += parseInt(el.css('margin-left'), 10) + pos.top += parseInt(el.css('margin-top'),10) + dimensions[i] = [ + pos.left - tolerance, + pos.left + el.outerWidth() + tolerance, + pos.top - tolerance, + pos.top + el.outerHeight() + tolerance + ] + } + } + + function getRelativePosition(pointer, element) { + var offset = element.offset() + return { + left: pointer.left - offset.left, + top: pointer.top - offset.top + } + } + + function sortByDistanceDesc(dimensions, pointer, lastPointer) { + pointer = [pointer.left, pointer.top] + lastPointer = lastPointer && [lastPointer.left, lastPointer.top] + + var dim, + i = dimensions.length, + distances = [] + + while(i--){ + dim = dimensions[i] + distances[i] = [i,d(dim,pointer), lastPointer && d(dim, lastPointer)] + } + distances = distances.sort(function (a,b) { + return b[1] - a[1] || b[2] - a[2] || b[0] - a[0] + }) + + // last entry is the closest + return distances + } + + function ContainerGroup(options) { + this.options = $.extend({}, groupDefaults, options) + this.containers = [] + + if(!this.options.rootGroup){ + this.scrollProxy = $.proxy(this.scroll, this) + this.dragProxy = $.proxy(this.drag, this) + this.dropProxy = $.proxy(this.drop, this) + this.placeholder = $(this.options.placeholder) + + if(!options.isValidTarget) + this.options.isValidTarget = undefined + } + } + + ContainerGroup.get = function (options) { + if(!containerGroups[options.group]) { + if(options.group === undefined) + options.group = groupCounter ++ + + containerGroups[options.group] = new ContainerGroup(options) + } + + return containerGroups[options.group] + } + + ContainerGroup.prototype = { + dragInit: function (e, itemContainer) { + this.$document = $(itemContainer.el[0].ownerDocument) + + // get item to drag + var closestItem = $(e.target).closest(this.options.itemSelector); + // using the length of this item, prevents the plugin from being started if there is no handle being clicked on. + // this may also be helpful in instantiating multidrag. + if (closestItem.length) { + this.item = closestItem; + this.itemContainer = itemContainer; + if (this.item.is(this.options.exclude) || !this.options.onMousedown(this.item, groupDefaults.onMousedown, e)) { + return; + } + this.setPointer(e); + this.toggleListeners('on'); + this.setupDelayTimer(); + this.dragInitDone = true; + } + }, + drag: function (e) { + if(!this.dragging){ + if(!this.distanceMet(e) || !this.delayMet) + return + + this.options.onDragStart(this.item, this.itemContainer, groupDefaults.onDragStart, e) + this.item.before(this.placeholder) + this.dragging = true + } + + this.setPointer(e) + // place item under the cursor + this.options.onDrag(this.item, + getRelativePosition(this.pointer, this.item.offsetParent()), + groupDefaults.onDrag, + e) + + var p = this.getPointer(e), + box = this.sameResultBox, + t = this.options.tolerance + + if(!box || box.top - t > p.top || box.bottom + t < p.top || box.left - t > p.left || box.right + t < p.left) + if(!this.searchValidTarget()){ + this.placeholder.detach() + this.lastAppendedItem = undefined + } + }, + drop: function (e) { + this.toggleListeners('off') + + this.dragInitDone = false + + if(this.dragging){ + // processing Drop, check if placeholder is detached + if(this.placeholder.closest("html")[0]){ + this.placeholder.before(this.item).detach() + } else { + this.options.onCancel(this.item, this.itemContainer, groupDefaults.onCancel, e) + } + this.options.onDrop(this.item, this.getContainer(this.item), groupDefaults.onDrop, e) + + // cleanup + this.clearDimensions() + this.clearOffsetParent() + this.lastAppendedItem = this.sameResultBox = undefined + this.dragging = false + } + }, + searchValidTarget: function (pointer, lastPointer) { + if(!pointer){ + pointer = this.relativePointer || this.pointer + lastPointer = this.lastRelativePointer || this.lastPointer + } + + var distances = sortByDistanceDesc(this.getContainerDimensions(), + pointer, + lastPointer), + i = distances.length + + while(i--){ + var index = distances[i][0], + distance = distances[i][1] + + if(!distance || this.options.pullPlaceholder){ + var container = this.containers[index] + if(!container.disabled){ + if(!this.$getOffsetParent()){ + var offsetParent = container.getItemOffsetParent() + pointer = getRelativePosition(pointer, offsetParent) + lastPointer = getRelativePosition(lastPointer, offsetParent) + } + if(container.searchValidTarget(pointer, lastPointer)) + return true + } + } + } + if(this.sameResultBox) + this.sameResultBox = undefined + }, + movePlaceholder: function (container, item, method, sameResultBox) { + var lastAppendedItem = this.lastAppendedItem + if(!sameResultBox && lastAppendedItem && lastAppendedItem[0] === item[0]) + return; + + item[method](this.placeholder) + this.lastAppendedItem = item + this.sameResultBox = sameResultBox + this.options.afterMove(this.placeholder, container, item) + }, + getContainerDimensions: function () { + if(!this.containerDimensions) + setDimensions(this.containers, this.containerDimensions = [], this.options.tolerance, !this.$getOffsetParent()) + return this.containerDimensions + }, + getContainer: function (element) { + return element.closest(this.options.containerSelector).data(pluginName) + }, + $getOffsetParent: function () { + if(this.offsetParent === undefined){ + var i = this.containers.length - 1, + offsetParent = this.containers[i].getItemOffsetParent() + + if(!this.options.rootGroup){ + while(i--){ + if(offsetParent[0] != this.containers[i].getItemOffsetParent()[0]){ + // If every container has the same offset parent, + // use position() which is relative to this parent, + // otherwise use offset() + // compare #setDimensions + offsetParent = false + break; + } + } + } + + this.offsetParent = offsetParent + } + return this.offsetParent + }, + setPointer: function (e) { + var pointer = this.getPointer(e) + + if(this.$getOffsetParent()){ + var relativePointer = getRelativePosition(pointer, this.$getOffsetParent()) + this.lastRelativePointer = this.relativePointer + this.relativePointer = relativePointer + } + + this.lastPointer = this.pointer + this.pointer = pointer + }, + distanceMet: function (e) { + var currentPointer = this.getPointer(e) + return (Math.max( + Math.abs(this.pointer.left - currentPointer.left), + Math.abs(this.pointer.top - currentPointer.top) + ) >= this.options.distance) + }, + getPointer: function(e) { + var o = e.originalEvent || e.originalEvent.touches && e.originalEvent.touches[0] + return { + left: e.pageX || o.pageX, + top: e.pageY || o.pageY + } + }, + setupDelayTimer: function () { + var that = this + this.delayMet = !this.options.delay + + // init delay timer if needed + if (!this.delayMet) { + clearTimeout(this._mouseDelayTimer); + this._mouseDelayTimer = setTimeout(function() { + that.delayMet = true + }, this.options.delay) + } + }, + scroll: function (e) { + this.clearDimensions() + this.clearOffsetParent() // TODO is this needed? + }, + toggleListeners: function (method) { + var that = this, + events = ['drag','drop','scroll'] + + $.each(events,function (i,event) { + that.$document[method](eventNames[event], that[event + 'Proxy']) + }) + }, + clearOffsetParent: function () { + this.offsetParent = undefined + }, + // Recursively clear container and item dimensions + clearDimensions: function () { + this.traverse(function(object){ + object._clearDimensions() + }) + }, + traverse: function(callback) { + callback(this) + var i = this.containers.length + while(i--){ + this.containers[i].traverse(callback) + } + }, + _clearDimensions: function(){ + this.containerDimensions = undefined + }, + _destroy: function () { + containerGroups[this.options.group] = undefined + } + } + + function Container(element, options) { + this.el = element + this.options = $.extend( {}, containerDefaults, options) + + this.group = ContainerGroup.get(this.options) + this.rootGroup = this.options.rootGroup || this.group + this.handle = this.rootGroup.options.handle || this.rootGroup.options.itemSelector + + var itemPath = this.rootGroup.options.itemPath + this.target = itemPath ? this.el.find(itemPath) : this.el + + this.target.on(eventNames.start, this.handle, $.proxy(this.dragInit, this)) + + if(this.options.drop) + this.group.containers.push(this) + } + + Container.prototype = { + dragInit: function (e) { + var rootGroup = this.rootGroup + + if( !this.disabled && + !rootGroup.dragInitDone && + this.options.drag && + this.isValidDrag(e)) { + rootGroup.dragInit(e, this) + } + }, + isValidDrag: function(e) { + return e.which == 1 || + e.type == "touchstart" && e.originalEvent.touches.length == 1 + }, + searchValidTarget: function (pointer, lastPointer) { + var distances = sortByDistanceDesc(this.getItemDimensions(), + pointer, + lastPointer), + i = distances.length, + rootGroup = this.rootGroup, + validTarget = !rootGroup.options.isValidTarget || + rootGroup.options.isValidTarget(rootGroup.item, this) + + if(!i && validTarget){ + rootGroup.movePlaceholder(this, this.target, "append") + return true + } else + while(i--){ + var index = distances[i][0], + distance = distances[i][1] + if(!distance && this.hasChildGroup(index)){ + var found = this.getContainerGroup(index).searchValidTarget(pointer, lastPointer) + if(found) + return true + } + else if(validTarget){ + this.movePlaceholder(index, pointer) + return true + } + } + }, + movePlaceholder: function (index, pointer) { + var item = $(this.items[index]), + dim = this.itemDimensions[index], + method = "after", + width = item.outerWidth(), + height = item.outerHeight(), + offset = item.offset(), + sameResultBox = { + left: offset.left, + right: offset.left + width, + top: offset.top, + bottom: offset.top + height + } + if(this.options.vertical){ + var yCenter = (dim[2] + dim[3]) / 2, + inUpperHalf = pointer.top <= yCenter + if(inUpperHalf){ + method = "before" + sameResultBox.bottom -= height / 2 + } else + sameResultBox.top += height / 2 + } else { + var xCenter = (dim[0] + dim[1]) / 2, + inLeftHalf = pointer.left <= xCenter + if(inLeftHalf){ + method = "before" + sameResultBox.right -= width / 2 + } else + sameResultBox.left += width / 2 + } + if(this.hasChildGroup(index)) + sameResultBox = emptyBox + this.rootGroup.movePlaceholder(this, item, method, sameResultBox) + }, + getItemDimensions: function () { + if(!this.itemDimensions){ + this.items = this.$getChildren(this.el, "item").filter( + ":not(." + this.group.options.placeholderClass + ", ." + this.group.options.draggedClass + ")" + ).get() + setDimensions(this.items, this.itemDimensions = [], this.options.tolerance) + } + return this.itemDimensions + }, + getItemOffsetParent: function () { + var offsetParent, + el = this.el + // Since el might be empty we have to check el itself and + // can not do something like el.children().first().offsetParent() + if(el.css("position") === "relative" || el.css("position") === "absolute" || el.css("position") === "fixed") + offsetParent = el + else + offsetParent = el.offsetParent() + return offsetParent + }, + hasChildGroup: function (index) { + return this.options.nested && this.getContainerGroup(index) + }, + getContainerGroup: function (index) { + var childGroup = $.data(this.items[index], subContainerKey) + if( childGroup === undefined){ + var childContainers = this.$getChildren(this.items[index], "container") + childGroup = false + + if(childContainers[0]){ + var options = $.extend({}, this.options, { + rootGroup: this.rootGroup, + group: groupCounter ++ + }) + childGroup = childContainers[pluginName](options).data(pluginName).group + } + $.data(this.items[index], subContainerKey, childGroup) + } + return childGroup + }, + $getChildren: function (parent, type) { + var options = this.rootGroup.options, + path = options[type + "Path"], + selector = options[type + "Selector"] + + parent = $(parent) + if(path) + parent = parent.find(path) + + return parent.children(selector) + }, + _serialize: function (parent, isContainer) { + var that = this, + childType = isContainer ? "item" : "container", + + children = this.$getChildren(parent, childType).not(this.options.exclude).map(function () { + return that._serialize($(this), !isContainer) + }).get() + + return this.rootGroup.options.serialize(parent, children, isContainer) + }, + traverse: function(callback) { + $.each(this.items || [], function(item){ + var group = $.data(this, subContainerKey) + if(group) + group.traverse(callback) + }); + + callback(this) + }, + _clearDimensions: function () { + this.itemDimensions = undefined + }, + _destroy: function() { + var that = this; + + this.target.off(eventNames.start, this.handle); + this.el.removeData(pluginName) + + if(this.options.drop) + this.group.containers = $.grep(this.group.containers, function(val){ + return val != that + }) + + $.each(this.items || [], function(){ + $.removeData(this, subContainerKey) + }) + } + } + + var API = { + enable: function() { + this.traverse(function(object){ + object.disabled = false + }) + }, + disable: function (){ + this.traverse(function(object){ + object.disabled = true + }) + }, + serialize: function () { + return this._serialize(this.el, true) + }, + refresh: function() { + this.traverse(function(object){ + object._clearDimensions() + }) + }, + destroy: function () { + this.traverse(function(object){ + object._destroy(); + }) + } + } + + $.extend(Container.prototype, API) + + /** + * jQuery API + * + * Parameters are + * either options on init + * or a method name followed by arguments to pass to the method + */ + $.fn[pluginName] = function(methodOrOptions) { + var args = Array.prototype.slice.call(arguments, 1) + + return this.map(function(){ + var $t = $(this), + object = $t.data(pluginName) + + if(object && API[methodOrOptions]) + return API[methodOrOptions].apply(object, args) || this + else if(!object && (methodOrOptions === undefined || + typeof methodOrOptions === "object")) + $t.data(pluginName, new Container($t, methodOrOptions)) + + return this + }); + }; + +}(jQuery, window, 'sortable'); diff --git a/src/assets/src/js/jquery-sortable.min.js b/src/assets/src/js/jquery-sortable.min.js new file mode 100644 index 0000000..942c62b --- /dev/null +++ b/src/assets/src/js/jquery-sortable.min.js @@ -0,0 +1 @@ +!function(t,e,i,o){function s(t,e){var i=Math.max(0,t[0]-e[0],e[0]-t[1]),o=Math.max(0,t[2]-e[1],e[1]-t[3]);return i+o}function n(e,i,o,s){var n=e.length,r=s?"offset":"position";for(o=o||0;n--;){var a=e[n].el?e[n].el:t(e[n]),h=a[r]();h.left+=parseInt(a.css("margin-left"),10),h.top+=parseInt(a.css("margin-top"),10),i[n]=[h.left-o,h.left+a.outerWidth()+o,h.top-o,h.top+a.outerHeight()+o]}}function r(t,e){var i=e.offset();return{left:t.left-i.left,top:t.top-i.top}}function a(t,e,i){e=[e.left,e.top],i=i&&[i.left,i.top];for(var o,n=t.length,r=[];n--;)o=t[n],r[n]=[n,s(o,e),i&&s(o,i)];return r=r.sort(function(t,e){return e[1]-t[1]||e[2]-t[2]||e[0]-t[0]})}function h(e){this.options=t.extend({},p,e),this.containers=[],this.options.rootGroup||(this.scrollProxy=t.proxy(this.scroll,this),this.dragProxy=t.proxy(this.drag,this),this.dropProxy=t.proxy(this.drop,this),this.placeholder=t(this.options.placeholder),e.isValidTarget||(this.options.isValidTarget=o))}function l(e,i){this.el=e,this.options=t.extend({},u,i),this.group=h.get(this.options),this.rootGroup=this.options.rootGroup||this.group,this.handle=this.rootGroup.options.handle||this.rootGroup.options.itemSelector;var o=this.rootGroup.options.itemPath;this.target=o?this.el.find(o):this.el,this.target.on(c.start,this.handle,t.proxy(this.dragInit,this)),this.options.drop&&this.group.containers.push(this)}var c,u={drag:!0,drop:!0,exclude:"",nested:!0,vertical:!0},p={afterMove:function(t,e,i){},containerPath:"",containerSelector:"ol, ul",distance:0,delay:0,handle:"",itemPath:"",itemSelector:"li",bodyClass:"dragging",draggedClass:"dragged",isValidTarget:function(t,e){return!0},onCancel:function(t,e,i,o){},onDrag:function(t,e,i,o){t.css(e)},onDragStart:function(e,i,o,s){e.css({height:e.outerHeight(),width:e.outerWidth()}),e.addClass(i.group.options.draggedClass),t("body").addClass(i.group.options.bodyClass)},onDrop:function(e,i,o,s){e.removeClass(i.group.options.draggedClass).removeAttr("style"),t("body").removeClass(i.group.options.bodyClass)},onMousedown:function(t,e,i){if(!i.target.nodeName.match(/^(input|select|textarea)$/i))return i.preventDefault(),!0},placeholderClass:"placeholder",placeholder:'
  • ',pullPlaceholder:!0,serialize:function(e,i,o){var s=t.extend({},e.data());return o?[i]:(i[0]&&(s.children=i),delete s.subContainers,delete s.sortable,s)},tolerance:0},f={},d=0,g={left:0,top:0,bottom:0,right:0},c={start:"touchstart.sortable mousedown.sortable",drop:"touchend.sortable touchcancel.sortable mouseup.sortable",drag:"touchmove.sortable mousemove.sortable",scroll:"scroll.sortable"},m="subContainers";h.get=function(t){return f[t.group]||(t.group===o&&(t.group=d++),f[t.group]=new h(t)),f[t.group]},h.prototype={dragInit:function(e,i){this.$document=t(i.el[0].ownerDocument);var o=t(e.target).closest(this.options.itemSelector);if(o.length){if(this.item=o,this.itemContainer=i,this.item.is(this.options.exclude)||!this.options.onMousedown(this.item,p.onMousedown,e))return;this.setPointer(e),this.toggleListeners("on"),this.setupDelayTimer(),this.dragInitDone=!0}},drag:function(t){if(!this.dragging){if(!this.distanceMet(t)||!this.delayMet)return;this.options.onDragStart(this.item,this.itemContainer,p.onDragStart,t),this.item.before(this.placeholder),this.dragging=!0}this.setPointer(t),this.options.onDrag(this.item,r(this.pointer,this.item.offsetParent()),p.onDrag,t);var e=this.getPointer(t),i=this.sameResultBox,s=this.options.tolerance;(!i||i.top-s>e.top||i.bottom+se.left||i.right+s=this.options.distance},getPointer:function(t){var e=t.originalEvent||t.originalEvent.touches&&t.originalEvent.touches[0];return{left:t.pageX||e.pageX,top:t.pageY||e.pageY}},setupDelayTimer:function(){var t=this;this.delayMet=!this.options.delay,this.delayMet||(clearTimeout(this._mouseDelayTimer),this._mouseDelayTimer=setTimeout(function(){t.delayMet=!0},this.options.delay))},scroll:function(t){this.clearDimensions(),this.clearOffsetParent()},toggleListeners:function(e){var i=this,o=["drag","drop","scroll"];t.each(o,function(t,o){i.$document[e](c[o],i[o+"Proxy"])})},clearOffsetParent:function(){this.offsetParent=o},clearDimensions:function(){this.traverse(function(t){t._clearDimensions()})},traverse:function(t){t(this);for(var e=this.containers.length;e--;)this.containers[e].traverse(t)},_clearDimensions:function(){this.containerDimensions=o},_destroy:function(){f[this.options.group]=o}},l.prototype={dragInit:function(t){var e=this.rootGroup;!this.disabled&&!e.dragInitDone&&this.options.drag&&this.isValidDrag(t)&&e.dragInit(t,this)},isValidDrag:function(t){return 1==t.which||"touchstart"==t.type&&1==t.originalEvent.touches.length},searchValidTarget:function(t,e){var i=a(this.getItemDimensions(),t,e),o=i.length,s=this.rootGroup,n=!s.options.isValidTarget||s.options.isValidTarget(s.item,this);if(!o&&n)return s.movePlaceholder(this,this.target,"append"),!0;for(;o--;){var r=i[o][0],h=i[o][1];if(!h&&this.hasChildGroup(r)){var l=this.getContainerGroup(r).searchValidTarget(t,e);if(l)return!0}else if(n)return this.movePlaceholder(r,t),!0}},movePlaceholder:function(e,i){var o=t(this.items[e]),s=this.itemDimensions[e],n="after",r=o.outerWidth(),a=o.outerHeight(),h=o.offset(),l={left:h.left,right:h.left+r,top:h.top,bottom:h.top+a};if(this.options.vertical){var c=(s[2]+s[3])/2,u=i.top<=c;u?(n="before",l.bottom-=a/2):l.top+=a/2}else{var p=(s[0]+s[1])/2,f=i.left<=p;f?(n="before",l.right-=r/2):l.left+=r/2}this.hasChildGroup(e)&&(l=g),this.rootGroup.movePlaceholder(this,o,n,l)},getItemDimensions:function(){return this.itemDimensions||(this.items=this.$getChildren(this.el,"item").filter(":not(."+this.group.options.placeholderClass+", ."+this.group.options.draggedClass+")").get(),n(this.items,this.itemDimensions=[],this.options.tolerance)),this.itemDimensions},getItemOffsetParent:function(){var t,e=this.el;return t="relative"===e.css("position")||"absolute"===e.css("position")||"fixed"===e.css("position")?e:e.offsetParent()},hasChildGroup:function(t){return this.options.nested&&this.getContainerGroup(t)},getContainerGroup:function(e){var s=t.data(this.items[e],m);if(s===o){var n=this.$getChildren(this.items[e],"container");if(s=!1,n[0]){var r=t.extend({},this.options,{rootGroup:this.rootGroup,group:d++});s=n[i](r).data(i).group}t.data(this.items[e],m,s)}return s},$getChildren:function(e,i){var o=this.rootGroup.options,s=o[i+"Path"],n=o[i+"Selector"];return e=t(e),s&&(e=e.find(s)),e.children(n)},_serialize:function(e,i){var o=this,s=i?"item":"container",n=this.$getChildren(e,s).not(this.options.exclude).map(function(){return o._serialize(t(this),!i)}).get();return this.rootGroup.options.serialize(e,n,i)},traverse:function(e){t.each(this.items||[],function(i){var o=t.data(this,m);o&&o.traverse(e)}),e(this)},_clearDimensions:function(){this.itemDimensions=o},_destroy:function(){var e=this;this.target.off(c.start,this.handle),this.el.removeData(i),this.options.drop&&(this.group.containers=t.grep(this.group.containers,function(t){return t!=e})),t.each(this.items||[],function(){t.removeData(this,m)})}};var v={enable:function(){this.traverse(function(t){t.disabled=!1})},disable:function(){this.traverse(function(t){t.disabled=!0})},serialize:function(){return this._serialize(this.el,!0)},refresh:function(){this.traverse(function(t){t._clearDimensions()})},destroy:function(){this.traverse(function(t){t._destroy()})}};t.extend(l.prototype,v),t.fn[i]=function(e){var s=Array.prototype.slice.call(arguments,1);return this.map(function(){var n=t(this),r=n.data(i);return r&&v[e]?v[e].apply(r,s)||this:(r||e!==o&&"object"!=typeof e||n.data(i,new l(n,e)),this)})}}(jQuery,window,"sortable"); \ No newline at end of file diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 0c5c00f..c30587c 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -22,6 +22,7 @@ use unclead\multipleinput\MultipleInput; use unclead\multipleinput\TabularInput; use unclead\multipleinput\assets\MultipleInputAsset; +use unclead\multipleinput\assets\MultipleInputSortableAsset; use unclead\multipleinput\components\BaseColumn; /** @@ -119,6 +120,12 @@ abstract class BaseRenderer extends Object implements RendererInterface */ public $form; + /** + * @var bool allow sorting. + * @internal this property is used when need to allow sorting rows. + */ + public $sortable = false; + /** * @inheritdoc */ @@ -299,6 +306,12 @@ protected function registerAssets() ]); $js = "jQuery('#{$this->id}').multipleInput($options);"; + + if($this->sortable) { + MultipleInputSortableAsset::register($view); + $js .= "$('#{$this->id} > table').sortable({containerSelector: 'table', itemPath: '> tbody', itemSelector: 'tr', placeholder: ''});"; + } + $view->registerJs($js); } From cb71311a3b8279bfe3c28dfedcbe1032800c9b3e Mon Sep 17 00:00:00 2001 From: Nikolya Date: Tue, 7 Mar 2017 00:13:47 +0200 Subject: [PATCH 052/247] Remove row FIX + hasProperty --- src/MultipleInput.php | 14 +++++++++++++- src/assets/src/css/sorting.css | 19 ++++++++++++++----- src/assets/src/css/sorting.min.css | 2 +- src/components/BaseColumn.php | 12 ++++++++++++ src/renderers/BaseRenderer.php | 2 +- 5 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 0abcde4..2ba70f8 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -161,7 +161,7 @@ protected function initData() } if ($this->model instanceof Model) { - $data = $this->model->hasAttribute($this->attribute) + $data = ($this->model->hasProperty($this->attribute) || isset($this->model->{$this->attribute})) ? ArrayHelper::getValue($this->model, $this->attribute, []) : []; @@ -213,6 +213,18 @@ public function run() */ private function createRenderer() { + if($this->sortable) { + $drag = [ + 'name' => 'drag', + 'type' => MultipleInputColumn::TYPE_DRAGCOLUMN, + 'headerOptions' => [ + 'style' => 'width: 20px;', + ] + ]; + + array_unshift($this->columns, $drag); + } + $config = [ 'id' => $this->getId(), 'columns' => $this->columns, diff --git a/src/assets/src/css/sorting.css b/src/assets/src/css/sorting.css index e496690..f4b3b20 100644 --- a/src/assets/src/css/sorting.css +++ b/src/assets/src/css/sorting.css @@ -1,14 +1,23 @@ -.multiple-input-list__item, +.drag-handle, .dragging, .dragging * { cursor: move !important; } .dragged { - position: absolute - top: 0 - opacity: .5 - z-index: 2000 + position: absolute; + top: 0; + opacity: .5; + z-index: 2000; +} + +.drag-handle { + opacity: .5; + padding: 7.5px; +} + +.drag-handle:hover { + opacity: 1; } tr.placeholder { diff --git a/src/assets/src/css/sorting.min.css b/src/assets/src/css/sorting.min.css index ea7f145..695dc55 100644 --- a/src/assets/src/css/sorting.min.css +++ b/src/assets/src/css/sorting.min.css @@ -1 +1 @@ -.dragging,.dragging *,.multiple-input-list__item{cursor:move!important}.dragged{position:absolute top: 0 opacity: .5 z-index: 2000}tr.placeholder{display:block;background:red;position:relative;margin:0;padding:0;border:none}tr.placeholder:before{content:"";position:absolute;width:0;height:0;border:5px solid transparent;border-left-color:red;margin-top:-5px;left:-5px;border-right:none} \ No newline at end of file +.drag-handle,.dragging,.dragging *{cursor:move!important}.dragged{position:absolute;top:0;opacity:.5;z-index:2000}.drag-handle{opacity:.5;padding:7.5px}.drag-handle:hover{opacity:1}tr.placeholder{display:block;background:red;position:relative;margin:0;padding:0;border:none}tr.placeholder:before{content:"";position:absolute;width:0;height:0;border:5px solid transparent;border-left-color:red;margin-top:-5px;left:-5px;border-right:none} \ No newline at end of file diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index f4a1204..66e8a63 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -34,6 +34,7 @@ abstract class BaseColumn extends Object const TYPE_STATIC = 'static'; const TYPE_CHECKBOX = 'checkbox'; const TYPE_RADIO = 'radio'; + const TYPE_DRAGCOLUMN = 'dragColumn'; /** * @var string input name @@ -448,6 +449,17 @@ protected function renderStatic($name, $value, $options) return Html::tag('p', $value, ['class' => 'form-control-static']); } + /** + * @param $name + * @param $value + * @param $options + * @return string + */ + protected function renderDragColumn($name, $options) + { + return Html::tag('span', $value, ['class' => 'glyphicon glyphicon-menu-hamburger drag-handle']); + } + /** * Renders an input. * diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index c30587c..7b802ec 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -309,7 +309,7 @@ protected function registerAssets() if($this->sortable) { MultipleInputSortableAsset::register($view); - $js .= "$('#{$this->id} > table').sortable({containerSelector: 'table', itemPath: '> tbody', itemSelector: 'tr', placeholder: ''});"; + $js .= "$('#{$this->id} table').sortable({containerSelector: 'table', itemPath: '> tbody', itemSelector: 'tr', placeholder: '', handle:'.drag-handle'});"; } $view->registerJs($js); From 0e9ced72cc73888179711be743d0756b5586cb93 Mon Sep 17 00:00:00 2001 From: Nikolya Date: Tue, 7 Mar 2017 00:17:31 +0200 Subject: [PATCH 053/247] changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 523b2e8..ef81da5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Yii2 multiple input change log ============================== +2.5.1 +===== +- Implemented `Sortting` +- fixed if attribute is set and hasProperty return false + 2.5.0 ===== From 1a0ea0aa90addec948d9c4053625700c01cf131d Mon Sep 17 00:00:00 2001 From: Nikolya Date: Tue, 7 Mar 2017 00:22:42 +0200 Subject: [PATCH 054/247] docs usage add 'sortable' option --- docs/usage.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/usage.md b/docs/usage.md index 1d5d887..48316a0 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -24,7 +24,8 @@ use unclead\multipleinput\MultipleInput; 'allowEmptyList' => false, 'enableGuessTitle' => true, 'min' => 2, // should be at least 2 rows - 'addButtonPosition' => MultipleInput::POS_HEADER // show add button in the header + 'addButtonPosition' => MultipleInput::POS_HEADER, // show add button in the header + 'sortable' => true, // optional. user can change order of rows ]) ->label(false); ?> From 619f77035ce5ae32bd5dc913c81010a0b7280f85 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Tue, 7 Mar 2017 13:02:03 +0600 Subject: [PATCH 055/247] prepare to release 2.6.0 --- CHANGELOG.md | 7 ++++--- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef81da5..4505426 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,11 @@ Yii2 multiple input change log ============================== -2.5.1 +2.6.0 ===== -- Implemented `Sortting` -- fixed if attribute is set and hasProperty return false + +- PR#132: Implemented `Sortting` (sankam-nikolya) +- PR#132: fixed if attribute is set and hasProperty return false (sankam-nikolya) 2.5.0 ===== diff --git a/README.md b/README.md index 0d7369d..5d4f187 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v2.5.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.6.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index b293cea..1998a8d 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.5.0", + "version": "2.6.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 3d89e38..d8ea419 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.5.0", + "version": "2.6.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 5c51552bbd1974654f4759e7155c0554dd0b3e1d Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Thu, 9 Mar 2017 16:00:32 +0300 Subject: [PATCH 056/247] Fixed assets --- CHANGELOG.md | 5 +++++ README.md | 2 +- composer.json | 2 +- package.json | 2 +- src/assets/MultipleInputAsset.php | 18 ++++++++++++++---- src/assets/MultipleInputSortableAsset.php | 20 ++++++++++++++------ 6 files changed, 36 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4505426..967e2cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Yii2 multiple input change log ============================== +2.6.1 +===== + +- Fixed assets + 2.6.0 ===== diff --git a/README.md b/README.md index 5d4f187..8a4fbae 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v2.6.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.6.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 1998a8d..4d99732 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.6.0", + "version": "2.6.1", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index d8ea419..485619c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.6.0", + "version": "2.6.1", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" diff --git a/src/assets/MultipleInputAsset.php b/src/assets/MultipleInputAsset.php index 4c1dded..b4bed86 100644 --- a/src/assets/MultipleInputAsset.php +++ b/src/assets/MultipleInputAsset.php @@ -16,10 +16,14 @@ */ class MultipleInputAsset extends AssetBundle { - public $css = [ - YII_DEBUG ? 'css/multiple-input.css' : 'css/multiple-input.min.css' - ]; - + /** + * @var array + */ + public $css = []; + + /** + * @var array + */ public $js = []; public $depends = [ @@ -29,9 +33,15 @@ class MultipleInputAsset extends AssetBundle public function init() { $this->sourcePath = __DIR__ . '/src/'; + $this->js = [ YII_DEBUG ? 'js/jquery.multipleInput.js' : 'js/jquery.multipleInput.min.js' ]; + + $this->css = [ + YII_DEBUG ? 'css/multiple-input.css' : 'css/multiple-input.min.css' + ]; + parent::init(); } diff --git a/src/assets/MultipleInputSortableAsset.php b/src/assets/MultipleInputSortableAsset.php index 2cd1378..ce51328 100644 --- a/src/assets/MultipleInputSortableAsset.php +++ b/src/assets/MultipleInputSortableAsset.php @@ -18,16 +18,24 @@ class MultipleInputSortableAsset extends AssetBundle { public $sourcePath = __DIR__ . '/src/'; - public $css = [ - YII_DEBUG ? 'css/sorting.css' : 'css/sorting.min.css' - ]; + public $css = []; - public $js = [ - YII_DEBUG ? 'js/jquery-sortable.js' : 'js/jquery-sortable.min.js' - ]; + public $js = []; public $depends = [ 'unclead\multipleinput\assets\MultipleInputAsset', ]; + public function init() + { + $this->js = [ + YII_DEBUG ? 'js/jquery-sortable.js' : 'js/jquery-sortable.min.js' + ]; + + $this->css = [ + YII_DEBUG ? 'css/sorting.css' : 'css/sorting.min.css' + ]; + + parent::init(); + } } \ No newline at end of file From e4e94df24c902f5f33b3a0edde222b28063dcc24 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 19 Mar 2017 09:41:36 +0300 Subject: [PATCH 057/247] Fixed an incorrect behavior of widget in case of ajax loading (e.g. in modal window) --- CHANGELOG.md | 5 ++ examples/views/multiple-input.php | 53 ++++++++-------- src/assets/src/js/jquery.multipleInput.js | 13 +++- src/assets/src/js/jquery.multipleInput.min.js | 2 +- src/components/BaseColumn.php | 34 ++++++----- src/renderers/BaseRenderer.php | 61 +++++++++++-------- 6 files changed, 102 insertions(+), 66 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 967e2cb..6c62e0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Yii2 multiple input change log ============================== +2.7.0 +===== + +- Fixed an incorrect behavior of widget in case of ajax loading (e.g. in modal window) + 2.6.1 ===== diff --git a/examples/views/multiple-input.php b/examples/views/multiple-input.php index 1a32baf..9c34b12 100644 --- a/examples/views/multiple-input.php +++ b/examples/views/multiple-input.php @@ -5,6 +5,7 @@ use unclead\multipleinput\examples\models\ExampleModel; use yii\helpers\Html; use unclead\multipleinput\MultipleInputColumn; +use yii\widgets\MaskedInput; // Note: You have to install https://github.com/kartik-v/yii2-widget-datepicker for correct work an example use kartik\date\DatePicker; @@ -23,25 +24,25 @@

    Single column

    field($model, 'emails')->widget(MultipleInput::className(), [ - 'max' => 6, - 'allowEmptyList' => false, - 'columns' => [ - [ - 'name' => 'emails', - 'options' => [ - 'placeholder' => 'E-mail' - ] - ] - ], - 'min' => 2, // should be at least 2 rows - 'addButtonPosition' => [ - MultipleInput::POS_HEADER, - MultipleInput::POS_FOOTER, - MultipleInput::POS_ROW - ] - ]) - ->label(false); +// echo $form->field($model, 'emails')->widget(MultipleInput::className(), [ +// 'max' => 6, +// 'allowEmptyList' => false, +// 'columns' => [ +// [ +// 'name' => 'emails', +// 'options' => [ +// 'placeholder' => 'E-mail' +// ] +// ] +// ], +// 'min' => 2, // should be at least 2 rows +// 'addButtonPosition' => [ +// MultipleInput::POS_HEADER, +// MultipleInput::POS_FOOTER, +// MultipleInput::POS_ROW +// ] +// ]) +// ->label(false); ?>

    Multiple columns

    @@ -88,7 +89,7 @@ ], [ 'name' => 'day', - 'type' => DatePicker::className(), + 'type' => MaskedInput::className(), 'title' => 'Day', 'value' => function($data) { return $data['day']; @@ -98,11 +99,15 @@ '1' => 'Monday' ], 'options' => [ - 'pluginOptions' => [ - 'format' => 'dd.mm.yyyy', - 'todayHighlight' => true - ] + 'mask' => '99.99.9999' + ], +// 'options' => [ +// 'pluginOptions' => [ +// 'format' => 'dd.mm.yyyy', +// 'todayHighlight' => true +// ] +// ], 'headerOptions' => [ 'style' => 'width: 250px;', 'class' => 'day-css-class' diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 7670e5d..c512252 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -61,9 +61,13 @@ */ template: null, /** - * string that collect js templates of widgets which uses in the columns + * array that collect js templates of widgets which uses in the columns */ jsTemplates: [], + /** + * array of scripts which need to execute before initialization + */ + jsInit: [], /** * how many row are allowed to render */ @@ -96,12 +100,17 @@ form = $wrapper.closest('form'), inputId = settings.inputId; + for (i in settings.jsInit) { + var script = document.createElement("script"); + script.innerHTML = settings.jsInit[i]; + document.body.appendChild(script); + } + $wrapper.data('multipleInput', { settings: settings, currentIndex: 0 }); - $wrapper.on('click.multipleInput', '.js-input-remove', function (e) { e.stopPropagation(); removeInput($(this)); diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index b042a55..0ff65e3 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return l[e]?l[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):l.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,l={init:function(l){if("object"!=typeof l)return void console.error("Options must be an object");var u=t.extend(!0,{},i,l||{}),p=t("#"+u.id),d=p.closest("form"),c=u.inputId;p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),r(t(this))});var f=0,v=t.Event(e.afterInit),m=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(m),p.trigger(v)}else f++;(0===d.length||f>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(m),p.trigger(v))},100)},add:function(e){r(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},r=function(i,l){var r=t(i).closest(".multiple-input").first(),a=r.data("multipleInput"),u=a.settings,d=u.template,c=r.children(".multiple-input-list").first();if(!(null!=u.max&&s(r)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),l instanceof Object){var f=[];for(var v in l)l.hasOwnProperty(v)&&f.push(l[v]);l=f}var m;for(var h in u.jsTemplates)m=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(m);var g=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,r=p(e),a=t("#"+r);if(l){var u=l[g];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),g++}),r.data("multipleInput").currentIndex++;var I=t.Event(e.afterAddRow);r.trigger(I)}},a=function(i){var l=i.closest(".multiple-input").first(),r=i.closest(".multiple-input-list__item"),a=l.data("multipleInput"),o=a.settings;if(s(l)>o.min){var p=t.Event(e.beforeDeleteRow);if(l.trigger(p,[r]),p.result===!1)return;n&&r.find("input, select, textarea").each(function(){u(t(this))}),r.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),l.trigger(p,[r])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!=l.length&&"undefined"==typeof r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),o={},u=i.replace(/-\d-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),r.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),d=p.closest("form"),c=u.inputId;for(m in u.jsInit){var f=document.createElement("script");f.innerHTML=u.jsInit[m],document.body.appendChild(f)}p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))});var m=0,v=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(h),p.trigger(v)}else m++;(0===d.length||m>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(h),p.trigger(v))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,c=l.children(".multiple-input-list").first();if(!(null!=u.max&&s(l)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),r instanceof Object){var f=[];for(var m in r)r.hasOwnProperty(m)&&f.push(r[m]);r=f}var v;for(var h in u.jsTemplates)v=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(v);var I=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,l=p(e),a=t("#"+l);if(r){var u=r[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),I++}),l.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);l.trigger(g)}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(){u(t(this))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!=r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 66e8a63..92b6f2b 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -439,9 +439,11 @@ protected function renderCheckboxList($name, $value, $options) } /** - * @param $name - * @param $value - * @param $options + * Renders a text. + * + * @param string $name the name of input + * @param mixed $value the value of input + * @param array $options the HTMl options of input * @return string */ protected function renderStatic($name, $value, $options) @@ -450,22 +452,24 @@ protected function renderStatic($name, $value, $options) } /** - * @param $name - * @param $value - * @param $options + * Renders a drag&drop column. + * + * @param string $name the name of input + * @param mixed $value the value of input + * @param array $options the HTMl options of input * @return string */ - protected function renderDragColumn($name, $options) + protected function renderDragColumn($name, $value, $options) { - return Html::tag('span', $value, ['class' => 'glyphicon glyphicon-menu-hamburger drag-handle']); + return Html::tag('span', null, ['class' => 'glyphicon glyphicon-menu-hamburger drag-handle']); } /** * Renders an input. * - * @param $name - * @param $value - * @param $options + * @param string $name the name of input + * @param mixed $value the value of input + * @param array $options the HTMl options of input * @return string * @throws InvalidConfigException */ @@ -488,10 +492,10 @@ protected function renderDefault($name, $value, $options) /** * Renders a widget. * - * @param $type - * @param $name - * @param $value - * @param $options + * @param string $type + * @param string $name the name of input + * @param mixed $value the value of input + * @param array $options the HTMl options of input * @return mixed */ protected function renderWidget($type, $name, $value, $options) diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 7b802ec..fef1747 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -250,42 +250,46 @@ protected function initColumns() public function render() { $this->initColumns(); - $content = $this->internalRender(); - $this->registerAssets(); - return $content; - } - - /** - * @return mixed - * @throws NotSupportedException - */ - abstract protected function internalRender(); - /** - * Register script. - * - * @throws \yii\base\InvalidParamException - */ - protected function registerAssets() - { $view = $this->context->getView(); MultipleInputAsset::register($view); - $view = $this->context->getView(); - + // Collect all js scripts which were added before rendering of our widget $jsBefore= []; - if (is_array($view->js) && array_key_exists(View::POS_READY, $view->js)) { - foreach ($view->js[View::POS_READY] as $key => $js) { - $jsBefore[$key] = $js; + if (is_array($view->js)) { + foreach ($view->js as $position => $scripts) { + foreach ($scripts as $key => $js) { + if (!isset($jsBefore[$position])) { + $jsBefore[$position] = []; + } + $jsBefore[$position][$key] = $js; + } + } + } + + $content = $this->internalRender(); + + // Collect all js scripts which has to be appended to page before initialization widget + $jsInit = []; + if (is_array($view->js)) { + foreach ($view->js as $position => $scripts) { + foreach ($scripts as $key => $js) { + if (isset($jsBefore[$position][$key])) { + continue; + } + $jsInit[$key] = $js; + $jsBefore[$position][$key] = $js; + unset($view->js[$position][$key]); + } } } $template = $this->prepareTemplate(); $jsTemplates = []; - if (is_array($view->js) && array_key_exists(View::POS_READY, $view->js)) { + if (is_array($view->js) && isset($view->js[View::POS_READY])) { foreach ($view->js[View::POS_READY] as $key => $js) { - if (array_key_exists($key, $jsBefore)) { + if (isset($jsBefore[View::POS_READY][$key])) { continue; } @@ -298,6 +302,7 @@ protected function registerAssets() 'id' => $this->id, 'inputId' => $this->context->options['id'], 'template' => $template, + 'jsInit' => $jsInit, 'jsTemplates' => $jsTemplates, 'max' => $this->max, 'min' => $this->min, @@ -313,8 +318,16 @@ protected function registerAssets() } $view->registerJs($js); + + return $content; } + /** + * @return mixed + * @throws NotSupportedException + */ + abstract protected function internalRender(); + /** * @return string */ From 5c4d4d195e4d55debe2730805c3d8c9326398844 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 19 Mar 2017 09:43:08 +0300 Subject: [PATCH 058/247] prepare to 2.7.0 --- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8a4fbae..4449590 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ##Latest release -The latest stable version of the extension is v2.6.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.7.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ##Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 4d99732..29a992e 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.6.1", + "version": "2.7.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 485619c..632e749 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.6.1", + "version": "2.7.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 10ccac5f7161b666e5d9a93bd10ac333b3fcbf57 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 19 Mar 2017 15:56:37 +0300 Subject: [PATCH 059/247] update readme --- README.md | 21 +- docs/README.md | 7 - docs/configuration.md | 109 --------- docs/javascript.md | 81 ------ docs/multiple_input_embedded.md | 0 docs/multiple_input_multiple.md | 119 --------- docs/multiple_input_single.md | 77 ------ docs/renderers.md | 28 --- docs/tabular_input.md | 167 ------------- docs/tips.md | 231 ------------------ docs/usage.md | 163 ------------ {docs => resources}/images/list-renderer.jpg | Bin .../images/multiple-column.gif | Bin {docs => resources}/images/single-column.gif | Bin {docs => resources}/images/table-renderer.jpg | Bin 15 files changed, 8 insertions(+), 995 deletions(-) delete mode 100644 docs/README.md delete mode 100644 docs/configuration.md delete mode 100644 docs/javascript.md delete mode 100644 docs/multiple_input_embedded.md delete mode 100644 docs/multiple_input_multiple.md delete mode 100644 docs/multiple_input_single.md delete mode 100644 docs/renderers.md delete mode 100644 docs/tabular_input.md delete mode 100644 docs/tips.md delete mode 100644 docs/usage.md rename {docs => resources}/images/list-renderer.jpg (100%) rename {docs => resources}/images/multiple-column.gif (100%) rename {docs => resources}/images/single-column.gif (100%) rename {docs => resources}/images/table-renderer.jpg (100%) diff --git a/README.md b/README.md index 4449590..32045c8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#Yii2 Multiple input widget. +# Yii2 Multiple input widget. Yii2 widget for handle multiple inputs for an attribute of model and tabular input for batch of models. [![Latest Stable Version](https://poser.pugx.org/unclead/yii2-multiple-input/v/stable)](https://packagist.org/packages/unclead/yii2-multiple-input) @@ -7,10 +7,10 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![Latest Unstable Version](https://poser.pugx.org/unclead/yii2-multiple-input/v/unstable)](https://packagist.org/packages/unclead/yii2-multiple-input) [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) -##Latest release +## Latest release The latest stable version of the extension is v2.7.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions -##Installation +## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). Either run @@ -27,9 +27,9 @@ or add to the require section of your `composer.json` file. -##Basic usage +## Basic usage -![Single column example](./docs/images/single-column.gif?raw=true) +![Single column example](./resources/images/single-column.gif?raw=true) For example you want to have an ability of entering several emails of user on profile page. In this case you can use yii2-multiple-input widget like in the following code @@ -51,16 +51,11 @@ use unclead\multipleinput\MultipleInput; ?> ``` -You can find more examples of usage [here](./docs/usage.md) +## Documentation -##Documentation +You can find a full version of documentation is available in [wiki](https://github.com/unclead/yii2-multiple-input/wiki/Usage) -- [Configuration](./docs/configuration.md) -- [Usage](./docs/usage.md) -- [Tips and tricks](./docs/tips.md) -- [Javascript Events and Operations](./docs/javascript.md) -- [Renderers](./docs/renderers.md) -##License +## License **yii2-multiple-input** is released under the BSD 3-Clause License. See the bundled [LICENSE.md](./LICENSE.md) for details. diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index a59deb0..0000000 --- a/docs/README.md +++ /dev/null @@ -1,7 +0,0 @@ -#Documentation - -- [Configuration](./configuration.md) -- [Usage](./usage.md) -- [Tips and tricks](./tips.md) -- [Javascript Events and Operations](./javascript.md) -- [Renderers](./renderers.md) diff --git a/docs/configuration.md b/docs/configuration.md deleted file mode 100644 index 22b94cc..0000000 --- a/docs/configuration.md +++ /dev/null @@ -1,109 +0,0 @@ -#Configuration - -Widget support the following options that are additionally recognized over and above the configuration options in the InputWidget. - -##Base options - -**max** *integer*: maximum number of rows. If not set will default to unlimited - -**min** *integer*: minimum number of rows. Set to `0` if you need the empty list in case you don't have any data - -**attributeOptions** *array*: client-side attribute options, e.g. enableAjaxValidation. You may use this property in case when -you use widget without a model, since in this case widget is not able to detect client-side options automatically - -**addButtonPosition** *integer|array*: the position(s) of `add` button. -This can be `MultipleInput::POS_HEADER`, `MultipleInput::POS_ROW` or `MultipleInput:POS_FOOTER` - -**addButtonOptions** *array*: the HTML options for `add` button. Can contains `class` and `label` keys - -**removeButtonOptions** *array*: the HTML options for `add` button. Can contains `class` and `label` keys - -**data** *array*: array of values in case you use widget without model - -**models** *array*: the list of models. Required in case you use `TabularInput` widget - -**allowEmptyList** *boolean*: whether to allow the empty list - -**columnClass** *string*: the name of column class. You can specify your own class to extend base functionality. -Defaults to `unclead\multipleinput\MultipleInputColumn` for `MultipleInput` and `unclead\multipleinput\TabularColumn` for `TabularInput`. - -**rendererClass** *string*: the name of renderer class. You can specify your own class to extend base functionality. -Defaults to `unclead\multipleinput\renderers\TableRenderer`. - -**columns** *array*: the row columns configuration where you can set the properties which is described below - -**rowOptions** *array|\Closure*: the HTML attributes for the table body rows. This can be either an array -specifying the common HTML attributes for all body rows, or an anonymous function that returns an array of the HTML attributes. -It should have the following signature: - -```php -function ($model, $index, $context) -``` - -- `$model`: the current data model being rendered -- `$index`: the zero-based index of the data model in the model array -- `$context`: the widget object - -##Column options - -**name** *string*: input name. *Required options* - -**type** *string*: type of the input. If not set will default to `textInput`. Read more about the types described below - -**title** *string*: the column title - -**value** *Closure*: you can set it to an anonymous function with the following signature: - -```php -function($data) {} -``` - -**defaultValue** *string*: default value of input - -**items** *array*|*Closure*: the items for input with type dropDownList, listBox, checkboxList, radioList -or anonymous function which return array of items and has the following signature: - -```php -function($data) {} -``` - -**options** *array*|*Closure*: the HTML attributes for the input, you can set it as array -or an anonymous function with the following signature: - -```php -function($data) {} -``` - -**headerOptions** *array*: the HTML attributes for the header cell - -**enableError** *boolean*: whether to render inline error for the input. Default to `false` - -**errorOptions** *array*: the HTMl attributes for the error tag - - -##Input types - -Each column in a row can has their own type. Widget supports: - -- all yii2 html input types: - - `textInput` - - `dropDownList` - - `radioList` - - `textarea` - - For more detail look at [Html helper class](http://www.yiiframework.com/doc-2.0/yii-helpers-html.html) -- input widget (widget that extends from `InputWidget` class). For example, `yii\widgets\MaskedInput` -- `static` to output a static HTML content - -For using widget as column input you may use the following code: - -```php -[ - 'name' => 'phone', - 'title' => 'Phone number', - 'type' => \yii\widgets\MaskedInput::className(), - 'options' => [ - 'class' => 'input-phone', - 'mask' => '999-999-99-99' - ] -] -``` diff --git a/docs/javascript.md b/docs/javascript.md deleted file mode 100644 index 86e611f..0000000 --- a/docs/javascript.md +++ /dev/null @@ -1,81 +0,0 @@ -#JavaScript events - -This widget has following events: - - `afterInit`: triggered after initialization - - `afterAddRow`: triggered after new row insertion - - `beforeDeleteRow`: triggered before the row removal - - `afterDeleteRow`: triggered after the row removal - -**Example** - -```js -jQuery('#multiple-input').on('afterInit', function(){ - console.log('calls on after initialization event'); -}).on('beforeAddRow', function(e) { - console.log('calls on before add row event'); -}).on('afterAddRow', function(e) { - console.log('calls on after add row event'); -}).on('beforeDeleteRow', function(e, row){ - // row - HTML container of the current row for removal. - // For TableRenderer it is tr.multiple-input-list__item - console.log('calls on before remove row event.'); - return confirm('Are you sure you want to delete row?') -}).on('afterDeleteRow', function(e, row){ - console.log('calls on after remove row event'); - console.log(row); -}); -``` - -#JavaScript operations - -####add - -Adding new row with specified settings. - -Input arguments: -- *object* - values for inputs, can be filled with '}); -``` - -####remove - -Remove row with specified index. - -Inout arguments: -- *integer* - row number for removing, if not specified then removes last row. - -Example: - -```js -$('#multiple-input').multipleInput('remove', 2); -``` - -####clear - -Remove all rows - -```js -$('#multiple-input').multipleInput('clear'); -``` - -####option - -Get or set a particular option - -Input arguments: -- *string* - a name of an option -- *mixed* - a value of an option (optional). If specified will be used as a new value of an option; - -Example: - -```js -$('#multiple-input').multipleInput('option', 'max'); -$('#multiple-input').multipleInput('option', 'max', 10); - -``` - - diff --git a/docs/multiple_input_embedded.md b/docs/multiple_input_embedded.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/multiple_input_multiple.md b/docs/multiple_input_multiple.md deleted file mode 100644 index a3225a7..0000000 --- a/docs/multiple_input_multiple.md +++ /dev/null @@ -1,119 +0,0 @@ -#Multiple columns example - -![Multiple columns example](./images/multiple-column.gif?raw=true) - -For example you want to have an interface for manage user schedule. For simplicity we will store the schedule in json string. - -In this case you can use `yii2-multiple-input` widget for supporting multiple inputs how to describe below. - -Our test model can looks like as the following snippet - -```php -class ExampleModel extends Model -{ - public $schedule; - - public function init() - { - parent::init(); - - $this->schedule = [ - [ - 'day' => '27.02.2015', - 'user_id' => 1, - 'priority' => 1 - ], - [ - 'day' => '27.02.2015', - 'user_id' => 2, - 'priority' => 2 - ], - ]; - } -``` - -Then we have to use `MultipleInput` widget for rendering form field in the view file - -```php -use yii\bootstrap\ActiveForm; -use unclead\multipleinput\MultipleInput; -use unclead\multipleinput\examples\models\ExampleModel; -use yii\helpers\Html; - -/* @var $this \yii\base\View */ -/* @var $model ExampleModel */ -?> - - true, - 'enableClientValidation' => false, - 'validateOnChange' => false, - 'validateOnSubmit' => true, - 'validateOnBlur' => false, -]);?> - -field($model, 'schedule')->widget(MultipleInput::className(), [ - 'max' => 4, - 'columns' => [ - [ - 'name' => 'user_id', - 'type' => 'dropDownList', - 'title' => 'User', - 'defaultValue' => 1, - 'items' => [ - 1 => 'User 1', - 2 => 'User 2' - ] - ], - [ - 'name' => 'day', - 'type' => \kartik\date\DatePicker::className(), - 'title' => 'Day', - 'value' => function($data) { - return $data['day']; - }, - 'items' => [ - '0' => 'Saturday', - '1' => 'Monday' - ], - 'options' => [ - 'pluginOptions' => [ - 'format' => 'dd.mm.yyyy', - 'todayHighlight' => true - ] - ] - ], - [ - 'name' => 'priority', - 'title' => 'Priority', - 'enableError' => true, - 'options' => [ - 'class' => 'input-priority' - ] - ] - ] - ]); -?> - 'btn btn-success']);?> - -``` - - -For validation the schedule you can use the following code - -```php - - public function validateSchedule($attribute) - { - $requiredValidator = new RequiredValidator(); - - foreach($this->$attribute as $index => $row) { - $error = null; - $requiredValidator->validate($row['priority'], $error); - if (!empty($error)) { - $key = $attribute . '[' . $index . '][priority]'; - $this->addError($key, $error); - } - } - } -``` diff --git a/docs/multiple_input_single.md b/docs/multiple_input_single.md deleted file mode 100644 index 0c86496..0000000 --- a/docs/multiple_input_single.md +++ /dev/null @@ -1,77 +0,0 @@ -#Single column example - -![Single column example](./images/single-column.gif?raw=true) - -For example your application contains the model `User` that has the related model `UserEmail` -You can add virtual attribute `emails` for collect emails from form and then you can save them to database. - -In this case you can use `yii2-multiple-input` widget for supporting multiple inputs how to describe below. - -First of all we have to declare virtual attribute in model - -```php -class ExampleModel extends Model -{ - /** - * @var array virtual attribute for keeping emails - */ - public $emails; -``` - -Then we have to use `MultipleInput` widget for rendering form field in the view file - -```php -use yii\bootstrap\ActiveForm; -use unclead\multipleinput\MultipleInput; -use unclead\multipleinput\examples\models\ExampleModel; -use yii\helpers\Html; - -/* @var $this \yii\base\View */ -/* @var $model ExampleModel */ -?> - - true, - 'enableClientValidation' => false, - 'validateOnChange' => false, - 'validateOnSubmit' => true, - 'validateOnBlur' => false, -]);?> - -field($model, 'emails')->widget(MultipleInput::className(), [ - 'max' => 4, - ]); -?> - 'btn btn-success']);?> - -``` - -Options `max` means that user able to input only 4 emails - -For validation emails you can use the following code - -```php - /** - * Email validation. - * - * @param $attribute - */ - public function validateEmails($attribute) - { - $items = $this->$attribute; - - if (!is_array($items)) { - $items = []; - } - - foreach ($items as $index => $item) { - $validator = new EmailValidator(); - $error = null; - $validator->validate($item, $error); - if (!empty($error)) { - $key = $attribute . '[' . $index . ']'; - $this->addError($key, $error); - } - } - } -``` diff --git a/docs/renderers.md b/docs/renderers.md deleted file mode 100644 index a7cc7ce..0000000 --- a/docs/renderers.md +++ /dev/null @@ -1,28 +0,0 @@ -##Renderers - -Currently widget supports two type of renderers - -###TableRenderer -![Table renderer](./images/table-renderer.jpg?raw=true) - -This renderer is enabled by default. - -###ListRenderer -![List renderer](./images/list-renderer.jpg?raw=true) - -To enable this renderer you have to use an option `rendererClass` -```php -field($model, 'schedule')->widget(MultipleInput::className(), [ - 'rendererClass' => \unclead\multipleinput\renderers\ListRenderer::className(), - 'max' => 4, - 'allowEmptyList' => true, - 'rowOptions' => function($model) { - $options = []; - - if ($model['priority'] > 1) { - $options['class'] = 'danger'; - } - return $options; - }, -``` \ No newline at end of file diff --git a/docs/tabular_input.md b/docs/tabular_input.md deleted file mode 100644 index 10ea779..0000000 --- a/docs/tabular_input.md +++ /dev/null @@ -1,167 +0,0 @@ -# Tabular input - -For example you want to have an interface for manage some abstract items via tabular input. - -In this case you can use `yii2-multiple-input` widget for supporting tabular input how to describe below. - -Our test model can looks like as the following snippet - -```php -namespace unclead\multipleinput\examples\models; - -use Yii; -use yii\base\Model; -// you have to install https://github.com/vova07/yii2-fileapi-widget -use vova07\fileapi\behaviors\UploadBehavior; - -/** - * Class Item - * @package unclead\multipleinput\examples\models - */ -class Item extends Model -{ - public $title; - public $description; - public $file; - public $date; - - public function behaviors() - { - return [ - 'uploadBehavior' => [ - 'class' => UploadBehavior::className(), - 'attributes' => [ - 'file' => [ - 'path' => Yii::getAlias('@webroot') . '/images/', - 'tempPath' => Yii::getAlias('@webroot') . '/images/tmp/', - 'url' => '/images/' - ], - ] - ] - ]; - } - - public function rules() - { - return [ - [['title', 'description'], 'required'], - ['file', 'safe'] - ]; - } -} -``` - -Then we have to use `TabularInput` widget for rendering form field in the view file - -```php - - - 'tabular-form', - 'enableAjaxValidation' => true, - 'enableClientValidation' => false, - 'validateOnChange' => false, - 'validateOnSubmit' => true, - 'validateOnBlur' => false, - 'options' => [ - 'enctype' => 'multipart/form-data' - ] -]) ?> - - $models, - 'attributeOptions' => [ - 'enableAjaxValidation' => true, - 'enableClientValidation' => false, - 'validateOnChange' => false, - 'validateOnSubmit' => true, - 'validateOnBlur' => false, - ], - 'columns' => [ - [ - 'name' => 'title', - 'title' => 'Title', - 'type' => \unclead\multipleinput\MultipleInputColumn::TYPE_TEXT_INPUT, - ], - [ - 'name' => 'description', - 'title' => 'Description', - ], - [ - 'name' => 'file', - 'title' => 'File', - 'type' => \vova07\fileapi\Widget::className(), - 'options' => [ - 'settings' => [ - 'url' => ['site/fileapi-upload'] - ] - ] - ], - [ - 'name' => 'date', - 'type' => \kartik\date\DatePicker::className(), - 'title' => 'Day', - 'options' => [ - 'pluginOptions' => [ - 'format' => 'dd.mm.yyyy', - 'todayHighlight' => true - ] - ], - 'headerOptions' => [ - 'style' => 'width: 250px;', - 'class' => 'day-css-class' - ] - ], - ], -]) ?> - - - 'btn btn-success']);?> - -``` - - -Your action can looks like the following code - -```php -/** - * Class TabularInputAction - * @package unclead\multipleinput\examples\actions - */ -class TabularInputAction extends Action -{ - public function run() - { - Yii::setAlias('@unclead-examples', realpath(__DIR__ . '/../')); - - $models = [new Item()]; - $request = Yii::$app->getRequest(); - if ($request->isPost && $request->post('ajax') !== null) { - $data = Yii::$app->request->post('Item', []); - foreach (array_keys($data) as $index) { - $models[$index] = new Item(); - } - Model::loadMultiple($models, Yii::$app->request->post()); - Yii::$app->response->format = Response::FORMAT_JSON; - $result = ActiveForm::validateMultiple($models); - return $result; - } - - if (Model::loadMultiple($models, Yii::$app->request->post())) { - // your magic - } - - - return $this->controller->render('@unclead-examples/views/tabular-input.php', ['models' => $models]); - } -} -``` diff --git a/docs/tips.md b/docs/tips.md deleted file mode 100644 index 88f4f77..0000000 --- a/docs/tips.md +++ /dev/null @@ -1,231 +0,0 @@ -#Tips and tricks - - - [How to customize buttons](#how-to-customize-buttons) - - [Work with empty list](#work-with-empty-list) - - [Guess column title](#guess-column-title) - - [Ajax loading of a widget](#ajax-loading) - - [Use of a widget's placeholder](#using-placeholder) - - [Custom index of the row](#custom-index) - - [Embedded MultipleInput widget](#embedded) - - [Client validation](#client-validation) - -##How to customize buttons - -You can customize `add` and `remove` buttons via `addButtonOptions` and `removeButtonOptions`. Here is the simple example -how you can use those options: - -```php - - echo $form->field($model, 'emails')->widget(MultipleInput::className(), [ - 'max' => 5, - 'addButtonOptions' => [ - 'class' => 'btn btn-success', - 'label' => 'add' // also you can use html code - ], - 'removeButtonOptions' => [ - 'label' => 'remove' - ] - ]) - ->label(false); - -``` - -##Work with an empty list - -In some cases you need to have the ability to delete all rows in the list. For this purpose you can use option `allowEmptyList` like in the example below: - -```php - - echo $form->field($model, 'emails')->widget(MultipleInput::className(), [ - 'max' => 5, - 'allowEmptyList' => true - ]) - ->label(false); - -``` - -Also you can set `0` in `min` option if you don't need first blank row when data is empty. - -##Guess column title - -Sometimes you can use the widget without defining columns but you want to have the column header of the table. -In this case you can use `enableGuessTitle` option like in the example below: - -```php - - echo $form->field($model, 'emails')->widget(MultipleInput::className(), [ - 'max' => 5, - 'allowEmptyList' => true, - 'enableGuessTitle' => true - ]) - ->label(false); - -``` - -##Ajax loading of a widget - -Assume you want to load a widget via ajax and then show it inside modal window. In this case you MUST: - -- Ensure that you specified ID of widget otherwise the widget will get random ID and it can be the same as id of others elements on the page. -- Ensure that you use the widget inside ActiveForm because it works incorrectly in this case. - -You can fina an example of usage in a discussion of [issue](https://github.com/unclead/yii2-multiple-input/issues/58) - -##Use of a widget's placeholder - -You can use a placeholder `{multiple_index}` in a widget configuration, e.g. for implementation of dependent drop down lists. - -```php - field($model, 'field')->widget(MultipleInput::className(), [ - 'id' => 'my_id', - 'allowEmptyList' => false, - 'rowOptions' => [ - 'id' => 'row{multiple_index_my_id}', - ], - 'columns' => [ - [ - 'name' => 'category', - 'type' => 'dropDownList', - 'title' => 'Category', - 'defaultValue' => '1', - 'items' => [ - '1' => 'Test 1', - '2' => 'Test 2', - '3' => 'Test 3', - '4' => 'Test 4', - ], - 'options' => [ - 'onchange' => <<< JS -$.post("list?id=" + $(this).val(), function(data){ - console.log(data); - $("select#subcat-{multiple_index_my_id}").html(data); -}); -JS - ], - ], - [ - 'name' => 'subcategory', - 'type' => 'dropDownList', - 'title' => 'Subcategory', - 'items' => [], - 'options'=> [ - 'id' => 'subcat-{multiple_index_my_id}' - ], - ], - ] - ]); - ?> -``` - -**Important** Ensure that you added ID of widget to a base placeholder `multiple_index` - -##Custom index of the row - -Assume that you want to set specific index for each row. In this case you can pass the `data` attribute as associative array -as in the example below: - -```php - field($model, 'field')->widget(MultipleInput::className(), [ - 'allowEmptyList' => false, - 'data' => [ - 3 => [ - 'day' => '27.02.2015', - 'user_id' => 31, - 'priority' => 1, - 'enable' => 1 - ], - - 'some-key' => [ - 'day' => '27.02.2015', - 'user_id' => 33, - 'priority' => 2, - 'enable' => 0 - ], - ] - - ... - -``` - - -##Embedded MultipleInput widget - -You can use nested `MultipleInput` as in the example below: - -``` -echo MultipleInput::widget([ - 'model' => $model, - 'attribute' => 'questions', - 'attributeOptions' => $commonAttributeOptions, - 'columns' => [ - [ - 'name' => 'question', - 'type' => 'textarea', - ], - [ - 'name' => 'answers', - 'type' => MultipleInput::class, - 'options' => [ - 'attributeOptions' => $commonAttributeOptions, - 'columns' => [ - [ - 'name' => 'right', - 'type' => MultipleInputColumn::TYPE_CHECKBOX - ], - [ - 'name' => 'answer' - ] - ] - ] - ] - ], -]); -``` - -But in this case you have to pass `attributeOptions` to the widget otherwise you will not be able to use ajax or client side validation of data. - -### Client validation - -Apart of ajax validation you can use client validation but in this case you MUST set property `form`. -Also ensure that you set `enableClientValidation` to `true` value in property `attributeOptions`. If you want to use client validation -for particular column you can use `attributeOptions` property for this column. An example of using client validation is listed below: - -```php - $models, - 'form' => $form, - 'attributeOptions' => [ - 'enableAjaxValidation' => true, - 'enableClientValidation' => false, - 'validateOnChange' => false, - 'validateOnSubmit' => true, - 'validateOnBlur' => false, - ], - 'columns' => [ - [ - 'name' => 'id', - 'type' => TabularColumn::TYPE_HIDDEN_INPUT - ], - [ - 'name' => 'title', - 'title' => 'Title', - 'type' => TabularColumn::TYPE_TEXT_INPUT, - 'attributeOptions' => [ - 'enableClientValidation' => true, - 'validateOnChange' => true, - ], - 'enableError' => true - ], - [ - 'name' => 'description', - 'title' => 'Description', - ], - ], -]) ?> - -``` - -In the example above we use client validation for column `title` and ajax validation for column `description`. -As you can noticed we also enabled `validateOnChange` for column `title` thus you can use all client-side options from the `ActiveField` class. - - diff --git a/docs/usage.md b/docs/usage.md deleted file mode 100644 index 48316a0..0000000 --- a/docs/usage.md +++ /dev/null @@ -1,163 +0,0 @@ -##Usage - -> You can find source code of examples [here](./examples/) - -- [One column](#one-column) -- [Multiple columns](#multiple-columns) -- [Tabular input](#tabular-input) - -##One column - -![Single column example](./images/single-column.gif?raw=true) - -For example you want to have an ability of entering several emails of user on profile page. -In this case you can use yii2-multiple-input widget like in the following code - -```php -use unclead\multipleinput\MultipleInput; - -... - -field($model, 'emails')->widget(MultipleInput::className(), [ - 'max' => 6, - 'allowEmptyList' => false, - 'enableGuessTitle' => true, - 'min' => 2, // should be at least 2 rows - 'addButtonPosition' => MultipleInput::POS_HEADER, // show add button in the header - 'sortable' => true, // optional. user can change order of rows - ]) - ->label(false); -?> -``` - -You can find more detail about this use case [here](multiple_input_single.md) - -##Multiple columns - -![Multiple columns example](./images/multiple-column.gif?raw=true) - -For example you keep some data in json format in attribute of model. Imagine that it is an abstract user schedule with keys: user_id, day, priority - -On the edit page you want to be able to manage this schedule and you can you yii2-multiple-input widget like in the following code - -```php -use unclead\multipleinput\MultipleInput; - -... - -field($model, 'schedule')->widget(MultipleInput::className(), [ - 'max' => 4, - 'columns' => [ - [ - 'name' => 'user_id', - 'type' => 'dropDownList', - 'title' => 'User', - 'defaultValue' => 1, - 'items' => [ - 1 => 'User 1', - 2 => 'User 2' - ] - ], - [ - 'name' => 'day', - 'type' => \kartik\date\DatePicker::className(), - 'title' => 'Day', - 'value' => function($data) { - return $data['day']; - }, - 'items' => [ - '0' => 'Saturday', - '1' => 'Monday' - ], - 'options' => [ - 'pluginOptions' => [ - 'format' => 'dd.mm.yyyy', - 'todayHighlight' => true - ] - ], - 'headerOptions' => [ - 'style' => 'width: 250px;', - 'class' => 'day-css-class' - ] - ], - [ - 'name' => 'priority', - 'enableError' => true, - 'title' => 'Priority', - 'options' => [ - 'class' => 'input-priority' - ] - ], - [ - 'name' => 'comment', - 'type' => 'static', - 'value' => function($data) { - return Html::tag('span', 'static content', ['class' => 'label label-info']); - }, - 'headerOptions' => [ - 'style' => 'width: 70px;', - ] - ] - ] - ]); -?> -``` - -You can find more detail about this use case [here](multiple_input_multiple.md) - -##Tabular input - -For example you want to manage some models via tabular input. In this case you can use `TabularInput` widget which is based on `MultipleInput` widget. -Use the following code for this purpose: - -```php - $models, - 'attributeOptions' => [ - 'enableAjaxValidation' => true, - 'enableClientValidation' => false, - 'validateOnChange' => false, - 'validateOnSubmit' => true, - 'validateOnBlur' => false, - ], - 'columns' => [ - [ - 'name' => 'title', - 'title' => 'Title', - 'type' => TabularInputColumn::TYPE_TEXT_INPUT, - ], - [ - 'name' => 'description', - 'title' => 'Description', - ], - [ - 'name' => 'file', - 'title' => 'File', - 'type' => \vova07\fileapi\Widget::className(), - 'options' => [ - 'settings' => [ - 'url' => ['site/fileapi-upload'] - ] - ] - ], - [ - 'name' => 'date', - 'type' => \kartik\date\DatePicker::className(), - 'title' => 'Day', - 'options' => [ - 'pluginOptions' => [ - 'format' => 'dd.mm.yyyy', - 'todayHighlight' => true - ] - ], - 'headerOptions' => [ - 'style' => 'width: 250px;', - 'class' => 'day-css-class' - ] - ], - ], -]) ?> -``` - -You can find more detail about this use case [here](tabular_input.md) \ No newline at end of file diff --git a/docs/images/list-renderer.jpg b/resources/images/list-renderer.jpg similarity index 100% rename from docs/images/list-renderer.jpg rename to resources/images/list-renderer.jpg diff --git a/docs/images/multiple-column.gif b/resources/images/multiple-column.gif similarity index 100% rename from docs/images/multiple-column.gif rename to resources/images/multiple-column.gif diff --git a/docs/images/single-column.gif b/resources/images/single-column.gif similarity index 100% rename from docs/images/single-column.gif rename to resources/images/single-column.gif diff --git a/docs/images/table-renderer.jpg b/resources/images/table-renderer.jpg similarity index 100% rename from docs/images/table-renderer.jpg rename to resources/images/table-renderer.jpg From ba2cc4e7ba9b4cfcf0374b19afabd2f5bfdeed4e Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 19 Mar 2017 15:56:52 +0300 Subject: [PATCH 060/247] fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 32045c8..a0d9068 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ use unclead\multipleinput\MultipleInput; ## Documentation -You can find a full version of documentation is available in [wiki](https://github.com/unclead/yii2-multiple-input/wiki/Usage) +You can find a full version of documentation in [wiki](https://github.com/unclead/yii2-multiple-input/wiki/Usage) ## License From 093aa32ea336737bb834495677a6d39eaa05c5ba Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 19 Mar 2017 15:57:26 +0300 Subject: [PATCH 061/247] fixed a link to wiki --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a0d9068..3a07031 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ use unclead\multipleinput\MultipleInput; ## Documentation -You can find a full version of documentation in [wiki](https://github.com/unclead/yii2-multiple-input/wiki/Usage) +You can find a full version of documentation in [wiki](https://github.com/unclead/yii2-multiple-input/wiki/) ## License From 779c7b58518768235ab28707e7411d883f9eeac3 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Thu, 23 Mar 2017 14:26:20 +0300 Subject: [PATCH 062/247] Fixed assets --- src/assets/MultipleInputAsset.php | 10 ---------- src/assets/MultipleInputSortableAsset.php | 8 ++------ 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/assets/MultipleInputAsset.php b/src/assets/MultipleInputAsset.php index b4bed86..1641a2f 100644 --- a/src/assets/MultipleInputAsset.php +++ b/src/assets/MultipleInputAsset.php @@ -16,16 +16,6 @@ */ class MultipleInputAsset extends AssetBundle { - /** - * @var array - */ - public $css = []; - - /** - * @var array - */ - public $js = []; - public $depends = [ 'yii\web\JqueryAsset' ]; diff --git a/src/assets/MultipleInputSortableAsset.php b/src/assets/MultipleInputSortableAsset.php index ce51328..5d72395 100644 --- a/src/assets/MultipleInputSortableAsset.php +++ b/src/assets/MultipleInputSortableAsset.php @@ -16,18 +16,14 @@ */ class MultipleInputSortableAsset extends AssetBundle { - public $sourcePath = __DIR__ . '/src/'; - - public $css = []; - - public $js = []; - public $depends = [ 'unclead\multipleinput\assets\MultipleInputAsset', ]; public function init() { + $this->sourcePath = __DIR__ . '/src/'; + $this->js = [ YII_DEBUG ? 'js/jquery-sortable.js' : 'js/jquery-sortable.min.js' ]; From ef346e9b2c321a12ae95798391afccd5496aa160 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Thu, 23 Mar 2017 14:28:43 +0300 Subject: [PATCH 063/247] bumped version --- CHANGELOG.md | 5 +++++ README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c62e0c..52f3cc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Yii2 multiple input change log ============================== +2.7.1 +===== + +- Fixed assets + 2.7.0 ===== diff --git a/README.md b/README.md index 3a07031..103c969 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.7.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.7.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 29a992e..8ad03be 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.7.0", + "version": "2.7.1", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 632e749..d5d9798 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.7.0", + "version": "2.7.1", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 8bf82a0ade1175646e1b082ee20e3c9ae122ef4b Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 24 Mar 2017 11:48:43 +0300 Subject: [PATCH 064/247] added option `nameSuffix` to avoid errors related to duplication of id in case when you use several copies of the widget on a page --- CHANGELOG.md | 5 +++++ src/MultipleInputColumn.php | 2 +- src/TabularColumn.php | 2 +- src/components/BaseColumn.php | 6 ++++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52f3cc2..602be99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Yii2 multiple input change log ============================== +2.8.0 +===== + +- #137: added option `nameSuffix` to avoid errors related to duplication of id in case when you use several copies of the widget on a page + 2.7.1 ===== diff --git a/src/MultipleInputColumn.php b/src/MultipleInputColumn.php index 8b4400f..d5dd103 100644 --- a/src/MultipleInputColumn.php +++ b/src/MultipleInputColumn.php @@ -63,7 +63,7 @@ public function getElementName($index, $withPrefix = true) $prefix = $this->context->name; } - return $prefix . $elementName; + return $prefix . $elementName . (empty($this->nameSuffix) ? '' : ('_' . $this->nameSuffix)); } /** diff --git a/src/TabularColumn.php b/src/TabularColumn.php index b36cfca..d797d3b 100644 --- a/src/TabularColumn.php +++ b/src/TabularColumn.php @@ -33,7 +33,7 @@ public function getElementName($index, $withPrefix = true) $elementName = '[' . $index . '][' . $this->name . ']'; $prefix = $withPrefix ? $this->getModel()->formName() : ''; - return $prefix . $elementName; + return $prefix . $elementName . (empty($this->nameSuffix) ? '' : ('_' . $this->nameSuffix)); } /** diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 92b6f2b..35509ff 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -120,6 +120,12 @@ abstract class BaseColumn extends Object * @since 2.1 */ public $attributeOptions = []; + + /** + * @var string the unique prefix for attribute's name to avoid id duplication e.g. in case of using Select2 widget. + * @since 2.8 + */ + public $nameSuffix; /** * @var Model|ActiveRecordInterface|array From 02360684624409db857430aede9e46971a72c1c1 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 24 Mar 2017 11:49:17 +0300 Subject: [PATCH 065/247] bumped version to 2.8.0 --- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 103c969..2319b43 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.7.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.8.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 8ad03be..ec81790 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.7.1", + "version": "2.8.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index d5d9798..c4f4b90 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.7.1", + "version": "2.8.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From f3fb78c43db3e0f6345b46728a6ec1064a765eca Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 25 Mar 2017 12:40:05 +0300 Subject: [PATCH 066/247] Fixed client validation when there are more then 10 rows --- src/assets/src/js/jquery.multipleInput.js | 2 +- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index c512252..b892552 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -356,7 +356,7 @@ // try to find options for embedded attribute at first. // For example the id of new input is example-1-field-0. // We remove last index and check whether attribute with such id exists or not. - var bareId = id.replace(/-\d-([^\d]+)$/, '-$1'); + var bareId = id.replace(/-\d+-([^\d]+)$/, '-$1'); if (data.settings.attributes.hasOwnProperty(bareId)) { attributeOptions = data.settings.attributes[bareId]; } else { diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 0ff65e3..68717ee 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),d=p.closest("form"),c=u.inputId;for(m in u.jsInit){var f=document.createElement("script");f.innerHTML=u.jsInit[m],document.body.appendChild(f)}p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))});var m=0,v=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(h),p.trigger(v)}else m++;(0===d.length||m>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(h),p.trigger(v))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,c=l.children(".multiple-input-list").first();if(!(null!=u.max&&s(l)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),r instanceof Object){var f=[];for(var m in r)r.hasOwnProperty(m)&&f.push(r[m]);r=f}var v;for(var h in u.jsTemplates)v=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(v);var I=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,l=p(e),a=t("#"+l);if(r){var u=r[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),I++}),l.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);l.trigger(g)}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(){u(t(this))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!=r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),d=p.closest("form"),c=u.inputId;for(m in u.jsInit){var f=document.createElement("script");f.innerHTML=u.jsInit[m],document.body.appendChild(f)}p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))});var m=0,v=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(h),p.trigger(v)}else m++;(0===d.length||m>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(h),p.trigger(v))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,c=l.children(".multiple-input-list").first();if(!(null!=u.max&&s(l)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),r instanceof Object){var f=[];for(var m in r)r.hasOwnProperty(m)&&f.push(r[m]);r=f}var v;for(var h in u.jsTemplates)v=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(v);var I=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,l=p(e),a=t("#"+l);if(r){var u=r[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),I++}),l.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);l.trigger(g)}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(){u(t(this))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!=r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file From ffb8ecc9910622da943800bb553374c8dd385941 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 25 Mar 2017 12:41:18 +0300 Subject: [PATCH 067/247] release 2.8.1 --- CHANGELOG.md | 5 +++++ README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 602be99..a419ea9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Yii2 multiple input change log ============================== +2.8.1 +===== + +- Fixed client validation + 2.8.0 ===== diff --git a/README.md b/README.md index 2319b43..5809102 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.8.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.8.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index ec81790..6776add 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.8.0", + "version": "2.8.1", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index c4f4b90..6352460 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.8.0", + "version": "2.8.1", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From e76944f4feb34a534682a92485f9343a6b38158b Mon Sep 17 00:00:00 2001 From: Nikolya Date: Tue, 28 Mar 2017 19:12:47 +0300 Subject: [PATCH 068/247] FIX for conflict with jQuery UI sortable --- src/assets/src/js/jquery-sortable.js | 2 +- src/assets/src/js/jquery-sortable.min.js | 2 +- src/renderers/BaseRenderer.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/assets/src/js/jquery-sortable.js b/src/assets/src/js/jquery-sortable.js index 5e93508..c92fa6c 100644 --- a/src/assets/src/js/jquery-sortable.js +++ b/src/assets/src/js/jquery-sortable.js @@ -690,4 +690,4 @@ }); }; -}(jQuery, window, 'sortable'); +}(jQuery, window, 'sorting'); diff --git a/src/assets/src/js/jquery-sortable.min.js b/src/assets/src/js/jquery-sortable.min.js index 942c62b..c849a74 100644 --- a/src/assets/src/js/jquery-sortable.min.js +++ b/src/assets/src/js/jquery-sortable.min.js @@ -1 +1 @@ -!function(t,e,i,o){function s(t,e){var i=Math.max(0,t[0]-e[0],e[0]-t[1]),o=Math.max(0,t[2]-e[1],e[1]-t[3]);return i+o}function n(e,i,o,s){var n=e.length,r=s?"offset":"position";for(o=o||0;n--;){var a=e[n].el?e[n].el:t(e[n]),h=a[r]();h.left+=parseInt(a.css("margin-left"),10),h.top+=parseInt(a.css("margin-top"),10),i[n]=[h.left-o,h.left+a.outerWidth()+o,h.top-o,h.top+a.outerHeight()+o]}}function r(t,e){var i=e.offset();return{left:t.left-i.left,top:t.top-i.top}}function a(t,e,i){e=[e.left,e.top],i=i&&[i.left,i.top];for(var o,n=t.length,r=[];n--;)o=t[n],r[n]=[n,s(o,e),i&&s(o,i)];return r=r.sort(function(t,e){return e[1]-t[1]||e[2]-t[2]||e[0]-t[0]})}function h(e){this.options=t.extend({},p,e),this.containers=[],this.options.rootGroup||(this.scrollProxy=t.proxy(this.scroll,this),this.dragProxy=t.proxy(this.drag,this),this.dropProxy=t.proxy(this.drop,this),this.placeholder=t(this.options.placeholder),e.isValidTarget||(this.options.isValidTarget=o))}function l(e,i){this.el=e,this.options=t.extend({},u,i),this.group=h.get(this.options),this.rootGroup=this.options.rootGroup||this.group,this.handle=this.rootGroup.options.handle||this.rootGroup.options.itemSelector;var o=this.rootGroup.options.itemPath;this.target=o?this.el.find(o):this.el,this.target.on(c.start,this.handle,t.proxy(this.dragInit,this)),this.options.drop&&this.group.containers.push(this)}var c,u={drag:!0,drop:!0,exclude:"",nested:!0,vertical:!0},p={afterMove:function(t,e,i){},containerPath:"",containerSelector:"ol, ul",distance:0,delay:0,handle:"",itemPath:"",itemSelector:"li",bodyClass:"dragging",draggedClass:"dragged",isValidTarget:function(t,e){return!0},onCancel:function(t,e,i,o){},onDrag:function(t,e,i,o){t.css(e)},onDragStart:function(e,i,o,s){e.css({height:e.outerHeight(),width:e.outerWidth()}),e.addClass(i.group.options.draggedClass),t("body").addClass(i.group.options.bodyClass)},onDrop:function(e,i,o,s){e.removeClass(i.group.options.draggedClass).removeAttr("style"),t("body").removeClass(i.group.options.bodyClass)},onMousedown:function(t,e,i){if(!i.target.nodeName.match(/^(input|select|textarea)$/i))return i.preventDefault(),!0},placeholderClass:"placeholder",placeholder:'
  • ',pullPlaceholder:!0,serialize:function(e,i,o){var s=t.extend({},e.data());return o?[i]:(i[0]&&(s.children=i),delete s.subContainers,delete s.sortable,s)},tolerance:0},f={},d=0,g={left:0,top:0,bottom:0,right:0},c={start:"touchstart.sortable mousedown.sortable",drop:"touchend.sortable touchcancel.sortable mouseup.sortable",drag:"touchmove.sortable mousemove.sortable",scroll:"scroll.sortable"},m="subContainers";h.get=function(t){return f[t.group]||(t.group===o&&(t.group=d++),f[t.group]=new h(t)),f[t.group]},h.prototype={dragInit:function(e,i){this.$document=t(i.el[0].ownerDocument);var o=t(e.target).closest(this.options.itemSelector);if(o.length){if(this.item=o,this.itemContainer=i,this.item.is(this.options.exclude)||!this.options.onMousedown(this.item,p.onMousedown,e))return;this.setPointer(e),this.toggleListeners("on"),this.setupDelayTimer(),this.dragInitDone=!0}},drag:function(t){if(!this.dragging){if(!this.distanceMet(t)||!this.delayMet)return;this.options.onDragStart(this.item,this.itemContainer,p.onDragStart,t),this.item.before(this.placeholder),this.dragging=!0}this.setPointer(t),this.options.onDrag(this.item,r(this.pointer,this.item.offsetParent()),p.onDrag,t);var e=this.getPointer(t),i=this.sameResultBox,s=this.options.tolerance;(!i||i.top-s>e.top||i.bottom+se.left||i.right+s=this.options.distance},getPointer:function(t){var e=t.originalEvent||t.originalEvent.touches&&t.originalEvent.touches[0];return{left:t.pageX||e.pageX,top:t.pageY||e.pageY}},setupDelayTimer:function(){var t=this;this.delayMet=!this.options.delay,this.delayMet||(clearTimeout(this._mouseDelayTimer),this._mouseDelayTimer=setTimeout(function(){t.delayMet=!0},this.options.delay))},scroll:function(t){this.clearDimensions(),this.clearOffsetParent()},toggleListeners:function(e){var i=this,o=["drag","drop","scroll"];t.each(o,function(t,o){i.$document[e](c[o],i[o+"Proxy"])})},clearOffsetParent:function(){this.offsetParent=o},clearDimensions:function(){this.traverse(function(t){t._clearDimensions()})},traverse:function(t){t(this);for(var e=this.containers.length;e--;)this.containers[e].traverse(t)},_clearDimensions:function(){this.containerDimensions=o},_destroy:function(){f[this.options.group]=o}},l.prototype={dragInit:function(t){var e=this.rootGroup;!this.disabled&&!e.dragInitDone&&this.options.drag&&this.isValidDrag(t)&&e.dragInit(t,this)},isValidDrag:function(t){return 1==t.which||"touchstart"==t.type&&1==t.originalEvent.touches.length},searchValidTarget:function(t,e){var i=a(this.getItemDimensions(),t,e),o=i.length,s=this.rootGroup,n=!s.options.isValidTarget||s.options.isValidTarget(s.item,this);if(!o&&n)return s.movePlaceholder(this,this.target,"append"),!0;for(;o--;){var r=i[o][0],h=i[o][1];if(!h&&this.hasChildGroup(r)){var l=this.getContainerGroup(r).searchValidTarget(t,e);if(l)return!0}else if(n)return this.movePlaceholder(r,t),!0}},movePlaceholder:function(e,i){var o=t(this.items[e]),s=this.itemDimensions[e],n="after",r=o.outerWidth(),a=o.outerHeight(),h=o.offset(),l={left:h.left,right:h.left+r,top:h.top,bottom:h.top+a};if(this.options.vertical){var c=(s[2]+s[3])/2,u=i.top<=c;u?(n="before",l.bottom-=a/2):l.top+=a/2}else{var p=(s[0]+s[1])/2,f=i.left<=p;f?(n="before",l.right-=r/2):l.left+=r/2}this.hasChildGroup(e)&&(l=g),this.rootGroup.movePlaceholder(this,o,n,l)},getItemDimensions:function(){return this.itemDimensions||(this.items=this.$getChildren(this.el,"item").filter(":not(."+this.group.options.placeholderClass+", ."+this.group.options.draggedClass+")").get(),n(this.items,this.itemDimensions=[],this.options.tolerance)),this.itemDimensions},getItemOffsetParent:function(){var t,e=this.el;return t="relative"===e.css("position")||"absolute"===e.css("position")||"fixed"===e.css("position")?e:e.offsetParent()},hasChildGroup:function(t){return this.options.nested&&this.getContainerGroup(t)},getContainerGroup:function(e){var s=t.data(this.items[e],m);if(s===o){var n=this.$getChildren(this.items[e],"container");if(s=!1,n[0]){var r=t.extend({},this.options,{rootGroup:this.rootGroup,group:d++});s=n[i](r).data(i).group}t.data(this.items[e],m,s)}return s},$getChildren:function(e,i){var o=this.rootGroup.options,s=o[i+"Path"],n=o[i+"Selector"];return e=t(e),s&&(e=e.find(s)),e.children(n)},_serialize:function(e,i){var o=this,s=i?"item":"container",n=this.$getChildren(e,s).not(this.options.exclude).map(function(){return o._serialize(t(this),!i)}).get();return this.rootGroup.options.serialize(e,n,i)},traverse:function(e){t.each(this.items||[],function(i){var o=t.data(this,m);o&&o.traverse(e)}),e(this)},_clearDimensions:function(){this.itemDimensions=o},_destroy:function(){var e=this;this.target.off(c.start,this.handle),this.el.removeData(i),this.options.drop&&(this.group.containers=t.grep(this.group.containers,function(t){return t!=e})),t.each(this.items||[],function(){t.removeData(this,m)})}};var v={enable:function(){this.traverse(function(t){t.disabled=!1})},disable:function(){this.traverse(function(t){t.disabled=!0})},serialize:function(){return this._serialize(this.el,!0)},refresh:function(){this.traverse(function(t){t._clearDimensions()})},destroy:function(){this.traverse(function(t){t._destroy()})}};t.extend(l.prototype,v),t.fn[i]=function(e){var s=Array.prototype.slice.call(arguments,1);return this.map(function(){var n=t(this),r=n.data(i);return r&&v[e]?v[e].apply(r,s)||this:(r||e!==o&&"object"!=typeof e||n.data(i,new l(n,e)),this)})}}(jQuery,window,"sortable"); \ No newline at end of file +!function(t,e,i,o){function s(t,e){var i=Math.max(0,t[0]-e[0],e[0]-t[1]),o=Math.max(0,t[2]-e[1],e[1]-t[3]);return i+o}function n(e,i,o,s){var n=e.length,r=s?"offset":"position";for(o=o||0;n--;){var a=e[n].el?e[n].el:t(e[n]),h=a[r]();h.left+=parseInt(a.css("margin-left"),10),h.top+=parseInt(a.css("margin-top"),10),i[n]=[h.left-o,h.left+a.outerWidth()+o,h.top-o,h.top+a.outerHeight()+o]}}function r(t,e){var i=e.offset();return{left:t.left-i.left,top:t.top-i.top}}function a(t,e,i){e=[e.left,e.top],i=i&&[i.left,i.top];for(var o,n=t.length,r=[];n--;)o=t[n],r[n]=[n,s(o,e),i&&s(o,i)];return r=r.sort(function(t,e){return e[1]-t[1]||e[2]-t[2]||e[0]-t[0]})}function h(e){this.options=t.extend({},p,e),this.containers=[],this.options.rootGroup||(this.scrollProxy=t.proxy(this.scroll,this),this.dragProxy=t.proxy(this.drag,this),this.dropProxy=t.proxy(this.drop,this),this.placeholder=t(this.options.placeholder),e.isValidTarget||(this.options.isValidTarget=o))}function l(e,i){this.el=e,this.options=t.extend({},u,i),this.group=h.get(this.options),this.rootGroup=this.options.rootGroup||this.group,this.handle=this.rootGroup.options.handle||this.rootGroup.options.itemSelector;var o=this.rootGroup.options.itemPath;this.target=o?this.el.find(o):this.el,this.target.on(c.start,this.handle,t.proxy(this.dragInit,this)),this.options.drop&&this.group.containers.push(this)}var c,u={drag:!0,drop:!0,exclude:"",nested:!0,vertical:!0},p={afterMove:function(t,e,i){},containerPath:"",containerSelector:"ol, ul",distance:0,delay:0,handle:"",itemPath:"",itemSelector:"li",bodyClass:"dragging",draggedClass:"dragged",isValidTarget:function(t,e){return!0},onCancel:function(t,e,i,o){},onDrag:function(t,e,i,o){t.css(e)},onDragStart:function(e,i,o,s){e.css({height:e.outerHeight(),width:e.outerWidth()}),e.addClass(i.group.options.draggedClass),t("body").addClass(i.group.options.bodyClass)},onDrop:function(e,i,o,s){e.removeClass(i.group.options.draggedClass).removeAttr("style"),t("body").removeClass(i.group.options.bodyClass)},onMousedown:function(t,e,i){if(!i.target.nodeName.match(/^(input|select|textarea)$/i))return i.preventDefault(),!0},placeholderClass:"placeholder",placeholder:'
  • ',pullPlaceholder:!0,serialize:function(e,i,o){var s=t.extend({},e.data());return o?[i]:(i[0]&&(s.children=i),delete s.subContainers,delete s.sortable,s)},tolerance:0},f={},d=0,g={left:0,top:0,bottom:0,right:0},c={start:"touchstart.sortable mousedown.sortable",drop:"touchend.sortable touchcancel.sortable mouseup.sortable",drag:"touchmove.sortable mousemove.sortable",scroll:"scroll.sortable"},m="subContainers";h.get=function(t){return f[t.group]||(t.group===o&&(t.group=d++),f[t.group]=new h(t)),f[t.group]},h.prototype={dragInit:function(e,i){this.$document=t(i.el[0].ownerDocument);var o=t(e.target).closest(this.options.itemSelector);if(o.length){if(this.item=o,this.itemContainer=i,this.item.is(this.options.exclude)||!this.options.onMousedown(this.item,p.onMousedown,e))return;this.setPointer(e),this.toggleListeners("on"),this.setupDelayTimer(),this.dragInitDone=!0}},drag:function(t){if(!this.dragging){if(!this.distanceMet(t)||!this.delayMet)return;this.options.onDragStart(this.item,this.itemContainer,p.onDragStart,t),this.item.before(this.placeholder),this.dragging=!0}this.setPointer(t),this.options.onDrag(this.item,r(this.pointer,this.item.offsetParent()),p.onDrag,t);var e=this.getPointer(t),i=this.sameResultBox,s=this.options.tolerance;(!i||i.top-s>e.top||i.bottom+se.left||i.right+s=this.options.distance},getPointer:function(t){var e=t.originalEvent||t.originalEvent.touches&&t.originalEvent.touches[0];return{left:t.pageX||e.pageX,top:t.pageY||e.pageY}},setupDelayTimer:function(){var t=this;this.delayMet=!this.options.delay,this.delayMet||(clearTimeout(this._mouseDelayTimer),this._mouseDelayTimer=setTimeout(function(){t.delayMet=!0},this.options.delay))},scroll:function(t){this.clearDimensions(),this.clearOffsetParent()},toggleListeners:function(e){var i=this,o=["drag","drop","scroll"];t.each(o,function(t,o){i.$document[e](c[o],i[o+"Proxy"])})},clearOffsetParent:function(){this.offsetParent=o},clearDimensions:function(){this.traverse(function(t){t._clearDimensions()})},traverse:function(t){t(this);for(var e=this.containers.length;e--;)this.containers[e].traverse(t)},_clearDimensions:function(){this.containerDimensions=o},_destroy:function(){f[this.options.group]=o}},l.prototype={dragInit:function(t){var e=this.rootGroup;!this.disabled&&!e.dragInitDone&&this.options.drag&&this.isValidDrag(t)&&e.dragInit(t,this)},isValidDrag:function(t){return 1==t.which||"touchstart"==t.type&&1==t.originalEvent.touches.length},searchValidTarget:function(t,e){var i=a(this.getItemDimensions(),t,e),o=i.length,s=this.rootGroup,n=!s.options.isValidTarget||s.options.isValidTarget(s.item,this);if(!o&&n)return s.movePlaceholder(this,this.target,"append"),!0;for(;o--;){var r=i[o][0],h=i[o][1];if(!h&&this.hasChildGroup(r)){var l=this.getContainerGroup(r).searchValidTarget(t,e);if(l)return!0}else if(n)return this.movePlaceholder(r,t),!0}},movePlaceholder:function(e,i){var o=t(this.items[e]),s=this.itemDimensions[e],n="after",r=o.outerWidth(),a=o.outerHeight(),h=o.offset(),l={left:h.left,right:h.left+r,top:h.top,bottom:h.top+a};if(this.options.vertical){var c=(s[2]+s[3])/2,u=i.top<=c;u?(n="before",l.bottom-=a/2):l.top+=a/2}else{var p=(s[0]+s[1])/2,f=i.left<=p;f?(n="before",l.right-=r/2):l.left+=r/2}this.hasChildGroup(e)&&(l=g),this.rootGroup.movePlaceholder(this,o,n,l)},getItemDimensions:function(){return this.itemDimensions||(this.items=this.$getChildren(this.el,"item").filter(":not(."+this.group.options.placeholderClass+", ."+this.group.options.draggedClass+")").get(),n(this.items,this.itemDimensions=[],this.options.tolerance)),this.itemDimensions},getItemOffsetParent:function(){var t,e=this.el;return t="relative"===e.css("position")||"absolute"===e.css("position")||"fixed"===e.css("position")?e:e.offsetParent()},hasChildGroup:function(t){return this.options.nested&&this.getContainerGroup(t)},getContainerGroup:function(e){var s=t.data(this.items[e],m);if(s===o){var n=this.$getChildren(this.items[e],"container");if(s=!1,n[0]){var r=t.extend({},this.options,{rootGroup:this.rootGroup,group:d++});s=n[i](r).data(i).group}t.data(this.items[e],m,s)}return s},$getChildren:function(e,i){var o=this.rootGroup.options,s=o[i+"Path"],n=o[i+"Selector"];return e=t(e),s&&(e=e.find(s)),e.children(n)},_serialize:function(e,i){var o=this,s=i?"item":"container",n=this.$getChildren(e,s).not(this.options.exclude).map(function(){return o._serialize(t(this),!i)}).get();return this.rootGroup.options.serialize(e,n,i)},traverse:function(e){t.each(this.items||[],function(i){var o=t.data(this,m);o&&o.traverse(e)}),e(this)},_clearDimensions:function(){this.itemDimensions=o},_destroy:function(){var e=this;this.target.off(c.start,this.handle),this.el.removeData(i),this.options.drop&&(this.group.containers=t.grep(this.group.containers,function(t){return t!=e})),t.each(this.items||[],function(){t.removeData(this,m)})}};var v={enable:function(){this.traverse(function(t){t.disabled=!1})},disable:function(){this.traverse(function(t){t.disabled=!0})},serialize:function(){return this._serialize(this.el,!0)},refresh:function(){this.traverse(function(t){t._clearDimensions()})},destroy:function(){this.traverse(function(t){t._destroy()})}};t.extend(l.prototype,v),t.fn[i]=function(e){var s=Array.prototype.slice.call(arguments,1);return this.map(function(){var n=t(this),r=n.data(i);return r&&v[e]?v[e].apply(r,s)||this:(r||e!==o&&"object"!=typeof e||n.data(i,new l(n,e)),this)})}}(jQuery,window,"sorting"); \ No newline at end of file diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index fef1747..00941c4 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -314,7 +314,7 @@ public function render() if($this->sortable) { MultipleInputSortableAsset::register($view); - $js .= "$('#{$this->id} table').sortable({containerSelector: 'table', itemPath: '> tbody', itemSelector: 'tr', placeholder: '', handle:'.drag-handle'});"; + $js .= "$('#{$this->id} table').sorting({containerSelector: 'table', itemPath: '> tbody', itemSelector: 'tr', placeholder: '', handle:'.drag-handle'});"; } $view->registerJs($js); From 8834bc69da6f7f72749ec16094ba24216548b990 Mon Sep 17 00:00:00 2001 From: Nikolya Date: Tue, 28 Mar 2017 19:18:24 +0300 Subject: [PATCH 069/247] version changes --- CHANGELOG.md | 5 +++++ README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a419ea9..1a48dd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Yii2 multiple input change log ============================== +2.8.2 +===== + +- Fixed conflict with jQuery UI sortable + 2.8.1 ===== diff --git a/README.md b/README.md index 5809102..f47b303 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.8.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.8.2 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 6776add..52abd93 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.8.1", + "version": "2.8.2", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 6352460..feb6b8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.8.1", + "version": "2.8.2", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From c34b94faaaa7f17e66b7f3b95d793b094ac4b422 Mon Sep 17 00:00:00 2001 From: Nikolya Date: Tue, 28 Mar 2017 19:27:20 +0300 Subject: [PATCH 070/247] add sorting colum width --- src/assets/src/css/sorting.css | 4 ++++ src/assets/src/css/sorting.min.css | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/assets/src/css/sorting.css b/src/assets/src/css/sorting.css index f4b3b20..829393a 100644 --- a/src/assets/src/css/sorting.css +++ b/src/assets/src/css/sorting.css @@ -39,4 +39,8 @@ tr.placeholder:before { margin-top: -5px; left: -5px; border-right: none; +} + +table.multiple-input-list.table-renderer tr > td.list-cell__drag { + width: 35px; } \ No newline at end of file diff --git a/src/assets/src/css/sorting.min.css b/src/assets/src/css/sorting.min.css index 695dc55..37ad594 100644 --- a/src/assets/src/css/sorting.min.css +++ b/src/assets/src/css/sorting.min.css @@ -1 +1 @@ -.drag-handle,.dragging,.dragging *{cursor:move!important}.dragged{position:absolute;top:0;opacity:.5;z-index:2000}.drag-handle{opacity:.5;padding:7.5px}.drag-handle:hover{opacity:1}tr.placeholder{display:block;background:red;position:relative;margin:0;padding:0;border:none}tr.placeholder:before{content:"";position:absolute;width:0;height:0;border:5px solid transparent;border-left-color:red;margin-top:-5px;left:-5px;border-right:none} \ No newline at end of file +.drag-handle,.dragging,.dragging *{cursor:move!important}.dragged{position:absolute;top:0;opacity:.5;z-index:2000}.drag-handle{opacity:.5;padding:7.5px}.drag-handle:hover{opacity:1}tr.placeholder{display:block;background:red;position:relative;margin:0;padding:0;border:none}tr.placeholder:before{content:"";position:absolute;width:0;height:0;border:5px solid transparent;border-left-color:red;margin-top:-5px;left:-5px;border-right:none}table.multiple-input-list.table-renderer tr>td.list-cell__drag{width:35px} \ No newline at end of file From 0d61af5db6e8f3f8961bebc11be1def972b00dd5 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 5 Apr 2017 23:26:44 +0300 Subject: [PATCH 071/247] Pass the added row to js event --- CHANGELOG.md | 5 ++ README.md | 2 +- composer.json | 2 +- examples/views/multiple-input.php | 57 +++++++++---------- package.json | 2 +- src/assets/src/js/jquery.multipleInput.js | 7 ++- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 7 files changed, 39 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a419ea9..cce1e12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Yii2 multiple input change log ============================== +2.8.2 +===== + +- Pass the added row to `afterAddRow` event + 2.8.1 ===== diff --git a/README.md b/README.md index 5809102..f47b303 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.8.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.8.2 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 6776add..52abd93 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.8.1", + "version": "2.8.2", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/examples/views/multiple-input.php b/examples/views/multiple-input.php index 9c34b12..c50d13f 100644 --- a/examples/views/multiple-input.php +++ b/examples/views/multiple-input.php @@ -5,7 +5,6 @@ use unclead\multipleinput\examples\models\ExampleModel; use yii\helpers\Html; use unclead\multipleinput\MultipleInputColumn; -use yii\widgets\MaskedInput; // Note: You have to install https://github.com/kartik-v/yii2-widget-datepicker for correct work an example use kartik\date\DatePicker; @@ -24,25 +23,25 @@

    Single column

    field($model, 'emails')->widget(MultipleInput::className(), [ -// 'max' => 6, -// 'allowEmptyList' => false, -// 'columns' => [ -// [ -// 'name' => 'emails', -// 'options' => [ -// 'placeholder' => 'E-mail' -// ] -// ] -// ], -// 'min' => 2, // should be at least 2 rows -// 'addButtonPosition' => [ -// MultipleInput::POS_HEADER, -// MultipleInput::POS_FOOTER, -// MultipleInput::POS_ROW -// ] -// ]) -// ->label(false); + echo $form->field($model, 'emails')->widget(MultipleInput::className(), [ + 'max' => 6, + 'allowEmptyList' => false, + 'columns' => [ + [ + 'name' => 'emails', + 'options' => [ + 'placeholder' => 'E-mail' + ] + ] + ], + 'min' => 2, // should be at least 2 rows + 'addButtonPosition' => [ + MultipleInput::POS_HEADER, + MultipleInput::POS_FOOTER, + MultipleInput::POS_ROW + ] + ]) + ->label(false); ?>

    Multiple columns

    @@ -89,7 +88,7 @@ ], [ 'name' => 'day', - 'type' => MaskedInput::className(), + 'type' => DatePicker::className(), 'title' => 'Day', 'value' => function($data) { return $data['day']; @@ -99,15 +98,11 @@ '1' => 'Monday' ], 'options' => [ - 'mask' => '99.99.9999' - + 'pluginOptions' => [ + 'format' => 'dd.mm.yyyy', + 'todayHighlight' => true + ] ], -// 'options' => [ -// 'pluginOptions' => [ -// 'format' => 'dd.mm.yyyy', -// 'todayHighlight' => true -// ] -// ], 'headerOptions' => [ 'style' => 'width: 250px;', 'class' => 'day-css-class' @@ -164,8 +159,8 @@ console.log('calls on after initialization event'); }).on('beforeAddRow', function(e) { console.log('calls on before add row event'); - }).on('afterAddRow', function(e) { - console.log('calls on after add row event'); + }).on('afterAddRow', function(e, row) { + console.log('calls on after add row event', $(row)); }).on('beforeDeleteRow', function(e, item){ console.log(item); console.log('calls on before remove row event'); diff --git a/package.json b/package.json index 6352460..feb6b8f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.8.1", + "version": "2.8.2", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index b892552..8b2c8a0 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -22,7 +22,7 @@ /** * afterAddRow event is triggered after successful adding new row. * The signature of the event handler should be: - * function (event) + * function (event, row) * where event is an Event object. * */ @@ -231,8 +231,9 @@ } template = template.replaceAll('{' + settings.indexPlaceholder + '}', data.currentIndex); + var $addedInput = $(template); - $(template).hide().appendTo(inputList).fadeIn(300); + $addedInput.hide().appendTo(inputList).fadeIn(300); if (values instanceof Object) { var tmp = []; @@ -290,7 +291,7 @@ $wrapper.data('multipleInput').currentIndex++; var event = $.Event(events.afterAddRow); - $wrapper.trigger(event); + $wrapper.trigger(event, [$addedInput]); }; var removeInput = function ($btn) { diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 68717ee..9fd325b 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),d=p.closest("form"),c=u.inputId;for(m in u.jsInit){var f=document.createElement("script");f.innerHTML=u.jsInit[m],document.body.appendChild(f)}p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))});var m=0,v=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(h),p.trigger(v)}else m++;(0===d.length||m>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(h),p.trigger(v))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,c=l.children(".multiple-input-list").first();if(!(null!=u.max&&s(l)>=u.max)){if(d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex),t(d).hide().appendTo(c).fadeIn(300),r instanceof Object){var f=[];for(var m in r)r.hasOwnProperty(m)&&f.push(r[m]);r=f}var v;for(var h in u.jsTemplates)v=u.jsTemplates[h].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(v);var I=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,l=p(e),a=t("#"+l);if(r){var u=r[I];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),I++}),l.data("multipleInput").currentIndex++;var g=t.Event(e.afterAddRow);l.trigger(g)}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(){u(t(this))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!=r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),d=p.closest("form"),c=u.inputId;for(m in u.jsInit){var f=document.createElement("script");f.innerHTML=u.jsInit[m],document.body.appendChild(f)}p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))});var m=0,v=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(h),p.trigger(v)}else m++;(0===d.length||m>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(h),p.trigger(v))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,c=l.children(".multiple-input-list").first();if(!(null!=u.max&&s(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var f=t(d);if(f.hide().appendTo(c).fadeIn(300),r instanceof Object){var m=[];for(var v in r)r.hasOwnProperty(v)&&m.push(r[v]);r=m}var h;for(var I in u.jsTemplates)h=u.jsTemplates[I].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(h);var g=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,l=p(e),a=t("#"+l);if(r){var u=r[g];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),g++}),l.data("multipleInput").currentIndex++;var y=t.Event(e.afterAddRow);l.trigger(y,[f])}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(){u(t(this))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!=r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file From 335d2905518f0c4085e2a8621b3f9f43c80208ba Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 5 Apr 2017 23:32:28 +0300 Subject: [PATCH 072/247] Fixes after merge --- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f47b303..a866fdc 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.8.2 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.9.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 52abd93..56bb51a 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.8.2", + "version": "2.9.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index feb6b8f..22e4c76 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.8.2", + "version": "2.9.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From b1d3c0920b8858a37cd73770e87c358c6e0bc07f Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Tue, 23 May 2017 22:29:04 +0300 Subject: [PATCH 073/247] Added missing js event: beforeAddRow --- CHANGELOG.md | 5 +++++ examples/views/multiple-input.php | 1 + src/assets/src/js/jquery.multipleInput.js | 19 +++++++++++++++++-- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c90fcb7..932eab7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ Yii2 multiple input change log ============================== +2.10.0 +====== + +- #154: Added missing js event: beforeAddRow + 2.9.0 ===== diff --git a/examples/views/multiple-input.php b/examples/views/multiple-input.php index c50d13f..09e116b 100644 --- a/examples/views/multiple-input.php +++ b/examples/views/multiple-input.php @@ -159,6 +159,7 @@ console.log('calls on after initialization event'); }).on('beforeAddRow', function(e) { console.log('calls on before add row event'); + return confirm('Are you sure you want to add row?') }).on('afterAddRow', function(e, row) { console.log('calls on after add row event', $(row)); }).on('beforeDeleteRow', function(e, item){ diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 8b2c8a0..3af4bbc 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -19,6 +19,14 @@ * */ afterInit: 'afterInit', + /** + * afterAddRow event is triggered after successful adding new row. + * The signature of the event handler should be: + * function (event, row) + * where event is an Event object. + * + */ + beforeAddRow: 'beforeAddRow', /** * afterAddRow event is triggered after successful adding new row. * The signature of the event handler should be: @@ -233,6 +241,13 @@ template = template.replaceAll('{' + settings.indexPlaceholder + '}', data.currentIndex); var $addedInput = $(template); + var beforeAddEvent = $.Event(events.beforeAddRow); + $wrapper.trigger(beforeAddEvent, [$addedInput]); + + if (beforeAddEvent.result === false) { + return; + } + $addedInput.hide().appendTo(inputList).fadeIn(300); if (values instanceof Object) { @@ -290,8 +305,8 @@ $wrapper.data('multipleInput').currentIndex++; - var event = $.Event(events.afterAddRow); - $wrapper.trigger(event, [$addedInput]); + var afterAddEvent = $.Event(events.afterAddRow); + $wrapper.trigger(afterAddEvent, [$addedInput]); }; var removeInput = function ($btn) { diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 9fd325b..4f4d412 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),d=p.closest("form"),c=u.inputId;for(m in u.jsInit){var f=document.createElement("script");f.innerHTML=u.jsInit[m],document.body.appendChild(f)}p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))});var m=0,v=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(h),p.trigger(v)}else m++;(0===d.length||m>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(h),p.trigger(v))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,c=l.children(".multiple-input-list").first();if(!(null!=u.max&&s(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var f=t(d);if(f.hide().appendTo(c).fadeIn(300),r instanceof Object){var m=[];for(var v in r)r.hasOwnProperty(v)&&m.push(r[v]);r=m}var h;for(var I in u.jsTemplates)h=u.jsTemplates[I].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(h);var g=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,l=p(e),a=t("#"+l);if(r){var u=r[g];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var s=a.find('option[value="'+u+'"]');s.length&&a.val(u)}}n&&o(e),g++}),l.data("multipleInput").currentIndex++;var y=t.Event(e.afterAddRow);l.trigger(y,[f])}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(){u(t(this))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!=r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=p(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),s=t("#"+u.id),d=s.closest("form"),c=u.inputId;for(v in u.jsInit){var f=document.createElement("script");f.innerHTML=u.jsInit[v],document.body.appendChild(f)}s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),n=!0,clearInterval(h),s.trigger(m)}else v++;(0===d.length||v>10)&&(s.data("multipleInput").currentIndex=p(s),n=!1,clearInterval(h),s.trigger(m))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,c=l.children(".multiple-input-list").first();if(!(null!=u.max&&p(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var f=t(d),v=t.Event(e.beforeAddRow);if(l.trigger(v,[f]),v.result!==!1){if(f.hide().appendTo(c).fadeIn(300),r instanceof Object){var m=[];for(var h in r)r.hasOwnProperty(h)&&m.push(r[h]);r=m}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var y=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,l=s(e),a=t("#"+l);if(r){var u=r[y];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var p=a.find('option[value="'+u+'"]');p.length&&a.val(u)}}n&&o(e),y++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[f])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(p(r)>o.min){var s=t.Event(e.beforeDeleteRow);if(r.trigger(s,[l]),s.result===!1)return;n&&l.find("input, select, textarea").each(function(){u(t(this))}),l.fadeOut(300,function(){t(this).remove(),s=t.Event(e.afterDeleteRow),r.trigger(s,[l])})}},o=function(e){var i=s(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!=r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=s(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file From 473825099f3f17be13a650ba6d2d901a565b584d Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 6 Aug 2017 17:11:20 +0300 Subject: [PATCH 074/247] improved calculation of current index --- package.json | 4 ++-- src/assets/src/js/jquery.multipleInput.js | 8 ++++---- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index 22e4c76..789412b 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "2.9.0", "description": "jQuery multipleInput", "scripts": { - "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" + "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" }, "repository": { "type": "git", @@ -14,7 +14,7 @@ "email": "unclead.nsk@gmail.com" }, "keywords": [ - "jquery" + "jquery" ], "license": "BSD-3-Clause", "bugs": { diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 3af4bbc..14e6b59 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -424,10 +424,10 @@ var getCurrentIndex = function($wrapper) { return $wrapper - .children('.multiple-input-list') - .children('tbody') - .children('.multiple-input-list__item') - .length; + .find('.multiple-input-list .multiple-input-list__item') + .filter(function(){ + return $(this).parents('.multiple-input').first().attr('id') === $wrapper.attr('id'); + }).length; }; String.prototype.replaceAll = function (search, replace) { diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 4f4d412..2d27ead 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),s=t("#"+u.id),d=s.closest("form"),c=u.inputId;for(v in u.jsInit){var f=document.createElement("script");f.innerHTML=u.jsInit[v],document.body.appendChild(f)}s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),n=!0,clearInterval(h),s.trigger(m)}else v++;(0===d.length||v>10)&&(s.data("multipleInput").currentIndex=p(s),n=!1,clearInterval(h),s.trigger(m))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,c=l.children(".multiple-input-list").first();if(!(null!=u.max&&p(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var f=t(d),v=t.Event(e.beforeAddRow);if(l.trigger(v,[f]),v.result!==!1){if(f.hide().appendTo(c).fadeIn(300),r instanceof Object){var m=[];for(var h in r)r.hasOwnProperty(h)&&m.push(r[h]);r=m}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var y=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,l=s(e),a=t("#"+l);if(r){var u=r[y];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var p=a.find('option[value="'+u+'"]');p.length&&a.val(u)}}n&&o(e),y++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[f])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(p(r)>o.min){var s=t.Event(e.beforeDeleteRow);if(r.trigger(s,[l]),s.result===!1)return;n&&l.find("input, select, textarea").each(function(){u(t(this))}),l.fadeOut(300,function(){t(this).remove(),s=t.Event(e.afterDeleteRow),r.trigger(s,[l])})}},o=function(e){var i=s(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!=r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=s(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(t){return t.children(".multiple-input-list").children("tbody").children(".multiple-input-list__item").length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),s=t("#"+u.id),d=s.closest("form"),f=u.inputId;for(m in u.jsInit){var c=document.createElement("script");c.innerHTML=u.jsInit[m],document.body.appendChild(c)}s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))});var m=0,v=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",f),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",f)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),n=!0,clearInterval(h),s.trigger(v)}else m++;(0===d.length||m>10)&&(s.data("multipleInput").currentIndex=p(s),n=!1,clearInterval(h),s.trigger(v))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!=u.max&&p(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),m=t.Event(e.beforeAddRow);if(l.trigger(m,[c]),m.result!==!1){if(c.hide().appendTo(f).fadeIn(300),r instanceof Object){var v=[];for(var h in r)r.hasOwnProperty(h)&&v.push(r[h]);r=v}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var y=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,l=s(e),a=t("#"+l);if(r){var u=r[y];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var p=a.find('option[value="'+u+'"]');p.length&&a.val(u)}}n&&o(e),y++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[c])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(p(r)>o.min){var s=t.Event(e.beforeDeleteRow);if(r.trigger(s,[l]),s.result===!1)return;n&&l.find("input, select, textarea").each(function(){u(t(this))}),l.fadeOut(300,function(){t(this).remove(),s=t.Event(e.afterDeleteRow),r.trigger(s,[l])})}},o=function(e){var i=s(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!=r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=s(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file From ad43b3d8062a4011ae2c4a7bc69215e2b4c49282 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 6 Aug 2017 18:09:03 +0300 Subject: [PATCH 075/247] fixed removal of rows in some border case --- src/assets/src/js/jquery.multipleInput.js | 32 +++++++++---------- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 14e6b59..4d0f581 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -228,10 +228,10 @@ }; var addInput = function (btn, values) { - var $wrapper = $(btn).closest('.multiple-input').first(), - data = $wrapper.data('multipleInput'), - settings = data.settings, - template = settings.template, + var $wrapper = $(btn).closest('.multiple-input').first(), + data = $wrapper.data('multipleInput'), + settings = data.settings, + template = settings.template, inputList = $wrapper.children('.multiple-input-list').first(); if (settings.max != null && getCurrentIndex($wrapper) >= settings.max) { @@ -273,10 +273,10 @@ var index = 0; - $(template).find('input, select, textarea').each(function () { - var that = $(this), - tag = that.get(0).tagName, - id = getInputId(that), + $(template).find('input, select, textarea').each(function (k, v) { + var ele = $(v), + tag = v.tagName, + id = getInputId(ele), obj = $('#' + id); if (values) { @@ -297,7 +297,7 @@ } if (isActiveFormEnabled) { - addAttribute(that); + addAttribute(ele); } index++; @@ -310,10 +310,10 @@ }; var removeInput = function ($btn) { - var $wrapper = $btn.closest('.multiple-input').first(), + var $wrapper = $btn.closest('.multiple-input').first(), $toDelete = $btn.closest('.multiple-input-list__item'), - data = $wrapper.data('multipleInput'), - settings = data.settings; + data = $wrapper.data('multipleInput'), + settings = data.settings; if (getCurrentIndex($wrapper) > settings.min) { var event = $.Event(events.beforeDeleteRow); @@ -324,8 +324,8 @@ } if (isActiveFormEnabled) { - $toDelete.find('input, select, textarea').each(function () { - removeAttribute($(this)); + $toDelete.find('input, select, textarea').each(function (index, ele) { + removeAttribute($(ele)); }); } @@ -394,8 +394,8 @@ /** * Removes an attribute from ActiveForm. */ - var removeAttribute = function () { - var id = getInputId($(this)); + var removeAttribute = function (ele) { + var id = getInputId(ele); if (id === null) { return; diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 2d27ead..60089c2 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),s=t("#"+u.id),d=s.closest("form"),f=u.inputId;for(m in u.jsInit){var c=document.createElement("script");c.innerHTML=u.jsInit[m],document.body.appendChild(c)}s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))});var m=0,v=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",f),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",f)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),n=!0,clearInterval(h),s.trigger(v)}else m++;(0===d.length||m>10)&&(s.data("multipleInput").currentIndex=p(s),n=!1,clearInterval(h),s.trigger(v))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!=u.max&&p(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),m=t.Event(e.beforeAddRow);if(l.trigger(m,[c]),m.result!==!1){if(c.hide().appendTo(f).fadeIn(300),r instanceof Object){var v=[];for(var h in r)r.hasOwnProperty(h)&&v.push(r[h]);r=v}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var y=0;t(d).find("input, select, textarea").each(function(){var e=t(this),i=e.get(0).tagName,l=s(e),a=t("#"+l);if(r){var u=r[y];if("INPUT"==i||"TEXTAREA"==i)a.val(u);else if("SELECT"==i)if(u&&-1!=u.indexOf("option"))a.append(u);else{var p=a.find('option[value="'+u+'"]');p.length&&a.val(u)}}n&&o(e),y++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[c])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(p(r)>o.min){var s=t.Event(e.beforeDeleteRow);if(r.trigger(s,[l]),s.result===!1)return;n&&l.find("input, select, textarea").each(function(){u(t(this))}),l.fadeOut(300,function(){t(this).remove(),s=t.Event(e.afterDeleteRow),r.trigger(s,[l])})}},o=function(e){var i=s(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!=r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(){var e=s(t(this));if(null!==e){var i=t("#"+e).closest("form");0!==i.length&&i.yiiActiveForm("remove",e)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),d=p.closest("form"),f=u.inputId;for(m in u.jsInit){var c=document.createElement("script");c.innerHTML=u.jsInit[m],document.body.appendChild(c)}p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))});var m=0,v=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",f),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",f)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(h),p.trigger(v)}else m++;(0===d.length||m>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(h),p.trigger(v))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!=u.max&&s(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),m=t.Event(e.beforeAddRow);if(l.trigger(m,[c]),m.result!==!1){if(c.hide().appendTo(f).fadeIn(300),r instanceof Object){var v=[];for(var h in r)r.hasOwnProperty(h)&&v.push(r[h]);r=v}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var y=0;t(d).find("input, select, textarea").each(function(e,i){var l=t(i),a=i.tagName,u=p(l),s=t("#"+u);if(r){var d=r[y];if("INPUT"==a||"TEXTAREA"==a)s.val(d);else if("SELECT"==a)if(d&&-1!=d.indexOf("option"))s.append(d);else{var f=s.find('option[value="'+d+'"]');f.length&&s.val(d)}}n&&o(l),y++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[c])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(e,i){u(t(i))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!=r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(e){var i=p(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file From 19377caec9ca0e6c13497b2fb05fe2716e8ed107 Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Tue, 29 Aug 2017 12:15:06 +0300 Subject: [PATCH 076/247] Add one more image to README.md --- README.md | 58 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/README.md b/README.md index a866fdc..efdf165 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,64 @@ use unclead\multipleinput\MultipleInput; ->label(false); ?> ``` +See more in [single column](https://github.com/unclead/yii2-multiple-input/wiki/Usage#one-column) + +## Advanced usage + +![Multiple columns example](https://raw.githubusercontent.com/unclead/yii2-multiple-input/master/resources/images/multiple-column.gif) + +For example you want to have an interface for manage user schedule. For simplicity we will store the schedule in json string. +In this case you can use yii2-multiple-input widget like in the following code + +```php +use unclead\multipleinput\MultipleInput; + +... + +field($model, 'schedule')->widget(MultipleInput::className(), [ + 'max' => 4, + 'columns' => [ + [ + 'name' => 'user_id', + 'type' => 'dropDownList', + 'title' => 'User', + 'defaultValue' => 1, + 'items' => [ + 1 => 'User 1', + 2 => 'User 2' + ] + ], + [ + 'name' => 'day', + 'type' => \kartik\date\DatePicker::className(), + 'title' => 'Day', + 'value' => function($data) { + return $data['day']; + }, + 'items' => [ + '0' => 'Saturday', + '1' => 'Monday' + ], + 'options' => [ + 'pluginOptions' => [ + 'format' => 'dd.mm.yyyy', + 'todayHighlight' => true + ] + ] + ], + [ + 'name' => 'priority', + 'title' => 'Priority', + 'enableError' => true, + 'options' => [ + 'class' => 'input-priority' + ] + ] + ] + ]); +?> +``` +See more in [multiple columns](https://github.com/unclead/yii2-multiple-input/wiki/Usage#multiple-columns) ## Documentation From 08e495dfb7f0aa55559269f46c19a1ea23bc4fe3 Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Tue, 29 Aug 2017 13:52:36 +0300 Subject: [PATCH 077/247] global option enableError --- CHANGELOG.md | 2 +- src/MultipleInput.php | 9 ++++++++- src/TabularInput.php | 9 ++++++++- src/renderers/BaseRenderer.php | 10 ++++++++++ tests/unit/MultipleInputTest.php | 17 +++++++++++++++++ 5 files changed, 44 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 932eab7..f3b0d66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ Yii2 multiple input change log 2.10.0 ====== - +- #170: Added global options `enableError` - #154: Added missing js event: beforeAddRow 2.9.0 diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 2ba70f8..f07c033 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -129,6 +129,12 @@ class MultipleInput extends InputWidget */ public $sortable = false; + /** + * @var bool whether to render inline error for all input. Default to `false`. Can be override in `columns` + * @since 2.10 + */ + public $enableError = false; + /** * Initialization. * @@ -238,7 +244,8 @@ private function createRenderer() 'rowOptions' => $this->rowOptions, 'context' => $this, 'form' => $this->form, - 'sortable' => $this->sortable + 'sortable' => $this->sortable, + 'enableError' => $this->enableError, ]; if ($this->removeButtonOptions !== null) { diff --git a/src/TabularInput.php b/src/TabularInput.php index 3a37781..d6ac2c2 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -115,6 +115,12 @@ class TabularInput extends Widget */ public $sortable = false; + /** + * @var bool whether to render inline error for all input. Default to `false`. Can be override in `columns` + * @since 2.10 + */ + public $enableError = false; + /** * Initialization. * @@ -165,7 +171,8 @@ private function createRenderer() 'addButtonPosition' => $this->addButtonPosition, 'context' => $this, 'form' => $this->form, - 'sortable' => $this->sortable + 'sortable' => $this->sortable, + 'enableError' => $this->enableError, ]; if ($this->removeButtonOptions !== null) { diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 00941c4..ebdb22c 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -126,6 +126,12 @@ abstract class BaseRenderer extends Object implements RendererInterface */ public $sortable = false; + /** + * @var bool whether to render inline error for all input. Default to `false`. Can be override in `columns` + * @since 2.10 + */ + public $enableError = false; + /** * @inheritdoc */ @@ -243,6 +249,10 @@ protected function initColumns() $definition['attributeOptions'] = $this->attributeOptions; } + if (!array_key_exists('enableError', $definition)) { + $definition['enableError'] = $this->enableError; + } + $this->columns[$i] = Yii::createObject($definition); } } diff --git a/tests/unit/MultipleInputTest.php b/tests/unit/MultipleInputTest.php index 84ca2b4..6a56bab 100644 --- a/tests/unit/MultipleInputTest.php +++ b/tests/unit/MultipleInputTest.php @@ -28,6 +28,23 @@ public function testGuessColumn() $this->assertEquals($expected, $widget->columns); } + public function testGlobalErrorGuessColumn() + { + $model = new TestModel(); + + $widget = new MultipleInput([ + 'model' => $model, + 'attribute' => 'email', + 'enableError' => true, + ]); + + $expected = [ + ['name' => 'email', 'type' => MultipleInputColumn::TYPE_TEXT_INPUT, 'enableError' => true] + ]; + + $this->assertEquals($expected, $widget->columns); + } + public function testInitData() { $model = new TestModel(); From dad7c6f6f2b97420d3996cf9e3611e0dd2120203 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 30 Aug 2017 11:51:49 +0300 Subject: [PATCH 078/247] prepare to release 2.10.0 --- CHANGELOG.md | 3 +++ README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f3b0d66..b2ab7ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ Yii2 multiple input change log ============================== +2.11.0 +====== + 2.10.0 ====== - #170: Added global options `enableError` diff --git a/README.md b/README.md index efdf165..502a960 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.9.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.10.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 56bb51a..47c034e 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.9.0", + "version": "2.10.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 789412b..fbc4db7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.9.0", + "version": "2.10.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From cb0d5c314da2c65ef1f122135ecad2361327d1b9 Mon Sep 17 00:00:00 2001 From: eXeCUT Date: Tue, 3 Oct 2017 17:47:31 +0300 Subject: [PATCH 079/247] Added before row position --- src/MultipleInput.php | 1 + src/renderers/BaseRenderer.php | 8 ++++++++ src/renderers/RendererInterface.php | 1 + src/renderers/TableRenderer.php | 8 ++++++-- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/MultipleInput.php b/src/MultipleInput.php index f07c033..d7f973a 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -29,6 +29,7 @@ class MultipleInput extends InputWidget { const POS_HEADER = RendererInterface::POS_HEADER; const POS_ROW = RendererInterface::POS_ROW; + const POS_ROW_BEGIN = RendererInterface::POS_ROW_BEGIN; const POS_FOOTER = RendererInterface::POS_FOOTER; /** diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index ebdb22c..bfd70e7 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -375,6 +375,14 @@ protected function isAddButtonPositionRow() return in_array(self::POS_ROW, $this->addButtonPosition); } + /** + * @return bool + */ + protected function isAddButtonPositionRowBegin() + { + return in_array(self::POS_ROW_BEGIN, $this->addButtonPosition); + } + private function prepareIndexPlaceholder() { $this->indexPlaceholder = 'multiple_index_' . $this->id; diff --git a/src/renderers/RendererInterface.php b/src/renderers/RendererInterface.php index 9f739a5..74072f7 100644 --- a/src/renderers/RendererInterface.php +++ b/src/renderers/RendererInterface.php @@ -17,6 +17,7 @@ interface RendererInterface { const POS_HEADER = 'header'; const POS_ROW = 'row'; + const POS_ROW_BEGIN = 'row_begin'; const POS_FOOTER = 'footer'; /** diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index a714039..11a459c 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -172,6 +172,10 @@ private function renderRowContent($index = null, $item = null) { $cells = []; $hiddenInputs = []; + $isLastRow = $this->max === $this->min; + if (!$isLastRow && $this->isAddButtonPositionRowBegin()) { + $cells[] = $this->renderActionColumn($index); + } foreach ($this->columns as $column) { /* @var $column BaseColumn */ @@ -183,7 +187,7 @@ private function renderRowContent($index = null, $item = null) } } - if ($this->max !== $this->min) { + if (!$isLastRow && $this->isAddButtonPositionRow()) { $cells[] = $this->renderActionColumn($index); } @@ -292,7 +296,7 @@ private function getActionButton($index) if ($index < $this->min) { return ''; } elseif ($index === $this->min) { - return $this->isAddButtonPositionRow() ? $this->renderAddButton() : ''; + return ($this->isAddButtonPositionRow() || $this->isAddButtonPositionRowBegin()) ? $this->renderAddButton() : ''; } else { return $this->renderRemoveButton(); } From 930670124d3ba189e2d3a52d4cc10c0fb2eb7d4f Mon Sep 17 00:00:00 2001 From: eXeCUT Date: Wed, 4 Oct 2017 09:01:35 +0300 Subject: [PATCH 080/247] Added change log --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2ab7ac..5432411 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Yii2 multiple input change log 2.11.0 ====== +- Added the possibility to substitute buttons before rows 2.10.0 ====== From b7460b25a73e948b1b2467dba2749913c26f6d2e Mon Sep 17 00:00:00 2001 From: Ankit Padia Date: Mon, 9 Oct 2017 17:22:24 +0530 Subject: [PATCH 081/247] Update BaseColumn.php --- src/components/BaseColumn.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 35509ff..352334b 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -11,7 +11,7 @@ use Closure; use yii\base\InvalidConfigException; use yii\base\Model; -use yii\base\Object; +use yii\base\BaseObject; use yii\db\ActiveRecordInterface; use yii\helpers\ArrayHelper; use yii\helpers\Html; @@ -23,7 +23,7 @@ * * @package unclead\multipleinput\components */ -abstract class BaseColumn extends Object +abstract class BaseColumn extends BaseObject { const TYPE_TEXT_INPUT = 'textInput'; const TYPE_HIDDEN_INPUT = 'hiddenInput'; @@ -552,4 +552,4 @@ public function renderError($error) * @return mixed */ abstract public function getFirstError($index); -} \ No newline at end of file +} From 6ad609c43bd6d8b014b677981f29a6bdc28b2f65 Mon Sep 17 00:00:00 2001 From: Ankit Padia Date: Mon, 9 Oct 2017 17:23:00 +0530 Subject: [PATCH 082/247] Update BaseRenderer.php --- src/renderers/BaseRenderer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index bfd70e7..1b288dc 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -15,7 +15,7 @@ use yii\base\InvalidConfigException; use yii\base\Model; use yii\base\NotSupportedException; -use yii\base\Object; +use yii\base\BaseObject; use yii\db\ActiveRecordInterface; use yii\web\View; use yii\widgets\ActiveForm; @@ -29,7 +29,7 @@ * Class BaseRenderer * @package unclead\multipleinput\renderers */ -abstract class BaseRenderer extends Object implements RendererInterface +abstract class BaseRenderer extends BaseObject implements RendererInterface { /** * @var string the ID of the widget From e2c6459846e3f898075fbded96ea48fbcb2c19c9 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Tue, 10 Oct 2017 14:42:00 +0300 Subject: [PATCH 083/247] prepare to release 2.11.0 --- CHANGELOG.md | 4 ++++ README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5432411..3a90d90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ Yii2 multiple input change log ============================== +2.12.0 +====== + + 2.11.0 ====== - Added the possibility to substitute buttons before rows diff --git a/README.md b/README.md index 502a960..94189c1 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.10.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.11.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 47c034e..bb3cf23 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.10.0", + "version": "2.11.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index fbc4db7..29e21c8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.10.0", + "version": "2.11.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 35ab354aad3d19aa8e91d31ba473bf6c05c52165 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 11 Oct 2017 12:18:17 +0300 Subject: [PATCH 084/247] Update UPGRADE.md --- UPGRADE.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/UPGRADE.md b/UPGRADE.md index c9fa17c..65d76aa 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -8,6 +8,10 @@ if you want to upgrade from version A to version C and there is version B between A and C, you need to following the instructions for both A and B. +Upgrade 2.11.0 +--------------------------- +- Ensure you use yii2 2.0.13 and higher, otherwise you have to use previous version of the widget + Upgrade from 2.2.0 tp 2.3.0 --------------------------- From 7b869c32de91ea13933c281c13c93ab29ff6f0fb Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 11 Oct 2017 22:24:34 +0300 Subject: [PATCH 085/247] revert usage of BaseObject --- README.md | 2 +- UPGRADE.md | 4 ++-- composer.json | 2 +- package.json | 2 +- src/components/BaseColumn.php | 4 ++-- src/renderers/BaseRenderer.php | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 94189c1..224eac8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.11.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.11.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/UPGRADE.md b/UPGRADE.md index 65d76aa..8de1aa0 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -8,8 +8,8 @@ if you want to upgrade from version A to version C and there is version B between A and C, you need to following the instructions for both A and B. -Upgrade 2.11.0 ---------------------------- +Upgrade 2.12.0 (when yii2 2.0.13 will be released) +-------------------------------------------------- - Ensure you use yii2 2.0.13 and higher, otherwise you have to use previous version of the widget Upgrade from 2.2.0 tp 2.3.0 diff --git a/composer.json b/composer.json index bb3cf23..2551c23 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.11.0", + "version": "2.11.1", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 29e21c8..f066824 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.11.0", + "version": "2.11.1", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 352334b..ed77358 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -11,7 +11,7 @@ use Closure; use yii\base\InvalidConfigException; use yii\base\Model; -use yii\base\BaseObject; +use yii\base\Object; use yii\db\ActiveRecordInterface; use yii\helpers\ArrayHelper; use yii\helpers\Html; @@ -23,7 +23,7 @@ * * @package unclead\multipleinput\components */ -abstract class BaseColumn extends BaseObject +abstract class BaseColumn extends Object { const TYPE_TEXT_INPUT = 'textInput'; const TYPE_HIDDEN_INPUT = 'hiddenInput'; diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 1b288dc..bfd70e7 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -15,7 +15,7 @@ use yii\base\InvalidConfigException; use yii\base\Model; use yii\base\NotSupportedException; -use yii\base\BaseObject; +use yii\base\Object; use yii\db\ActiveRecordInterface; use yii\web\View; use yii\widgets\ActiveForm; @@ -29,7 +29,7 @@ * Class BaseRenderer * @package unclead\multipleinput\renderers */ -abstract class BaseRenderer extends BaseObject implements RendererInterface +abstract class BaseRenderer extends Object implements RendererInterface { /** * @var string the ID of the widget From 05ab8ba3ae7682204c988e3288c8b8d6110a902e Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 13 Oct 2017 14:09:36 +0300 Subject: [PATCH 086/247] fixed rendering of an action column --- README.md | 2 +- composer.json | 2 +- package.json | 2 +- src/TabularInput.php | 1 + src/renderers/TableRenderer.php | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 224eac8..e1ffe82 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.11.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.11.2 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 2551c23..ea49fd2 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.11.1", + "version": "2.11.2", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index f066824..4c65998 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.11.1", + "version": "2.11.2", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" diff --git a/src/TabularInput.php b/src/TabularInput.php index d6ac2c2..98e9038 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -26,6 +26,7 @@ class TabularInput extends Widget const POS_HEADER = RendererInterface::POS_HEADER; const POS_ROW = RendererInterface::POS_ROW; const POS_FOOTER = RendererInterface::POS_FOOTER; + const POS_ROW_BEGIN = RendererInterface::POS_ROW_BEGIN; /** * @var array diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 11a459c..7eb46d2 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -187,7 +187,7 @@ private function renderRowContent($index = null, $item = null) } } - if (!$isLastRow && $this->isAddButtonPositionRow()) { + if (!$isLastRow && !$this->isAddButtonPositionRow()) { $cells[] = $this->renderActionColumn($index); } From 06bc83b0508b0a9ec9de8622d6cae2433d9bac45 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Thu, 19 Oct 2017 22:58:28 +0300 Subject: [PATCH 087/247] fixed rendering of an action column --- README.md | 2 +- composer.json | 2 +- package.json | 2 +- src/renderers/TableRenderer.php | 38 ++++++++++++++++++++++----------- 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index e1ffe82..d701b39 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.11.2 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.11.3 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index ea49fd2..dc98137 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.11.2", + "version": "2.11.3", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 4c65998..55e4c83 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.11.2", + "version": "2.11.3", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 7eb46d2..55938e4 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -174,7 +174,7 @@ private function renderRowContent($index = null, $item = null) $hiddenInputs = []; $isLastRow = $this->max === $this->min; if (!$isLastRow && $this->isAddButtonPositionRowBegin()) { - $cells[] = $this->renderActionColumn($index); + $cells[] = $this->renderActionColumn($index, true); } foreach ($this->columns as $column) { @@ -187,8 +187,8 @@ private function renderRowContent($index = null, $item = null) } } - if (!$isLastRow && !$this->isAddButtonPositionRow()) { - $cells[] = $this->renderActionColumn($index); + if (!$isLastRow) { + $cells[] = $this->renderActionColumn($index, false); } if ($hiddenInputs) { @@ -276,30 +276,44 @@ public function renderCellContent($column, $index) * Renders the action column. * * @param null|int $index + * @param bool $isFirstColumn * @return string - * @throws \Exception */ - private function renderActionColumn($index = null) + private function renderActionColumn($index = null, $isFirstColumn = false) { - return Html::tag('td', $this->getActionButton($index), [ + return Html::tag('td', $this->getActionButton($index, $isFirstColumn), [ 'class' => 'list-cell__button', ]); } - private function getActionButton($index) + private function getActionButton($index, $isFirstColumn) { if ($index === null || $this->min === 0) { - return $this->renderRemoveButton(); + if ($isFirstColumn) { + return $this->isAddButtonPositionRowBegin() ? $this->renderRemoveButton() : ''; + } + + return $this->isAddButtonPositionRowBegin() ? '' : $this->renderRemoveButton(); } $index++; if ($index < $this->min) { return ''; - } elseif ($index === $this->min) { - return ($this->isAddButtonPositionRow() || $this->isAddButtonPositionRowBegin()) ? $this->renderAddButton() : ''; - } else { - return $this->renderRemoveButton(); } + + if ($index === $this->min) { + if ($isFirstColumn) { + return $this->isAddButtonPositionRowBegin() ? $this->renderAddButton() : ''; + } + + return $this->isAddButtonPositionRow() ? $this->renderAddButton() : ''; + } + + if ($isFirstColumn) { + return $this->isAddButtonPositionRowBegin() ? $this->renderRemoveButton() : ''; + } + + return $this->isAddButtonPositionRowBegin() ? '' : $this->renderRemoveButton(); } private function renderAddButton() From e9ce2d63624e08e797a4bf0e93d0b5f7e82c34c1 Mon Sep 17 00:00:00 2001 From: Ankit Padia Date: Fri, 3 Nov 2017 16:52:20 +0530 Subject: [PATCH 088/247] Rename yii\base\Object to yii\base\BaseObject. --- src/components/BaseColumn.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index ed77358..352334b 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -11,7 +11,7 @@ use Closure; use yii\base\InvalidConfigException; use yii\base\Model; -use yii\base\Object; +use yii\base\BaseObject; use yii\db\ActiveRecordInterface; use yii\helpers\ArrayHelper; use yii\helpers\Html; @@ -23,7 +23,7 @@ * * @package unclead\multipleinput\components */ -abstract class BaseColumn extends Object +abstract class BaseColumn extends BaseObject { const TYPE_TEXT_INPUT = 'textInput'; const TYPE_HIDDEN_INPUT = 'hiddenInput'; From dfeb61549c90814db35d0fb80b15ac28117efba4 Mon Sep 17 00:00:00 2001 From: Ankit Padia Date: Fri, 3 Nov 2017 16:53:27 +0530 Subject: [PATCH 089/247] Rename yii\base\Object to yii\base\BaseObject. --- src/renderers/BaseRenderer.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index bfd70e7..1b288dc 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -15,7 +15,7 @@ use yii\base\InvalidConfigException; use yii\base\Model; use yii\base\NotSupportedException; -use yii\base\Object; +use yii\base\BaseObject; use yii\db\ActiveRecordInterface; use yii\web\View; use yii\widgets\ActiveForm; @@ -29,7 +29,7 @@ * Class BaseRenderer * @package unclead\multipleinput\renderers */ -abstract class BaseRenderer extends Object implements RendererInterface +abstract class BaseRenderer extends BaseObject implements RendererInterface { /** * @var string the ID of the widget From 12fcc7321b490c32cabaaafcdfef4e731b0807a1 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 12 Nov 2017 16:54:34 +0300 Subject: [PATCH 090/247] release 2.12.0 --- CHANGELOG.md | 4 ++++ README.md | 2 +- UPGRADE.md | 4 ++-- composer.json | 4 ++-- package.json | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a90d90..fdc4d61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ Yii2 multiple input change log ============================== +2.13.0 +====== + 2.12.0 ====== +- Rename yii\base\Object to yii\base\BaseObject 2.11.0 diff --git a/README.md b/README.md index d701b39..436cc37 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.11.3 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.12.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/UPGRADE.md b/UPGRADE.md index 8de1aa0..db7f21e 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -8,8 +8,8 @@ if you want to upgrade from version A to version C and there is version B between A and C, you need to following the instructions for both A and B. -Upgrade 2.12.0 (when yii2 2.0.13 will be released) --------------------------------------------------- +Upgrade 2.12.0 +-------------- - Ensure you use yii2 2.0.13 and higher, otherwise you have to use previous version of the widget Upgrade from 2.2.0 tp 2.3.0 diff --git a/composer.json b/composer.json index dc98137..b4d723d 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.11.3", + "version": "2.12.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", @@ -23,7 +23,7 @@ ], "require": { "php": ">=5.4.0", - "yiisoft/yii2": "*" + "yiisoft/yii2": ">=2.0.13" }, "require-dev": { "phpunit/phpunit": "5.7.*" diff --git a/package.json b/package.json index 55e4c83..0c303a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.11.3", + "version": "2.12.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From be9194b0430fd9bcc4c0eae97de73b235b33249f Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Thu, 4 Jan 2018 17:07:03 +0300 Subject: [PATCH 091/247] added ability to allow an empty list (or set min property to 0) for TabularInput --- CHANGELOG.md | 1 + examples/views/tabular-input.php | 2 ++ src/TabularColumn.php | 19 +++++++++--------- src/TabularInput.php | 34 ++++++++++++++++++++++++++------ 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fdc4d61..05b1fc6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Yii2 multiple input change log 2.13.0 ====== +- #152 added ability to allow an empty list (or set `min` property to 0) for TabularInput 2.12.0 ====== diff --git a/examples/views/tabular-input.php b/examples/views/tabular-input.php index a50b8de..5d0e975 100644 --- a/examples/views/tabular-input.php +++ b/examples/views/tabular-input.php @@ -20,6 +20,8 @@ $models, + 'modelClass' => Item::class, + 'min' => 0, 'attributeOptions' => [ 'enableAjaxValidation' => true, 'enableClientValidation' => false, diff --git a/src/TabularColumn.php b/src/TabularColumn.php index d797d3b..97600c6 100644 --- a/src/TabularColumn.php +++ b/src/TabularColumn.php @@ -17,6 +17,11 @@ */ class TabularColumn extends BaseColumn { + /** + * @var TabularInput + */ + public $context; + /** * Returns element's name. * @@ -63,16 +68,10 @@ protected function ensureModel($model) */ public function setModel($model) { - $currentModel = $this->getModel(); - - // If model is null and current model is not empty it means that widget renders a template - // In this case we have to unset all model attributes - if ($model === null && $currentModel !== null) { - foreach ($currentModel->attributes() as $attribute) { - $currentModel->$attribute = null; - } - } else { - parent::setModel($model); + if ($model === null) { + $model = \Yii::createObject(['class' => $this->context->modelClass]); } + + parent::setModel($model); } } \ No newline at end of file diff --git a/src/TabularInput.php b/src/TabularInput.php index 98e9038..6e65ad7 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -68,7 +68,7 @@ class TabularInput extends Widget /** * @var Model[]|ActiveRecordInterface[] */ - public $models; + public $models = []; /** * @var string|array position of add button. @@ -122,6 +122,13 @@ class TabularInput extends Widget */ public $enableError = false; + /** + * @var string a class of model which is used to render the widget. + * You have to specify this property in case you set `min` property to 0 (when you want to allow an empty list) + * @since 2.13 + */ + public $modelClass; + /** * Initialization. * @@ -129,18 +136,33 @@ class TabularInput extends Widget */ public function init() { - if (empty($this->models)) { - throw new InvalidConfigException('You must specify "models"'); + if (empty($this->models) && !$this->modelClass) { + throw new InvalidConfigException('You must at least specify "models" or "modelClass"'); } if ($this->form !== null && !$this->form instanceof ActiveForm) { throw new InvalidConfigException('Property "form" must be an instance of yii\widgets\ActiveForm'); } - foreach ($this->models as $model) { - if (!$model instanceof Model) { - throw new InvalidConfigException('Model has to be an instance of yii\base\Model'); + if (!is_array($this->models)) { + throw new InvalidConfigException('Property "models" must be an array'); + } + + if ($this->models) { + $modelClasses = []; + foreach ($this->models as $model) { + if (!$model instanceof Model) { + throw new InvalidConfigException('Model has to be an instance of yii\base\Model'); + } + + $modelClasses[get_class($model)] = true; + } + + if (count($modelClasses) > 1) { + throw new InvalidConfigException("You cannot use models of different classes"); } + + $this->modelClass = key($modelClasses); } parent::init(); From 983178c8b2eab2aa5ee44ad62366931305ef7141 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Mon, 8 Jan 2018 23:19:38 +0300 Subject: [PATCH 092/247] bump version --- CHANGELOG.md | 2 ++ README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 05b1fc6..b5037cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ Yii2 multiple input change log ============================== +2.14.0 +====== 2.13.0 ====== diff --git a/README.md b/README.md index 436cc37..1829614 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.12.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.13.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index b4d723d..2eb50bd 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.12.0", + "version": "2.13.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 0c303a0..d1d3430 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.12.0", + "version": "2.13.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 576e43e0fa7d171d6925c676bda9af0f1a8fedbb Mon Sep 17 00:00:00 2001 From: Alexander Nesterov Date: Thu, 22 Feb 2018 11:13:30 +0200 Subject: [PATCH 093/247] added clone button --- README.md | 3 +- src/MultipleInput.php | 15 +++++++ src/TabularInput.php | 14 +++++++ src/assets/src/js/jquery.multipleInput.js | 19 +++++++++ src/renderers/BaseRenderer.php | 18 ++++++++ src/renderers/ListRenderer.php | 30 ++++++++++++++ src/renderers/TableRenderer.php | 50 ++++++++++++++++++++--- 7 files changed, 143 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1829614..e4ea10d 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,8 @@ use unclead\multipleinput\MultipleInput; 'min' => 2, // should be at least 2 rows 'allowEmptyList' => false, 'enableGuessTitle' => true, - 'addButtonPosition' => MultipleInput::POS_HEADER // show add button in the header + 'addButtonPosition' => MultipleInput::POS_HEADER, // show add button in the header + 'cloneButton' => true, // show clone button ]) ->label(false); ?> diff --git a/src/MultipleInput.php b/src/MultipleInput.php index d7f973a..78aa4e5 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -64,6 +64,11 @@ class MultipleInput extends InputWidget */ public $addButtonOptions; + /** + * @var array the HTML options for the `clone` button + */ + public $cloneButtonOptions; + /** * @var bool whether to allow the empty list */ @@ -136,6 +141,11 @@ class MultipleInput extends InputWidget */ public $enableError = false; + /** + * @var bool whether to render clone button. Default to `false`. + */ + public $cloneButton = false; + /** * Initialization. * @@ -247,6 +257,7 @@ private function createRenderer() 'form' => $this->form, 'sortable' => $this->sortable, 'enableError' => $this->enableError, + 'cloneButton' => $this->cloneButton, ]; if ($this->removeButtonOptions !== null) { @@ -257,6 +268,10 @@ private function createRenderer() $config['addButtonOptions'] = $this->addButtonOptions; } + if ($this->cloneButtonOptions !== null) { + $config['cloneButtonOptions'] = $this->cloneButtonOptions; + } + $config['class'] = $this->rendererClass ?: TableRenderer::className(); return Yii::createObject($config); diff --git a/src/TabularInput.php b/src/TabularInput.php index 6e65ad7..d38d9f2 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -60,6 +60,11 @@ class TabularInput extends Widget */ public $addButtonOptions; + /** + * @var array the HTML options for the `clone` button + */ + public $cloneButtonOptions; + /** * @var bool whether to allow the empty list */ @@ -122,6 +127,11 @@ class TabularInput extends Widget */ public $enableError = false; + /** + * @var bool whether to render clone button. Default to `false`. + */ + public $cloneButton = false; + /** * @var string a class of model which is used to render the widget. * You have to specify this property in case you set `min` property to 0 (when you want to allow an empty list) @@ -206,6 +216,10 @@ private function createRenderer() $config['addButtonOptions'] = $this->addButtonOptions; } + if ($this->cloneButtonOptions !== null) { + $config['cloneButtonOptions'] = $this->cloneButtonOptions; + } + if (!$this->rendererClass) { $this->rendererClass = TableRenderer::className(); } diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 4d0f581..9ac9908 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -129,6 +129,11 @@ addInput($(this)); }); + $wrapper.on('click.multipleInput', '.js-input-clone', function (e) { + e.stopPropagation(); + addInput($(this), getRowValues(e)); + }); + var i = 0, event = $.Event(events.afterInit); @@ -430,6 +435,20 @@ }).length; }; + var getRowValues = function (event) { + var tr = $(event.currentTarget).closest('tr'); + var values = {}; + tr.find('td').each(function (index, value) { + $(value).find('input, select, textarea').each(function (k, v) { + var ele = $(v), + id = getInputId(ele), + obj = $('#' + id); + values[id] = obj.val(); + }); + }); + return values; + }; + String.prototype.replaceAll = function (search, replace) { return this.split(search).join(replace); }; diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 1b288dc..6881854 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -74,6 +74,11 @@ abstract class BaseRenderer extends BaseObject implements RendererInterface */ public $addButtonOptions = []; + /** + * @var array the HTML options for the `clone` button + */ + public $cloneButtonOptions = []; + /** * @var bool whether to allow the empty list */ @@ -132,6 +137,11 @@ abstract class BaseRenderer extends BaseObject implements RendererInterface */ public $enableError = false; + /** + * @var bool whether to render clone button. Default to `false`. + */ + public $cloneButton = false; + /** * @inheritdoc */ @@ -224,6 +234,14 @@ private function prepareButtons() if (!array_key_exists('label', $this->addButtonOptions)) { $this->addButtonOptions['label'] = Html::tag('i', null, ['class' => 'glyphicon glyphicon-plus']); } + + if (!array_key_exists('class', $this->cloneButtonOptions)) { + $this->cloneButtonOptions['class'] = 'btn btn-info'; + } + + if (!array_key_exists('label', $this->cloneButtonOptions)) { + $this->cloneButtonOptions['label'] = Html::tag('i', null, ['class' => 'glyphicon glyphicon-duplicate']); + } } diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index d89a2d2..e8eacdb 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -140,6 +140,7 @@ private function renderRowContent($index = null, $item = null) $content[] = $this->renderActionColumn($index); } + $this->cloneButton && $content[] = $this->renderCloneColumn(); $content = Html::tag('tr', implode("\n", $content), $this->prepareRowOptions($index, $item)); if ($index !== null) { @@ -234,6 +235,19 @@ private function renderActionColumn($index = null) ]); } + /** + * Renders the clone column. + * + * @return string + * @throws \Exception + */ + private function renderCloneColumn() + { + return Html::tag('td', $this->renderCloneButton(), [ + 'class' => 'list-cell__button', + ]); + } + private function getActionButton($index) { if ($index === null || $this->min === 0) { @@ -276,6 +290,22 @@ private function renderRemoveButton() return Html::tag('div', $this->removeButtonOptions['label'], $options); } + /** + * Renders clone button. + * + * @return string + * @throws \Exception + */ + private function renderCloneButton() + { + $options = [ + 'class' => 'btn multiple-input-list__btn js-input-clone', + ]; + Html::addCssClass($options, $this->cloneButtonOptions['class']); + + return Html::tag('div', $this->cloneButtonOptions['label'], $options); + } + /** * Returns template for using in js. * diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 55938e4..8deef9c 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -61,9 +61,9 @@ public function renderHeader() if ($this->max === null || ($this->max >= 1 && $this->max !== $this->min)) { $button = $this->isAddButtonPositionHeader() ? $this->renderAddButton() : ''; - $cells[] = Html::tag('th', $button, [ - 'class' => 'list-cell__button' - ]); + $cells[] = $this->renderButtonHeaderCell($button); + + $this->cloneButton && $cells[] = $this->renderButtonHeaderCell(); } return Html::tag('thead', Html::tag('tr', implode("\n", $cells))); @@ -127,6 +127,18 @@ private function renderHeaderCell($column) return Html::tag('th', $column->title, $options); } + /** + * Renders the button header cell. + * @param string + * @return string + */ + private function renderButtonHeaderCell($button = '') + { + return Html::tag('th', $button, [ + 'class' => 'list-cell__button' + ]); + } + /** * Renders the body. * @@ -186,9 +198,9 @@ private function renderRowContent($index = null, $item = null) $cells[] = $this->renderCellContent($column, $index); } } - + $this->cloneButton && $cells[] = $this->renderCloneColumn(); if (!$isLastRow) { - $cells[] = $this->renderActionColumn($index, false); + $cells[] = $this->renderActionColumn($index); } if ($hiddenInputs) { @@ -286,6 +298,18 @@ private function renderActionColumn($index = null, $isFirstColumn = false) ]); } + /** + * Renders the clone column. + * + * @return string + */ + private function renderCloneColumn() + { + return Html::tag('td', $this->renderCloneButton(), [ + 'class' => 'list-cell__button', + ]); + } + private function getActionButton($index, $isFirstColumn) { if ($index === null || $this->min === 0) { @@ -342,6 +366,22 @@ private function renderRemoveButton() return Html::tag('div', $this->removeButtonOptions['label'], $options); } + /** + * Renders clone button. + * + * @return string + * @throws \Exception + */ + private function renderCloneButton() + { + $options = [ + 'class' => 'btn multiple-input-list__btn js-input-clone', + ]; + Html::addCssClass($options, $this->cloneButtonOptions['class']); + + return Html::tag('div', $this->cloneButtonOptions['label'], $options); + } + /** * Returns template for using in js. * From cd61b1f5be0e83a5d0c1a3da591f5a8ca8c2ca40 Mon Sep 17 00:00:00 2001 From: Alexander Nesterov Date: Thu, 22 Feb 2018 23:07:18 +0200 Subject: [PATCH 094/247] refactoring --- src/assets/src/js/jquery.multipleInput.js | 7 +++---- src/renderers/ListRenderer.php | 5 ++++- src/renderers/TableRenderer.php | 9 +++++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 9ac9908..2689425 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -131,7 +131,7 @@ $wrapper.on('click.multipleInput', '.js-input-clone', function (e) { e.stopPropagation(); - addInput($(this), getRowValues(e)); + addInput($(this), getRowValues($(this))); }); var i = 0, @@ -435,10 +435,9 @@ }).length; }; - var getRowValues = function (event) { - var tr = $(event.currentTarget).closest('tr'); + var getRowValues = function (element) { var values = {}; - tr.find('td').each(function (index, value) { + element.closest('tr').find('td').each(function (index, value) { $(value).find('input, select, textarea').each(function (k, v) { var ele = $(v), id = getInputId(ele), diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index e8eacdb..49aabd8 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -140,7 +140,10 @@ private function renderRowContent($index = null, $item = null) $content[] = $this->renderActionColumn($index); } - $this->cloneButton && $content[] = $this->renderCloneColumn(); + if ($this->cloneButton) { + $content[] = $this->renderCloneColumn(); + } + $content = Html::tag('tr', implode("\n", $content), $this->prepareRowOptions($index, $item)); if ($index !== null) { diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 8deef9c..2542de0 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -63,7 +63,9 @@ public function renderHeader() $cells[] = $this->renderButtonHeaderCell($button); - $this->cloneButton && $cells[] = $this->renderButtonHeaderCell(); + if ($this->cloneButton) { + $cells[] = $this->renderButtonHeaderCell(); + } } return Html::tag('thead', Html::tag('tr', implode("\n", $cells))); @@ -198,7 +200,10 @@ private function renderRowContent($index = null, $item = null) $cells[] = $this->renderCellContent($column, $index); } } - $this->cloneButton && $cells[] = $this->renderCloneColumn(); + if ($this->cloneButton) { + $cells[] = $this->renderCloneColumn(); + } + if (!$isLastRow) { $cells[] = $this->renderActionColumn($index); } From 957710d5d71d850c7fe3804d50083d8a579893ea Mon Sep 17 00:00:00 2001 From: Dmitry Lavrenyuk Date: Wed, 7 Mar 2018 23:24:30 +1000 Subject: [PATCH 095/247] added extra buttons --- src/MultipleInput.php | 15 ++++++++++++ src/renderers/BaseRenderer.php | 42 +++++++++++++++++++++++++++++++++ src/renderers/ListRenderer.php | 7 ++++-- src/renderers/TableRenderer.php | 11 +++++---- 4 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 78aa4e5..3cb0998 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -146,6 +146,20 @@ class MultipleInput extends InputWidget */ public $cloneButton = false; + /** + * @var string|\Closure the HTML content that will be rendered after the buttons. + * + * ```php + * function ($model, $index, $context) + * ``` + * + * - `$model`: the current data model being rendered + * - `$index`: the zero-based index of the data model in the model array + * - `$context`: the MultipleInput widget object + * + */ + public $extraButtons; + /** * Initialization. * @@ -258,6 +272,7 @@ private function createRenderer() 'sortable' => $this->sortable, 'enableError' => $this->enableError, 'cloneButton' => $this->cloneButton, + 'extraButtons' => $this->extraButtons, ]; if ($this->removeButtonOptions !== null) { diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 6881854..0b94b5a 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -13,6 +13,7 @@ use yii\helpers\Html; use yii\helpers\Json; use yii\base\InvalidConfigException; +use yii\base\InvalidParamException; use yii\base\Model; use yii\base\NotSupportedException; use yii\base\BaseObject; @@ -142,6 +143,20 @@ abstract class BaseRenderer extends BaseObject implements RendererInterface */ public $cloneButton = false; + /** + * @var string|\Closure the HTML content that will be rendered after the buttons. + * + * ```php + * function ($model, $index, $context) + * ``` + * + * - `$model`: the current data model being rendered + * - `$index`: the zero-based index of the data model in the model array + * - `$context`: the MultipleInput widget object + * + */ + public $extraButtons; + /** * @inheritdoc */ @@ -275,6 +290,33 @@ protected function initColumns() } } + /** + * Render extra content in action column. + * + * @param $index + * @param $item + * + * @return string + */ + protected function getExtraButtons($index, $item) + { + if (!$this->extraButtons) { + return ''; + } + + if (is_callable($this->extraButtons)) { + $content = call_user_func($this->extraButtons, $item, $index, $this->context); + } else { + $content = $this->extraButtons; + } + + if (!is_string($content)) { + throw new InvalidParamException('Property "extraButtons" must return string.'); + } + + return $content; + } + public function render() { $this->initColumns(); diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index 49aabd8..cbcd4be 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -228,12 +228,15 @@ public function renderCellContent($column, $index) * Renders the action column. * * @param null|int $index + * @param null|ActiveRecordInterface|array $item * @return string * @throws \Exception */ - private function renderActionColumn($index = null) + private function renderActionColumn($index = null, $item = null) { - return Html::tag('td', $this->getActionButton($index), [ + $content = $this->getActionButton($index) . $this->getExtraButtons($index, $item); + + return Html::tag('td', $content, [ 'class' => 'list-cell__button', ]); } diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 2542de0..71a5c71 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -188,7 +188,7 @@ private function renderRowContent($index = null, $item = null) $hiddenInputs = []; $isLastRow = $this->max === $this->min; if (!$isLastRow && $this->isAddButtonPositionRowBegin()) { - $cells[] = $this->renderActionColumn($index, true); + $cells[] = $this->renderActionColumn($index, $item, true); } foreach ($this->columns as $column) { @@ -205,7 +205,7 @@ private function renderRowContent($index = null, $item = null) } if (!$isLastRow) { - $cells[] = $this->renderActionColumn($index); + $cells[] = $this->renderActionColumn($index, $item); } if ($hiddenInputs) { @@ -293,12 +293,15 @@ public function renderCellContent($column, $index) * Renders the action column. * * @param null|int $index + * @param null|ActiveRecordInterface|array $item * @param bool $isFirstColumn * @return string */ - private function renderActionColumn($index = null, $isFirstColumn = false) + private function renderActionColumn($index = null, $item = null, $isFirstColumn = false) { - return Html::tag('td', $this->getActionButton($index, $isFirstColumn), [ + $content = $this->getActionButton($index, $isFirstColumn) . $this->getExtraButtons($index, $item); + + return Html::tag('td', $content, [ 'class' => 'list-cell__button', ]); } From 2e8baae9014affb04f2bcebc16f67508172f9495 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Thu, 8 Mar 2018 10:53:30 +0300 Subject: [PATCH 096/247] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5037cb..73a38b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,9 @@ Yii2 multiple input change log ============================== + 2.14.0 ====== +- #202 added extra buttons (dimmitri) 2.13.0 ====== From dbfd58641ed2338c3771561fb8f726f56e337351 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 9 Mar 2018 18:27:43 +0300 Subject: [PATCH 097/247] prepare to release 2.14.0 --- CHANGELOG.md | 4 ++++ README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73a38b6..f63d79d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ Yii2 multiple input change log ============================== +2.15.0 (in development) +======================= + 2.14.0 ====== - #202 added extra buttons (dimmitri) +- PR#201 added optional clone button (alex-nesterov) 2.13.0 ====== diff --git a/README.md b/README.md index e4ea10d..b9a4e0c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.13.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.14.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 2eb50bd..f4df911 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.13.0", + "version": "2.14.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index d1d3430..3b29d35 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.13.0", + "version": "2.14.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From f3d18692c1605da27837bfc082ca762c63278f53 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 9 Mar 2018 18:30:24 +0300 Subject: [PATCH 098/247] fixed README --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index b9a4e0c..199d6b6 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,6 @@ use unclead\multipleinput\MultipleInput; 'allowEmptyList' => false, 'enableGuessTitle' => true, 'addButtonPosition' => MultipleInput::POS_HEADER, // show add button in the header - 'cloneButton' => true, // show clone button ]) ->label(false); ?> From 3ac36565204beee3124a80c4cf07f1aac7790b7d Mon Sep 17 00:00:00 2001 From: Alexander Nesterov Date: Tue, 10 Apr 2018 10:20:56 +0300 Subject: [PATCH 099/247] added clone button example to readme --- README.md | 44 ++++++++++++++++++++++++++++++ resources/images/clone-button.gif | Bin 0 -> 221258 bytes 2 files changed, 44 insertions(+) create mode 100644 resources/images/clone-button.gif diff --git a/README.md b/README.md index 199d6b6..4360ffb 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,50 @@ use unclead\multipleinput\MultipleInput; ``` See more in [multiple columns](https://github.com/unclead/yii2-multiple-input/wiki/Usage#multiple-columns) +### Clone filled rows +![Clone button example](https://raw.githubusercontent.com/unclead/yii2-multiple-input/master/resources/images/clone-button.gif) +```php +use unclead\multipleinput\MultipleInput; + +... + +field($model, 'products')->widget(MultipleInput::className(), [ + 'max' => 10, + 'cloneButton' => true, + 'columns' => [ + [ + 'name' => 'product_id', + 'type' => 'dropDownList', + 'title' => 'Special Products', + 'defaultValue' => 1, + 'items' => [ + 1 => 'id: 1, price: $19.99, title: product1', + 2 => 'id: 2, price: $29.99, title: product2', + 3 => 'id: 3, price: $39.99, title: product3', + 4 => 'id: 4, price: $49.99, title: product4', + 5 => 'id: 5, price: $59.99, title: product5', + ], + ], + [ + 'name' => 'time', + 'type' => DateTimePicker::className(), + 'title' => 'due date', + 'defaultValue' => date('d-m-Y h:i') + ], + [ + 'name' => 'count', + 'title' => 'Count', + 'defaultValue' => 1, + 'enableError' => true, + 'options' => [ + 'type' => 'number', + 'class' => 'input-priority', + ] + ] + ] +])->label(false); +``` + ## Documentation You can find a full version of documentation in [wiki](https://github.com/unclead/yii2-multiple-input/wiki/) diff --git a/resources/images/clone-button.gif b/resources/images/clone-button.gif new file mode 100644 index 0000000000000000000000000000000000000000..f3718d003eb8ec2146e000aed8f9aaca7efc1412 GIT binary patch literal 221258 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

    Multiple columns

    field($model, 'schedule')->widget(MultipleInput::className(), [ +echo $form->field($model, 'schedule')->label(false)->widget(MultipleInput::className(), [ 'id' => 'examplemodel-schedule', 'max' => 4, 'allowEmptyList' => true, @@ -58,6 +58,7 @@ } return $options; }, + 'cloneButton' => true, 'columns' => [ [ 'name' => 'user_id', diff --git a/src/assets/src/css/multiple-input.css b/src/assets/src/css/multiple-input.css index c7ef1e0..a7361cb 100644 --- a/src/assets/src/css/multiple-input.css +++ b/src/assets/src/css/multiple-input.css @@ -59,6 +59,10 @@ table.multiple-input-list tr > th { padding-left: 5px; } +.multiple-input-list.list-renderer .list-cell__button:last-child { + padding-right: 15px; +} + .multiple-input-list.list-renderer tbody .list-cell__button .btn{ margin-top: 25px; } diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 2689425..92156b1 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -151,7 +151,7 @@ // fetch default attribute options from active from attribute if (typeof attribute === 'object') { $.each(attribute, function (key, value) { - if (['id', 'input', 'container'].indexOf(key) == -1) { + if (['id', 'input', 'container'].indexOf(key) === -1) { defaultAttributeOptions[key] = value; } }); @@ -199,7 +199,7 @@ remove: function (index) { var row = null; - if (index != undefined) { + if (index !== undefined) { row = $(this).find('.js-input-remove:eq(' + index + ')'); } else { row = $(this).find('.js-input-remove').last(); @@ -239,7 +239,7 @@ template = settings.template, inputList = $wrapper.children('.multiple-input-list').first(); - if (settings.max != null && getCurrentIndex($wrapper) >= settings.max) { + if (settings.max !== null && getCurrentIndex($wrapper) >= settings.max) { return; } @@ -287,10 +287,10 @@ if (values) { var val = values[index]; - if (tag == 'INPUT' || tag == 'TEXTAREA') { + if (tag === 'INPUT' || tag === 'TEXTAREA') { obj.val(val); - } else if (tag == 'SELECT') { - if (val && val.indexOf('option') != -1) { + } else if (tag === 'SELECT') { + if (val && val.indexOf('option') !== -1) { obj.append(val); } else { var option = obj.find('option[value="' + val + '"]'); @@ -362,7 +362,7 @@ // do not add attribute which are not the part of widget - if (wrapper.length == 0) { + if (wrapper.length === 0) { return; } diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 8694eda..8c0c6f3 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),f=p.closest("form"),c=u.inputId;for(m in u.jsInit){var v=document.createElement("script");v.innerHTML=u.jsInit[m],document.body.appendChild(v)}p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),p.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),d(t(this)))});var m=0,h=t.Event(e.afterInit),I=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)==-1&&(i[t]=e)}),f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(I),p.trigger(h)}else m++;(0===f.length||m>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(I),p.trigger(h))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!=u.max&&s(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),v=t.Event(e.beforeAddRow);if(l.trigger(v,[c]),v.result!==!1){if(c.hide().appendTo(f).fadeIn(300),r instanceof Object){var m=[];for(var h in r)r.hasOwnProperty(h)&&m.push(r[h]);r=m}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var y=0;t(d).find("input, select, textarea").each(function(e,i){var l=t(i),a=i.tagName,u=p(l),s=t("#"+u);if(r){var d=r[y];if("INPUT"==a||"TEXTAREA"==a)s.val(d);else if("SELECT"==a)if(d&&d.indexOf("option")!=-1)s.append(d);else{var f=s.find('option[value="'+d+'"]');f.length&&s.val(d)}}n&&o(l),y++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[c])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(e,i){u(t(i))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!=r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(e){var i=p(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var i={};return e.closest("tr").find("td").each(function(e,n){t(n).find("input, select, textarea").each(function(e,n){var r=t(n),l=p(r),a=t("#"+l);i[l]=a.val()})}),i};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),f=p.closest("form"),c=u.inputId;for(m in u.jsInit){var v=document.createElement("script");v.innerHTML=u.jsInit[m],document.body.appendChild(v)}p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),p.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),d(t(this)))});var m=0,h=t.Event(e.afterInit),I=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(i[t]=e)}),f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(I),p.trigger(h)}else m++;(0===f.length||m>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(I),p.trigger(h))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!==u.max&&s(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),v=t.Event(e.beforeAddRow);if(l.trigger(v,[c]),v.result!==!1){if(c.hide().appendTo(f).fadeIn(300),r instanceof Object){var m=[];for(var h in r)r.hasOwnProperty(h)&&m.push(r[h]);r=m}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var y=0;t(d).find("input, select, textarea").each(function(e,i){var l=t(i),a=i.tagName,u=p(l),s=t("#"+u);if(r){var d=r[y];if("INPUT"===a||"TEXTAREA"===a)s.val(d);else if("SELECT"===a)if(d&&d.indexOf("option")!==-1)s.append(d);else{var f=s.find('option[value="'+d+'"]');f.length&&s.val(d)}}n&&o(l),y++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[c])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(e,i){u(t(i))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!==r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(e){var i=p(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var i={};return e.closest("tr").find("td").each(function(e,n){t(n).find("input, select, textarea").each(function(e,n){var r=t(n),l=p(r),a=t("#"+l);i[l]=a.val()})}),i};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index 95a3704..9cf85ef 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -57,6 +57,11 @@ public function renderHeader() $content = []; $content[] = Html::tag('td', ' '); + + if ($this->cloneButton) { + $content[] = Html::tag('td', ' '); + } + $content[] = Html::tag('td', $button, [ 'class' => 'list-cell__button', ]); diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 0968231..4940183 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -58,8 +58,6 @@ public function renderHeader() $cells[] = $this->renderHeaderCell($column); } - - if ($this->max === null || ($this->max >= 1 && $this->max !== $this->min)) { $button = $this->isAddButtonPositionHeader() ? $this->renderAddButton() : ''; From 122c0ed24f2b9460b1bd0e4d8e3939d478901603 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 30 Sep 2018 17:09:55 +0300 Subject: [PATCH 111/247] add support of DynamicModel --- src/MultipleInputColumn.php | 18 +++++++++++++----- src/TabularColumn.php | 4 ++-- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/MultipleInputColumn.php b/src/MultipleInputColumn.php index d5dd103..53053c5 100644 --- a/src/MultipleInputColumn.php +++ b/src/MultipleInputColumn.php @@ -10,6 +10,7 @@ use yii\base\InvalidConfigException; use yii\base\Model; +use yii\base\DynamicModel; use yii\db\ActiveRecordInterface; use yii\helpers\Html; use unclead\multipleinput\components\BaseColumn; @@ -40,13 +41,14 @@ public function init() /** * Returns element's name. * - * @param int|null $index current row index + * @param int|null|string $index current row index * @param bool $withPrefix whether to add prefix. + * * @return string */ public function getElementName($index, $withPrefix = true) { - if (is_null($index)) { + if ($index === null) { $index = '{' . $this->renderer->getIndexPlaceholder() . '}'; } @@ -99,11 +101,17 @@ protected function hasModelAttribute($name) if ($model->hasProperty($name)) { return true; - } elseif ($model instanceof ActiveRecordInterface && $model->hasAttribute($name)) { + } + + if ($model instanceof ActiveRecordInterface && $model->hasAttribute($name)) { return true; - } else { - return false; } + + if ($model instanceof DynamicModel && isset($model->{$name})) { + return true; + } + + return false; } /** diff --git a/src/TabularColumn.php b/src/TabularColumn.php index 97600c6..a87c241 100644 --- a/src/TabularColumn.php +++ b/src/TabularColumn.php @@ -25,13 +25,13 @@ class TabularColumn extends BaseColumn /** * Returns element's name. * - * @param int|null $index current row index + * @param int|null|string $index current row index * @param bool $withPrefix whether to add prefix. * @return string */ public function getElementName($index, $withPrefix = true) { - if (is_null($index)) { + if ($index === null) { $index = '{' . $this->renderer->getIndexPlaceholder() . '}'; } From 4b67422a6983f9c1a6e2b0f3d9dd4b8f29a39e91 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 30 Sep 2018 17:32:47 +0300 Subject: [PATCH 112/247] added event `afterDropRow` --- CHANGELOG.md | 2 ++ examples/views/multiple-input.php | 3 +++ src/assets/src/js/jquery.multipleInput.js | 10 +++++++++- src/assets/src/js/jquery.multipleInput.min.js | 2 +- src/renderers/BaseRenderer.php | 17 ++++++++++++++++- 5 files changed, 31 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 644e87c..c413996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ Yii2 multiple input change log - #228 added `iconMap` and `iconSource`property for MultipleInput and TabularInput - #228 changed the following methods to support icon class: BaseColumn->renderDragColumn(), TableRenderer->renderCellContent(), BaseRenderer->prepareButtons() +- #194 added support of yii\base\DynamicModel +- #186 added event `afterDropRow` 2.15.0 ======================= diff --git a/examples/views/multiple-input.php b/examples/views/multiple-input.php index 7109550..6845a96 100644 --- a/examples/views/multiple-input.php +++ b/examples/views/multiple-input.php @@ -49,6 +49,7 @@ echo $form->field($model, 'schedule')->label(false)->widget(MultipleInput::className(), [ 'id' => 'examplemodel-schedule', 'max' => 4, + 'sortable' => true, 'allowEmptyList' => true, 'rowOptions' => function($model) { $options = []; @@ -170,6 +171,8 @@ }).on('afterDeleteRow', function(e, item){ console.log('calls on after remove row event'); console.log('User_id:' + item.find('.list-cell__user_id').find('select').first().val()); + }).on('afterDropRow', function(e, item){ + console.log('calls on after drop row', item); }); JS; diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 92156b1..16f880c 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -52,7 +52,15 @@ * where event is an Event object. * */ - afterDeleteRow: 'afterDeleteRow' + afterDeleteRow: 'afterDeleteRow', + + /** + * afterDropRow event is triggered after drop the row in sortable mode. + * The signature of the event handler should be: + * function (event, row) + * where event is an Event object and row is html container of dragged row + */ + afterDropRow: 'afterDropRow' }; var defaultOptions = { diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 8c0c6f3..9827565 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),f=p.closest("form"),c=u.inputId;for(m in u.jsInit){var v=document.createElement("script");v.innerHTML=u.jsInit[m],document.body.appendChild(v)}p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),p.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),d(t(this)))});var m=0,h=t.Event(e.afterInit),I=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(i[t]=e)}),f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(I),p.trigger(h)}else m++;(0===f.length||m>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(I),p.trigger(h))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!==u.max&&s(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),v=t.Event(e.beforeAddRow);if(l.trigger(v,[c]),v.result!==!1){if(c.hide().appendTo(f).fadeIn(300),r instanceof Object){var m=[];for(var h in r)r.hasOwnProperty(h)&&m.push(r[h]);r=m}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var y=0;t(d).find("input, select, textarea").each(function(e,i){var l=t(i),a=i.tagName,u=p(l),s=t("#"+u);if(r){var d=r[y];if("INPUT"===a||"TEXTAREA"===a)s.val(d);else if("SELECT"===a)if(d&&d.indexOf("option")!==-1)s.append(d);else{var f=s.find('option[value="'+d+'"]');f.length&&s.val(d)}}n&&o(l),y++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[c])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(e,i){u(t(i))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!==r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(e){var i=p(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var i={};return e.closest("tr").find("td").each(function(e,n){t(n).find("input, select, textarea").each(function(e,n){var r=t(n),l=p(r),a=t("#"+l);i[l]=a.val()})}),i};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),f=p.closest("form"),c=u.inputId;for(m in u.jsInit){var v=document.createElement("script");v.innerHTML=u.jsInit[m],document.body.appendChild(v)}p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),p.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),d(t(this)))});var m=0,h=t.Event(e.afterInit),I=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(i[t]=e)}),f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(I),p.trigger(h)}else m++;(0===f.length||m>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(I),p.trigger(h))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!==u.max&&s(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),v=t.Event(e.beforeAddRow);if(l.trigger(v,[c]),v.result!==!1){if(c.hide().appendTo(f).fadeIn(300),r instanceof Object){var m=[];for(var h in r)r.hasOwnProperty(h)&&m.push(r[h]);r=m}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var y=0;t(d).find("input, select, textarea").each(function(e,i){var l=t(i),a=i.tagName,u=p(l),s=t("#"+u);if(r){var d=r[y];if("INPUT"===a||"TEXTAREA"===a)s.val(d);else if("SELECT"===a)if(d&&d.indexOf("option")!==-1)s.append(d);else{var f=s.find('option[value="'+d+'"]');f.length&&s.val(d)}}n&&o(l),y++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[c])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(e,i){u(t(i))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!==r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(e){var i=p(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var i={};return e.closest("tr").find("td").each(function(e,n){t(n).find("input, select, textarea").each(function(e,n){var r=t(n),l=p(r),a=t("#"+l);i[l]=a.val()})}),i};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 1f6c4f9..b13acaf 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -395,7 +395,22 @@ public function render() if($this->sortable) { MultipleInputSortableAsset::register($view); - $js .= "$('#{$this->id} table').sorting({containerSelector: 'table', itemPath: '> tbody', itemSelector: 'tr', placeholder: '', handle:'.drag-handle'});"; + $js .= <<id} table').sorting({ + containerSelector: 'table', + itemPath: '> tbody', + itemSelector: 'tr', + placeholder: '', + handle:'.drag-handle', + onDrop: function(item, container, _super, event) { + _super(item, container, _super, event); + + var wrapper = item.closest('.multiple-input').first(); + event = $.Event('afterDropRow'); + wrapper.trigger(event, [item]); + } + }); +JS; } $view->registerJs($js); From bc7fe00b7e63d4d01241bf4655273c3856c43b9f Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 30 Sep 2018 17:37:22 +0300 Subject: [PATCH 113/247] prepare to release 2.16.0 --- CHANGELOG.md | 6 +++++- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c413996..b6315ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ Yii2 multiple input change log ============================== -2.15.1 (in development) +2.17.0 (in development) ======================= + + +2.16.0 +====== - #220 fixed error message for clientValidation and ajaxValidation (antkaz) - #228 added `iconMap` and `iconSource`property for MultipleInput and TabularInput - #228 changed the following methods to support icon class: diff --git a/README.md b/README.md index a1ab56a..be03821 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.15.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.16.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 1691178..464ef01 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.15.0", + "version": "2.16.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index e8388f9..d88ad80 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.15.0", + "version": "2.16.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From ae0f7f932b43d22fa069a61c3446817cf6465c0a Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 6 Oct 2018 14:33:50 +0300 Subject: [PATCH 114/247] #215 collect all js script that has to be evaluate when add new row (not only from " on ready" section) --- CHANGELOG.md | 2 +- src/assets/src/js/jquery.multipleInput.js | 4 +- src/assets/src/js/jquery.multipleInput.min.js | 2 +- src/renderers/BaseRenderer.php | 68 +++++++++++-------- 4 files changed, 42 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6315ee..70c6626 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ Yii2 multiple input change log 2.17.0 (in development) ======================= - +- #215 collect all js script that has to be evaluate when add new row (not only from " on ready" section) 2.16.0 ====== diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 16f880c..f321faa 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -117,9 +117,7 @@ inputId = settings.inputId; for (i in settings.jsInit) { - var script = document.createElement("script"); - script.innerHTML = settings.jsInit[i]; - document.body.appendChild(script); + window.eval(settings.jsInit[i]); } $wrapper.data('multipleInput', { diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 9827565..8995111 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),f=p.closest("form"),c=u.inputId;for(m in u.jsInit){var v=document.createElement("script");v.innerHTML=u.jsInit[m],document.body.appendChild(v)}p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),p.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),d(t(this)))});var m=0,h=t.Event(e.afterInit),I=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(i[t]=e)}),f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(I),p.trigger(h)}else m++;(0===f.length||m>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(I),p.trigger(h))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!==u.max&&s(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),v=t.Event(e.beforeAddRow);if(l.trigger(v,[c]),v.result!==!1){if(c.hide().appendTo(f).fadeIn(300),r instanceof Object){var m=[];for(var h in r)r.hasOwnProperty(h)&&m.push(r[h]);r=m}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var y=0;t(d).find("input, select, textarea").each(function(e,i){var l=t(i),a=i.tagName,u=p(l),s=t("#"+u);if(r){var d=r[y];if("INPUT"===a||"TEXTAREA"===a)s.val(d);else if("SELECT"===a)if(d&&d.indexOf("option")!==-1)s.append(d);else{var f=s.find('option[value="'+d+'"]');f.length&&s.val(d)}}n&&o(l),y++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[c])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(e,i){u(t(i))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!==r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(e){var i=p(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var i={};return e.closest("tr").find("td").each(function(e,n){t(n).find("input, select, textarea").each(function(e,n){var r=t(n),l=p(r),a=t("#"+l);i[l]=a.val()})}),i};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),d=p.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),p.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),f(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(h),p.trigger(m)}else v++;(0===d.length||v>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(h),p.trigger(m))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,f=u.template,d=l.children(".multiple-input-list").first();if(!(null!==u.max&&s(l)>=u.max)){f=f.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(f),v=t.Event(e.beforeAddRow);if(l.trigger(v,[c]),v.result!==!1){if(c.hide().appendTo(d).fadeIn(300),r instanceof Object){var m=[];for(var h in r)r.hasOwnProperty(h)&&m.push(r[h]);r=m}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var w=0;t(f).find("input, select, textarea").each(function(e,i){var l=t(i),a=i.tagName,u=p(l),s=t("#"+u);if(r){var f=r[w];if("INPUT"===a||"TEXTAREA"===a)s.val(f);else if("SELECT"===a)if(f&&f.indexOf("option")!==-1)s.append(f);else{var d=s.find('option[value="'+f+'"]');d.length&&s.val(f)}}n&&o(l),w++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[c])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(e,i){u(t(i))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!==r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(e){var i=p(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},f=function(e){var i={};return e.closest("tr").find("td").each(function(e,n){t(n).find("input, select, textarea").each(function(e,n){var r=t(n),l=p(r),a=t("#"+l);i[l]=a.val()})}),i};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index b13acaf..6310588 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -339,7 +339,7 @@ public function render() $jsBefore= []; if (is_array($view->js)) { foreach ($view->js as $position => $scripts) { - foreach ($scripts as $key => $js) { + foreach ((array)$scripts as $key => $js) { if (!isset($jsBefore[$position])) { $jsBefore[$position] = []; } @@ -354,7 +354,7 @@ public function render() $jsInit = []; if (is_array($view->js)) { foreach ($view->js as $position => $scripts) { - foreach ($scripts as $key => $js) { + foreach ((array)$scripts as $key => $js) { if (isset($jsBefore[$position][$key])) { continue; } @@ -368,14 +368,15 @@ public function render() $template = $this->prepareTemplate(); $jsTemplates = []; - if (is_array($view->js) && isset($view->js[View::POS_READY])) { - foreach ($view->js[View::POS_READY] as $key => $js) { - if (isset($jsBefore[View::POS_READY][$key])) { - continue; + if (is_array($view->js)) { + foreach ($view->js as $position => $scripts) { + foreach ((array)$scripts as $key => $js) { + if (isset($jsBefore[$position][$key])) { + continue; + } + $jsTemplates[$key] = $js; + unset($view->js[$position][$key]); } - - $jsTemplates[$key] = $js; - unset($view->js[View::POS_READY][$key]); } } @@ -392,32 +393,41 @@ public function render() ]); $js = "jQuery('#{$this->id}').multipleInput($options);"; + $view->registerJs($js); - if($this->sortable) { - MultipleInputSortableAsset::register($view); - $js .= <<id} table').sorting({ - containerSelector: 'table', - itemPath: '> tbody', - itemSelector: 'tr', - placeholder: '', - handle:'.drag-handle', - onDrop: function(item, container, _super, event) { - _super(item, container, _super, event); - - var wrapper = item.closest('.multiple-input').first(); - event = $.Event('afterDropRow'); - wrapper.trigger(event, [item]); - } - }); -JS; + if ($this->sortable) { + $this->registerJsSortable(); } - $view->registerJs($js); - return $content; } + private function registerJsSortable() + { + $view = $this->context->getView(); + MultipleInputSortableAsset::register($view); + + // todo override when ListRenderer will use div markup + $options = Json::encode([ + 'containerSelector' => 'table', + 'itemPath' => '> tbody', + 'itemSelector' => 'tr', + 'placeholder' => '', + 'handle' => '.drag-handle', + 'onDrop' => new \yii\web\JsExpression(" + function(item, container, _super, event) { + _super(item, container, _super, event); + + var wrapper = item.closest('.multiple-input').first(); + event = $.Event('afterDropRow'); + wrapper.trigger(event, [item]); + } + ") + ]); + $js = "$('#{$this->id} table').sorting($options);"; + $view->registerJs($js); + } + /** * @return mixed * @throws NotSupportedException From 2c4ca72544aba69e62b09ded57dbc6a3ff477f55 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 6 Oct 2018 14:44:13 +0300 Subject: [PATCH 115/247] proper handling AR relations --- src/components/BaseColumn.php | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 93249f5..123e6f0 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -198,7 +198,12 @@ protected function prepareValue() } else { $value = null; if ($data instanceof ActiveRecordInterface ) { - $value = $data->getAttribute($this->name); + $relation = $data->getRelation($this->name, false); + if ($relation !== null) { + $value = $relation; + } else { + $value = $data->getAttribute($this->name); + } } elseif ($data instanceof Model) { $value = $data->{$this->name}; } elseif (is_array($data)) { @@ -207,7 +212,7 @@ protected function prepareValue() $value = $data; } - if ($this->isEmpty($value) && $this->defaultValue !== null) { + if ($this->defaultValue !== null && $this->isEmpty($value)) { $value = $this->defaultValue; } } From 46bb2af5b801ff20b673a837b0c8825502343e2b Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 6 Oct 2018 23:16:28 +0300 Subject: [PATCH 116/247] introduce the option `theme` to disable all bootstrap css classes + explicitly set tabindex for all inputs --- CHANGELOG.md | 2 + examples/views/multiple-input.php | 4 +- examples/views/tabular-input.php | 27 ++++----- src/MultipleInput.php | 50 +++++++++++----- src/MultipleInputColumn.php | 7 +-- src/TabularColumn.php | 7 +-- src/TabularInput.php | 63 ++++++++++++++++----- src/components/BaseColumn.php | 88 +++++++++++++++++++++-------- src/renderers/BaseRenderer.php | 49 ++++++++++++---- src/renderers/ListRenderer.php | 61 ++++++++++++-------- src/renderers/RendererInterface.php | 3 + src/renderers/TableRenderer.php | 28 ++++++--- 12 files changed, 268 insertions(+), 121 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70c6626..bcf807d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Yii2 multiple input change log 2.17.0 (in development) ======================= - #215 collect all js script that has to be evaluate when add new row (not only from " on ready" section) +- #198 introduce the option `theme` to disable all bootstrap css classes +- #197 explicitly set tabindex for all inputs 2.16.0 ====== diff --git a/examples/views/multiple-input.php b/examples/views/multiple-input.php index 6845a96..da59bc3 100644 --- a/examples/views/multiple-input.php +++ b/examples/views/multiple-input.php @@ -26,6 +26,7 @@ echo $form->field($model, 'emails')->widget(MultipleInput::className(), [ 'max' => 6, 'allowEmptyList' => false, + 'sortable' => true, 'columns' => [ [ 'name' => 'emails', @@ -60,6 +61,7 @@ return $options; }, 'cloneButton' => true, + 'columns' => [ [ 'name' => 'user_id', @@ -108,7 +110,7 @@ 'headerOptions' => [ 'style' => 'width: 250px;', 'class' => 'day-css-class' - ] + ], ], [ 'name' => 'priority', diff --git a/examples/views/tabular-input.php b/examples/views/tabular-input.php index 7c651a9..fc97965 100644 --- a/examples/views/tabular-input.php +++ b/examples/views/tabular-input.php @@ -1,6 +1,5 @@ $models, 'modelClass' => Item::class, - 'rendererClass' => ListRenderer::class, 'cloneButton' => true, + 'sortable' => true, 'min' => 0, 'addButtonPosition' => [ TabularInput::POS_HEADER, @@ -64,25 +63,21 @@ 'name' => 'description', 'title' => 'Description', ], -// [ -// 'name' => 'file', -// 'title' => 'File', -// 'type' => \vova07\fileapi\Widget::className(), -// 'options' => [ -// 'settings' => [ -// 'url' => ['site/fileapi-upload'] -// ] -// ] -// ], [ 'name' => 'date', - 'type' => \kartik\date\DatePicker::className(), + 'type' => \kartik\date\DatePicker::class, 'title' => 'Day', 'options' => [ + 'type' => \kartik\datecontrol\DateControl::FORMAT_DATE, 'pluginOptions' => [ - 'format' => 'dd.mm.yyyy', - 'todayHighlight' => true - ] + 'autoclose' => true, + 'format' => 'dd/mm/yyyy', + 'todayHighlight' => true, + ], + 'widgetOptions' => [ + 'type' => \kartik\date\DatePicker::TYPE_INPUT, + ], + ], 'headerOptions' => [ 'style' => 'width: 250px;', diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 0101eb0..4aaa1a9 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -32,6 +32,12 @@ class MultipleInput extends InputWidget const POS_ROW_BEGIN = RendererInterface::POS_ROW_BEGIN; const POS_FOOTER = RendererInterface::POS_FOOTER; + const THEME_DEFAULT = 'default'; + const THEME_BS = 'bootstrap'; + + const ICONS_SOURCE_GLYPHICONS = 'glyphicons'; + const ICONS_SOURCE_FONTAWESOME = 'fa'; + /** * @var ActiveRecordInterface[]|array[] input data */ @@ -174,24 +180,30 @@ class MultipleInput extends InputWidget * --icon library classes mapped for various controls */ public $iconMap = [ - 'glyphicons' => [ - 'drag-handle' => 'glyphicon glyphicon-menu-hamburger', - 'remove' => 'glyphicon glyphicon-remove', - 'add' => 'glyphicon glyphicon-plus', - 'clone' => 'glyphicon glyphicon-duplicate', + self::ICONS_SOURCE_GLYPHICONS => [ + 'drag-handle' => 'glyphicon glyphicon-menu-hamburger', + 'remove' => 'glyphicon glyphicon-remove', + 'add' => 'glyphicon glyphicon-plus', + 'clone' => 'glyphicon glyphicon-duplicate', ], - 'fa' => [ - 'drag-handle' => 'fa fa-bars', - 'remove' => 'fa fa-times', - 'add' => 'fa fa-plus', - 'clone' => 'fa fa-files-o', + self::ICONS_SOURCE_FONTAWESOME => [ + 'drag-handle' => 'fa fa-bars', + 'remove' => 'fa fa-times', + 'add' => 'fa fa-plus', + 'clone' => 'fa fa-files-o', ], ]; /** - * @var string - * --name of default icon library + * @var string the name of default icon library + */ + public $iconSource = self::ICONS_SOURCE_GLYPHICONS; + + /** + * @var string the CSS theme of the widget + * + * @todo Use bootstrap theme for BC. We can switch to default theme in major release */ - public $iconSource = 'glyphicons'; + public $theme = self::THEME_BS; /** * Initialization. @@ -289,12 +301,21 @@ private function createRenderer() array_unshift($this->columns, $drag); } + $available_themes = [ + self::THEME_BS, + self::THEME_DEFAULT + ]; + + if (!in_array($this->theme, $available_themes, true)) { + $this->theme = self::THEME_BS; + } + /** * set default icon map */ $iconMap = array_key_exists($this->iconSource, $this->iconMap) ? $this->iconMap[$this->iconSource] - : $this->iconMap['glyphicons']; + : $this->iconMap[self::ICONS_SOURCE_GLYPHICONS]; $config = [ 'id' => $this->getId(), @@ -315,6 +336,7 @@ private function createRenderer() 'extraButtons' => $this->extraButtons, 'layoutConfig' => $this->layoutConfig, 'iconMap' => $iconMap, + 'theme' => $this->theme ]; if ($this->removeButtonOptions !== null) { diff --git a/src/MultipleInputColumn.php b/src/MultipleInputColumn.php index 53053c5..3dc07a8 100644 --- a/src/MultipleInputColumn.php +++ b/src/MultipleInputColumn.php @@ -18,14 +18,11 @@ /** * Class MultipleInputColumn * @package unclead\multipleinput + * + * @property MultipleInput $context */ class MultipleInputColumn extends BaseColumn { - /** - * @var MultipleInput - */ - public $context; - /** * @throws InvalidConfigException */ diff --git a/src/TabularColumn.php b/src/TabularColumn.php index a87c241..f8aba91 100644 --- a/src/TabularColumn.php +++ b/src/TabularColumn.php @@ -14,14 +14,11 @@ /** * Class TabularColumn * @package unclead\multipleinput + * + * @property TabularInput $context */ class TabularColumn extends BaseColumn { - /** - * @var TabularInput - */ - public $context; - /** * Returns element's name. * diff --git a/src/TabularInput.php b/src/TabularInput.php index ef66dda..cf4e3b3 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -28,6 +28,12 @@ class TabularInput extends Widget const POS_FOOTER = RendererInterface::POS_FOOTER; const POS_ROW_BEGIN = RendererInterface::POS_ROW_BEGIN; + const THEME_DEFAULT = 'default'; + const THEME_BS = 'bootstrap'; + + const ICONS_SOURCE_GLYPHICONS = 'glyphicons'; + const ICONS_SOURCE_FONTAWESOME = 'fa'; + /** * @var array */ @@ -167,24 +173,31 @@ class TabularInput extends Widget * --icon library classes mapped for various controls */ public $iconMap = [ - 'glyphicons' => [ - 'drag-handle' => 'glyphicon glyphicon-menu-hamburger', - 'remove' => 'glyphicon glyphicon-remove', - 'add' => 'glyphicon glyphicon-plus', - 'clone' => 'glyphicon glyphicon-duplicate', + self::ICONS_SOURCE_GLYPHICONS => [ + 'drag-handle' => 'glyphicon glyphicon-menu-hamburger', + 'remove' => 'glyphicon glyphicon-remove', + 'add' => 'glyphicon glyphicon-plus', + 'clone' => 'glyphicon glyphicon-duplicate', ], - 'fa' => [ - 'drag-handle' => 'fa fa-bars', - 'remove' => 'fa fa-times', - 'add' => 'fa fa-plus', - 'clone' => 'fa fa-files-o', + self::ICONS_SOURCE_FONTAWESOME => [ + 'drag-handle' => 'fa fa-bars', + 'remove' => 'fa fa-times', + 'add' => 'fa fa-plus', + 'clone' => 'fa fa-files-o', ], ]; + + /** + * @var string the CSS theme of the widget + * + * @todo Use bootstrap theme for BC. We can switch to default theme in major release + */ + public $theme = self::THEME_BS; + /** - * @var string - * --name of default icon library + * @var string the name of default icon library */ - public $iconSource = 'glyphicons'; + public $iconSource = self::ICONS_SOURCE_GLYPHICONS; /** * Initialization. @@ -238,12 +251,33 @@ public function run() */ private function createRenderer() { + if($this->sortable) { + $drag = [ + 'name' => 'drag', + 'type' => TabularColumn::TYPE_DRAGCOLUMN, + 'headerOptions' => [ + 'style' => 'width: 20px;', + ] + ]; + + array_unshift($this->columns, $drag); + } + + $available_themes = [ + self::THEME_BS, + self::THEME_DEFAULT + ]; + + if (!in_array($this->theme, $available_themes, true)) { + $this->theme = self::THEME_BS; + } + /** * set default icon map */ $iconMap = array_key_exists($this->iconSource, $this->iconMap) ? $this->iconMap[$this->iconSource] - : $this->iconMap['glyphicons']; + : $this->iconMap[self::ICONS_SOURCE_GLYPHICONS]; $config = [ 'id' => $this->getId(), @@ -264,6 +298,7 @@ private function createRenderer() 'extraButtons' => $this->extraButtons, 'layoutConfig' => $this->layoutConfig, 'iconMap' => $iconMap, + 'theme' => $this->theme ]; if ($this->removeButtonOptions !== null) { diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 123e6f0..1bca95f 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -36,6 +36,8 @@ abstract class BaseColumn extends BaseObject const TYPE_RADIO = 'radio'; const TYPE_DRAGCOLUMN = 'dragColumn'; + const TABINDEX = 1; + /** * @var string input name */ @@ -167,7 +169,7 @@ public function init() throw new InvalidConfigException("The 'name' option is required."); } - if (is_null($this->type)) { + if ($this->type === null) { $this->type = self::TYPE_TEXT_INPUT; } @@ -268,17 +270,21 @@ public function renderInput($name, $options) } else { $optionsExt = $this->options; } - + $options = ArrayHelper::merge($options, $optionsExt); $method = 'render' . Inflector::camelize($this->type); - $value = $this->prepareValue(); + + $value = null; + if ($this->type !== self::TYPE_DRAGCOLUMN) { + $value = $this->prepareValue(); + } if (isset($options['items'])) { $options['items'] = $this->prepareItems($options['items']); } if (method_exists($this, $method)) { - $input = call_user_func_array([$this, $method], [$name, $value, $options]); + $input = $this->$method($name, $value, $options); } else { $input = $this->renderDefault($name, $value, $options); } @@ -297,7 +303,11 @@ public function renderInput($name, $options) */ protected function renderDropDownList($name, $value, $options) { - Html::addCssClass($options, 'form-control'); + if ($this->renderer->isBootstrapTheme()) { + Html::addCssClass($options, 'form-control'); + } + + $options['tabindex'] = self::TABINDEX; return Html::dropDownList($name, $value, $this->prepareItems($this->items), $options); } @@ -311,10 +321,10 @@ protected function renderDropDownList($name, $value, $options) private function prepareItems($items) { if ($items instanceof \Closure) { - return call_user_func($items, $this->getModel()); - } else { - return $items; + return $items($this->getModel()); } + + return $items; } /** @@ -327,7 +337,11 @@ private function prepareItems($items) */ protected function renderListBox($name, $value, $options) { - Html::addCssClass($options, 'form-control'); + if ($this->renderer->isBootstrapTheme()) { + Html::addCssClass($options, 'form-control'); + } + + $options['tabindex'] = self::TABINDEX; return Html::listBox($name, $value, $this->prepareItems($this->items), $options); } @@ -355,6 +369,8 @@ protected function renderHiddenInput($name, $value, $options) */ protected function renderRadio($name, $value, $options) { + $options['tabindex'] = self::TABINDEX; + if (!isset($options['label'])) { $options['label'] = ''; } @@ -378,15 +394,18 @@ protected function renderRadio($name, $value, $options) */ protected function renderRadioList($name, $value, $options) { + $options['tabindex'] = self::TABINDEX; + if (!array_key_exists('unselect', $options)) { $options['unselect'] = ''; } $options['item'] = function ($index, $label, $name, $checked, $value) use ($options) { $content = Html::radio($name, $checked, [ - 'label' => $label, - 'value' => $value, - 'data-id' => ArrayHelper::getValue($options, 'id') + 'label' => $label, + 'value' => $value, + 'data-id' => ArrayHelper::getValue($options, 'id'), + 'tabindex' => self::TABINDEX ]); return Html::tag('div', $content, ['class' => 'radio']); @@ -407,6 +426,8 @@ protected function renderRadioList($name, $value, $options) */ protected function renderCheckbox($name, $value, $options) { + $options['tabindex'] = self::TABINDEX; + if (!isset($options['label'])) { $options['label'] = ''; } @@ -430,15 +451,18 @@ protected function renderCheckbox($name, $value, $options) */ protected function renderCheckboxList($name, $value, $options) { + $options['tabindex'] = self::TABINDEX; + if (!array_key_exists('unselect', $options)) { $options['unselect'] = ''; } $options['item'] = function ($index, $label, $name, $checked, $value) use ($options) { $content = Html::checkbox($name, $checked, [ - 'label' => $label, - 'value' => $value, - 'data-id' => ArrayHelper::getValue($options, 'id') + 'label' => $label, + 'value' => $value, + 'data-id' => ArrayHelper::getValue($options, 'id'), + 'tabindex' => self::TABINDEX ]); return Html::tag('div', $content, ['class' => 'checkbox']); @@ -459,7 +483,13 @@ protected function renderCheckboxList($name, $value, $options) */ protected function renderStatic($name, $value, $options) { - return Html::tag('p', $value, ['class' => 'form-control-static']); + $options['tabindex'] = self::TABINDEX; + + if ($this->renderer->isBootstrapTheme()) { + Html::addCssClass($options, 'form-control-static'); + } + + return Html::tag('p', $value, $options); } /** @@ -480,7 +510,8 @@ protected function renderDragColumn($name, $value, $options) if (array_key_exists('class', $options)) { $class = ArrayHelper::remove($options, 'class'); } - $dragClass = implode(" ", [$class, 'drag-handle']); + + $dragClass = implode(' ', [$class, 'drag-handle']); return Html::tag('span', null, ['class' => $dragClass]); } @@ -498,8 +529,13 @@ protected function renderDefault($name, $value, $options) { $type = $this->type; - if (method_exists('yii\helpers\Html', $type)) { - Html::addCssClass($options, 'form-control'); + if (method_exists(Html::class, $type)) { + $options['tabindex'] = self::TABINDEX; + + if ($this->renderer->isBootstrapTheme()) { + Html::addCssClass($options, 'form-control'); + } + $input = Html::$type($name, $value, $options); } elseif (class_exists($type) && method_exists($type, 'widget')) { $input = $this->renderWidget($type, $name, $value, $options); @@ -521,6 +557,8 @@ protected function renderDefault($name, $value, $options) */ protected function renderWidget($type, $name, $value, $options) { + unset($options['tabindex']); + $model = $this->getModel(); if ($model instanceof Model) { $model->{$this->name} = $value; @@ -529,14 +567,18 @@ protected function renderWidget($type, $name, $value, $options) 'attribute' => $this->name, 'value' => $value, 'options' => [ - 'id' => $this->normalize($name), - 'name' => $name + 'id' => $this->normalize($name), + 'name' => $name, + 'tabindex' => self::TABINDEX ] ]; } else { $widgetOptions = [ - 'name' => $name, - 'value' => $value + 'name' => $name, + 'value' => $value, + 'options' => [ + 'tabindex' => self::TABINDEX + ] ]; } diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 6310588..4611287 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -166,7 +166,17 @@ abstract class BaseRenderer extends BaseObject implements RendererInterface */ public $layoutConfig = []; - public $iconMap; + /** + * @var array + */ + public $iconMap = []; + + /** + * @var string + * + * @todo Use bootstrap theme for BC. We can switch to default theme in major release + */ + public $theme = self::THEME_BS; /** * @inheritdoc @@ -236,17 +246,21 @@ private function prepareMaxOption() } } + /** + * @throws InvalidConfigException + */ private function prepareButtons() { if ($this->addButtonPosition === null || $this->addButtonPosition === []) { $this->addButtonPosition = $this->min === 0 ? self::POS_HEADER : self::POS_ROW; } + if (!is_array($this->addButtonPosition)) { $this->addButtonPosition = (array) $this->addButtonPosition; } if (!array_key_exists('class', $this->removeButtonOptions)) { - $this->removeButtonOptions['class'] = 'btn btn-danger'; + $this->removeButtonOptions['class'] = $this->isBootstrapTheme() ? 'btn btn-danger' : ''; } if (!array_key_exists('label', $this->removeButtonOptions)) { @@ -254,7 +268,7 @@ private function prepareButtons() } if (!array_key_exists('class', $this->addButtonOptions)) { - $this->addButtonOptions['class'] = 'btn btn-default'; + $this->addButtonOptions['class'] = $this->isBootstrapTheme() ? 'btn btn-default' : ''; } if (!array_key_exists('label', $this->addButtonOptions)) { @@ -262,7 +276,7 @@ private function prepareButtons() } if (!array_key_exists('class', $this->cloneButtonOptions)) { - $this->cloneButtonOptions['class'] = 'btn btn-info'; + $this->cloneButtonOptions['class'] = $this->isBootstrapTheme() ? 'btn btn-info' : ''; } if (!array_key_exists('label', $this->cloneButtonOptions)) { @@ -280,20 +294,18 @@ protected function initColumns() { foreach ($this->columns as $i => $column) { $definition = array_merge([ - 'class' => $this->columnClass, - 'renderer' => $this, - 'context' => $this->context + 'class' => $this->columnClass, + 'renderer' => $this, + 'context' => $this->context, ], $column); - if (!is_array($this->addButtonOptions)) { - $this->addButtonOptions = [$this->addButtonOptions]; - } + $this->addButtonOptions = (array)$this->addButtonOptions; - if (!array_key_exists('attributeOptions', $definition)) { + if (!isset($definition['attributeOptions'])) { $definition['attributeOptions'] = $this->attributeOptions; } - if (!array_key_exists('enableError', $definition)) { + if (!isset($definition['enableError'])) { $definition['enableError'] = $this->enableError; } @@ -520,6 +532,8 @@ protected function prepareJsAttributes() /** * @param $action - the control parameter, used as key into allowed types * @return string - the relevant icon class + * + * @throws InvalidConfigException */ protected function getIconClass($action) { if (in_array($action, ['add', 'remove', 'clone', 'drag-handle'])) { @@ -531,4 +545,15 @@ protected function getIconClass($action) { } return ''; } + + public function isDefaultTheme() + { + return $this->theme === self::THEME_DEFAULT; + } + + public function isBootstrapTheme() + { + return $this->theme === self::THEME_BS; + } + } diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index 9cf85ef..0af069c 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -32,7 +32,11 @@ protected function internalRender() $content[] = $this->renderFooter(); $options = []; - Html::addCssClass($options, 'multiple-input-list list-renderer table form-horizontal'); + Html::addCssClass($options, 'multiple-input-list list-renderer'); + + if ($this->isBootstrapTheme()) { + Html::addCssClass($options, 'table form-horizontal'); + } $content = Html::tag('table', implode("\n", $content), $options); @@ -197,39 +201,50 @@ public function renderCellContent($column, $index) return $input; } - $hasError = false; - $error = ''; - $wrapperOptions = []; $layoutConfig = array_merge([ - 'offsetClass' => 'col-sm-offset-3', - 'labelClass' => 'col-sm-3', - 'wrapperClass' => 'col-sm-6', - 'errorClass' => 'col-sm-offset-3 col-sm-6', + 'offsetClass' => $this->isBootstrapTheme() ? 'col-sm-offset-3' : '', + 'labelClass' => $this->isBootstrapTheme() ? 'col-sm-3' : '', + 'wrapperClass' => $this->isBootstrapTheme() ? 'col-sm-6' : '', + 'errorClass' => $this->isBootstrapTheme() ? 'col-sm-offset-3 col-sm-6' : '', ], $this->layoutConfig); Html::addCssClass($column->errorOptions, $layoutConfig['errorClass']); + $hasError = false; + $error = ''; + if ($index !== null) { $error = $column->getFirstError($index); $hasError = !empty($error); } + $wrapperOptions = []; + if ($hasError) { Html::addCssClass($wrapperOptions, 'has-error'); } Html::addCssClass($wrapperOptions, $layoutConfig['wrapperClass']); - $content = Html::beginTag('div', [ - 'class' => "form-group field-$id list-cell__$column->name" . ($hasError ? ' has-error' : '') - ]); + $options = [ + 'class' => "field-$id list-cell__$column->name" . ($hasError ? ' has-error' : '') + ]; + + if ($this->isBootstrapTheme()) { + Html::addCssClass($options, 'form-group'); + } + + $content = Html::beginTag('div', $options); if (empty($column->title)) { Html::addCssClass($wrapperOptions, $layoutConfig['offsetClass']); } else { - $content .= Html::label($column->title, $id, [ - 'class' => $layoutConfig['labelClass'] . ' control-label' - ]); + $labelOptions = ['class' => $layoutConfig['labelClass']]; + if ($this->isBootstrapTheme()) { + Html::addCssClass($labelOptions, 'control-label'); + } + + $content .= Html::label($column->title, $id, $labelOptions); } $content .= Html::tag('div', $input, $wrapperOptions); @@ -282,17 +297,19 @@ private function getActionButton($index) $index++; if ($index < $this->min) { return ''; - } elseif ($index === $this->min) { + } + + if ($index === $this->min) { return $this->isAddButtonPositionRow() ? $this->renderAddButton() : ''; - } else { - return $this->renderRemoveButton(); } + + return $this->renderRemoveButton(); } private function renderAddButton() { $options = [ - 'class' => 'btn multiple-input-list__btn js-input-plus', + 'class' => 'multiple-input-list__btn js-input-plus', ]; Html::addCssClass($options, $this->addButtonOptions['class']); @@ -303,12 +320,11 @@ private function renderAddButton() * Renders remove button. * * @return string - * @throws \Exception */ private function renderRemoveButton() { $options = [ - 'class' => 'btn multiple-input-list__btn js-input-remove', + 'class' => 'multiple-input-list__btn js-input-remove', ]; Html::addCssClass($options, $this->removeButtonOptions['class']); @@ -319,12 +335,11 @@ private function renderRemoveButton() * Renders clone button. * * @return string - * @throws \Exception */ private function renderCloneButton() { $options = [ - 'class' => 'btn multiple-input-list__btn js-input-clone', + 'class' => 'multiple-input-list__btn js-input-clone', ]; Html::addCssClass($options, $this->cloneButtonOptions['class']); @@ -335,6 +350,8 @@ private function renderCloneButton() * Returns template for using in js. * * @return string + * + * @throws \yii\base\InvalidConfigException */ protected function prepareTemplate() { diff --git a/src/renderers/RendererInterface.php b/src/renderers/RendererInterface.php index 74072f7..395ef3e 100644 --- a/src/renderers/RendererInterface.php +++ b/src/renderers/RendererInterface.php @@ -20,6 +20,9 @@ interface RendererInterface const POS_ROW_BEGIN = 'row_begin'; const POS_FOOTER = 'footer'; + const THEME_DEFAULT = 'default'; + const THEME_BS = 'bootstrap'; + /** * Renders the widget's content. * diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 4940183..9d4832a 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -35,7 +35,11 @@ protected function internalRender() $content[] = $this->renderFooter(); $options = []; - Html::addCssClass($options, 'multiple-input-list table table-condensed table-renderer'); + Html::addCssClass($options, 'multiple-input-list'); + + if ($this->isBootstrapTheme()) { + Html::addCssClass($options, 'table table-condensed table-renderer'); + } $content = Html::tag('table', implode("\n", $content), $options); @@ -134,6 +138,7 @@ private function renderHeaderCell($column) if ($column->isHiddenInput()) { return null; } + $options = $column->headerOptions; Html::addCssClass($options, 'list-cell__' . $column->name); @@ -156,6 +161,7 @@ private function renderButtonHeaderCell($button = '') * Renders the body. * * @return string + * * @throws \yii\base\InvalidConfigException * @throws \yii\base\InvalidParamException */ @@ -292,9 +298,10 @@ public function renderCellContent($column, $index) $input .= "\n" . $column->renderError($error); } - $wrapperOptions = [ - 'class' => 'form-group field-' . $id - ]; + $wrapperOptions = ['class' => 'field-' . $id]; + if ($this->isBootstrapTheme()) { + Html::addCssClass($options, 'form-group'); + } if ($hasError) { Html::addCssClass($wrapperOptions, 'has-error'); @@ -370,8 +377,9 @@ private function getActionButton($index, $isFirstColumn) private function renderAddButton() { $options = [ - 'class' => 'btn multiple-input-list__btn js-input-plus', + 'class' => 'multiple-input-list__btn js-input-plus', ]; + Html::addCssClass($options, $this->addButtonOptions['class']); return Html::tag('div', $this->addButtonOptions['label'], $options); @@ -381,13 +389,13 @@ private function renderAddButton() * Renders remove button. * * @return string - * @throws \Exception */ private function renderRemoveButton() { $options = [ - 'class' => 'btn multiple-input-list__btn js-input-remove', + 'class' => 'multiple-input-list__btn js-input-remove', ]; + Html::addCssClass($options, $this->removeButtonOptions['class']); return Html::tag('div', $this->removeButtonOptions['label'], $options); @@ -397,13 +405,13 @@ private function renderRemoveButton() * Renders clone button. * * @return string - * @throws \Exception */ private function renderCloneButton() { $options = [ - 'class' => 'btn multiple-input-list__btn js-input-clone', + 'class' => 'multiple-input-list__btn js-input-clone', ]; + Html::addCssClass($options, $this->cloneButtonOptions['class']); return Html::tag('div', $this->cloneButtonOptions['label'], $options); @@ -413,6 +421,8 @@ private function renderCloneButton() * Returns template for using in js. * * @return string + * + * @throws \yii\base\InvalidConfigException */ protected function prepareTemplate() { From a584dbe69747cd8154855b022100bc38b22c5e24 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 6 Oct 2018 23:29:29 +0300 Subject: [PATCH 117/247] fix the example --- examples/views/tabular-input.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/views/tabular-input.php b/examples/views/tabular-input.php index fc97965..eba56ba 100644 --- a/examples/views/tabular-input.php +++ b/examples/views/tabular-input.php @@ -68,15 +68,11 @@ 'type' => \kartik\date\DatePicker::class, 'title' => 'Day', 'options' => [ - 'type' => \kartik\datecontrol\DateControl::FORMAT_DATE, 'pluginOptions' => [ 'autoclose' => true, 'format' => 'dd/mm/yyyy', 'todayHighlight' => true, ], - 'widgetOptions' => [ - 'type' => \kartik\date\DatePicker::TYPE_INPUT, - ], ], 'headerOptions' => [ From 2510db7ffd5d672b6d4b355655e2fbc5b51a8d1c Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 7 Oct 2018 00:01:00 +0300 Subject: [PATCH 118/247] #175 option `generalErrorPosition` to enable displaying of general error message --- CHANGELOG.md | 1 + examples/models/ExampleModel.php | 2 +- examples/views/multiple-input.php | 2 ++ src/MultipleInput.php | 35 +++++++++++++++++-- src/assets/src/js/jquery.multipleInput.js | 8 +++-- src/assets/src/js/jquery.multipleInput.min.js | 2 +- src/renderers/BaseRenderer.php | 13 ++++--- 7 files changed, 53 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcf807d..705ae76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Yii2 multiple input change log - #215 collect all js script that has to be evaluate when add new row (not only from " on ready" section) - #198 introduce the option `theme` to disable all bootstrap css classes - #197 explicitly set tabindex for all inputs +- #175 option `generalErrorPosition` to enable displaying of general error message 2.16.0 ====== diff --git a/examples/models/ExampleModel.php b/examples/models/ExampleModel.php index 2fbc1ec..b05218d 100644 --- a/examples/models/ExampleModel.php +++ b/examples/models/ExampleModel.php @@ -96,7 +96,7 @@ public function rules() ['title', 'string', 'min' => 5], ['emails', 'validateEmails'], ['phones', 'validatePhones'], - ['schedule', 'validateSchedule'], + ['schedule', 'validateSchedule', 'skipOnEmpty' => false], ['questions', 'validateQuestions'] ]; } diff --git a/examples/views/multiple-input.php b/examples/views/multiple-input.php index da59bc3..6851f00 100644 --- a/examples/views/multiple-input.php +++ b/examples/views/multiple-input.php @@ -21,6 +21,7 @@ 'validateOnBlur' => false, ]);?> +

    Single column

    field($model, 'emails')->widget(MultipleInput::className(), [ @@ -52,6 +53,7 @@ 'max' => 4, 'sortable' => true, 'allowEmptyList' => true, + 'generalErrorPosition' => 'top', 'rowOptions' => function($model) { $options = []; diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 4aaa1a9..bdd1baf 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -37,11 +37,14 @@ class MultipleInput extends InputWidget const ICONS_SOURCE_GLYPHICONS = 'glyphicons'; const ICONS_SOURCE_FONTAWESOME = 'fa'; + + const GENERAL_ERROR_POS_TOP = 'top'; + const GENERAL_ERROR_POS_BOTTOM = 'bottom'; /** * @var ActiveRecordInterface[]|array[] input data */ - public $data = null; + public $data; /** * @var array columns configuration @@ -205,6 +208,17 @@ class MultipleInput extends InputWidget */ public $theme = self::THEME_BS; + /** + * @var string|null set a position of generic error message (when you set an error for generic field instead of particular input) + * If set to null no generic error message will be shown. + */ + public $generalErrorPosition; + + /** + * @var bool + */ + private $showGeneralError = false; + /** * Initialization. * @@ -216,6 +230,16 @@ public function init() throw new InvalidConfigException('Property "form" must be an instance of yii\widgets\ActiveForm'); } + $this->showGeneralError = $this->generalErrorPosition !== null && !$this->isEmbedded && $this->field; + + if ($this->showGeneralError) { + if ($this->generalErrorPosition === self::GENERAL_ERROR_POS_BOTTOM) { + $this->field->template = "{input}\n{error}"; + } else { + $this->field->template = "{error}\n{input}"; + } + } + $this->guessColumns(); $this->initData(); @@ -276,9 +300,10 @@ protected function guessColumns() public function run() { $content = ''; - if ($this->hasModel() && $this->isEmbedded === false) { + if ($this->isEmbedded === false && $this->hasModel()) { $content .= Html::hiddenInput(Html::getInputName($this->model, $this->attribute), null); } + $content .= $this->createRenderer()->render(); return $content; @@ -339,6 +364,12 @@ private function createRenderer() 'theme' => $this->theme ]; + if ($this->showGeneralError) { + $config['jsExtraSettings'] = [ + 'showGeneralError' => true + ]; + } + if ($this->removeButtonOptions !== null) { $config['removeButtonOptions'] = $this->removeButtonOptions; } diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index f321faa..ff81f40 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -99,7 +99,9 @@ /** * default prefix of a widget's placeholder */ - indexPlaceholder: 'multiple_index' + indexPlaceholder: 'multiple_index', + + showGeneralError: false }; var isActiveFormEnabled = false; @@ -162,7 +164,9 @@ } }); - form.yiiActiveForm('remove', inputId); + if (!settings.showGeneralError) { + form.yiiActiveForm('remove', inputId); + } } // append default options to option from settings diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 8995111..63cc980 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),d=p.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),p.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),f(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(i[t]=e)}),d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(h),p.trigger(m)}else v++;(0===d.length||v>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(h),p.trigger(m))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,f=u.template,d=l.children(".multiple-input-list").first();if(!(null!==u.max&&s(l)>=u.max)){f=f.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(f),v=t.Event(e.beforeAddRow);if(l.trigger(v,[c]),v.result!==!1){if(c.hide().appendTo(d).fadeIn(300),r instanceof Object){var m=[];for(var h in r)r.hasOwnProperty(h)&&m.push(r[h]);r=m}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var w=0;t(f).find("input, select, textarea").each(function(e,i){var l=t(i),a=i.tagName,u=p(l),s=t("#"+u);if(r){var f=r[w];if("INPUT"===a||"TEXTAREA"===a)s.val(f);else if("SELECT"===a)if(f&&f.indexOf("option")!==-1)s.append(f);else{var d=s.find('option[value="'+f+'"]');d.length&&s.val(f)}}n&&o(l),w++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[c])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(e,i){u(t(i))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!==r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(e){var i=p(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},f=function(e){var i={};return e.closest("tr").find("td").each(function(e,n){t(n).find("input, select, textarea").each(function(e,n){var r=t(n),l=p(r),a=t("#"+l);i[l]=a.val()})}),i};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),s=t("#"+u.id),d=s.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),s.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),f(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(i[t]=e)}),u.showGeneralError||d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),n=!0,clearInterval(h),s.trigger(m)}else v++;(0===d.length||v>10)&&(s.data("multipleInput").currentIndex=p(s),n=!1,clearInterval(h),s.trigger(m))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,f=u.template,d=l.children(".multiple-input-list").first();if(!(null!==u.max&&p(l)>=u.max)){f=f.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(f),v=t.Event(e.beforeAddRow);if(l.trigger(v,[c]),v.result!==!1){if(c.hide().appendTo(d).fadeIn(300),r instanceof Object){var m=[];for(var h in r)r.hasOwnProperty(h)&&m.push(r[h]);r=m}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var w=0;t(f).find("input, select, textarea").each(function(e,i){var l=t(i),a=i.tagName,u=s(l),p=t("#"+u);if(r){var f=r[w];if("INPUT"===a||"TEXTAREA"===a)p.val(f);else if("SELECT"===a)if(f&&f.indexOf("option")!==-1)p.append(f);else{var d=p.find('option[value="'+f+'"]');d.length&&p.val(f)}}n&&o(l),w++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[c])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(p(r)>o.min){var s=t.Event(e.beforeDeleteRow);if(r.trigger(s,[l]),s.result===!1)return;n&&l.find("input, select, textarea").each(function(e,i){u(t(i))}),l.fadeOut(300,function(){t(this).remove(),s=t.Event(e.afterDeleteRow),r.trigger(s,[l])})}},o=function(e){var i=s(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!==r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(e){var i=s(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},f=function(e){var i={};return e.closest("tr").find("td").each(function(e,n){t(n).find("input, select, textarea").each(function(e,n){var r=t(n),l=s(r),a=t("#"+l);i[l]=a.val()})}),i};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 4611287..349e545 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -107,7 +107,7 @@ abstract class BaseRenderer extends BaseObject implements RendererInterface public $columnClass; /** - * @var string position of add button. By default button is rendered in the row. + * @var array|string position of add button. By default button is rendered in the row. */ public $addButtonPosition = self::POS_ROW; @@ -178,6 +178,11 @@ abstract class BaseRenderer extends BaseObject implements RendererInterface */ public $theme = self::THEME_BS; + /** + * @var array + */ + public $jsExtraSettings = []; + /** * @inheritdoc */ @@ -392,7 +397,7 @@ public function render() } } - $options = Json::encode([ + $options = Json::encode(array_merge([ 'id' => $this->id, 'inputId' => $this->context->options['id'], 'template' => $template, @@ -402,7 +407,7 @@ public function render() 'min' => $this->min, 'attributes' => $this->prepareJsAttributes(), 'indexPlaceholder' => $this->getIndexPlaceholder() - ]); + ], $this->jsExtraSettings)); $js = "jQuery('#{$this->id}').multipleInput($options);"; $view->registerJs($js); @@ -519,7 +524,7 @@ protected function prepareJsAttributes() if (isset($attributeOptions['name']) && $attributeOptions['name'] === $column->name) { $attributes[$inputID] = ArrayHelper::merge($attributeOptions, $column->attributeOptions); } else { - array_push($this->form->attributes, $attributeOptions); + $this->form->attributes[] = $attributeOptions; } } else { $attributes[$inputID] = $column->attributeOptions; From 918f6cfc5cd5cd1e70e5cb35f67233573cc470d7 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 7 Oct 2018 02:40:17 +0300 Subject: [PATCH 119/247] #175 simplify --- CHANGELOG.md | 2 +- examples/views/multiple-input.php | 2 +- src/MultipleInput.php | 21 +++------------------ 3 files changed, 5 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 705ae76..96254fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ Yii2 multiple input change log - #215 collect all js script that has to be evaluate when add new row (not only from " on ready" section) - #198 introduce the option `theme` to disable all bootstrap css classes - #197 explicitly set tabindex for all inputs -- #175 option `generalErrorPosition` to enable displaying of general error message +- #175 option `showGeneralError` to enable displaying of general error message 2.16.0 ====== diff --git a/examples/views/multiple-input.php b/examples/views/multiple-input.php index 6851f00..74200ec 100644 --- a/examples/views/multiple-input.php +++ b/examples/views/multiple-input.php @@ -53,7 +53,7 @@ 'max' => 4, 'sortable' => true, 'allowEmptyList' => true, - 'generalErrorPosition' => 'top', + 'showGeneralError' => true, 'rowOptions' => function($model) { $options = []; diff --git a/src/MultipleInput.php b/src/MultipleInput.php index bdd1baf..1a0eaee 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -37,9 +37,6 @@ class MultipleInput extends InputWidget const ICONS_SOURCE_GLYPHICONS = 'glyphicons'; const ICONS_SOURCE_FONTAWESOME = 'fa'; - - const GENERAL_ERROR_POS_TOP = 'top'; - const GENERAL_ERROR_POS_BOTTOM = 'bottom'; /** * @var ActiveRecordInterface[]|array[] input data @@ -208,16 +205,10 @@ class MultipleInput extends InputWidget */ public $theme = self::THEME_BS; - /** - * @var string|null set a position of generic error message (when you set an error for generic field instead of particular input) - * If set to null no generic error message will be shown. - */ - public $generalErrorPosition; - /** * @var bool */ - private $showGeneralError = false; + public $showGeneralError = false; /** * Initialization. @@ -230,14 +221,8 @@ public function init() throw new InvalidConfigException('Property "form" must be an instance of yii\widgets\ActiveForm'); } - $this->showGeneralError = $this->generalErrorPosition !== null && !$this->isEmbedded && $this->field; - - if ($this->showGeneralError) { - if ($this->generalErrorPosition === self::GENERAL_ERROR_POS_BOTTOM) { - $this->field->template = "{input}\n{error}"; - } else { - $this->field->template = "{error}\n{input}"; - } + if ($this->showGeneralError && $this->field === null) { + $this->showGeneralError = false; } $this->guessColumns(); From d28cbeb7c821eff58a8236cbe786d0da1e40170a Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 7 Oct 2018 11:45:55 +0300 Subject: [PATCH 120/247] bump version --- CHANGELOG.md | 5 ++++- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96254fe..9fbfbee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ Yii2 multiple input change log ============================== -2.17.0 (in development) +2.18.0 (in development) ======================= + +2.17.0 +====== - #215 collect all js script that has to be evaluate when add new row (not only from " on ready" section) - #198 introduce the option `theme` to disable all bootstrap css classes - #197 explicitly set tabindex for all inputs diff --git a/README.md b/README.md index be03821..2181662 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.16.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.17.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 464ef01..cd87495 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.16.0", + "version": "2.17.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index d88ad80..684f578 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.16.0", + "version": "2.17.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 78c300e1ae85caeff416c02d868c797704e73064 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Mon, 8 Oct 2018 12:53:12 +0300 Subject: [PATCH 121/247] fix BC --- README.md | 2 +- composer.json | 2 +- examples/views/tabular-input.php | 2 +- package.json | 2 +- src/components/BaseColumn.php | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2181662..f2b3a00 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.17.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.17.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index cd87495..ac79951 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.17.0", + "version": "2.17.1", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/examples/views/tabular-input.php b/examples/views/tabular-input.php index eba56ba..43e61ff 100644 --- a/examples/views/tabular-input.php +++ b/examples/views/tabular-input.php @@ -65,7 +65,7 @@ ], [ 'name' => 'date', - 'type' => \kartik\date\DatePicker::class, + 'type' => '\kartik\date\DatePicker', 'title' => 'Day', 'options' => [ 'pluginOptions' => [ diff --git a/package.json b/package.json index 684f578..09c239a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.17.0", + "version": "2.17.1", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 1bca95f..2d1c139 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -529,7 +529,7 @@ protected function renderDefault($name, $value, $options) { $type = $this->type; - if (method_exists(Html::class, $type)) { + if (method_exists('yii\helpers\Html', $type)) { $options['tabindex'] = self::TABINDEX; if ($this->renderer->isBootstrapTheme()) { From 40a8d3f03a9cee7c332a1a326296f4f2b2022625 Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Wed, 31 Oct 2018 12:44:43 +0300 Subject: [PATCH 122/247] add \Traversable support --- src/MultipleInput.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 1a0eaee..02ede92 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -254,7 +254,11 @@ protected function initData() return; } - foreach ((array) $data as $index => $value) { + if (!($data instanceof \Traversable)) { + $data = (array) $data; + } + + foreach ($data as $index => $value) { $this->data[$index] = $value; } } From 30d5d165d1f66d2b76431d53468f6912baff8c27 Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Wed, 31 Oct 2018 15:03:55 +0300 Subject: [PATCH 123/247] :book: --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fbfbee..dc335ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Yii2 multiple input change log 2.18.0 (in development) ======================= +- #246 accept `\Traversable` in model attribute for `yield` compatibility (bscheshirwork) 2.17.0 ====== From 1e5c898660d91e1b7d5e0ecdc59f5316c0114791 Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Tue, 13 Nov 2018 14:40:11 +0300 Subject: [PATCH 124/247] Refactor TableRenderer for support yield --- CHANGELOG.md | 1 + src/renderers/TableRenderer.php | 19 +++++++++---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc335ce..00f28c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Yii2 multiple input change log 2.18.0 (in development) ======================= - #246 accept `\Traversable` in model attribute for `yield` compatibility (bscheshirwork) +- #250 accept `\Traversable` in TableRenderer for `yield` compatibility (bscheshirwork) 2.17.0 ====== diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 9d4832a..768f2cf 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -170,17 +170,16 @@ protected function renderBody() $rows = []; if ($this->data) { - $cnt = count($this->data); - if ($this->min === $this->max && $cnt < $this->max) { - $cnt = $this->max; + $j = 0; + foreach ($this->data as $index => $item) { + if ($j++ <= $this->max) { + $rows[] = $this->renderRowContent($index, $item); + } else { + break; + } } - - $indices = array_keys($this->data); - - for ($i = 0; $i < $cnt; $i++) { - $index = ArrayHelper::getValue($indices, $i, $i); - $item = ArrayHelper::getValue($this->data, $index, null); - $rows[] = $this->renderRowContent($index, $item); + for ($i = $j; $i < $this->min; $i++) { + $rows[] = $this->renderRowContent($i); } } elseif ($this->min > 0) { for ($i = 0; $i < $this->min; $i++) { From 567ecb958ba131002656aba359babcbe7d632620 Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Tue, 13 Nov 2018 18:43:45 +0300 Subject: [PATCH 125/247] Refactor ListRenderer for support yield --- CHANGELOG.md | 2 +- src/renderers/ListRenderer.php | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 00f28c9..df6c681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ Yii2 multiple input change log 2.18.0 (in development) ======================= - #246 accept `\Traversable` in model attribute for `yield` compatibility (bscheshirwork) -- #250 accept `\Traversable` in TableRenderer for `yield` compatibility (bscheshirwork) +- #250 accept `\Traversable` in TableRenderer and ListRenderer for `yield` compatibility (bscheshirwork) 2.17.0 ====== diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index 0af069c..9e337d0 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -105,17 +105,16 @@ protected function renderBody() $rows = []; if ($this->data) { - $cnt = count($this->data); - if ($this->min === $this->max && $cnt < $this->max) { - $cnt = $this->max; + $j = 0; + foreach ($this->data as $index => $item) { + if ($j++ <= $this->max) { + $rows[] = $this->renderRowContent($index, $item); + } else { + break; + } } - - $indices = array_keys($this->data); - - for ($i = 0; $i < $cnt; $i++) { - $index = ArrayHelper::getValue($indices, $i, $i); - $item = ArrayHelper::getValue($this->data, $index, null); - $rows[] = $this->renderRowContent($index, $item); + for ($i = $j; $i < $this->min; $i++) { + $rows[] = $this->renderRowContent($i); } } elseif ($this->min > 0) { for ($i = 0; $i < $this->min; $i++) { From 2cb98077661775fe71aed13835b3481d09d59896 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 25 Nov 2018 17:16:46 +0300 Subject: [PATCH 126/247] allow to omit a name for static columns --- CHANGELOG.md | 3 ++- examples/views/multiple-input.php | 4 ++-- src/components/BaseColumn.php | 16 +++++++++++----- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index df6c681..0c6b090 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ Yii2 multiple input change log 2.18.0 (in development) ======================= - #246 accept `\Traversable` in model attribute for `yield` compatibility (bscheshirwork) -- #250 accept `\Traversable` in TableRenderer and ListRenderer for `yield` compatibility (bscheshirwork) +- #250 accept `\Traversable` in TableRenderer and ListRenderer for `yield` compatibility (bscheshirwork) +- #253 allow to omit a name for static column 2.17.0 ====== diff --git a/examples/views/multiple-input.php b/examples/views/multiple-input.php index 74200ec..9532cae 100644 --- a/examples/views/multiple-input.php +++ b/examples/views/multiple-input.php @@ -124,7 +124,7 @@ ] ], [ - 'name' => 'comment', + 'name' => 'comment', // can be ommited in case of static column 'type' => MultipleInputColumn::TYPE_STATIC, 'value' => function($data) { return Html::tag('span', 'static content', ['class' => 'label label-info']); @@ -180,4 +180,4 @@ }); JS; -$this->registerJs($js); \ No newline at end of file +$this->registerJs($js); diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 2d1c139..41802a2 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -38,6 +38,8 @@ abstract class BaseColumn extends BaseObject const TABINDEX = 1; + const DEFAULT_STATIC_COLUMN_NAME = 'static-column'; + /** * @var string input name */ @@ -128,7 +130,7 @@ abstract class BaseColumn extends BaseObject * @since 2.8 */ public $nameSuffix; - + /** * @var Model|ActiveRecordInterface|array */ @@ -165,14 +167,18 @@ public function init() { parent::init(); - if (empty($this->name)) { - throw new InvalidConfigException("The 'name' option is required."); - } - if ($this->type === null) { $this->type = self::TYPE_TEXT_INPUT; } + if ($this->type === self::TYPE_STATIC && empty($this->name)) { + $this->name = self::DEFAULT_STATIC_COLUMN_NAME; + } + + if (empty($this->name)) { + throw new InvalidConfigException("The 'name' option is required."); + } + if (empty($this->options)) { $this->options = []; } From 3fb853d1d3636b634694d6acebfea742d14805b9 Mon Sep 17 00:00:00 2001 From: Spell6inder Date: Thu, 6 Dec 2018 11:27:55 +0200 Subject: [PATCH 127/247] Fix order of registered JS code block or files --- src/renderers/BaseRenderer.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 349e545..9f121f6 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -183,6 +183,11 @@ abstract class BaseRenderer extends BaseObject implements RendererInterface */ public $jsExtraSettings = []; + /** + * @var array List of the locations of registered JavaScript code block or files in right order. + */ + public $jsPositions = [View::POS_HEAD, View::POS_BEGIN, View::POS_END, View::POS_READY, View::POS_LOAD]; + /** * @inheritdoc */ @@ -370,8 +375,8 @@ public function render() // Collect all js scripts which has to be appended to page before initialization widget $jsInit = []; if (is_array($view->js)) { - foreach ($view->js as $position => $scripts) { - foreach ((array)$scripts as $key => $js) { + foreach ($this->jsPositions as $position) { + foreach (ArrayHelper::getValue($view->js, $position, []) as $key => $js) { if (isset($jsBefore[$position][$key])) { continue; } @@ -386,8 +391,8 @@ public function render() $jsTemplates = []; if (is_array($view->js)) { - foreach ($view->js as $position => $scripts) { - foreach ((array)$scripts as $key => $js) { + foreach ($this->jsPositions as $position) { + foreach (ArrayHelper::getValue($view->js, $position, []) as $key => $js) { if (isset($jsBefore[$position][$key])) { continue; } From 43ba4f57aa3a86ad866b5ecb4fed01f6b7e24d8e Mon Sep 17 00:00:00 2001 From: Spell6inder Date: Thu, 6 Dec 2018 14:50:38 +0200 Subject: [PATCH 128/247] upd CHANGELOG.md #257 added `jsPositions` property for the `BaseRenderer` to set right order js-code in `jsInit` and `jsTemplates` (Spell6inder) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c6b090..be33f4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Yii2 multiple input change log - #246 accept `\Traversable` in model attribute for `yield` compatibility (bscheshirwork) - #250 accept `\Traversable` in TableRenderer and ListRenderer for `yield` compatibility (bscheshirwork) - #253 allow to omit a name for static column +- #257 added `jsPositions` property for the `BaseRenderer` to set right order js-code in `jsInit` and `jsTemplates` (Spell6inder) 2.17.0 ====== From 523ba9731f78c51e78b1350ab9abb2faea34d2f7 Mon Sep 17 00:00:00 2001 From: Ankit Padia Date: Sat, 8 Dec 2018 11:14:40 +0530 Subject: [PATCH 129/247] Added support for columOptions --- src/renderers/TableRenderer.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 768f2cf..de978a2 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -308,9 +308,9 @@ public function renderCellContent($column, $index) $input = Html::tag('div', $input, $wrapperOptions); - return Html::tag('td', $input, [ - 'class' => 'list-cell__' . $column->name, - ]); + Html::addCssClass($column->columnOptions, 'list-cell__' . $column->name); + + return Html::tag('td', $input, $column->columnOptions); } From 6bf404f7560543f2c4f2340db5001a248ab37f48 Mon Sep 17 00:00:00 2001 From: Ankit Padia Date: Sat, 8 Dec 2018 11:22:21 +0530 Subject: [PATCH 130/247] Update TabularColumn.php --- src/TabularColumn.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/TabularColumn.php b/src/TabularColumn.php index f8aba91..e77e548 100644 --- a/src/TabularColumn.php +++ b/src/TabularColumn.php @@ -19,6 +19,11 @@ */ class TabularColumn extends BaseColumn { + /** + * @var array the HTML attributes for the body cell tag. + */ + public $columnOptions = []; + /** * Returns element's name. * @@ -71,4 +76,4 @@ public function setModel($model) parent::setModel($model); } -} \ No newline at end of file +} From e52a8d9ef4616140a35abe4b5999ad576e5a9b81 Mon Sep 17 00:00:00 2001 From: Ankit Padia Date: Mon, 10 Dec 2018 11:14:32 +0530 Subject: [PATCH 131/247] Added support for closure in `columnOptions` --- src/renderers/TableRenderer.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index de978a2..af1ae93 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -306,11 +306,17 @@ public function renderCellContent($column, $index) Html::addCssClass($wrapperOptions, 'has-error'); } - $input = Html::tag('div', $input, $wrapperOptions); + if (is_callable($column->columnOptions)) { + $columnOptions = call_user_func($column->columnOptions, $column->getModel(), $index, $this->context); + } else { + $columnOptions = $column->columnOptions; + } - Html::addCssClass($column->columnOptions, 'list-cell__' . $column->name); + Html::addCssClass($columnOptions, 'list-cell__' . $column->name); + + $input = Html::tag('div', $input, $wrapperOptions); - return Html::tag('td', $input, $column->columnOptions); + return Html::tag('td', $input, $columnOptions); } From 3c7abf48c498f1f4c45af35ab07dab6255f7e182 Mon Sep 17 00:00:00 2001 From: Ankit Padia Date: Mon, 10 Dec 2018 11:35:14 +0530 Subject: [PATCH 132/247] Document comment added --- src/TabularColumn.php | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/TabularColumn.php b/src/TabularColumn.php index e77e548..b2d3868 100644 --- a/src/TabularColumn.php +++ b/src/TabularColumn.php @@ -20,7 +20,18 @@ class TabularColumn extends BaseColumn { /** - * @var array the HTML attributes for the body cell tag. + * @var array|\Closure the HTML attributes for the table body columns. This can be either an array + * specifying the common HTML attributes for all body column, or an anonymous function that + * returns an array of the HTML attributes. It should have the following signature: + * + * ```php + * function ($model, $index, $context) + * ``` + * + * - `$model`: the current data model being rendered + * - `$index`: the zero-based index of the data model in the model array + * - `$context`: the widget object + * */ public $columnOptions = []; From d0c1cb111a96821bf1266e98bf2bd0a2b5d8851c Mon Sep 17 00:00:00 2001 From: Ankit Padia Date: Mon, 10 Dec 2018 11:35:58 +0530 Subject: [PATCH 133/247] Update TabularColumn.php --- src/TabularColumn.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TabularColumn.php b/src/TabularColumn.php index b2d3868..ee551cb 100644 --- a/src/TabularColumn.php +++ b/src/TabularColumn.php @@ -20,8 +20,8 @@ class TabularColumn extends BaseColumn { /** - * @var array|\Closure the HTML attributes for the table body columns. This can be either an array - * specifying the common HTML attributes for all body column, or an anonymous function that + * @var array|\Closure the HTML attributes for the indivdual table body column. This can be either an array + * specifying the common HTML attributes for indivdual body column, or an anonymous function that * returns an array of the HTML attributes. It should have the following signature: * * ```php From 5a0c69d63e3dcb972d1584f0e96c5676d51fe748 Mon Sep 17 00:00:00 2001 From: Ankit Padia Date: Mon, 10 Dec 2018 16:36:19 +0530 Subject: [PATCH 134/247] Moved `columnOptions` to BaseColumn --- src/TabularColumn.php | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/TabularColumn.php b/src/TabularColumn.php index ee551cb..e5dbf8e 100644 --- a/src/TabularColumn.php +++ b/src/TabularColumn.php @@ -18,23 +18,7 @@ * @property TabularInput $context */ class TabularColumn extends BaseColumn -{ - /** - * @var array|\Closure the HTML attributes for the indivdual table body column. This can be either an array - * specifying the common HTML attributes for indivdual body column, or an anonymous function that - * returns an array of the HTML attributes. It should have the following signature: - * - * ```php - * function ($model, $index, $context) - * ``` - * - * - `$model`: the current data model being rendered - * - `$index`: the zero-based index of the data model in the model array - * - `$context`: the widget object - * - */ - public $columnOptions = []; - +{ /** * Returns element's name. * From 9d2652bcf2b89597a89b8a70711f5aba9bbf3e8c Mon Sep 17 00:00:00 2001 From: Ankit Padia Date: Mon, 10 Dec 2018 16:37:59 +0530 Subject: [PATCH 135/247] Added `columOptions` --- src/components/BaseColumn.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 41802a2..bc991a6 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -131,6 +131,23 @@ abstract class BaseColumn extends BaseObject */ public $nameSuffix; + /** + * @var array|\Closure the HTML attributes for the indivdual table body column. This can be either an array + * specifying the common HTML attributes for indivdual body column, or an anonymous function that + * returns an array of the HTML attributes. It should have the following signature: + * + * ```php + * function ($model, $index, $context) + * ``` + * + * - `$model`: the current data model being rendered + * - `$index`: the zero-based index of the data model in the model array + * - `$context`: the widget object + * + * @since 2.17.2 + */ + public $columnOptions = []; + /** * @var Model|ActiveRecordInterface|array */ From d670db2b0814c8ec4c1d1b77ac4c0a0f903d1ce2 Mon Sep 17 00:00:00 2001 From: Ankit Padia Date: Mon, 10 Dec 2018 16:45:47 +0530 Subject: [PATCH 136/247] Added support for `columOptions` in ListRenderer --- src/renderers/ListRenderer.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index 9e337d0..2919281 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -233,6 +233,14 @@ public function renderCellContent($column, $index) Html::addCssClass($options, 'form-group'); } + if (is_callable($column->columnOptions)) { + $columnOptions = call_user_func($column->columnOptions, $column->getModel(), $index, $this->context); + } else { + $columnOptions = $column->columnOptions; + } + + $options = array_merge_recursive($options, $columnOptions); + $content = Html::beginTag('div', $options); if (empty($column->title)) { From bc5aaea8d178efb5f7fef37eaf48d90bccc07d1e Mon Sep 17 00:00:00 2001 From: Ankit Padia Date: Mon, 10 Dec 2018 17:23:20 +0530 Subject: [PATCH 137/247] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be33f4e..e4aed33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Yii2 multiple input change log - #250 accept `\Traversable` in TableRenderer and ListRenderer for `yield` compatibility (bscheshirwork) - #253 allow to omit a name for static column - #257 added `jsPositions` property for the `BaseRenderer` to set right order js-code in `jsInit` and `jsTemplates` (Spell6inder) +- #259 added `columnOptions` property in the `BaseColumn` for TableRenderer and ListRenderer to support HTML options of individual column (InsaneSkull) 2.17.0 ====== From 438ac8ed27ca9d33129ab000b6523de708c7017f Mon Sep 17 00:00:00 2001 From: Ankit Padia Date: Mon, 10 Dec 2018 17:31:12 +0530 Subject: [PATCH 138/247] Correct version --- src/components/BaseColumn.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index bc991a6..16b6a0d 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -144,7 +144,7 @@ abstract class BaseColumn extends BaseObject * - `$index`: the zero-based index of the data model in the model array * - `$context`: the widget object * - * @since 2.17.2 + * @since 2.18.0 */ public $columnOptions = []; From 40e320c2445df33c2142a54f53fa6ef4ef8f2075 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Tue, 11 Dec 2018 10:12:10 +0300 Subject: [PATCH 139/247] prepare to release 2.18.0 --- CHANGELOG.md | 5 ++++- README.md | 2 +- composer.json | 6 +++--- package.json | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4aed33..c31179b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ Yii2 multiple input change log ============================== -2.18.0 (in development) +2.19.0 (in development) ======================= + +2.18.0 +====== - #246 accept `\Traversable` in model attribute for `yield` compatibility (bscheshirwork) - #250 accept `\Traversable` in TableRenderer and ListRenderer for `yield` compatibility (bscheshirwork) - #253 allow to omit a name for static column diff --git a/README.md b/README.md index f2b3a00..5c5737d 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.17.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.18.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index ac79951..364ff81 100644 --- a/composer.json +++ b/composer.json @@ -2,14 +2,14 @@ "name": "unclead/yii2-multiple-input", "description": "Widget for handle multiple inputs for an attribute of Yii2 framework model", "keywords": [ - "yii2", - "yii2 multiple input", + "yii2", + "yii2 multiple input", "yii2 array input", "yii2 multiple field", "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.17.1", + "version": "2.18.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 09c239a..be96021 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.17.1", + "version": "2.18.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 311e5d229e625c918c83952c1aa647083845c76d Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 14 Dec 2018 16:03:21 +0300 Subject: [PATCH 140/247] make `createRenderer` protected instead of private --- src/MultipleInput.php | 4 ++-- src/TabularInput.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 02ede92..cf79351 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -301,7 +301,7 @@ public function run() /** * @return TableRenderer */ - private function createRenderer() + protected function createRenderer() { if($this->sortable) { $drag = [ @@ -375,4 +375,4 @@ private function createRenderer() return Yii::createObject($config); } -} \ No newline at end of file +} diff --git a/src/TabularInput.php b/src/TabularInput.php index cf4e3b3..fc6f9a0 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -249,7 +249,7 @@ public function run() /** * @return TableRenderer */ - private function createRenderer() + protected function createRenderer() { if($this->sortable) { $drag = [ @@ -321,4 +321,4 @@ private function createRenderer() return Yii::createObject($config); } -} \ No newline at end of file +} From 25b7a4b39a49db2c4287cd8b3ded740da418ea81 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 14 Dec 2018 16:28:04 +0300 Subject: [PATCH 141/247] #249 try to use copy of a model to set default value before passing it to a widget --- src/components/BaseColumn.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 16b6a0d..27c5099 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -147,7 +147,7 @@ abstract class BaseColumn extends BaseObject * @since 2.18.0 */ public $columnOptions = []; - + /** * @var Model|ActiveRecordInterface|array */ @@ -584,9 +584,13 @@ protected function renderWidget($type, $name, $value, $options) $model = $this->getModel(); if ($model instanceof Model) { - $model->{$this->name} = $value; + // @see https://github.com/unclead/yii2-multiple-input/issues/249 + // don't modify original model + $cloneModel = clone $model; + $cloneModel->{$this->name} = $value; + $widgetOptions = [ - 'model' => $model, + 'model' => $cloneModel, 'attribute' => $this->name, 'value' => $value, 'options' => [ From 0bc68df9c923856f6d27e9789b1d0440639d618a Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Thu, 10 Jan 2019 15:27:02 +0300 Subject: [PATCH 142/247] Use DynamecModel instead use setter of real model --- src/components/BaseColumn.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 27c5099..a2ee19e 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -9,6 +9,8 @@ namespace unclead\multipleinput\components; use Closure; +use Yii; +use yii\base\DynamicModel; use yii\base\InvalidConfigException; use yii\base\Model; use yii\base\BaseObject; @@ -586,11 +588,10 @@ protected function renderWidget($type, $name, $value, $options) if ($model instanceof Model) { // @see https://github.com/unclead/yii2-multiple-input/issues/249 // don't modify original model - $cloneModel = clone $model; - $cloneModel->{$this->name} = $value; + $dynamicModel = Yii::createObject(DynamicModel::className(), [[$this->name => $value]]); $widgetOptions = [ - 'model' => $cloneModel, + 'model' => $dynamicModel, 'attribute' => $this->name, 'value' => $value, 'options' => [ From 4489740067a8ae76b9bb8c099e12731bd0b383ca Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 19 Jan 2019 10:40:22 +0300 Subject: [PATCH 143/247] add an ability to modify JQuery sortable options --- src/renderers/BaseRenderer.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 9f121f6..695c2b6 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -430,7 +430,20 @@ private function registerJsSortable() MultipleInputSortableAsset::register($view); // todo override when ListRenderer will use div markup - $options = Json::encode([ + $options = Json::encode($this->getJsSortableOptions()); + $js = "$('#{$this->id} table').sorting($options);"; + $view->registerJs($js); + } + + /** + * Returns an array of JQuery sortable plugin options. + * You can override this method extend plugin behaviour. + * + * @return array + */ + protected function getJsSortableOptions() + { + return [ 'containerSelector' => 'table', 'itemPath' => '> tbody', 'itemSelector' => 'tr', @@ -445,9 +458,7 @@ function(item, container, _super, event) { wrapper.trigger(event, [item]); } ") - ]); - $js = "$('#{$this->id} table').sorting($options);"; - $view->registerJs($js); + ]; } /** From f30a9b34d9d7bd4bc6bd346a4a6c4a0d1adad900 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 20 Jan 2019 18:07:55 +0300 Subject: [PATCH 144/247] don't modify attribute value when render column widget --- examples/views/tabular-input.php | 3 ++- src/components/BaseColumn.php | 14 +++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/examples/views/tabular-input.php b/examples/views/tabular-input.php index 43e61ff..bba351e 100644 --- a/examples/views/tabular-input.php +++ b/examples/views/tabular-input.php @@ -67,6 +67,7 @@ 'name' => 'date', 'type' => '\kartik\date\DatePicker', 'title' => 'Day', + 'defaultValue' => '1970/01/01', 'options' => [ 'pluginOptions' => [ 'autoclose' => true, @@ -85,4 +86,4 @@ 'btn btn-success']); ?> - \ No newline at end of file + diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index a2ee19e..06c8eaf 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -586,18 +586,15 @@ protected function renderWidget($type, $name, $value, $options) $model = $this->getModel(); if ($model instanceof Model) { - // @see https://github.com/unclead/yii2-multiple-input/issues/249 - // don't modify original model - $dynamicModel = Yii::createObject(DynamicModel::className(), [[$this->name => $value]]); - $widgetOptions = [ - 'model' => $dynamicModel, + 'model' => $model, 'attribute' => $this->name, 'value' => $value, 'options' => [ 'id' => $this->normalize($name), 'name' => $name, - 'tabindex' => self::TABINDEX + 'tabindex' => self::TABINDEX, + 'value' => $value ] ]; } else { @@ -605,7 +602,10 @@ protected function renderWidget($type, $name, $value, $options) 'name' => $name, 'value' => $value, 'options' => [ - 'tabindex' => self::TABINDEX + 'id' => $this->normalize($name), + 'name' => $name, + 'tabindex' => self::TABINDEX, + 'value' => $value ] ]; } From 2189acc4e4fcb8ec2d82c64191c0e1d26e4d7d29 Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Tue, 22 Jan 2019 16:25:58 +0300 Subject: [PATCH 145/247] allow currentIndex in js events --- src/assets/src/js/jquery.multipleInput.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index ff81f40..555935a 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -255,9 +255,10 @@ template = template.replaceAll('{' + settings.indexPlaceholder + '}', data.currentIndex); var $addedInput = $(template); + var currentIndex = data.currentIndex; var beforeAddEvent = $.Event(events.beforeAddRow); - $wrapper.trigger(beforeAddEvent, [$addedInput]); + $wrapper.trigger(beforeAddEvent, [$addedInput, currentIndex]); if (beforeAddEvent.result === false) { return; @@ -274,7 +275,7 @@ } values = tmp; - } + } var jsTemplate; @@ -282,12 +283,12 @@ jsTemplate = settings.jsTemplates[i] .replaceAll('{' + settings.indexPlaceholder + '}', data.currentIndex) .replaceAll('%7B' + settings.indexPlaceholder + '%7D', data.currentIndex); - + window.eval(jsTemplate); } var index = 0; - + $(template).find('input, select, textarea').each(function (k, v) { var ele = $(v), tag = v.tagName, @@ -321,7 +322,7 @@ $wrapper.data('multipleInput').currentIndex++; var afterAddEvent = $.Event(events.afterAddRow); - $wrapper.trigger(afterAddEvent, [$addedInput]); + $wrapper.trigger(afterAddEvent, [$addedInput, currentIndex]); }; var removeInput = function ($btn) { @@ -330,9 +331,10 @@ data = $wrapper.data('multipleInput'), settings = data.settings; - if (getCurrentIndex($wrapper) > settings.min) { + var currentIndex = getCurrentIndex($wrapper); + if (currentIndex > settings.min) { var event = $.Event(events.beforeDeleteRow); - $wrapper.trigger(event, [$toDelete]); + $wrapper.trigger(event, [$toDelete, currentIndex]); if (event.result === false) { return; @@ -348,7 +350,7 @@ $(this).remove(); event = $.Event(events.afterDeleteRow); - $wrapper.trigger(event, [$toDelete]); + $wrapper.trigger(event, [$toDelete, currentIndex]); }); } }; From 6b4adbfe413cd9022eb8bb9c5d8e589df82044fe Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Thu, 24 Jan 2019 10:00:54 +0300 Subject: [PATCH 146/247] update minified version --- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 63cc980..9c01bce 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),s=t("#"+u.id),d=s.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),s.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),f(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(i[t]=e)}),u.showGeneralError||d.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),n=!0,clearInterval(h),s.trigger(m)}else v++;(0===d.length||v>10)&&(s.data("multipleInput").currentIndex=p(s),n=!1,clearInterval(h),s.trigger(m))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,f=u.template,d=l.children(".multiple-input-list").first();if(!(null!==u.max&&p(l)>=u.max)){f=f.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(f),v=t.Event(e.beforeAddRow);if(l.trigger(v,[c]),v.result!==!1){if(c.hide().appendTo(d).fadeIn(300),r instanceof Object){var m=[];for(var h in r)r.hasOwnProperty(h)&&m.push(r[h]);r=m}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var w=0;t(f).find("input, select, textarea").each(function(e,i){var l=t(i),a=i.tagName,u=s(l),p=t("#"+u);if(r){var f=r[w];if("INPUT"===a||"TEXTAREA"===a)p.val(f);else if("SELECT"===a)if(f&&f.indexOf("option")!==-1)p.append(f);else{var d=p.find('option[value="'+f+'"]');d.length&&p.val(f)}}n&&o(l),w++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[c])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(p(r)>o.min){var s=t.Event(e.beforeDeleteRow);if(r.trigger(s,[l]),s.result===!1)return;n&&l.find("input, select, textarea").each(function(e,i){u(t(i))}),l.fadeOut(300,function(){t(this).remove(),s=t.Event(e.afterDeleteRow),r.trigger(s,[l])})}},o=function(e){var i=s(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!==r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(e){var i=s(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},f=function(e){var i={};return e.closest("tr").find("td").each(function(e,n){t(n).find("input, select, textarea").each(function(e,n){var r=t(n),l=s(r),a=t("#"+l);i[l]=a.val()})}),i};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},n={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1},i=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},n,r||{}),s=t("#"+u.id),f=s.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),s.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),d(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(n[t]=e)}),u.showGeneralError||f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,i){i=t.extend({},n,i),u.attributes[e]=i}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),i=!0,clearInterval(h),s.trigger(m)}else v++;(0===f.length||v>10)&&(s.data("multipleInput").currentIndex=p(s),i=!1,clearInterval(h),s.trigger(m))},100)},add:function(e){l(t(this),e)},remove:function(e){var n=null;n=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(n)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,n){n=n||null;var i=t(this).data("multipleInput"),r=i.settings;if(null===n){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=n,i.settings=r,t(this).data("multipleInput",i))}},l=function(n,r){var l=t(n).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!==u.max&&p(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),v=a.currentIndex,m=t.Event(e.beforeAddRow);if(l.trigger(m,[c,v]),m.result!==!1){if(c.hide().appendTo(f).fadeIn(300),r instanceof Object){var h=[];for(var I in r)r.hasOwnProperty(I)&&h.push(r[I]);r=h}var g;for(var w in u.jsTemplates)g=u.jsTemplates[w].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(g);var x=0;t(d).find("input, select, textarea").each(function(e,n){var l=t(n),a=n.tagName,u=s(l),p=t("#"+u);if(r){var d=r[x];if("INPUT"===a||"TEXTAREA"===a)p.val(d);else if("SELECT"===a)if(d&&d.indexOf("option")!==-1)p.append(d);else{var f=p.find('option[value="'+d+'"]');f.length&&p.val(d)}}i&&o(l),x++}),l.data("multipleInput").currentIndex++;var y=t.Event(e.afterAddRow);l.trigger(y,[c,v])}}},a=function(n){var r=n.closest(".multiple-input").first(),l=n.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings,s=p(r);if(s>o.min){var d=t.Event(e.beforeDeleteRow);if(r.trigger(d,[l,s]),d.result===!1)return;i&&l.find("input, select, textarea").each(function(e,n){u(t(n))}),l.fadeOut(300,function(){t(this).remove(),d=t.Event(e.afterDeleteRow),r.trigger(d,[l,s])})}},o=function(e){var n=s(e);if(null!==n){var i=t("#"+n),r=i.closest(".multiple-input").first(),l=i.closest("form");if(0!==r.length&&"undefined"==typeof l.yiiActiveForm("find",n)){var a=r.data("multipleInput"),o={},u=n.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:n,input:"#"+n,container:".field-"+n}))}}},u=function(e){var n=s(e);if(null!==n){var i=t("#"+n).closest("form");0!==i.length&&i.yiiActiveForm("remove",n)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var n={};return e.closest("tr").find("td").each(function(e,i){t(i).find("input, select, textarea").each(function(e,i){var r=t(i),l=s(r),a=t("#"+l);n[l]=a.val()})}),n};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file From 3ab6658bf8015204da3f084667ccbca97eeef5d6 Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Thu, 24 Jan 2019 11:28:37 +0300 Subject: [PATCH 147/247] Remove table from selector of sorting --- src/renderers/BaseRenderer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 695c2b6..e4f56ed 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -431,7 +431,7 @@ private function registerJsSortable() // todo override when ListRenderer will use div markup $options = Json::encode($this->getJsSortableOptions()); - $js = "$('#{$this->id} table').sorting($options);"; + $js = "$('#{$this->id}').sorting($options);"; $view->registerJs($js); } From 3a9e06d0cdda6889e663e55436f8ee3f501c3af6 Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Thu, 24 Jan 2019 15:28:01 +0300 Subject: [PATCH 148/247] Use .multiple-input-list instead global container for sorting --- src/renderers/BaseRenderer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index e4f56ed..7f5d734 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -431,7 +431,7 @@ private function registerJsSortable() // todo override when ListRenderer will use div markup $options = Json::encode($this->getJsSortableOptions()); - $js = "$('#{$this->id}').sorting($options);"; + $js = "$('#{$this->id} .multiple-input-list').sorting($options);"; $view->registerJs($js); } From 6f2e30217731a068e5fad697e71a61f19997c0ad Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Thu, 24 Jan 2019 16:16:10 +0300 Subject: [PATCH 149/247] Pass more params to a prepareValue closure --- src/components/BaseColumn.php | 26 +++++++++++++++++------- src/renderers/TableRenderer.php | 35 +++++++++++++++++++++------------ 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 06c8eaf..aea9a1c 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -214,14 +214,19 @@ public function isHiddenInput() /** * Prepares the value of column. - * + * @param array $contextParams the params who passed to closure: + * string $id the id of input element + * string $name the name of input element + * string $indexPlaceholder The index placeholder of multiple input. The {$indexPlaceholder} template will be replace by $index + * int $index The index of multiple input + * int $columnIndex The index of current model attributes * @return mixed */ - protected function prepareValue() + protected function prepareValue($contextParams = []) { $data = $this->getModel(); if ($this->value instanceof \Closure) { - $value = call_user_func($this->value, $data); + $value = call_user_func($this->value, $data, $contextParams); } else { $value = null; if ($data instanceof ActiveRecordInterface ) { @@ -284,11 +289,18 @@ private function normalize($name) { /** * Renders the input. * - * @param string $name the name of the input - * @param array $options the HTML options of input + * @param string $name the name of the input + * @param array $options the HTML options of input + * @param array $contextParams the params who passed to closure: + * string $id the id of input element + * string $name the name of input element + * string $indexPlaceholder The index placeholder of multiple input. The {$indexPlaceholder} template will be replace by $index + * int $index The index of multiple input + * int $columnIndex The index of current model attributes * @return string + * @throws InvalidConfigException */ - public function renderInput($name, $options) + public function renderInput($name, $options, $contextParams = []) { if ($this->options instanceof \Closure) { $optionsExt = call_user_func($this->options, $this->getModel()); @@ -301,7 +313,7 @@ public function renderInput($name, $options) $value = null; if ($this->type !== self::TYPE_DRAGCOLUMN) { - $value = $this->prepareValue(); + $value = $this->prepareValue($contextParams); } if (isset($options['items'])) { diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index af1ae93..54f6264 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -117,14 +117,14 @@ private function hasHeader() if ($this->min === 0 || $this->isAddButtonPositionHeader()) { return true; } - + foreach ($this->columns as $column) { /* @var $column BaseColumn */ if ($column->title) { return true; } } - + return false; } @@ -141,7 +141,7 @@ private function renderHeaderCell($column) $options = $column->headerOptions; Html::addCssClass($options, 'list-cell__' . $column->name); - + return Html::tag('th', $column->title, $options); } @@ -186,7 +186,7 @@ protected function renderBody() $rows[] = $this->renderRowContent($i); } } - + return Html::tag('tbody', implode("\n", $rows)); } @@ -207,13 +207,14 @@ private function renderRowContent($index = null, $item = null) $cells[] = $this->renderActionColumn($index, $item, true); } + $columnIndex = 0; foreach ($this->columns as $column) { /* @var $column BaseColumn */ $column->setModel($item); if ($column->isHiddenInput()) { - $hiddenInputs[] = $this->renderCellContent($column, $index); + $hiddenInputs[] = $this->renderCellContent($column, $index, $columnIndex++); } else { - $cells[] = $this->renderCellContent($column, $index); + $cells[] = $this->renderCellContent($column, $index, $columnIndex++); } } if ($this->cloneButton) { @@ -228,7 +229,7 @@ private function renderRowContent($index = null, $item = null) $hiddenInputs = implode("\n", $hiddenInputs); $cells[0] = preg_replace('/^(]+>)(.*)(<\/td>)$/s', '${1}' . $hiddenInputs . '$2$3', $cells[0]); } - + $content = Html::tag('tr', implode("\n", $cells), $this->prepareRowOptions($index, $item)); if ($index !== null) { @@ -263,9 +264,10 @@ protected function prepareRowOptions($index, $item) * * @param BaseColumn $column * @param int|null $index + * @param int|null $columnIndex * @return string */ - public function renderCellContent($column, $index) + public function renderCellContent($column, $index, $columnIndex = null) { $id = $column->getElementId($index); $name = $column->getElementName($index); @@ -279,7 +281,14 @@ public function renderCellContent($column, $index) if (substr($id, -4) === 'drag') { $options = ArrayHelper::merge($options, ['class' => $this->iconMap['drag-handle']]); } - $input = $column->renderInput($name, $options); + $input = $column->renderInput($name, $options, [ + 'id' => $id, + 'name' => $name, + 'indexPlaceholder' => $this->getIndexPlaceholder(), + 'index' => $index, + 'columnIndex' => $columnIndex, + 'context' => $this->context, + ]); if ($column->isHiddenInput()) { return $input; @@ -305,7 +314,7 @@ public function renderCellContent($column, $index) if ($hasError) { Html::addCssClass($wrapperOptions, 'has-error'); } - + if (is_callable($column->columnOptions)) { $columnOptions = call_user_func($column->columnOptions, $column->getModel(), $index, $this->context); } else { @@ -313,7 +322,7 @@ public function renderCellContent($column, $index) } Html::addCssClass($columnOptions, 'list-cell__' . $column->name); - + $input = Html::tag('div', $input, $wrapperOptions); return Html::tag('td', $input, $columnOptions); @@ -386,7 +395,7 @@ private function renderAddButton() ]; Html::addCssClass($options, $this->addButtonOptions['class']); - + return Html::tag('div', $this->addButtonOptions['label'], $options); } @@ -402,7 +411,7 @@ private function renderRemoveButton() ]; Html::addCssClass($options, $this->removeButtonOptions['class']); - + return Html::tag('div', $this->removeButtonOptions['label'], $options); } From c7e5e485ddd4e75a033d5a2d634a8b20f7823713 Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Thu, 24 Jan 2019 16:22:20 +0300 Subject: [PATCH 150/247] Change template of build ListRenderer - avoid table tag --- src/renderers/ListRenderer.php | 153 +++++++++++++++++++++------------ 1 file changed, 98 insertions(+), 55 deletions(-) diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index 2919281..eeb8d33 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -8,11 +8,14 @@ namespace unclead\multipleinput\renderers; +use unclead\multipleinput\assets\MultipleInputAsset; +use unclead\multipleinput\assets\MultipleInputSortableAsset; use yii\base\InvalidConfigException; use yii\db\ActiveRecordInterface; use yii\helpers\ArrayHelper; use yii\helpers\Html; use unclead\multipleinput\components\BaseColumn; +use yii\helpers\Json; /** * Class ListRenderer @@ -22,6 +25,7 @@ class ListRenderer extends BaseRenderer { /** * @return mixed + * @throws InvalidConfigException */ protected function internalRender() { @@ -35,10 +39,10 @@ protected function internalRender() Html::addCssClass($options, 'multiple-input-list list-renderer'); if ($this->isBootstrapTheme()) { - Html::addCssClass($options, 'table form-horizontal'); + Html::addCssClass($options, 'form-horizontal'); } - $content = Html::tag('table', implode("\n", $content), $options); + $content = Html::tag('div', implode("\n", $content), $options); return Html::tag('div', $content, [ 'id' => $this->id, @@ -53,24 +57,17 @@ protected function internalRender() */ public function renderHeader() { - if ($this->min !== 0 || !$this->isAddButtonPositionHeader()) { + if (!$this->isAddButtonPositionHeader()) { return ''; } - $button = $this->isAddButtonPositionHeader() ? $this->renderAddButton() : ''; - - $content = []; - $content[] = Html::tag('td', ' '); - - if ($this->cloneButton) { - $content[] = Html::tag('td', ' '); - } - - $content[] = Html::tag('td', $button, [ - 'class' => 'list-cell__button', - ]); + $options = ['class' => 'list-cell__button']; + $layoutConfig = array_merge([ + 'buttonAddClass' => $this->isBootstrapTheme() ? 'col-sm-offset-9 col-sm-3' : '', + ], $this->layoutConfig); + Html::addCssClass($options, $layoutConfig['buttonAddClass']); - return Html::tag('thead', Html::tag('tr', implode("\n", $content))); + return Html::tag('div', $this->renderAddButton(), $options); } /** @@ -84,13 +81,13 @@ public function renderFooter() return ''; } - $cells = []; - $cells[] = Html::tag('td', ' '); - $cells[] = Html::tag('td', $this->renderAddButton(), [ - 'class' => 'list-cell__button' - ]); + $options = ['class' => 'list-cell__button']; + $layoutConfig = array_merge([ + 'buttonAddClass' => $this->isBootstrapTheme() ? 'col-sm-offset-9 col-sm-3' : '', + ], $this->layoutConfig); + Html::addCssClass($options, $layoutConfig['buttonAddClass']); - return Html::tag('tfoot', Html::tag('tr', implode("\n", $cells))); + return Html::tag('div', $this->renderAddButton(), $options); } /** @@ -122,7 +119,7 @@ protected function renderBody() } } - return Html::tag('tbody', implode("\n", $rows)); + return implode("\n", $rows); } /** @@ -131,29 +128,18 @@ protected function renderBody() * @param int $index * @param ActiveRecordInterface|array $item * @return mixed - * @throws InvalidConfigException */ private function renderRowContent($index = null, $item = null) { $elements = []; + $columnIndex = 0; foreach ($this->columns as $column) { /* @var $column BaseColumn */ $column->setModel($item); - $elements[] = $this->renderCellContent($column, $index); - } - - $content = []; - $content[] = Html::tag('td', implode("\n", $elements)); - if ($this->max !== $this->min) { - $content[] = $this->renderActionColumn($index); - } - - if ($this->cloneButton) { - $content[] = $this->renderCloneColumn(); + $elements[] = $this->renderCellContent($column, $index, $columnIndex++); } - $content = Html::tag('tr', implode("\n", $content), $this->prepareRowOptions($index, $item)); - + $content = Html::tag('div', implode("\n", $elements), $this->prepareRowOptions($index, $item)); if ($index !== null) { $content = str_replace('{' . $this->getIndexPlaceholder() . '}', $index, $content); } @@ -186,14 +172,31 @@ protected function prepareRowOptions($index, $item) * * @param BaseColumn $column * @param int|null $index + * @param int|null $columnIndex * @return string + * @throws \Exception */ - public function renderCellContent($column, $index) + public function renderCellContent($column, $index, $columnIndex = null) { - $id = $column->getElementId($index); - $name = $column->getElementName($index); - $input = $column->renderInput($name, [ - 'id' => $id + $id = $column->getElementId($index); + $name = $column->getElementName($index); + + /** + * This class inherits iconMap from BaseRenderer + * If the input to be rendered is a drag column, we give it the appropriate icon class + * via the $options array + */ + $options = ['id' => $id]; + if (substr($id, -4) === 'drag') { + $options = ArrayHelper::merge($options, ['class' => $this->iconMap['drag-handle']]); + } + $input = $column->renderInput($name, $options, [ + 'id' => $id, + 'name' => $name, + 'indexPlaceholder' => $this->getIndexPlaceholder(), + 'index' => $index, + 'columnIndex' => $columnIndex, + 'context' => $this->context, ]); if ($column->isHiddenInput()) { @@ -201,10 +204,10 @@ public function renderCellContent($column, $index) } $layoutConfig = array_merge([ - 'offsetClass' => $this->isBootstrapTheme() ? 'col-sm-offset-3' : '', - 'labelClass' => $this->isBootstrapTheme() ? 'col-sm-3' : '', - 'wrapperClass' => $this->isBootstrapTheme() ? 'col-sm-6' : '', - 'errorClass' => $this->isBootstrapTheme() ? 'col-sm-offset-3 col-sm-6' : '', + 'offsetClass' => $this->isBootstrapTheme() ? 'col-sm-offset-3' : '', + 'labelClass' => $this->isBootstrapTheme() ? 'col-sm-3' : '', + 'wrapperClass' => $this->isBootstrapTheme() ? 'col-sm-6' : '', + 'errorClass' => $this->isBootstrapTheme() ? 'col-sm-offset-3 col-sm-6' : '', ], $this->layoutConfig); Html::addCssClass($column->errorOptions, $layoutConfig['errorClass']); @@ -240,7 +243,7 @@ public function renderCellContent($column, $index) } $options = array_merge_recursive($options, $columnOptions); - + $content = Html::beginTag('div', $options); if (empty($column->title)) { @@ -256,6 +259,16 @@ public function renderCellContent($column, $index) $content .= Html::tag('div', $input, $wrapperOptions); + // first line + if ($columnIndex == 0) { + if ($this->max !== $this->min) { + $content .= $this->renderActionColumn($index); + } + if ($this->cloneButton) { + $content .= $this->renderCloneColumn(); + } + } + if ($column->enableError) { $content .= "\n" . $column->renderError($error); } @@ -271,28 +284,35 @@ public function renderCellContent($column, $index) * @param null|int $index * @param null|ActiveRecordInterface|array $item * @return string - * @throws \Exception */ private function renderActionColumn($index = null, $item = null) { $content = $this->getActionButton($index) . $this->getExtraButtons($index, $item); - return Html::tag('td', $content, [ - 'class' => 'list-cell__button', - ]); + $options = ['class' => 'list-cell__button']; + $layoutConfig = array_merge([ + 'buttonActionClass' => $this->isBootstrapTheme() ? 'col-sm-offset-0 col-sm-2' : '', + ], $this->layoutConfig); + Html::addCssClass($options, $layoutConfig['buttonActionClass']); + + return Html::tag('div', $content, $options); } /** * Renders the clone column. * * @return string - * @throws \Exception */ private function renderCloneColumn() { - return Html::tag('td', $this->renderCloneButton(), [ - 'class' => 'list-cell__button', - ]); + + $options = ['class' => 'list-cell__button']; + $layoutConfig = array_merge([ + 'buttonCloneClass' => $this->isBootstrapTheme() ? 'col-sm-offset-0 col-sm-1' : '', + ], $this->layoutConfig); + Html::addCssClass($options, $layoutConfig['buttonCloneClass']); + + return Html::tag('div', $this->renderCloneButton(), $options); } private function getActionButton($index) @@ -364,4 +384,27 @@ protected function prepareTemplate() { return $this->renderRowContent(); } + + /** + * Returns an array of JQuery sortable plugin options for ListRenderer + * @return array + */ + protected function getJsSortableOptions() + { + return [ + 'containerSelector' => '.list-renderer', + 'itemSelector' => '.multiple-input-list__item', + 'placeholder' => '
    ', + 'handle' => '.drag-handle', + 'onDrop' => new \yii\web\JsExpression(" + function(item, container, _super, event) { + _super(item, container, _super, event); + + var wrapper = item.closest('.multiple-input').first(); + event = $.Event('afterDropRow'); + wrapper.trigger(event, [item]); + } + ") + ]; + } } From 6f93873247361906b6aecf98bd2125ad390f5e7d Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Thu, 24 Jan 2019 17:13:01 +0300 Subject: [PATCH 151/247] use parent options for sortable --- src/renderers/ListRenderer.php | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index eeb8d33..e62b66f 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -8,14 +8,12 @@ namespace unclead\multipleinput\renderers; -use unclead\multipleinput\assets\MultipleInputAsset; -use unclead\multipleinput\assets\MultipleInputSortableAsset; use yii\base\InvalidConfigException; use yii\db\ActiveRecordInterface; use yii\helpers\ArrayHelper; use yii\helpers\Html; use unclead\multipleinput\components\BaseColumn; -use yii\helpers\Json; +use yii\helpers\UnsetArrayValue; /** * Class ListRenderer @@ -391,20 +389,11 @@ protected function prepareTemplate() */ protected function getJsSortableOptions() { - return [ - 'containerSelector' => '.list-renderer', - 'itemSelector' => '.multiple-input-list__item', - 'placeholder' => '
    ', - 'handle' => '.drag-handle', - 'onDrop' => new \yii\web\JsExpression(" - function(item, container, _super, event) { - _super(item, container, _super, event); - - var wrapper = item.closest('.multiple-input').first(); - event = $.Event('afterDropRow'); - wrapper.trigger(event, [item]); - } - ") - ]; + return ArrayHelper::merge(parent::getJsSortableOptions(), + [ + 'containerSelector' => '.list-renderer', + 'itemPath' => new UnsetArrayValue, + 'itemSelector' => '.multiple-input-list__item', + ]); } } From c33eadb582e0e4c64cf44e932ee3b50437f2b806 Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Fri, 25 Jan 2019 14:55:02 +0300 Subject: [PATCH 152/247] Add template for input --- src/components/BaseColumn.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index aea9a1c..abb2e7f 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -150,6 +150,12 @@ abstract class BaseColumn extends BaseObject */ public $columnOptions = []; + /** + * @var string the template of input for customize view. + * For example: '
    {input}
    ' + */ + public $inputTemplate = '{input}'; + /** * @var Model|ActiveRecordInterface|array */ @@ -326,7 +332,7 @@ public function renderInput($name, $options, $contextParams = []) $input = $this->renderDefault($name, $value, $options); } - return $input; + return strtr($this->inputTemplate, ['{input}' => $input]); } From 7ea250acc2dc40963eb68e64085f11f58fa62617 Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Fri, 1 Feb 2019 14:00:22 +0300 Subject: [PATCH 153/247] separate ListRenderer and DivRenderer for BC reasons --- src/renderers/DivRenderer.php | 399 +++++++++++++++++++++++++++++++++ src/renderers/ListRenderer.php | 142 +++++------- 2 files changed, 454 insertions(+), 87 deletions(-) create mode 100644 src/renderers/DivRenderer.php diff --git a/src/renderers/DivRenderer.php b/src/renderers/DivRenderer.php new file mode 100644 index 0000000..e6a69c9 --- /dev/null +++ b/src/renderers/DivRenderer.php @@ -0,0 +1,399 @@ +renderHeader(); + $content[] = $this->renderBody(); + $content[] = $this->renderFooter(); + + $options = []; + Html::addCssClass($options, 'multiple-input-list list-renderer'); + + if ($this->isBootstrapTheme()) { + Html::addCssClass($options, 'form-horizontal'); + } + + $content = Html::tag('div', implode("\n", $content), $options); + + return Html::tag('div', $content, [ + 'id' => $this->id, + 'class' => 'multiple-input' + ]); + } + + /** + * Renders the header. + * + * @return string + */ + public function renderHeader() + { + if (!$this->isAddButtonPositionHeader()) { + return ''; + } + + $options = ['class' => 'list-cell__button']; + $layoutConfig = array_merge([ + 'buttonAddClass' => $this->isBootstrapTheme() ? 'col-sm-offset-9 col-sm-3' : '', + ], $this->layoutConfig); + Html::addCssClass($options, $layoutConfig['buttonAddClass']); + + return Html::tag('div', $this->renderAddButton(), $options); + } + + /** + * Renders the footer. + * + * @return string + */ + public function renderFooter() + { + if (!$this->isAddButtonPositionFooter()) { + return ''; + } + + $options = ['class' => 'list-cell__button']; + $layoutConfig = array_merge([ + 'buttonAddClass' => $this->isBootstrapTheme() ? 'col-sm-offset-9 col-sm-3' : '', + ], $this->layoutConfig); + Html::addCssClass($options, $layoutConfig['buttonAddClass']); + + return Html::tag('div', $this->renderAddButton(), $options); + } + + /** + * Renders the body. + * + * @return string + * @throws \yii\base\InvalidConfigException + * @throws \yii\base\InvalidParamException + */ + protected function renderBody() + { + $rows = []; + + if ($this->data) { + $j = 0; + foreach ($this->data as $index => $item) { + if ($j++ <= $this->max) { + $rows[] = $this->renderRowContent($index, $item); + } else { + break; + } + } + for ($i = $j; $i < $this->min; $i++) { + $rows[] = $this->renderRowContent($i); + } + } elseif ($this->min > 0) { + for ($i = 0; $i < $this->min; $i++) { + $rows[] = $this->renderRowContent($i); + } + } + + return implode("\n", $rows); + } + + /** + * Renders the row content. + * + * @param int $index + * @param ActiveRecordInterface|array $item + * @return mixed + */ + private function renderRowContent($index = null, $item = null) + { + $elements = []; + $columnIndex = 0; + foreach ($this->columns as $column) { + /* @var $column BaseColumn */ + $column->setModel($item); + $elements[] = $this->renderCellContent($column, $index, $columnIndex++); + } + + $content = Html::tag('div', implode("\n", $elements), $this->prepareRowOptions($index, $item)); + if ($index !== null) { + $content = str_replace('{' . $this->getIndexPlaceholder() . '}', $index, $content); + } + + return $content; + } + + /** + * Prepares the row options. + * + * @param int $index + * @param ActiveRecordInterface|array $item + * @return array + */ + protected function prepareRowOptions($index, $item) + { + if (is_callable($this->rowOptions)) { + $options = call_user_func($this->rowOptions, $item, $index, $this->context); + } else { + $options = $this->rowOptions; + } + + Html::addCssClass($options, 'multiple-input-list__item'); + + return $options; + } + + /** + * Renders the cell content. + * + * @param BaseColumn $column + * @param int|null $index + * @param int|null $columnIndex + * @return string + * @throws \Exception + */ + public function renderCellContent($column, $index, $columnIndex = null) + { + $id = $column->getElementId($index); + $name = $column->getElementName($index); + + /** + * This class inherits iconMap from BaseRenderer + * If the input to be rendered is a drag column, we give it the appropriate icon class + * via the $options array + */ + $options = ['id' => $id]; + if (substr($id, -4) === 'drag') { + $options = ArrayHelper::merge($options, ['class' => $this->iconMap['drag-handle']]); + } + $input = $column->renderInput($name, $options, [ + 'id' => $id, + 'name' => $name, + 'indexPlaceholder' => $this->getIndexPlaceholder(), + 'index' => $index, + 'columnIndex' => $columnIndex, + 'context' => $this->context, + ]); + + if ($column->isHiddenInput()) { + return $input; + } + + $layoutConfig = array_merge([ + 'offsetClass' => $this->isBootstrapTheme() ? 'col-sm-offset-3' : '', + 'labelClass' => $this->isBootstrapTheme() ? 'col-sm-3' : '', + 'wrapperClass' => $this->isBootstrapTheme() ? 'col-sm-6' : '', + 'errorClass' => $this->isBootstrapTheme() ? 'col-sm-offset-3 col-sm-6' : '', + ], $this->layoutConfig); + + Html::addCssClass($column->errorOptions, $layoutConfig['errorClass']); + + $hasError = false; + $error = ''; + + if ($index !== null) { + $error = $column->getFirstError($index); + $hasError = !empty($error); + } + + $wrapperOptions = []; + + if ($hasError) { + Html::addCssClass($wrapperOptions, 'has-error'); + } + + Html::addCssClass($wrapperOptions, $layoutConfig['wrapperClass']); + + $options = [ + 'class' => "field-$id list-cell__$column->name" . ($hasError ? ' has-error' : '') + ]; + + if ($this->isBootstrapTheme()) { + Html::addCssClass($options, 'form-group'); + } + + if (is_callable($column->columnOptions)) { + $columnOptions = call_user_func($column->columnOptions, $column->getModel(), $index, $this->context); + } else { + $columnOptions = $column->columnOptions; + } + + $options = array_merge_recursive($options, $columnOptions); + + $content = Html::beginTag('div', $options); + + if (empty($column->title)) { + Html::addCssClass($wrapperOptions, $layoutConfig['offsetClass']); + } else { + $labelOptions = ['class' => $layoutConfig['labelClass']]; + if ($this->isBootstrapTheme()) { + Html::addCssClass($labelOptions, 'control-label'); + } + + $content .= Html::label($column->title, $id, $labelOptions); + } + + $content .= Html::tag('div', $input, $wrapperOptions); + + // first line + if ($columnIndex == 0) { + if ($this->max !== $this->min) { + $content .= $this->renderActionColumn($index); + } + if ($this->cloneButton) { + $content .= $this->renderCloneColumn(); + } + } + + if ($column->enableError) { + $content .= "\n" . $column->renderError($error); + } + + $content .= Html::endTag('div'); + + return $content; + } + + /** + * Renders the action column. + * + * @param null|int $index + * @param null|ActiveRecordInterface|array $item + * @return string + */ + private function renderActionColumn($index = null, $item = null) + { + $content = $this->getActionButton($index) . $this->getExtraButtons($index, $item); + + $options = ['class' => 'list-cell__button']; + $layoutConfig = array_merge([ + 'buttonActionClass' => $this->isBootstrapTheme() ? 'col-sm-offset-0 col-sm-2' : '', + ], $this->layoutConfig); + Html::addCssClass($options, $layoutConfig['buttonActionClass']); + + return Html::tag('div', $content, $options); + } + + /** + * Renders the clone column. + * + * @return string + */ + private function renderCloneColumn() + { + + $options = ['class' => 'list-cell__button']; + $layoutConfig = array_merge([ + 'buttonCloneClass' => $this->isBootstrapTheme() ? 'col-sm-offset-0 col-sm-1' : '', + ], $this->layoutConfig); + Html::addCssClass($options, $layoutConfig['buttonCloneClass']); + + return Html::tag('div', $this->renderCloneButton(), $options); + } + + private function getActionButton($index) + { + if ($index === null || $this->min === 0) { + return $this->renderRemoveButton(); + } + + $index++; + if ($index < $this->min) { + return ''; + } + + if ($index === $this->min) { + return $this->isAddButtonPositionRow() ? $this->renderAddButton() : ''; + } + + return $this->renderRemoveButton(); + } + + private function renderAddButton() + { + $options = [ + 'class' => 'multiple-input-list__btn js-input-plus', + ]; + Html::addCssClass($options, $this->addButtonOptions['class']); + + return Html::tag('div', $this->addButtonOptions['label'], $options); + } + + /** + * Renders remove button. + * + * @return string + */ + private function renderRemoveButton() + { + $options = [ + 'class' => 'multiple-input-list__btn js-input-remove', + ]; + Html::addCssClass($options, $this->removeButtonOptions['class']); + + return Html::tag('div', $this->removeButtonOptions['label'], $options); + } + + /** + * Renders clone button. + * + * @return string + */ + private function renderCloneButton() + { + $options = [ + 'class' => 'multiple-input-list__btn js-input-clone', + ]; + Html::addCssClass($options, $this->cloneButtonOptions['class']); + + return Html::tag('div', $this->cloneButtonOptions['label'], $options); + } + + /** + * Returns template for using in js. + * + * @return string + * + * @throws \yii\base\InvalidConfigException + */ + protected function prepareTemplate() + { + return $this->renderRowContent(); + } + + /** + * Returns an array of JQuery sortable plugin options for DivRenderer + * @return array + */ + protected function getJsSortableOptions() + { + return ArrayHelper::merge(parent::getJsSortableOptions(), + [ + 'containerSelector' => '.list-renderer', + 'itemPath' => new UnsetArrayValue, + 'itemSelector' => '.multiple-input-list__item', + ]); + } +} diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index e62b66f..2919281 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -13,7 +13,6 @@ use yii\helpers\ArrayHelper; use yii\helpers\Html; use unclead\multipleinput\components\BaseColumn; -use yii\helpers\UnsetArrayValue; /** * Class ListRenderer @@ -23,7 +22,6 @@ class ListRenderer extends BaseRenderer { /** * @return mixed - * @throws InvalidConfigException */ protected function internalRender() { @@ -37,10 +35,10 @@ protected function internalRender() Html::addCssClass($options, 'multiple-input-list list-renderer'); if ($this->isBootstrapTheme()) { - Html::addCssClass($options, 'form-horizontal'); + Html::addCssClass($options, 'table form-horizontal'); } - $content = Html::tag('div', implode("\n", $content), $options); + $content = Html::tag('table', implode("\n", $content), $options); return Html::tag('div', $content, [ 'id' => $this->id, @@ -55,17 +53,24 @@ protected function internalRender() */ public function renderHeader() { - if (!$this->isAddButtonPositionHeader()) { + if ($this->min !== 0 || !$this->isAddButtonPositionHeader()) { return ''; } - $options = ['class' => 'list-cell__button']; - $layoutConfig = array_merge([ - 'buttonAddClass' => $this->isBootstrapTheme() ? 'col-sm-offset-9 col-sm-3' : '', - ], $this->layoutConfig); - Html::addCssClass($options, $layoutConfig['buttonAddClass']); + $button = $this->isAddButtonPositionHeader() ? $this->renderAddButton() : ''; - return Html::tag('div', $this->renderAddButton(), $options); + $content = []; + $content[] = Html::tag('td', ' '); + + if ($this->cloneButton) { + $content[] = Html::tag('td', ' '); + } + + $content[] = Html::tag('td', $button, [ + 'class' => 'list-cell__button', + ]); + + return Html::tag('thead', Html::tag('tr', implode("\n", $content))); } /** @@ -79,13 +84,13 @@ public function renderFooter() return ''; } - $options = ['class' => 'list-cell__button']; - $layoutConfig = array_merge([ - 'buttonAddClass' => $this->isBootstrapTheme() ? 'col-sm-offset-9 col-sm-3' : '', - ], $this->layoutConfig); - Html::addCssClass($options, $layoutConfig['buttonAddClass']); + $cells = []; + $cells[] = Html::tag('td', ' '); + $cells[] = Html::tag('td', $this->renderAddButton(), [ + 'class' => 'list-cell__button' + ]); - return Html::tag('div', $this->renderAddButton(), $options); + return Html::tag('tfoot', Html::tag('tr', implode("\n", $cells))); } /** @@ -117,7 +122,7 @@ protected function renderBody() } } - return implode("\n", $rows); + return Html::tag('tbody', implode("\n", $rows)); } /** @@ -126,18 +131,29 @@ protected function renderBody() * @param int $index * @param ActiveRecordInterface|array $item * @return mixed + * @throws InvalidConfigException */ private function renderRowContent($index = null, $item = null) { $elements = []; - $columnIndex = 0; foreach ($this->columns as $column) { /* @var $column BaseColumn */ $column->setModel($item); - $elements[] = $this->renderCellContent($column, $index, $columnIndex++); + $elements[] = $this->renderCellContent($column, $index); + } + + $content = []; + $content[] = Html::tag('td', implode("\n", $elements)); + if ($this->max !== $this->min) { + $content[] = $this->renderActionColumn($index); } - $content = Html::tag('div', implode("\n", $elements), $this->prepareRowOptions($index, $item)); + if ($this->cloneButton) { + $content[] = $this->renderCloneColumn(); + } + + $content = Html::tag('tr', implode("\n", $content), $this->prepareRowOptions($index, $item)); + if ($index !== null) { $content = str_replace('{' . $this->getIndexPlaceholder() . '}', $index, $content); } @@ -170,31 +186,14 @@ protected function prepareRowOptions($index, $item) * * @param BaseColumn $column * @param int|null $index - * @param int|null $columnIndex * @return string - * @throws \Exception */ - public function renderCellContent($column, $index, $columnIndex = null) + public function renderCellContent($column, $index) { - $id = $column->getElementId($index); - $name = $column->getElementName($index); - - /** - * This class inherits iconMap from BaseRenderer - * If the input to be rendered is a drag column, we give it the appropriate icon class - * via the $options array - */ - $options = ['id' => $id]; - if (substr($id, -4) === 'drag') { - $options = ArrayHelper::merge($options, ['class' => $this->iconMap['drag-handle']]); - } - $input = $column->renderInput($name, $options, [ - 'id' => $id, - 'name' => $name, - 'indexPlaceholder' => $this->getIndexPlaceholder(), - 'index' => $index, - 'columnIndex' => $columnIndex, - 'context' => $this->context, + $id = $column->getElementId($index); + $name = $column->getElementName($index); + $input = $column->renderInput($name, [ + 'id' => $id ]); if ($column->isHiddenInput()) { @@ -202,10 +201,10 @@ public function renderCellContent($column, $index, $columnIndex = null) } $layoutConfig = array_merge([ - 'offsetClass' => $this->isBootstrapTheme() ? 'col-sm-offset-3' : '', - 'labelClass' => $this->isBootstrapTheme() ? 'col-sm-3' : '', - 'wrapperClass' => $this->isBootstrapTheme() ? 'col-sm-6' : '', - 'errorClass' => $this->isBootstrapTheme() ? 'col-sm-offset-3 col-sm-6' : '', + 'offsetClass' => $this->isBootstrapTheme() ? 'col-sm-offset-3' : '', + 'labelClass' => $this->isBootstrapTheme() ? 'col-sm-3' : '', + 'wrapperClass' => $this->isBootstrapTheme() ? 'col-sm-6' : '', + 'errorClass' => $this->isBootstrapTheme() ? 'col-sm-offset-3 col-sm-6' : '', ], $this->layoutConfig); Html::addCssClass($column->errorOptions, $layoutConfig['errorClass']); @@ -241,7 +240,7 @@ public function renderCellContent($column, $index, $columnIndex = null) } $options = array_merge_recursive($options, $columnOptions); - + $content = Html::beginTag('div', $options); if (empty($column->title)) { @@ -257,16 +256,6 @@ public function renderCellContent($column, $index, $columnIndex = null) $content .= Html::tag('div', $input, $wrapperOptions); - // first line - if ($columnIndex == 0) { - if ($this->max !== $this->min) { - $content .= $this->renderActionColumn($index); - } - if ($this->cloneButton) { - $content .= $this->renderCloneColumn(); - } - } - if ($column->enableError) { $content .= "\n" . $column->renderError($error); } @@ -282,35 +271,28 @@ public function renderCellContent($column, $index, $columnIndex = null) * @param null|int $index * @param null|ActiveRecordInterface|array $item * @return string + * @throws \Exception */ private function renderActionColumn($index = null, $item = null) { $content = $this->getActionButton($index) . $this->getExtraButtons($index, $item); - $options = ['class' => 'list-cell__button']; - $layoutConfig = array_merge([ - 'buttonActionClass' => $this->isBootstrapTheme() ? 'col-sm-offset-0 col-sm-2' : '', - ], $this->layoutConfig); - Html::addCssClass($options, $layoutConfig['buttonActionClass']); - - return Html::tag('div', $content, $options); + return Html::tag('td', $content, [ + 'class' => 'list-cell__button', + ]); } /** * Renders the clone column. * * @return string + * @throws \Exception */ private function renderCloneColumn() { - - $options = ['class' => 'list-cell__button']; - $layoutConfig = array_merge([ - 'buttonCloneClass' => $this->isBootstrapTheme() ? 'col-sm-offset-0 col-sm-1' : '', - ], $this->layoutConfig); - Html::addCssClass($options, $layoutConfig['buttonCloneClass']); - - return Html::tag('div', $this->renderCloneButton(), $options); + return Html::tag('td', $this->renderCloneButton(), [ + 'class' => 'list-cell__button', + ]); } private function getActionButton($index) @@ -382,18 +364,4 @@ protected function prepareTemplate() { return $this->renderRowContent(); } - - /** - * Returns an array of JQuery sortable plugin options for ListRenderer - * @return array - */ - protected function getJsSortableOptions() - { - return ArrayHelper::merge(parent::getJsSortableOptions(), - [ - 'containerSelector' => '.list-renderer', - 'itemPath' => new UnsetArrayValue, - 'itemSelector' => '.multiple-input-list__item', - ]); - } } From 753e7be9c6c6a4f90c70d3be8950d4c83b64dd54 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 2 Feb 2019 10:38:08 +0300 Subject: [PATCH 154/247] prepare to release 2.19.0 --- CHANGELOG.md | 8 +++++++- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c31179b..43f9a6e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ Yii2 multiple input change log ============================== -2.19.0 (in development) +2.20.0 (in development) ======================= +2.19.0 +====== +- add template for input (bscheshirwork) +- pass more params to a prepareValue closure (bscheshirwork) +- add DivRenderer (bscheshirwork) + 2.18.0 ====== - #246 accept `\Traversable` in model attribute for `yield` compatibility (bscheshirwork) diff --git a/README.md b/README.md index 5c5737d..97720a9 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.18.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.19.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 364ff81..0ced4c5 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.18.0", + "version": "2.19.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index be96021..611734a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.18.0", + "version": "2.19.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 3d4895cb35d45535d554bccd781e8fddfc87820b Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 17 Mar 2019 21:28:10 +0300 Subject: [PATCH 155/247] replace the widget placeholder in all nested options --- src/components/BaseColumn.php | 13 +++++++++++++ src/renderers/TableRenderer.php | 1 + 2 files changed, 14 insertions(+) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index abb2e7f..84f5857 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -18,6 +18,7 @@ use yii\helpers\ArrayHelper; use yii\helpers\Html; use yii\helpers\Inflector; +use yii\helpers\Json; use unclead\multipleinput\renderers\BaseRenderer; /** @@ -317,6 +318,18 @@ public function renderInput($name, $options, $contextParams = []) $options = ArrayHelper::merge($options, $optionsExt); $method = 'render' . Inflector::camelize($this->type); + // @see https://github.com/unclead/yii2-multiple-input/issues/261 + // we have to replace placeholder in all nested options and the best way to do it is: + // 1) encode the options to a json string + // 2) replace the placeholder + // 3) decode the json string back to array + // But we have to leave the placeholder in case of processing a row template + if ($contextParams['index'] !== null) { + $options_string = Json::encode($options); + $options_string = str_replace('{' . $contextParams['indexPlaceholder'] . '}', $contextParams['index'], $options_string); + $options = Json::decode($options_string); + } + $value = null; if ($this->type !== self::TYPE_DRAGCOLUMN) { $value = $this->prepareValue($contextParams); diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 54f6264..0e551b4 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -281,6 +281,7 @@ public function renderCellContent($column, $index, $columnIndex = null) if (substr($id, -4) === 'drag') { $options = ArrayHelper::merge($options, ['class' => $this->iconMap['drag-handle']]); } + $input = $column->renderInput($name, $options, [ 'id' => $id, 'name' => $name, From 3ac263962d5ff7f7df20fb6044752f5cbb5a2778 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 17 Mar 2019 21:37:31 +0300 Subject: [PATCH 156/247] #278 allow name '0' --- src/components/BaseColumn.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 84f5857..9097ae9 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -201,7 +201,7 @@ public function init() $this->name = self::DEFAULT_STATIC_COLUMN_NAME; } - if (empty($this->name)) { + if ($this->isNameEmpty()) { throw new InvalidConfigException("The 'name' option is required."); } @@ -210,6 +210,19 @@ public function init() } } + private function isNameEmpty() + { + if (empty($this->name)) { + if ($this->name === 0 || $this->name === '0') { + return false; + } + + return true; + } + + return false; + } + /** * @return bool whether the type of column is hidden input. */ From d17966f58408b53e582739103bd194af9be14ba1 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 17 Mar 2019 21:40:19 +0300 Subject: [PATCH 157/247] update changelog and prepare to release 2.20.0 --- CHANGELOG.md | 8 +++++++- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43f9a6e..c7dd167 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,15 @@ Yii2 multiple input change log ============================== -2.20.0 (in development) +2.21.0 (in development) ======================= +2.20.0 +====== + +- #278 allow zero name +- #261 replace the widget placeholder in all nested options + 2.19.0 ====== - add template for input (bscheshirwork) diff --git a/README.md b/README.md index 97720a9..80307ae 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.19.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.20.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 0ced4c5..a17341d 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.19.0", + "version": "2.20.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 611734a..f99d53b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.19.0", + "version": "2.20.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 17c77b3b4f4f8d07b563b0e30dd8e0ffaead4b55 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Tue, 19 Mar 2019 20:55:54 +0300 Subject: [PATCH 158/247] try another approach to replace index placeholder in options --- src/components/BaseColumn.php | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 9097ae9..d9a743a 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -18,7 +18,6 @@ use yii\helpers\ArrayHelper; use yii\helpers\Html; use yii\helpers\Inflector; -use yii\helpers\Json; use unclead\multipleinput\renderers\BaseRenderer; /** @@ -332,15 +331,8 @@ public function renderInput($name, $options, $contextParams = []) $method = 'render' . Inflector::camelize($this->type); // @see https://github.com/unclead/yii2-multiple-input/issues/261 - // we have to replace placeholder in all nested options and the best way to do it is: - // 1) encode the options to a json string - // 2) replace the placeholder - // 3) decode the json string back to array - // But we have to leave the placeholder in case of processing a row template if ($contextParams['index'] !== null) { - $options_string = Json::encode($options); - $options_string = str_replace('{' . $contextParams['indexPlaceholder'] . '}', $contextParams['index'], $options_string); - $options = Json::decode($options_string); + $options = $this->replaceIndexPlaceholderInOptions($options, $contextParams['indexPlaceholder'], $contextParams['index']); } $value = null; @@ -361,6 +353,19 @@ public function renderInput($name, $options, $contextParams = []) return strtr($this->inputTemplate, ['{input}' => $input]); } + private function replaceIndexPlaceholderInOptions($options, $indexPlaceholder, $index) + { + $result = []; + foreach ($options as $key => $value) { + if (is_array($value)) { + $result[$key] = $this->replaceIndexPlaceholderInOptions($value, $indexPlaceholder, $index); + } elseif (is_string($value)) { + $result[$key] = str_replace('{' . $indexPlaceholder . '}', $index, $value); + } + } + + return $result; + } /** * Renders drop down list. From 5e21ab734d9187c39ece3a93c9e056def741b271 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Tue, 19 Mar 2019 20:56:30 +0300 Subject: [PATCH 159/247] release 2.20.1 --- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 80307ae..3270ffb 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.20.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.20.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index a17341d..fa750c6 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.20.0", + "version": "2.20.1", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index f99d53b..392f955 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.20.0", + "version": "2.20.1", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 316521a310fecdf4265bde59e9ad84dc4406eb2f Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 20 Mar 2019 18:22:24 +0300 Subject: [PATCH 160/247] pass missed context params in ListRenderer --- src/renderers/ListRenderer.php | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index 2919281..7877e9d 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -192,8 +192,24 @@ public function renderCellContent($column, $index) { $id = $column->getElementId($index); $name = $column->getElementName($index); - $input = $column->renderInput($name, [ - 'id' => $id + + /** + * This class inherits iconMap from BaseRenderer + * If the input to be rendered is a drag column, we give it the appropriate icon class + * via the $options array + */ + $options = ['id' => $id]; + if (substr($id, -4) === 'drag') { + $options = ArrayHelper::merge($options, ['class' => $this->iconMap['drag-handle']]); + } + + $input = $column->renderInput($name, $options, [ + 'id' => $id, + 'name' => $name, + 'indexPlaceholder' => $this->getIndexPlaceholder(), + 'index' => $index, + 'columnIndex' => $columnIndex, + 'context' => $this->context, ]); if ($column->isHiddenInput()) { @@ -240,7 +256,7 @@ public function renderCellContent($column, $index) } $options = array_merge_recursive($options, $columnOptions); - + $content = Html::beginTag('div', $options); if (empty($column->title)) { From ec7f38d9074491e0f9850451393b419deda653fd Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 20 Mar 2019 18:22:52 +0300 Subject: [PATCH 161/247] bump version --- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3270ffb..f9ff855 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.20.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.20.2 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index fa750c6..dbbd809 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.20.1", + "version": "2.20.2", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 392f955..0f27d73 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.20.1", + "version": "2.20.2", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 56658437f7ef549293ba604a9c4fd2dcadf589f9 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Thu, 21 Mar 2019 10:00:59 +0300 Subject: [PATCH 162/247] fix undefined index --- src/renderers/ListRenderer.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index 7877e9d..392053b 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -136,10 +136,12 @@ protected function renderBody() private function renderRowContent($index = null, $item = null) { $elements = []; + + $columnIndex = 0; foreach ($this->columns as $column) { /* @var $column BaseColumn */ $column->setModel($item); - $elements[] = $this->renderCellContent($column, $index); + $elements[] = $this->renderCellContent($column, $index, $columnIndex++); } $content = []; From b5d14d1c7074348d61bd1c775bfe02533863e98f Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Thu, 21 Mar 2019 10:02:10 +0300 Subject: [PATCH 163/247] bump version --- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f9ff855..83bdee4 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.20.2 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.20.3 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index dbbd809..2dacce7 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.20.2", + "version": "2.20.3", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 0f27d73..04b124a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.20.2", + "version": "2.20.3", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From d924d9c1063419d98d0b4b8803547262d66d22b7 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Thu, 21 Mar 2019 11:10:46 +0300 Subject: [PATCH 164/247] declare missed method argument --- README.md | 2 +- composer.json | 2 +- package.json | 2 +- src/renderers/ListRenderer.php | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 83bdee4..0849a41 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.20.3 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.20.4 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 2dacce7..7145581 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.20.3", + "version": "2.20.4", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 04b124a..112292b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.20.3", + "version": "2.20.4", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index 392053b..f6389bd 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -190,7 +190,7 @@ protected function prepareRowOptions($index, $item) * @param int|null $index * @return string */ - public function renderCellContent($column, $index) + public function renderCellContent($column, $index, $columnIndex = null) { $id = $column->getElementId($index); $name = $column->getElementName($index); From 5da611429a3513fac981994a5621e191c8385671 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Thu, 21 Mar 2019 14:04:39 +0300 Subject: [PATCH 165/247] prevent losing numeric options --- README.md | 2 +- composer.json | 2 +- package.json | 2 +- src/components/BaseColumn.php | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0849a41..41b4568 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.20.4 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.20.5 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 7145581..f5b6f2c 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.20.4", + "version": "2.20.5", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 112292b..19f83fb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.20.4", + "version": "2.20.5", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index d9a743a..9d6609b 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -361,6 +361,8 @@ private function replaceIndexPlaceholderInOptions($options, $indexPlaceholder, $ $result[$key] = $this->replaceIndexPlaceholderInOptions($value, $indexPlaceholder, $index); } elseif (is_string($value)) { $result[$key] = str_replace('{' . $indexPlaceholder . '}', $index, $value); + } else { + $result[$key] = $value; } } From 7e263beb559c939c3dc9fd844eb1917441ecb9ad Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 31 Mar 2019 18:58:48 +0300 Subject: [PATCH 166/247] check keys existence --- src/components/BaseColumn.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 9d6609b..5edd131 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -331,7 +331,7 @@ public function renderInput($name, $options, $contextParams = []) $method = 'render' . Inflector::camelize($this->type); // @see https://github.com/unclead/yii2-multiple-input/issues/261 - if ($contextParams['index'] !== null) { + if (isset($contextParams['index']) && isset($contextParams['indexPlaceholder'])) { $options = $this->replaceIndexPlaceholderInOptions($options, $contextParams['indexPlaceholder'], $contextParams['index']); } From 6ee2001f8b108f6c4e4c6dfdf9acfb11d4429554 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 31 Mar 2019 19:15:47 +0300 Subject: [PATCH 167/247] don't cast JsExpression to string after replace widget placeholder --- src/components/BaseColumn.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 5edd131..6934d66 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -18,6 +18,7 @@ use yii\helpers\ArrayHelper; use yii\helpers\Html; use yii\helpers\Inflector; +use yii\web\JsExpression; use unclead\multipleinput\renderers\BaseRenderer; /** @@ -362,6 +363,10 @@ private function replaceIndexPlaceholderInOptions($options, $indexPlaceholder, $ } elseif (is_string($value)) { $result[$key] = str_replace('{' . $indexPlaceholder . '}', $index, $value); } else { + if ($value instanceof JsExpression) { + $value->expression = str_replace('{' . $indexPlaceholder . '}', $index, $value->expression); + } + $result[$key] = $value; } } From 4f4a0c33c75d79e4279f81989839859651cb0f8b Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 31 Mar 2019 19:17:50 +0300 Subject: [PATCH 168/247] bump version --- CHANGELOG.md | 5 +++++ README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7dd167..9b48fa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Yii2 multiple input change log 2.21.0 (in development) ======================= +2.20.6 +====== + +- don't cast JsExpression to string after replace widget placeholder + 2.20.0 ====== diff --git a/README.md b/README.md index 41b4568..78e44a4 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.20.5 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.20.6 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index f5b6f2c..071cd75 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.20.5", + "version": "2.20.6", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 19f83fb..a4a97d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.20.5", + "version": "2.20.6", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From f42476180cc438f2885359212102a8309ddf8243 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 31 Mar 2019 19:34:16 +0300 Subject: [PATCH 169/247] ability to prepend new row instead of append --- CHANGELOG.md | 2 ++ src/MultipleInput.php | 10 ++++++++-- src/TabularInput.php | 8 +++++++- src/assets/src/js/jquery.multipleInput.js | 11 +++++++++-- src/assets/src/js/jquery.multipleInput.min.js | 2 +- src/renderers/BaseRenderer.php | 8 +++++++- 6 files changed, 34 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b48fa2..5830be2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Yii2 multiple input change log 2.21.0 (in development) ======================= +- #279 ability to prepend new row instead of append + 2.20.6 ====== diff --git a/src/MultipleInput.php b/src/MultipleInput.php index cf79351..dcad5eb 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -210,6 +210,11 @@ class MultipleInput extends InputWidget */ public $showGeneralError = false; + /** + * @var bool add a new line to the beginning of the list, not to the end + */ + public $prepend = false; + /** * Initialization. * @@ -350,7 +355,8 @@ protected function createRenderer() 'extraButtons' => $this->extraButtons, 'layoutConfig' => $this->layoutConfig, 'iconMap' => $iconMap, - 'theme' => $this->theme + 'theme' => $this->theme, + 'prepend' => $this->prepend ]; if ($this->showGeneralError) { @@ -358,7 +364,7 @@ protected function createRenderer() 'showGeneralError' => true ]; } - + if ($this->removeButtonOptions !== null) { $config['removeButtonOptions'] = $this->removeButtonOptions; } diff --git a/src/TabularInput.php b/src/TabularInput.php index fc6f9a0..95e2f89 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -199,6 +199,11 @@ class TabularInput extends Widget */ public $iconSource = self::ICONS_SOURCE_GLYPHICONS; + /** + * @var bool add a new line to the beginning of the list, not to the end + */ + public $prepend = false; + /** * Initialization. * @@ -298,7 +303,8 @@ protected function createRenderer() 'extraButtons' => $this->extraButtons, 'layoutConfig' => $this->layoutConfig, 'iconMap' => $iconMap, - 'theme' => $this->theme + 'theme' => $this->theme, + 'prepend' => $this->prepend ]; if ($this->removeButtonOptions !== null) { diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 555935a..34b0edf 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -101,7 +101,9 @@ */ indexPlaceholder: 'multiple_index', - showGeneralError: false + showGeneralError: false, + + prepend: false }; var isActiveFormEnabled = false; @@ -264,7 +266,12 @@ return; } - $addedInput.hide().appendTo(inputList).fadeIn(300); + + if (settings.prepend) { + $addedInput.hide().prependTo(inputList).fadeIn(300); + } else { + $addedInput.hide().appendTo(inputList).fadeIn(300); + } if (values instanceof Object) { var tmp = []; diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 9c01bce..5ae5ec7 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},n={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1},i=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},n,r||{}),s=t("#"+u.id),f=s.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),s.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),d(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(n[t]=e)}),u.showGeneralError||f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,i){i=t.extend({},n,i),u.attributes[e]=i}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),i=!0,clearInterval(h),s.trigger(m)}else v++;(0===f.length||v>10)&&(s.data("multipleInput").currentIndex=p(s),i=!1,clearInterval(h),s.trigger(m))},100)},add:function(e){l(t(this),e)},remove:function(e){var n=null;n=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(n)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,n){n=n||null;var i=t(this).data("multipleInput"),r=i.settings;if(null===n){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=n,i.settings=r,t(this).data("multipleInput",i))}},l=function(n,r){var l=t(n).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!==u.max&&p(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),v=a.currentIndex,m=t.Event(e.beforeAddRow);if(l.trigger(m,[c,v]),m.result!==!1){if(c.hide().appendTo(f).fadeIn(300),r instanceof Object){var h=[];for(var I in r)r.hasOwnProperty(I)&&h.push(r[I]);r=h}var g;for(var w in u.jsTemplates)g=u.jsTemplates[w].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(g);var x=0;t(d).find("input, select, textarea").each(function(e,n){var l=t(n),a=n.tagName,u=s(l),p=t("#"+u);if(r){var d=r[x];if("INPUT"===a||"TEXTAREA"===a)p.val(d);else if("SELECT"===a)if(d&&d.indexOf("option")!==-1)p.append(d);else{var f=p.find('option[value="'+d+'"]');f.length&&p.val(d)}}i&&o(l),x++}),l.data("multipleInput").currentIndex++;var y=t.Event(e.afterAddRow);l.trigger(y,[c,v])}}},a=function(n){var r=n.closest(".multiple-input").first(),l=n.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings,s=p(r);if(s>o.min){var d=t.Event(e.beforeDeleteRow);if(r.trigger(d,[l,s]),d.result===!1)return;i&&l.find("input, select, textarea").each(function(e,n){u(t(n))}),l.fadeOut(300,function(){t(this).remove(),d=t.Event(e.afterDeleteRow),r.trigger(d,[l,s])})}},o=function(e){var n=s(e);if(null!==n){var i=t("#"+n),r=i.closest(".multiple-input").first(),l=i.closest("form");if(0!==r.length&&"undefined"==typeof l.yiiActiveForm("find",n)){var a=r.data("multipleInput"),o={},u=n.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:n,input:"#"+n,container:".field-"+n}))}}},u=function(e){var n=s(e);if(null!==n){var i=t("#"+n).closest("form");0!==i.length&&i.yiiActiveForm("remove",n)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var n={};return e.closest("tr").find("td").each(function(e,i){t(i).find("input, select, textarea").each(function(e,i){var r=t(i),l=s(r),a=t("#"+l);n[l]=a.val()})}),n};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},n={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},i=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},n,r||{}),p=t("#"+u.id),f=p.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),p.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),d(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(n[t]=e)}),u.showGeneralError||f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,i){i=t.extend({},n,i),u.attributes[e]=i}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),i=!0,clearInterval(h),p.trigger(m)}else v++;(0===f.length||v>10)&&(p.data("multipleInput").currentIndex=s(p),i=!1,clearInterval(h),p.trigger(m))},100)},add:function(e){l(t(this),e)},remove:function(e){var n=null;n=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(n)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,n){n=n||null;var i=t(this).data("multipleInput"),r=i.settings;if(null===n){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=n,i.settings=r,t(this).data("multipleInput",i))}},l=function(n,r){var l=t(n).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!==u.max&&s(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),v=a.currentIndex,m=t.Event(e.beforeAddRow);if(l.trigger(m,[c,v]),m.result!==!1){if(u.prepend?c.hide().prependTo(f).fadeIn(300):c.hide().appendTo(f).fadeIn(300),r instanceof Object){var h=[];for(var I in r)r.hasOwnProperty(I)&&h.push(r[I]);r=h}var g;for(var w in u.jsTemplates)g=u.jsTemplates[w].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(g);var x=0;t(d).find("input, select, textarea").each(function(e,n){var l=t(n),a=n.tagName,u=p(l),s=t("#"+u);if(r){var d=r[x];if("INPUT"===a||"TEXTAREA"===a)s.val(d);else if("SELECT"===a)if(d&&d.indexOf("option")!==-1)s.append(d);else{var f=s.find('option[value="'+d+'"]');f.length&&s.val(d)}}i&&o(l),x++}),l.data("multipleInput").currentIndex++;var y=t.Event(e.afterAddRow);l.trigger(y,[c,v])}}},a=function(n){var r=n.closest(".multiple-input").first(),l=n.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings,p=s(r);if(p>o.min){var d=t.Event(e.beforeDeleteRow);if(r.trigger(d,[l,p]),d.result===!1)return;i&&l.find("input, select, textarea").each(function(e,n){u(t(n))}),l.fadeOut(300,function(){t(this).remove(),d=t.Event(e.afterDeleteRow),r.trigger(d,[l,p])})}},o=function(e){var n=p(e);if(null!==n){var i=t("#"+n),r=i.closest(".multiple-input").first(),l=i.closest("form");if(0!==r.length&&"undefined"==typeof l.yiiActiveForm("find",n)){var a=r.data("multipleInput"),o={},u=n.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:n,input:"#"+n,container:".field-"+n}))}}},u=function(e){var n=p(e);if(null!==n){var i=t("#"+n).closest("form");0!==i.length&&i.yiiActiveForm("remove",n)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var n={};return e.closest("tr").find("td").each(function(e,i){t(i).find("input, select, textarea").each(function(e,i){var r=t(i),l=p(r),a=t("#"+l);n[l]=a.val()})}),n};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 7f5d734..c2c1c1d 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -188,6 +188,11 @@ abstract class BaseRenderer extends BaseObject implements RendererInterface */ public $jsPositions = [View::POS_HEAD, View::POS_BEGIN, View::POS_END, View::POS_READY, View::POS_LOAD]; + /** + * @var bool add a new line to the beginning of the list, not to the end + */ + public $prepend = false; + /** * @inheritdoc */ @@ -411,7 +416,8 @@ public function render() 'max' => $this->max, 'min' => $this->min, 'attributes' => $this->prepareJsAttributes(), - 'indexPlaceholder' => $this->getIndexPlaceholder() + 'indexPlaceholder' => $this->getIndexPlaceholder(), + 'prepend' => $this->prepend ], $this->jsExtraSettings)); $js = "jQuery('#{$this->id}').multipleInput($options);"; From 1d00a0a97971f2fbd9d59d0b5a7fafc795f7f443 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 31 Mar 2019 19:35:25 +0300 Subject: [PATCH 170/247] bump version --- CHANGELOG.md | 5 ++++- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5830be2..5c5f161 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ Yii2 multiple input change log ============================== -2.21.0 (in development) +2.22.0 (in development) +======================= + +2.21.0 ======================= - #279 ability to prepend new row instead of append diff --git a/README.md b/README.md index 78e44a4..e1c130f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.20.6 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.21.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 071cd75..d36b0f3 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.20.6", + "version": "2.21.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index a4a97d3..11b3430 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.20.6", + "version": "2.21.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 2fcdcd6d39a8c282ec29b84e7ba3c70a7e8927e7 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 13 Apr 2019 13:18:56 +0300 Subject: [PATCH 171/247] #286 avoid removal of all rows on the page when there are several widgets on the page and method `clear` was called --- src/assets/src/js/jquery.multipleInput.js | 26 +++++++++++++++---- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 34b0edf..43b81d1 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -1,4 +1,10 @@ (function ($) { + 'use strict'; + + String.prototype.replaceAll = function (search, replace) { + return this.split(search).join(replace); + }; + $.fn.multipleInput = function (method) { if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); @@ -68,41 +74,55 @@ * the ID of widget */ id: null, + /** * the ID of related input in case of using widget for an active field */ inputId: null, + /** * the template of row */ template: null, + /** * array that collect js templates of widgets which uses in the columns */ jsTemplates: [], + /** * array of scripts which need to execute before initialization */ jsInit: [], + /** * how many row are allowed to render */ max: 1, + /** * a minimum number of rows */ min: 1, + /** * active form options of attributes */ attributes: {}, + /** * default prefix of a widget's placeholder */ indexPlaceholder: 'multiple_index', + /** + * whether need to show general error message or no + */ showGeneralError: false, + /** + * if need to prepend new row, not append + */ prepend: false }; @@ -221,7 +241,7 @@ }, clear: function () { - $('.js-input-remove').each(function () { + $(this).find('.js-input-remove').each(function () { removeInput($(this)); }); }, @@ -466,8 +486,4 @@ }); return values; }; - - String.prototype.replaceAll = function (search, replace) { - return this.split(search).join(replace); - }; })(window.jQuery); diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 5ae5ec7..3c5bff9 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},n={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},i=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},n,r||{}),p=t("#"+u.id),f=p.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),p.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),d(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(n[t]=e)}),u.showGeneralError||f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,i){i=t.extend({},n,i),u.attributes[e]=i}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),i=!0,clearInterval(h),p.trigger(m)}else v++;(0===f.length||v>10)&&(p.data("multipleInput").currentIndex=s(p),i=!1,clearInterval(h),p.trigger(m))},100)},add:function(e){l(t(this),e)},remove:function(e){var n=null;n=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(n)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,n){n=n||null;var i=t(this).data("multipleInput"),r=i.settings;if(null===n){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=n,i.settings=r,t(this).data("multipleInput",i))}},l=function(n,r){var l=t(n).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!==u.max&&s(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),v=a.currentIndex,m=t.Event(e.beforeAddRow);if(l.trigger(m,[c,v]),m.result!==!1){if(u.prepend?c.hide().prependTo(f).fadeIn(300):c.hide().appendTo(f).fadeIn(300),r instanceof Object){var h=[];for(var I in r)r.hasOwnProperty(I)&&h.push(r[I]);r=h}var g;for(var w in u.jsTemplates)g=u.jsTemplates[w].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(g);var x=0;t(d).find("input, select, textarea").each(function(e,n){var l=t(n),a=n.tagName,u=p(l),s=t("#"+u);if(r){var d=r[x];if("INPUT"===a||"TEXTAREA"===a)s.val(d);else if("SELECT"===a)if(d&&d.indexOf("option")!==-1)s.append(d);else{var f=s.find('option[value="'+d+'"]');f.length&&s.val(d)}}i&&o(l),x++}),l.data("multipleInput").currentIndex++;var y=t.Event(e.afterAddRow);l.trigger(y,[c,v])}}},a=function(n){var r=n.closest(".multiple-input").first(),l=n.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings,p=s(r);if(p>o.min){var d=t.Event(e.beforeDeleteRow);if(r.trigger(d,[l,p]),d.result===!1)return;i&&l.find("input, select, textarea").each(function(e,n){u(t(n))}),l.fadeOut(300,function(){t(this).remove(),d=t.Event(e.afterDeleteRow),r.trigger(d,[l,p])})}},o=function(e){var n=p(e);if(null!==n){var i=t("#"+n),r=i.closest(".multiple-input").first(),l=i.closest("form");if(0!==r.length&&"undefined"==typeof l.yiiActiveForm("find",n)){var a=r.data("multipleInput"),o={},u=n.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:n,input:"#"+n,container:".field-"+n}))}}},u=function(e){var n=p(e);if(null!==n){var i=t("#"+n).closest("form");0!==i.length&&i.yiiActiveForm("remove",n)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var n={};return e.closest("tr").find("td").each(function(e,i){t(i).find("input, select, textarea").each(function(e,i){var r=t(i),l=p(r),a=t("#"+l);n[l]=a.val()})}),n};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){"use strict";String.prototype.replaceAll=function(t,e){return this.split(t).join(e)},t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},n={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},i=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},n,r||{}),p=t("#"+u.id),f=p.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),p.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),d(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(n[t]=e)}),u.showGeneralError||f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,i){i=t.extend({},n,i),u.attributes[e]=i}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),i=!0,clearInterval(h),p.trigger(m)}else v++;(0===f.length||v>10)&&(p.data("multipleInput").currentIndex=s(p),i=!1,clearInterval(h),p.trigger(m))},100)},add:function(e){l(t(this),e)},remove:function(e){var n=null;n=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(n)},clear:function(){t(this).find(".js-input-remove").each(function(){a(t(this))})},option:function(e,n){n=n||null;var i=t(this).data("multipleInput"),r=i.settings;if(null===n){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=n,i.settings=r,t(this).data("multipleInput",i))}},l=function(n,r){var l=t(n).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!==u.max&&s(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),v=a.currentIndex,m=t.Event(e.beforeAddRow);if(l.trigger(m,[c,v]),m.result!==!1){if(u.prepend?c.hide().prependTo(f).fadeIn(300):c.hide().appendTo(f).fadeIn(300),r instanceof Object){var h=[];for(var I in r)r.hasOwnProperty(I)&&h.push(r[I]);r=h}var g;for(var w in u.jsTemplates)g=u.jsTemplates[w].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(g);var x=0;t(d).find("input, select, textarea").each(function(e,n){var l=t(n),a=n.tagName,u=p(l),s=t("#"+u);if(r){var d=r[x];if("INPUT"===a||"TEXTAREA"===a)s.val(d);else if("SELECT"===a)if(d&&d.indexOf("option")!==-1)s.append(d);else{var f=s.find('option[value="'+d+'"]');f.length&&s.val(d)}}i&&o(l),x++}),l.data("multipleInput").currentIndex++;var y=t.Event(e.afterAddRow);l.trigger(y,[c,v])}}},a=function(n){var r=n.closest(".multiple-input").first(),l=n.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings,p=s(r);if(p>o.min){var d=t.Event(e.beforeDeleteRow);if(r.trigger(d,[l,p]),d.result===!1)return;i&&l.find("input, select, textarea").each(function(e,n){u(t(n))}),l.fadeOut(300,function(){t(this).remove(),d=t.Event(e.afterDeleteRow),r.trigger(d,[l,p])})}},o=function(e){var n=p(e);if(null!==n){var i=t("#"+n),r=i.closest(".multiple-input").first(),l=i.closest("form");if(0!==r.length&&"undefined"==typeof l.yiiActiveForm("find",n)){var a=r.data("multipleInput"),o={},u=n.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:n,input:"#"+n,container:".field-"+n}))}}},u=function(e){var n=p(e);if(null!==n){var i=t("#"+n).closest("form");0!==i.length&&i.yiiActiveForm("remove",n)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var n={};return e.closest("tr").find("td").each(function(e,i){t(i).find("input, select, textarea").each(function(e,i){var r=t(i),l=p(r),a=t("#"+l);n[l]=a.val()})}),n}}(window.jQuery); \ No newline at end of file From 92a62a07aee6e6a58f0594545ccf0cf3ca6b2979 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 13 Apr 2019 13:20:42 +0300 Subject: [PATCH 172/247] release 2.21.1 --- CHANGELOG.md | 7 +++++-- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c5f161..59d8e6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,12 @@ Yii2 multiple input change log 2.22.0 (in development) ======================= -2.21.0 -======================= +2.21.1 +====== +- #286 avoid removal of all rows on the page when there are several widgets on the page and method `clear` was called +2.21.0 +====== - #279 ability to prepend new row instead of append 2.20.6 diff --git a/README.md b/README.md index e1c130f..b95e97b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.21.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.21.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index d36b0f3..1138a57 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.21.0", + "version": "2.21.1", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 11b3430..4a86228 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.21.0", + "version": "2.21.1", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 45e643912fe4fa34a51bc59be4a6c23adf1433d5 Mon Sep 17 00:00:00 2001 From: Volodymyr Kovalenko Date: Mon, 22 Apr 2019 15:33:11 +0300 Subject: [PATCH 173/247] FIX wrapper options for bootstrap theme --- src/renderers/TableRenderer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 0e551b4..8678c39 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -309,7 +309,7 @@ public function renderCellContent($column, $index, $columnIndex = null) $wrapperOptions = ['class' => 'field-' . $id]; if ($this->isBootstrapTheme()) { - Html::addCssClass($options, 'form-group'); + Html::addCssClass($wrapperOptions, 'form-group'); } if ($hasError) { From 2639ebd0132f76154f67a0b24cfeda7b7f1d7075 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 27 Apr 2019 12:50:29 +0300 Subject: [PATCH 174/247] bump version --- CHANGELOG.md | 4 ++++ README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59d8e6a..fd4b8f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Yii2 multiple input change log 2.22.0 (in development) ======================= +2.21.2 +====== +- FIX wrapper options for bootstrap theme + 2.21.1 ====== - #286 avoid removal of all rows on the page when there are several widgets on the page and method `clear` was called diff --git a/README.md b/README.md index b95e97b..b034702 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.21.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.21.2 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 1138a57..e7cefad 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.21.1", + "version": "2.21.2", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 4a86228..07cfd57 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.21.1", + "version": "2.21.2", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From eb7439ef681fe0af35d14d5591fac5cefc70d183 Mon Sep 17 00:00:00 2001 From: eXeCUT Date: Wed, 15 May 2019 10:14:58 +0300 Subject: [PATCH 175/247] Added ability for redefine js and css via config --- src/assets/MultipleInputAsset.php | 18 +++++++--------- tests/unit/MultipleInputAssetTest.php | 30 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 11 deletions(-) create mode 100644 tests/unit/MultipleInputAssetTest.php diff --git a/src/assets/MultipleInputAsset.php b/src/assets/MultipleInputAsset.php index 1641a2f..7a51032 100644 --- a/src/assets/MultipleInputAsset.php +++ b/src/assets/MultipleInputAsset.php @@ -20,19 +20,15 @@ class MultipleInputAsset extends AssetBundle 'yii\web\JqueryAsset' ]; - public function init() + public function __construct($config = []) { - $this->sourcePath = __DIR__ . '/src/'; + $config = array_merge([ + 'sourcePath' => __DIR__ . '/src/', + 'js' => [YII_DEBUG ? 'js/jquery.multipleInput.js' : 'js/jquery.multipleInput.min.js'], + 'css' => [YII_DEBUG ? 'css/multiple-input.css' : 'css/multiple-input.min.css'], + ], $config); - $this->js = [ - YII_DEBUG ? 'js/jquery.multipleInput.js' : 'js/jquery.multipleInput.min.js' - ]; - - $this->css = [ - YII_DEBUG ? 'css/multiple-input.css' : 'css/multiple-input.min.css' - ]; - - parent::init(); + parent::__construct($config); } diff --git a/tests/unit/MultipleInputAssetTest.php b/tests/unit/MultipleInputAssetTest.php new file mode 100644 index 0000000..8a0f49a --- /dev/null +++ b/tests/unit/MultipleInputAssetTest.php @@ -0,0 +1,30 @@ +assertIsString($asset->sourcePath); + $this->assertCount(1, $asset->js); + $this->assertCount(1, $asset->css); + } + + public function testSetEmptyJsViaConfig() + { + $asset = new MultipleInputAsset([ + 'js' => [], + 'css' => [], + 'sourcePath' => 'test', + ]); + $this->assertEquals('test', $asset->sourcePath); + $this->assertCount(0, $asset->js); + $this->assertCount(0, $asset->css); + } +} \ No newline at end of file From a8fb3ee25ce7d2d37f064076979951b136a01fe6 Mon Sep 17 00:00:00 2001 From: Yuriy Mamaev Date: Wed, 15 May 2019 10:21:50 +0300 Subject: [PATCH 176/247] Added spaces --- src/assets/MultipleInputAsset.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/assets/MultipleInputAsset.php b/src/assets/MultipleInputAsset.php index 7a51032..df3f426 100644 --- a/src/assets/MultipleInputAsset.php +++ b/src/assets/MultipleInputAsset.php @@ -24,12 +24,16 @@ public function __construct($config = []) { $config = array_merge([ 'sourcePath' => __DIR__ . '/src/', - 'js' => [YII_DEBUG ? 'js/jquery.multipleInput.js' : 'js/jquery.multipleInput.min.js'], - 'css' => [YII_DEBUG ? 'css/multiple-input.css' : 'css/multiple-input.min.css'], + 'js' => [ + YII_DEBUG ? 'js/jquery.multipleInput.js' : 'js/jquery.multipleInput.min.js' + ], + 'css' => [ + YII_DEBUG ? 'css/multiple-input.css' : 'css/multiple-input.min.css' + ], ], $config); parent::__construct($config); } -} \ No newline at end of file +} From 98d9b6c73ef5aad68c6dd18bd42f0bf0ed08863c Mon Sep 17 00:00:00 2001 From: Yuriy Mamaev Date: Wed, 15 May 2019 10:22:29 +0300 Subject: [PATCH 177/247] Rename function --- tests/unit/MultipleInputAssetTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/MultipleInputAssetTest.php b/tests/unit/MultipleInputAssetTest.php index 8a0f49a..035a87d 100644 --- a/tests/unit/MultipleInputAssetTest.php +++ b/tests/unit/MultipleInputAssetTest.php @@ -9,7 +9,7 @@ */ class MultipleInputAssetTest extends TestCase { - public function testInit() { + public function testConstuctor() { $asset = new MultipleInputAsset(); $this->assertIsString($asset->sourcePath); $this->assertCount(1, $asset->js); @@ -27,4 +27,4 @@ public function testSetEmptyJsViaConfig() $this->assertCount(0, $asset->js); $this->assertCount(0, $asset->css); } -} \ No newline at end of file +} From d9f34550c9826be00a77027411aea0f05620c195 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 1 Jun 2019 12:50:43 +0300 Subject: [PATCH 178/247] don't extend string prototype to add replaceAll function --- src/assets/src/js/jquery.multipleInput.js | 27 +++++++++++-------- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 43b81d1..08047eb 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -1,10 +1,6 @@ (function ($) { 'use strict'; - String.prototype.replaceAll = function (search, replace) { - return this.split(search).join(replace); - }; - $.fn.multipleInput = function (method) { if (methods[method]) { return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); @@ -275,7 +271,7 @@ return; } - template = template.replaceAll('{' + settings.indexPlaceholder + '}', data.currentIndex); + template = replaceAll('{' + settings.indexPlaceholder + '}', data.currentIndex, template); var $addedInput = $(template); var currentIndex = data.currentIndex; @@ -307,9 +303,9 @@ var jsTemplate; for (var i in settings.jsTemplates) { - jsTemplate = settings.jsTemplates[i] - .replaceAll('{' + settings.indexPlaceholder + '}', data.currentIndex) - .replaceAll('%7B' + settings.indexPlaceholder + '%7D', data.currentIndex); + jsTemplate = settings.jsTemplates[i]; + jsTemplate = replaceAll('{' + settings.indexPlaceholder + '}', data.currentIndex, jsTemplate); + jsTemplate = replaceAll('%7B' + settings.indexPlaceholder + '%7D', data.currentIndex, jsTemplate); window.eval(jsTemplate); } @@ -420,9 +416,9 @@ if (data.settings.attributes.hasOwnProperty(bareId)) { attributeOptions = data.settings.attributes[bareId]; } else { - // fallback in case of using flatten widget - just remove all digital indexes and check whether attribute - // exists or not. - bareId = bareId.replaceAll(/-\d-/, '-').replaceAll(/-\d/, ''); + // fallback in case of using flatten widget - just remove all digital indexes + // and check whether attribute exists or not. + bareId = replaceAll(/-\d-/, '-').replaceAll(/-\d/, '', bareId); if (data.settings.attributes.hasOwnProperty(bareId)) { attributeOptions = data.settings.attributes[bareId]; } @@ -486,4 +482,13 @@ }); return values; }; + + var replaceAll = function (search, replace, subject) { + if (!subject instanceof String) { + console.warn('Call replaceAll for non-string value: ' + subject); + return subject; + } + + return subject.split(search).join(replace); + }; })(window.jQuery); diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 3c5bff9..71753ea 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){"use strict";String.prototype.replaceAll=function(t,e){return this.split(t).join(e)},t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},n={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},i=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},n,r||{}),p=t("#"+u.id),f=p.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),p.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),d(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(n[t]=e)}),u.showGeneralError||f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,i){i=t.extend({},n,i),u.attributes[e]=i}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),i=!0,clearInterval(h),p.trigger(m)}else v++;(0===f.length||v>10)&&(p.data("multipleInput").currentIndex=s(p),i=!1,clearInterval(h),p.trigger(m))},100)},add:function(e){l(t(this),e)},remove:function(e){var n=null;n=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(n)},clear:function(){t(this).find(".js-input-remove").each(function(){a(t(this))})},option:function(e,n){n=n||null;var i=t(this).data("multipleInput"),r=i.settings;if(null===n){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=n,i.settings=r,t(this).data("multipleInput",i))}},l=function(n,r){var l=t(n).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!==u.max&&s(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),v=a.currentIndex,m=t.Event(e.beforeAddRow);if(l.trigger(m,[c,v]),m.result!==!1){if(u.prepend?c.hide().prependTo(f).fadeIn(300):c.hide().appendTo(f).fadeIn(300),r instanceof Object){var h=[];for(var I in r)r.hasOwnProperty(I)&&h.push(r[I]);r=h}var g;for(var w in u.jsTemplates)g=u.jsTemplates[w].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(g);var x=0;t(d).find("input, select, textarea").each(function(e,n){var l=t(n),a=n.tagName,u=p(l),s=t("#"+u);if(r){var d=r[x];if("INPUT"===a||"TEXTAREA"===a)s.val(d);else if("SELECT"===a)if(d&&d.indexOf("option")!==-1)s.append(d);else{var f=s.find('option[value="'+d+'"]');f.length&&s.val(d)}}i&&o(l),x++}),l.data("multipleInput").currentIndex++;var y=t.Event(e.afterAddRow);l.trigger(y,[c,v])}}},a=function(n){var r=n.closest(".multiple-input").first(),l=n.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings,p=s(r);if(p>o.min){var d=t.Event(e.beforeDeleteRow);if(r.trigger(d,[l,p]),d.result===!1)return;i&&l.find("input, select, textarea").each(function(e,n){u(t(n))}),l.fadeOut(300,function(){t(this).remove(),d=t.Event(e.afterDeleteRow),r.trigger(d,[l,p])})}},o=function(e){var n=p(e);if(null!==n){var i=t("#"+n),r=i.closest(".multiple-input").first(),l=i.closest("form");if(0!==r.length&&"undefined"==typeof l.yiiActiveForm("find",n)){var a=r.data("multipleInput"),o={},u=n.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:n,input:"#"+n,container:".field-"+n}))}}},u=function(e){var n=p(e);if(null!==n){var i=t("#"+n).closest("form");0!==i.length&&i.yiiActiveForm("remove",n)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var n={};return e.closest("tr").find("td").each(function(e,i){t(i).find("input, select, textarea").each(function(e,i){var r=t(i),l=p(r),a=t("#"+l);n[l]=a.val()})}),n}}(window.jQuery); \ No newline at end of file +!function(t){"use strict";t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},n={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},i=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},n,r||{}),s=t("#"+u.id),f=s.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),l(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),a(t(this),d(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(n[t]=e)}),u.showGeneralError||f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,i){i=t.extend({},n,i),u.attributes[e]=i}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),i=!0,clearInterval(h),s.trigger(m)}else v++;(0===f.length||v>10)&&(s.data("multipleInput").currentIndex=p(s),i=!1,clearInterval(h),s.trigger(m))},100)},add:function(e){a(t(this),e)},remove:function(e){var n=null;n=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),l(n)},clear:function(){t(this).find(".js-input-remove").each(function(){l(t(this))})},option:function(e,n){n=n||null;var i=t(this).data("multipleInput"),r=i.settings;if(null===n){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=n,i.settings=r,t(this).data("multipleInput",i))}},a=function(n,r){var a=t(n).closest(".multiple-input").first(),l=a.data("multipleInput"),u=l.settings,d=u.template,c=a.children(".multiple-input-list").first();if(!(null!==u.max&&p(a)>=u.max)){d=f("{"+u.indexPlaceholder+"}",l.currentIndex,d);var v=t(d),m=l.currentIndex,h=t.Event(e.beforeAddRow);if(a.trigger(h,[v,m]),h.result!==!1){if(u.prepend?v.hide().prependTo(c).fadeIn(300):v.hide().appendTo(c).fadeIn(300),r instanceof Object){var I=[];for(var g in r)r.hasOwnProperty(g)&&I.push(r[g]);r=I}var w;for(var x in u.jsTemplates)w=u.jsTemplates[x],w=f("{"+u.indexPlaceholder+"}",l.currentIndex,w),w=f("%7B"+u.indexPlaceholder+"%7D",l.currentIndex,w),window.eval(w);var y=0;t(d).find("input, select, textarea").each(function(e,n){var a=t(n),l=n.tagName,u=s(a),p=t("#"+u);if(r){var d=r[y];if("INPUT"===l||"TEXTAREA"===l)p.val(d);else if("SELECT"===l)if(d&&d.indexOf("option")!==-1)p.append(d);else{var f=p.find('option[value="'+d+'"]');f.length&&p.val(d)}}i&&o(a),y++}),a.data("multipleInput").currentIndex++;var j=t.Event(e.afterAddRow);a.trigger(j,[v,m])}}},l=function(n){var r=n.closest(".multiple-input").first(),a=n.closest(".multiple-input-list__item"),l=r.data("multipleInput"),o=l.settings,s=p(r);if(s>o.min){var d=t.Event(e.beforeDeleteRow);if(r.trigger(d,[a,s]),d.result===!1)return;i&&a.find("input, select, textarea").each(function(e,n){u(t(n))}),a.fadeOut(300,function(){t(this).remove(),d=t.Event(e.afterDeleteRow),r.trigger(d,[a,s])})}},o=function(e){var n=s(e);if(null!==n){var i=t("#"+n),r=i.closest(".multiple-input").first(),a=i.closest("form");if(0!==r.length&&"undefined"==typeof a.yiiActiveForm("find",n)){var l=r.data("multipleInput"),o={},u=n.replace(/-\d+-([^\d]+)$/,"-$1");l.settings.attributes.hasOwnProperty(u)?o=l.settings.attributes[u]:(u=f(/-\d-/,"-").replaceAll(/-\d/,"",u),l.settings.attributes.hasOwnProperty(u)&&(o=l.settings.attributes[u])),a.yiiActiveForm("add",t.extend({},o,{id:n,input:"#"+n,container:".field-"+n}))}}},u=function(e){var n=s(e);if(null!==n){var i=t("#"+n).closest("form");0!==i.length&&i.yiiActiveForm("remove",n)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var n={};return e.closest("tr").find("td").each(function(e,i){t(i).find("input, select, textarea").each(function(e,i){var r=t(i),a=s(r),l=t("#"+a);n[a]=l.val()})}),n},f=function(t,e,n){return!n instanceof String?(console.warn("Call replaceAll for non-string value: "+n),n):n.split(t).join(e)}}(window.jQuery); \ No newline at end of file From f36260d6cf1d2b2b6c4272e59e896195af810a79 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 9 Jun 2019 12:18:18 +0300 Subject: [PATCH 179/247] retrieve relation data before processing --- src/components/BaseColumn.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 6934d66..82993c6 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -252,7 +252,7 @@ protected function prepareValue($contextParams = []) if ($data instanceof ActiveRecordInterface ) { $relation = $data->getRelation($this->name, false); if ($relation !== null) { - $value = $relation; + $value = $relation->findFor($this->name, $data); } else { $value = $data->getAttribute($this->name); } @@ -268,6 +268,7 @@ protected function prepareValue($contextParams = []) $value = $this->defaultValue; } } + return $value; } From 2e04be2c8ddda80462a67841e177a6e384a02e55 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 9 Jun 2019 12:28:15 +0300 Subject: [PATCH 180/247] bump version --- CHANGELOG.md | 4 ++++ README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd4b8f6..e5ad5f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Yii2 multiple input change log 2.22.0 (in development) ======================= +2.21.3 +====== +- fix retrieving AR relation data + 2.21.2 ====== - FIX wrapper options for bootstrap theme diff --git a/README.md b/README.md index b034702..daefcdb 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.21.2 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.21.3 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index e7cefad..9fe2a5d 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.21.2", + "version": "2.21.3", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 07cfd57..00bbeaf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.21.2", + "version": "2.21.3", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 6411bbffe7c0972fc76a9a651bc0b3759194e4e8 Mon Sep 17 00:00:00 2001 From: NidgetGod Date: Fri, 14 Jun 2019 14:41:57 +0800 Subject: [PATCH 181/247] Fix replaceAll unfinished modify --- src/assets/src/js/jquery.multipleInput.js | 5 +++-- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 08047eb..3ca5013 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -418,7 +418,8 @@ } else { // fallback in case of using flatten widget - just remove all digital indexes // and check whether attribute exists or not. - bareId = replaceAll(/-\d-/, '-').replaceAll(/-\d/, '', bareId); + bareId = replaceAll(/-\d-/, '-', bareId); + bareId = replaceAll(/-\d/, '', bareId); if (data.settings.attributes.hasOwnProperty(bareId)) { attributeOptions = data.settings.attributes[bareId]; } @@ -484,7 +485,7 @@ }; var replaceAll = function (search, replace, subject) { - if (!subject instanceof String) { + if (!(subject instanceof String)) { console.warn('Call replaceAll for non-string value: ' + subject); return subject; } diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 71753ea..2c114e2 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){"use strict";t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},n={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},i=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},n,r||{}),s=t("#"+u.id),f=s.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),l(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),a(t(this),d(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(n[t]=e)}),u.showGeneralError||f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,i){i=t.extend({},n,i),u.attributes[e]=i}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),i=!0,clearInterval(h),s.trigger(m)}else v++;(0===f.length||v>10)&&(s.data("multipleInput").currentIndex=p(s),i=!1,clearInterval(h),s.trigger(m))},100)},add:function(e){a(t(this),e)},remove:function(e){var n=null;n=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),l(n)},clear:function(){t(this).find(".js-input-remove").each(function(){l(t(this))})},option:function(e,n){n=n||null;var i=t(this).data("multipleInput"),r=i.settings;if(null===n){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=n,i.settings=r,t(this).data("multipleInput",i))}},a=function(n,r){var a=t(n).closest(".multiple-input").first(),l=a.data("multipleInput"),u=l.settings,d=u.template,c=a.children(".multiple-input-list").first();if(!(null!==u.max&&p(a)>=u.max)){d=f("{"+u.indexPlaceholder+"}",l.currentIndex,d);var v=t(d),m=l.currentIndex,h=t.Event(e.beforeAddRow);if(a.trigger(h,[v,m]),h.result!==!1){if(u.prepend?v.hide().prependTo(c).fadeIn(300):v.hide().appendTo(c).fadeIn(300),r instanceof Object){var I=[];for(var g in r)r.hasOwnProperty(g)&&I.push(r[g]);r=I}var w;for(var x in u.jsTemplates)w=u.jsTemplates[x],w=f("{"+u.indexPlaceholder+"}",l.currentIndex,w),w=f("%7B"+u.indexPlaceholder+"%7D",l.currentIndex,w),window.eval(w);var y=0;t(d).find("input, select, textarea").each(function(e,n){var a=t(n),l=n.tagName,u=s(a),p=t("#"+u);if(r){var d=r[y];if("INPUT"===l||"TEXTAREA"===l)p.val(d);else if("SELECT"===l)if(d&&d.indexOf("option")!==-1)p.append(d);else{var f=p.find('option[value="'+d+'"]');f.length&&p.val(d)}}i&&o(a),y++}),a.data("multipleInput").currentIndex++;var j=t.Event(e.afterAddRow);a.trigger(j,[v,m])}}},l=function(n){var r=n.closest(".multiple-input").first(),a=n.closest(".multiple-input-list__item"),l=r.data("multipleInput"),o=l.settings,s=p(r);if(s>o.min){var d=t.Event(e.beforeDeleteRow);if(r.trigger(d,[a,s]),d.result===!1)return;i&&a.find("input, select, textarea").each(function(e,n){u(t(n))}),a.fadeOut(300,function(){t(this).remove(),d=t.Event(e.afterDeleteRow),r.trigger(d,[a,s])})}},o=function(e){var n=s(e);if(null!==n){var i=t("#"+n),r=i.closest(".multiple-input").first(),a=i.closest("form");if(0!==r.length&&"undefined"==typeof a.yiiActiveForm("find",n)){var l=r.data("multipleInput"),o={},u=n.replace(/-\d+-([^\d]+)$/,"-$1");l.settings.attributes.hasOwnProperty(u)?o=l.settings.attributes[u]:(u=f(/-\d-/,"-").replaceAll(/-\d/,"",u),l.settings.attributes.hasOwnProperty(u)&&(o=l.settings.attributes[u])),a.yiiActiveForm("add",t.extend({},o,{id:n,input:"#"+n,container:".field-"+n}))}}},u=function(e){var n=s(e);if(null!==n){var i=t("#"+n).closest("form");0!==i.length&&i.yiiActiveForm("remove",n)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var n={};return e.closest("tr").find("td").each(function(e,i){t(i).find("input, select, textarea").each(function(e,i){var r=t(i),a=s(r),l=t("#"+a);n[a]=l.val()})}),n},f=function(t,e,n){return!n instanceof String?(console.warn("Call replaceAll for non-string value: "+n),n):n.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){"use strict";t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},n={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},i=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},n,r||{}),s=t("#"+u.id),f=s.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),l(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),a(t(this),d(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(n[t]=e)}),u.showGeneralError||f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,i){i=t.extend({},n,i),u.attributes[e]=i}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),i=!0,clearInterval(h),s.trigger(m)}else v++;(0===f.length||v>10)&&(s.data("multipleInput").currentIndex=p(s),i=!1,clearInterval(h),s.trigger(m))},100)},add:function(e){a(t(this),e)},remove:function(e){var n=null;n=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),l(n)},clear:function(){t(this).find(".js-input-remove").each(function(){l(t(this))})},option:function(e,n){n=n||null;var i=t(this).data("multipleInput"),r=i.settings;if(null===n){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=n,i.settings=r,t(this).data("multipleInput",i))}},a=function(n,r){var a=t(n).closest(".multiple-input").first(),l=a.data("multipleInput"),u=l.settings,d=u.template,c=a.children(".multiple-input-list").first();if(!(null!==u.max&&p(a)>=u.max)){d=f("{"+u.indexPlaceholder+"}",l.currentIndex,d);var v=t(d),m=l.currentIndex,h=t.Event(e.beforeAddRow);if(a.trigger(h,[v,m]),h.result!==!1){if(u.prepend?v.hide().prependTo(c).fadeIn(300):v.hide().appendTo(c).fadeIn(300),r instanceof Object){var I=[];for(var g in r)r.hasOwnProperty(g)&&I.push(r[g]);r=I}var w;for(var x in u.jsTemplates)w=u.jsTemplates[x],w=f("{"+u.indexPlaceholder+"}",l.currentIndex,w),w=f("%7B"+u.indexPlaceholder+"%7D",l.currentIndex,w),window.eval(w);var y=0;t(d).find("input, select, textarea").each(function(e,n){var a=t(n),l=n.tagName,u=s(a),p=t("#"+u);if(r){var d=r[y];if("INPUT"===l||"TEXTAREA"===l)p.val(d);else if("SELECT"===l)if(d&&d.indexOf("option")!==-1)p.append(d);else{var f=p.find('option[value="'+d+'"]');f.length&&p.val(d)}}i&&o(a),y++}),a.data("multipleInput").currentIndex++;var j=t.Event(e.afterAddRow);a.trigger(j,[v,m])}}},l=function(n){var r=n.closest(".multiple-input").first(),a=n.closest(".multiple-input-list__item"),l=r.data("multipleInput"),o=l.settings,s=p(r);if(s>o.min){var d=t.Event(e.beforeDeleteRow);if(r.trigger(d,[a,s]),d.result===!1)return;i&&a.find("input, select, textarea").each(function(e,n){u(t(n))}),a.fadeOut(300,function(){t(this).remove(),d=t.Event(e.afterDeleteRow),r.trigger(d,[a,s])})}},o=function(e){var n=s(e);if(null!==n){var i=t("#"+n),r=i.closest(".multiple-input").first(),a=i.closest("form");if(0!==r.length&&"undefined"==typeof a.yiiActiveForm("find",n)){var l=r.data("multipleInput"),o={},u=n.replace(/-\d+-([^\d]+)$/,"-$1");l.settings.attributes.hasOwnProperty(u)?o=l.settings.attributes[u]:(u=f(/-\d-/,"-",u),u=f(/-\d/,"",u),l.settings.attributes.hasOwnProperty(u)&&(o=l.settings.attributes[u])),a.yiiActiveForm("add",t.extend({},o,{id:n,input:"#"+n,container:".field-"+n}))}}},u=function(e){var n=s(e);if(null!==n){var i=t("#"+n).closest("form");0!==i.length&&i.yiiActiveForm("remove",n)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var n={};return e.closest("tr").find("td").each(function(e,i){t(i).find("input, select, textarea").each(function(e,i){var r=t(i),a=s(r),l=t("#"+a);n[a]=l.val()})}),n},f=function(t,e,n){return!(n instanceof String)?(console.warn("Call replaceAll for non-string value: "+n),n):n.split(t).join(e)}}(window.jQuery); \ No newline at end of file From 9018e3bef2c4ed1742b756b32c74dee627431b64 Mon Sep 17 00:00:00 2001 From: NidgetGod Date: Fri, 14 Jun 2019 17:58:02 +0800 Subject: [PATCH 182/247] More completely type detect to replaceAll --- src/assets/src/js/jquery.multipleInput.js | 2 +- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 3ca5013..076a5a2 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -485,7 +485,7 @@ }; var replaceAll = function (search, replace, subject) { - if (!(subject instanceof String)) { + if (!(subject instanceof String) && typeof subject !== 'string')) { console.warn('Call replaceAll for non-string value: ' + subject); return subject; } diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 2c114e2..0636728 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){"use strict";t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},n={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},i=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},n,r||{}),s=t("#"+u.id),f=s.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),l(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),a(t(this),d(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(n[t]=e)}),u.showGeneralError||f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,i){i=t.extend({},n,i),u.attributes[e]=i}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),i=!0,clearInterval(h),s.trigger(m)}else v++;(0===f.length||v>10)&&(s.data("multipleInput").currentIndex=p(s),i=!1,clearInterval(h),s.trigger(m))},100)},add:function(e){a(t(this),e)},remove:function(e){var n=null;n=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),l(n)},clear:function(){t(this).find(".js-input-remove").each(function(){l(t(this))})},option:function(e,n){n=n||null;var i=t(this).data("multipleInput"),r=i.settings;if(null===n){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=n,i.settings=r,t(this).data("multipleInput",i))}},a=function(n,r){var a=t(n).closest(".multiple-input").first(),l=a.data("multipleInput"),u=l.settings,d=u.template,c=a.children(".multiple-input-list").first();if(!(null!==u.max&&p(a)>=u.max)){d=f("{"+u.indexPlaceholder+"}",l.currentIndex,d);var v=t(d),m=l.currentIndex,h=t.Event(e.beforeAddRow);if(a.trigger(h,[v,m]),h.result!==!1){if(u.prepend?v.hide().prependTo(c).fadeIn(300):v.hide().appendTo(c).fadeIn(300),r instanceof Object){var I=[];for(var g in r)r.hasOwnProperty(g)&&I.push(r[g]);r=I}var w;for(var x in u.jsTemplates)w=u.jsTemplates[x],w=f("{"+u.indexPlaceholder+"}",l.currentIndex,w),w=f("%7B"+u.indexPlaceholder+"%7D",l.currentIndex,w),window.eval(w);var y=0;t(d).find("input, select, textarea").each(function(e,n){var a=t(n),l=n.tagName,u=s(a),p=t("#"+u);if(r){var d=r[y];if("INPUT"===l||"TEXTAREA"===l)p.val(d);else if("SELECT"===l)if(d&&d.indexOf("option")!==-1)p.append(d);else{var f=p.find('option[value="'+d+'"]');f.length&&p.val(d)}}i&&o(a),y++}),a.data("multipleInput").currentIndex++;var j=t.Event(e.afterAddRow);a.trigger(j,[v,m])}}},l=function(n){var r=n.closest(".multiple-input").first(),a=n.closest(".multiple-input-list__item"),l=r.data("multipleInput"),o=l.settings,s=p(r);if(s>o.min){var d=t.Event(e.beforeDeleteRow);if(r.trigger(d,[a,s]),d.result===!1)return;i&&a.find("input, select, textarea").each(function(e,n){u(t(n))}),a.fadeOut(300,function(){t(this).remove(),d=t.Event(e.afterDeleteRow),r.trigger(d,[a,s])})}},o=function(e){var n=s(e);if(null!==n){var i=t("#"+n),r=i.closest(".multiple-input").first(),a=i.closest("form");if(0!==r.length&&"undefined"==typeof a.yiiActiveForm("find",n)){var l=r.data("multipleInput"),o={},u=n.replace(/-\d+-([^\d]+)$/,"-$1");l.settings.attributes.hasOwnProperty(u)?o=l.settings.attributes[u]:(u=f(/-\d-/,"-",u),u=f(/-\d/,"",u),l.settings.attributes.hasOwnProperty(u)&&(o=l.settings.attributes[u])),a.yiiActiveForm("add",t.extend({},o,{id:n,input:"#"+n,container:".field-"+n}))}}},u=function(e){var n=s(e);if(null!==n){var i=t("#"+n).closest("form");0!==i.length&&i.yiiActiveForm("remove",n)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var n={};return e.closest("tr").find("td").each(function(e,i){t(i).find("input, select, textarea").each(function(e,i){var r=t(i),a=s(r),l=t("#"+a);n[a]=l.val()})}),n},f=function(t,e,n){return!(n instanceof String)?(console.warn("Call replaceAll for non-string value: "+n),n):n.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){"use strict";t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},n={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},i=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},n,r||{}),s=t("#"+u.id),f=s.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),l(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),a(t(this),d(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(n[t]=e)}),u.showGeneralError||f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,i){i=t.extend({},n,i),u.attributes[e]=i}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),i=!0,clearInterval(h),s.trigger(m)}else v++;(0===f.length||v>10)&&(s.data("multipleInput").currentIndex=p(s),i=!1,clearInterval(h),s.trigger(m))},100)},add:function(e){a(t(this),e)},remove:function(e){var n=null;n=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),l(n)},clear:function(){t(this).find(".js-input-remove").each(function(){l(t(this))})},option:function(e,n){n=n||null;var i=t(this).data("multipleInput"),r=i.settings;if(null===n){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=n,i.settings=r,t(this).data("multipleInput",i))}},a=function(n,r){var a=t(n).closest(".multiple-input").first(),l=a.data("multipleInput"),u=l.settings,d=u.template,c=a.children(".multiple-input-list").first();if(!(null!==u.max&&p(a)>=u.max)){d=f("{"+u.indexPlaceholder+"}",l.currentIndex,d);var v=t(d),m=l.currentIndex,h=t.Event(e.beforeAddRow);if(a.trigger(h,[v,m]),h.result!==!1){if(u.prepend?v.hide().prependTo(c).fadeIn(300):v.hide().appendTo(c).fadeIn(300),r instanceof Object){var I=[];for(var g in r)r.hasOwnProperty(g)&&I.push(r[g]);r=I}var w;for(var x in u.jsTemplates)w=u.jsTemplates[x],w=f("{"+u.indexPlaceholder+"}",l.currentIndex,w),w=f("%7B"+u.indexPlaceholder+"%7D",l.currentIndex,w),window.eval(w);var y=0;t(d).find("input, select, textarea").each(function(e,n){var a=t(n),l=n.tagName,u=s(a),p=t("#"+u);if(r){var d=r[y];if("INPUT"===l||"TEXTAREA"===l)p.val(d);else if("SELECT"===l)if(d&&d.indexOf("option")!==-1)p.append(d);else{var f=p.find('option[value="'+d+'"]');f.length&&p.val(d)}}i&&o(a),y++}),a.data("multipleInput").currentIndex++;var j=t.Event(e.afterAddRow);a.trigger(j,[v,m])}}},l=function(n){var r=n.closest(".multiple-input").first(),a=n.closest(".multiple-input-list__item"),l=r.data("multipleInput"),o=l.settings,s=p(r);if(s>o.min){var d=t.Event(e.beforeDeleteRow);if(r.trigger(d,[a,s]),d.result===!1)return;i&&a.find("input, select, textarea").each(function(e,n){u(t(n))}),a.fadeOut(300,function(){t(this).remove(),d=t.Event(e.afterDeleteRow),r.trigger(d,[a,s])})}},o=function(e){var n=s(e);if(null!==n){var i=t("#"+n),r=i.closest(".multiple-input").first(),a=i.closest("form");if(0!==r.length&&"undefined"==typeof a.yiiActiveForm("find",n)){var l=r.data("multipleInput"),o={},u=n.replace(/-\d+-([^\d]+)$/,"-$1");l.settings.attributes.hasOwnProperty(u)?o=l.settings.attributes[u]:(u=f(/-\d-/,"-",u),u=f(/-\d/,"",u),l.settings.attributes.hasOwnProperty(u)&&(o=l.settings.attributes[u])),a.yiiActiveForm("add",t.extend({},o,{id:n,input:"#"+n,container:".field-"+n}))}}},u=function(e){var n=s(e);if(null!==n){var i=t("#"+n).closest("form");0!==i.length&&i.yiiActiveForm("remove",n)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var n={};return e.closest("tr").find("td").each(function(e,i){t(i).find("input, select, textarea").each(function(e,i){var r=t(i),a=s(r),l=t("#"+a);n[a]=l.val()})}),n},f=function(t,e,n){return!(n instanceof String)&&"string"!==typeof n?(console.warn("Call replaceAll for non-string value: "+n),n):n.split(t).join(e)}}(window.jQuery); \ No newline at end of file From f0b1569ccfc25f05b389ec797ad0c1c179468321 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 15 Jun 2019 14:46:23 +0300 Subject: [PATCH 183/247] bump version --- CHANGELOG.md | 5 +++++ README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5ad5f1..1e9aad1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Yii2 multiple input change log 2.22.0 (in development) ======================= +2.21.4 +====== +- Fix replaceAll unfinished modify +- More completely type detect to replaceAll + 2.21.3 ====== - fix retrieving AR relation data diff --git a/README.md b/README.md index daefcdb..2a27482 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.21.3 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.21.4 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 9fe2a5d..c115ff9 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.21.3", + "version": "2.21.4", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 00bbeaf..2a6086e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.21.3", + "version": "2.21.4", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From ac8ae4a7b774e84e7b1669b09022978c449e0694 Mon Sep 17 00:00:00 2001 From: nidgetgod Date: Mon, 17 Jun 2019 13:30:57 +0800 Subject: [PATCH 184/247] Remove unexpected brackets --- src/assets/src/js/jquery.multipleInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 076a5a2..56693c7 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -485,7 +485,7 @@ }; var replaceAll = function (search, replace, subject) { - if (!(subject instanceof String) && typeof subject !== 'string')) { + if (!(subject instanceof String) && typeof subject !== 'string') { console.warn('Call replaceAll for non-string value: ' + subject); return subject; } From cad3cc288b8736ed6e9b161222628b1612ebbf69 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Mon, 17 Jun 2019 12:43:34 +0300 Subject: [PATCH 185/247] bump version --- README.md | 2 +- composer.json | 2 +- package.json | 2 +- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2a27482..ae1235b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.21.4 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.21.5 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index c115ff9..52c4cd5 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.21.4", + "version": "2.21.5", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 2a6086e..63c75d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.21.4", + "version": "2.21.5", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 0636728..4b573ba 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){"use strict";t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},n={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},i=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},n,r||{}),s=t("#"+u.id),f=s.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),l(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),a(t(this),d(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(n[t]=e)}),u.showGeneralError||f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,i){i=t.extend({},n,i),u.attributes[e]=i}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),i=!0,clearInterval(h),s.trigger(m)}else v++;(0===f.length||v>10)&&(s.data("multipleInput").currentIndex=p(s),i=!1,clearInterval(h),s.trigger(m))},100)},add:function(e){a(t(this),e)},remove:function(e){var n=null;n=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),l(n)},clear:function(){t(this).find(".js-input-remove").each(function(){l(t(this))})},option:function(e,n){n=n||null;var i=t(this).data("multipleInput"),r=i.settings;if(null===n){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=n,i.settings=r,t(this).data("multipleInput",i))}},a=function(n,r){var a=t(n).closest(".multiple-input").first(),l=a.data("multipleInput"),u=l.settings,d=u.template,c=a.children(".multiple-input-list").first();if(!(null!==u.max&&p(a)>=u.max)){d=f("{"+u.indexPlaceholder+"}",l.currentIndex,d);var v=t(d),m=l.currentIndex,h=t.Event(e.beforeAddRow);if(a.trigger(h,[v,m]),h.result!==!1){if(u.prepend?v.hide().prependTo(c).fadeIn(300):v.hide().appendTo(c).fadeIn(300),r instanceof Object){var I=[];for(var g in r)r.hasOwnProperty(g)&&I.push(r[g]);r=I}var w;for(var x in u.jsTemplates)w=u.jsTemplates[x],w=f("{"+u.indexPlaceholder+"}",l.currentIndex,w),w=f("%7B"+u.indexPlaceholder+"%7D",l.currentIndex,w),window.eval(w);var y=0;t(d).find("input, select, textarea").each(function(e,n){var a=t(n),l=n.tagName,u=s(a),p=t("#"+u);if(r){var d=r[y];if("INPUT"===l||"TEXTAREA"===l)p.val(d);else if("SELECT"===l)if(d&&d.indexOf("option")!==-1)p.append(d);else{var f=p.find('option[value="'+d+'"]');f.length&&p.val(d)}}i&&o(a),y++}),a.data("multipleInput").currentIndex++;var j=t.Event(e.afterAddRow);a.trigger(j,[v,m])}}},l=function(n){var r=n.closest(".multiple-input").first(),a=n.closest(".multiple-input-list__item"),l=r.data("multipleInput"),o=l.settings,s=p(r);if(s>o.min){var d=t.Event(e.beforeDeleteRow);if(r.trigger(d,[a,s]),d.result===!1)return;i&&a.find("input, select, textarea").each(function(e,n){u(t(n))}),a.fadeOut(300,function(){t(this).remove(),d=t.Event(e.afterDeleteRow),r.trigger(d,[a,s])})}},o=function(e){var n=s(e);if(null!==n){var i=t("#"+n),r=i.closest(".multiple-input").first(),a=i.closest("form");if(0!==r.length&&"undefined"==typeof a.yiiActiveForm("find",n)){var l=r.data("multipleInput"),o={},u=n.replace(/-\d+-([^\d]+)$/,"-$1");l.settings.attributes.hasOwnProperty(u)?o=l.settings.attributes[u]:(u=f(/-\d-/,"-",u),u=f(/-\d/,"",u),l.settings.attributes.hasOwnProperty(u)&&(o=l.settings.attributes[u])),a.yiiActiveForm("add",t.extend({},o,{id:n,input:"#"+n,container:".field-"+n}))}}},u=function(e){var n=s(e);if(null!==n){var i=t("#"+n).closest("form");0!==i.length&&i.yiiActiveForm("remove",n)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var n={};return e.closest("tr").find("td").each(function(e,i){t(i).find("input, select, textarea").each(function(e,i){var r=t(i),a=s(r),l=t("#"+a);n[a]=l.val()})}),n},f=function(t,e,n){return!(n instanceof String)&&"string"!==typeof n?(console.warn("Call replaceAll for non-string value: "+n),n):n.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){"use strict";t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},n={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},i=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},n,r||{}),s=t("#"+u.id),f=s.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),l(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),a(t(this),d(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(n[t]=e)}),u.showGeneralError||f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,i){i=t.extend({},n,i),u.attributes[e]=i}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),i=!0,clearInterval(h),s.trigger(m)}else v++;(0===f.length||v>10)&&(s.data("multipleInput").currentIndex=p(s),i=!1,clearInterval(h),s.trigger(m))},100)},add:function(e){a(t(this),e)},remove:function(e){var n=null;n=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),l(n)},clear:function(){t(this).find(".js-input-remove").each(function(){l(t(this))})},option:function(e,n){n=n||null;var i=t(this).data("multipleInput"),r=i.settings;if(null===n){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=n,i.settings=r,t(this).data("multipleInput",i))}},a=function(n,r){var a=t(n).closest(".multiple-input").first(),l=a.data("multipleInput"),u=l.settings,d=u.template,c=a.children(".multiple-input-list").first();if(!(null!==u.max&&p(a)>=u.max)){d=f("{"+u.indexPlaceholder+"}",l.currentIndex,d);var v=t(d),m=l.currentIndex,h=t.Event(e.beforeAddRow);if(a.trigger(h,[v,m]),h.result!==!1){if(u.prepend?v.hide().prependTo(c).fadeIn(300):v.hide().appendTo(c).fadeIn(300),r instanceof Object){var I=[];for(var g in r)r.hasOwnProperty(g)&&I.push(r[g]);r=I}var w;for(var x in u.jsTemplates)w=u.jsTemplates[x],w=f("{"+u.indexPlaceholder+"}",l.currentIndex,w),w=f("%7B"+u.indexPlaceholder+"%7D",l.currentIndex,w),window.eval(w);var y=0;t(d).find("input, select, textarea").each(function(e,n){var a=t(n),l=n.tagName,u=s(a),p=t("#"+u);if(r){var d=r[y];if("INPUT"===l||"TEXTAREA"===l)p.val(d);else if("SELECT"===l)if(d&&d.indexOf("option")!==-1)p.append(d);else{var f=p.find('option[value="'+d+'"]');f.length&&p.val(d)}}i&&o(a),y++}),a.data("multipleInput").currentIndex++;var j=t.Event(e.afterAddRow);a.trigger(j,[v,m])}}},l=function(n){var r=n.closest(".multiple-input").first(),a=n.closest(".multiple-input-list__item"),l=r.data("multipleInput"),o=l.settings,s=p(r);if(s>o.min){var d=t.Event(e.beforeDeleteRow);if(r.trigger(d,[a,s]),d.result===!1)return;i&&a.find("input, select, textarea").each(function(e,n){u(t(n))}),a.fadeOut(300,function(){t(this).remove(),d=t.Event(e.afterDeleteRow),r.trigger(d,[a,s])})}},o=function(e){var n=s(e);if(null!==n){var i=t("#"+n),r=i.closest(".multiple-input").first(),a=i.closest("form");if(0!==r.length&&"undefined"==typeof a.yiiActiveForm("find",n)){var l=r.data("multipleInput"),o={},u=n.replace(/-\d+-([^\d]+)$/,"-$1");l.settings.attributes.hasOwnProperty(u)?o=l.settings.attributes[u]:(u=f(/-\d-/,"-",u),u=f(/-\d/,"",u),l.settings.attributes.hasOwnProperty(u)&&(o=l.settings.attributes[u])),a.yiiActiveForm("add",t.extend({},o,{id:n,input:"#"+n,container:".field-"+n}))}}},u=function(e){var n=s(e);if(null!==n){var i=t("#"+n).closest("form");0!==i.length&&i.yiiActiveForm("remove",n)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var n={};return e.closest("tr").find("td").each(function(e,i){t(i).find("input, select, textarea").each(function(e,i){var r=t(i),a=s(r),l=t("#"+a);n[a]=l.val()})}),n},f=function(t,e,n){return n instanceof String||"string"==typeof n?n.split(t).join(e):(console.warn("Call replaceAll for non-string value: "+n),n)}}(window.jQuery); \ No newline at end of file From d092a27dc3805dad218e9e03eb882ac4699fccf3 Mon Sep 17 00:00:00 2001 From: bscheshirwork Date: Fri, 23 Aug 2019 15:06:57 +0300 Subject: [PATCH 186/247] read getter of AR if attribute not found --- src/components/BaseColumn.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 82993c6..1a4e1a0 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -253,8 +253,10 @@ protected function prepareValue($contextParams = []) $relation = $data->getRelation($this->name, false); if ($relation !== null) { $value = $relation->findFor($this->name, $data); - } else { + } elseif ($data->hasAttribute($this->name)) { $value = $data->getAttribute($this->name); + } else { + $value = $data->{$this->name}; } } elseif ($data instanceof Model) { $value = $data->{$this->name}; From 11ca532f0069fb202e9e3e7faf8f664e6843c670 Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Sun, 1 Sep 2019 00:05:20 +0700 Subject: [PATCH 187/247] Ignore dev files in zip distribution --- .gitattributes | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..75f9963 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +/.gitattributes export-ignore +/.gitignore export-ignore +/composer.lock export-ignore +/examples export-ignore +/gulpfile.js export-ignore +/package.json export-ignore +/phpunit.xml.dist export-ignore +/resources export-ignore +/tests export-ignore From d1d4d29e0d7fef0e3833d0b476812122a901f9b7 Mon Sep 17 00:00:00 2001 From: Yusup Hambali Date: Sun, 1 Sep 2019 00:09:48 +0700 Subject: [PATCH 188/247] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1e9aad1..c422a73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Yii2 multiple input change log 2.22.0 (in development) ======================= +- Ignore dev files in zip distribution (sup-ham) 2.21.4 ====== From 7986ca9263f9ea4ff596b5340de5ac38eacf8b23 Mon Sep 17 00:00:00 2001 From: eXeCUT Date: Tue, 3 Dec 2019 11:09:30 +0300 Subject: [PATCH 189/247] Updated to new phpunit. Added test for guess title --- tests/unit/MultipleInputTest.php | 10 +++++++--- tests/unit/TestCase.php | 2 +- tests/unit/data/TestModel.php | 7 +++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/tests/unit/MultipleInputTest.php b/tests/unit/MultipleInputTest.php index 6a56bab..4fd54d9 100644 --- a/tests/unit/MultipleInputTest.php +++ b/tests/unit/MultipleInputTest.php @@ -28,18 +28,22 @@ public function testGuessColumn() $this->assertEquals($expected, $widget->columns); } - public function testGlobalErrorGuessColumn() + public function testEnableGuessTitleInsideGuessColumn() { $model = new TestModel(); $widget = new MultipleInput([ 'model' => $model, 'attribute' => 'email', - 'enableError' => true, + 'enableGuessTitle' => true, ]); $expected = [ - ['name' => 'email', 'type' => MultipleInputColumn::TYPE_TEXT_INPUT, 'enableError' => true] + [ + 'name' => 'email', + 'type' => MultipleInputColumn::TYPE_TEXT_INPUT, + 'title' => 'Email', + ] ]; $this->assertEquals($expected, $widget->columns); diff --git a/tests/unit/TestCase.php b/tests/unit/TestCase.php index 9e05bcd..e908b41 100644 --- a/tests/unit/TestCase.php +++ b/tests/unit/TestCase.php @@ -3,7 +3,7 @@ namespace unclead\multipleinput\tests\unit; -abstract class TestCase extends \PHPUnit_Framework_TestCase +abstract class TestCase extends \PHPUnit\Framework\TestCase { } \ No newline at end of file diff --git a/tests/unit/data/TestModel.php b/tests/unit/data/TestModel.php index 5f672f4..1dba5d8 100644 --- a/tests/unit/data/TestModel.php +++ b/tests/unit/data/TestModel.php @@ -21,4 +21,11 @@ public function rules() ['email', 'safe'] ]; } + + public function attributeLabels() + { + return [ + 'email' => 'Email', + ]; + } } \ No newline at end of file From 375a488901ba55ea09172489f8a2e47f050ecba4 Mon Sep 17 00:00:00 2001 From: eXeCUT Date: Tue, 3 Dec 2019 14:40:21 +0300 Subject: [PATCH 190/247] Extracted value preparer for base column --- src/components/BaseColumn.php | 23 +----- src/components/ValuePreparer.php | 59 ++++++++++++++ tests/unit/components/ValuePreparerTest.php | 90 +++++++++++++++++++++ tests/unit/data/TestActiveRecord.php | 55 +++++++++++++ tests/unit/data/TestActiveRecordRelated.php | 17 ++++ 5 files changed, 223 insertions(+), 21 deletions(-) create mode 100644 src/components/ValuePreparer.php create mode 100644 tests/unit/components/ValuePreparerTest.php create mode 100644 tests/unit/data/TestActiveRecord.php create mode 100644 tests/unit/data/TestActiveRecordRelated.php diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 1a4e1a0..37a907b 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -248,27 +248,8 @@ protected function prepareValue($contextParams = []) if ($this->value instanceof \Closure) { $value = call_user_func($this->value, $data, $contextParams); } else { - $value = null; - if ($data instanceof ActiveRecordInterface ) { - $relation = $data->getRelation($this->name, false); - if ($relation !== null) { - $value = $relation->findFor($this->name, $data); - } elseif ($data->hasAttribute($this->name)) { - $value = $data->getAttribute($this->name); - } else { - $value = $data->{$this->name}; - } - } elseif ($data instanceof Model) { - $value = $data->{$this->name}; - } elseif (is_array($data)) { - $value = ArrayHelper::getValue($data, $this->name, null); - } elseif(is_string($data) || is_numeric($data)) { - $value = $data; - } - - if ($this->defaultValue !== null && $this->isEmpty($value)) { - $value = $this->defaultValue; - } + $valuePreparer = new ValuePreparer($this->name, $this->defaultValue); + $value = $valuePreparer->prepare($data); } return $value; diff --git a/src/components/ValuePreparer.php b/src/components/ValuePreparer.php new file mode 100644 index 0000000..874203d --- /dev/null +++ b/src/components/ValuePreparer.php @@ -0,0 +1,59 @@ +name = $name; + $this->defaultValue = $defaultValue; + } + + public function prepare($data, $contextParams = []) + { + $value = null; + if ($data instanceof ActiveRecordInterface) { + $relation = $data->getRelation($this->name, false); + if ($relation !== null) { + $value = $relation->findFor($this->name, $data); + } else if ($data->hasAttribute($this->name)) { + $value = $data->getAttribute($this->name); + } else { + $value = $data->{$this->name}; + } + } else if ($data instanceof Model) { + $value = $data->{$this->name}; + } else + if (is_array($data)) { + $value = ArrayHelper::getValue($data, $this->name, null); + } else if(is_string($data) || is_numeric($data)) { + $value = $data; + } +// + if ($this->defaultValue !== null && $this->isEmpty($value)) { + $value = $this->defaultValue; + } + + return $value; + } + + protected function isEmpty($value) + { + return $value === null || $value === [] || $value === ''; + } +} \ No newline at end of file diff --git a/tests/unit/components/ValuePreparerTest.php b/tests/unit/components/ValuePreparerTest.php new file mode 100644 index 0000000..5b96c8c --- /dev/null +++ b/tests/unit/components/ValuePreparerTest.php @@ -0,0 +1,90 @@ +assertEquals($defaultValue, $preparer->prepare(null)); + $this->assertEquals($defaultValue, $preparer->prepare([])); + $this->assertEquals($defaultValue, $preparer->prepare('')); + } + + public function testPrepareStringOrNumber() { + $model = new TestModel(); + $preparer = new ValuePreparer(); + $this->assertEquals(1, $preparer->prepare(1)); + $this->assertEquals('1', $preparer->prepare('1')); + } + + public function testPrepareArrayKey() { + $model = new TestModel(); + $preparer = new ValuePreparer('test'); + $this->assertEquals(1, $preparer->prepare([ + 'test' => 1 + ])); + } + + public function testPrepareModelAttribute() { + $model = new TestModel(); + $exprectedValue = [ + 'test' + ]; + $model->email = $exprectedValue; + $preparer = new ValuePreparer('email'); + $this->assertEquals($exprectedValue, $preparer->prepare($model)); + } + + public function testPrepareActiveRecordDirectAttribute() { + $model = new TestActiveRecord(); + $exprectedValue = 'test'; + $model->email = $exprectedValue; + $preparer = new ValuePreparer('email'); + $this->assertEquals($exprectedValue, $preparer->prepare($model)); + } + + public function testPrepareActiveRecordRelation() { + $relatedModel = new TestActiveRecordRelated(); + $model = $this->createMock(TestActiveRecord::class); + $query = $this->createMock(ActiveQuery::class); + $query->expects($this->once()) + ->method('findFor') + ->with('testRelation', $model) + ->willReturn($relatedModel); + + $model->expects($this->once()) + ->method('getRelation') + ->with('testRelation', false) + ->willReturn($query); + + $preparer = new ValuePreparer('testRelation'); + + $result = $preparer->prepare($model); + + $this->assertEquals($relatedModel, $result); + } + + public function testPrepareActiveRecordDatabaseAttribute() { + $model = new TestActiveRecord(); + $exprectedValue = 'test'; + $model->setDatabaseAttribute($exprectedValue); + + $preparer = new ValuePreparer('databaseAttribute'); + $this->assertEquals($exprectedValue, $preparer->prepare($model)); + } +} \ No newline at end of file diff --git a/tests/unit/data/TestActiveRecord.php b/tests/unit/data/TestActiveRecord.php new file mode 100644 index 0000000..1674bfe --- /dev/null +++ b/tests/unit/data/TestActiveRecord.php @@ -0,0 +1,55 @@ + 'Email', + ]; + } + + public function getTestHasOneRelation() {} + + public function attributes() + { + return ['databaseAttribute']; // TODO: Change the autogenerated stub + } + + protected $_databaseAttribute = null; + public function setDatabaseAttribute($value) { + $this->_databaseAttribute = $value; + } + + public function getAttribute($name) + { + if ($name === 'databaseAttribute') { + return $this->_databaseAttribute; + } + + return parent::getAttribute($name); + } +} \ No newline at end of file diff --git a/tests/unit/data/TestActiveRecordRelated.php b/tests/unit/data/TestActiveRecordRelated.php new file mode 100644 index 0000000..1890f57 --- /dev/null +++ b/tests/unit/data/TestActiveRecordRelated.php @@ -0,0 +1,17 @@ + Date: Tue, 3 Dec 2019 14:40:51 +0300 Subject: [PATCH 191/247] remove comment --- src/components/ValuePreparer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ValuePreparer.php b/src/components/ValuePreparer.php index 874203d..e6f3283 100644 --- a/src/components/ValuePreparer.php +++ b/src/components/ValuePreparer.php @@ -44,7 +44,7 @@ public function prepare($data, $contextParams = []) } else if(is_string($data) || is_numeric($data)) { $value = $data; } -// + if ($this->defaultValue !== null && $this->isEmpty($value)) { $value = $this->defaultValue; } From 95882827510abe6fb9779ad4ab2252417803b13e Mon Sep 17 00:00:00 2001 From: eXeCUT Date: Tue, 3 Dec 2019 15:12:13 +0300 Subject: [PATCH 192/247] Added support prepare values of attributes with same name as the relation. Added comments, removed unused code. --- src/components/BaseColumn.php | 5 --- src/components/ValuePreparer.php | 48 +++++++++++++++------ tests/unit/components/ValuePreparerTest.php | 18 +++----- tests/unit/data/TestActiveRecord.php | 23 ++++------ 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 37a907b..72dbede 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -255,11 +255,6 @@ protected function prepareValue($contextParams = []) return $value; } - protected function isEmpty($value) - { - return $value === null || $value === [] || $value === ''; - } - /** * Returns element id. * diff --git a/src/components/ValuePreparer.php b/src/components/ValuePreparer.php index e6f3283..e77f84d 100644 --- a/src/components/ValuePreparer.php +++ b/src/components/ValuePreparer.php @@ -1,9 +1,9 @@ name = $name; $this->defaultValue = $defaultValue; } - public function prepare($data, $contextParams = []) + /** + * @param $data Prepared data + * + * @return int|mixed|null|string + */ + public function prepare($data) { $value = null; if ($data instanceof ActiveRecordInterface) { - $relation = $data->getRelation($this->name, false); - if ($relation !== null) { - $value = $relation->findFor($this->name, $data); - } else if ($data->hasAttribute($this->name)) { - $value = $data->getAttribute($this->name); - } else { + if ($data->canGetProperty($this->name)) { $value = $data->{$this->name}; + } else { + $relation = $data->getRelation($this->name, false); + if ($relation !== null) { + $value = $relation->findFor($this->name, $data); + } else { + $value = $data->{$this->name}; + } } } else if ($data instanceof Model) { $value = $data->{$this->name}; diff --git a/tests/unit/components/ValuePreparerTest.php b/tests/unit/components/ValuePreparerTest.php index 5b96c8c..6f28606 100644 --- a/tests/unit/components/ValuePreparerTest.php +++ b/tests/unit/components/ValuePreparerTest.php @@ -1,12 +1,6 @@ assertEquals($relatedModel, $result); } - public function testPrepareActiveRecordDatabaseAttribute() { + public function testPrepareActiveRecordRelationWithSameAsAttributeName() { $model = new TestActiveRecord(); - $exprectedValue = 'test'; - $model->setDatabaseAttribute($exprectedValue); + $relatedModel = new TestActiveRecordRelated(); + $model->testRelation = $relatedModel; - $preparer = new ValuePreparer('databaseAttribute'); - $this->assertEquals($exprectedValue, $preparer->prepare($model)); + $preparer = new ValuePreparer( 'testRelation'); + $this->assertEquals($relatedModel, $preparer->prepare($model)); } } \ No newline at end of file diff --git a/tests/unit/data/TestActiveRecord.php b/tests/unit/data/TestActiveRecord.php index 1674bfe..757435c 100644 --- a/tests/unit/data/TestActiveRecord.php +++ b/tests/unit/data/TestActiveRecord.php @@ -1,11 +1,4 @@ _databaseAttribute = $value; + return ['testRelation']; // TODO: Change the autogenerated stub } public function getAttribute($name) { - if ($name === 'databaseAttribute') { - return $this->_databaseAttribute; + if ($name === 'testRelation') { + return $this->testRelation; } return parent::getAttribute($name); } + + public function getTestRelation() { + return $this->hasOne(TestActiveRecordRelated::class, []); + } } \ No newline at end of file From e90bce89aee9347ccbd96d88217392cddbe83c0d Mon Sep 17 00:00:00 2001 From: eXeCUT Date: Thu, 5 Dec 2019 09:26:57 +0300 Subject: [PATCH 193/247] fixed cs --- src/components/ValuePreparer.php | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/ValuePreparer.php b/src/components/ValuePreparer.php index e77f84d..98a7c97 100644 --- a/src/components/ValuePreparer.php +++ b/src/components/ValuePreparer.php @@ -60,10 +60,9 @@ public function prepare($data) $value = $data->{$this->name}; } } - } else if ($data instanceof Model) { + } elseif ($data instanceof Model) { $value = $data->{$this->name}; - } else - if (is_array($data)) { + } elseif (is_array($data)) { $value = ArrayHelper::getValue($data, $this->name, null); } else if(is_string($data) || is_numeric($data)) { $value = $data; From e2f67af58c6ee0156905942d73202a50058b4db2 Mon Sep 17 00:00:00 2001 From: eXeCUT Date: Thu, 5 Dec 2019 09:27:39 +0300 Subject: [PATCH 194/247] fixed cs --- src/components/ValuePreparer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ValuePreparer.php b/src/components/ValuePreparer.php index 98a7c97..91e01fb 100644 --- a/src/components/ValuePreparer.php +++ b/src/components/ValuePreparer.php @@ -64,7 +64,7 @@ public function prepare($data) $value = $data->{$this->name}; } elseif (is_array($data)) { $value = ArrayHelper::getValue($data, $this->name, null); - } else if(is_string($data) || is_numeric($data)) { + } elseif(is_string($data) || is_numeric($data)) { $value = $data; } From 1442258abf041e7993851ecdcef5631348f86fee Mon Sep 17 00:00:00 2001 From: eXeCUT Date: Thu, 5 Dec 2019 13:14:44 +0300 Subject: [PATCH 195/247] added changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c422a73..8973761 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Yii2 multiple input change log 2.22.0 (in development) ======================= - Ignore dev files in zip distribution (sup-ham) +- #292 Fixed tests for last PHPUnit +- Added support prepare values of attributes with same name as the relation 2.21.4 ====== From cc89d57b083ae82fec3f4dc547e6c936fa1719bd Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 14 Dec 2019 13:41:23 +0300 Subject: [PATCH 196/247] bump version to 2.22.0 --- CHANGELOG.md | 5 ++++- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8973761..cf2b218 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ Yii2 multiple input change log ============================== -2.22.0 (in development) +2.23.0 (in development) +======================= + +2.22.0 ======================= - Ignore dev files in zip distribution (sup-ham) - #292 Fixed tests for last PHPUnit diff --git a/README.md b/README.md index ae1235b..ea16b70 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.21.5 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.22.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 52c4cd5..e9b39bd 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.21.5", + "version": "2.22.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 63c75d1..1b69468 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.21.5", + "version": "2.22.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 54f670d78faa849c012319c678546d67ad43a5bc Mon Sep 17 00:00:00 2001 From: oleg <9407674@gmail.com> Date: Tue, 18 Feb 2020 13:52:04 +0200 Subject: [PATCH 197/247] ability to set tabindex in $options array When i use MultipleInput inside regular form i need an ability to start tabindex form my value. $form->field($model, 'name')->textInput(['tabindex' => '1']) $form->field($model, 'email')->textInput(['tabindex' => '2']) $form->field($model, 'phones')->widget(MultipleInput::className(), ...) // here i need tabindex = 3 --- src/components/BaseColumn.php | 44 ++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 72dbede..bb02e62 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -367,7 +367,9 @@ protected function renderDropDownList($name, $value, $options) Html::addCssClass($options, 'form-control'); } - $options['tabindex'] = self::TABINDEX; + if (!isset($options['tabindex'])) { + $options['tabindex'] = self::TABINDEX; + } return Html::dropDownList($name, $value, $this->prepareItems($this->items), $options); } @@ -401,7 +403,9 @@ protected function renderListBox($name, $value, $options) Html::addCssClass($options, 'form-control'); } - $options['tabindex'] = self::TABINDEX; + if (!isset($options['tabindex'])) { + $options['tabindex'] = self::TABINDEX; + } return Html::listBox($name, $value, $this->prepareItems($this->items), $options); } @@ -429,7 +433,9 @@ protected function renderHiddenInput($name, $value, $options) */ protected function renderRadio($name, $value, $options) { - $options['tabindex'] = self::TABINDEX; + if (!isset($options['tabindex'])) { + $options['tabindex'] = self::TABINDEX; + } if (!isset($options['label'])) { $options['label'] = ''; @@ -454,7 +460,9 @@ protected function renderRadio($name, $value, $options) */ protected function renderRadioList($name, $value, $options) { - $options['tabindex'] = self::TABINDEX; + if (!isset($options['tabindex'])) { + $options['tabindex'] = self::TABINDEX; + } if (!array_key_exists('unselect', $options)) { $options['unselect'] = ''; @@ -465,7 +473,7 @@ protected function renderRadioList($name, $value, $options) 'label' => $label, 'value' => $value, 'data-id' => ArrayHelper::getValue($options, 'id'), - 'tabindex' => self::TABINDEX + 'tabindex' => $options['tabindex'] ]); return Html::tag('div', $content, ['class' => 'radio']); @@ -486,7 +494,9 @@ protected function renderRadioList($name, $value, $options) */ protected function renderCheckbox($name, $value, $options) { - $options['tabindex'] = self::TABINDEX; + if (!isset($options['tabindex'])) { + $options['tabindex'] = self::TABINDEX; + } if (!isset($options['label'])) { $options['label'] = ''; @@ -511,7 +521,9 @@ protected function renderCheckbox($name, $value, $options) */ protected function renderCheckboxList($name, $value, $options) { - $options['tabindex'] = self::TABINDEX; + if (!isset($options['tabindex'])) { + $options['tabindex'] = self::TABINDEX; + } if (!array_key_exists('unselect', $options)) { $options['unselect'] = ''; @@ -522,7 +534,7 @@ protected function renderCheckboxList($name, $value, $options) 'label' => $label, 'value' => $value, 'data-id' => ArrayHelper::getValue($options, 'id'), - 'tabindex' => self::TABINDEX + 'tabindex' => $options['tabindex'] ]); return Html::tag('div', $content, ['class' => 'checkbox']); @@ -543,7 +555,9 @@ protected function renderCheckboxList($name, $value, $options) */ protected function renderStatic($name, $value, $options) { - $options['tabindex'] = self::TABINDEX; + if (!isset($options['tabindex'])) { + $options['tabindex'] = self::TABINDEX; + } if ($this->renderer->isBootstrapTheme()) { Html::addCssClass($options, 'form-control-static'); @@ -590,7 +604,9 @@ protected function renderDefault($name, $value, $options) $type = $this->type; if (method_exists('yii\helpers\Html', $type)) { - $options['tabindex'] = self::TABINDEX; + if (!isset($options['tabindex'])) { + $options['tabindex'] = self::TABINDEX; + } if ($this->renderer->isBootstrapTheme()) { Html::addCssClass($options, 'form-control'); @@ -617,7 +633,9 @@ protected function renderDefault($name, $value, $options) */ protected function renderWidget($type, $name, $value, $options) { - unset($options['tabindex']); + if (!isset($options['tabindex'])) { + $options['tabindex'] = self::TABINDEX; + } $model = $this->getModel(); if ($model instanceof Model) { @@ -628,7 +646,7 @@ protected function renderWidget($type, $name, $value, $options) 'options' => [ 'id' => $this->normalize($name), 'name' => $name, - 'tabindex' => self::TABINDEX, + 'tabindex' => $options['tabindex'], 'value' => $value ] ]; @@ -639,7 +657,7 @@ protected function renderWidget($type, $name, $value, $options) 'options' => [ 'id' => $this->normalize($name), 'name' => $name, - 'tabindex' => self::TABINDEX, + 'tabindex' => $options['tabindex'], 'value' => $value ] ]; From f7dc94aa56699daf3c87f10cb5ac597040f6409d Mon Sep 17 00:00:00 2001 From: oleg <9407674@gmail.com> Date: Tue, 18 Feb 2020 14:36:29 +0200 Subject: [PATCH 198/247] set tabindex in renderWidget options inside options $form->field($model, 'phones')->widget(MultipleInput::className(), [ .., 'columns' => [ 'options' => [ 'options' => ['class' => 'form-control', 'tabindex' => '5'], ], ] ]); --- src/components/BaseColumn.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index bb02e62..3d4caea 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -473,7 +473,7 @@ protected function renderRadioList($name, $value, $options) 'label' => $label, 'value' => $value, 'data-id' => ArrayHelper::getValue($options, 'id'), - 'tabindex' => $options['tabindex'] + 'tabindex' => self::TABINDEX ]); return Html::tag('div', $content, ['class' => 'radio']); @@ -534,7 +534,7 @@ protected function renderCheckboxList($name, $value, $options) 'label' => $label, 'value' => $value, 'data-id' => ArrayHelper::getValue($options, 'id'), - 'tabindex' => $options['tabindex'] + 'tabindex' => self::TABINDEX ]); return Html::tag('div', $content, ['class' => 'checkbox']); @@ -633,9 +633,9 @@ protected function renderDefault($name, $value, $options) */ protected function renderWidget($type, $name, $value, $options) { - if (!isset($options['tabindex'])) { - $options['tabindex'] = self::TABINDEX; - } + + $tabindex = isset($options['options']['tabindex']) ? $options['options']['tabindex'] : self::TABINDEX; + unset($options['tabindex']); $model = $this->getModel(); if ($model instanceof Model) { @@ -646,7 +646,7 @@ protected function renderWidget($type, $name, $value, $options) 'options' => [ 'id' => $this->normalize($name), 'name' => $name, - 'tabindex' => $options['tabindex'], + 'tabindex' => $tabindex, 'value' => $value ] ]; @@ -657,7 +657,7 @@ protected function renderWidget($type, $name, $value, $options) 'options' => [ 'id' => $this->normalize($name), 'name' => $name, - 'tabindex' => $options['tabindex'], + 'tabindex' => $tabindex, 'value' => $value ] ]; From 4ba73bff6469728bfcacab1da05732c2299a9d4e Mon Sep 17 00:00:00 2001 From: oleg <9407674@gmail.com> Date: Tue, 18 Feb 2020 17:13:57 +0200 Subject: [PATCH 199/247] CHANGELOG.md v2.23.0 tabindex --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cf2b218..fae547a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Yii2 multiple input change log 2.23.0 (in development) ======================= +- Ability to add custom tabindex via options array 2.22.0 ======================= From b98aefd780374c7ef70addd6b4b13aa1ff5d63a3 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 15 Nov 2020 20:40:57 +0300 Subject: [PATCH 200/247] fix input name in case of one coumn and enabled sorting --- CHANGELOG.md | 1 + src/MultipleInputColumn.php | 16 ++++++++++------ src/renderers/DivRenderer.php | 6 ++++-- src/renderers/ListRenderer.php | 2 +- src/renderers/TableRenderer.php | 3 ++- 5 files changed, 18 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fae547a..7e35ddc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Yii2 multiple input change log 2.23.0 (in development) ======================= - Ability to add custom tabindex via options array +- #335 fix input name in case of one coumn and enabled sorting 2.22.0 ======================= diff --git a/src/MultipleInputColumn.php b/src/MultipleInputColumn.php index 3dc07a8..e92c20c 100644 --- a/src/MultipleInputColumn.php +++ b/src/MultipleInputColumn.php @@ -70,7 +70,11 @@ public function getElementName($index, $withPrefix = true) */ private function isRendererHasOneColumn() { - return count($this->renderer->columns) === 1; + $columns = \array_filter($this->renderer->columns, function(self $column) { + return $column->type !== self::TYPE_DRAGCOLUMN; + }); + + return count($columns) === 1; } /** @@ -85,10 +89,10 @@ protected function getInputNamePrefix() if (empty($this->renderer->columns) || ($this->isRendererHasOneColumn() && $this->hasModelAttribute($this->name))) { return $model->formName(); } - + return Html::getInputName($this->context->model, $this->context->attribute); } - + return $this->context->name; } @@ -120,7 +124,7 @@ public function getFirstError($index) if ($index === null) { return null; } - + if ($this->isRendererHasOneColumn()) { $attribute = $this->name . '[' . $index . ']'; } else { @@ -161,7 +165,7 @@ protected function renderWidget($type, $name, $value, $options) $options['attribute'] = $attribute; // Remember current name and mark the widget as embedded to prevent - // generation of wrong prefix in case when column is associated with AR relation + // generation of wrong prefix in case the column is associated with AR relation // @see https://github.com/unclead/yii2-multiple-input/issues/92 $options['name'] = $name; $options['isEmbedded'] = true; @@ -169,4 +173,4 @@ protected function renderWidget($type, $name, $value, $options) return parent::renderWidget($type, $name, $value, $options); } -} \ No newline at end of file +} diff --git a/src/renderers/DivRenderer.php b/src/renderers/DivRenderer.php index e6a69c9..4b9de64 100644 --- a/src/renderers/DivRenderer.php +++ b/src/renderers/DivRenderer.php @@ -16,7 +16,8 @@ use yii\helpers\UnsetArrayValue; /** - * Class DivRenderer is a list renderer who use divs + * Class DivRenderer is a list renderer which uses divs + * * @package unclead\multipleinput\renderers */ class DivRenderer extends BaseRenderer @@ -185,9 +186,10 @@ public function renderCellContent($column, $index, $columnIndex = null) * via the $options array */ $options = ['id' => $id]; - if (substr($id, -4) === 'drag') { + if ($column->type === BaseColumn::TYPE_DRAGCOLUMN) { $options = ArrayHelper::merge($options, ['class' => $this->iconMap['drag-handle']]); } + $input = $column->renderInput($name, $options, [ 'id' => $id, 'name' => $name, diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index f6389bd..9a39358 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -201,7 +201,7 @@ public function renderCellContent($column, $index, $columnIndex = null) * via the $options array */ $options = ['id' => $id]; - if (substr($id, -4) === 'drag') { + if ($column->type === BaseColumn::TYPE_DRAGCOLUMN) { $options = ArrayHelper::merge($options, ['class' => $this->iconMap['drag-handle']]); } diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 8678c39..e53ae2a 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -278,10 +278,11 @@ public function renderCellContent($column, $index, $columnIndex = null) * via the $options array */ $options = ['id' => $id]; - if (substr($id, -4) === 'drag') { + if ($column->type === BaseColumn::TYPE_DRAGCOLUMN) { $options = ArrayHelper::merge($options, ['class' => $this->iconMap['drag-handle']]); } + $input = $column->renderInput($name, $options, [ 'id' => $id, 'name' => $name, From 2c7020768d7f1bb1e6088f9df202851f50256f6e Mon Sep 17 00:00:00 2001 From: Daniil Sazhin Date: Fri, 8 Jan 2021 02:53:57 +0700 Subject: [PATCH 201/247] Can't add more on dependent dropdown this part won't work if we use a widget like select2. could this be fixed maybe? https://github.com/unclead/yii2-multiple-input/issues/261#issuecomment-533017470 --- src/components/BaseColumn.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 3d4caea..4cb985c 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -637,6 +637,7 @@ protected function renderWidget($type, $name, $value, $options) $tabindex = isset($options['options']['tabindex']) ? $options['options']['tabindex'] : self::TABINDEX; unset($options['tabindex']); + $id = isset($options['id']) ? $options['id'] : $this->normalize($name); $model = $this->getModel(); if ($model instanceof Model) { $widgetOptions = [ @@ -644,7 +645,7 @@ protected function renderWidget($type, $name, $value, $options) 'attribute' => $this->name, 'value' => $value, 'options' => [ - 'id' => $this->normalize($name), + 'id' => $id, 'name' => $name, 'tabindex' => $tabindex, 'value' => $value @@ -655,7 +656,7 @@ protected function renderWidget($type, $name, $value, $options) 'name' => $name, 'value' => $value, 'options' => [ - 'id' => $this->normalize($name), + 'id' => $id, 'name' => $name, 'tabindex' => $tabindex, 'value' => $value From 981ef019de3b71f04ed59da87e12c02d36d9ebe6 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 8 Jan 2021 14:42:50 +0300 Subject: [PATCH 202/247] prepare to release 2.23.0 --- CHANGELOG.md | 10 +++++++--- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e35ddc..bbecb5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,17 @@ Yii2 multiple input change log ============================== -2.23.0 (in development) +2.24.0 (in development) ======================= + +2.23.0 +====== +- always use `id` from the settings if it is specified - Ability to add custom tabindex via options array -- #335 fix input name in case of one coumn and enabled sorting +- #335 fix input name in case of one column and enabled sorting 2.22.0 -======================= +====== - Ignore dev files in zip distribution (sup-ham) - #292 Fixed tests for last PHPUnit - Added support prepare values of attributes with same name as the relation diff --git a/README.md b/README.md index ea16b70..48a1c86 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.22.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.23.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index e9b39bd..99bb307 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.22.0", + "version": "2.23.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 1b69468..0cd13a9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.22.0", + "version": "2.23.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 16f8671131eb16faa77714cf354d2ae685cdc10d Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 8 Jan 2021 14:44:08 +0300 Subject: [PATCH 203/247] fix https://github.com/advisories/GHSA-699q-wcff-g9mj --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 99bb307..371bb9c 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ ], "require": { "php": ">=5.4.0", - "yiisoft/yii2": ">=2.0.13" + "yiisoft/yii2": ">=2.0.38" }, "require-dev": { "phpunit/phpunit": "5.7.*" From ec04946f9d92fadc4fbe4004e4c0a267b7807bf2 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 8 Jan 2021 14:48:46 +0300 Subject: [PATCH 204/247] don't keep lock files in the repo --- .gitattributes | 1 - .gitignore | 4 +- composer.lock | 1702 ------------------------------------------------ 3 files changed, 3 insertions(+), 1704 deletions(-) delete mode 100644 composer.lock diff --git a/.gitattributes b/.gitattributes index 75f9963..e4a26aa 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,5 @@ /.gitattributes export-ignore /.gitignore export-ignore -/composer.lock export-ignore /examples export-ignore /gulpfile.js export-ignore /package.json export-ignore diff --git a/.gitignore b/.gitignore index 945dd9f..60ad359 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .idea/* node_modules/ -vendor/ \ No newline at end of file +vendor/ +composer.lock +package-lock.json diff --git a/composer.lock b/composer.lock deleted file mode 100644 index 7aa8236..0000000 --- a/composer.lock +++ /dev/null @@ -1,1702 +0,0 @@ -{ - "_readme": [ - "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", - "This file is @generated automatically" - ], - "hash": "0c37169e6d126cfea4070e3b6703de94", - "content-hash": "d7051054c08c885a9347ca56fb769211", - "packages": [ - { - "name": "bower-asset/jquery", - "version": "2.2.4", - "source": { - "type": "git", - "url": "https://github.com/jquery/jquery-dist.git", - "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/jquery/jquery-dist/zipball/c0185ab7c75aab88762c5aae780b9d83b80eda72", - "reference": "c0185ab7c75aab88762c5aae780b9d83b80eda72", - "shasum": "" - }, - "type": "bower-asset-library", - "extra": { - "bower-asset-main": "dist/jquery.js", - "bower-asset-ignore": [ - "package.json" - ] - }, - "license": [ - "MIT" - ], - "keywords": [ - "browser", - "javascript", - "jquery", - "library" - ] - }, - { - "name": "bower-asset/jquery.inputmask", - "version": "3.2.7", - "source": { - "type": "git", - "url": "https://github.com/RobinHerbots/jquery.inputmask.git", - "reference": "5a72c563b502b8e05958a524cdfffafe9987be38" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/RobinHerbots/jquery.inputmask/zipball/5a72c563b502b8e05958a524cdfffafe9987be38", - "reference": "5a72c563b502b8e05958a524cdfffafe9987be38", - "shasum": "" - }, - "require": { - "bower-asset/jquery": ">=1.7" - }, - "type": "bower-asset-library", - "extra": { - "bower-asset-main": [ - "./dist/inputmask/inputmask.js" - ], - "bower-asset-ignore": [ - "**/*", - "!dist/*", - "!dist/inputmask/*", - "!dist/min/*", - "!dist/min/inputmask/*", - "!extra/bindings/*", - "!extra/dependencyLibs/*", - "!extra/phone-codes/*" - ] - }, - "license": [ - "http://opensource.org/licenses/mit-license.php" - ], - "description": "jquery.inputmask is a jquery plugin which create an input mask.", - "keywords": [ - "form", - "input", - "inputmask", - "jquery", - "mask", - "plugins" - ] - }, - { - "name": "bower-asset/punycode", - "version": "v1.3.2", - "source": { - "type": "git", - "url": "https://github.com/bestiejs/punycode.js.git", - "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/bestiejs/punycode.js/zipball/38c8d3131a82567bfef18da09f7f4db68c84f8a3", - "reference": "38c8d3131a82567bfef18da09f7f4db68c84f8a3", - "shasum": "" - }, - "type": "bower-asset-library", - "extra": { - "bower-asset-main": "punycode.js", - "bower-asset-ignore": [ - "coverage", - "tests", - ".*", - "component.json", - "Gruntfile.js", - "node_modules", - "package.json" - ] - } - }, - { - "name": "bower-asset/yii2-pjax", - "version": "v2.0.6", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/jquery-pjax.git", - "reference": "60728da6ade5879e807a49ce59ef9a72039b8978" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/jquery-pjax/zipball/60728da6ade5879e807a49ce59ef9a72039b8978", - "reference": "60728da6ade5879e807a49ce59ef9a72039b8978", - "shasum": "" - }, - "require": { - "bower-asset/jquery": ">=1.8" - }, - "type": "bower-asset-library", - "extra": { - "bower-asset-main": "./jquery.pjax.js", - "bower-asset-ignore": [ - ".travis.yml", - "Gemfile", - "Gemfile.lock", - "CONTRIBUTING.md", - "vendor/", - "script/", - "test/" - ] - }, - "license": [ - "MIT" - ] - }, - { - "name": "cebe/markdown", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/cebe/markdown.git", - "reference": "54a2c49de31cc44e864ebf0500a35ef21d0010b2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/cebe/markdown/zipball/54a2c49de31cc44e864ebf0500a35ef21d0010b2", - "reference": "54a2c49de31cc44e864ebf0500a35ef21d0010b2", - "shasum": "" - }, - "require": { - "lib-pcre": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "cebe/indent": "*", - "facebook/xhprof": "*@dev", - "phpunit/phpunit": "4.1.*" - }, - "bin": [ - "bin/markdown" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1.x-dev" - } - }, - "autoload": { - "psr-4": { - "cebe\\markdown\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Carsten Brandt", - "email": "mail@cebe.cc", - "homepage": "http://cebe.cc/", - "role": "Creator" - } - ], - "description": "A super fast, highly extensible markdown parser for PHP", - "homepage": "https://github.com/cebe/markdown#readme", - "keywords": [ - "extensible", - "fast", - "gfm", - "markdown", - "markdown-extra" - ], - "time": "2015-03-06 05:28:07" - }, - { - "name": "ezyang/htmlpurifier", - "version": "v4.8.0", - "source": { - "type": "git", - "url": "https://github.com/ezyang/htmlpurifier.git", - "reference": "d0c392f77d2f2a3dcf7fcb79e2a1e2b8804e75b2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/d0c392f77d2f2a3dcf7fcb79e2a1e2b8804e75b2", - "reference": "d0c392f77d2f2a3dcf7fcb79e2a1e2b8804e75b2", - "shasum": "" - }, - "require": { - "php": ">=5.2" - }, - "type": "library", - "autoload": { - "psr-0": { - "HTMLPurifier": "library/" - }, - "files": [ - "library/HTMLPurifier.composer.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "LGPL" - ], - "authors": [ - { - "name": "Edward Z. Yang", - "email": "admin@htmlpurifier.org", - "homepage": "http://ezyang.com" - } - ], - "description": "Standards compliant HTML filter written in PHP", - "homepage": "http://htmlpurifier.org/", - "keywords": [ - "html" - ], - "time": "2016-07-16 12:58:58" - }, - { - "name": "yiisoft/yii2", - "version": "2.0.9", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-framework.git", - "reference": "2b75151ea60e1fd820046416eee2e89c3dda1133" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-framework/zipball/2b75151ea60e1fd820046416eee2e89c3dda1133", - "reference": "2b75151ea60e1fd820046416eee2e89c3dda1133", - "shasum": "" - }, - "require": { - "bower-asset/jquery": "2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", - "bower-asset/jquery.inputmask": "~3.2.2", - "bower-asset/punycode": "1.3.*", - "bower-asset/yii2-pjax": "~2.0.1", - "cebe/markdown": "~1.0.0 | ~1.1.0", - "ext-ctype": "*", - "ext-mbstring": "*", - "ezyang/htmlpurifier": "~4.6", - "lib-pcre": "*", - "php": ">=5.4.0", - "yiisoft/yii2-composer": "~2.0.4" - }, - "bin": [ - "yii" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "yii\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com", - "homepage": "http://www.yiiframework.com/", - "role": "Founder and project lead" - }, - { - "name": "Alexander Makarov", - "email": "sam@rmcreative.ru", - "homepage": "http://rmcreative.ru/", - "role": "Core framework development" - }, - { - "name": "Maurizio Domba", - "homepage": "http://mdomba.info/", - "role": "Core framework development" - }, - { - "name": "Carsten Brandt", - "email": "mail@cebe.cc", - "homepage": "http://cebe.cc/", - "role": "Core framework development" - }, - { - "name": "Timur Ruziev", - "email": "resurtm@gmail.com", - "homepage": "http://resurtm.com/", - "role": "Core framework development" - }, - { - "name": "Paul Klimov", - "email": "klimov.paul@gmail.com", - "role": "Core framework development" - }, - { - "name": "Dmitry Naumenko", - "email": "d.naumenko.a@gmail.com", - "role": "Core framework development" - } - ], - "description": "Yii PHP Framework Version 2", - "homepage": "http://www.yiiframework.com/", - "keywords": [ - "framework", - "yii2" - ], - "time": "2016-07-11 13:36:42" - }, - { - "name": "yiisoft/yii2-composer", - "version": "2.0.4", - "source": { - "type": "git", - "url": "https://github.com/yiisoft/yii2-composer.git", - "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/yiisoft/yii2-composer/zipball/7452fd908a5023b8bb5ea1b123a174ca080de464", - "reference": "7452fd908a5023b8bb5ea1b123a174ca080de464", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0" - }, - "type": "composer-plugin", - "extra": { - "class": "yii\\composer\\Plugin", - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "yii\\composer\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Qiang Xue", - "email": "qiang.xue@gmail.com" - } - ], - "description": "The composer plugin for Yii extension installer", - "keywords": [ - "composer", - "extension installer", - "yii2" - ], - "time": "2016-02-06 00:49:24" - } - ], - "packages-dev": [ - { - "name": "doctrine/instantiator", - "version": "1.0.5", - "source": { - "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", - "shasum": "" - }, - "require": { - "php": ">=5.3,<8.0-DEV" - }, - "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", - "keywords": [ - "constructor", - "instantiate" - ], - "time": "2015-06-14 21:17:01" - }, - { - "name": "myclabs/deep-copy", - "version": "1.5.1", - "source": { - "type": "git", - "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "a8773992b362b58498eed24bf85005f363c34771" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/a8773992b362b58498eed24bf85005f363c34771", - "reference": "a8773992b362b58498eed24bf85005f363c34771", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "doctrine/collections": "1.*", - "phpunit/phpunit": "~4.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "DeepCopy\\": "src/DeepCopy/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Create deep copies (clones) of your objects", - "homepage": "https://github.com/myclabs/DeepCopy", - "keywords": [ - "clone", - "copy", - "duplicate", - "object", - "object graph" - ], - "time": "2015-11-20 12:04:31" - }, - { - "name": "phpdocumentor/reflection-common", - "version": "1.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/144c307535e82c8fdcaacbcfc1d6d8eeb896687c", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", - "shasum": "" - }, - "require": { - "php": ">=5.5" - }, - "require-dev": { - "phpunit/phpunit": "^4.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ], - "time": "2015-12-27 11:43:31" - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "3.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "9270140b940ff02e58ec577c237274e92cd40cdd" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9270140b940ff02e58ec577c237274e92cd40cdd", - "reference": "9270140b940ff02e58ec577c237274e92cd40cdd", - "shasum": "" - }, - "require": { - "php": ">=5.5", - "phpdocumentor/reflection-common": "^1.0@dev", - "phpdocumentor/type-resolver": "^0.2.0", - "webmozart/assert": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^4.4" - }, - "type": "library", - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2016-06-10 09:48:41" - }, - { - "name": "phpdocumentor/type-resolver", - "version": "0.2", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/b39c7a5b194f9ed7bd0dd345c751007a41862443", - "reference": "b39c7a5b194f9ed7bd0dd345c751007a41862443", - "shasum": "" - }, - "require": { - "php": ">=5.5", - "phpdocumentor/reflection-common": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "time": "2016-06-10 07:14:17" - }, - { - "name": "phpspec/prophecy", - "version": "v1.6.1", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "58a8137754bc24b25740d4281399a4a3596058e0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/58a8137754bc24b25740d4281399a4a3596058e0", - "reference": "58a8137754bc24b25740d4281399a4a3596058e0", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", - "sebastian/comparator": "^1.1", - "sebastian/recursion-context": "^1.0" - }, - "require-dev": { - "phpspec/phpspec": "^2.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, - "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ], - "time": "2016-06-07 08:13:47" - }, - { - "name": "phpunit/php-code-coverage", - "version": "4.0.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/5f3f7e736d6319d5f1fc402aff8b026da26709a3", - "reference": "5f3f7e736d6319d5f1fc402aff8b026da26709a3", - "shasum": "" - }, - "require": { - "php": "^5.6 || ^7.0", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.2", - "phpunit/php-token-stream": "^1.4.2", - "sebastian/code-unit-reverse-lookup": "~1.0", - "sebastian/environment": "^1.3.2 || ^2.0", - "sebastian/version": "~1.0|~2.0" - }, - "require-dev": { - "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "^5.4" - }, - "suggest": { - "ext-dom": "*", - "ext-xdebug": ">=2.4.0", - "ext-xmlwriter": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ], - "time": "2016-07-26 14:39:29" - }, - { - "name": "phpunit/php-file-iterator", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", - "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ], - "time": "2015-06-21 13:08:43" - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ], - "time": "2015-06-21 13:50:34" - }, - { - "name": "phpunit/php-timer", - "version": "1.0.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", - "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4|~5" - }, - "type": "library", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ], - "time": "2016-05-12 18:03:57" - }, - { - "name": "phpunit/php-token-stream", - "version": "1.4.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", - "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ], - "time": "2015-09-15 10:49:45" - }, - { - "name": "phpunit/phpunit", - "version": "5.4.8", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3132365e1430c091f208e120b8845d39c25f20e6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3132365e1430c091f208e120b8845d39c25f20e6", - "reference": "3132365e1430c091f208e120b8845d39c25f20e6", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "myclabs/deep-copy": "~1.3", - "php": "^5.6 || ^7.0", - "phpspec/prophecy": "^1.3.1", - "phpunit/php-code-coverage": "^4.0.1", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "^3.2", - "sebastian/comparator": "~1.1", - "sebastian/diff": "~1.2", - "sebastian/environment": "^1.3 || ^2.0", - "sebastian/exporter": "~1.2", - "sebastian/global-state": "~1.0", - "sebastian/object-enumerator": "~1.0", - "sebastian/resource-operations": "~1.0", - "sebastian/version": "~1.0|~2.0", - "symfony/yaml": "~2.1|~3.0" - }, - "conflict": { - "phpdocumentor/reflection-docblock": "3.0.2" - }, - "suggest": { - "phpunit/php-invoker": "~1.1" - }, - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.4.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ], - "time": "2016-07-26 14:48:00" - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "3.2.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "b13d0d9426ced06958bd32104653526a6c998a52" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/b13d0d9426ced06958bd32104653526a6c998a52", - "reference": "b13d0d9426ced06958bd32104653526a6c998a52", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.6 || ^7.0", - "phpunit/php-text-template": "^1.2", - "sebastian/exporter": "^1.2" - }, - "conflict": { - "phpunit/phpunit": "<5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^5.4" - }, - "suggest": { - "ext-soap": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ], - "time": "2016-06-12 07:37:26" - }, - { - "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/c36f5e7cfce482fde5bf8d10d41a53591e0198fe", - "reference": "c36f5e7cfce482fde5bf8d10d41a53591e0198fe", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "phpunit/phpunit": "~5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Looks up which function or method a line of code belongs to", - "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", - "time": "2016-02-13 06:45:14" - }, - { - "name": "sebastian/comparator", - "version": "1.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", - "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ], - "time": "2015-07-26 15:48:44" - }, - { - "name": "sebastian/diff", - "version": "1.4.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", - "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.8" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff" - ], - "time": "2015-12-08 07:14:41" - }, - { - "name": "sebastian/environment", - "version": "1.3.7", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/4e8f0da10ac5802913afc151413bc8c53b6c2716", - "reference": "4e8f0da10ac5802913afc151413bc8c53b6c2716", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ], - "time": "2016-05-17 03:18:57" - }, - { - "name": "sebastian/exporter", - "version": "1.2.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/42c4c2eec485ee3e159ec9884f95b431287edde4", - "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~1.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ], - "time": "2016-06-17 09:04:28" - }, - { - "name": "sebastian/global-state", - "version": "1.1.1", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "suggest": { - "ext-uopz": "*" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ], - "time": "2015-10-12 03:26:01" - }, - { - "name": "sebastian/object-enumerator", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "d4ca2fb70344987502567bc50081c03e6192fb26" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/d4ca2fb70344987502567bc50081c03e6192fb26", - "reference": "d4ca2fb70344987502567bc50081c03e6192fb26", - "shasum": "" - }, - "require": { - "php": ">=5.6", - "sebastian/recursion-context": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "~5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Traverses array structures and object graphs to enumerate all referenced objects", - "homepage": "https://github.com/sebastianbergmann/object-enumerator/", - "time": "2016-01-28 13:25:10" - }, - { - "name": "sebastian/recursion-context", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/913401df809e99e4f47b27cdd781f4a258d58791", - "reference": "913401df809e99e4f47b27cdd781f4a258d58791", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context", - "time": "2015-11-11 19:50:13" - }, - { - "name": "sebastian/resource-operations", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/resource-operations.git", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", - "shasum": "" - }, - "require": { - "php": ">=5.6.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides a list of PHP built-in functions that operate on resources", - "homepage": "https://www.github.com/sebastianbergmann/resource-operations", - "time": "2015-07-28 20:34:47" - }, - { - "name": "sebastian/version", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", - "reference": "c829badbd8fdf16a0bad8aa7fa7971c029f1b9c5", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version", - "time": "2016-02-04 12:56:52" - }, - { - "name": "symfony/yaml", - "version": "v3.1.3", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "1819adf2066880c7967df7180f4f662b6f0567ac" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/1819adf2066880c7967df7180f4f662b6f0567ac", - "reference": "1819adf2066880c7967df7180f4f662b6f0567ac", - "shasum": "" - }, - "require": { - "php": ">=5.5.9" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.1-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com", - "time": "2016-07-17 14:02:08" - }, - { - "name": "webmozart/assert", - "version": "1.0.2", - "source": { - "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", - "reference": "30eed06dd6bc88410a4ff7f77b6d22f3ce13dbde", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "^4.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ], - "time": "2015-08-24 13:29:44" - } - ], - "aliases": [], - "minimum-stability": "stable", - "stability-flags": [], - "prefer-stable": false, - "prefer-lowest": false, - "platform": { - "php": ">=5.4.0" - }, - "platform-dev": [] -} From cc39a0a0aa9d7ef138c362227f80c02a6ea1eb73 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 9 Jan 2021 13:53:01 +0300 Subject: [PATCH 205/247] #339 don't set tabindex explicitly --- CHANGELOG.md | 6 +++- src/components/BaseColumn.php | 53 ++++++++--------------------------- 2 files changed, 16 insertions(+), 43 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bbecb5e..dc699b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ Yii2 multiple input change log ============================== -2.24.0 (in development) +2.25.0 (in development) ======================= +2.24.0 +====== +- #339 don't set tabindex explicitly + 2.23.0 ====== - always use `id` from the settings if it is specified diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 4cb985c..b1cee6a 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -39,8 +39,6 @@ abstract class BaseColumn extends BaseObject const TYPE_RADIO = 'radio'; const TYPE_DRAGCOLUMN = 'dragColumn'; - const TABINDEX = 1; - const DEFAULT_STATIC_COLUMN_NAME = 'static-column'; /** @@ -366,11 +364,7 @@ protected function renderDropDownList($name, $value, $options) if ($this->renderer->isBootstrapTheme()) { Html::addCssClass($options, 'form-control'); } - - if (!isset($options['tabindex'])) { - $options['tabindex'] = self::TABINDEX; - } - + return Html::dropDownList($name, $value, $this->prepareItems($this->items), $options); } @@ -402,11 +396,7 @@ protected function renderListBox($name, $value, $options) if ($this->renderer->isBootstrapTheme()) { Html::addCssClass($options, 'form-control'); } - - if (!isset($options['tabindex'])) { - $options['tabindex'] = self::TABINDEX; - } - + return Html::listBox($name, $value, $this->prepareItems($this->items), $options); } @@ -433,10 +423,6 @@ protected function renderHiddenInput($name, $value, $options) */ protected function renderRadio($name, $value, $options) { - if (!isset($options['tabindex'])) { - $options['tabindex'] = self::TABINDEX; - } - if (!isset($options['label'])) { $options['label'] = ''; } @@ -460,10 +446,6 @@ protected function renderRadio($name, $value, $options) */ protected function renderRadioList($name, $value, $options) { - if (!isset($options['tabindex'])) { - $options['tabindex'] = self::TABINDEX; - } - if (!array_key_exists('unselect', $options)) { $options['unselect'] = ''; } @@ -473,7 +455,6 @@ protected function renderRadioList($name, $value, $options) 'label' => $label, 'value' => $value, 'data-id' => ArrayHelper::getValue($options, 'id'), - 'tabindex' => self::TABINDEX ]); return Html::tag('div', $content, ['class' => 'radio']); @@ -494,10 +475,6 @@ protected function renderRadioList($name, $value, $options) */ protected function renderCheckbox($name, $value, $options) { - if (!isset($options['tabindex'])) { - $options['tabindex'] = self::TABINDEX; - } - if (!isset($options['label'])) { $options['label'] = ''; } @@ -521,10 +498,6 @@ protected function renderCheckbox($name, $value, $options) */ protected function renderCheckboxList($name, $value, $options) { - if (!isset($options['tabindex'])) { - $options['tabindex'] = self::TABINDEX; - } - if (!array_key_exists('unselect', $options)) { $options['unselect'] = ''; } @@ -534,7 +507,6 @@ protected function renderCheckboxList($name, $value, $options) 'label' => $label, 'value' => $value, 'data-id' => ArrayHelper::getValue($options, 'id'), - 'tabindex' => self::TABINDEX ]); return Html::tag('div', $content, ['class' => 'checkbox']); @@ -555,10 +527,6 @@ protected function renderCheckboxList($name, $value, $options) */ protected function renderStatic($name, $value, $options) { - if (!isset($options['tabindex'])) { - $options['tabindex'] = self::TABINDEX; - } - if ($this->renderer->isBootstrapTheme()) { Html::addCssClass($options, 'form-control-static'); } @@ -604,10 +572,6 @@ protected function renderDefault($name, $value, $options) $type = $this->type; if (method_exists('yii\helpers\Html', $type)) { - if (!isset($options['tabindex'])) { - $options['tabindex'] = self::TABINDEX; - } - if ($this->renderer->isBootstrapTheme()) { Html::addCssClass($options, 'form-control'); } @@ -633,10 +597,15 @@ protected function renderDefault($name, $value, $options) */ protected function renderWidget($type, $name, $value, $options) { - - $tabindex = isset($options['options']['tabindex']) ? $options['options']['tabindex'] : self::TABINDEX; - unset($options['tabindex']); - + if (isset($options['options']['tabindex'])) { + $tabindex = $options['options']['tabindex']; + } elseif (isset($options['tabindex'])) { + $tabindex = $options['tabindex']; + unset($options['tabindex']); + } else { + $tabindex = null; + } + $id = isset($options['id']) ? $options['id'] : $this->normalize($name); $model = $this->getModel(); if ($model instanceof Model) { From fa4a69a96643b532ea2b49f86bdd6aeb6cd201a7 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 9 Jan 2021 13:53:24 +0300 Subject: [PATCH 206/247] bump version --- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 48a1c86..6be0873 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.23.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.24.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index 371bb9c..bd75afd 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.23.0", + "version": "2.24.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 0cd13a9..1c38947 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.23.0", + "version": "2.24.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From bcb10402152745500de6e7a4483609ab245ca6c5 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 6 Aug 2021 10:47:18 +0300 Subject: [PATCH 207/247] rework cloning --- src/assets/src/js/jquery.multipleInput.js | 187 +++++++++++++--------- 1 file changed, 108 insertions(+), 79 deletions(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 56693c7..625101f 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -157,7 +157,7 @@ $wrapper.on('click.multipleInput', '.js-input-clone', function (e) { e.stopPropagation(); - addInput($(this), getRowValues($(this))); + cloneInput($(this)); }); var i = 0, @@ -196,10 +196,10 @@ $wrapper.data('multipleInput').settings = settings; $wrapper.find('.multiple-input-list').find('input, select, textarea').each(function () { - addAttribute($(this)); + addActiveFormAttribute($(this)); }); - $wrapper.data('multipleInput').currentIndex = getCurrentIndex($wrapper); + $wrapper.data('multipleInput').currentIndex = getCurrentRowsCount($wrapper); isActiveFormEnabled = true; clearInterval(intervalID); @@ -212,7 +212,7 @@ // If after a second system could not detect ActiveForm it means // that widget is used without ActiveForm and we should just complete initialization of the widget if (form.length === 0 || i > 10) { - $wrapper.data('multipleInput').currentIndex = getCurrentIndex($wrapper); + $wrapper.data('multipleInput').currentIndex = getCurrentRowsCount($wrapper); isActiveFormEnabled = false; clearInterval(intervalID); @@ -260,92 +260,134 @@ } }; - var addInput = function (btn, values) { - var $wrapper = $(btn).closest('.multiple-input').first(), - data = $wrapper.data('multipleInput'), - settings = data.settings, - template = settings.template, - inputList = $wrapper.children('.multiple-input-list').first(); + var cloneInput = function (btn) { + let $wrapper = $(btn).closest('.multiple-input').first(); + let data = $wrapper.data('multipleInput'); + let settings = data.settings; + + let values = {}; + + btn.closest('.multiple-input-list__item').find('input, select, textarea').each(function (k, v) { + let $element = $(v); + + let id = getInputId($element); + if (id) { + let columnName = id.replace(settings.inputId, '').replace(/-\d+-/, ''); + + if ($element.is(':checkbox')) { + if (!values.hasOwnProperty(columnName)) { + values[columnName] = []; + } + + if ($element.is(':checked')) { + values[columnName].push($element.val()); + } + } else { + values[columnName] = $element.val(); + } + } + }); - if (settings.max !== null && getCurrentIndex($wrapper) >= settings.max) { + addInput(btn, values); + } + + var addInput = function (btn, rowValues) { + rowValues = rowValues || {}; + + let $wrapper = $(btn).closest('.multiple-input').first(); + let data = $wrapper.data('multipleInput'); + let settings = data.settings; + let inputList = $wrapper.children('.multiple-input-list').first(); + + if (settings.max !== null && getCurrentRowsCount($wrapper) >= settings.max) { return; } - template = replaceAll('{' + settings.indexPlaceholder + '}', data.currentIndex, template); - var $addedInput = $(template); - var currentIndex = data.currentIndex; + let currentIndex = data.currentIndex; + + let template = replaceAll('{' + settings.indexPlaceholder + '}', data.currentIndex, settings.template); + let $newRow = $(template); var beforeAddEvent = $.Event(events.beforeAddRow); - $wrapper.trigger(beforeAddEvent, [$addedInput, currentIndex]); + $wrapper.trigger(beforeAddEvent, [$newRow, currentIndex]); if (beforeAddEvent.result === false) { return; } + $newRow.find('input, select, textarea').each(function (index, element) { + let $element = $(element); - if (settings.prepend) { - $addedInput.hide().prependTo(inputList).fadeIn(300); - } else { - $addedInput.hide().appendTo(inputList).fadeIn(300); - } + let id = getInputId($element); + if (id) { + let columnName = id.replace(settings.inputId, '').replace(/-\d+-/, ''); - if (values instanceof Object) { - var tmp = []; - for (var key in values) { - if (values.hasOwnProperty(key)) { - tmp.push(values[key]); - } - } + if (rowValues.hasOwnProperty(columnName)) { + let tag = $element.get(0).tagName; - values = tmp; - } + let inputValue = rowValues[columnName]; - var jsTemplate; + switch (tag) { + case 'INPUT': + if ($element.is(':checkbox')) { + if (inputValue.indexOf($element.val()) !== -1) { + $element.prop('checked', true); + } + } else { + $element.val(inputValue); + } - for (var i in settings.jsTemplates) { - jsTemplate = settings.jsTemplates[i]; - jsTemplate = replaceAll('{' + settings.indexPlaceholder + '}', data.currentIndex, jsTemplate); - jsTemplate = replaceAll('%7B' + settings.indexPlaceholder + '%7D', data.currentIndex, jsTemplate); + break; - window.eval(jsTemplate); - } + case 'TEXTAREA': + $element.val(inputValue); + break; - var index = 0; - - $(template).find('input, select, textarea').each(function (k, v) { - var ele = $(v), - tag = v.tagName, - id = getInputId(ele), - obj = $('#' + id); - - if (values) { - var val = values[index]; - - if (tag === 'INPUT' || tag === 'TEXTAREA') { - obj.val(val); - } else if (tag === 'SELECT') { - if (val && val.indexOf('option') !== -1) { - obj.append(val); - } else { - var option = obj.find('option[value="' + val + '"]'); - if (option.length) { - obj.val(val); - } + case 'SELECT': + if (inputValue && inputValue.indexOf('option') !== -1) { + $element.append(inputValue); + } else { + var option = $element.find('option[value="' + inputValue + '"]'); + if (option.length) { + $element.val(inputValue); + } + } + + break; + + default: + break; } } } + if (isActiveFormEnabled) { - addAttribute(ele); + addActiveFormAttribute($element); } - - index++; }); + // display the new row + if (settings.prepend) { + $newRow.hide().prependTo(inputList).fadeIn(300); + } else { + $newRow.hide().appendTo(inputList).fadeIn(300); + } + + // apply js templates + let jsTemplate = null; + for (var i in settings.jsTemplates) { + jsTemplate = settings.jsTemplates[i]; + jsTemplate = replaceAll('{' + settings.indexPlaceholder + '}', currentIndex, jsTemplate); + jsTemplate = replaceAll('%7B' + settings.indexPlaceholder + '%7D', currentIndex, jsTemplate); + + window.eval(jsTemplate); + } + $wrapper.data('multipleInput').currentIndex++; var afterAddEvent = $.Event(events.afterAddRow); - $wrapper.trigger(afterAddEvent, [$addedInput, currentIndex]); + $wrapper.trigger(afterAddEvent, [$newRow, currentIndex]); }; var removeInput = function ($btn) { @@ -354,7 +396,7 @@ data = $wrapper.data('multipleInput'), settings = data.settings; - var currentIndex = getCurrentIndex($wrapper); + var currentIndex = getCurrentRowsCount($wrapper); if (currentIndex > settings.min) { var event = $.Event(events.beforeDeleteRow); $wrapper.trigger(event, [$toDelete, currentIndex]); @@ -365,7 +407,7 @@ if (isActiveFormEnabled) { $toDelete.find('input, select, textarea').each(function (index, ele) { - removeAttribute($(ele)); + removeActiveFormAttribute($(ele)); }); } @@ -383,7 +425,7 @@ * * @param input */ - var addAttribute = function (input) { + var addActiveFormAttribute = function (input) { var id = getInputId(input); // skip if we could not get an ID of input @@ -435,7 +477,7 @@ /** * Removes an attribute from ActiveForm. */ - var removeAttribute = function (ele) { + var removeActiveFormAttribute = function (ele) { var id = getInputId(ele); if (id === null) { @@ -463,7 +505,7 @@ return id; }; - var getCurrentIndex = function($wrapper) { + var getCurrentRowsCount = function($wrapper) { return $wrapper .find('.multiple-input-list .multiple-input-list__item') .filter(function(){ @@ -471,19 +513,6 @@ }).length; }; - var getRowValues = function (element) { - var values = {}; - element.closest('tr').find('td').each(function (index, value) { - $(value).find('input, select, textarea').each(function (k, v) { - var ele = $(v), - id = getInputId(ele), - obj = $('#' + id); - values[id] = obj.val(); - }); - }); - return values; - }; - var replaceAll = function (search, replace, subject) { if (!(subject instanceof String) && typeof subject !== 'string') { console.warn('Call replaceAll for non-string value: ' + subject); From 0060ece1112859ccb132c53da702943e327acad4 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 6 Aug 2021 11:12:27 +0300 Subject: [PATCH 208/247] adjust minification via gulp --- gulpfile.js | 22 +++++++++++----------- package.json | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 79206fc..5e2a310 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,18 +1,18 @@ -var gulp = require('gulp'), - uglify = require('gulp-uglify'), - concat = require('gulp-concat'), - rename = require('gulp-rename'); +const {gulp, src, dest} = require('gulp'); +const uglify = require('gulp-uglify-es').default; +const concat = require('gulp-concat'); +const rename = require('gulp-rename'); -gulp.task('js', function () { +function build () { var path = './src/assets/src/js/'; - - return gulp.src([ + + return src([ path + 'jquery.multipleInput.js' ]) .pipe(concat('jquery.multipleInput.js')) .pipe(uglify()) .pipe(rename({suffix: '.min'})) - .pipe(gulp.dest(path)); -}); - -gulp.task('default', ['js']); \ No newline at end of file + .pipe(dest(path)); +}; + +exports.default = build; diff --git a/package.json b/package.json index 1c38947..516f8bd 100644 --- a/package.json +++ b/package.json @@ -22,9 +22,9 @@ }, "homepage": "https://github.com/unclead/yii2-multiple-input", "devDependencies": { - "gulp": "^3.8.8", - "gulp-uglify": "^1.0.1", + "gulp": "^4.0.2", "gulp-concat": "^2.4.1", - "gulp-rename": "^1.2.0" + "gulp-rename": "^1.4.0", + "gulp-uglify-es": "3.0.0" } } From 42dac184b7401e71073d2d534f5ea24550ef25e1 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 6 Aug 2021 11:12:37 +0300 Subject: [PATCH 209/247] update minified version --- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 4b573ba..d62bc94 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){"use strict";t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow",afterDropRow:"afterDropRow"},n={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},i=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},n,r||{}),s=t("#"+u.id),f=s.closest("form"),c=u.inputId;for(v in u.jsInit)window.eval(u.jsInit[v]);s.data("multipleInput",{settings:u,currentIndex:0}),s.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),l(t(this))}),s.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),a(t(this))}),s.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),a(t(this),d(t(this)))});var v=0,m=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),n={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)===-1&&(n[t]=e)}),u.showGeneralError||f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,i){i=t.extend({},n,i),u.attributes[e]=i}),s.data("multipleInput").settings=u,s.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),s.data("multipleInput").currentIndex=p(s),i=!0,clearInterval(h),s.trigger(m)}else v++;(0===f.length||v>10)&&(s.data("multipleInput").currentIndex=p(s),i=!1,clearInterval(h),s.trigger(m))},100)},add:function(e){a(t(this),e)},remove:function(e){var n=null;n=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),l(n)},clear:function(){t(this).find(".js-input-remove").each(function(){l(t(this))})},option:function(e,n){n=n||null;var i=t(this).data("multipleInput"),r=i.settings;if(null===n){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=n,i.settings=r,t(this).data("multipleInput",i))}},a=function(n,r){var a=t(n).closest(".multiple-input").first(),l=a.data("multipleInput"),u=l.settings,d=u.template,c=a.children(".multiple-input-list").first();if(!(null!==u.max&&p(a)>=u.max)){d=f("{"+u.indexPlaceholder+"}",l.currentIndex,d);var v=t(d),m=l.currentIndex,h=t.Event(e.beforeAddRow);if(a.trigger(h,[v,m]),h.result!==!1){if(u.prepend?v.hide().prependTo(c).fadeIn(300):v.hide().appendTo(c).fadeIn(300),r instanceof Object){var I=[];for(var g in r)r.hasOwnProperty(g)&&I.push(r[g]);r=I}var w;for(var x in u.jsTemplates)w=u.jsTemplates[x],w=f("{"+u.indexPlaceholder+"}",l.currentIndex,w),w=f("%7B"+u.indexPlaceholder+"%7D",l.currentIndex,w),window.eval(w);var y=0;t(d).find("input, select, textarea").each(function(e,n){var a=t(n),l=n.tagName,u=s(a),p=t("#"+u);if(r){var d=r[y];if("INPUT"===l||"TEXTAREA"===l)p.val(d);else if("SELECT"===l)if(d&&d.indexOf("option")!==-1)p.append(d);else{var f=p.find('option[value="'+d+'"]');f.length&&p.val(d)}}i&&o(a),y++}),a.data("multipleInput").currentIndex++;var j=t.Event(e.afterAddRow);a.trigger(j,[v,m])}}},l=function(n){var r=n.closest(".multiple-input").first(),a=n.closest(".multiple-input-list__item"),l=r.data("multipleInput"),o=l.settings,s=p(r);if(s>o.min){var d=t.Event(e.beforeDeleteRow);if(r.trigger(d,[a,s]),d.result===!1)return;i&&a.find("input, select, textarea").each(function(e,n){u(t(n))}),a.fadeOut(300,function(){t(this).remove(),d=t.Event(e.afterDeleteRow),r.trigger(d,[a,s])})}},o=function(e){var n=s(e);if(null!==n){var i=t("#"+n),r=i.closest(".multiple-input").first(),a=i.closest("form");if(0!==r.length&&"undefined"==typeof a.yiiActiveForm("find",n)){var l=r.data("multipleInput"),o={},u=n.replace(/-\d+-([^\d]+)$/,"-$1");l.settings.attributes.hasOwnProperty(u)?o=l.settings.attributes[u]:(u=f(/-\d-/,"-",u),u=f(/-\d/,"",u),l.settings.attributes.hasOwnProperty(u)&&(o=l.settings.attributes[u])),a.yiiActiveForm("add",t.extend({},o,{id:n,input:"#"+n,container:".field-"+n}))}}},u=function(e){var n=s(e);if(null!==n){var i=t("#"+n).closest("form");0!==i.length&&i.yiiActiveForm("remove",n)}},s=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},p=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var n={};return e.closest("tr").find("td").each(function(e,i){t(i).find("input, select, textarea").each(function(e,i){var r=t(i),a=s(r),l=t("#"+a);n[a]=l.val()})}),n},f=function(t,e,n){return n instanceof String||"string"==typeof n?n.split(t).join(e):(console.warn("Call replaceAll for non-string value: "+n),n)}}(window.jQuery); \ No newline at end of file +!function(t){"use strict";t.fn.multipleInput=function(e){return o[e]?o[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):o.init.apply(this,arguments)};var e="afterInit",i="beforeAddRow",n="afterAddRow",l="beforeDeleteRow",r="afterDeleteRow",a={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},u=!1,o={init:function(i){if("object"==typeof i){var n=t.extend(!0,{},a,i||{}),l=t("#"+n.id),r=l.closest("form"),o=n.inputId;for(f in n.jsInit)window.eval(n.jsInit[f]);l.data("multipleInput",{settings:n,currentIndex:0}),l.on("click.multipleInput",".js-input-remove",(function(e){e.stopPropagation(),c(t(this))})),l.on("click.multipleInput",".js-input-plus",(function(e){e.stopPropagation(),p(t(this))})),l.on("click.multipleInput",".js-input-clone",(function(e){e.stopPropagation(),s(t(this))}));var f=0,m=t.Event(e),h=setInterval((function(){if("object"==typeof r.data("yiiActiveForm")){var e=r.yiiActiveForm("find",o),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,(function(t,e){-1===["id","input","container"].indexOf(t)&&(i[t]=e)})),n.showGeneralError||r.yiiActiveForm("remove",o)),t.each(n.attributes,(function(e,l){l=t.extend({},i,l),n.attributes[e]=l})),l.data("multipleInput").settings=n,l.find(".multiple-input-list").find("input, select, textarea").each((function(){d(t(this))})),l.data("multipleInput").currentIndex=v(l),u=!0,clearInterval(h),l.trigger(m)}else f++;(0===r.length||f>10)&&(l.data("multipleInput").currentIndex=v(l),u=!1,clearInterval(h),l.trigger(m))}),100)}else console.error("Options must be an object")},add:function(e){p(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),c(i)},clear:function(){t(this).find(".js-input-remove").each((function(){c(t(this))}))},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},s=function(e){let i=t(e).closest(".multiple-input").first().data("multipleInput").settings,n={};e.closest(".multiple-input-list__item").find("input, select, textarea").each((function(e,l){let r=t(l),a=m(r);if(a){let t=a.replace(i.inputId,"").replace(/-\d+-/,"");r.is(":checkbox")?(n.hasOwnProperty(t)||(n[t]=[]),r.is(":checked")&&n[t].push(r.val())):n[t]=r.val()}})),p(e,n)},p=function(e,l){l=l||{};let r=t(e).closest(".multiple-input").first(),a=r.data("multipleInput"),o=a.settings,s=r.children(".multiple-input-list").first();if(null!==o.max&&v(r)>=o.max)return;let p=a.currentIndex,c=h("{"+o.indexPlaceholder+"}",a.currentIndex,o.template),f=t(c);var g=t.Event(i);if(r.trigger(g,[f,p]),!1===g.result)return;f.find("input, select, textarea").each((function(e,i){let n=t(i),r=m(n);if(r){let t=r.replace(o.inputId,"").replace(/-\d+-/,"");if(l.hasOwnProperty(t)){let e=n.get(0).tagName,i=l[t];switch(e){case"INPUT":n.is(":checkbox")?-1!==i.indexOf(n.val())&&n.prop("checked",!0):n.val(i);break;case"TEXTAREA":n.val(i);break;case"SELECT":if(i&&-1!==i.indexOf("option"))n.append(i);else n.find('option[value="'+i+'"]').length&&n.val(i)}}}u&&d(n)})),o.prepend?f.hide().prependTo(s).fadeIn(300):f.hide().appendTo(s).fadeIn(300);let I=null;for(var x in o.jsTemplates)I=o.jsTemplates[x],I=h("{"+o.indexPlaceholder+"}",p,I),I=h("%7B"+o.indexPlaceholder+"%7D",p,I),window.eval(I);r.data("multipleInput").currentIndex++;var y=t.Event(n);r.trigger(y,[f,p])},c=function(e){var i=e.closest(".multiple-input").first(),n=e.closest(".multiple-input-list__item"),a=i.data("multipleInput").settings,o=v(i);if(o>a.min){var s=t.Event(l);if(i.trigger(s,[n,o]),!1===s.result)return;u&&n.find("input, select, textarea").each((function(e,i){f(t(i))})),n.fadeOut(300,(function(){t(this).remove(),s=t.Event(r),i.trigger(s,[n,o])}))}},d=function(e){var i=m(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!==l.length&&void 0===r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),u={},o=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(o)?u=a.settings.attributes[o]:(o=h(/-\d-/,"-",o),o=h(/-\d/,"",o),a.settings.attributes.hasOwnProperty(o)&&(u=a.settings.attributes[o])),r.yiiActiveForm("add",t.extend({},u,{id:i,input:"#"+i,container:".field-"+i}))}}},f=function(e){var i=m(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},m=function(t){var e=t.attr("id");return void 0===e&&(e=t.data("id")),void 0===e?null:e},v=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter((function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")})).length},h=function(t,e,i){return i instanceof String||"string"==typeof i?i.split(t).join(e):(console.warn("Call replaceAll for non-string value: "+i),i)}}(window.jQuery); \ No newline at end of file From 7c02a4e509cfdc948400ad490fdeaef1c4184891 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 6 Aug 2021 11:20:21 +0300 Subject: [PATCH 210/247] bump version to 2.25.0 --- CHANGELOG.md | 4 ++++ README.md | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc699b4..6f8db64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ Yii2 multiple input change log ============================== +2.26.0 (in development) +======================= + 2.25.0 (in development) ======================= +- rework cloning: fix #277, #351, #348 (unclead) 2.24.0 ====== diff --git a/README.md b/README.md index 6be0873..d8da173 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.24.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.25.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/package.json b/package.json index 1c38947..bb22d37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.24.0", + "version": "2.25.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 491199ecda6b62465e4b2ca9381b12d3c1199c5d Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 6 Aug 2021 11:21:45 +0300 Subject: [PATCH 211/247] fix typo --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f8db64..8970692 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,8 @@ Yii2 multiple input change log 2.26.0 (in development) ======================= -2.25.0 (in development) -======================= +2.25.0 +====== - rework cloning: fix #277, #351, #348 (unclead) 2.24.0 From c0c1ef64b8a4da266509ce44a9835c4e0bfddb72 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 7 Aug 2021 20:03:00 +0300 Subject: [PATCH 212/247] update documentation section --- README.md | 140 +----------------------------------------------------- 1 file changed, 1 insertion(+), 139 deletions(-) diff --git a/README.md b/README.md index d8da173..a12da19 100644 --- a/README.md +++ b/README.md @@ -50,148 +50,10 @@ use unclead\multipleinput\MultipleInput; ->label(false); ?> ``` -See more in [single column](https://github.com/unclead/yii2-multiple-input/wiki/Usage#one-column) - -## Advanced usage - -![Multiple columns example](https://raw.githubusercontent.com/unclead/yii2-multiple-input/master/resources/images/multiple-column.gif) - -For example you want to have an interface for manage user schedule. For simplicity we will store the schedule in json string. -In this case you can use yii2-multiple-input widget like in the following code - -```php -use unclead\multipleinput\MultipleInput; - -... - -field($model, 'schedule')->widget(MultipleInput::className(), [ - 'max' => 4, - 'columns' => [ - [ - 'name' => 'user_id', - 'type' => 'dropDownList', - 'title' => 'User', - 'defaultValue' => 1, - 'items' => [ - 1 => 'User 1', - 2 => 'User 2' - ] - ], - [ - 'name' => 'day', - 'type' => \kartik\date\DatePicker::className(), - 'title' => 'Day', - 'value' => function($data) { - return $data['day']; - }, - 'items' => [ - '0' => 'Saturday', - '1' => 'Monday' - ], - 'options' => [ - 'pluginOptions' => [ - 'format' => 'dd.mm.yyyy', - 'todayHighlight' => true - ] - ] - ], - [ - 'name' => 'priority', - 'title' => 'Priority', - 'enableError' => true, - 'options' => [ - 'class' => 'input-priority' - ] - ] - ] - ]); -?> -``` -See more in [multiple columns](https://github.com/unclead/yii2-multiple-input/wiki/Usage#multiple-columns) - -### Clone filled rows -![Clone button example](https://raw.githubusercontent.com/unclead/yii2-multiple-input/master/resources/images/clone-button.gif) -```php -use unclead\multipleinput\MultipleInput; - -... - -field($model, 'products')->widget(MultipleInput::className(), [ - 'max' => 10, - 'cloneButton' => true, - 'columns' => [ - [ - 'name' => 'product_id', - 'type' => 'dropDownList', - 'title' => 'Special Products', - 'defaultValue' => 1, - 'items' => [ - 1 => 'id: 1, price: $19.99, title: product1', - 2 => 'id: 2, price: $29.99, title: product2', - 3 => 'id: 3, price: $39.99, title: product3', - 4 => 'id: 4, price: $49.99, title: product4', - 5 => 'id: 5, price: $59.99, title: product5', - ], - ], - [ - 'name' => 'time', - 'type' => DateTimePicker::className(), - 'title' => 'due date', - 'defaultValue' => date('d-m-Y h:i') - ], - [ - 'name' => 'count', - 'title' => 'Count', - 'defaultValue' => 1, - 'enableError' => true, - 'options' => [ - 'type' => 'number', - 'class' => 'input-priority', - ] - ] - ] -])->label(false); -``` - -## Using other icon libraries -Multiple input and Tabular input widgets now support FontAwesome and indeed any other icon library you chose to integrate to your project. - -To take advantage of this, please proceed as follows: -1. Include the preferred icon library into your project. If you wish to use fontAwesome, you can use the included FontAwesomeAsset which will integrate the free fa from their CDN; -2. Add a mapping for your preferred icon library if its not in the iconMap array of the widget, like the following; -``` -public $iconMap = [ - 'glyphicons' => [ - 'drag-handle' => 'glyphicon glyphicon-menu-hamburger', - 'remove' => 'glyphicon glyphicon-remove', - 'add' => 'glyphicon glyphicon-plus', - 'clone' => 'glyphicon glyphicon-duplicate', - ], - 'fa' => [ - 'drag-handle' => 'fa fa-bars', - 'remove' => 'fa fa-times', - 'add' => 'fa fa-plus', - 'clone' => 'fa fa-files-o', - ], - 'my-amazing-icons' => [ - 'drag-handle' => 'my my-bars', - 'remove' => 'my my-times', - 'add' => 'my my-plus', - 'clone' => 'my my-files', - ] -]; -``` -3. Set the preffered icon source -``` - public $iconSource = 'my-amazing-icons'; -``` -If you do none of the above, the default behavior which assumes you are using glyphicons is retained. - ## Documentation -You can find a full version of documentation in [wiki](https://github.com/unclead/yii2-multiple-input/wiki/) - +You can find a full version of documentation [here](https://unclead.gitbook.io/yii2-multiple-input/) ## License From 490412d8c972e3e6e4281022ebac8652543f9b56 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 7 Aug 2021 17:18:05 +0000 Subject: [PATCH 213/247] GitBook: [master] 10 pages modified --- README.md | 50 +--- SUMMARY.md | 12 + clonning.md | 48 ++++ configuration.md | 145 +++++++++++ getting-started.md | 133 ++++++++++ javascript-events.md | 86 +++++++ renderers.md | 60 +++++ tips-and-tricks.md | 218 +++++++++++++++++ usage.md | 441 ++++++++++++++++++++++++++++++++++ using-other-icon-libraries.md | 40 +++ 10 files changed, 1187 insertions(+), 46 deletions(-) create mode 100644 SUMMARY.md create mode 100644 clonning.md create mode 100644 configuration.md create mode 100644 getting-started.md create mode 100644 javascript-events.md create mode 100644 renderers.md create mode 100644 tips-and-tricks.md create mode 100644 usage.md create mode 100644 using-other-icon-libraries.md diff --git a/README.md b/README.md index a12da19..8a9b853 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,18 @@ -# Yii2 Multiple input widget. -Yii2 widget for handle multiple inputs for an attribute of model and tabular input for batch of models. +# Installation -[![Latest Stable Version](https://poser.pugx.org/unclead/yii2-multiple-input/v/stable)](https://packagist.org/packages/unclead/yii2-multiple-input) -[![Total Downloads](https://poser.pugx.org/unclead/yii2-multiple-input/downloads)](https://packagist.org/packages/unclead/yii2-multiple-input) -[![Daily Downloads](https://poser.pugx.org/unclead/yii2-multiple-input/d/daily)](https://packagist.org/packages/unclead/yii2-multiple-input) -[![Latest Unstable Version](https://poser.pugx.org/unclead/yii2-multiple-input/v/unstable)](https://packagist.org/packages/unclead/yii2-multiple-input) -[![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) - -## Latest release -The latest stable version of the extension is v2.25.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions - -## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). Either run -``` +```text php composer.phar require unclead/yii2-multiple-input "~2.0" ``` or add -``` +```text "unclead/yii2-multiple-input": "~2.0" ``` -to the require section of your `composer.json` file. - -## Basic usage - -![Single column example](./resources/images/single-column.gif?raw=true) - -For example you want to have an ability of entering several emails of user on profile page. -In this case you can use yii2-multiple-input widget like in the following code - -```php -use unclead\multipleinput\MultipleInput; - -... - -field($model, 'emails')->widget(MultipleInput::className(), [ - 'max' => 6, - 'min' => 2, // should be at least 2 rows - 'allowEmptyList' => false, - 'enableGuessTitle' => true, - 'addButtonPosition' => MultipleInput::POS_HEADER, // show add button in the header - ]) - ->label(false); -?> -``` - -## Documentation - -You can find a full version of documentation [here](https://unclead.gitbook.io/yii2-multiple-input/) - -## License +to the `require`section of your `composer.json` file. -**yii2-multiple-input** is released under the BSD 3-Clause License. See the bundled [LICENSE.md](./LICENSE.md) for details. diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000..ae0d571 --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,12 @@ +# Table of contents + +* [Installation](README.md) +* [Getting started](getting-started.md) +* [Usage](usage.md) +* [Configuration](configuration.md) +* [Javascript events](javascript-events.md) +* [Using other icon libraries](using-other-icon-libraries.md) +* [Renderers](renderers.md) +* [Clonning](clonning.md) +* [Tips and tricks](tips-and-tricks.md) + diff --git a/clonning.md b/clonning.md new file mode 100644 index 0000000..0171e82 --- /dev/null +++ b/clonning.md @@ -0,0 +1,48 @@ +# Clonning + +![Clone button example](https://raw.githubusercontent.com/unclead/yii2-multiple-input/master/resources/images/clone-button.gif) + +```text +use unclead\multipleinput\MultipleInput; + +... + +$form->field($model, 'products')->widget(MultipleInput::className(), [ + 'max' => 10, + 'cloneButton' => true, + 'columns' => [ + [ + 'name' => 'product_id', + 'type' => 'dropDownList', + 'title' => 'Special Products', + 'defaultValue' => 1, + 'items' => [ + 1 => 'id: 1, price: $19.99, title: product1', + 2 => 'id: 2, price: $29.99, title: product2', + 3 => 'id: 3, price: $39.99, title: product3', + 4 => 'id: 4, price: $49.99, title: product4', + 5 => 'id: 5, price: $59.99, title: product5', + ], + ], + [ + 'name' => 'time', + 'type' => DateTimePicker::className(), + 'title' => 'due date', + 'defaultValue' => date('d-m-Y h:i') + ], + [ + 'name' => 'count', + 'title' => 'Count', + 'defaultValue' => 1, + 'enableError' => true, + 'options' => [ + 'type' => 'number', + 'class' => 'input-priority', + ] + ] + ] +])->label(false); +``` + + + diff --git a/configuration.md b/configuration.md new file mode 100644 index 0000000..64b264c --- /dev/null +++ b/configuration.md @@ -0,0 +1,145 @@ +# Configuration + +Widget support the following options that are additionally recognized over and above the configuration options in the InputWidget. + +## Base options + +**theme** _string_: specify the theme of the widget. Available 2 themes: + +* `default` with only widget css classes +* `bs` \(twitter bootstrap\) theme with additional BS ccs classes\). + +Default value is `bs` + +**max** _integer_: maximum number of rows. If not set will default to unlimited + +**min** _integer_: minimum number of rows. Set to `0` if you need the empty list in case you don't have any data + +**prepend** _boolean_: add a new row to the beginning of the list, not to the end + +**attributeOptions** _array_: client-side attribute options, e.g. enableAjaxValidation. You may use this property in case when you use widget without a model, since in this case widget is not able to detect client-side options automatically + +**addButtonPosition** _integer\|array_: the position\(s\) of `add` button. This can be `MultipleInput::POS_HEADER`, `MultipleInput::POS_ROW`, `MultipleInput::POS_ROW_BEGIN` or `MultipleInput::POS_FOOTER`. + +**addButtonOptions** _array_: the HTML options for `add` button. Can contains `class` and `label` keys + +**removeButtonOptions** _array_: the HTML options for `remove` button. Can contains `class` and `label` keys + +**data** _array_: array of values in case you use widget without model + +**models** _array_: the list of models. Required in case you use `TabularInput` widget + +**allowEmptyList** _boolean_: whether to allow the empty list + +**columnClass** _string_: the name of column class. You can specify your own class to extend base functionality. Defaults to `unclead\multipleinput\MultipleInputColumn` for `MultipleInput` and `unclead\multipleinput\TabularColumn` for `TabularInput`. + +**rendererClass** _string_: the name of renderer class. You can specify your own class to extend base functionality. Defaults to `unclead\multipleinput\renderers\TableRenderer`. + +**columns** _array_: the row columns configuration where you can set the properties which is described below + +**rowOptions** _array\|\Closure_: the HTML attributes for the table body rows. This can be either an array specifying the common HTML attributes for all body rows, or an anonymous function that returns an array of the HTML attributes. It should have the following signature: + +```php +function ($model, $index, $context) +``` + +* `$model`: the current data model being rendered +* `$index`: the zero-based index of the data model in the model array +* `$context`: the widget object + +**sortable** _bool_: whether need to enable sorting or not + +**modelClass** _string_: a class of model which is used to render `TabularInput`. You must specify this property when a list of `models` is empty. If this property is not specified the widget will detect it based on a class of `models` + +**cloneButton** _bool_: whether need to enable clone buttons or not + +**extraButtons** _string\|\Closure_: the HTML content that will be rendered after the buttons. It can be either string or an anonymous function that returns a string which will be treated as HTML content. It should have the following signature: + +```php +function ($model, $index, $context) +``` + +* `$model`: the current data model being rendered +* `$index`: the zero-based index of the data model in the model array +* `$context`: the MultipleInput widget object + +**layoutConfig** _array_: CSS grid classes for horizontal layout \(only supported for `ListRenderer` class\). This must be an array with these keys: + +* `'offsetClass'`: the offset grid class to append to the wrapper if no label is rendered +* `'labelClass'`: the label grid class +* `'wrapperClass'`: the wrapper grid class +* `'errorClass'`: the error grid class + +**showGeneralError** _bool_: whether need to show error message for main attribute, when you don't want to validate particular input and want to validate a filed in general. + +## Column options + +**name** _string_: input name. _Required options_ + +**type** _string_: type of the input. If not set will default to `textInput`. Read more about the types described below + +**title** _string_: the column title + +**value** _Closure_: you can set it to an anonymous function with the following signature: + +```php +function($data) {} +``` + +**defaultValue** _string_: default value of input + +**items** _array_\|_Closure_: the items for input with type dropDownList, listBox, checkboxList, radioList or anonymous function which return array of items and has the following signature: + +```php +function($data) {} +``` + +**options** _array_\|_Closure_: the HTML attributes for the input, you can set it as array or an anonymous function with the following signature: + +```php +function($data) {} +``` + +**headerOptions** _array_: the HTML attributes for the header cell + +**enableError** _boolean_: whether to render inline error for the input. Default to `false` + +**errorOptions** _array_: the HTMl attributes for the error tag + +**nameSuffix** _string_: the unique prefix for attribute's name to avoid id duplication e.g. in case of using several copies of the widget on a page and one column is a Select2 widget + +**tabindex** _integer_: use it to customize a form element `tabindex` + +## Input types + +Each column in a row can has their own type. Widget supports: + +* all yii2 html input types: + * `textInput` + * `dropDownList` + * `radioList` + * `textarea` + * For more detail look at [Html helper class](http://www.yiiframework.com/doc-2.0/yii-helpers-html.html) +* input widget \(widget that extends from `InputWidget` class\). For example, `yii\widgets\MaskedInput` +* `static` to output a static HTML content + +For using widget as column input you may use the following code: + +```php +echo $form->field($model, 'phones')->widget(MultipleInput::className(), [ +... + 'columns' => [ + ... + [ + 'name' => 'phones', + 'title' => $model->getAttributeLabel('phones'), + 'type' => \yii\widgets\MaskedInput::className(), + 'options' => [ + 'class' => 'input-phone', + 'mask' => '999-999-99-99', + ], + ], + ], +])->label(false); +``` + diff --git a/getting-started.md b/getting-started.md new file mode 100644 index 0000000..2e1daa9 --- /dev/null +++ b/getting-started.md @@ -0,0 +1,133 @@ +# Getting started + +I found this small guide here [https://stackoverflow.com/a/51849747](https://stackoverflow.com/a/51849747) and I think it is a good example of basic usage of the widget + +## Question + +I want to generate a different number of rows with values from my database. How can I do this? + +I can design my columns in view and edit data manually after a page was generated. But miss how to program the number of rows and their values in the view. + +My code is as follows: + +```text + field($User, 'User')->widget(MultipleInput::className(), [ + 'min' => 0, + 'max' => 4, + 'columns' => [ + [ + 'name' => 'name', + 'title' => 'Name', + 'type' => 'textInput', + 'options' => [ + 'onchange' => $onchange, + ], + ], + [ + 'name' => 'birth', + 'type' => \kartik\date\DatePicker::className(), + 'title' => 'Birth', + 'value' => function($data) { + return $data['day']; + }, + + 'options' => [ + 'pluginOptions' => [ + 'format' => 'dd.mm.yyyy', + 'todayHighlight' => true + ] + ] + ], + + ] + ])->label(false); +``` + +How can I make \(for example\) 8 rows with different values, and also have the ability to edit/remove/update some of them? + +## Answer + +You need to look into the documentation as it says that you need to assign a separate field into the model which will store all the schedule in form of JSON and then provide it back to the field when editing/updating the model. + +You have not added the appropriate model to verify how are you creating the field User in your given case above. so, I will try to create a simple example that will help you implement it in your scenario. + +For Example. + +You have to store a user in the database along with his favorite books. + +```text +User +id, name, email + +Books +id, name +``` + +Create a field/column in your User table with the name schedule of type text, you can write a migration or add manually. Add it to the rules in the User model as safe. + +like below + +```text +public function rules() { + return [ + ....//other rules + [ [ 'schedule'] , 'safe' ] + ]; +} +``` + +Add the widget to the newly created column in ActiveForm + +```text +echo $form->field($model,'schedule')->widget(MultipleInput::class,[ + 'max' => 4, + 'columns' => [ + [ + 'name' => 'book_id', + 'type' => 'dropDownList', + 'title' => 'Book', + 'items' => ArrayHelper::map( Books::find()->asArray()->all (),'id','name'), + ], + ] + +]); +``` + +When saving the User model convert the array to JSON string + +```text +if( Yii::$app->request->isPost && $model->load(Yii::$app->request->post()) ){ + $model->schedule = \yii\helpers\Json::encode($model->schedule); + $model->save(); +} +``` + +Override the afterFind\(\) of the User model to covert the JSON back to the array before loading the form + +```text +public function afterFind() { + parent::afterFind(); + $this->schedule = \yii\helpers\Json::decode($this->schedule); +} +``` + +Now when saved the schedule field against the current user will have the JSON for the selected rows for the books, as many selected, for example, if I saved three books having ids\(1,2,3\) then it will have JSON + +```text +{ + "0": { + "book_id": "1" + }, + "2": { + "book_id": "2" + }, + "3": { + "book_id": "3" + } +} +``` + +The above JSON will be converted to an array in the afterFind\(\) so that the widget loads the saved schedule when you EDIT the record. + +Now go to your update page or edit the newly saved model you will see the books loaded automatically. + diff --git a/javascript-events.md b/javascript-events.md new file mode 100644 index 0000000..5a31c25 --- /dev/null +++ b/javascript-events.md @@ -0,0 +1,86 @@ +# Javascript events + +This widget has following events: + +* `afterInit`: triggered after initialization +* `afterAddRow`: triggered after new row insertion +* `beforeDeleteRow`: triggered before the row removal +* `afterDeleteRow`: triggered after the row removal +* `afterDropRow`: triggered after drop the row when sortable mode is on + +**Example** + +```javascript +jQuery('#multiple-input').on('afterInit', function(){ + console.log('calls on after initialization event'); +}).on('beforeAddRow', function(e, row, currentIndex) { + console.log('calls on before add row event'); +}).on('afterAddRow', function(e, row, currentIndex) { + console.log('calls on after add row event'); +}).on('beforeDeleteRow', function(e, row, currentIndex){ + // row - HTML container of the current row for removal. + // For TableRenderer it is tr.multiple-input-list__item + console.log('calls on before remove row event.'); + return confirm('Are you sure you want to delete row?') +}).on('afterDeleteRow', function(e, row, currentIndex){ + console.log('calls on after remove row event'); + console.log(row); +}).on('afterDropRow', function(e, item){ + console.log('calls on after drop row', item); +}); +``` + +## JavaScript operations + +### add + +Adding new row with specified settings. + +Input arguments: + +* _object_ - values for inputs, can be filled with tags for dynamically added options for select \(for ajax select\). + +Example: + +```javascript +$('#multiple-input').multipleInput('add', {first: 10, second: ''}); +``` + +### remove + +Remove row with specified index. + +Input arguments: + +* _integer_ - row number for removing, if not specified then removes last row. + +Example: + +```javascript +$('#multiple-input').multipleInput('remove', 2); +``` + +### clear + +Remove all rows + +```javascript +$('#multiple-input').multipleInput('clear'); +``` + +### option + +Get or set a particular option + +Input arguments: + +* _string_ - a name of an option +* _mixed_ - a value of an option \(optional\). If specified will be used as a new value of an option; + +Example: + +```javascript +$('#multiple-input').multipleInput('option', 'max'); +$('#multiple-input').multipleInput('option', 'max', 10); +``` + diff --git a/renderers.md b/renderers.md new file mode 100644 index 0000000..67d33bb --- /dev/null +++ b/renderers.md @@ -0,0 +1,60 @@ +# Renderers + +Currently widget supports three type of renderers + +## TableRenderer + +![Table renderer](https://raw.githubusercontent.com/unclead/yii2-multiple-input/master/resources/images/table-renderer.jpg?raw=true) + +This renderer is enabled by default. + +## ListRenderer + +![List renderer](https://raw.githubusercontent.com/unclead/yii2-multiple-input/master/resources/images/list-renderer.jpg?raw=true) + +To enable this renderer you have to use an option `rendererClass` + +```php +field($model, 'schedule')->widget(MultipleInput::className(), [ + 'rendererClass' => \unclead\multipleinput\renderers\ListRenderer::className(), + 'max' => 4, + 'allowEmptyList' => true, + 'rowOptions' => function($model) { + $options = []; + + if ($model['priority'] > 1) { + $options['class'] = 'danger'; + } + return $options; + }, +``` + +## DivRenderer + +![List renderer](https://raw.githubusercontent.com/unclead/yii2-multiple-input/master/resources/images/list-renderer.jpg?raw=true) + +To enable this renderer you have to use an option `rendererClass` + +```php +field($model, 'schedule')->widget(MultipleInput::className(), [ + 'rendererClass' => \unclead\multipleinput\renderers\ListRenderer::class, + 'addButtonPosition' => MultipleInput::POS_ROW, // show add button inside of the row + 'extraButtons' => function ($model, $index, $context) { + if ($index === 0) { + return Html::tag('div', Yii::t('object', 'Add object'), ['class' => 'mi-after-add']); + } + + return Html::tag('div', Yii::t('object', 'Remove object'), ['class' => 'mi-after-remove']); + }, + 'layoutConfig' => [ + 'offsetClass' => 'col-md-offset-2', + 'labelClass' => 'col-md-2', + 'wrapperClass' => 'col-md-6', + 'errorClass' => 'col-md-offset-2 col-md-6', + 'buttonActionClass' => 'col-md-offset-1 col-md-2', + ], +... +``` + diff --git a/tips-and-tricks.md b/tips-and-tricks.md new file mode 100644 index 0000000..caa449c --- /dev/null +++ b/tips-and-tricks.md @@ -0,0 +1,218 @@ +# Tips and tricks + +## How to customize buttons + +You can customize `add` and `remove` buttons via `addButtonOptions` and `removeButtonOptions`. Here is a simple example of how you can use those options: + +```php + echo $form->field($model, 'emails')->widget(MultipleInput::className(), [ + 'max' => 5, + 'addButtonOptions' => [ + 'class' => 'btn btn-success', + 'label' => 'add' // also you can use html code + ], + 'removeButtonOptions' => [ + 'label' => 'remove' + ] + ]) + ->label(false); +``` + +## How to add content after the buttons + +You can add html content after `add` and `remove` buttons via `extraButtons`. + +```php + echo $form->field($model, 'field')->widget(MultipleInput::className(), [ + 'rendererClass' => \unclead\multipleinput\renderers\ListRenderer::class, + 'extraButtons' => function ($model, $index, $context) { + return Html::tag('span', '', ['class' => "btn-show-hide-{$index} glyphicon glyphicon-eye-open btn btn-info"]); + }, + ]) + ->label(false); +``` + +## Work with an empty list + +In some cases, you need to have the ability to delete all rows in the list. For this purpose, you can use option `allowEmptyList` like in the example below: + +```php + echo $form->field($model, 'emails')->widget(MultipleInput::className(), [ + 'max' => 5, + 'allowEmptyList' => true + ]) + ->label(false); +``` + +Also, you can set `0` in `min` option if you don't need the first blank row when data is empty. + +## Guess column title + +Sometimes you can use the widget without defining columns but you want to have the column header of the table. In this case, you can use the option `enableGuessTitle` like in the example below: + +```php + echo $form->field($model, 'emails')->widget(MultipleInput::className(), [ + 'max' => 5, + 'allowEmptyList' => true, + 'enableGuessTitle' => true + ]) + ->label(false); +``` + +## Ajax loading of a widget + +Assume you want to load a widget via ajax and then show it inside the modal window. In this case, you MUST: + +* Ensure that you specified the ID of the widget otherwise the widget will get a random ID and it can be the same as the ID of others elements on the page. +* Ensure that you use the widget inside ActiveForm because it works incorrectly in this case. + +## Use of a widget's placeholder + +You can use a placeholder `{multiple_index}` in a widget configuration, e.g. for implementation of dependent drop-down lists. + +```php + field($model, 'field')->widget(MultipleInput::className(), [ + 'id' => 'my_id', + 'allowEmptyList' => false, + 'rowOptions' => [ + 'id' => 'row{multiple_index_my_id}', + ], + 'columns' => [ + [ + 'name' => 'category', + 'type' => 'dropDownList', + 'title' => 'Category', + 'defaultValue' => '1', + 'items' => [ + '1' => 'Test 1', + '2' => 'Test 2', + '3' => 'Test 3', + '4' => 'Test 4', + ], + 'options' => [ + 'onchange' => <<< JS +$.post("list?id=" + $(this).val(), function(data){ + console.log(data); + $("select#subcat-{multiple_index_my_id}").html(data); +}); +JS + ], + ], + [ + 'name' => 'subcategory', + 'type' => 'dropDownList', + 'title' => 'Subcategory', + 'items' => [], + 'options'=> [ + 'id' => 'subcat-{multiple_index_my_id}' + ], + ], + ] + ]); + ?> +``` + +**Important** Ensure that you added ID of widget to a base placeholder `multiple_index` + +## Custom index of the row + +Assume that you want to set a specific index for each row. In this case, you can pass the `data` attribute as an associative array as in the example below: + +```php + field($model, 'field')->widget(MultipleInput::className(), [ + 'allowEmptyList' => false, + 'data' => [ + 3 => [ + 'day' => '27.02.2015', + 'user_id' => 31, + 'priority' => 1, + 'enable' => 1 + ], + + 'some-key' => [ + 'day' => '27.02.2015', + 'user_id' => 33, + 'priority' => 2, + 'enable' => 0 + ], + ] + + ... +``` + +## Embedded MultipleInput widget + +You can use nested `MultipleInput` as in the example below: + +```php +echo MultipleInput::widget([ + 'model' => $model, + 'attribute' => 'questions', + 'attributeOptions' => $commonAttributeOptions, + 'columns' => [ + [ + 'name' => 'question', + 'type' => 'textarea', + ], + [ + 'name' => 'answers', + 'type' => MultipleInput::class, + 'options' => [ + 'attributeOptions' => $commonAttributeOptions, + 'columns' => [ + [ + 'name' => 'right', + 'type' => MultipleInputColumn::TYPE_CHECKBOX + ], + [ + 'name' => 'answer' + ] + ] + ] + ] + ], +]); +``` + +But in this case, you have to pass `attributeOptions` to the widget otherwise, you will not be able to use ajax or client-side validation of data. + +## Client validation + +Apart from ajax validation, you can use client validation but in this case, you MUST set property `form`. Also, ensure that you set `enableClientValidation` to `true` value in property `attributeOptions`. If you want to use client validation for a particular column you can use the property `attributeOptions`. An example of using client validation is listed below: + +```php + $models, + 'form' => $form, + 'attributeOptions' => [ + 'enableAjaxValidation' => true, + 'enableClientValidation' => false, + 'validateOnChange' => false, + 'validateOnSubmit' => true, + 'validateOnBlur' => false, + ], + 'columns' => [ + [ + 'name' => 'id', + 'type' => TabularColumn::TYPE_HIDDEN_INPUT + ], + [ + 'name' => 'title', + 'title' => 'Title', + 'type' => TabularColumn::TYPE_TEXT_INPUT, + 'attributeOptions' => [ + 'enableClientValidation' => true, + 'validateOnChange' => true, + ], + 'enableError' => true + ], + [ + 'name' => 'description', + 'title' => 'Description', + ], + ], +]) ?> +``` + +In the example above we use client validation for column `title` and ajax validation for column `description`. As you can seee we also enabled `validateOnChange` for column `title` thus you can use all client-side options from the `ActiveField` class. + diff --git a/usage.md b/usage.md new file mode 100644 index 0000000..d610429 --- /dev/null +++ b/usage.md @@ -0,0 +1,441 @@ +# Usage + +{% hint style="info" %} +You can find the source code of examples [here](https://github.com/unclead/yii2-multiple-input/tree/master/examples) +{% endhint %} + +## One column + +![Single column example](https://raw.githubusercontent.com/unclead/yii2-multiple-input/master/resources/images/single-column.gif) + +For example, your application contains the model `User` that has the related model `UserEmail` You can add virtual attribute `emails` for collect emails from a form and then you can save them to the database. + +In this case, you can use `yii2-multiple-input` widget for supporting multiple inputs how to describe below. + +First of all, we have to declare a virtual attribute in the model + +```php +class ExampleModel extends Model +{ + /** + * @var array virtual attribute for keeping emails + */ + public $emails; +``` + +Then we have to use `MultipleInput` widget for rendering form field in the view file + +```php +use yii\bootstrap\ActiveForm; +use unclead\multipleinput\MultipleInput; +use unclead\multipleinput\examples\models\ExampleModel; +use yii\helpers\Html; + +/* @var $this \yii\base\View */ +/* @var $model ExampleModel */ +?> + + true, + 'enableClientValidation' => false, + 'validateOnChange' => false, + 'validateOnSubmit' => true, + 'validateOnBlur' => false, +]);?> + +field($model, 'emails')->widget(MultipleInput::className(), [ + 'max' => 4, + ]); +?> + 'btn btn-success']);?> + +``` + +Options `max` means that a user is able to input only 4 emails + +For validation emails, you can use the following code + +```php + /** + * Email validation. + * + * @param $attribute + */ + public function validateEmails($attribute) + { + $items = $this->$attribute; + + if (!is_array($items)) { + $items = []; + } + + foreach ($items as $index => $item) { + $validator = new EmailValidator(); + $error = null; + $validator->validate($item, $error); + if (!empty($error)) { + $key = $attribute . '[' . $index . ']'; + $this->addError($key, $error); + } + } + } +``` + +## Multiple columns + +![Multiple columns example](https://raw.githubusercontent.com/unclead/yii2-multiple-input/master/resources/images/multiple-column.gif) + +For example, you want to have an interface for manage a user schedule. For simplicity, we will store the schedule in json string. + +In this case, you can use `yii2-multiple-input` widget for supporting multiple inputs how to describe below. + +Our test model can look like as the following snippet + +```php +class ExampleModel extends Model +{ + public $schedule; + + public function init() + { + parent::init(); + + $this->schedule = [ + [ + 'day' => '27.02.2015', + 'user_id' => 1, + 'priority' => 1 + ], + [ + 'day' => '27.02.2015', + 'user_id' => 2, + 'priority' => 2 + ], + ]; + } +``` + +Then we have to use `MultipleInput` widget for rendering form field in the view file + +```php +use yii\bootstrap\ActiveForm; +use unclead\multipleinput\MultipleInput; +use unclead\multipleinput\examples\models\ExampleModel; +use yii\helpers\Html; + +/* @var $this \yii\base\View */ +/* @var $model ExampleModel */ +?> + + true, + 'enableClientValidation' => false, + 'validateOnChange' => false, + 'validateOnSubmit' => true, + 'validateOnBlur' => false, +]);?> + +field($model, 'schedule')->widget(MultipleInput::className(), [ + 'max' => 4, + 'columns' => [ + [ + 'name' => 'user_id', + 'type' => 'dropDownList', + 'title' => 'User', + 'defaultValue' => 1, + 'items' => [ + 1 => 'User 1', + 2 => 'User 2' + ] + ], + [ + 'name' => 'day', + 'type' => \kartik\date\DatePicker::className(), + 'title' => 'Day', + 'value' => function($data) { + return $data['day']; + }, + 'items' => [ + '0' => 'Saturday', + '1' => 'Monday' + ], + 'options' => [ + 'pluginOptions' => [ + 'format' => 'dd.mm.yyyy', + 'todayHighlight' => true + ] + ] + ], + [ + 'name' => 'priority', + 'title' => 'Priority', + 'enableError' => true, + 'options' => [ + 'class' => 'input-priority' + ] + ] + ] + ]); +?> + 'btn btn-success']);?> + +``` + +For validation of the schedule you can use the following code + +```php + public function validateSchedule($attribute) + { + $requiredValidator = new RequiredValidator(); + + foreach($this->$attribute as $index => $row) { + $error = null; + $requiredValidator->validate($row['priority'], $error); + if (!empty($error)) { + $key = $attribute . '[' . $index . '][priority]'; + $this->addError($key, $error); + } + } + } +``` + +For example, you keep some data in json format in an attribute of a model. Imagine that it is an abstract user schedule with keys: user\_id, day, priority + +On the edit page, you want to be able to manage this schedule and you can you yii2-multiple-input widget like in the following code + +```php +use unclead\multipleinput\MultipleInput; + +... + +field($model, 'schedule')->widget(MultipleInput::className(), [ + 'max' => 4, + 'columns' => [ + [ + 'name' => 'user_id', + 'type' => 'dropDownList', + 'title' => 'User', + 'defaultValue' => 1, + 'items' => [ + 1 => 'User 1', + 2 => 'User 2' + ] + ], + [ + 'name' => 'day', + 'type' => \kartik\date\DatePicker::className(), + 'title' => 'Day', + 'value' => function($data) { + return $data['day']; + }, + 'items' => [ + '0' => 'Saturday', + '1' => 'Monday' + ], + 'options' => [ + 'pluginOptions' => [ + 'format' => 'dd.mm.yyyy', + 'todayHighlight' => true + ] + ], + 'headerOptions' => [ + 'style' => 'width: 250px;', + 'class' => 'day-css-class' + ] + ], + [ + 'name' => 'priority', + 'enableError' => true, + 'title' => 'Priority', + 'options' => [ + 'class' => 'input-priority' + ] + ], + [ + 'name' => 'comment', + 'type' => 'static', + 'value' => function($data) { + return Html::tag('span', 'static content', ['class' => 'label label-info']); + }, + 'headerOptions' => [ + 'style' => 'width: 70px;', + ] + ] + ] + ]); +?> +``` + +## Tabular input + +For example, you want to have an interface for manage some abstract items via tabular input. + +In this case, you can use `yii2-multiple-input` widget for supporting tabular input how to describe below. + +Our test model can look like as the following snippet + +```php +namespace unclead\multipleinput\examples\models; + +use Yii; +use yii\base\Model; +// you have to install https://github.com/vova07/yii2-fileapi-widget +use vova07\fileapi\behaviors\UploadBehavior; + +/** + * Class Item + * @package unclead\multipleinput\examples\models + */ +class Item extends Model +{ + public $title; + public $description; + public $file; + public $date; + + public function behaviors() + { + return [ + 'uploadBehavior' => [ + 'class' => UploadBehavior::className(), + 'attributes' => [ + 'file' => [ + 'path' => Yii::getAlias('@webroot') . '/images/', + 'tempPath' => Yii::getAlias('@webroot') . '/images/tmp/', + 'url' => '/images/' + ], + ] + ] + ]; + } + + public function rules() + { + return [ + [['title', 'description'], 'required'], + ['file', 'safe'] + ]; + } +} +``` + +Then we have to use `TabularInput` widget for rendering form field in the view file + +Since version **2.18.0** you can configure `columnOptions` also. + +```php + + + 'tabular-form', + 'enableAjaxValidation' => true, + 'enableClientValidation' => false, + 'validateOnChange' => false, + 'validateOnSubmit' => true, + 'validateOnBlur' => false, + 'options' => [ + 'enctype' => 'multipart/form-data' + ] +]) ?> + + $models, + 'attributeOptions' => [ + 'enableAjaxValidation' => true, + 'enableClientValidation' => false, + 'validateOnChange' => false, + 'validateOnSubmit' => true, + 'validateOnBlur' => false, + ], + 'columns' => [ + [ + 'name' => 'title', + 'title' => 'Title', + 'type' => \unclead\multipleinput\MultipleInputColumn::TYPE_TEXT_INPUT, + ], + [ + 'name' => 'description', + 'title' => 'Description', + ], + [ + 'name' => 'file', + 'title' => 'File', + 'type' => \vova07\fileapi\Widget::className(), + 'options' => [ + 'settings' => [ + 'url' => ['site/fileapi-upload'] + ] + ], + 'columnOptions' => [ + 'style' => 'width: 250px;', + 'class' => 'custom-css-class' + ] + ], + [ + 'name' => 'date', + 'type' => \kartik\date\DatePicker::className(), + 'title' => 'Day', + 'options' => [ + 'pluginOptions' => [ + 'format' => 'dd.mm.yyyy', + 'todayHighlight' => true + ] + ], + 'headerOptions' => [ + 'style' => 'width: 250px;', + 'class' => 'day-css-class' + ] + ], + ], +]) ?> + + + 'btn btn-success']);?> + +``` + +Your action can look like the following code + +```php +/** + * Class TabularInputAction + * @package unclead\multipleinput\examples\actions + */ +class TabularInputAction extends Action +{ + public function run() + { + Yii::setAlias('@unclead-examples', realpath(__DIR__ . '/../')); + + $models = [new Item()]; + $request = Yii::$app->getRequest(); + if ($request->isPost && $request->post('ajax') !== null) { + $data = Yii::$app->request->post('Item', []); + foreach (array_keys($data) as $index) { + $models[$index] = new Item(); + } + Model::loadMultiple($models, Yii::$app->request->post()); + Yii::$app->response->format = Response::FORMAT_JSON; + $result = ActiveForm::validateMultiple($models); + return $result; + } + + if (Model::loadMultiple($models, Yii::$app->request->post())) { + // your magic + } + + + return $this->controller->render('@unclead-examples/views/tabular-input.php', ['models' => $models]); + } +} +``` + diff --git a/using-other-icon-libraries.md b/using-other-icon-libraries.md new file mode 100644 index 0000000..e26cf1b --- /dev/null +++ b/using-other-icon-libraries.md @@ -0,0 +1,40 @@ +# Using other icon libraries + +Multiple input and Tabular input widgets now support FontAwesome and indeed any other icon library you chose to integrate into your project. + +To take advantage of this, please proceed as follows: + +1. Include the preferred icon library in your project. If you wish to use **font awesome**, you can use the included FontAwesomeAsset which will integrate the free fa from their CDN; +2. Add a mapping for your preferred icon library if it is not in the `iconMap` array of the widget, like the following; + +```text +public $iconMap = [ + 'glyphicons' => [ + 'drag-handle' => 'glyphicon glyphicon-menu-hamburger', + 'remove' => 'glyphicon glyphicon-remove', + 'add' => 'glyphicon glyphicon-plus', + 'clone' => 'glyphicon glyphicon-duplicate', + ], + 'fa' => [ + 'drag-handle' => 'fa fa-bars', + 'remove' => 'fa fa-times', + 'add' => 'fa fa-plus', + 'clone' => 'fa fa-files-o', + ], + 'my-amazing-icons' => [ + 'drag-handle' => 'my my-bars', + 'remove' => 'my my-times', + 'add' => 'my my-plus', + 'clone' => 'my my-files', + ] +]; +``` + +1. Set the preferred icon source + +```text + public $iconSource = 'my-amazing-icons'; +``` + +If you do none of the above, the default behavior which assumes you are using `glyphicons` is retained. + From de244e5b7fa47c37aa0a448721818d8887be10bc Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 7 Aug 2021 20:21:27 +0300 Subject: [PATCH 214/247] move gitbook content to docs folder --- .gitbook.yaml | 1 + SUMMARY.md => docs/SUMMARY.md | 0 clonning.md => docs/clonning.md | 0 configuration.md => docs/configuration.md | 0 getting-started.md => docs/getting-started.md | 0 javascript-events.md => docs/javascript-events.md | 0 renderers.md => docs/renderers.md | 0 tips-and-tricks.md => docs/tips-and-tricks.md | 0 usage.md => docs/usage.md | 0 .../using-other-icon-libraries.md | 0 10 files changed, 1 insertion(+) create mode 100644 .gitbook.yaml rename SUMMARY.md => docs/SUMMARY.md (100%) rename clonning.md => docs/clonning.md (100%) rename configuration.md => docs/configuration.md (100%) rename getting-started.md => docs/getting-started.md (100%) rename javascript-events.md => docs/javascript-events.md (100%) rename renderers.md => docs/renderers.md (100%) rename tips-and-tricks.md => docs/tips-and-tricks.md (100%) rename usage.md => docs/usage.md (100%) rename using-other-icon-libraries.md => docs/using-other-icon-libraries.md (100%) diff --git a/.gitbook.yaml b/.gitbook.yaml new file mode 100644 index 0000000..e454be0 --- /dev/null +++ b/.gitbook.yaml @@ -0,0 +1 @@ +root: ./docs/ diff --git a/SUMMARY.md b/docs/SUMMARY.md similarity index 100% rename from SUMMARY.md rename to docs/SUMMARY.md diff --git a/clonning.md b/docs/clonning.md similarity index 100% rename from clonning.md rename to docs/clonning.md diff --git a/configuration.md b/docs/configuration.md similarity index 100% rename from configuration.md rename to docs/configuration.md diff --git a/getting-started.md b/docs/getting-started.md similarity index 100% rename from getting-started.md rename to docs/getting-started.md diff --git a/javascript-events.md b/docs/javascript-events.md similarity index 100% rename from javascript-events.md rename to docs/javascript-events.md diff --git a/renderers.md b/docs/renderers.md similarity index 100% rename from renderers.md rename to docs/renderers.md diff --git a/tips-and-tricks.md b/docs/tips-and-tricks.md similarity index 100% rename from tips-and-tricks.md rename to docs/tips-and-tricks.md diff --git a/usage.md b/docs/usage.md similarity index 100% rename from usage.md rename to docs/usage.md diff --git a/using-other-icon-libraries.md b/docs/using-other-icon-libraries.md similarity index 100% rename from using-other-icon-libraries.md rename to docs/using-other-icon-libraries.md From de103710eec55531f5890adf93e084b13cbacb90 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 7 Aug 2021 20:35:51 +0300 Subject: [PATCH 215/247] restore original README.md --- README.md | 188 +++++++++++++++++++++++++++++++++++++++++++++++-- docs/README.md | 18 +++++ 2 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 docs/README.md diff --git a/README.md b/README.md index 8a9b853..e8d3380 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,198 @@ -# Installation +# Yii2 Multiple input widget. +Yii2 widget for handle multiple inputs for an attribute of model and tabular input for batch of models. +[![Latest Stable Version](https://poser.pugx.org/unclead/yii2-multiple-input/v/stable)](https://packagist.org/packages/unclead/yii2-multiple-input) +[![Total Downloads](https://poser.pugx.org/unclead/yii2-multiple-input/downloads)](https://packagist.org/packages/unclead/yii2-multiple-input) +[![Daily Downloads](https://poser.pugx.org/unclead/yii2-multiple-input/d/daily)](https://packagist.org/packages/unclead/yii2-multiple-input) +[![Latest Unstable Version](https://poser.pugx.org/unclead/yii2-multiple-input/v/unstable)](https://packagist.org/packages/unclead/yii2-multiple-input) +[![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) + +## Latest release +The latest stable version of the extension is v2.25.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions + +## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). Either run -```text +``` php composer.phar require unclead/yii2-multiple-input "~2.0" ``` or add -```text +``` "unclead/yii2-multiple-input": "~2.0" ``` -to the `require`section of your `composer.json` file. +to the require section of your `composer.json` file. + +## Basic usage + +![Single column example](./resources/images/single-column.gif?raw=true) + +For example you want to have an ability of entering several emails of user on profile page. +In this case you can use yii2-multiple-input widget like in the following code + +```php +use unclead\multipleinput\MultipleInput; + +... + +field($model, 'emails')->widget(MultipleInput::className(), [ + 'max' => 6, + 'min' => 2, // should be at least 2 rows + 'allowEmptyList' => false, + 'enableGuessTitle' => true, + 'addButtonPosition' => MultipleInput::POS_HEADER, // show add button in the header + ]) + ->label(false); +?> +``` +See more in [single column](https://github.com/unclead/yii2-multiple-input/wiki/Usage#one-column) + +## Advanced usage + +![Multiple columns example](https://raw.githubusercontent.com/unclead/yii2-multiple-input/master/resources/images/multiple-column.gif) + +For example you want to have an interface for manage user schedule. For simplicity we will store the schedule in json string. +In this case you can use yii2-multiple-input widget like in the following code + +```php +use unclead\multipleinput\MultipleInput; + +... + +field($model, 'schedule')->widget(MultipleInput::className(), [ + 'max' => 4, + 'columns' => [ + [ + 'name' => 'user_id', + 'type' => 'dropDownList', + 'title' => 'User', + 'defaultValue' => 1, + 'items' => [ + 1 => 'User 1', + 2 => 'User 2' + ] + ], + [ + 'name' => 'day', + 'type' => \kartik\date\DatePicker::className(), + 'title' => 'Day', + 'value' => function($data) { + return $data['day']; + }, + 'items' => [ + '0' => 'Saturday', + '1' => 'Monday' + ], + 'options' => [ + 'pluginOptions' => [ + 'format' => 'dd.mm.yyyy', + 'todayHighlight' => true + ] + ] + ], + [ + 'name' => 'priority', + 'title' => 'Priority', + 'enableError' => true, + 'options' => [ + 'class' => 'input-priority' + ] + ] + ] + ]); +?> +``` +See more in [multiple columns](https://github.com/unclead/yii2-multiple-input/wiki/Usage#multiple-columns) + +### Clone filled rows +![Clone button example](https://raw.githubusercontent.com/unclead/yii2-multiple-input/master/resources/images/clone-button.gif) +```php +use unclead\multipleinput\MultipleInput; + +... + +field($model, 'products')->widget(MultipleInput::className(), [ + 'max' => 10, + 'cloneButton' => true, + 'columns' => [ + [ + 'name' => 'product_id', + 'type' => 'dropDownList', + 'title' => 'Special Products', + 'defaultValue' => 1, + 'items' => [ + 1 => 'id: 1, price: $19.99, title: product1', + 2 => 'id: 2, price: $29.99, title: product2', + 3 => 'id: 3, price: $39.99, title: product3', + 4 => 'id: 4, price: $49.99, title: product4', + 5 => 'id: 5, price: $59.99, title: product5', + ], + ], + [ + 'name' => 'time', + 'type' => DateTimePicker::className(), + 'title' => 'due date', + 'defaultValue' => date('d-m-Y h:i') + ], + [ + 'name' => 'count', + 'title' => 'Count', + 'defaultValue' => 1, + 'enableError' => true, + 'options' => [ + 'type' => 'number', + 'class' => 'input-priority', + ] + ] + ] +])->label(false); +``` + +## Using other icon libraries +Multiple input and Tabular input widgets now support FontAwesome and indeed any other icon library you chose to integrate to your project. + +To take advantage of this, please proceed as follows: +1. Include the preferred icon library into your project. If you wish to use fontAwesome, you can use the included FontAwesomeAsset which will integrate the free fa from their CDN; +2. Add a mapping for your preferred icon library if its not in the iconMap array of the widget, like the following; +``` +public $iconMap = [ + 'glyphicons' => [ + 'drag-handle' => 'glyphicon glyphicon-menu-hamburger', + 'remove' => 'glyphicon glyphicon-remove', + 'add' => 'glyphicon glyphicon-plus', + 'clone' => 'glyphicon glyphicon-duplicate', + ], + 'fa' => [ + 'drag-handle' => 'fa fa-bars', + 'remove' => 'fa fa-times', + 'add' => 'fa fa-plus', + 'clone' => 'fa fa-files-o', + ], + 'my-amazing-icons' => [ + 'drag-handle' => 'my my-bars', + 'remove' => 'my my-times', + 'add' => 'my my-plus', + 'clone' => 'my my-files', + ] +]; +``` +3. Set the preffered icon source +``` + public $iconSource = 'my-amazing-icons'; +``` +If you do none of the above, the default behavior which assumes you are using glyphicons is retained. + + +## Documentation + +You can find a full version of documentation in [wiki](https://github.com/unclead/yii2-multiple-input/wiki/) + + +## License +**yii2-multiple-input** is released under the BSD 3-Clause License. See the bundled [LICENSE.md](./LICENSE.md) for details. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..8a9b853 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,18 @@ +# Installation + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +```text +php composer.phar require unclead/yii2-multiple-input "~2.0" +``` + +or add + +```text +"unclead/yii2-multiple-input": "~2.0" +``` + +to the `require`section of your `composer.json` file. + From eaf2c200fc9d08d4c8b923b08ee8759534402c58 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 7 Aug 2021 20:37:40 +0300 Subject: [PATCH 216/247] fix README --- README.md | 140 +----------------------------------------------------- 1 file changed, 1 insertion(+), 139 deletions(-) diff --git a/README.md b/README.md index e8d3380..86e7acb 100644 --- a/README.md +++ b/README.md @@ -50,148 +50,10 @@ use unclead\multipleinput\MultipleInput; ->label(false); ?> ``` -See more in [single column](https://github.com/unclead/yii2-multiple-input/wiki/Usage#one-column) - -## Advanced usage - -![Multiple columns example](https://raw.githubusercontent.com/unclead/yii2-multiple-input/master/resources/images/multiple-column.gif) - -For example you want to have an interface for manage user schedule. For simplicity we will store the schedule in json string. -In this case you can use yii2-multiple-input widget like in the following code - -```php -use unclead\multipleinput\MultipleInput; - -... - -field($model, 'schedule')->widget(MultipleInput::className(), [ - 'max' => 4, - 'columns' => [ - [ - 'name' => 'user_id', - 'type' => 'dropDownList', - 'title' => 'User', - 'defaultValue' => 1, - 'items' => [ - 1 => 'User 1', - 2 => 'User 2' - ] - ], - [ - 'name' => 'day', - 'type' => \kartik\date\DatePicker::className(), - 'title' => 'Day', - 'value' => function($data) { - return $data['day']; - }, - 'items' => [ - '0' => 'Saturday', - '1' => 'Monday' - ], - 'options' => [ - 'pluginOptions' => [ - 'format' => 'dd.mm.yyyy', - 'todayHighlight' => true - ] - ] - ], - [ - 'name' => 'priority', - 'title' => 'Priority', - 'enableError' => true, - 'options' => [ - 'class' => 'input-priority' - ] - ] - ] - ]); -?> -``` -See more in [multiple columns](https://github.com/unclead/yii2-multiple-input/wiki/Usage#multiple-columns) - -### Clone filled rows -![Clone button example](https://raw.githubusercontent.com/unclead/yii2-multiple-input/master/resources/images/clone-button.gif) -```php -use unclead\multipleinput\MultipleInput; - -... - -field($model, 'products')->widget(MultipleInput::className(), [ - 'max' => 10, - 'cloneButton' => true, - 'columns' => [ - [ - 'name' => 'product_id', - 'type' => 'dropDownList', - 'title' => 'Special Products', - 'defaultValue' => 1, - 'items' => [ - 1 => 'id: 1, price: $19.99, title: product1', - 2 => 'id: 2, price: $29.99, title: product2', - 3 => 'id: 3, price: $39.99, title: product3', - 4 => 'id: 4, price: $49.99, title: product4', - 5 => 'id: 5, price: $59.99, title: product5', - ], - ], - [ - 'name' => 'time', - 'type' => DateTimePicker::className(), - 'title' => 'due date', - 'defaultValue' => date('d-m-Y h:i') - ], - [ - 'name' => 'count', - 'title' => 'Count', - 'defaultValue' => 1, - 'enableError' => true, - 'options' => [ - 'type' => 'number', - 'class' => 'input-priority', - ] - ] - ] -])->label(false); -``` - -## Using other icon libraries -Multiple input and Tabular input widgets now support FontAwesome and indeed any other icon library you chose to integrate to your project. - -To take advantage of this, please proceed as follows: -1. Include the preferred icon library into your project. If you wish to use fontAwesome, you can use the included FontAwesomeAsset which will integrate the free fa from their CDN; -2. Add a mapping for your preferred icon library if its not in the iconMap array of the widget, like the following; -``` -public $iconMap = [ - 'glyphicons' => [ - 'drag-handle' => 'glyphicon glyphicon-menu-hamburger', - 'remove' => 'glyphicon glyphicon-remove', - 'add' => 'glyphicon glyphicon-plus', - 'clone' => 'glyphicon glyphicon-duplicate', - ], - 'fa' => [ - 'drag-handle' => 'fa fa-bars', - 'remove' => 'fa fa-times', - 'add' => 'fa fa-plus', - 'clone' => 'fa fa-files-o', - ], - 'my-amazing-icons' => [ - 'drag-handle' => 'my my-bars', - 'remove' => 'my my-times', - 'add' => 'my my-plus', - 'clone' => 'my my-files', - ] -]; -``` -3. Set the preffered icon source -``` - public $iconSource = 'my-amazing-icons'; -``` -If you do none of the above, the default behavior which assumes you are using glyphicons is retained. - ## Documentation -You can find a full version of documentation in [wiki](https://github.com/unclead/yii2-multiple-input/wiki/) - +You can find a full version of documentation [here](https://unclead.gitbook.io/yii2-multiple-input/) ## License From 210a65c91ed1b28fd058dbfbc3097b66ba2cb75d Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 7 Aug 2021 20:54:06 +0300 Subject: [PATCH 217/247] update documentation --- docs/README.md | 18 ------------------ docs/SUMMARY.md | 2 +- docs/installation.md | 18 ++++++++++++++++++ 3 files changed, 19 insertions(+), 19 deletions(-) create mode 100644 docs/installation.md diff --git a/docs/README.md b/docs/README.md index 8a9b853..e69de29 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,18 +0,0 @@ -# Installation - -The preferred way to install this extension is through [composer](http://getcomposer.org/download/). - -Either run - -```text -php composer.phar require unclead/yii2-multiple-input "~2.0" -``` - -or add - -```text -"unclead/yii2-multiple-input": "~2.0" -``` - -to the `require`section of your `composer.json` file. - diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index ae0d571..79ef8cd 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -1,6 +1,6 @@ # Table of contents -* [Installation](README.md) +* [Installation](installation.md) * [Getting started](getting-started.md) * [Usage](usage.md) * [Configuration](configuration.md) diff --git a/docs/installation.md b/docs/installation.md new file mode 100644 index 0000000..8a9b853 --- /dev/null +++ b/docs/installation.md @@ -0,0 +1,18 @@ +# Installation + +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). + +Either run + +```text +php composer.phar require unclead/yii2-multiple-input "~2.0" +``` + +or add + +```text +"unclead/yii2-multiple-input": "~2.0" +``` + +to the `require`section of your `composer.json` file. + From f65c0d6082d685ab85b523ae7940b6fba96728bb Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 14 Aug 2021 16:09:43 +0300 Subject: [PATCH 218/247] experiment with GitHub pages --- .github/workflow/main.yml | 18 ++++++++++++++++++ docs/index.md | 11 +++++++++++ mkdocs.yml | 10 ++++++++++ 3 files changed, 39 insertions(+) create mode 100644 .github/workflow/main.yml create mode 100644 docs/index.md create mode 100644 mkdocs.yml diff --git a/.github/workflow/main.yml b/.github/workflow/main.yml new file mode 100644 index 0000000..b276734 --- /dev/null +++ b/.github/workflow/main.yml @@ -0,0 +1,18 @@ +name: Publish docs via GitHub Pages +on: + push: + branches: + - master + +jobs: + build: + name: Deploy docs + runs-on: ubuntu-latest + steps: + - name: Checkout master + uses: actions/checkout@v1 + + - name: Deploy docs + uses: mhausenblas/mkdocs-deploy-gh-pages@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..e8dbbcf --- /dev/null +++ b/docs/index.md @@ -0,0 +1,11 @@ +# Yii2 Multiple input widget. +Yii2 widget for handle multiple inputs for an attribute of model and tabular input for batch of models. + +[![Latest Stable Version](https://poser.pugx.org/unclead/yii2-multiple-input/v/stable)](https://packagist.org/packages/unclead/yii2-multiple-input) +[![Total Downloads](https://poser.pugx.org/unclead/yii2-multiple-input/downloads)](https://packagist.org/packages/unclead/yii2-multiple-input) +[![Daily Downloads](https://poser.pugx.org/unclead/yii2-multiple-input/d/daily)](https://packagist.org/packages/unclead/yii2-multiple-input) +[![Latest Unstable Version](https://poser.pugx.org/unclead/yii2-multiple-input/v/unstable)](https://packagist.org/packages/unclead/yii2-multiple-input) +[![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) + +## Latest release +The latest stable version of the extension is v2.25.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..decde6c --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,10 @@ +site_name: yii2-multipke-input +site_description: 'A docs test' +site_author: 'Eugene Tupikov' +docs_dir: docs/ +repo_name: 'unclead/yii2-multiple-input' +repo_url: 'https://github.com/unclead/yii2-multiple-input' +nav: + - About: index.md +theme: + name: 'material' From 44c747c1e84360a64571b3279f0d79d2c7cd0d38 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 14 Aug 2021 16:11:28 +0300 Subject: [PATCH 219/247] fix folder name --- .github/{workflow => workflows}/main.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{workflow => workflows}/main.yml (100%) diff --git a/.github/workflow/main.yml b/.github/workflows/main.yml similarity index 100% rename from .github/workflow/main.yml rename to .github/workflows/main.yml From 7f3fa81d9e4884ec0799984c8ff906b5c50e9894 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 14 Aug 2021 16:23:09 +0300 Subject: [PATCH 220/247] fix docs --- docs/README.md | 0 docs/SUMMARY.md | 12 ------------ docs/usage.md | 2 -- 3 files changed, 14 deletions(-) delete mode 100644 docs/README.md delete mode 100644 docs/SUMMARY.md diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md deleted file mode 100644 index 79ef8cd..0000000 --- a/docs/SUMMARY.md +++ /dev/null @@ -1,12 +0,0 @@ -# Table of contents - -* [Installation](installation.md) -* [Getting started](getting-started.md) -* [Usage](usage.md) -* [Configuration](configuration.md) -* [Javascript events](javascript-events.md) -* [Using other icon libraries](using-other-icon-libraries.md) -* [Renderers](renderers.md) -* [Clonning](clonning.md) -* [Tips and tricks](tips-and-tricks.md) - diff --git a/docs/usage.md b/docs/usage.md index d610429..654c8a3 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -1,8 +1,6 @@ # Usage -{% hint style="info" %} You can find the source code of examples [here](https://github.com/unclead/yii2-multiple-input/tree/master/examples) -{% endhint %} ## One column From f3493bcb81f5208ece0e96222d2a59940b8c75c7 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 14 Aug 2021 16:33:48 +0300 Subject: [PATCH 221/247] update docs --- docs/{using-other-icon-libraries.md => icons.md} | 0 mkdocs.yml | 11 ++++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) rename docs/{using-other-icon-libraries.md => icons.md} (100%) diff --git a/docs/using-other-icon-libraries.md b/docs/icons.md similarity index 100% rename from docs/using-other-icon-libraries.md rename to docs/icons.md diff --git a/mkdocs.yml b/mkdocs.yml index decde6c..a9dafea 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,10 +1,19 @@ site_name: yii2-multipke-input -site_description: 'A docs test' +site_description: 'Yii2 widget to handle multiple inputs for an attribute of a model' site_author: 'Eugene Tupikov' docs_dir: docs/ repo_name: 'unclead/yii2-multiple-input' repo_url: 'https://github.com/unclead/yii2-multiple-input' nav: - About: index.md + - Installation: installation.md + - Getting started: getting-started.md + - Configuration: configuration.md + - Renderers: renderers.md + - JS events: javascript-events.md + - Clonning: clonning.md + - Tips and Tricks: tips-and-tricks.md + - Custom icons: icons.md + - Usage: usage.md theme: name: 'material' From 099311db74db0115a17e0e36025615e5243d2ced Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 14 Aug 2021 16:45:46 +0300 Subject: [PATCH 222/247] fix docs --- docs/icons.md | 2 +- mkdocs.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/icons.md b/docs/icons.md index e26cf1b..c805877 100644 --- a/docs/icons.md +++ b/docs/icons.md @@ -30,7 +30,7 @@ public $iconMap = [ ]; ``` -1. Set the preferred icon source +3. Set the preferred icon source ```text public $iconSource = 'my-amazing-icons'; diff --git a/mkdocs.yml b/mkdocs.yml index a9dafea..e006c20 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,4 +1,4 @@ -site_name: yii2-multipke-input +site_name: Yii2 multiple input site_description: 'Yii2 widget to handle multiple inputs for an attribute of a model' site_author: 'Eugene Tupikov' docs_dir: docs/ From a62db0932ec409d4b0c54a7ec7c2a1393b5a7688 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 14 Aug 2021 16:49:43 +0300 Subject: [PATCH 223/247] update the link to the docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 86e7acb..fe02ed8 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ use unclead\multipleinput\MultipleInput; ## Documentation -You can find a full version of documentation [here](https://unclead.gitbook.io/yii2-multiple-input/) +You can find a full version of documentation [here](https://unclead.github.io/yii2-multiple-input/) ## License From becc3dd576ae0533ded89d3d30b28201286ea7b0 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 15 Aug 2021 11:38:05 +0300 Subject: [PATCH 224/247] fix calculation of current index --- src/assets/src/js/jquery.multipleInput.js | 55 ++++++++++++------- src/assets/src/js/jquery.multipleInput.min.js | 2 +- src/components/BaseColumn.php | 6 +- src/renderers/DivRenderer.php | 4 +- src/renderers/ListRenderer.php | 2 + src/renderers/TableRenderer.php | 2 + 6 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 625101f..011f5c2 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -199,7 +199,7 @@ addActiveFormAttribute($(this)); }); - $wrapper.data('multipleInput').currentIndex = getCurrentRowsCount($wrapper); + $wrapper.data('multipleInput').currentIndex = findMaxRowIndex($wrapper); isActiveFormEnabled = true; clearInterval(intervalID); @@ -212,7 +212,7 @@ // If after a second system could not detect ActiveForm it means // that widget is used without ActiveForm and we should just complete initialization of the widget if (form.length === 0 || i > 10) { - $wrapper.data('multipleInput').currentIndex = getCurrentRowsCount($wrapper); + $wrapper.data('multipleInput').currentIndex = findMaxRowIndex($wrapper); isActiveFormEnabled = false; clearInterval(intervalID); @@ -272,8 +272,8 @@ let id = getInputId($element); if (id) { + // todo still doesn't work for sinlge column let columnName = id.replace(settings.inputId, '').replace(/-\d+-/, ''); - if ($element.is(':checkbox')) { if (!values.hasOwnProperty(columnName)) { values[columnName] = []; @@ -299,18 +299,18 @@ let settings = data.settings; let inputList = $wrapper.children('.multiple-input-list').first(); - if (settings.max !== null && getCurrentRowsCount($wrapper) >= settings.max) { + if (settings.max !== null && getRowsCount($wrapper) >= settings.max) { return; } - let currentIndex = data.currentIndex; + let newRowIndex = data.currentIndex + 1; - let template = replaceAll('{' + settings.indexPlaceholder + '}', data.currentIndex, settings.template); + let template = replaceAll('{' + settings.indexPlaceholder + '}', newRowIndex, settings.template); let $newRow = $(template); var beforeAddEvent = $.Event(events.beforeAddRow); - $wrapper.trigger(beforeAddEvent, [$newRow, currentIndex]); + $wrapper.trigger(beforeAddEvent, [$newRow, newRowIndex]); if (beforeAddEvent.result === false) { return; } @@ -320,7 +320,7 @@ let id = getInputId($element); if (id) { - let columnName = id.replace(settings.inputId, '').replace(/-\d+-/, ''); + let columnName = id.replace(settings.inputId, '').replace(/-\d+-?/, ''); if (rowValues.hasOwnProperty(columnName)) { let tag = $element.get(0).tagName; @@ -367,27 +367,25 @@ } }); - // display the new row if (settings.prepend) { $newRow.hide().prependTo(inputList).fadeIn(300); } else { $newRow.hide().appendTo(inputList).fadeIn(300); } - // apply js templates let jsTemplate = null; for (var i in settings.jsTemplates) { jsTemplate = settings.jsTemplates[i]; - jsTemplate = replaceAll('{' + settings.indexPlaceholder + '}', currentIndex, jsTemplate); - jsTemplate = replaceAll('%7B' + settings.indexPlaceholder + '%7D', currentIndex, jsTemplate); + jsTemplate = replaceAll('{' + settings.indexPlaceholder + '}', newRowIndex, jsTemplate); + jsTemplate = replaceAll('%7B' + settings.indexPlaceholder + '%7D', newRowIndex, jsTemplate); window.eval(jsTemplate); } - $wrapper.data('multipleInput').currentIndex++; + $wrapper.data('multipleInput').currentIndex = newRowIndex; var afterAddEvent = $.Event(events.afterAddRow); - $wrapper.trigger(afterAddEvent, [$newRow, currentIndex]); + $wrapper.trigger(afterAddEvent, [$newRow, newRowIndex]); }; var removeInput = function ($btn) { @@ -396,10 +394,10 @@ data = $wrapper.data('multipleInput'), settings = data.settings; - var currentIndex = getCurrentRowsCount($wrapper); - if (currentIndex > settings.min) { + var rowsCount = getRowsCount($wrapper); + if (rowsCount > settings.min) { var event = $.Event(events.beforeDeleteRow); - $wrapper.trigger(event, [$toDelete, currentIndex]); + $wrapper.trigger(event, [$toDelete, rowsCount]); if (event.result === false) { return; @@ -415,7 +413,7 @@ $(this).remove(); event = $.Event(events.afterDeleteRow); - $wrapper.trigger(event, [$toDelete, currentIndex]); + $wrapper.trigger(event, [$toDelete, rowsCount]); }); } }; @@ -505,12 +503,29 @@ return id; }; - var getCurrentRowsCount = function($wrapper) { + var getRowsCount = function($wrapper) { + return findRows($wrapper).length; + }; + + var findRows = function($wrapper) { return $wrapper .find('.multiple-input-list .multiple-input-list__item') .filter(function(){ return $(this).parents('.multiple-input').first().attr('id') === $wrapper.attr('id'); - }).length; + }); + } + + var findMaxRowIndex = function($wrapper) { + let maxIndex = 0; + + findRows($wrapper).each(function(key, element) { + let index = $(element).data('index'); + if (index > maxIndex) { + maxIndex = index; + } + }); + + return maxIndex; }; var replaceAll = function (search, replace, subject) { diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index d62bc94..f67b8aa 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){"use strict";t.fn.multipleInput=function(e){return o[e]?o[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):o.init.apply(this,arguments)};var e="afterInit",i="beforeAddRow",n="afterAddRow",l="beforeDeleteRow",r="afterDeleteRow",a={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},u=!1,o={init:function(i){if("object"==typeof i){var n=t.extend(!0,{},a,i||{}),l=t("#"+n.id),r=l.closest("form"),o=n.inputId;for(f in n.jsInit)window.eval(n.jsInit[f]);l.data("multipleInput",{settings:n,currentIndex:0}),l.on("click.multipleInput",".js-input-remove",(function(e){e.stopPropagation(),c(t(this))})),l.on("click.multipleInput",".js-input-plus",(function(e){e.stopPropagation(),p(t(this))})),l.on("click.multipleInput",".js-input-clone",(function(e){e.stopPropagation(),s(t(this))}));var f=0,m=t.Event(e),h=setInterval((function(){if("object"==typeof r.data("yiiActiveForm")){var e=r.yiiActiveForm("find",o),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,(function(t,e){-1===["id","input","container"].indexOf(t)&&(i[t]=e)})),n.showGeneralError||r.yiiActiveForm("remove",o)),t.each(n.attributes,(function(e,l){l=t.extend({},i,l),n.attributes[e]=l})),l.data("multipleInput").settings=n,l.find(".multiple-input-list").find("input, select, textarea").each((function(){d(t(this))})),l.data("multipleInput").currentIndex=v(l),u=!0,clearInterval(h),l.trigger(m)}else f++;(0===r.length||f>10)&&(l.data("multipleInput").currentIndex=v(l),u=!1,clearInterval(h),l.trigger(m))}),100)}else console.error("Options must be an object")},add:function(e){p(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),c(i)},clear:function(){t(this).find(".js-input-remove").each((function(){c(t(this))}))},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},s=function(e){let i=t(e).closest(".multiple-input").first().data("multipleInput").settings,n={};e.closest(".multiple-input-list__item").find("input, select, textarea").each((function(e,l){let r=t(l),a=m(r);if(a){let t=a.replace(i.inputId,"").replace(/-\d+-/,"");r.is(":checkbox")?(n.hasOwnProperty(t)||(n[t]=[]),r.is(":checked")&&n[t].push(r.val())):n[t]=r.val()}})),p(e,n)},p=function(e,l){l=l||{};let r=t(e).closest(".multiple-input").first(),a=r.data("multipleInput"),o=a.settings,s=r.children(".multiple-input-list").first();if(null!==o.max&&v(r)>=o.max)return;let p=a.currentIndex,c=h("{"+o.indexPlaceholder+"}",a.currentIndex,o.template),f=t(c);var g=t.Event(i);if(r.trigger(g,[f,p]),!1===g.result)return;f.find("input, select, textarea").each((function(e,i){let n=t(i),r=m(n);if(r){let t=r.replace(o.inputId,"").replace(/-\d+-/,"");if(l.hasOwnProperty(t)){let e=n.get(0).tagName,i=l[t];switch(e){case"INPUT":n.is(":checkbox")?-1!==i.indexOf(n.val())&&n.prop("checked",!0):n.val(i);break;case"TEXTAREA":n.val(i);break;case"SELECT":if(i&&-1!==i.indexOf("option"))n.append(i);else n.find('option[value="'+i+'"]').length&&n.val(i)}}}u&&d(n)})),o.prepend?f.hide().prependTo(s).fadeIn(300):f.hide().appendTo(s).fadeIn(300);let I=null;for(var x in o.jsTemplates)I=o.jsTemplates[x],I=h("{"+o.indexPlaceholder+"}",p,I),I=h("%7B"+o.indexPlaceholder+"%7D",p,I),window.eval(I);r.data("multipleInput").currentIndex++;var y=t.Event(n);r.trigger(y,[f,p])},c=function(e){var i=e.closest(".multiple-input").first(),n=e.closest(".multiple-input-list__item"),a=i.data("multipleInput").settings,o=v(i);if(o>a.min){var s=t.Event(l);if(i.trigger(s,[n,o]),!1===s.result)return;u&&n.find("input, select, textarea").each((function(e,i){f(t(i))})),n.fadeOut(300,(function(){t(this).remove(),s=t.Event(r),i.trigger(s,[n,o])}))}},d=function(e){var i=m(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!==l.length&&void 0===r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),u={},o=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(o)?u=a.settings.attributes[o]:(o=h(/-\d-/,"-",o),o=h(/-\d/,"",o),a.settings.attributes.hasOwnProperty(o)&&(u=a.settings.attributes[o])),r.yiiActiveForm("add",t.extend({},u,{id:i,input:"#"+i,container:".field-"+i}))}}},f=function(e){var i=m(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},m=function(t){var e=t.attr("id");return void 0===e&&(e=t.data("id")),void 0===e?null:e},v=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter((function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")})).length},h=function(t,e,i){return i instanceof String||"string"==typeof i?i.split(t).join(e):(console.warn("Call replaceAll for non-string value: "+i),i)}}(window.jQuery); \ No newline at end of file +!function(t){"use strict";t.fn.multipleInput=function(e){return o[e]?o[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):o.init.apply(this,arguments)};var e="afterInit",i="beforeAddRow",n="afterAddRow",l="beforeDeleteRow",r="afterDeleteRow",a={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},u=!1,o={init:function(i){if("object"==typeof i){var n=t.extend(!0,{},a,i||{}),l=t("#"+n.id),r=l.closest("form"),o=n.inputId;for(f in n.jsInit)window.eval(n.jsInit[f]);l.data("multipleInput",{settings:n,currentIndex:0}),l.on("click.multipleInput",".js-input-remove",(function(e){e.stopPropagation(),c(t(this))})),l.on("click.multipleInput",".js-input-plus",(function(e){e.stopPropagation(),p(t(this))})),l.on("click.multipleInput",".js-input-clone",(function(e){e.stopPropagation(),s(t(this))}));var f=0,m=t.Event(e),v=setInterval((function(){if("object"==typeof r.data("yiiActiveForm")){var e=r.yiiActiveForm("find",o),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,(function(t,e){-1===["id","input","container"].indexOf(t)&&(i[t]=e)})),n.showGeneralError||r.yiiActiveForm("remove",o)),t.each(n.attributes,(function(e,l){l=t.extend({},i,l),n.attributes[e]=l})),l.data("multipleInput").settings=n,l.find(".multiple-input-list").find("input, select, textarea").each((function(){d(t(this))})),l.data("multipleInput").currentIndex=g(l),u=!0,clearInterval(v),l.trigger(m)}else f++;(0===r.length||f>10)&&(l.data("multipleInput").currentIndex=g(l),u=!1,clearInterval(v),l.trigger(m))}),100)}else console.error("Options must be an object")},add:function(e){p(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),c(i)},clear:function(){t(this).find(".js-input-remove").each((function(){c(t(this))}))},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},s=function(e){let i=t(e).closest(".multiple-input").first().data("multipleInput").settings,n={};e.closest(".multiple-input-list__item").find("input, select, textarea").each((function(e,l){let r=t(l),a=m(r);if(a){let t=a.replace(i.inputId,"").replace(/-\d+-/,"");r.is(":checkbox")?(n.hasOwnProperty(t)||(n[t]=[]),r.is(":checked")&&n[t].push(r.val())):n[t]=r.val()}})),p(e,n)},p=function(e,l){l=l||{};let r=t(e).closest(".multiple-input").first(),a=r.data("multipleInput"),o=a.settings,s=r.children(".multiple-input-list").first();if(null!==o.max&&v(r)>=o.max)return;let p=a.currentIndex+1,c=I("{"+o.indexPlaceholder+"}",p,o.template),f=t(c);var h=t.Event(i);if(r.trigger(h,[f,p]),!1===h.result)return;f.find("input, select, textarea").each((function(e,i){let n=t(i),r=m(n);if(r){let t=r.replace(o.inputId,"").replace(/-\d+-?/,"");if(l.hasOwnProperty(t)){let e=n.get(0).tagName,i=l[t];switch(e){case"INPUT":n.is(":checkbox")?-1!==i.indexOf(n.val())&&n.prop("checked",!0):n.val(i);break;case"TEXTAREA":n.val(i);break;case"SELECT":if(i&&-1!==i.indexOf("option"))n.append(i);else n.find('option[value="'+i+'"]').length&&n.val(i)}}}u&&d(n)})),o.prepend?f.hide().prependTo(s).fadeIn(300):f.hide().appendTo(s).fadeIn(300);let g=null;for(var x in o.jsTemplates)g=o.jsTemplates[x],g=I("{"+o.indexPlaceholder+"}",p,g),g=I("%7B"+o.indexPlaceholder+"%7D",p,g),window.eval(g);r.data("multipleInput").currentIndex=p;var y=t.Event(n);r.trigger(y,[f,p])},c=function(e){var i=e.closest(".multiple-input").first(),n=e.closest(".multiple-input-list__item"),a=i.data("multipleInput").settings,o=v(i);if(o>a.min){var s=t.Event(l);if(i.trigger(s,[n,o]),!1===s.result)return;u&&n.find("input, select, textarea").each((function(e,i){f(t(i))})),n.fadeOut(300,(function(){t(this).remove(),s=t.Event(r),i.trigger(s,[n,o])}))}},d=function(e){var i=m(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!==l.length&&void 0===r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),u={},o=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(o)?u=a.settings.attributes[o]:(o=I(/-\d-/,"-",o),o=I(/-\d/,"",o),a.settings.attributes.hasOwnProperty(o)&&(u=a.settings.attributes[o])),r.yiiActiveForm("add",t.extend({},u,{id:i,input:"#"+i,container:".field-"+i}))}}},f=function(e){var i=m(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},m=function(t){var e=t.attr("id");return void 0===e&&(e=t.data("id")),void 0===e?null:e},v=function(t){return h(t).length},h=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter((function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}))},g=function(e){let i=0;return h(e).each((function(e,n){let l=t(n).data("index");l>i&&(i=l)})),i},I=function(t,e,i){return i instanceof String||"string"==typeof i?i.split(t).join(e):(console.warn("Call replaceAll for non-string value: "+i),i)}}(window.jQuery); \ No newline at end of file diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index b1cee6a..d4a9928 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -364,7 +364,7 @@ protected function renderDropDownList($name, $value, $options) if ($this->renderer->isBootstrapTheme()) { Html::addCssClass($options, 'form-control'); } - + return Html::dropDownList($name, $value, $this->prepareItems($this->items), $options); } @@ -396,7 +396,7 @@ protected function renderListBox($name, $value, $options) if ($this->renderer->isBootstrapTheme()) { Html::addCssClass($options, 'form-control'); } - + return Html::listBox($name, $value, $this->prepareItems($this->items), $options); } @@ -605,7 +605,7 @@ protected function renderWidget($type, $name, $value, $options) } else { $tabindex = null; } - + $id = isset($options['id']) ? $options['id'] : $this->normalize($name); $model = $this->getModel(); if ($model instanceof Model) { diff --git a/src/renderers/DivRenderer.php b/src/renderers/DivRenderer.php index 4b9de64..c717fa0 100644 --- a/src/renderers/DivRenderer.php +++ b/src/renderers/DivRenderer.php @@ -17,7 +17,7 @@ /** * Class DivRenderer is a list renderer which uses divs - * + * * @package unclead\multipleinput\renderers */ class DivRenderer extends BaseRenderer @@ -161,6 +161,8 @@ protected function prepareRowOptions($index, $item) $options = $this->rowOptions; } + $options['data-index'] = '{' . $this->getIndexPlaceholder() . '}'; + Html::addCssClass($options, 'multiple-input-list__item'); return $options; diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index 9a39358..a34ad63 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -178,6 +178,8 @@ protected function prepareRowOptions($index, $item) $options = $this->rowOptions; } + $options['data-index'] = '{' . $this->getIndexPlaceholder() . '}'; + Html::addCssClass($options, 'multiple-input-list__item'); return $options; diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index e53ae2a..3394b0b 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -254,6 +254,8 @@ protected function prepareRowOptions($index, $item) $options = $this->rowOptions; } + $options['data-index'] = '{' . $this->getIndexPlaceholder() . '}'; + Html::addCssClass($options, 'multiple-input-list__item'); return $options; From 5380fb98f8687ecd9f48ff1a4d8b3f391bae667a Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 15 Aug 2021 11:44:11 +0300 Subject: [PATCH 225/247] pass the currentIndex instead of the number of rows to beforeDeleteRow event --- src/assets/src/js/jquery.multipleInput.js | 5 ++--- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 011f5c2..ec52349 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -394,10 +394,9 @@ data = $wrapper.data('multipleInput'), settings = data.settings; - var rowsCount = getRowsCount($wrapper); - if (rowsCount > settings.min) { + if (getRowsCount($wrapper) > settings.min) { var event = $.Event(events.beforeDeleteRow); - $wrapper.trigger(event, [$toDelete, rowsCount]); + $wrapper.trigger(event, [$toDelete, data.currentIndex]); if (event.result === false) { return; diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index f67b8aa..8e59b2a 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){"use strict";t.fn.multipleInput=function(e){return o[e]?o[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):o.init.apply(this,arguments)};var e="afterInit",i="beforeAddRow",n="afterAddRow",l="beforeDeleteRow",r="afterDeleteRow",a={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},u=!1,o={init:function(i){if("object"==typeof i){var n=t.extend(!0,{},a,i||{}),l=t("#"+n.id),r=l.closest("form"),o=n.inputId;for(f in n.jsInit)window.eval(n.jsInit[f]);l.data("multipleInput",{settings:n,currentIndex:0}),l.on("click.multipleInput",".js-input-remove",(function(e){e.stopPropagation(),c(t(this))})),l.on("click.multipleInput",".js-input-plus",(function(e){e.stopPropagation(),p(t(this))})),l.on("click.multipleInput",".js-input-clone",(function(e){e.stopPropagation(),s(t(this))}));var f=0,m=t.Event(e),v=setInterval((function(){if("object"==typeof r.data("yiiActiveForm")){var e=r.yiiActiveForm("find",o),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,(function(t,e){-1===["id","input","container"].indexOf(t)&&(i[t]=e)})),n.showGeneralError||r.yiiActiveForm("remove",o)),t.each(n.attributes,(function(e,l){l=t.extend({},i,l),n.attributes[e]=l})),l.data("multipleInput").settings=n,l.find(".multiple-input-list").find("input, select, textarea").each((function(){d(t(this))})),l.data("multipleInput").currentIndex=g(l),u=!0,clearInterval(v),l.trigger(m)}else f++;(0===r.length||f>10)&&(l.data("multipleInput").currentIndex=g(l),u=!1,clearInterval(v),l.trigger(m))}),100)}else console.error("Options must be an object")},add:function(e){p(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),c(i)},clear:function(){t(this).find(".js-input-remove").each((function(){c(t(this))}))},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},s=function(e){let i=t(e).closest(".multiple-input").first().data("multipleInput").settings,n={};e.closest(".multiple-input-list__item").find("input, select, textarea").each((function(e,l){let r=t(l),a=m(r);if(a){let t=a.replace(i.inputId,"").replace(/-\d+-/,"");r.is(":checkbox")?(n.hasOwnProperty(t)||(n[t]=[]),r.is(":checked")&&n[t].push(r.val())):n[t]=r.val()}})),p(e,n)},p=function(e,l){l=l||{};let r=t(e).closest(".multiple-input").first(),a=r.data("multipleInput"),o=a.settings,s=r.children(".multiple-input-list").first();if(null!==o.max&&v(r)>=o.max)return;let p=a.currentIndex+1,c=I("{"+o.indexPlaceholder+"}",p,o.template),f=t(c);var h=t.Event(i);if(r.trigger(h,[f,p]),!1===h.result)return;f.find("input, select, textarea").each((function(e,i){let n=t(i),r=m(n);if(r){let t=r.replace(o.inputId,"").replace(/-\d+-?/,"");if(l.hasOwnProperty(t)){let e=n.get(0).tagName,i=l[t];switch(e){case"INPUT":n.is(":checkbox")?-1!==i.indexOf(n.val())&&n.prop("checked",!0):n.val(i);break;case"TEXTAREA":n.val(i);break;case"SELECT":if(i&&-1!==i.indexOf("option"))n.append(i);else n.find('option[value="'+i+'"]').length&&n.val(i)}}}u&&d(n)})),o.prepend?f.hide().prependTo(s).fadeIn(300):f.hide().appendTo(s).fadeIn(300);let g=null;for(var x in o.jsTemplates)g=o.jsTemplates[x],g=I("{"+o.indexPlaceholder+"}",p,g),g=I("%7B"+o.indexPlaceholder+"%7D",p,g),window.eval(g);r.data("multipleInput").currentIndex=p;var y=t.Event(n);r.trigger(y,[f,p])},c=function(e){var i=e.closest(".multiple-input").first(),n=e.closest(".multiple-input-list__item"),a=i.data("multipleInput").settings,o=v(i);if(o>a.min){var s=t.Event(l);if(i.trigger(s,[n,o]),!1===s.result)return;u&&n.find("input, select, textarea").each((function(e,i){f(t(i))})),n.fadeOut(300,(function(){t(this).remove(),s=t.Event(r),i.trigger(s,[n,o])}))}},d=function(e){var i=m(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!==l.length&&void 0===r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),u={},o=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(o)?u=a.settings.attributes[o]:(o=I(/-\d-/,"-",o),o=I(/-\d/,"",o),a.settings.attributes.hasOwnProperty(o)&&(u=a.settings.attributes[o])),r.yiiActiveForm("add",t.extend({},u,{id:i,input:"#"+i,container:".field-"+i}))}}},f=function(e){var i=m(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},m=function(t){var e=t.attr("id");return void 0===e&&(e=t.data("id")),void 0===e?null:e},v=function(t){return h(t).length},h=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter((function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}))},g=function(e){let i=0;return h(e).each((function(e,n){let l=t(n).data("index");l>i&&(i=l)})),i},I=function(t,e,i){return i instanceof String||"string"==typeof i?i.split(t).join(e):(console.warn("Call replaceAll for non-string value: "+i),i)}}(window.jQuery); \ No newline at end of file +!function(t){"use strict";t.fn.multipleInput=function(e){return o[e]?o[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):o.init.apply(this,arguments)};var e="afterInit",i="beforeAddRow",n="afterAddRow",l="beforeDeleteRow",r="afterDeleteRow",a={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},u=!1,o={init:function(i){if("object"==typeof i){var n=t.extend(!0,{},a,i||{}),l=t("#"+n.id),r=l.closest("form"),o=n.inputId;for(f in n.jsInit)window.eval(n.jsInit[f]);l.data("multipleInput",{settings:n,currentIndex:0}),l.on("click.multipleInput",".js-input-remove",(function(e){e.stopPropagation(),c(t(this))})),l.on("click.multipleInput",".js-input-plus",(function(e){e.stopPropagation(),p(t(this))})),l.on("click.multipleInput",".js-input-clone",(function(e){e.stopPropagation(),s(t(this))}));var f=0,m=t.Event(e),v=setInterval((function(){if("object"==typeof r.data("yiiActiveForm")){var e=r.yiiActiveForm("find",o),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,(function(t,e){-1===["id","input","container"].indexOf(t)&&(i[t]=e)})),n.showGeneralError||r.yiiActiveForm("remove",o)),t.each(n.attributes,(function(e,l){l=t.extend({},i,l),n.attributes[e]=l})),l.data("multipleInput").settings=n,l.find(".multiple-input-list").find("input, select, textarea").each((function(){d(t(this))})),l.data("multipleInput").currentIndex=g(l),u=!0,clearInterval(v),l.trigger(m)}else f++;(0===r.length||f>10)&&(l.data("multipleInput").currentIndex=g(l),u=!1,clearInterval(v),l.trigger(m))}),100)}else console.error("Options must be an object")},add:function(e){p(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),c(i)},clear:function(){t(this).find(".js-input-remove").each((function(){c(t(this))}))},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},s=function(e){let i=t(e).closest(".multiple-input").first().data("multipleInput").settings,n={};e.closest(".multiple-input-list__item").find("input, select, textarea").each((function(e,l){let r=t(l),a=m(r);if(a){let t=a.replace(i.inputId,"").replace(/-\d+-/,"");r.is(":checkbox")?(n.hasOwnProperty(t)||(n[t]=[]),r.is(":checked")&&n[t].push(r.val())):n[t]=r.val()}})),p(e,n)},p=function(e,l){l=l||{};let r=t(e).closest(".multiple-input").first(),a=r.data("multipleInput"),o=a.settings,s=r.children(".multiple-input-list").first();if(null!==o.max&&v(r)>=o.max)return;let p=a.currentIndex+1,c=I("{"+o.indexPlaceholder+"}",p,o.template),f=t(c);var h=t.Event(i);if(r.trigger(h,[f,p]),!1===h.result)return;f.find("input, select, textarea").each((function(e,i){let n=t(i),r=m(n);if(r){let t=r.replace(o.inputId,"").replace(/-\d+-?/,"");if(l.hasOwnProperty(t)){let e=n.get(0).tagName,i=l[t];switch(e){case"INPUT":n.is(":checkbox")?-1!==i.indexOf(n.val())&&n.prop("checked",!0):n.val(i);break;case"TEXTAREA":n.val(i);break;case"SELECT":if(i&&-1!==i.indexOf("option"))n.append(i);else n.find('option[value="'+i+'"]').length&&n.val(i)}}}u&&d(n)})),o.prepend?f.hide().prependTo(s).fadeIn(300):f.hide().appendTo(s).fadeIn(300);let g=null;for(var x in o.jsTemplates)g=o.jsTemplates[x],g=I("{"+o.indexPlaceholder+"}",p,g),g=I("%7B"+o.indexPlaceholder+"%7D",p,g),window.eval(g);r.data("multipleInput").currentIndex=p;var y=t.Event(n);r.trigger(y,[f,p])},c=function(e){var i=e.closest(".multiple-input").first(),n=e.closest(".multiple-input-list__item"),a=i.data("multipleInput"),o=a.settings;if(v(i)>o.min){var s=t.Event(l);if(i.trigger(s,[n,a.currentIndex]),!1===s.result)return;u&&n.find("input, select, textarea").each((function(e,i){f(t(i))})),n.fadeOut(300,(function(){t(this).remove(),s=t.Event(r),i.trigger(s,[n,rowsCount])}))}},d=function(e){var i=m(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!==l.length&&void 0===r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),u={},o=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(o)?u=a.settings.attributes[o]:(o=I(/-\d-/,"-",o),o=I(/-\d/,"",o),a.settings.attributes.hasOwnProperty(o)&&(u=a.settings.attributes[o])),r.yiiActiveForm("add",t.extend({},u,{id:i,input:"#"+i,container:".field-"+i}))}}},f=function(e){var i=m(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},m=function(t){var e=t.attr("id");return void 0===e&&(e=t.data("id")),void 0===e?null:e},v=function(t){return h(t).length},h=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter((function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}))},g=function(e){let i=0;return h(e).each((function(e,n){let l=t(n).data("index");l>i&&(i=l)})),i},I=function(t,e,i){return i instanceof String||"string"==typeof i?i.split(t).join(e):(console.warn("Call replaceAll for non-string value: "+i),i)}}(window.jQuery); \ No newline at end of file From 651f07c875494bba82cf6e1589ed43fff1fdb267 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 15 Aug 2021 11:46:41 +0300 Subject: [PATCH 226/247] release 2.26.0 --- CHANGELOG.md | 7 ++++++- README.md | 2 +- package.json | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8970692..34a6269 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,14 @@ Yii2 multiple input change log ============================== -2.26.0 (in development) +2.27.0 (in development) ======================= +2.26.0 +====== +- fix calculation of the current row index +- fix incrementing the current row index after adding a new row + 2.25.0 ====== - rework cloning: fix #277, #351, #348 (unclead) diff --git a/README.md b/README.md index fe02ed8..cab211f 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.25.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.26.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/package.json b/package.json index c066a0f..b7ea83d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.25.0", + "version": "2.26.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 444d22b7e96e106e586fb1b8e090cddfec6e4663 Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 1 Oct 2021 10:00:42 +0200 Subject: [PATCH 227/247] prevent error loop in case of undefined `$wrapper.data('multipleInput')` if for any reason this code line fails, the interval is not removed and console.log fills with error messages every 10ms. This change prevents that from happening. I have no idea why it fails, but the widget works fine. --- src/assets/src/js/jquery.multipleInput.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index ec52349..ac5ce92 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -212,10 +212,13 @@ // If after a second system could not detect ActiveForm it means // that widget is used without ActiveForm and we should just complete initialization of the widget if (form.length === 0 || i > 10) { - $wrapper.data('multipleInput').currentIndex = findMaxRowIndex($wrapper); + clearInterval(intervalID); isActiveFormEnabled = false; - clearInterval(intervalID); + if (typeof $wrapper.data('multipleInput') !== 'undefined') { + $wrapper.data('multipleInput').currentIndex = findMaxRowIndex($wrapper); + } + $wrapper.trigger(event); } }, 100); From 2d93c85f31ec1cf0c0eb52ab6165b16d58aa778d Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 1 Oct 2021 10:19:28 +0200 Subject: [PATCH 228/247] fix undefined variable `rowsCount` --- src/assets/src/js/jquery.multipleInput.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index ac5ce92..7027f9a 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -397,7 +397,8 @@ data = $wrapper.data('multipleInput'), settings = data.settings; - if (getRowsCount($wrapper) > settings.min) { + var rowsCount = getRowsCount($wrapper); + if (rowsCount > settings.min) { var event = $.Event(events.beforeDeleteRow); $wrapper.trigger(event, [$toDelete, data.currentIndex]); From bc5b67a5354c300af67c1c31770456659918cd8a Mon Sep 17 00:00:00 2001 From: Carsten Brandt Date: Fri, 1 Oct 2021 10:20:58 +0200 Subject: [PATCH 229/247] run gulp for jquery.multipleInput.min.js --- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 8e59b2a..2b11221 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){"use strict";t.fn.multipleInput=function(e){return o[e]?o[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):o.init.apply(this,arguments)};var e="afterInit",i="beforeAddRow",n="afterAddRow",l="beforeDeleteRow",r="afterDeleteRow",a={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},u=!1,o={init:function(i){if("object"==typeof i){var n=t.extend(!0,{},a,i||{}),l=t("#"+n.id),r=l.closest("form"),o=n.inputId;for(f in n.jsInit)window.eval(n.jsInit[f]);l.data("multipleInput",{settings:n,currentIndex:0}),l.on("click.multipleInput",".js-input-remove",(function(e){e.stopPropagation(),c(t(this))})),l.on("click.multipleInput",".js-input-plus",(function(e){e.stopPropagation(),p(t(this))})),l.on("click.multipleInput",".js-input-clone",(function(e){e.stopPropagation(),s(t(this))}));var f=0,m=t.Event(e),v=setInterval((function(){if("object"==typeof r.data("yiiActiveForm")){var e=r.yiiActiveForm("find",o),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,(function(t,e){-1===["id","input","container"].indexOf(t)&&(i[t]=e)})),n.showGeneralError||r.yiiActiveForm("remove",o)),t.each(n.attributes,(function(e,l){l=t.extend({},i,l),n.attributes[e]=l})),l.data("multipleInput").settings=n,l.find(".multiple-input-list").find("input, select, textarea").each((function(){d(t(this))})),l.data("multipleInput").currentIndex=g(l),u=!0,clearInterval(v),l.trigger(m)}else f++;(0===r.length||f>10)&&(l.data("multipleInput").currentIndex=g(l),u=!1,clearInterval(v),l.trigger(m))}),100)}else console.error("Options must be an object")},add:function(e){p(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),c(i)},clear:function(){t(this).find(".js-input-remove").each((function(){c(t(this))}))},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},s=function(e){let i=t(e).closest(".multiple-input").first().data("multipleInput").settings,n={};e.closest(".multiple-input-list__item").find("input, select, textarea").each((function(e,l){let r=t(l),a=m(r);if(a){let t=a.replace(i.inputId,"").replace(/-\d+-/,"");r.is(":checkbox")?(n.hasOwnProperty(t)||(n[t]=[]),r.is(":checked")&&n[t].push(r.val())):n[t]=r.val()}})),p(e,n)},p=function(e,l){l=l||{};let r=t(e).closest(".multiple-input").first(),a=r.data("multipleInput"),o=a.settings,s=r.children(".multiple-input-list").first();if(null!==o.max&&v(r)>=o.max)return;let p=a.currentIndex+1,c=I("{"+o.indexPlaceholder+"}",p,o.template),f=t(c);var h=t.Event(i);if(r.trigger(h,[f,p]),!1===h.result)return;f.find("input, select, textarea").each((function(e,i){let n=t(i),r=m(n);if(r){let t=r.replace(o.inputId,"").replace(/-\d+-?/,"");if(l.hasOwnProperty(t)){let e=n.get(0).tagName,i=l[t];switch(e){case"INPUT":n.is(":checkbox")?-1!==i.indexOf(n.val())&&n.prop("checked",!0):n.val(i);break;case"TEXTAREA":n.val(i);break;case"SELECT":if(i&&-1!==i.indexOf("option"))n.append(i);else n.find('option[value="'+i+'"]').length&&n.val(i)}}}u&&d(n)})),o.prepend?f.hide().prependTo(s).fadeIn(300):f.hide().appendTo(s).fadeIn(300);let g=null;for(var x in o.jsTemplates)g=o.jsTemplates[x],g=I("{"+o.indexPlaceholder+"}",p,g),g=I("%7B"+o.indexPlaceholder+"%7D",p,g),window.eval(g);r.data("multipleInput").currentIndex=p;var y=t.Event(n);r.trigger(y,[f,p])},c=function(e){var i=e.closest(".multiple-input").first(),n=e.closest(".multiple-input-list__item"),a=i.data("multipleInput"),o=a.settings;if(v(i)>o.min){var s=t.Event(l);if(i.trigger(s,[n,a.currentIndex]),!1===s.result)return;u&&n.find("input, select, textarea").each((function(e,i){f(t(i))})),n.fadeOut(300,(function(){t(this).remove(),s=t.Event(r),i.trigger(s,[n,rowsCount])}))}},d=function(e){var i=m(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!==l.length&&void 0===r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),u={},o=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(o)?u=a.settings.attributes[o]:(o=I(/-\d-/,"-",o),o=I(/-\d/,"",o),a.settings.attributes.hasOwnProperty(o)&&(u=a.settings.attributes[o])),r.yiiActiveForm("add",t.extend({},u,{id:i,input:"#"+i,container:".field-"+i}))}}},f=function(e){var i=m(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},m=function(t){var e=t.attr("id");return void 0===e&&(e=t.data("id")),void 0===e?null:e},v=function(t){return h(t).length},h=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter((function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}))},g=function(e){let i=0;return h(e).each((function(e,n){let l=t(n).data("index");l>i&&(i=l)})),i},I=function(t,e,i){return i instanceof String||"string"==typeof i?i.split(t).join(e):(console.warn("Call replaceAll for non-string value: "+i),i)}}(window.jQuery); \ No newline at end of file +!function(t){"use strict";t.fn.multipleInput=function(e){return o[e]?o[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):o.init.apply(this,arguments)};var e="afterInit",i="beforeAddRow",n="afterAddRow",l="beforeDeleteRow",r="afterDeleteRow",a={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},u=!1,o={init:function(i){if("object"==typeof i){var n=t.extend(!0,{},a,i||{}),l=t("#"+n.id),r=l.closest("form"),o=n.inputId;for(f in n.jsInit)window.eval(n.jsInit[f]);l.data("multipleInput",{settings:n,currentIndex:0}),l.on("click.multipleInput",".js-input-remove",(function(e){e.stopPropagation(),c(t(this))})),l.on("click.multipleInput",".js-input-plus",(function(e){e.stopPropagation(),p(t(this))})),l.on("click.multipleInput",".js-input-clone",(function(e){e.stopPropagation(),s(t(this))}));var f=0,m=t.Event(e),v=setInterval((function(){if("object"==typeof r.data("yiiActiveForm")){var e=r.yiiActiveForm("find",o),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,(function(t,e){-1===["id","input","container"].indexOf(t)&&(i[t]=e)})),n.showGeneralError||r.yiiActiveForm("remove",o)),t.each(n.attributes,(function(e,l){l=t.extend({},i,l),n.attributes[e]=l})),l.data("multipleInput").settings=n,l.find(".multiple-input-list").find("input, select, textarea").each((function(){d(t(this))})),l.data("multipleInput").currentIndex=g(l),u=!0,clearInterval(v),l.trigger(m)}else f++;(0===r.length||f>10)&&(clearInterval(v),u=!1,void 0!==l.data("multipleInput")&&(l.data("multipleInput").currentIndex=g(l)),l.trigger(m))}),100)}else console.error("Options must be an object")},add:function(e){p(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),c(i)},clear:function(){t(this).find(".js-input-remove").each((function(){c(t(this))}))},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},s=function(e){let i=t(e).closest(".multiple-input").first().data("multipleInput").settings,n={};e.closest(".multiple-input-list__item").find("input, select, textarea").each((function(e,l){let r=t(l),a=m(r);if(a){let t=a.replace(i.inputId,"").replace(/-\d+-/,"");r.is(":checkbox")?(n.hasOwnProperty(t)||(n[t]=[]),r.is(":checked")&&n[t].push(r.val())):n[t]=r.val()}})),p(e,n)},p=function(e,l){l=l||{};let r=t(e).closest(".multiple-input").first(),a=r.data("multipleInput"),o=a.settings,s=r.children(".multiple-input-list").first();if(null!==o.max&&v(r)>=o.max)return;let p=a.currentIndex+1,c=I("{"+o.indexPlaceholder+"}",p,o.template),f=t(c);var h=t.Event(i);if(r.trigger(h,[f,p]),!1===h.result)return;f.find("input, select, textarea").each((function(e,i){let n=t(i),r=m(n);if(r){let t=r.replace(o.inputId,"").replace(/-\d+-?/,"");if(l.hasOwnProperty(t)){let e=n.get(0).tagName,i=l[t];switch(e){case"INPUT":n.is(":checkbox")?-1!==i.indexOf(n.val())&&n.prop("checked",!0):n.val(i);break;case"TEXTAREA":n.val(i);break;case"SELECT":if(i&&-1!==i.indexOf("option"))n.append(i);else n.find('option[value="'+i+'"]').length&&n.val(i)}}}u&&d(n)})),o.prepend?f.hide().prependTo(s).fadeIn(300):f.hide().appendTo(s).fadeIn(300);let g=null;for(var x in o.jsTemplates)g=o.jsTemplates[x],g=I("{"+o.indexPlaceholder+"}",p,g),g=I("%7B"+o.indexPlaceholder+"%7D",p,g),window.eval(g);r.data("multipleInput").currentIndex=p;var y=t.Event(n);r.trigger(y,[f,p])},c=function(e){var i=e.closest(".multiple-input").first(),n=e.closest(".multiple-input-list__item"),a=i.data("multipleInput"),o=a.settings,s=v(i);if(s>o.min){var p=t.Event(l);if(i.trigger(p,[n,a.currentIndex]),!1===p.result)return;u&&n.find("input, select, textarea").each((function(e,i){f(t(i))})),n.fadeOut(300,(function(){t(this).remove(),p=t.Event(r),i.trigger(p,[n,s])}))}},d=function(e){var i=m(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!==l.length&&void 0===r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),u={},o=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(o)?u=a.settings.attributes[o]:(o=I(/-\d-/,"-",o),o=I(/-\d/,"",o),a.settings.attributes.hasOwnProperty(o)&&(u=a.settings.attributes[o])),r.yiiActiveForm("add",t.extend({},u,{id:i,input:"#"+i,container:".field-"+i}))}}},f=function(e){var i=m(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},m=function(t){var e=t.attr("id");return void 0===e&&(e=t.data("id")),void 0===e?null:e},v=function(t){return h(t).length},h=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter((function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}))},g=function(e){let i=0;return h(e).each((function(e,n){let l=t(n).data("index");l>i&&(i=l)})),i},I=function(t,e,i){return i instanceof String||"string"==typeof i?i.split(t).join(e):(console.warn("Call replaceAll for non-string value: "+i),i)}}(window.jQuery); \ No newline at end of file From 68470ced7cc4c6e70cab2a1199140cc3b86765c6 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 1 Oct 2021 12:40:38 +0300 Subject: [PATCH 230/247] drop the field "version" from the composer.json because it's an optional in case of using git --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index bd75afd..f5e557e 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,6 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.24.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", From b2f0dafb6e2ae68abf300b9b8d8b4b5960dfed1b Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 1 Oct 2021 12:46:02 +0300 Subject: [PATCH 231/247] bump version --- CHANGELOG.md | 4 ++++ README.md | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 34a6269..d795bf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Yii2 multiple input change log 2.27.0 (in development) ======================= +2.26.1 +====== +- remove version from composer.json + 2.26.0 ====== - fix calculation of the current row index diff --git a/README.md b/README.md index cab211f..1655b8e 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.26.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.26.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/package.json b/package.json index b7ea83d..e0a0bf7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.26.0", + "version": "2.26.1", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 6c6d00e69ddf93f6eea89da07ec7de372dc2dae3 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Fri, 1 Oct 2021 13:05:32 +0300 Subject: [PATCH 232/247] prepare release 2.26.2 --- CHANGELOG.md | 4 ++++ README.md | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d795bf9..975cc89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Yii2 multiple input change log 2.27.0 (in development) ======================= +2.26.2 +====== +- prevent error loop in case of undefined $wrapper.data('multipleInput') (cebe) + 2.26.1 ====== - remove version from composer.json diff --git a/README.md b/README.md index 1655b8e..6f6f6d3 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.26.1 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.26.2 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/package.json b/package.json index e0a0bf7..d702b81 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.26.1", + "version": "2.26.2", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From 1ba3607cddadf01fdc5ea88815e310e22c86c29a Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 17 Oct 2021 20:17:57 +0300 Subject: [PATCH 233/247] update docs --- docs/configuration.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/configuration.md b/docs/configuration.md index 64b264c..b863ab6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -110,6 +110,24 @@ function($data) {} **tabindex** _integer_: use it to customize a form element `tabindex` +**columnOptions** _array|\Closure_ the HTML attributes for the indivdual table body column. This can be either an array specifying the common HTML attributes for indivdual body column, or an anonymous function that returns an array of the HTML attributes. + +It should have the following signature: +```php +function ($model, $index, $context) +``` +* `$model`: the current data model being rendered +* `$index`: the zero-based index of the data model in the model array +* `$context`: the widget object + +_Supported versions >= 2.18.0_ + +**inputTemplate** _string_ the template of input for customize view. Defailt is `{input}`. + +**Example** + +`
    {input}
    ` + ## Input types Each column in a row can has their own type. Widget supports: From ceeea7bf19a7fbc27eee44e36020fef85e6bd419 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 17 Oct 2021 20:54:33 +0300 Subject: [PATCH 234/247] add missing options to the docs --- docs/configuration.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index b863ab6..fe67e51 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -25,11 +25,15 @@ Default value is `bs` **removeButtonOptions** _array_: the HTML options for `remove` button. Can contains `class` and `label` keys +**cloneButton** _bool_: whether need to enable clone buttons or not + +**cloneButtonOptions** _array_: the HTML options for `remove` button. Can contains `class` and `label` keys + **data** _array_: array of values in case you use widget without model **models** _array_: the list of models. Required in case you use `TabularInput` widget -**allowEmptyList** _boolean_: whether to allow the empty list +**allowEmptyList** _boolean_: whether to allow the empty list. **Deprecateed** use the `min` option instead **columnClass** _string_: the name of column class. You can specify your own class to extend base functionality. Defaults to `unclead\multipleinput\MultipleInputColumn` for `MultipleInput` and `unclead\multipleinput\TabularColumn` for `TabularInput`. @@ -51,8 +55,6 @@ function ($model, $index, $context) **modelClass** _string_: a class of model which is used to render `TabularInput`. You must specify this property when a list of `models` is empty. If this property is not specified the widget will detect it based on a class of `models` -**cloneButton** _bool_: whether need to enable clone buttons or not - **extraButtons** _string\|\Closure_: the HTML content that will be rendered after the buttons. It can be either string or an anonymous function that returns a string which will be treated as HTML content. It should have the following signature: ```php @@ -110,7 +112,11 @@ function($data) {} **tabindex** _integer_: use it to customize a form element `tabindex` -**columnOptions** _array|\Closure_ the HTML attributes for the indivdual table body column. This can be either an array specifying the common HTML attributes for indivdual body column, or an anonymous function that returns an array of the HTML attributes. +**attributeOptions** _array_: client-side options of the attribute, e.g. enableAjaxValidation. You can use this property for custom configuration of the column (attribute). By default, the column will use options which are defined on widget level. + +_Supported versions >= 2.1.0 + +**columnOptions** _array|\Closure_: the HTML attributes for the indivdual table body column. This can be either an array specifying the common HTML attributes for indivdual body column, or an anonymous function that returns an array of the HTML attributes. It should have the following signature: ```php @@ -122,7 +128,7 @@ function ($model, $index, $context) _Supported versions >= 2.18.0_ -**inputTemplate** _string_ the template of input for customize view. Defailt is `{input}`. +**inputTemplate** _string_: the template of input for customize view. Defailt is `{input}`. **Example** From 627c18d8c8f5d63650bd278c7e4e74be40ddb739 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 17 Oct 2021 20:58:27 +0300 Subject: [PATCH 235/247] typo --- docs/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index fe67e51..42d7df6 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -128,7 +128,7 @@ function ($model, $index, $context) _Supported versions >= 2.18.0_ -**inputTemplate** _string_: the template of input for customize view. Defailt is `{input}`. +**inputTemplate** _string_: the template of input for customize view. Default is `{input}`. **Example** From 48ce3a8a1fd4b54b8ade1f106dd1285a6c1459a8 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 24 Oct 2021 21:24:47 +0300 Subject: [PATCH 236/247] move collecting js expressions to a separate class --- src/components/JsCollector.php | 113 +++++++++++++++++++++++++++++++++ src/renderers/BaseRenderer.php | 58 +++++------------ 2 files changed, 128 insertions(+), 43 deletions(-) create mode 100644 src/components/JsCollector.php diff --git a/src/components/JsCollector.php b/src/components/JsCollector.php new file mode 100644 index 0000000..070ad46 --- /dev/null +++ b/src/components/JsCollector.php @@ -0,0 +1,113 @@ +view = $view; + } + + public function onBeforeRender() + { + if (!is_array($this->view->js)) { + return; + } + + $this->jsExclude = []; + foreach ($this->view->js as $position => $scripts) { + foreach ((array)$scripts as $key => $js) { + if (!isset($this->jsExclude[$position])) { + $this->jsExclude[$position] = []; + } + + $this->jsExclude[$position][$key] = $js; + } + } + } + + public function onAfterRender() + { + if (!is_array($this->view->js)) { + return; + } + + foreach (self::POSITIONS_ORDER as $position) { + foreach (ArrayHelper::getValue($this->view->js, $position, []) as $key => $js) { + if (isset($this->jsExclude[$position][$key])) { + continue; + } + + $this->jsExclude[$position][$key] = $js; + + $this->jsInit[$key] = $js; + + unset($this->view->js[$position][$key]); + } + } + } + + public function onAfterPrepareTemplate() + { + if (!is_array($this->view->js)) { + return; + } + + $this->jsTemplates = []; + + foreach (self::POSITIONS_ORDER as $position) { + foreach (ArrayHelper::getValue($this->view->js, $position, []) as $key => $js) { + if (isset($this->jsExclude[$position][$key])) { + continue; + } + + $this->jsTemplates[$key] = $js; + + unset($this->view->js[$position][$key]); + } + } + + } + + /** + * @return array + */ + public function getJsInit() + { + return $this->jsInit; + } + + /** + * @return array + */ + public function getJsTemplates() + { + return $this->jsTemplates; + } +} diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index c2c1c1d..45f3aff 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -24,6 +24,7 @@ use unclead\multipleinput\TabularInput; use unclead\multipleinput\assets\MultipleInputAsset; use unclead\multipleinput\assets\MultipleInputSortableAsset; +use unclead\multipleinput\components\JsCollector; use unclead\multipleinput\components\BaseColumn; /** @@ -355,6 +356,12 @@ protected function getExtraButtons($index, $item) return $content; } + /** + * @return mixed + * + * @throws InvalidConfigException + * @throws NotSupportedException + */ public function render() { $this->initColumns(); @@ -362,57 +369,22 @@ public function render() $view = $this->context->getView(); MultipleInputAsset::register($view); - // Collect all js scripts which were added before rendering of our widget - $jsBefore= []; - if (is_array($view->js)) { - foreach ($view->js as $position => $scripts) { - foreach ((array)$scripts as $key => $js) { - if (!isset($jsBefore[$position])) { - $jsBefore[$position] = []; - } - $jsBefore[$position][$key] = $js; - } - } - } + $jsCollector = new JsCollector($view); - $content = $this->internalRender(); + $jsCollector->onBeforeRender(); - // Collect all js scripts which has to be appended to page before initialization widget - $jsInit = []; - if (is_array($view->js)) { - foreach ($this->jsPositions as $position) { - foreach (ArrayHelper::getValue($view->js, $position, []) as $key => $js) { - if (isset($jsBefore[$position][$key])) { - continue; - } - $jsInit[$key] = $js; - $jsBefore[$position][$key] = $js; - unset($view->js[$position][$key]); - } - } - } + $content = $this->internalRender(); + $jsCollector->onAfterRender(); $template = $this->prepareTemplate(); - - $jsTemplates = []; - if (is_array($view->js)) { - foreach ($this->jsPositions as $position) { - foreach (ArrayHelper::getValue($view->js, $position, []) as $key => $js) { - if (isset($jsBefore[$position][$key])) { - continue; - } - $jsTemplates[$key] = $js; - unset($view->js[$position][$key]); - } - } - } + $jsCollector->onAfterPrepareTemplate(); $options = Json::encode(array_merge([ 'id' => $this->id, 'inputId' => $this->context->options['id'], 'template' => $template, - 'jsInit' => $jsInit, - 'jsTemplates' => $jsTemplates, + 'jsInit' => $jsCollector->getJsInit(), + 'jsTemplates' => $jsCollector->getJsTemplates(), 'max' => $this->max, 'min' => $this->min, 'attributes' => $this->prepareJsAttributes(), @@ -444,7 +416,7 @@ private function registerJsSortable() /** * Returns an array of JQuery sortable plugin options. * You can override this method extend plugin behaviour. - * + * * @return array */ protected function getJsSortableOptions() From 2c713e48268465d044c78472b9a1eff40ee18378 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 29 Jan 2022 21:35:50 +0300 Subject: [PATCH 237/247] (fix) ajax validation doesn't work for newly added/cloned inputs --- CHANGELOG.md | 6 +- README.md | 2 +- package.json | 2 +- src/assets/src/js/jquery.multipleInput.js | 15 ++- src/assets/src/js/jquery.multipleInput.min.js | 2 +- src/components/JsCollector.php | 113 ------------------ 6 files changed, 17 insertions(+), 123 deletions(-) delete mode 100644 src/components/JsCollector.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 975cc89..3e652fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ Yii2 multiple input change log ============================== -2.27.0 (in development) +2.28.0 (in development) ======================= +2.27.0 +====== +- #367 (fix) ajax validation doesn't work for newly added/cloned inputs + 2.26.2 ====== - prevent error loop in case of undefined $wrapper.data('multipleInput') (cebe) diff --git a/README.md b/README.md index 6f6f6d3..61b815b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.26.2 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.27.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/package.json b/package.json index d702b81..f566264 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.26.2", + "version": "2.27.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 7027f9a..cbaaa63 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -363,11 +363,6 @@ } } } - - - if (isActiveFormEnabled) { - addActiveFormAttribute($element); - } }); if (settings.prepend) { @@ -376,6 +371,15 @@ $newRow.hide().appendTo(inputList).fadeIn(300); } + // in order to initialize an active form attribute we need to find an input wrapper and we can do it + // only after adding a new rows to dom tree + if (isActiveFormEnabled) { + $newRow.find('input, select, textarea').each(function (index, element) { + let $element = $(element); + addActiveFormAttribute($element); + }); + } + let jsTemplate = null; for (var i in settings.jsTemplates) { jsTemplate = settings.jsTemplates[i]; @@ -438,7 +442,6 @@ wrapper = ele.closest('.multiple-input').first(), form = ele.closest('form'); - // do not add attribute which are not the part of widget if (wrapper.length === 0) { return; diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 2b11221..fa71f5a 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){"use strict";t.fn.multipleInput=function(e){return o[e]?o[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):o.init.apply(this,arguments)};var e="afterInit",i="beforeAddRow",n="afterAddRow",l="beforeDeleteRow",r="afterDeleteRow",a={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},u=!1,o={init:function(i){if("object"==typeof i){var n=t.extend(!0,{},a,i||{}),l=t("#"+n.id),r=l.closest("form"),o=n.inputId;for(f in n.jsInit)window.eval(n.jsInit[f]);l.data("multipleInput",{settings:n,currentIndex:0}),l.on("click.multipleInput",".js-input-remove",(function(e){e.stopPropagation(),c(t(this))})),l.on("click.multipleInput",".js-input-plus",(function(e){e.stopPropagation(),p(t(this))})),l.on("click.multipleInput",".js-input-clone",(function(e){e.stopPropagation(),s(t(this))}));var f=0,m=t.Event(e),v=setInterval((function(){if("object"==typeof r.data("yiiActiveForm")){var e=r.yiiActiveForm("find",o),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,(function(t,e){-1===["id","input","container"].indexOf(t)&&(i[t]=e)})),n.showGeneralError||r.yiiActiveForm("remove",o)),t.each(n.attributes,(function(e,l){l=t.extend({},i,l),n.attributes[e]=l})),l.data("multipleInput").settings=n,l.find(".multiple-input-list").find("input, select, textarea").each((function(){d(t(this))})),l.data("multipleInput").currentIndex=g(l),u=!0,clearInterval(v),l.trigger(m)}else f++;(0===r.length||f>10)&&(clearInterval(v),u=!1,void 0!==l.data("multipleInput")&&(l.data("multipleInput").currentIndex=g(l)),l.trigger(m))}),100)}else console.error("Options must be an object")},add:function(e){p(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),c(i)},clear:function(){t(this).find(".js-input-remove").each((function(){c(t(this))}))},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},s=function(e){let i=t(e).closest(".multiple-input").first().data("multipleInput").settings,n={};e.closest(".multiple-input-list__item").find("input, select, textarea").each((function(e,l){let r=t(l),a=m(r);if(a){let t=a.replace(i.inputId,"").replace(/-\d+-/,"");r.is(":checkbox")?(n.hasOwnProperty(t)||(n[t]=[]),r.is(":checked")&&n[t].push(r.val())):n[t]=r.val()}})),p(e,n)},p=function(e,l){l=l||{};let r=t(e).closest(".multiple-input").first(),a=r.data("multipleInput"),o=a.settings,s=r.children(".multiple-input-list").first();if(null!==o.max&&v(r)>=o.max)return;let p=a.currentIndex+1,c=I("{"+o.indexPlaceholder+"}",p,o.template),f=t(c);var h=t.Event(i);if(r.trigger(h,[f,p]),!1===h.result)return;f.find("input, select, textarea").each((function(e,i){let n=t(i),r=m(n);if(r){let t=r.replace(o.inputId,"").replace(/-\d+-?/,"");if(l.hasOwnProperty(t)){let e=n.get(0).tagName,i=l[t];switch(e){case"INPUT":n.is(":checkbox")?-1!==i.indexOf(n.val())&&n.prop("checked",!0):n.val(i);break;case"TEXTAREA":n.val(i);break;case"SELECT":if(i&&-1!==i.indexOf("option"))n.append(i);else n.find('option[value="'+i+'"]').length&&n.val(i)}}}u&&d(n)})),o.prepend?f.hide().prependTo(s).fadeIn(300):f.hide().appendTo(s).fadeIn(300);let g=null;for(var x in o.jsTemplates)g=o.jsTemplates[x],g=I("{"+o.indexPlaceholder+"}",p,g),g=I("%7B"+o.indexPlaceholder+"%7D",p,g),window.eval(g);r.data("multipleInput").currentIndex=p;var y=t.Event(n);r.trigger(y,[f,p])},c=function(e){var i=e.closest(".multiple-input").first(),n=e.closest(".multiple-input-list__item"),a=i.data("multipleInput"),o=a.settings,s=v(i);if(s>o.min){var p=t.Event(l);if(i.trigger(p,[n,a.currentIndex]),!1===p.result)return;u&&n.find("input, select, textarea").each((function(e,i){f(t(i))})),n.fadeOut(300,(function(){t(this).remove(),p=t.Event(r),i.trigger(p,[n,s])}))}},d=function(e){var i=m(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!==l.length&&void 0===r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),u={},o=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(o)?u=a.settings.attributes[o]:(o=I(/-\d-/,"-",o),o=I(/-\d/,"",o),a.settings.attributes.hasOwnProperty(o)&&(u=a.settings.attributes[o])),r.yiiActiveForm("add",t.extend({},u,{id:i,input:"#"+i,container:".field-"+i}))}}},f=function(e){var i=m(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},m=function(t){var e=t.attr("id");return void 0===e&&(e=t.data("id")),void 0===e?null:e},v=function(t){return h(t).length},h=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter((function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}))},g=function(e){let i=0;return h(e).each((function(e,n){let l=t(n).data("index");l>i&&(i=l)})),i},I=function(t,e,i){return i instanceof String||"string"==typeof i?i.split(t).join(e):(console.warn("Call replaceAll for non-string value: "+i),i)}}(window.jQuery); \ No newline at end of file +!function(t){"use strict";t.fn.multipleInput=function(e){return o[e]?o[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):o.init.apply(this,arguments)};var e="afterInit",i="beforeAddRow",n="afterAddRow",l="beforeDeleteRow",r="afterDeleteRow",a={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},u=!1,o={init:function(i){if("object"==typeof i){var n=t.extend(!0,{},a,i||{}),l=t("#"+n.id),r=l.closest("form"),o=n.inputId;for(f in n.jsInit)window.eval(n.jsInit[f]);l.data("multipleInput",{settings:n,currentIndex:0}),l.on("click.multipleInput",".js-input-remove",(function(e){e.stopPropagation(),c(t(this))})),l.on("click.multipleInput",".js-input-plus",(function(e){e.stopPropagation(),p(t(this))})),l.on("click.multipleInput",".js-input-clone",(function(e){e.stopPropagation(),s(t(this))}));var f=0,m=t.Event(e),v=setInterval((function(){if("object"==typeof r.data("yiiActiveForm")){var e=r.yiiActiveForm("find",o),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,(function(t,e){-1===["id","input","container"].indexOf(t)&&(i[t]=e)})),n.showGeneralError||r.yiiActiveForm("remove",o)),t.each(n.attributes,(function(e,l){l=t.extend({},i,l),n.attributes[e]=l})),l.data("multipleInput").settings=n,l.find(".multiple-input-list").find("input, select, textarea").each((function(){d(t(this))})),l.data("multipleInput").currentIndex=g(l),u=!0,clearInterval(v),l.trigger(m)}else f++;(0===r.length||f>10)&&(clearInterval(v),u=!1,void 0!==l.data("multipleInput")&&(l.data("multipleInput").currentIndex=g(l)),l.trigger(m))}),100)}else console.error("Options must be an object")},add:function(e){p(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),c(i)},clear:function(){t(this).find(".js-input-remove").each((function(){c(t(this))}))},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},s=function(e){let i=t(e).closest(".multiple-input").first().data("multipleInput").settings,n={};e.closest(".multiple-input-list__item").find("input, select, textarea").each((function(e,l){let r=t(l),a=m(r);if(a){let t=a.replace(i.inputId,"").replace(/-\d+-/,"");r.is(":checkbox")?(n.hasOwnProperty(t)||(n[t]=[]),r.is(":checked")&&n[t].push(r.val())):n[t]=r.val()}})),p(e,n)},p=function(e,l){l=l||{};let r=t(e).closest(".multiple-input").first(),a=r.data("multipleInput"),o=a.settings,s=r.children(".multiple-input-list").first();if(null!==o.max&&v(r)>=o.max)return;let p=a.currentIndex+1,c=I("{"+o.indexPlaceholder+"}",p,o.template),f=t(c);var h=t.Event(i);if(r.trigger(h,[f,p]),!1===h.result)return;f.find("input, select, textarea").each((function(e,i){let n=t(i),r=m(n);if(r){let t=r.replace(o.inputId,"").replace(/-\d+-?/,"");if(l.hasOwnProperty(t)){let e=n.get(0).tagName,i=l[t];switch(e){case"INPUT":n.is(":checkbox")?-1!==i.indexOf(n.val())&&n.prop("checked",!0):n.val(i);break;case"TEXTAREA":n.val(i);break;case"SELECT":if(i&&-1!==i.indexOf("option"))n.append(i);else n.find('option[value="'+i+'"]').length&&n.val(i)}}}})),o.prepend?f.hide().prependTo(s).fadeIn(300):f.hide().appendTo(s).fadeIn(300),u&&f.find("input, select, textarea").each((function(e,i){let n=t(i);d(n)}));let g=null;for(var x in o.jsTemplates)g=o.jsTemplates[x],g=I("{"+o.indexPlaceholder+"}",p,g),g=I("%7B"+o.indexPlaceholder+"%7D",p,g),window.eval(g);r.data("multipleInput").currentIndex=p;var y=t.Event(n);r.trigger(y,[f,p])},c=function(e){var i=e.closest(".multiple-input").first(),n=e.closest(".multiple-input-list__item"),a=i.data("multipleInput"),o=a.settings,s=v(i);if(s>o.min){var p=t.Event(l);if(i.trigger(p,[n,a.currentIndex]),!1===p.result)return;u&&n.find("input, select, textarea").each((function(e,i){f(t(i))})),n.fadeOut(300,(function(){t(this).remove(),p=t.Event(r),i.trigger(p,[n,s])}))}},d=function(e){var i=m(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!==l.length&&void 0===r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),u={},o=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(o)?u=a.settings.attributes[o]:(o=I(/-\d-/,"-",o),o=I(/-\d/,"",o),a.settings.attributes.hasOwnProperty(o)&&(u=a.settings.attributes[o])),r.yiiActiveForm("add",t.extend({},u,{id:i,input:"#"+i,container:".field-"+i}))}}},f=function(e){var i=m(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},m=function(t){var e=t.attr("id");return void 0===e&&(e=t.data("id")),void 0===e?null:e},v=function(t){return h(t).length},h=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter((function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}))},g=function(e){let i=0;return h(e).each((function(e,n){let l=t(n).data("index");l>i&&(i=l)})),i},I=function(t,e,i){return i instanceof String||"string"==typeof i?i.split(t).join(e):(console.warn("Call replaceAll for non-string value: "+i),i)}}(window.jQuery); \ No newline at end of file diff --git a/src/components/JsCollector.php b/src/components/JsCollector.php deleted file mode 100644 index 070ad46..0000000 --- a/src/components/JsCollector.php +++ /dev/null @@ -1,113 +0,0 @@ -view = $view; - } - - public function onBeforeRender() - { - if (!is_array($this->view->js)) { - return; - } - - $this->jsExclude = []; - foreach ($this->view->js as $position => $scripts) { - foreach ((array)$scripts as $key => $js) { - if (!isset($this->jsExclude[$position])) { - $this->jsExclude[$position] = []; - } - - $this->jsExclude[$position][$key] = $js; - } - } - } - - public function onAfterRender() - { - if (!is_array($this->view->js)) { - return; - } - - foreach (self::POSITIONS_ORDER as $position) { - foreach (ArrayHelper::getValue($this->view->js, $position, []) as $key => $js) { - if (isset($this->jsExclude[$position][$key])) { - continue; - } - - $this->jsExclude[$position][$key] = $js; - - $this->jsInit[$key] = $js; - - unset($this->view->js[$position][$key]); - } - } - } - - public function onAfterPrepareTemplate() - { - if (!is_array($this->view->js)) { - return; - } - - $this->jsTemplates = []; - - foreach (self::POSITIONS_ORDER as $position) { - foreach (ArrayHelper::getValue($this->view->js, $position, []) as $key => $js) { - if (isset($this->jsExclude[$position][$key])) { - continue; - } - - $this->jsTemplates[$key] = $js; - - unset($this->view->js[$position][$key]); - } - } - - } - - /** - * @return array - */ - public function getJsInit() - { - return $this->jsInit; - } - - /** - * @return array - */ - public function getJsTemplates() - { - return $this->jsTemplates; - } -} From c63151f832e7184f8fdf672e9426b0c6159fb2c7 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 29 Jan 2022 21:38:18 +0300 Subject: [PATCH 238/247] Revert "move collecting js expressions to a separate class" This reverts commit 48ce3a8a1fd4b54b8ade1f106dd1285a6c1459a8. --- src/renderers/BaseRenderer.php | 58 +++++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 45f3aff..c2c1c1d 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -24,7 +24,6 @@ use unclead\multipleinput\TabularInput; use unclead\multipleinput\assets\MultipleInputAsset; use unclead\multipleinput\assets\MultipleInputSortableAsset; -use unclead\multipleinput\components\JsCollector; use unclead\multipleinput\components\BaseColumn; /** @@ -356,12 +355,6 @@ protected function getExtraButtons($index, $item) return $content; } - /** - * @return mixed - * - * @throws InvalidConfigException - * @throws NotSupportedException - */ public function render() { $this->initColumns(); @@ -369,22 +362,57 @@ public function render() $view = $this->context->getView(); MultipleInputAsset::register($view); - $jsCollector = new JsCollector($view); + // Collect all js scripts which were added before rendering of our widget + $jsBefore= []; + if (is_array($view->js)) { + foreach ($view->js as $position => $scripts) { + foreach ((array)$scripts as $key => $js) { + if (!isset($jsBefore[$position])) { + $jsBefore[$position] = []; + } + $jsBefore[$position][$key] = $js; + } + } + } - $jsCollector->onBeforeRender(); + $content = $this->internalRender(); - $content = $this->internalRender(); - $jsCollector->onAfterRender(); + // Collect all js scripts which has to be appended to page before initialization widget + $jsInit = []; + if (is_array($view->js)) { + foreach ($this->jsPositions as $position) { + foreach (ArrayHelper::getValue($view->js, $position, []) as $key => $js) { + if (isset($jsBefore[$position][$key])) { + continue; + } + $jsInit[$key] = $js; + $jsBefore[$position][$key] = $js; + unset($view->js[$position][$key]); + } + } + } $template = $this->prepareTemplate(); - $jsCollector->onAfterPrepareTemplate(); + + $jsTemplates = []; + if (is_array($view->js)) { + foreach ($this->jsPositions as $position) { + foreach (ArrayHelper::getValue($view->js, $position, []) as $key => $js) { + if (isset($jsBefore[$position][$key])) { + continue; + } + $jsTemplates[$key] = $js; + unset($view->js[$position][$key]); + } + } + } $options = Json::encode(array_merge([ 'id' => $this->id, 'inputId' => $this->context->options['id'], 'template' => $template, - 'jsInit' => $jsCollector->getJsInit(), - 'jsTemplates' => $jsCollector->getJsTemplates(), + 'jsInit' => $jsInit, + 'jsTemplates' => $jsTemplates, 'max' => $this->max, 'min' => $this->min, 'attributes' => $this->prepareJsAttributes(), @@ -416,7 +444,7 @@ private function registerJsSortable() /** * Returns an array of JQuery sortable plugin options. * You can override this method extend plugin behaviour. - * + * * @return array */ protected function getJsSortableOptions() From 8763afad71936c5490cfb1d982240bf7a64b8177 Mon Sep 17 00:00:00 2001 From: KirDE Date: Wed, 28 Sep 2022 19:56:12 +0200 Subject: [PATCH 239/247] Allow use inside Editable IDs created by yii generateRandomString have capital and small letters, in the normalize function capital letters were removed only for indexPlaceholder and in id repained capital so it was not everywhere replaced in js Templates. --- src/components/BaseColumn.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index d4a9928..bf65c97 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -280,7 +280,7 @@ abstract public function getElementName($index, $withPrefix = true); * @return mixed */ private function normalize($name) { - return str_replace(['[]', '][', '[', ']', ' ', '.'], ['', '-', '-', '', '-', '-'], strtolower($name)); + return str_replace(['[]', '][', '[', ']', ' ', '.'], ['', '-', '-', '', '-', '-'], $name); } /** From 977ca80c115fbfc1293dbe0eb811c754f6637964 Mon Sep 17 00:00:00 2001 From: Nikolya Date: Mon, 19 Feb 2024 22:47:14 +0200 Subject: [PATCH 240/247] Fix sorting Replace outdated jquery-sortable.js with a modern alternative (sortable.js) with better flexibility and support for mobile devices --- CHANGELOG.md | 6 +- src/assets/MultipleInputSortableAsset.php | 2 +- src/assets/src/js/jquery-sortable.js | 693 ----- src/assets/src/js/jquery-sortable.min.js | 1 - src/assets/src/js/sortable.js | 3362 +++++++++++++++++++++ src/assets/src/js/sortable.min.js | 2 + src/renderers/BaseRenderer.php | 24 +- 7 files changed, 3380 insertions(+), 710 deletions(-) delete mode 100644 src/assets/src/js/jquery-sortable.js delete mode 100644 src/assets/src/js/jquery-sortable.min.js create mode 100644 src/assets/src/js/sortable.js create mode 100644 src/assets/src/js/sortable.min.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e652fb..b58aa62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ Yii2 multiple input change log ============================== -2.28.0 (in development) +2.29.0 (in development) ======================= +2.28.0 +======================= +- replace outdated jquery-sortable.js with a modern alternative (sortable.js) (sankam-nikolya) + 2.27.0 ====== - #367 (fix) ajax validation doesn't work for newly added/cloned inputs diff --git a/src/assets/MultipleInputSortableAsset.php b/src/assets/MultipleInputSortableAsset.php index 5d72395..152d558 100644 --- a/src/assets/MultipleInputSortableAsset.php +++ b/src/assets/MultipleInputSortableAsset.php @@ -25,7 +25,7 @@ public function init() $this->sourcePath = __DIR__ . '/src/'; $this->js = [ - YII_DEBUG ? 'js/jquery-sortable.js' : 'js/jquery-sortable.min.js' + YII_DEBUG ? 'js/sortable.js' : 'js/sortable.min.js' ]; $this->css = [ diff --git a/src/assets/src/js/jquery-sortable.js b/src/assets/src/js/jquery-sortable.js deleted file mode 100644 index c92fa6c..0000000 --- a/src/assets/src/js/jquery-sortable.js +++ /dev/null @@ -1,693 +0,0 @@ -/* =================================================== - * jquery-sortable.js v0.9.13 - * http://johnny.github.com/jquery-sortable/ - * =================================================== - * Copyright (c) 2012 Jonas von Andrian - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * * The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY - * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * ========================================================== */ - -!function ( $, window, pluginName, undefined){ - var eventNames, - containerDefaults = { - // If true, items can be dragged from this container - drag: true, - // If true, items can be droped onto this container - drop: true, - // Exclude items from being draggable, if the - // selector matches the item - exclude: "", - // If true, search for nested containers within an item.If you nest containers, - // either the original selector with which you call the plugin must only match the top containers, - // or you need to specify a group (see the bootstrap nav example) - nested: true, - // If true, the items are assumed to be arranged vertically - vertical: true - }, // end container defaults - groupDefaults = { - // This is executed after the placeholder has been moved. - // $closestItemOrContainer contains the closest item, the placeholder - // has been put at or the closest empty Container, the placeholder has - // been appended to. - afterMove: function ($placeholder, container, $closestItemOrContainer) { - }, - // The exact css path between the container and its items, e.g. "> tbody" - containerPath: "", - // The css selector of the containers - containerSelector: "ol, ul", - // Distance the mouse has to travel to start dragging - distance: 0, - // Time in milliseconds after mousedown until dragging should start. - // This option can be used to prevent unwanted drags when clicking on an element. - delay: 0, - // The css selector of the drag handle - handle: "", - // The exact css path between the item and its subcontainers. - // It should only match the immediate items of a container. - // No item of a subcontainer should be matched. E.g. for ol>div>li the itemPath is "> div" - itemPath: "", - // The css selector of the items - itemSelector: "li", - // The class given to "body" while an item is being dragged - bodyClass: "dragging", - // The class giving to an item while being dragged - draggedClass: "dragged", - // Check if the dragged item may be inside the container. - // Use with care, since the search for a valid container entails a depth first search - // and may be quite expensive. - isValidTarget: function ($item, container) { - return true - }, - // Executed before onDrop if placeholder is detached. - // This happens if pullPlaceholder is set to false and the drop occurs outside a container. - onCancel: function ($item, container, _super, event) { - }, - // Executed at the beginning of a mouse move event. - // The Placeholder has not been moved yet. - onDrag: function ($item, position, _super, event) { - $item.css(position) - }, - // Called after the drag has been started, - // that is the mouse button is being held down and - // the mouse is moving. - // The container is the closest initialized container. - // Therefore it might not be the container, that actually contains the item. - onDragStart: function ($item, container, _super, event) { - $item.css({ - height: $item.outerHeight(), - width: $item.outerWidth() - }) - $item.addClass(container.group.options.draggedClass) - $("body").addClass(container.group.options.bodyClass) - }, - // Called when the mouse button is being released - onDrop: function ($item, container, _super, event) { - $item.removeClass(container.group.options.draggedClass).removeAttr("style") - $("body").removeClass(container.group.options.bodyClass) - }, - // Called on mousedown. If falsy value is returned, the dragging will not start. - // Ignore if element clicked is input, select or textarea - onMousedown: function ($item, _super, event) { - if (!event.target.nodeName.match(/^(input|select|textarea)$/i)) { - event.preventDefault() - return true - } - }, - // The class of the placeholder (must match placeholder option markup) - placeholderClass: "placeholder", - // Template for the placeholder. Can be any valid jQuery input - // e.g. a string, a DOM element. - // The placeholder must have the class "placeholder" - placeholder: '
  • ', - // If true, the position of the placeholder is calculated on every mousemove. - // If false, it is only calculated when the mouse is above a container. - pullPlaceholder: true, - // Specifies serialization of the container group. - // The pair $parent/$children is either container/items or item/subcontainers. - serialize: function ($parent, $children, parentIsContainer) { - var result = $.extend({}, $parent.data()) - - if(parentIsContainer) - return [$children] - else if ($children[0]){ - result.children = $children - } - - delete result.subContainers - delete result.sortable - - return result - }, - // Set tolerance while dragging. Positive values decrease sensitivity, - // negative values increase it. - tolerance: 0 - }, // end group defaults - containerGroups = {}, - groupCounter = 0, - emptyBox = { - left: 0, - top: 0, - bottom: 0, - right:0 - }, - eventNames = { - start: "touchstart.sortable mousedown.sortable", - drop: "touchend.sortable touchcancel.sortable mouseup.sortable", - drag: "touchmove.sortable mousemove.sortable", - scroll: "scroll.sortable" - }, - subContainerKey = "subContainers" - - /* - * a is Array [left, right, top, bottom] - * b is array [left, top] - */ - function d(a,b) { - var x = Math.max(0, a[0] - b[0], b[0] - a[1]), - y = Math.max(0, a[2] - b[1], b[1] - a[3]) - return x+y; - } - - function setDimensions(array, dimensions, tolerance, useOffset) { - var i = array.length, - offsetMethod = useOffset ? "offset" : "position" - tolerance = tolerance || 0 - - while(i--){ - var el = array[i].el ? array[i].el : $(array[i]), - // use fitting method - pos = el[offsetMethod]() - pos.left += parseInt(el.css('margin-left'), 10) - pos.top += parseInt(el.css('margin-top'),10) - dimensions[i] = [ - pos.left - tolerance, - pos.left + el.outerWidth() + tolerance, - pos.top - tolerance, - pos.top + el.outerHeight() + tolerance - ] - } - } - - function getRelativePosition(pointer, element) { - var offset = element.offset() - return { - left: pointer.left - offset.left, - top: pointer.top - offset.top - } - } - - function sortByDistanceDesc(dimensions, pointer, lastPointer) { - pointer = [pointer.left, pointer.top] - lastPointer = lastPointer && [lastPointer.left, lastPointer.top] - - var dim, - i = dimensions.length, - distances = [] - - while(i--){ - dim = dimensions[i] - distances[i] = [i,d(dim,pointer), lastPointer && d(dim, lastPointer)] - } - distances = distances.sort(function (a,b) { - return b[1] - a[1] || b[2] - a[2] || b[0] - a[0] - }) - - // last entry is the closest - return distances - } - - function ContainerGroup(options) { - this.options = $.extend({}, groupDefaults, options) - this.containers = [] - - if(!this.options.rootGroup){ - this.scrollProxy = $.proxy(this.scroll, this) - this.dragProxy = $.proxy(this.drag, this) - this.dropProxy = $.proxy(this.drop, this) - this.placeholder = $(this.options.placeholder) - - if(!options.isValidTarget) - this.options.isValidTarget = undefined - } - } - - ContainerGroup.get = function (options) { - if(!containerGroups[options.group]) { - if(options.group === undefined) - options.group = groupCounter ++ - - containerGroups[options.group] = new ContainerGroup(options) - } - - return containerGroups[options.group] - } - - ContainerGroup.prototype = { - dragInit: function (e, itemContainer) { - this.$document = $(itemContainer.el[0].ownerDocument) - - // get item to drag - var closestItem = $(e.target).closest(this.options.itemSelector); - // using the length of this item, prevents the plugin from being started if there is no handle being clicked on. - // this may also be helpful in instantiating multidrag. - if (closestItem.length) { - this.item = closestItem; - this.itemContainer = itemContainer; - if (this.item.is(this.options.exclude) || !this.options.onMousedown(this.item, groupDefaults.onMousedown, e)) { - return; - } - this.setPointer(e); - this.toggleListeners('on'); - this.setupDelayTimer(); - this.dragInitDone = true; - } - }, - drag: function (e) { - if(!this.dragging){ - if(!this.distanceMet(e) || !this.delayMet) - return - - this.options.onDragStart(this.item, this.itemContainer, groupDefaults.onDragStart, e) - this.item.before(this.placeholder) - this.dragging = true - } - - this.setPointer(e) - // place item under the cursor - this.options.onDrag(this.item, - getRelativePosition(this.pointer, this.item.offsetParent()), - groupDefaults.onDrag, - e) - - var p = this.getPointer(e), - box = this.sameResultBox, - t = this.options.tolerance - - if(!box || box.top - t > p.top || box.bottom + t < p.top || box.left - t > p.left || box.right + t < p.left) - if(!this.searchValidTarget()){ - this.placeholder.detach() - this.lastAppendedItem = undefined - } - }, - drop: function (e) { - this.toggleListeners('off') - - this.dragInitDone = false - - if(this.dragging){ - // processing Drop, check if placeholder is detached - if(this.placeholder.closest("html")[0]){ - this.placeholder.before(this.item).detach() - } else { - this.options.onCancel(this.item, this.itemContainer, groupDefaults.onCancel, e) - } - this.options.onDrop(this.item, this.getContainer(this.item), groupDefaults.onDrop, e) - - // cleanup - this.clearDimensions() - this.clearOffsetParent() - this.lastAppendedItem = this.sameResultBox = undefined - this.dragging = false - } - }, - searchValidTarget: function (pointer, lastPointer) { - if(!pointer){ - pointer = this.relativePointer || this.pointer - lastPointer = this.lastRelativePointer || this.lastPointer - } - - var distances = sortByDistanceDesc(this.getContainerDimensions(), - pointer, - lastPointer), - i = distances.length - - while(i--){ - var index = distances[i][0], - distance = distances[i][1] - - if(!distance || this.options.pullPlaceholder){ - var container = this.containers[index] - if(!container.disabled){ - if(!this.$getOffsetParent()){ - var offsetParent = container.getItemOffsetParent() - pointer = getRelativePosition(pointer, offsetParent) - lastPointer = getRelativePosition(lastPointer, offsetParent) - } - if(container.searchValidTarget(pointer, lastPointer)) - return true - } - } - } - if(this.sameResultBox) - this.sameResultBox = undefined - }, - movePlaceholder: function (container, item, method, sameResultBox) { - var lastAppendedItem = this.lastAppendedItem - if(!sameResultBox && lastAppendedItem && lastAppendedItem[0] === item[0]) - return; - - item[method](this.placeholder) - this.lastAppendedItem = item - this.sameResultBox = sameResultBox - this.options.afterMove(this.placeholder, container, item) - }, - getContainerDimensions: function () { - if(!this.containerDimensions) - setDimensions(this.containers, this.containerDimensions = [], this.options.tolerance, !this.$getOffsetParent()) - return this.containerDimensions - }, - getContainer: function (element) { - return element.closest(this.options.containerSelector).data(pluginName) - }, - $getOffsetParent: function () { - if(this.offsetParent === undefined){ - var i = this.containers.length - 1, - offsetParent = this.containers[i].getItemOffsetParent() - - if(!this.options.rootGroup){ - while(i--){ - if(offsetParent[0] != this.containers[i].getItemOffsetParent()[0]){ - // If every container has the same offset parent, - // use position() which is relative to this parent, - // otherwise use offset() - // compare #setDimensions - offsetParent = false - break; - } - } - } - - this.offsetParent = offsetParent - } - return this.offsetParent - }, - setPointer: function (e) { - var pointer = this.getPointer(e) - - if(this.$getOffsetParent()){ - var relativePointer = getRelativePosition(pointer, this.$getOffsetParent()) - this.lastRelativePointer = this.relativePointer - this.relativePointer = relativePointer - } - - this.lastPointer = this.pointer - this.pointer = pointer - }, - distanceMet: function (e) { - var currentPointer = this.getPointer(e) - return (Math.max( - Math.abs(this.pointer.left - currentPointer.left), - Math.abs(this.pointer.top - currentPointer.top) - ) >= this.options.distance) - }, - getPointer: function(e) { - var o = e.originalEvent || e.originalEvent.touches && e.originalEvent.touches[0] - return { - left: e.pageX || o.pageX, - top: e.pageY || o.pageY - } - }, - setupDelayTimer: function () { - var that = this - this.delayMet = !this.options.delay - - // init delay timer if needed - if (!this.delayMet) { - clearTimeout(this._mouseDelayTimer); - this._mouseDelayTimer = setTimeout(function() { - that.delayMet = true - }, this.options.delay) - } - }, - scroll: function (e) { - this.clearDimensions() - this.clearOffsetParent() // TODO is this needed? - }, - toggleListeners: function (method) { - var that = this, - events = ['drag','drop','scroll'] - - $.each(events,function (i,event) { - that.$document[method](eventNames[event], that[event + 'Proxy']) - }) - }, - clearOffsetParent: function () { - this.offsetParent = undefined - }, - // Recursively clear container and item dimensions - clearDimensions: function () { - this.traverse(function(object){ - object._clearDimensions() - }) - }, - traverse: function(callback) { - callback(this) - var i = this.containers.length - while(i--){ - this.containers[i].traverse(callback) - } - }, - _clearDimensions: function(){ - this.containerDimensions = undefined - }, - _destroy: function () { - containerGroups[this.options.group] = undefined - } - } - - function Container(element, options) { - this.el = element - this.options = $.extend( {}, containerDefaults, options) - - this.group = ContainerGroup.get(this.options) - this.rootGroup = this.options.rootGroup || this.group - this.handle = this.rootGroup.options.handle || this.rootGroup.options.itemSelector - - var itemPath = this.rootGroup.options.itemPath - this.target = itemPath ? this.el.find(itemPath) : this.el - - this.target.on(eventNames.start, this.handle, $.proxy(this.dragInit, this)) - - if(this.options.drop) - this.group.containers.push(this) - } - - Container.prototype = { - dragInit: function (e) { - var rootGroup = this.rootGroup - - if( !this.disabled && - !rootGroup.dragInitDone && - this.options.drag && - this.isValidDrag(e)) { - rootGroup.dragInit(e, this) - } - }, - isValidDrag: function(e) { - return e.which == 1 || - e.type == "touchstart" && e.originalEvent.touches.length == 1 - }, - searchValidTarget: function (pointer, lastPointer) { - var distances = sortByDistanceDesc(this.getItemDimensions(), - pointer, - lastPointer), - i = distances.length, - rootGroup = this.rootGroup, - validTarget = !rootGroup.options.isValidTarget || - rootGroup.options.isValidTarget(rootGroup.item, this) - - if(!i && validTarget){ - rootGroup.movePlaceholder(this, this.target, "append") - return true - } else - while(i--){ - var index = distances[i][0], - distance = distances[i][1] - if(!distance && this.hasChildGroup(index)){ - var found = this.getContainerGroup(index).searchValidTarget(pointer, lastPointer) - if(found) - return true - } - else if(validTarget){ - this.movePlaceholder(index, pointer) - return true - } - } - }, - movePlaceholder: function (index, pointer) { - var item = $(this.items[index]), - dim = this.itemDimensions[index], - method = "after", - width = item.outerWidth(), - height = item.outerHeight(), - offset = item.offset(), - sameResultBox = { - left: offset.left, - right: offset.left + width, - top: offset.top, - bottom: offset.top + height - } - if(this.options.vertical){ - var yCenter = (dim[2] + dim[3]) / 2, - inUpperHalf = pointer.top <= yCenter - if(inUpperHalf){ - method = "before" - sameResultBox.bottom -= height / 2 - } else - sameResultBox.top += height / 2 - } else { - var xCenter = (dim[0] + dim[1]) / 2, - inLeftHalf = pointer.left <= xCenter - if(inLeftHalf){ - method = "before" - sameResultBox.right -= width / 2 - } else - sameResultBox.left += width / 2 - } - if(this.hasChildGroup(index)) - sameResultBox = emptyBox - this.rootGroup.movePlaceholder(this, item, method, sameResultBox) - }, - getItemDimensions: function () { - if(!this.itemDimensions){ - this.items = this.$getChildren(this.el, "item").filter( - ":not(." + this.group.options.placeholderClass + ", ." + this.group.options.draggedClass + ")" - ).get() - setDimensions(this.items, this.itemDimensions = [], this.options.tolerance) - } - return this.itemDimensions - }, - getItemOffsetParent: function () { - var offsetParent, - el = this.el - // Since el might be empty we have to check el itself and - // can not do something like el.children().first().offsetParent() - if(el.css("position") === "relative" || el.css("position") === "absolute" || el.css("position") === "fixed") - offsetParent = el - else - offsetParent = el.offsetParent() - return offsetParent - }, - hasChildGroup: function (index) { - return this.options.nested && this.getContainerGroup(index) - }, - getContainerGroup: function (index) { - var childGroup = $.data(this.items[index], subContainerKey) - if( childGroup === undefined){ - var childContainers = this.$getChildren(this.items[index], "container") - childGroup = false - - if(childContainers[0]){ - var options = $.extend({}, this.options, { - rootGroup: this.rootGroup, - group: groupCounter ++ - }) - childGroup = childContainers[pluginName](options).data(pluginName).group - } - $.data(this.items[index], subContainerKey, childGroup) - } - return childGroup - }, - $getChildren: function (parent, type) { - var options = this.rootGroup.options, - path = options[type + "Path"], - selector = options[type + "Selector"] - - parent = $(parent) - if(path) - parent = parent.find(path) - - return parent.children(selector) - }, - _serialize: function (parent, isContainer) { - var that = this, - childType = isContainer ? "item" : "container", - - children = this.$getChildren(parent, childType).not(this.options.exclude).map(function () { - return that._serialize($(this), !isContainer) - }).get() - - return this.rootGroup.options.serialize(parent, children, isContainer) - }, - traverse: function(callback) { - $.each(this.items || [], function(item){ - var group = $.data(this, subContainerKey) - if(group) - group.traverse(callback) - }); - - callback(this) - }, - _clearDimensions: function () { - this.itemDimensions = undefined - }, - _destroy: function() { - var that = this; - - this.target.off(eventNames.start, this.handle); - this.el.removeData(pluginName) - - if(this.options.drop) - this.group.containers = $.grep(this.group.containers, function(val){ - return val != that - }) - - $.each(this.items || [], function(){ - $.removeData(this, subContainerKey) - }) - } - } - - var API = { - enable: function() { - this.traverse(function(object){ - object.disabled = false - }) - }, - disable: function (){ - this.traverse(function(object){ - object.disabled = true - }) - }, - serialize: function () { - return this._serialize(this.el, true) - }, - refresh: function() { - this.traverse(function(object){ - object._clearDimensions() - }) - }, - destroy: function () { - this.traverse(function(object){ - object._destroy(); - }) - } - } - - $.extend(Container.prototype, API) - - /** - * jQuery API - * - * Parameters are - * either options on init - * or a method name followed by arguments to pass to the method - */ - $.fn[pluginName] = function(methodOrOptions) { - var args = Array.prototype.slice.call(arguments, 1) - - return this.map(function(){ - var $t = $(this), - object = $t.data(pluginName) - - if(object && API[methodOrOptions]) - return API[methodOrOptions].apply(object, args) || this - else if(!object && (methodOrOptions === undefined || - typeof methodOrOptions === "object")) - $t.data(pluginName, new Container($t, methodOrOptions)) - - return this - }); - }; - -}(jQuery, window, 'sorting'); diff --git a/src/assets/src/js/jquery-sortable.min.js b/src/assets/src/js/jquery-sortable.min.js deleted file mode 100644 index c849a74..0000000 --- a/src/assets/src/js/jquery-sortable.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(t,e,i,o){function s(t,e){var i=Math.max(0,t[0]-e[0],e[0]-t[1]),o=Math.max(0,t[2]-e[1],e[1]-t[3]);return i+o}function n(e,i,o,s){var n=e.length,r=s?"offset":"position";for(o=o||0;n--;){var a=e[n].el?e[n].el:t(e[n]),h=a[r]();h.left+=parseInt(a.css("margin-left"),10),h.top+=parseInt(a.css("margin-top"),10),i[n]=[h.left-o,h.left+a.outerWidth()+o,h.top-o,h.top+a.outerHeight()+o]}}function r(t,e){var i=e.offset();return{left:t.left-i.left,top:t.top-i.top}}function a(t,e,i){e=[e.left,e.top],i=i&&[i.left,i.top];for(var o,n=t.length,r=[];n--;)o=t[n],r[n]=[n,s(o,e),i&&s(o,i)];return r=r.sort(function(t,e){return e[1]-t[1]||e[2]-t[2]||e[0]-t[0]})}function h(e){this.options=t.extend({},p,e),this.containers=[],this.options.rootGroup||(this.scrollProxy=t.proxy(this.scroll,this),this.dragProxy=t.proxy(this.drag,this),this.dropProxy=t.proxy(this.drop,this),this.placeholder=t(this.options.placeholder),e.isValidTarget||(this.options.isValidTarget=o))}function l(e,i){this.el=e,this.options=t.extend({},u,i),this.group=h.get(this.options),this.rootGroup=this.options.rootGroup||this.group,this.handle=this.rootGroup.options.handle||this.rootGroup.options.itemSelector;var o=this.rootGroup.options.itemPath;this.target=o?this.el.find(o):this.el,this.target.on(c.start,this.handle,t.proxy(this.dragInit,this)),this.options.drop&&this.group.containers.push(this)}var c,u={drag:!0,drop:!0,exclude:"",nested:!0,vertical:!0},p={afterMove:function(t,e,i){},containerPath:"",containerSelector:"ol, ul",distance:0,delay:0,handle:"",itemPath:"",itemSelector:"li",bodyClass:"dragging",draggedClass:"dragged",isValidTarget:function(t,e){return!0},onCancel:function(t,e,i,o){},onDrag:function(t,e,i,o){t.css(e)},onDragStart:function(e,i,o,s){e.css({height:e.outerHeight(),width:e.outerWidth()}),e.addClass(i.group.options.draggedClass),t("body").addClass(i.group.options.bodyClass)},onDrop:function(e,i,o,s){e.removeClass(i.group.options.draggedClass).removeAttr("style"),t("body").removeClass(i.group.options.bodyClass)},onMousedown:function(t,e,i){if(!i.target.nodeName.match(/^(input|select|textarea)$/i))return i.preventDefault(),!0},placeholderClass:"placeholder",placeholder:'
  • ',pullPlaceholder:!0,serialize:function(e,i,o){var s=t.extend({},e.data());return o?[i]:(i[0]&&(s.children=i),delete s.subContainers,delete s.sortable,s)},tolerance:0},f={},d=0,g={left:0,top:0,bottom:0,right:0},c={start:"touchstart.sortable mousedown.sortable",drop:"touchend.sortable touchcancel.sortable mouseup.sortable",drag:"touchmove.sortable mousemove.sortable",scroll:"scroll.sortable"},m="subContainers";h.get=function(t){return f[t.group]||(t.group===o&&(t.group=d++),f[t.group]=new h(t)),f[t.group]},h.prototype={dragInit:function(e,i){this.$document=t(i.el[0].ownerDocument);var o=t(e.target).closest(this.options.itemSelector);if(o.length){if(this.item=o,this.itemContainer=i,this.item.is(this.options.exclude)||!this.options.onMousedown(this.item,p.onMousedown,e))return;this.setPointer(e),this.toggleListeners("on"),this.setupDelayTimer(),this.dragInitDone=!0}},drag:function(t){if(!this.dragging){if(!this.distanceMet(t)||!this.delayMet)return;this.options.onDragStart(this.item,this.itemContainer,p.onDragStart,t),this.item.before(this.placeholder),this.dragging=!0}this.setPointer(t),this.options.onDrag(this.item,r(this.pointer,this.item.offsetParent()),p.onDrag,t);var e=this.getPointer(t),i=this.sameResultBox,s=this.options.tolerance;(!i||i.top-s>e.top||i.bottom+se.left||i.right+s=this.options.distance},getPointer:function(t){var e=t.originalEvent||t.originalEvent.touches&&t.originalEvent.touches[0];return{left:t.pageX||e.pageX,top:t.pageY||e.pageY}},setupDelayTimer:function(){var t=this;this.delayMet=!this.options.delay,this.delayMet||(clearTimeout(this._mouseDelayTimer),this._mouseDelayTimer=setTimeout(function(){t.delayMet=!0},this.options.delay))},scroll:function(t){this.clearDimensions(),this.clearOffsetParent()},toggleListeners:function(e){var i=this,o=["drag","drop","scroll"];t.each(o,function(t,o){i.$document[e](c[o],i[o+"Proxy"])})},clearOffsetParent:function(){this.offsetParent=o},clearDimensions:function(){this.traverse(function(t){t._clearDimensions()})},traverse:function(t){t(this);for(var e=this.containers.length;e--;)this.containers[e].traverse(t)},_clearDimensions:function(){this.containerDimensions=o},_destroy:function(){f[this.options.group]=o}},l.prototype={dragInit:function(t){var e=this.rootGroup;!this.disabled&&!e.dragInitDone&&this.options.drag&&this.isValidDrag(t)&&e.dragInit(t,this)},isValidDrag:function(t){return 1==t.which||"touchstart"==t.type&&1==t.originalEvent.touches.length},searchValidTarget:function(t,e){var i=a(this.getItemDimensions(),t,e),o=i.length,s=this.rootGroup,n=!s.options.isValidTarget||s.options.isValidTarget(s.item,this);if(!o&&n)return s.movePlaceholder(this,this.target,"append"),!0;for(;o--;){var r=i[o][0],h=i[o][1];if(!h&&this.hasChildGroup(r)){var l=this.getContainerGroup(r).searchValidTarget(t,e);if(l)return!0}else if(n)return this.movePlaceholder(r,t),!0}},movePlaceholder:function(e,i){var o=t(this.items[e]),s=this.itemDimensions[e],n="after",r=o.outerWidth(),a=o.outerHeight(),h=o.offset(),l={left:h.left,right:h.left+r,top:h.top,bottom:h.top+a};if(this.options.vertical){var c=(s[2]+s[3])/2,u=i.top<=c;u?(n="before",l.bottom-=a/2):l.top+=a/2}else{var p=(s[0]+s[1])/2,f=i.left<=p;f?(n="before",l.right-=r/2):l.left+=r/2}this.hasChildGroup(e)&&(l=g),this.rootGroup.movePlaceholder(this,o,n,l)},getItemDimensions:function(){return this.itemDimensions||(this.items=this.$getChildren(this.el,"item").filter(":not(."+this.group.options.placeholderClass+", ."+this.group.options.draggedClass+")").get(),n(this.items,this.itemDimensions=[],this.options.tolerance)),this.itemDimensions},getItemOffsetParent:function(){var t,e=this.el;return t="relative"===e.css("position")||"absolute"===e.css("position")||"fixed"===e.css("position")?e:e.offsetParent()},hasChildGroup:function(t){return this.options.nested&&this.getContainerGroup(t)},getContainerGroup:function(e){var s=t.data(this.items[e],m);if(s===o){var n=this.$getChildren(this.items[e],"container");if(s=!1,n[0]){var r=t.extend({},this.options,{rootGroup:this.rootGroup,group:d++});s=n[i](r).data(i).group}t.data(this.items[e],m,s)}return s},$getChildren:function(e,i){var o=this.rootGroup.options,s=o[i+"Path"],n=o[i+"Selector"];return e=t(e),s&&(e=e.find(s)),e.children(n)},_serialize:function(e,i){var o=this,s=i?"item":"container",n=this.$getChildren(e,s).not(this.options.exclude).map(function(){return o._serialize(t(this),!i)}).get();return this.rootGroup.options.serialize(e,n,i)},traverse:function(e){t.each(this.items||[],function(i){var o=t.data(this,m);o&&o.traverse(e)}),e(this)},_clearDimensions:function(){this.itemDimensions=o},_destroy:function(){var e=this;this.target.off(c.start,this.handle),this.el.removeData(i),this.options.drop&&(this.group.containers=t.grep(this.group.containers,function(t){return t!=e})),t.each(this.items||[],function(){t.removeData(this,m)})}};var v={enable:function(){this.traverse(function(t){t.disabled=!1})},disable:function(){this.traverse(function(t){t.disabled=!0})},serialize:function(){return this._serialize(this.el,!0)},refresh:function(){this.traverse(function(t){t._clearDimensions()})},destroy:function(){this.traverse(function(t){t._destroy()})}};t.extend(l.prototype,v),t.fn[i]=function(e){var s=Array.prototype.slice.call(arguments,1);return this.map(function(){var n=t(this),r=n.data(i);return r&&v[e]?v[e].apply(r,s)||this:(r||e!==o&&"object"!=typeof e||n.data(i,new l(n,e)),this)})}}(jQuery,window,"sorting"); \ No newline at end of file diff --git a/src/assets/src/js/sortable.js b/src/assets/src/js/sortable.js new file mode 100644 index 0000000..98c90ab --- /dev/null +++ b/src/assets/src/js/sortable.js @@ -0,0 +1,3362 @@ +/**! + * Sortable 1.15.2 + * @author RubaXa + * @author owenm + * @license MIT + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.Sortable = factory()); +}(this, (function () { 'use strict'; + + function ownKeys(object, enumerableOnly) { + var keys = Object.keys(object); + if (Object.getOwnPropertySymbols) { + var symbols = Object.getOwnPropertySymbols(object); + if (enumerableOnly) { + symbols = symbols.filter(function (sym) { + return Object.getOwnPropertyDescriptor(object, sym).enumerable; + }); + } + keys.push.apply(keys, symbols); + } + return keys; + } + function _objectSpread2(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] != null ? arguments[i] : {}; + if (i % 2) { + ownKeys(Object(source), true).forEach(function (key) { + _defineProperty(target, key, source[key]); + }); + } else if (Object.getOwnPropertyDescriptors) { + Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); + } else { + ownKeys(Object(source)).forEach(function (key) { + Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); + }); + } + } + return target; + } + function _typeof(obj) { + "@babel/helpers - typeof"; + + if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { + _typeof = function (obj) { + return typeof obj; + }; + } else { + _typeof = function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + } + return _typeof(obj); + } + function _defineProperty(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + return obj; + } + function _extends() { + _extends = Object.assign || function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + return target; + }; + return _extends.apply(this, arguments); + } + function _objectWithoutPropertiesLoose(source, excluded) { + if (source == null) return {}; + var target = {}; + var sourceKeys = Object.keys(source); + var key, i; + for (i = 0; i < sourceKeys.length; i++) { + key = sourceKeys[i]; + if (excluded.indexOf(key) >= 0) continue; + target[key] = source[key]; + } + return target; + } + function _objectWithoutProperties(source, excluded) { + if (source == null) return {}; + var target = _objectWithoutPropertiesLoose(source, excluded); + var key, i; + if (Object.getOwnPropertySymbols) { + var sourceSymbolKeys = Object.getOwnPropertySymbols(source); + for (i = 0; i < sourceSymbolKeys.length; i++) { + key = sourceSymbolKeys[i]; + if (excluded.indexOf(key) >= 0) continue; + if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; + target[key] = source[key]; + } + } + return target; + } + function _toConsumableArray(arr) { + return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); + } + function _arrayWithoutHoles(arr) { + if (Array.isArray(arr)) return _arrayLikeToArray(arr); + } + function _iterableToArray(iter) { + if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); + } + function _unsupportedIterableToArray(o, minLen) { + if (!o) return; + if (typeof o === "string") return _arrayLikeToArray(o, minLen); + var n = Object.prototype.toString.call(o).slice(8, -1); + if (n === "Object" && o.constructor) n = o.constructor.name; + if (n === "Map" || n === "Set") return Array.from(o); + if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); + } + function _arrayLikeToArray(arr, len) { + if (len == null || len > arr.length) len = arr.length; + for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; + return arr2; + } + function _nonIterableSpread() { + throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); + } + + var version = "1.15.2"; + + function userAgent(pattern) { + if (typeof window !== 'undefined' && window.navigator) { + return !! /*@__PURE__*/navigator.userAgent.match(pattern); + } + } + var IE11OrLess = userAgent(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i); + var Edge = userAgent(/Edge/i); + var FireFox = userAgent(/firefox/i); + var Safari = userAgent(/safari/i) && !userAgent(/chrome/i) && !userAgent(/android/i); + var IOS = userAgent(/iP(ad|od|hone)/i); + var ChromeForAndroid = userAgent(/chrome/i) && userAgent(/android/i); + + var captureMode = { + capture: false, + passive: false + }; + function on(el, event, fn) { + el.addEventListener(event, fn, !IE11OrLess && captureMode); + } + function off(el, event, fn) { + el.removeEventListener(event, fn, !IE11OrLess && captureMode); + } + function matches( /**HTMLElement*/el, /**String*/selector) { + if (!selector) return; + selector[0] === '>' && (selector = selector.substring(1)); + if (el) { + try { + if (el.matches) { + return el.matches(selector); + } else if (el.msMatchesSelector) { + return el.msMatchesSelector(selector); + } else if (el.webkitMatchesSelector) { + return el.webkitMatchesSelector(selector); + } + } catch (_) { + return false; + } + } + return false; + } + function getParentOrHost(el) { + return el.host && el !== document && el.host.nodeType ? el.host : el.parentNode; + } + function closest( /**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx, includeCTX) { + if (el) { + ctx = ctx || document; + do { + if (selector != null && (selector[0] === '>' ? el.parentNode === ctx && matches(el, selector) : matches(el, selector)) || includeCTX && el === ctx) { + return el; + } + if (el === ctx) break; + /* jshint boss:true */ + } while (el = getParentOrHost(el)); + } + return null; + } + var R_SPACE = /\s+/g; + function toggleClass(el, name, state) { + if (el && name) { + if (el.classList) { + el.classList[state ? 'add' : 'remove'](name); + } else { + var className = (' ' + el.className + ' ').replace(R_SPACE, ' ').replace(' ' + name + ' ', ' '); + el.className = (className + (state ? ' ' + name : '')).replace(R_SPACE, ' '); + } + } + } + function css(el, prop, val) { + var style = el && el.style; + if (style) { + if (val === void 0) { + if (document.defaultView && document.defaultView.getComputedStyle) { + val = document.defaultView.getComputedStyle(el, ''); + } else if (el.currentStyle) { + val = el.currentStyle; + } + return prop === void 0 ? val : val[prop]; + } else { + if (!(prop in style) && prop.indexOf('webkit') === -1) { + prop = '-webkit-' + prop; + } + style[prop] = val + (typeof val === 'string' ? '' : 'px'); + } + } + } + function matrix(el, selfOnly) { + var appliedTransforms = ''; + if (typeof el === 'string') { + appliedTransforms = el; + } else { + do { + var transform = css(el, 'transform'); + if (transform && transform !== 'none') { + appliedTransforms = transform + ' ' + appliedTransforms; + } + /* jshint boss:true */ + } while (!selfOnly && (el = el.parentNode)); + } + var matrixFn = window.DOMMatrix || window.WebKitCSSMatrix || window.CSSMatrix || window.MSCSSMatrix; + /*jshint -W056 */ + return matrixFn && new matrixFn(appliedTransforms); + } + function find(ctx, tagName, iterator) { + if (ctx) { + var list = ctx.getElementsByTagName(tagName), + i = 0, + n = list.length; + if (iterator) { + for (; i < n; i++) { + iterator(list[i], i); + } + } + return list; + } + return []; + } + function getWindowScrollingElement() { + var scrollingElement = document.scrollingElement; + if (scrollingElement) { + return scrollingElement; + } else { + return document.documentElement; + } + } + + /** + * Returns the "bounding client rect" of given element + * @param {HTMLElement} el The element whose boundingClientRect is wanted + * @param {[Boolean]} relativeToContainingBlock Whether the rect should be relative to the containing block of (including) the container + * @param {[Boolean]} relativeToNonStaticParent Whether the rect should be relative to the relative parent of (including) the contaienr + * @param {[Boolean]} undoScale Whether the container's scale() should be undone + * @param {[HTMLElement]} container The parent the element will be placed in + * @return {Object} The boundingClientRect of el, with specified adjustments + */ + function getRect(el, relativeToContainingBlock, relativeToNonStaticParent, undoScale, container) { + if (!el.getBoundingClientRect && el !== window) return; + var elRect, top, left, bottom, right, height, width; + if (el !== window && el.parentNode && el !== getWindowScrollingElement()) { + elRect = el.getBoundingClientRect(); + top = elRect.top; + left = elRect.left; + bottom = elRect.bottom; + right = elRect.right; + height = elRect.height; + width = elRect.width; + } else { + top = 0; + left = 0; + bottom = window.innerHeight; + right = window.innerWidth; + height = window.innerHeight; + width = window.innerWidth; + } + if ((relativeToContainingBlock || relativeToNonStaticParent) && el !== window) { + // Adjust for translate() + container = container || el.parentNode; + + // solves #1123 (see: https://stackoverflow.com/a/37953806/6088312) + // Not needed on <= IE11 + if (!IE11OrLess) { + do { + if (container && container.getBoundingClientRect && (css(container, 'transform') !== 'none' || relativeToNonStaticParent && css(container, 'position') !== 'static')) { + var containerRect = container.getBoundingClientRect(); + + // Set relative to edges of padding box of container + top -= containerRect.top + parseInt(css(container, 'border-top-width')); + left -= containerRect.left + parseInt(css(container, 'border-left-width')); + bottom = top + elRect.height; + right = left + elRect.width; + break; + } + /* jshint boss:true */ + } while (container = container.parentNode); + } + } + if (undoScale && el !== window) { + // Adjust for scale() + var elMatrix = matrix(container || el), + scaleX = elMatrix && elMatrix.a, + scaleY = elMatrix && elMatrix.d; + if (elMatrix) { + top /= scaleY; + left /= scaleX; + width /= scaleX; + height /= scaleY; + bottom = top + height; + right = left + width; + } + } + return { + top: top, + left: left, + bottom: bottom, + right: right, + width: width, + height: height + }; + } + + /** + * Checks if a side of an element is scrolled past a side of its parents + * @param {HTMLElement} el The element who's side being scrolled out of view is in question + * @param {String} elSide Side of the element in question ('top', 'left', 'right', 'bottom') + * @param {String} parentSide Side of the parent in question ('top', 'left', 'right', 'bottom') + * @return {HTMLElement} The parent scroll element that the el's side is scrolled past, or null if there is no such element + */ + function isScrolledPast(el, elSide, parentSide) { + var parent = getParentAutoScrollElement(el, true), + elSideVal = getRect(el)[elSide]; + + /* jshint boss:true */ + while (parent) { + var parentSideVal = getRect(parent)[parentSide], + visible = void 0; + if (parentSide === 'top' || parentSide === 'left') { + visible = elSideVal >= parentSideVal; + } else { + visible = elSideVal <= parentSideVal; + } + if (!visible) return parent; + if (parent === getWindowScrollingElement()) break; + parent = getParentAutoScrollElement(parent, false); + } + return false; + } + + /** + * Gets nth child of el, ignoring hidden children, sortable's elements (does not ignore clone if it's visible) + * and non-draggable elements + * @param {HTMLElement} el The parent element + * @param {Number} childNum The index of the child + * @param {Object} options Parent Sortable's options + * @return {HTMLElement} The child at index childNum, or null if not found + */ + function getChild(el, childNum, options, includeDragEl) { + var currentChild = 0, + i = 0, + children = el.children; + while (i < children.length) { + if (children[i].style.display !== 'none' && children[i] !== Sortable.ghost && (includeDragEl || children[i] !== Sortable.dragged) && closest(children[i], options.draggable, el, false)) { + if (currentChild === childNum) { + return children[i]; + } + currentChild++; + } + i++; + } + return null; + } + + /** + * Gets the last child in the el, ignoring ghostEl or invisible elements (clones) + * @param {HTMLElement} el Parent element + * @param {selector} selector Any other elements that should be ignored + * @return {HTMLElement} The last child, ignoring ghostEl + */ + function lastChild(el, selector) { + var last = el.lastElementChild; + while (last && (last === Sortable.ghost || css(last, 'display') === 'none' || selector && !matches(last, selector))) { + last = last.previousElementSibling; + } + return last || null; + } + + /** + * Returns the index of an element within its parent for a selected set of + * elements + * @param {HTMLElement} el + * @param {selector} selector + * @return {number} + */ + function index(el, selector) { + var index = 0; + if (!el || !el.parentNode) { + return -1; + } + + /* jshint boss:true */ + while (el = el.previousElementSibling) { + if (el.nodeName.toUpperCase() !== 'TEMPLATE' && el !== Sortable.clone && (!selector || matches(el, selector))) { + index++; + } + } + return index; + } + + /** + * Returns the scroll offset of the given element, added with all the scroll offsets of parent elements. + * The value is returned in real pixels. + * @param {HTMLElement} el + * @return {Array} Offsets in the format of [left, top] + */ + function getRelativeScrollOffset(el) { + var offsetLeft = 0, + offsetTop = 0, + winScroller = getWindowScrollingElement(); + if (el) { + do { + var elMatrix = matrix(el), + scaleX = elMatrix.a, + scaleY = elMatrix.d; + offsetLeft += el.scrollLeft * scaleX; + offsetTop += el.scrollTop * scaleY; + } while (el !== winScroller && (el = el.parentNode)); + } + return [offsetLeft, offsetTop]; + } + + /** + * Returns the index of the object within the given array + * @param {Array} arr Array that may or may not hold the object + * @param {Object} obj An object that has a key-value pair unique to and identical to a key-value pair in the object you want to find + * @return {Number} The index of the object in the array, or -1 + */ + function indexOfObject(arr, obj) { + for (var i in arr) { + if (!arr.hasOwnProperty(i)) continue; + for (var key in obj) { + if (obj.hasOwnProperty(key) && obj[key] === arr[i][key]) return Number(i); + } + } + return -1; + } + function getParentAutoScrollElement(el, includeSelf) { + // skip to window + if (!el || !el.getBoundingClientRect) return getWindowScrollingElement(); + var elem = el; + var gotSelf = false; + do { + // we don't need to get elem css if it isn't even overflowing in the first place (performance) + if (elem.clientWidth < elem.scrollWidth || elem.clientHeight < elem.scrollHeight) { + var elemCSS = css(elem); + if (elem.clientWidth < elem.scrollWidth && (elemCSS.overflowX == 'auto' || elemCSS.overflowX == 'scroll') || elem.clientHeight < elem.scrollHeight && (elemCSS.overflowY == 'auto' || elemCSS.overflowY == 'scroll')) { + if (!elem.getBoundingClientRect || elem === document.body) return getWindowScrollingElement(); + if (gotSelf || includeSelf) return elem; + gotSelf = true; + } + } + /* jshint boss:true */ + } while (elem = elem.parentNode); + return getWindowScrollingElement(); + } + function extend(dst, src) { + if (dst && src) { + for (var key in src) { + if (src.hasOwnProperty(key)) { + dst[key] = src[key]; + } + } + } + return dst; + } + function isRectEqual(rect1, rect2) { + return Math.round(rect1.top) === Math.round(rect2.top) && Math.round(rect1.left) === Math.round(rect2.left) && Math.round(rect1.height) === Math.round(rect2.height) && Math.round(rect1.width) === Math.round(rect2.width); + } + var _throttleTimeout; + function throttle(callback, ms) { + return function () { + if (!_throttleTimeout) { + var args = arguments, + _this = this; + if (args.length === 1) { + callback.call(_this, args[0]); + } else { + callback.apply(_this, args); + } + _throttleTimeout = setTimeout(function () { + _throttleTimeout = void 0; + }, ms); + } + }; + } + function cancelThrottle() { + clearTimeout(_throttleTimeout); + _throttleTimeout = void 0; + } + function scrollBy(el, x, y) { + el.scrollLeft += x; + el.scrollTop += y; + } + function clone(el) { + var Polymer = window.Polymer; + var $ = window.jQuery || window.Zepto; + if (Polymer && Polymer.dom) { + return Polymer.dom(el).cloneNode(true); + } else if ($) { + return $(el).clone(true)[0]; + } else { + return el.cloneNode(true); + } + } + function setRect(el, rect) { + css(el, 'position', 'absolute'); + css(el, 'top', rect.top); + css(el, 'left', rect.left); + css(el, 'width', rect.width); + css(el, 'height', rect.height); + } + function unsetRect(el) { + css(el, 'position', ''); + css(el, 'top', ''); + css(el, 'left', ''); + css(el, 'width', ''); + css(el, 'height', ''); + } + function getChildContainingRectFromElement(container, options, ghostEl) { + var rect = {}; + Array.from(container.children).forEach(function (child) { + var _rect$left, _rect$top, _rect$right, _rect$bottom; + if (!closest(child, options.draggable, container, false) || child.animated || child === ghostEl) return; + var childRect = getRect(child); + rect.left = Math.min((_rect$left = rect.left) !== null && _rect$left !== void 0 ? _rect$left : Infinity, childRect.left); + rect.top = Math.min((_rect$top = rect.top) !== null && _rect$top !== void 0 ? _rect$top : Infinity, childRect.top); + rect.right = Math.max((_rect$right = rect.right) !== null && _rect$right !== void 0 ? _rect$right : -Infinity, childRect.right); + rect.bottom = Math.max((_rect$bottom = rect.bottom) !== null && _rect$bottom !== void 0 ? _rect$bottom : -Infinity, childRect.bottom); + }); + rect.width = rect.right - rect.left; + rect.height = rect.bottom - rect.top; + rect.x = rect.left; + rect.y = rect.top; + return rect; + } + var expando = 'Sortable' + new Date().getTime(); + + function AnimationStateManager() { + var animationStates = [], + animationCallbackId; + return { + captureAnimationState: function captureAnimationState() { + animationStates = []; + if (!this.options.animation) return; + var children = [].slice.call(this.el.children); + children.forEach(function (child) { + if (css(child, 'display') === 'none' || child === Sortable.ghost) return; + animationStates.push({ + target: child, + rect: getRect(child) + }); + var fromRect = _objectSpread2({}, animationStates[animationStates.length - 1].rect); + + // If animating: compensate for current animation + if (child.thisAnimationDuration) { + var childMatrix = matrix(child, true); + if (childMatrix) { + fromRect.top -= childMatrix.f; + fromRect.left -= childMatrix.e; + } + } + child.fromRect = fromRect; + }); + }, + addAnimationState: function addAnimationState(state) { + animationStates.push(state); + }, + removeAnimationState: function removeAnimationState(target) { + animationStates.splice(indexOfObject(animationStates, { + target: target + }), 1); + }, + animateAll: function animateAll(callback) { + var _this = this; + if (!this.options.animation) { + clearTimeout(animationCallbackId); + if (typeof callback === 'function') callback(); + return; + } + var animating = false, + animationTime = 0; + animationStates.forEach(function (state) { + var time = 0, + target = state.target, + fromRect = target.fromRect, + toRect = getRect(target), + prevFromRect = target.prevFromRect, + prevToRect = target.prevToRect, + animatingRect = state.rect, + targetMatrix = matrix(target, true); + if (targetMatrix) { + // Compensate for current animation + toRect.top -= targetMatrix.f; + toRect.left -= targetMatrix.e; + } + target.toRect = toRect; + if (target.thisAnimationDuration) { + // Could also check if animatingRect is between fromRect and toRect + if (isRectEqual(prevFromRect, toRect) && !isRectEqual(fromRect, toRect) && + // Make sure animatingRect is on line between toRect & fromRect + (animatingRect.top - toRect.top) / (animatingRect.left - toRect.left) === (fromRect.top - toRect.top) / (fromRect.left - toRect.left)) { + // If returning to same place as started from animation and on same axis + time = calculateRealTime(animatingRect, prevFromRect, prevToRect, _this.options); + } + } + + // if fromRect != toRect: animate + if (!isRectEqual(toRect, fromRect)) { + target.prevFromRect = fromRect; + target.prevToRect = toRect; + if (!time) { + time = _this.options.animation; + } + _this.animate(target, animatingRect, toRect, time); + } + if (time) { + animating = true; + animationTime = Math.max(animationTime, time); + clearTimeout(target.animationResetTimer); + target.animationResetTimer = setTimeout(function () { + target.animationTime = 0; + target.prevFromRect = null; + target.fromRect = null; + target.prevToRect = null; + target.thisAnimationDuration = null; + }, time); + target.thisAnimationDuration = time; + } + }); + clearTimeout(animationCallbackId); + if (!animating) { + if (typeof callback === 'function') callback(); + } else { + animationCallbackId = setTimeout(function () { + if (typeof callback === 'function') callback(); + }, animationTime); + } + animationStates = []; + }, + animate: function animate(target, currentRect, toRect, duration) { + if (duration) { + css(target, 'transition', ''); + css(target, 'transform', ''); + var elMatrix = matrix(this.el), + scaleX = elMatrix && elMatrix.a, + scaleY = elMatrix && elMatrix.d, + translateX = (currentRect.left - toRect.left) / (scaleX || 1), + translateY = (currentRect.top - toRect.top) / (scaleY || 1); + target.animatingX = !!translateX; + target.animatingY = !!translateY; + css(target, 'transform', 'translate3d(' + translateX + 'px,' + translateY + 'px,0)'); + this.forRepaintDummy = repaint(target); // repaint + + css(target, 'transition', 'transform ' + duration + 'ms' + (this.options.easing ? ' ' + this.options.easing : '')); + css(target, 'transform', 'translate3d(0,0,0)'); + typeof target.animated === 'number' && clearTimeout(target.animated); + target.animated = setTimeout(function () { + css(target, 'transition', ''); + css(target, 'transform', ''); + target.animated = false; + target.animatingX = false; + target.animatingY = false; + }, duration); + } + } + }; + } + function repaint(target) { + return target.offsetWidth; + } + function calculateRealTime(animatingRect, fromRect, toRect, options) { + return Math.sqrt(Math.pow(fromRect.top - animatingRect.top, 2) + Math.pow(fromRect.left - animatingRect.left, 2)) / Math.sqrt(Math.pow(fromRect.top - toRect.top, 2) + Math.pow(fromRect.left - toRect.left, 2)) * options.animation; + } + + var plugins = []; + var defaults = { + initializeByDefault: true + }; + var PluginManager = { + mount: function mount(plugin) { + // Set default static properties + for (var option in defaults) { + if (defaults.hasOwnProperty(option) && !(option in plugin)) { + plugin[option] = defaults[option]; + } + } + plugins.forEach(function (p) { + if (p.pluginName === plugin.pluginName) { + throw "Sortable: Cannot mount plugin ".concat(plugin.pluginName, " more than once"); + } + }); + plugins.push(plugin); + }, + pluginEvent: function pluginEvent(eventName, sortable, evt) { + var _this = this; + this.eventCanceled = false; + evt.cancel = function () { + _this.eventCanceled = true; + }; + var eventNameGlobal = eventName + 'Global'; + plugins.forEach(function (plugin) { + if (!sortable[plugin.pluginName]) return; + // Fire global events if it exists in this sortable + if (sortable[plugin.pluginName][eventNameGlobal]) { + sortable[plugin.pluginName][eventNameGlobal](_objectSpread2({ + sortable: sortable + }, evt)); + } + + // Only fire plugin event if plugin is enabled in this sortable, + // and plugin has event defined + if (sortable.options[plugin.pluginName] && sortable[plugin.pluginName][eventName]) { + sortable[plugin.pluginName][eventName](_objectSpread2({ + sortable: sortable + }, evt)); + } + }); + }, + initializePlugins: function initializePlugins(sortable, el, defaults, options) { + plugins.forEach(function (plugin) { + var pluginName = plugin.pluginName; + if (!sortable.options[pluginName] && !plugin.initializeByDefault) return; + var initialized = new plugin(sortable, el, sortable.options); + initialized.sortable = sortable; + initialized.options = sortable.options; + sortable[pluginName] = initialized; + + // Add default options from plugin + _extends(defaults, initialized.defaults); + }); + for (var option in sortable.options) { + if (!sortable.options.hasOwnProperty(option)) continue; + var modified = this.modifyOption(sortable, option, sortable.options[option]); + if (typeof modified !== 'undefined') { + sortable.options[option] = modified; + } + } + }, + getEventProperties: function getEventProperties(name, sortable) { + var eventProperties = {}; + plugins.forEach(function (plugin) { + if (typeof plugin.eventProperties !== 'function') return; + _extends(eventProperties, plugin.eventProperties.call(sortable[plugin.pluginName], name)); + }); + return eventProperties; + }, + modifyOption: function modifyOption(sortable, name, value) { + var modifiedValue; + plugins.forEach(function (plugin) { + // Plugin must exist on the Sortable + if (!sortable[plugin.pluginName]) return; + + // If static option listener exists for this option, call in the context of the Sortable's instance of this plugin + if (plugin.optionListeners && typeof plugin.optionListeners[name] === 'function') { + modifiedValue = plugin.optionListeners[name].call(sortable[plugin.pluginName], value); + } + }); + return modifiedValue; + } + }; + + function dispatchEvent(_ref) { + var sortable = _ref.sortable, + rootEl = _ref.rootEl, + name = _ref.name, + targetEl = _ref.targetEl, + cloneEl = _ref.cloneEl, + toEl = _ref.toEl, + fromEl = _ref.fromEl, + oldIndex = _ref.oldIndex, + newIndex = _ref.newIndex, + oldDraggableIndex = _ref.oldDraggableIndex, + newDraggableIndex = _ref.newDraggableIndex, + originalEvent = _ref.originalEvent, + putSortable = _ref.putSortable, + extraEventProperties = _ref.extraEventProperties; + sortable = sortable || rootEl && rootEl[expando]; + if (!sortable) return; + var evt, + options = sortable.options, + onName = 'on' + name.charAt(0).toUpperCase() + name.substr(1); + // Support for new CustomEvent feature + if (window.CustomEvent && !IE11OrLess && !Edge) { + evt = new CustomEvent(name, { + bubbles: true, + cancelable: true + }); + } else { + evt = document.createEvent('Event'); + evt.initEvent(name, true, true); + } + evt.to = toEl || rootEl; + evt.from = fromEl || rootEl; + evt.item = targetEl || rootEl; + evt.clone = cloneEl; + evt.oldIndex = oldIndex; + evt.newIndex = newIndex; + evt.oldDraggableIndex = oldDraggableIndex; + evt.newDraggableIndex = newDraggableIndex; + evt.originalEvent = originalEvent; + evt.pullMode = putSortable ? putSortable.lastPutMode : undefined; + var allEventProperties = _objectSpread2(_objectSpread2({}, extraEventProperties), PluginManager.getEventProperties(name, sortable)); + for (var option in allEventProperties) { + evt[option] = allEventProperties[option]; + } + if (rootEl) { + rootEl.dispatchEvent(evt); + } + if (options[onName]) { + options[onName].call(sortable, evt); + } + } + + var _excluded = ["evt"]; + var pluginEvent = function pluginEvent(eventName, sortable) { + var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}, + originalEvent = _ref.evt, + data = _objectWithoutProperties(_ref, _excluded); + PluginManager.pluginEvent.bind(Sortable)(eventName, sortable, _objectSpread2({ + dragEl: dragEl, + parentEl: parentEl, + ghostEl: ghostEl, + rootEl: rootEl, + nextEl: nextEl, + lastDownEl: lastDownEl, + cloneEl: cloneEl, + cloneHidden: cloneHidden, + dragStarted: moved, + putSortable: putSortable, + activeSortable: Sortable.active, + originalEvent: originalEvent, + oldIndex: oldIndex, + oldDraggableIndex: oldDraggableIndex, + newIndex: newIndex, + newDraggableIndex: newDraggableIndex, + hideGhostForTarget: _hideGhostForTarget, + unhideGhostForTarget: _unhideGhostForTarget, + cloneNowHidden: function cloneNowHidden() { + cloneHidden = true; + }, + cloneNowShown: function cloneNowShown() { + cloneHidden = false; + }, + dispatchSortableEvent: function dispatchSortableEvent(name) { + _dispatchEvent({ + sortable: sortable, + name: name, + originalEvent: originalEvent + }); + } + }, data)); + }; + function _dispatchEvent(info) { + dispatchEvent(_objectSpread2({ + putSortable: putSortable, + cloneEl: cloneEl, + targetEl: dragEl, + rootEl: rootEl, + oldIndex: oldIndex, + oldDraggableIndex: oldDraggableIndex, + newIndex: newIndex, + newDraggableIndex: newDraggableIndex + }, info)); + } + var dragEl, + parentEl, + ghostEl, + rootEl, + nextEl, + lastDownEl, + cloneEl, + cloneHidden, + oldIndex, + newIndex, + oldDraggableIndex, + newDraggableIndex, + activeGroup, + putSortable, + awaitingDragStarted = false, + ignoreNextClick = false, + sortables = [], + tapEvt, + touchEvt, + lastDx, + lastDy, + tapDistanceLeft, + tapDistanceTop, + moved, + lastTarget, + lastDirection, + pastFirstInvertThresh = false, + isCircumstantialInvert = false, + targetMoveDistance, + // For positioning ghost absolutely + ghostRelativeParent, + ghostRelativeParentInitialScroll = [], + // (left, top) + + _silent = false, + savedInputChecked = []; + + /** @const */ + var documentExists = typeof document !== 'undefined', + PositionGhostAbsolutely = IOS, + CSSFloatProperty = Edge || IE11OrLess ? 'cssFloat' : 'float', + // This will not pass for IE9, because IE9 DnD only works on anchors + supportDraggable = documentExists && !ChromeForAndroid && !IOS && 'draggable' in document.createElement('div'), + supportCssPointerEvents = function () { + if (!documentExists) return; + // false when <= IE11 + if (IE11OrLess) { + return false; + } + var el = document.createElement('x'); + el.style.cssText = 'pointer-events:auto'; + return el.style.pointerEvents === 'auto'; + }(), + _detectDirection = function _detectDirection(el, options) { + var elCSS = css(el), + elWidth = parseInt(elCSS.width) - parseInt(elCSS.paddingLeft) - parseInt(elCSS.paddingRight) - parseInt(elCSS.borderLeftWidth) - parseInt(elCSS.borderRightWidth), + child1 = getChild(el, 0, options), + child2 = getChild(el, 1, options), + firstChildCSS = child1 && css(child1), + secondChildCSS = child2 && css(child2), + firstChildWidth = firstChildCSS && parseInt(firstChildCSS.marginLeft) + parseInt(firstChildCSS.marginRight) + getRect(child1).width, + secondChildWidth = secondChildCSS && parseInt(secondChildCSS.marginLeft) + parseInt(secondChildCSS.marginRight) + getRect(child2).width; + if (elCSS.display === 'flex') { + return elCSS.flexDirection === 'column' || elCSS.flexDirection === 'column-reverse' ? 'vertical' : 'horizontal'; + } + if (elCSS.display === 'grid') { + return elCSS.gridTemplateColumns.split(' ').length <= 1 ? 'vertical' : 'horizontal'; + } + if (child1 && firstChildCSS["float"] && firstChildCSS["float"] !== 'none') { + var touchingSideChild2 = firstChildCSS["float"] === 'left' ? 'left' : 'right'; + return child2 && (secondChildCSS.clear === 'both' || secondChildCSS.clear === touchingSideChild2) ? 'vertical' : 'horizontal'; + } + return child1 && (firstChildCSS.display === 'block' || firstChildCSS.display === 'flex' || firstChildCSS.display === 'table' || firstChildCSS.display === 'grid' || firstChildWidth >= elWidth && elCSS[CSSFloatProperty] === 'none' || child2 && elCSS[CSSFloatProperty] === 'none' && firstChildWidth + secondChildWidth > elWidth) ? 'vertical' : 'horizontal'; + }, + _dragElInRowColumn = function _dragElInRowColumn(dragRect, targetRect, vertical) { + var dragElS1Opp = vertical ? dragRect.left : dragRect.top, + dragElS2Opp = vertical ? dragRect.right : dragRect.bottom, + dragElOppLength = vertical ? dragRect.width : dragRect.height, + targetS1Opp = vertical ? targetRect.left : targetRect.top, + targetS2Opp = vertical ? targetRect.right : targetRect.bottom, + targetOppLength = vertical ? targetRect.width : targetRect.height; + return dragElS1Opp === targetS1Opp || dragElS2Opp === targetS2Opp || dragElS1Opp + dragElOppLength / 2 === targetS1Opp + targetOppLength / 2; + }, + /** + * Detects first nearest empty sortable to X and Y position using emptyInsertThreshold. + * @param {Number} x X position + * @param {Number} y Y position + * @return {HTMLElement} Element of the first found nearest Sortable + */ + _detectNearestEmptySortable = function _detectNearestEmptySortable(x, y) { + var ret; + sortables.some(function (sortable) { + var threshold = sortable[expando].options.emptyInsertThreshold; + if (!threshold || lastChild(sortable)) return; + var rect = getRect(sortable), + insideHorizontally = x >= rect.left - threshold && x <= rect.right + threshold, + insideVertically = y >= rect.top - threshold && y <= rect.bottom + threshold; + if (insideHorizontally && insideVertically) { + return ret = sortable; + } + }); + return ret; + }, + _prepareGroup = function _prepareGroup(options) { + function toFn(value, pull) { + return function (to, from, dragEl, evt) { + var sameGroup = to.options.group.name && from.options.group.name && to.options.group.name === from.options.group.name; + if (value == null && (pull || sameGroup)) { + // Default pull value + // Default pull and put value if same group + return true; + } else if (value == null || value === false) { + return false; + } else if (pull && value === 'clone') { + return value; + } else if (typeof value === 'function') { + return toFn(value(to, from, dragEl, evt), pull)(to, from, dragEl, evt); + } else { + var otherGroup = (pull ? to : from).options.group.name; + return value === true || typeof value === 'string' && value === otherGroup || value.join && value.indexOf(otherGroup) > -1; + } + }; + } + var group = {}; + var originalGroup = options.group; + if (!originalGroup || _typeof(originalGroup) != 'object') { + originalGroup = { + name: originalGroup + }; + } + group.name = originalGroup.name; + group.checkPull = toFn(originalGroup.pull, true); + group.checkPut = toFn(originalGroup.put); + group.revertClone = originalGroup.revertClone; + options.group = group; + }, + _hideGhostForTarget = function _hideGhostForTarget() { + if (!supportCssPointerEvents && ghostEl) { + css(ghostEl, 'display', 'none'); + } + }, + _unhideGhostForTarget = function _unhideGhostForTarget() { + if (!supportCssPointerEvents && ghostEl) { + css(ghostEl, 'display', ''); + } + }; + + // #1184 fix - Prevent click event on fallback if dragged but item not changed position + if (documentExists && !ChromeForAndroid) { + document.addEventListener('click', function (evt) { + if (ignoreNextClick) { + evt.preventDefault(); + evt.stopPropagation && evt.stopPropagation(); + evt.stopImmediatePropagation && evt.stopImmediatePropagation(); + ignoreNextClick = false; + return false; + } + }, true); + } + var nearestEmptyInsertDetectEvent = function nearestEmptyInsertDetectEvent(evt) { + if (dragEl) { + evt = evt.touches ? evt.touches[0] : evt; + var nearest = _detectNearestEmptySortable(evt.clientX, evt.clientY); + if (nearest) { + // Create imitation event + var event = {}; + for (var i in evt) { + if (evt.hasOwnProperty(i)) { + event[i] = evt[i]; + } + } + event.target = event.rootEl = nearest; + event.preventDefault = void 0; + event.stopPropagation = void 0; + nearest[expando]._onDragOver(event); + } + } + }; + var _checkOutsideTargetEl = function _checkOutsideTargetEl(evt) { + if (dragEl) { + dragEl.parentNode[expando]._isOutsideThisEl(evt.target); + } + }; + + /** + * @class Sortable + * @param {HTMLElement} el + * @param {Object} [options] + */ + function Sortable(el, options) { + if (!(el && el.nodeType && el.nodeType === 1)) { + throw "Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(el)); + } + this.el = el; // root element + this.options = options = _extends({}, options); + + // Export instance + el[expando] = this; + var defaults = { + group: null, + sort: true, + disabled: false, + store: null, + handle: null, + draggable: /^[uo]l$/i.test(el.nodeName) ? '>li' : '>*', + swapThreshold: 1, + // percentage; 0 <= x <= 1 + invertSwap: false, + // invert always + invertedSwapThreshold: null, + // will be set to same as swapThreshold if default + removeCloneOnHide: true, + direction: function direction() { + return _detectDirection(el, this.options); + }, + ghostClass: 'sortable-ghost', + chosenClass: 'sortable-chosen', + dragClass: 'sortable-drag', + ignore: 'a, img', + filter: null, + preventOnFilter: true, + animation: 0, + easing: null, + setData: function setData(dataTransfer, dragEl) { + dataTransfer.setData('Text', dragEl.textContent); + }, + dropBubble: false, + dragoverBubble: false, + dataIdAttr: 'data-id', + delay: 0, + delayOnTouchOnly: false, + touchStartThreshold: (Number.parseInt ? Number : window).parseInt(window.devicePixelRatio, 10) || 1, + forceFallback: false, + fallbackClass: 'sortable-fallback', + fallbackOnBody: false, + fallbackTolerance: 0, + fallbackOffset: { + x: 0, + y: 0 + }, + supportPointer: Sortable.supportPointer !== false && 'PointerEvent' in window && !Safari, + emptyInsertThreshold: 5 + }; + PluginManager.initializePlugins(this, el, defaults); + + // Set default options + for (var name in defaults) { + !(name in options) && (options[name] = defaults[name]); + } + _prepareGroup(options); + + // Bind all private methods + for (var fn in this) { + if (fn.charAt(0) === '_' && typeof this[fn] === 'function') { + this[fn] = this[fn].bind(this); + } + } + + // Setup drag mode + this.nativeDraggable = options.forceFallback ? false : supportDraggable; + if (this.nativeDraggable) { + // Touch start threshold cannot be greater than the native dragstart threshold + this.options.touchStartThreshold = 1; + } + + // Bind events + if (options.supportPointer) { + on(el, 'pointerdown', this._onTapStart); + } else { + on(el, 'mousedown', this._onTapStart); + on(el, 'touchstart', this._onTapStart); + } + if (this.nativeDraggable) { + on(el, 'dragover', this); + on(el, 'dragenter', this); + } + sortables.push(this.el); + + // Restore sorting + options.store && options.store.get && this.sort(options.store.get(this) || []); + + // Add animation state manager + _extends(this, AnimationStateManager()); + } + Sortable.prototype = /** @lends Sortable.prototype */{ + constructor: Sortable, + _isOutsideThisEl: function _isOutsideThisEl(target) { + if (!this.el.contains(target) && target !== this.el) { + lastTarget = null; + } + }, + _getDirection: function _getDirection(evt, target) { + return typeof this.options.direction === 'function' ? this.options.direction.call(this, evt, target, dragEl) : this.options.direction; + }, + _onTapStart: function _onTapStart( /** Event|TouchEvent */evt) { + if (!evt.cancelable) return; + var _this = this, + el = this.el, + options = this.options, + preventOnFilter = options.preventOnFilter, + type = evt.type, + touch = evt.touches && evt.touches[0] || evt.pointerType && evt.pointerType === 'touch' && evt, + target = (touch || evt).target, + originalTarget = evt.target.shadowRoot && (evt.path && evt.path[0] || evt.composedPath && evt.composedPath()[0]) || target, + filter = options.filter; + _saveInputCheckedState(el); + + // Don't trigger start event when an element is been dragged, otherwise the evt.oldindex always wrong when set option.group. + if (dragEl) { + return; + } + if (/mousedown|pointerdown/.test(type) && evt.button !== 0 || options.disabled) { + return; // only left button and enabled + } + + // cancel dnd if original target is content editable + if (originalTarget.isContentEditable) { + return; + } + + // Safari ignores further event handling after mousedown + if (!this.nativeDraggable && Safari && target && target.tagName.toUpperCase() === 'SELECT') { + return; + } + target = closest(target, options.draggable, el, false); + if (target && target.animated) { + return; + } + if (lastDownEl === target) { + // Ignoring duplicate `down` + return; + } + + // Get the index of the dragged element within its parent + oldIndex = index(target); + oldDraggableIndex = index(target, options.draggable); + + // Check filter + if (typeof filter === 'function') { + if (filter.call(this, evt, target, this)) { + _dispatchEvent({ + sortable: _this, + rootEl: originalTarget, + name: 'filter', + targetEl: target, + toEl: el, + fromEl: el + }); + pluginEvent('filter', _this, { + evt: evt + }); + preventOnFilter && evt.cancelable && evt.preventDefault(); + return; // cancel dnd + } + } else if (filter) { + filter = filter.split(',').some(function (criteria) { + criteria = closest(originalTarget, criteria.trim(), el, false); + if (criteria) { + _dispatchEvent({ + sortable: _this, + rootEl: criteria, + name: 'filter', + targetEl: target, + fromEl: el, + toEl: el + }); + pluginEvent('filter', _this, { + evt: evt + }); + return true; + } + }); + if (filter) { + preventOnFilter && evt.cancelable && evt.preventDefault(); + return; // cancel dnd + } + } + if (options.handle && !closest(originalTarget, options.handle, el, false)) { + return; + } + + // Prepare `dragstart` + this._prepareDragStart(evt, touch, target); + }, + _prepareDragStart: function _prepareDragStart( /** Event */evt, /** Touch */touch, /** HTMLElement */target) { + var _this = this, + el = _this.el, + options = _this.options, + ownerDocument = el.ownerDocument, + dragStartFn; + if (target && !dragEl && target.parentNode === el) { + var dragRect = getRect(target); + rootEl = el; + dragEl = target; + parentEl = dragEl.parentNode; + nextEl = dragEl.nextSibling; + lastDownEl = target; + activeGroup = options.group; + Sortable.dragged = dragEl; + tapEvt = { + target: dragEl, + clientX: (touch || evt).clientX, + clientY: (touch || evt).clientY + }; + tapDistanceLeft = tapEvt.clientX - dragRect.left; + tapDistanceTop = tapEvt.clientY - dragRect.top; + this._lastX = (touch || evt).clientX; + this._lastY = (touch || evt).clientY; + dragEl.style['will-change'] = 'all'; + dragStartFn = function dragStartFn() { + pluginEvent('delayEnded', _this, { + evt: evt + }); + if (Sortable.eventCanceled) { + _this._onDrop(); + return; + } + // Delayed drag has been triggered + // we can re-enable the events: touchmove/mousemove + _this._disableDelayedDragEvents(); + if (!FireFox && _this.nativeDraggable) { + dragEl.draggable = true; + } + + // Bind the events: dragstart/dragend + _this._triggerDragStart(evt, touch); + + // Drag start event + _dispatchEvent({ + sortable: _this, + name: 'choose', + originalEvent: evt + }); + + // Chosen item + toggleClass(dragEl, options.chosenClass, true); + }; + + // Disable "draggable" + options.ignore.split(',').forEach(function (criteria) { + find(dragEl, criteria.trim(), _disableDraggable); + }); + on(ownerDocument, 'dragover', nearestEmptyInsertDetectEvent); + on(ownerDocument, 'mousemove', nearestEmptyInsertDetectEvent); + on(ownerDocument, 'touchmove', nearestEmptyInsertDetectEvent); + on(ownerDocument, 'mouseup', _this._onDrop); + on(ownerDocument, 'touchend', _this._onDrop); + on(ownerDocument, 'touchcancel', _this._onDrop); + + // Make dragEl draggable (must be before delay for FireFox) + if (FireFox && this.nativeDraggable) { + this.options.touchStartThreshold = 4; + dragEl.draggable = true; + } + pluginEvent('delayStart', this, { + evt: evt + }); + + // Delay is impossible for native DnD in Edge or IE + if (options.delay && (!options.delayOnTouchOnly || touch) && (!this.nativeDraggable || !(Edge || IE11OrLess))) { + if (Sortable.eventCanceled) { + this._onDrop(); + return; + } + // If the user moves the pointer or let go the click or touch + // before the delay has been reached: + // disable the delayed drag + on(ownerDocument, 'mouseup', _this._disableDelayedDrag); + on(ownerDocument, 'touchend', _this._disableDelayedDrag); + on(ownerDocument, 'touchcancel', _this._disableDelayedDrag); + on(ownerDocument, 'mousemove', _this._delayedDragTouchMoveHandler); + on(ownerDocument, 'touchmove', _this._delayedDragTouchMoveHandler); + options.supportPointer && on(ownerDocument, 'pointermove', _this._delayedDragTouchMoveHandler); + _this._dragStartTimer = setTimeout(dragStartFn, options.delay); + } else { + dragStartFn(); + } + } + }, + _delayedDragTouchMoveHandler: function _delayedDragTouchMoveHandler( /** TouchEvent|PointerEvent **/e) { + var touch = e.touches ? e.touches[0] : e; + if (Math.max(Math.abs(touch.clientX - this._lastX), Math.abs(touch.clientY - this._lastY)) >= Math.floor(this.options.touchStartThreshold / (this.nativeDraggable && window.devicePixelRatio || 1))) { + this._disableDelayedDrag(); + } + }, + _disableDelayedDrag: function _disableDelayedDrag() { + dragEl && _disableDraggable(dragEl); + clearTimeout(this._dragStartTimer); + this._disableDelayedDragEvents(); + }, + _disableDelayedDragEvents: function _disableDelayedDragEvents() { + var ownerDocument = this.el.ownerDocument; + off(ownerDocument, 'mouseup', this._disableDelayedDrag); + off(ownerDocument, 'touchend', this._disableDelayedDrag); + off(ownerDocument, 'touchcancel', this._disableDelayedDrag); + off(ownerDocument, 'mousemove', this._delayedDragTouchMoveHandler); + off(ownerDocument, 'touchmove', this._delayedDragTouchMoveHandler); + off(ownerDocument, 'pointermove', this._delayedDragTouchMoveHandler); + }, + _triggerDragStart: function _triggerDragStart( /** Event */evt, /** Touch */touch) { + touch = touch || evt.pointerType == 'touch' && evt; + if (!this.nativeDraggable || touch) { + if (this.options.supportPointer) { + on(document, 'pointermove', this._onTouchMove); + } else if (touch) { + on(document, 'touchmove', this._onTouchMove); + } else { + on(document, 'mousemove', this._onTouchMove); + } + } else { + on(dragEl, 'dragend', this); + on(rootEl, 'dragstart', this._onDragStart); + } + try { + if (document.selection) { + // Timeout neccessary for IE9 + _nextTick(function () { + document.selection.empty(); + }); + } else { + window.getSelection().removeAllRanges(); + } + } catch (err) {} + }, + _dragStarted: function _dragStarted(fallback, evt) { + awaitingDragStarted = false; + if (rootEl && dragEl) { + pluginEvent('dragStarted', this, { + evt: evt + }); + if (this.nativeDraggable) { + on(document, 'dragover', _checkOutsideTargetEl); + } + var options = this.options; + + // Apply effect + !fallback && toggleClass(dragEl, options.dragClass, false); + toggleClass(dragEl, options.ghostClass, true); + Sortable.active = this; + fallback && this._appendGhost(); + + // Drag start event + _dispatchEvent({ + sortable: this, + name: 'start', + originalEvent: evt + }); + } else { + this._nulling(); + } + }, + _emulateDragOver: function _emulateDragOver() { + if (touchEvt) { + this._lastX = touchEvt.clientX; + this._lastY = touchEvt.clientY; + _hideGhostForTarget(); + var target = document.elementFromPoint(touchEvt.clientX, touchEvt.clientY); + var parent = target; + while (target && target.shadowRoot) { + target = target.shadowRoot.elementFromPoint(touchEvt.clientX, touchEvt.clientY); + if (target === parent) break; + parent = target; + } + dragEl.parentNode[expando]._isOutsideThisEl(target); + if (parent) { + do { + if (parent[expando]) { + var inserted = void 0; + inserted = parent[expando]._onDragOver({ + clientX: touchEvt.clientX, + clientY: touchEvt.clientY, + target: target, + rootEl: parent + }); + if (inserted && !this.options.dragoverBubble) { + break; + } + } + target = parent; // store last element + } + /* jshint boss:true */ while (parent = parent.parentNode); + } + _unhideGhostForTarget(); + } + }, + _onTouchMove: function _onTouchMove( /**TouchEvent*/evt) { + if (tapEvt) { + var options = this.options, + fallbackTolerance = options.fallbackTolerance, + fallbackOffset = options.fallbackOffset, + touch = evt.touches ? evt.touches[0] : evt, + ghostMatrix = ghostEl && matrix(ghostEl, true), + scaleX = ghostEl && ghostMatrix && ghostMatrix.a, + scaleY = ghostEl && ghostMatrix && ghostMatrix.d, + relativeScrollOffset = PositionGhostAbsolutely && ghostRelativeParent && getRelativeScrollOffset(ghostRelativeParent), + dx = (touch.clientX - tapEvt.clientX + fallbackOffset.x) / (scaleX || 1) + (relativeScrollOffset ? relativeScrollOffset[0] - ghostRelativeParentInitialScroll[0] : 0) / (scaleX || 1), + dy = (touch.clientY - tapEvt.clientY + fallbackOffset.y) / (scaleY || 1) + (relativeScrollOffset ? relativeScrollOffset[1] - ghostRelativeParentInitialScroll[1] : 0) / (scaleY || 1); + + // only set the status to dragging, when we are actually dragging + if (!Sortable.active && !awaitingDragStarted) { + if (fallbackTolerance && Math.max(Math.abs(touch.clientX - this._lastX), Math.abs(touch.clientY - this._lastY)) < fallbackTolerance) { + return; + } + this._onDragStart(evt, true); + } + if (ghostEl) { + if (ghostMatrix) { + ghostMatrix.e += dx - (lastDx || 0); + ghostMatrix.f += dy - (lastDy || 0); + } else { + ghostMatrix = { + a: 1, + b: 0, + c: 0, + d: 1, + e: dx, + f: dy + }; + } + var cssMatrix = "matrix(".concat(ghostMatrix.a, ",").concat(ghostMatrix.b, ",").concat(ghostMatrix.c, ",").concat(ghostMatrix.d, ",").concat(ghostMatrix.e, ",").concat(ghostMatrix.f, ")"); + css(ghostEl, 'webkitTransform', cssMatrix); + css(ghostEl, 'mozTransform', cssMatrix); + css(ghostEl, 'msTransform', cssMatrix); + css(ghostEl, 'transform', cssMatrix); + lastDx = dx; + lastDy = dy; + touchEvt = touch; + } + evt.cancelable && evt.preventDefault(); + } + }, + _appendGhost: function _appendGhost() { + // Bug if using scale(): https://stackoverflow.com/questions/2637058 + // Not being adjusted for + if (!ghostEl) { + var container = this.options.fallbackOnBody ? document.body : rootEl, + rect = getRect(dragEl, true, PositionGhostAbsolutely, true, container), + options = this.options; + + // Position absolutely + if (PositionGhostAbsolutely) { + // Get relatively positioned parent + ghostRelativeParent = container; + while (css(ghostRelativeParent, 'position') === 'static' && css(ghostRelativeParent, 'transform') === 'none' && ghostRelativeParent !== document) { + ghostRelativeParent = ghostRelativeParent.parentNode; + } + if (ghostRelativeParent !== document.body && ghostRelativeParent !== document.documentElement) { + if (ghostRelativeParent === document) ghostRelativeParent = getWindowScrollingElement(); + rect.top += ghostRelativeParent.scrollTop; + rect.left += ghostRelativeParent.scrollLeft; + } else { + ghostRelativeParent = getWindowScrollingElement(); + } + ghostRelativeParentInitialScroll = getRelativeScrollOffset(ghostRelativeParent); + } + ghostEl = dragEl.cloneNode(true); + toggleClass(ghostEl, options.ghostClass, false); + toggleClass(ghostEl, options.fallbackClass, true); + toggleClass(ghostEl, options.dragClass, true); + css(ghostEl, 'transition', ''); + css(ghostEl, 'transform', ''); + css(ghostEl, 'box-sizing', 'border-box'); + css(ghostEl, 'margin', 0); + css(ghostEl, 'top', rect.top); + css(ghostEl, 'left', rect.left); + css(ghostEl, 'width', rect.width); + css(ghostEl, 'height', rect.height); + css(ghostEl, 'opacity', '0.8'); + css(ghostEl, 'position', PositionGhostAbsolutely ? 'absolute' : 'fixed'); + css(ghostEl, 'zIndex', '100000'); + css(ghostEl, 'pointerEvents', 'none'); + Sortable.ghost = ghostEl; + container.appendChild(ghostEl); + + // Set transform-origin + css(ghostEl, 'transform-origin', tapDistanceLeft / parseInt(ghostEl.style.width) * 100 + '% ' + tapDistanceTop / parseInt(ghostEl.style.height) * 100 + '%'); + } + }, + _onDragStart: function _onDragStart( /**Event*/evt, /**boolean*/fallback) { + var _this = this; + var dataTransfer = evt.dataTransfer; + var options = _this.options; + pluginEvent('dragStart', this, { + evt: evt + }); + if (Sortable.eventCanceled) { + this._onDrop(); + return; + } + pluginEvent('setupClone', this); + if (!Sortable.eventCanceled) { + cloneEl = clone(dragEl); + cloneEl.removeAttribute("id"); + cloneEl.draggable = false; + cloneEl.style['will-change'] = ''; + this._hideClone(); + toggleClass(cloneEl, this.options.chosenClass, false); + Sortable.clone = cloneEl; + } + + // #1143: IFrame support workaround + _this.cloneId = _nextTick(function () { + pluginEvent('clone', _this); + if (Sortable.eventCanceled) return; + if (!_this.options.removeCloneOnHide) { + rootEl.insertBefore(cloneEl, dragEl); + } + _this._hideClone(); + _dispatchEvent({ + sortable: _this, + name: 'clone' + }); + }); + !fallback && toggleClass(dragEl, options.dragClass, true); + + // Set proper drop events + if (fallback) { + ignoreNextClick = true; + _this._loopId = setInterval(_this._emulateDragOver, 50); + } else { + // Undo what was set in _prepareDragStart before drag started + off(document, 'mouseup', _this._onDrop); + off(document, 'touchend', _this._onDrop); + off(document, 'touchcancel', _this._onDrop); + if (dataTransfer) { + dataTransfer.effectAllowed = 'move'; + options.setData && options.setData.call(_this, dataTransfer, dragEl); + } + on(document, 'drop', _this); + + // #1276 fix: + css(dragEl, 'transform', 'translateZ(0)'); + } + awaitingDragStarted = true; + _this._dragStartId = _nextTick(_this._dragStarted.bind(_this, fallback, evt)); + on(document, 'selectstart', _this); + moved = true; + if (Safari) { + css(document.body, 'user-select', 'none'); + } + }, + // Returns true - if no further action is needed (either inserted or another condition) + _onDragOver: function _onDragOver( /**Event*/evt) { + var el = this.el, + target = evt.target, + dragRect, + targetRect, + revert, + options = this.options, + group = options.group, + activeSortable = Sortable.active, + isOwner = activeGroup === group, + canSort = options.sort, + fromSortable = putSortable || activeSortable, + vertical, + _this = this, + completedFired = false; + if (_silent) return; + function dragOverEvent(name, extra) { + pluginEvent(name, _this, _objectSpread2({ + evt: evt, + isOwner: isOwner, + axis: vertical ? 'vertical' : 'horizontal', + revert: revert, + dragRect: dragRect, + targetRect: targetRect, + canSort: canSort, + fromSortable: fromSortable, + target: target, + completed: completed, + onMove: function onMove(target, after) { + return _onMove(rootEl, el, dragEl, dragRect, target, getRect(target), evt, after); + }, + changed: changed + }, extra)); + } + + // Capture animation state + function capture() { + dragOverEvent('dragOverAnimationCapture'); + _this.captureAnimationState(); + if (_this !== fromSortable) { + fromSortable.captureAnimationState(); + } + } + + // Return invocation when dragEl is inserted (or completed) + function completed(insertion) { + dragOverEvent('dragOverCompleted', { + insertion: insertion + }); + if (insertion) { + // Clones must be hidden before folding animation to capture dragRectAbsolute properly + if (isOwner) { + activeSortable._hideClone(); + } else { + activeSortable._showClone(_this); + } + if (_this !== fromSortable) { + // Set ghost class to new sortable's ghost class + toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : activeSortable.options.ghostClass, false); + toggleClass(dragEl, options.ghostClass, true); + } + if (putSortable !== _this && _this !== Sortable.active) { + putSortable = _this; + } else if (_this === Sortable.active && putSortable) { + putSortable = null; + } + + // Animation + if (fromSortable === _this) { + _this._ignoreWhileAnimating = target; + } + _this.animateAll(function () { + dragOverEvent('dragOverAnimationComplete'); + _this._ignoreWhileAnimating = null; + }); + if (_this !== fromSortable) { + fromSortable.animateAll(); + fromSortable._ignoreWhileAnimating = null; + } + } + + // Null lastTarget if it is not inside a previously swapped element + if (target === dragEl && !dragEl.animated || target === el && !target.animated) { + lastTarget = null; + } + + // no bubbling and not fallback + if (!options.dragoverBubble && !evt.rootEl && target !== document) { + dragEl.parentNode[expando]._isOutsideThisEl(evt.target); + + // Do not detect for empty insert if already inserted + !insertion && nearestEmptyInsertDetectEvent(evt); + } + !options.dragoverBubble && evt.stopPropagation && evt.stopPropagation(); + return completedFired = true; + } + + // Call when dragEl has been inserted + function changed() { + newIndex = index(dragEl); + newDraggableIndex = index(dragEl, options.draggable); + _dispatchEvent({ + sortable: _this, + name: 'change', + toEl: el, + newIndex: newIndex, + newDraggableIndex: newDraggableIndex, + originalEvent: evt + }); + } + if (evt.preventDefault !== void 0) { + evt.cancelable && evt.preventDefault(); + } + target = closest(target, options.draggable, el, true); + dragOverEvent('dragOver'); + if (Sortable.eventCanceled) return completedFired; + if (dragEl.contains(evt.target) || target.animated && target.animatingX && target.animatingY || _this._ignoreWhileAnimating === target) { + return completed(false); + } + ignoreNextClick = false; + if (activeSortable && !options.disabled && (isOwner ? canSort || (revert = parentEl !== rootEl) // Reverting item into the original list + : putSortable === this || (this.lastPutMode = activeGroup.checkPull(this, activeSortable, dragEl, evt)) && group.checkPut(this, activeSortable, dragEl, evt))) { + vertical = this._getDirection(evt, target) === 'vertical'; + dragRect = getRect(dragEl); + dragOverEvent('dragOverValid'); + if (Sortable.eventCanceled) return completedFired; + if (revert) { + parentEl = rootEl; // actualization + capture(); + this._hideClone(); + dragOverEvent('revert'); + if (!Sortable.eventCanceled) { + if (nextEl) { + rootEl.insertBefore(dragEl, nextEl); + } else { + rootEl.appendChild(dragEl); + } + } + return completed(true); + } + var elLastChild = lastChild(el, options.draggable); + if (!elLastChild || _ghostIsLast(evt, vertical, this) && !elLastChild.animated) { + // Insert to end of list + + // If already at end of list: Do not insert + if (elLastChild === dragEl) { + return completed(false); + } + + // if there is a last element, it is the target + if (elLastChild && el === evt.target) { + target = elLastChild; + } + if (target) { + targetRect = getRect(target); + } + if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, !!target) !== false) { + capture(); + if (elLastChild && elLastChild.nextSibling) { + // the last draggable element is not the last node + el.insertBefore(dragEl, elLastChild.nextSibling); + } else { + el.appendChild(dragEl); + } + parentEl = el; // actualization + + changed(); + return completed(true); + } + } else if (elLastChild && _ghostIsFirst(evt, vertical, this)) { + // Insert to start of list + var firstChild = getChild(el, 0, options, true); + if (firstChild === dragEl) { + return completed(false); + } + target = firstChild; + targetRect = getRect(target); + if (_onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, false) !== false) { + capture(); + el.insertBefore(dragEl, firstChild); + parentEl = el; // actualization + + changed(); + return completed(true); + } + } else if (target.parentNode === el) { + targetRect = getRect(target); + var direction = 0, + targetBeforeFirstSwap, + differentLevel = dragEl.parentNode !== el, + differentRowCol = !_dragElInRowColumn(dragEl.animated && dragEl.toRect || dragRect, target.animated && target.toRect || targetRect, vertical), + side1 = vertical ? 'top' : 'left', + scrolledPastTop = isScrolledPast(target, 'top', 'top') || isScrolledPast(dragEl, 'top', 'top'), + scrollBefore = scrolledPastTop ? scrolledPastTop.scrollTop : void 0; + if (lastTarget !== target) { + targetBeforeFirstSwap = targetRect[side1]; + pastFirstInvertThresh = false; + isCircumstantialInvert = !differentRowCol && options.invertSwap || differentLevel; + } + direction = _getSwapDirection(evt, target, targetRect, vertical, differentRowCol ? 1 : options.swapThreshold, options.invertedSwapThreshold == null ? options.swapThreshold : options.invertedSwapThreshold, isCircumstantialInvert, lastTarget === target); + var sibling; + if (direction !== 0) { + // Check if target is beside dragEl in respective direction (ignoring hidden elements) + var dragIndex = index(dragEl); + do { + dragIndex -= direction; + sibling = parentEl.children[dragIndex]; + } while (sibling && (css(sibling, 'display') === 'none' || sibling === ghostEl)); + } + // If dragEl is already beside target: Do not insert + if (direction === 0 || sibling === target) { + return completed(false); + } + lastTarget = target; + lastDirection = direction; + var nextSibling = target.nextElementSibling, + after = false; + after = direction === 1; + var moveVector = _onMove(rootEl, el, dragEl, dragRect, target, targetRect, evt, after); + if (moveVector !== false) { + if (moveVector === 1 || moveVector === -1) { + after = moveVector === 1; + } + _silent = true; + setTimeout(_unsilent, 30); + capture(); + if (after && !nextSibling) { + el.appendChild(dragEl); + } else { + target.parentNode.insertBefore(dragEl, after ? nextSibling : target); + } + + // Undo chrome's scroll adjustment (has no effect on other browsers) + if (scrolledPastTop) { + scrollBy(scrolledPastTop, 0, scrollBefore - scrolledPastTop.scrollTop); + } + parentEl = dragEl.parentNode; // actualization + + // must be done before animation + if (targetBeforeFirstSwap !== undefined && !isCircumstantialInvert) { + targetMoveDistance = Math.abs(targetBeforeFirstSwap - getRect(target)[side1]); + } + changed(); + return completed(true); + } + } + if (el.contains(dragEl)) { + return completed(false); + } + } + return false; + }, + _ignoreWhileAnimating: null, + _offMoveEvents: function _offMoveEvents() { + off(document, 'mousemove', this._onTouchMove); + off(document, 'touchmove', this._onTouchMove); + off(document, 'pointermove', this._onTouchMove); + off(document, 'dragover', nearestEmptyInsertDetectEvent); + off(document, 'mousemove', nearestEmptyInsertDetectEvent); + off(document, 'touchmove', nearestEmptyInsertDetectEvent); + }, + _offUpEvents: function _offUpEvents() { + var ownerDocument = this.el.ownerDocument; + off(ownerDocument, 'mouseup', this._onDrop); + off(ownerDocument, 'touchend', this._onDrop); + off(ownerDocument, 'pointerup', this._onDrop); + off(ownerDocument, 'touchcancel', this._onDrop); + off(document, 'selectstart', this); + }, + _onDrop: function _onDrop( /**Event*/evt) { + var el = this.el, + options = this.options; + + // Get the index of the dragged element within its parent + newIndex = index(dragEl); + newDraggableIndex = index(dragEl, options.draggable); + pluginEvent('drop', this, { + evt: evt + }); + parentEl = dragEl && dragEl.parentNode; + + // Get again after plugin event + newIndex = index(dragEl); + newDraggableIndex = index(dragEl, options.draggable); + if (Sortable.eventCanceled) { + this._nulling(); + return; + } + awaitingDragStarted = false; + isCircumstantialInvert = false; + pastFirstInvertThresh = false; + clearInterval(this._loopId); + clearTimeout(this._dragStartTimer); + _cancelNextTick(this.cloneId); + _cancelNextTick(this._dragStartId); + + // Unbind events + if (this.nativeDraggable) { + off(document, 'drop', this); + off(el, 'dragstart', this._onDragStart); + } + this._offMoveEvents(); + this._offUpEvents(); + if (Safari) { + css(document.body, 'user-select', ''); + } + css(dragEl, 'transform', ''); + if (evt) { + if (moved) { + evt.cancelable && evt.preventDefault(); + !options.dropBubble && evt.stopPropagation(); + } + ghostEl && ghostEl.parentNode && ghostEl.parentNode.removeChild(ghostEl); + if (rootEl === parentEl || putSortable && putSortable.lastPutMode !== 'clone') { + // Remove clone(s) + cloneEl && cloneEl.parentNode && cloneEl.parentNode.removeChild(cloneEl); + } + if (dragEl) { + if (this.nativeDraggable) { + off(dragEl, 'dragend', this); + } + _disableDraggable(dragEl); + dragEl.style['will-change'] = ''; + + // Remove classes + // ghostClass is added in dragStarted + if (moved && !awaitingDragStarted) { + toggleClass(dragEl, putSortable ? putSortable.options.ghostClass : this.options.ghostClass, false); + } + toggleClass(dragEl, this.options.chosenClass, false); + + // Drag stop event + _dispatchEvent({ + sortable: this, + name: 'unchoose', + toEl: parentEl, + newIndex: null, + newDraggableIndex: null, + originalEvent: evt + }); + if (rootEl !== parentEl) { + if (newIndex >= 0) { + // Add event + _dispatchEvent({ + rootEl: parentEl, + name: 'add', + toEl: parentEl, + fromEl: rootEl, + originalEvent: evt + }); + + // Remove event + _dispatchEvent({ + sortable: this, + name: 'remove', + toEl: parentEl, + originalEvent: evt + }); + + // drag from one list and drop into another + _dispatchEvent({ + rootEl: parentEl, + name: 'sort', + toEl: parentEl, + fromEl: rootEl, + originalEvent: evt + }); + _dispatchEvent({ + sortable: this, + name: 'sort', + toEl: parentEl, + originalEvent: evt + }); + } + putSortable && putSortable.save(); + } else { + if (newIndex !== oldIndex) { + if (newIndex >= 0) { + // drag & drop within the same list + _dispatchEvent({ + sortable: this, + name: 'update', + toEl: parentEl, + originalEvent: evt + }); + _dispatchEvent({ + sortable: this, + name: 'sort', + toEl: parentEl, + originalEvent: evt + }); + } + } + } + if (Sortable.active) { + /* jshint eqnull:true */ + if (newIndex == null || newIndex === -1) { + newIndex = oldIndex; + newDraggableIndex = oldDraggableIndex; + } + _dispatchEvent({ + sortable: this, + name: 'end', + toEl: parentEl, + originalEvent: evt + }); + + // Save sorting + this.save(); + } + } + } + this._nulling(); + }, + _nulling: function _nulling() { + pluginEvent('nulling', this); + rootEl = dragEl = parentEl = ghostEl = nextEl = cloneEl = lastDownEl = cloneHidden = tapEvt = touchEvt = moved = newIndex = newDraggableIndex = oldIndex = oldDraggableIndex = lastTarget = lastDirection = putSortable = activeGroup = Sortable.dragged = Sortable.ghost = Sortable.clone = Sortable.active = null; + savedInputChecked.forEach(function (el) { + el.checked = true; + }); + savedInputChecked.length = lastDx = lastDy = 0; + }, + handleEvent: function handleEvent( /**Event*/evt) { + switch (evt.type) { + case 'drop': + case 'dragend': + this._onDrop(evt); + break; + case 'dragenter': + case 'dragover': + if (dragEl) { + this._onDragOver(evt); + _globalDragOver(evt); + } + break; + case 'selectstart': + evt.preventDefault(); + break; + } + }, + /** + * Serializes the item into an array of string. + * @returns {String[]} + */ + toArray: function toArray() { + var order = [], + el, + children = this.el.children, + i = 0, + n = children.length, + options = this.options; + for (; i < n; i++) { + el = children[i]; + if (closest(el, options.draggable, this.el, false)) { + order.push(el.getAttribute(options.dataIdAttr) || _generateId(el)); + } + } + return order; + }, + /** + * Sorts the elements according to the array. + * @param {String[]} order order of the items + */ + sort: function sort(order, useAnimation) { + var items = {}, + rootEl = this.el; + this.toArray().forEach(function (id, i) { + var el = rootEl.children[i]; + if (closest(el, this.options.draggable, rootEl, false)) { + items[id] = el; + } + }, this); + useAnimation && this.captureAnimationState(); + order.forEach(function (id) { + if (items[id]) { + rootEl.removeChild(items[id]); + rootEl.appendChild(items[id]); + } + }); + useAnimation && this.animateAll(); + }, + /** + * Save the current sorting + */ + save: function save() { + var store = this.options.store; + store && store.set && store.set(this); + }, + /** + * For each element in the set, get the first element that matches the selector by testing the element itself and traversing up through its ancestors in the DOM tree. + * @param {HTMLElement} el + * @param {String} [selector] default: `options.draggable` + * @returns {HTMLElement|null} + */ + closest: function closest$1(el, selector) { + return closest(el, selector || this.options.draggable, this.el, false); + }, + /** + * Set/get option + * @param {string} name + * @param {*} [value] + * @returns {*} + */ + option: function option(name, value) { + var options = this.options; + if (value === void 0) { + return options[name]; + } else { + var modifiedValue = PluginManager.modifyOption(this, name, value); + if (typeof modifiedValue !== 'undefined') { + options[name] = modifiedValue; + } else { + options[name] = value; + } + if (name === 'group') { + _prepareGroup(options); + } + } + }, + /** + * Destroy + */ + destroy: function destroy() { + pluginEvent('destroy', this); + var el = this.el; + el[expando] = null; + off(el, 'mousedown', this._onTapStart); + off(el, 'touchstart', this._onTapStart); + off(el, 'pointerdown', this._onTapStart); + if (this.nativeDraggable) { + off(el, 'dragover', this); + off(el, 'dragenter', this); + } + // Remove draggable attributes + Array.prototype.forEach.call(el.querySelectorAll('[draggable]'), function (el) { + el.removeAttribute('draggable'); + }); + this._onDrop(); + this._disableDelayedDragEvents(); + sortables.splice(sortables.indexOf(this.el), 1); + this.el = el = null; + }, + _hideClone: function _hideClone() { + if (!cloneHidden) { + pluginEvent('hideClone', this); + if (Sortable.eventCanceled) return; + css(cloneEl, 'display', 'none'); + if (this.options.removeCloneOnHide && cloneEl.parentNode) { + cloneEl.parentNode.removeChild(cloneEl); + } + cloneHidden = true; + } + }, + _showClone: function _showClone(putSortable) { + if (putSortable.lastPutMode !== 'clone') { + this._hideClone(); + return; + } + if (cloneHidden) { + pluginEvent('showClone', this); + if (Sortable.eventCanceled) return; + + // show clone at dragEl or original position + if (dragEl.parentNode == rootEl && !this.options.group.revertClone) { + rootEl.insertBefore(cloneEl, dragEl); + } else if (nextEl) { + rootEl.insertBefore(cloneEl, nextEl); + } else { + rootEl.appendChild(cloneEl); + } + if (this.options.group.revertClone) { + this.animate(dragEl, cloneEl); + } + css(cloneEl, 'display', ''); + cloneHidden = false; + } + } + }; + function _globalDragOver( /**Event*/evt) { + if (evt.dataTransfer) { + evt.dataTransfer.dropEffect = 'move'; + } + evt.cancelable && evt.preventDefault(); + } + function _onMove(fromEl, toEl, dragEl, dragRect, targetEl, targetRect, originalEvent, willInsertAfter) { + var evt, + sortable = fromEl[expando], + onMoveFn = sortable.options.onMove, + retVal; + // Support for new CustomEvent feature + if (window.CustomEvent && !IE11OrLess && !Edge) { + evt = new CustomEvent('move', { + bubbles: true, + cancelable: true + }); + } else { + evt = document.createEvent('Event'); + evt.initEvent('move', true, true); + } + evt.to = toEl; + evt.from = fromEl; + evt.dragged = dragEl; + evt.draggedRect = dragRect; + evt.related = targetEl || toEl; + evt.relatedRect = targetRect || getRect(toEl); + evt.willInsertAfter = willInsertAfter; + evt.originalEvent = originalEvent; + fromEl.dispatchEvent(evt); + if (onMoveFn) { + retVal = onMoveFn.call(sortable, evt, originalEvent); + } + return retVal; + } + function _disableDraggable(el) { + el.draggable = false; + } + function _unsilent() { + _silent = false; + } + function _ghostIsFirst(evt, vertical, sortable) { + var firstElRect = getRect(getChild(sortable.el, 0, sortable.options, true)); + var childContainingRect = getChildContainingRectFromElement(sortable.el, sortable.options, ghostEl); + var spacer = 10; + return vertical ? evt.clientX < childContainingRect.left - spacer || evt.clientY < firstElRect.top && evt.clientX < firstElRect.right : evt.clientY < childContainingRect.top - spacer || evt.clientY < firstElRect.bottom && evt.clientX < firstElRect.left; + } + function _ghostIsLast(evt, vertical, sortable) { + var lastElRect = getRect(lastChild(sortable.el, sortable.options.draggable)); + var childContainingRect = getChildContainingRectFromElement(sortable.el, sortable.options, ghostEl); + var spacer = 10; + return vertical ? evt.clientX > childContainingRect.right + spacer || evt.clientY > lastElRect.bottom && evt.clientX > lastElRect.left : evt.clientY > childContainingRect.bottom + spacer || evt.clientX > lastElRect.right && evt.clientY > lastElRect.top; + } + function _getSwapDirection(evt, target, targetRect, vertical, swapThreshold, invertedSwapThreshold, invertSwap, isLastTarget) { + var mouseOnAxis = vertical ? evt.clientY : evt.clientX, + targetLength = vertical ? targetRect.height : targetRect.width, + targetS1 = vertical ? targetRect.top : targetRect.left, + targetS2 = vertical ? targetRect.bottom : targetRect.right, + invert = false; + if (!invertSwap) { + // Never invert or create dragEl shadow when target movemenet causes mouse to move past the end of regular swapThreshold + if (isLastTarget && targetMoveDistance < targetLength * swapThreshold) { + // multiplied only by swapThreshold because mouse will already be inside target by (1 - threshold) * targetLength / 2 + // check if past first invert threshold on side opposite of lastDirection + if (!pastFirstInvertThresh && (lastDirection === 1 ? mouseOnAxis > targetS1 + targetLength * invertedSwapThreshold / 2 : mouseOnAxis < targetS2 - targetLength * invertedSwapThreshold / 2)) { + // past first invert threshold, do not restrict inverted threshold to dragEl shadow + pastFirstInvertThresh = true; + } + if (!pastFirstInvertThresh) { + // dragEl shadow (target move distance shadow) + if (lastDirection === 1 ? mouseOnAxis < targetS1 + targetMoveDistance // over dragEl shadow + : mouseOnAxis > targetS2 - targetMoveDistance) { + return -lastDirection; + } + } else { + invert = true; + } + } else { + // Regular + if (mouseOnAxis > targetS1 + targetLength * (1 - swapThreshold) / 2 && mouseOnAxis < targetS2 - targetLength * (1 - swapThreshold) / 2) { + return _getInsertDirection(target); + } + } + } + invert = invert || invertSwap; + if (invert) { + // Invert of regular + if (mouseOnAxis < targetS1 + targetLength * invertedSwapThreshold / 2 || mouseOnAxis > targetS2 - targetLength * invertedSwapThreshold / 2) { + return mouseOnAxis > targetS1 + targetLength / 2 ? 1 : -1; + } + } + return 0; + } + + /** + * Gets the direction dragEl must be swapped relative to target in order to make it + * seem that dragEl has been "inserted" into that element's position + * @param {HTMLElement} target The target whose position dragEl is being inserted at + * @return {Number} Direction dragEl must be swapped + */ + function _getInsertDirection(target) { + if (index(dragEl) < index(target)) { + return 1; + } else { + return -1; + } + } + + /** + * Generate id + * @param {HTMLElement} el + * @returns {String} + * @private + */ + function _generateId(el) { + var str = el.tagName + el.className + el.src + el.href + el.textContent, + i = str.length, + sum = 0; + while (i--) { + sum += str.charCodeAt(i); + } + return sum.toString(36); + } + function _saveInputCheckedState(root) { + savedInputChecked.length = 0; + var inputs = root.getElementsByTagName('input'); + var idx = inputs.length; + while (idx--) { + var el = inputs[idx]; + el.checked && savedInputChecked.push(el); + } + } + function _nextTick(fn) { + return setTimeout(fn, 0); + } + function _cancelNextTick(id) { + return clearTimeout(id); + } + + // Fixed #973: + if (documentExists) { + on(document, 'touchmove', function (evt) { + if ((Sortable.active || awaitingDragStarted) && evt.cancelable) { + evt.preventDefault(); + } + }); + } + + // Export utils + Sortable.utils = { + on: on, + off: off, + css: css, + find: find, + is: function is(el, selector) { + return !!closest(el, selector, el, false); + }, + extend: extend, + throttle: throttle, + closest: closest, + toggleClass: toggleClass, + clone: clone, + index: index, + nextTick: _nextTick, + cancelNextTick: _cancelNextTick, + detectDirection: _detectDirection, + getChild: getChild + }; + + /** + * Get the Sortable instance of an element + * @param {HTMLElement} element The element + * @return {Sortable|undefined} The instance of Sortable + */ + Sortable.get = function (element) { + return element[expando]; + }; + + /** + * Mount a plugin to Sortable + * @param {...SortablePlugin|SortablePlugin[]} plugins Plugins being mounted + */ + Sortable.mount = function () { + for (var _len = arguments.length, plugins = new Array(_len), _key = 0; _key < _len; _key++) { + plugins[_key] = arguments[_key]; + } + if (plugins[0].constructor === Array) plugins = plugins[0]; + plugins.forEach(function (plugin) { + if (!plugin.prototype || !plugin.prototype.constructor) { + throw "Sortable: Mounted plugin must be a constructor function, not ".concat({}.toString.call(plugin)); + } + if (plugin.utils) Sortable.utils = _objectSpread2(_objectSpread2({}, Sortable.utils), plugin.utils); + PluginManager.mount(plugin); + }); + }; + + /** + * Create sortable instance + * @param {HTMLElement} el + * @param {Object} [options] + */ + Sortable.create = function (el, options) { + return new Sortable(el, options); + }; + + // Export + Sortable.version = version; + + var autoScrolls = [], + scrollEl, + scrollRootEl, + scrolling = false, + lastAutoScrollX, + lastAutoScrollY, + touchEvt$1, + pointerElemChangedInterval; + function AutoScrollPlugin() { + function AutoScroll() { + this.defaults = { + scroll: true, + forceAutoScrollFallback: false, + scrollSensitivity: 30, + scrollSpeed: 10, + bubbleScroll: true + }; + + // Bind all private methods + for (var fn in this) { + if (fn.charAt(0) === '_' && typeof this[fn] === 'function') { + this[fn] = this[fn].bind(this); + } + } + } + AutoScroll.prototype = { + dragStarted: function dragStarted(_ref) { + var originalEvent = _ref.originalEvent; + if (this.sortable.nativeDraggable) { + on(document, 'dragover', this._handleAutoScroll); + } else { + if (this.options.supportPointer) { + on(document, 'pointermove', this._handleFallbackAutoScroll); + } else if (originalEvent.touches) { + on(document, 'touchmove', this._handleFallbackAutoScroll); + } else { + on(document, 'mousemove', this._handleFallbackAutoScroll); + } + } + }, + dragOverCompleted: function dragOverCompleted(_ref2) { + var originalEvent = _ref2.originalEvent; + // For when bubbling is canceled and using fallback (fallback 'touchmove' always reached) + if (!this.options.dragOverBubble && !originalEvent.rootEl) { + this._handleAutoScroll(originalEvent); + } + }, + drop: function drop() { + if (this.sortable.nativeDraggable) { + off(document, 'dragover', this._handleAutoScroll); + } else { + off(document, 'pointermove', this._handleFallbackAutoScroll); + off(document, 'touchmove', this._handleFallbackAutoScroll); + off(document, 'mousemove', this._handleFallbackAutoScroll); + } + clearPointerElemChangedInterval(); + clearAutoScrolls(); + cancelThrottle(); + }, + nulling: function nulling() { + touchEvt$1 = scrollRootEl = scrollEl = scrolling = pointerElemChangedInterval = lastAutoScrollX = lastAutoScrollY = null; + autoScrolls.length = 0; + }, + _handleFallbackAutoScroll: function _handleFallbackAutoScroll(evt) { + this._handleAutoScroll(evt, true); + }, + _handleAutoScroll: function _handleAutoScroll(evt, fallback) { + var _this = this; + var x = (evt.touches ? evt.touches[0] : evt).clientX, + y = (evt.touches ? evt.touches[0] : evt).clientY, + elem = document.elementFromPoint(x, y); + touchEvt$1 = evt; + + // IE does not seem to have native autoscroll, + // Edge's autoscroll seems too conditional, + // MACOS Safari does not have autoscroll, + // Firefox and Chrome are good + if (fallback || this.options.forceAutoScrollFallback || Edge || IE11OrLess || Safari) { + autoScroll(evt, this.options, elem, fallback); + + // Listener for pointer element change + var ogElemScroller = getParentAutoScrollElement(elem, true); + if (scrolling && (!pointerElemChangedInterval || x !== lastAutoScrollX || y !== lastAutoScrollY)) { + pointerElemChangedInterval && clearPointerElemChangedInterval(); + // Detect for pointer elem change, emulating native DnD behaviour + pointerElemChangedInterval = setInterval(function () { + var newElem = getParentAutoScrollElement(document.elementFromPoint(x, y), true); + if (newElem !== ogElemScroller) { + ogElemScroller = newElem; + clearAutoScrolls(); + } + autoScroll(evt, _this.options, newElem, fallback); + }, 10); + lastAutoScrollX = x; + lastAutoScrollY = y; + } + } else { + // if DnD is enabled (and browser has good autoscrolling), first autoscroll will already scroll, so get parent autoscroll of first autoscroll + if (!this.options.bubbleScroll || getParentAutoScrollElement(elem, true) === getWindowScrollingElement()) { + clearAutoScrolls(); + return; + } + autoScroll(evt, this.options, getParentAutoScrollElement(elem, false), false); + } + } + }; + return _extends(AutoScroll, { + pluginName: 'scroll', + initializeByDefault: true + }); + } + function clearAutoScrolls() { + autoScrolls.forEach(function (autoScroll) { + clearInterval(autoScroll.pid); + }); + autoScrolls = []; + } + function clearPointerElemChangedInterval() { + clearInterval(pointerElemChangedInterval); + } + var autoScroll = throttle(function (evt, options, rootEl, isFallback) { + // Bug: https://bugzilla.mozilla.org/show_bug.cgi?id=505521 + if (!options.scroll) return; + var x = (evt.touches ? evt.touches[0] : evt).clientX, + y = (evt.touches ? evt.touches[0] : evt).clientY, + sens = options.scrollSensitivity, + speed = options.scrollSpeed, + winScroller = getWindowScrollingElement(); + var scrollThisInstance = false, + scrollCustomFn; + + // New scroll root, set scrollEl + if (scrollRootEl !== rootEl) { + scrollRootEl = rootEl; + clearAutoScrolls(); + scrollEl = options.scroll; + scrollCustomFn = options.scrollFn; + if (scrollEl === true) { + scrollEl = getParentAutoScrollElement(rootEl, true); + } + } + var layersOut = 0; + var currentParent = scrollEl; + do { + var el = currentParent, + rect = getRect(el), + top = rect.top, + bottom = rect.bottom, + left = rect.left, + right = rect.right, + width = rect.width, + height = rect.height, + canScrollX = void 0, + canScrollY = void 0, + scrollWidth = el.scrollWidth, + scrollHeight = el.scrollHeight, + elCSS = css(el), + scrollPosX = el.scrollLeft, + scrollPosY = el.scrollTop; + if (el === winScroller) { + canScrollX = width < scrollWidth && (elCSS.overflowX === 'auto' || elCSS.overflowX === 'scroll' || elCSS.overflowX === 'visible'); + canScrollY = height < scrollHeight && (elCSS.overflowY === 'auto' || elCSS.overflowY === 'scroll' || elCSS.overflowY === 'visible'); + } else { + canScrollX = width < scrollWidth && (elCSS.overflowX === 'auto' || elCSS.overflowX === 'scroll'); + canScrollY = height < scrollHeight && (elCSS.overflowY === 'auto' || elCSS.overflowY === 'scroll'); + } + var vx = canScrollX && (Math.abs(right - x) <= sens && scrollPosX + width < scrollWidth) - (Math.abs(left - x) <= sens && !!scrollPosX); + var vy = canScrollY && (Math.abs(bottom - y) <= sens && scrollPosY + height < scrollHeight) - (Math.abs(top - y) <= sens && !!scrollPosY); + if (!autoScrolls[layersOut]) { + for (var i = 0; i <= layersOut; i++) { + if (!autoScrolls[i]) { + autoScrolls[i] = {}; + } + } + } + if (autoScrolls[layersOut].vx != vx || autoScrolls[layersOut].vy != vy || autoScrolls[layersOut].el !== el) { + autoScrolls[layersOut].el = el; + autoScrolls[layersOut].vx = vx; + autoScrolls[layersOut].vy = vy; + clearInterval(autoScrolls[layersOut].pid); + if (vx != 0 || vy != 0) { + scrollThisInstance = true; + /* jshint loopfunc:true */ + autoScrolls[layersOut].pid = setInterval(function () { + // emulate drag over during autoscroll (fallback), emulating native DnD behaviour + if (isFallback && this.layer === 0) { + Sortable.active._onTouchMove(touchEvt$1); // To move ghost if it is positioned absolutely + } + var scrollOffsetY = autoScrolls[this.layer].vy ? autoScrolls[this.layer].vy * speed : 0; + var scrollOffsetX = autoScrolls[this.layer].vx ? autoScrolls[this.layer].vx * speed : 0; + if (typeof scrollCustomFn === 'function') { + if (scrollCustomFn.call(Sortable.dragged.parentNode[expando], scrollOffsetX, scrollOffsetY, evt, touchEvt$1, autoScrolls[this.layer].el) !== 'continue') { + return; + } + } + scrollBy(autoScrolls[this.layer].el, scrollOffsetX, scrollOffsetY); + }.bind({ + layer: layersOut + }), 24); + } + } + layersOut++; + } while (options.bubbleScroll && currentParent !== winScroller && (currentParent = getParentAutoScrollElement(currentParent, false))); + scrolling = scrollThisInstance; // in case another function catches scrolling as false in between when it is not + }, 30); + + var drop = function drop(_ref) { + var originalEvent = _ref.originalEvent, + putSortable = _ref.putSortable, + dragEl = _ref.dragEl, + activeSortable = _ref.activeSortable, + dispatchSortableEvent = _ref.dispatchSortableEvent, + hideGhostForTarget = _ref.hideGhostForTarget, + unhideGhostForTarget = _ref.unhideGhostForTarget; + if (!originalEvent) return; + var toSortable = putSortable || activeSortable; + hideGhostForTarget(); + var touch = originalEvent.changedTouches && originalEvent.changedTouches.length ? originalEvent.changedTouches[0] : originalEvent; + var target = document.elementFromPoint(touch.clientX, touch.clientY); + unhideGhostForTarget(); + if (toSortable && !toSortable.el.contains(target)) { + dispatchSortableEvent('spill'); + this.onSpill({ + dragEl: dragEl, + putSortable: putSortable + }); + } + }; + function Revert() {} + Revert.prototype = { + startIndex: null, + dragStart: function dragStart(_ref2) { + var oldDraggableIndex = _ref2.oldDraggableIndex; + this.startIndex = oldDraggableIndex; + }, + onSpill: function onSpill(_ref3) { + var dragEl = _ref3.dragEl, + putSortable = _ref3.putSortable; + this.sortable.captureAnimationState(); + if (putSortable) { + putSortable.captureAnimationState(); + } + var nextSibling = getChild(this.sortable.el, this.startIndex, this.options); + if (nextSibling) { + this.sortable.el.insertBefore(dragEl, nextSibling); + } else { + this.sortable.el.appendChild(dragEl); + } + this.sortable.animateAll(); + if (putSortable) { + putSortable.animateAll(); + } + }, + drop: drop + }; + _extends(Revert, { + pluginName: 'revertOnSpill' + }); + function Remove() {} + Remove.prototype = { + onSpill: function onSpill(_ref4) { + var dragEl = _ref4.dragEl, + putSortable = _ref4.putSortable; + var parentSortable = putSortable || this.sortable; + parentSortable.captureAnimationState(); + dragEl.parentNode && dragEl.parentNode.removeChild(dragEl); + parentSortable.animateAll(); + }, + drop: drop + }; + _extends(Remove, { + pluginName: 'removeOnSpill' + }); + + var lastSwapEl; + function SwapPlugin() { + function Swap() { + this.defaults = { + swapClass: 'sortable-swap-highlight' + }; + } + Swap.prototype = { + dragStart: function dragStart(_ref) { + var dragEl = _ref.dragEl; + lastSwapEl = dragEl; + }, + dragOverValid: function dragOverValid(_ref2) { + var completed = _ref2.completed, + target = _ref2.target, + onMove = _ref2.onMove, + activeSortable = _ref2.activeSortable, + changed = _ref2.changed, + cancel = _ref2.cancel; + if (!activeSortable.options.swap) return; + var el = this.sortable.el, + options = this.options; + if (target && target !== el) { + var prevSwapEl = lastSwapEl; + if (onMove(target) !== false) { + toggleClass(target, options.swapClass, true); + lastSwapEl = target; + } else { + lastSwapEl = null; + } + if (prevSwapEl && prevSwapEl !== lastSwapEl) { + toggleClass(prevSwapEl, options.swapClass, false); + } + } + changed(); + completed(true); + cancel(); + }, + drop: function drop(_ref3) { + var activeSortable = _ref3.activeSortable, + putSortable = _ref3.putSortable, + dragEl = _ref3.dragEl; + var toSortable = putSortable || this.sortable; + var options = this.options; + lastSwapEl && toggleClass(lastSwapEl, options.swapClass, false); + if (lastSwapEl && (options.swap || putSortable && putSortable.options.swap)) { + if (dragEl !== lastSwapEl) { + toSortable.captureAnimationState(); + if (toSortable !== activeSortable) activeSortable.captureAnimationState(); + swapNodes(dragEl, lastSwapEl); + toSortable.animateAll(); + if (toSortable !== activeSortable) activeSortable.animateAll(); + } + } + }, + nulling: function nulling() { + lastSwapEl = null; + } + }; + return _extends(Swap, { + pluginName: 'swap', + eventProperties: function eventProperties() { + return { + swapItem: lastSwapEl + }; + } + }); + } + function swapNodes(n1, n2) { + var p1 = n1.parentNode, + p2 = n2.parentNode, + i1, + i2; + if (!p1 || !p2 || p1.isEqualNode(n2) || p2.isEqualNode(n1)) return; + i1 = index(n1); + i2 = index(n2); + if (p1.isEqualNode(p2) && i1 < i2) { + i2++; + } + p1.insertBefore(n2, p1.children[i1]); + p2.insertBefore(n1, p2.children[i2]); + } + + var multiDragElements = [], + multiDragClones = [], + lastMultiDragSelect, + // for selection with modifier key down (SHIFT) + multiDragSortable, + initialFolding = false, + // Initial multi-drag fold when drag started + folding = false, + // Folding any other time + dragStarted = false, + dragEl$1, + clonesFromRect, + clonesHidden; + function MultiDragPlugin() { + function MultiDrag(sortable) { + // Bind all private methods + for (var fn in this) { + if (fn.charAt(0) === '_' && typeof this[fn] === 'function') { + this[fn] = this[fn].bind(this); + } + } + if (!sortable.options.avoidImplicitDeselect) { + if (sortable.options.supportPointer) { + on(document, 'pointerup', this._deselectMultiDrag); + } else { + on(document, 'mouseup', this._deselectMultiDrag); + on(document, 'touchend', this._deselectMultiDrag); + } + } + on(document, 'keydown', this._checkKeyDown); + on(document, 'keyup', this._checkKeyUp); + this.defaults = { + selectedClass: 'sortable-selected', + multiDragKey: null, + avoidImplicitDeselect: false, + setData: function setData(dataTransfer, dragEl) { + var data = ''; + if (multiDragElements.length && multiDragSortable === sortable) { + multiDragElements.forEach(function (multiDragElement, i) { + data += (!i ? '' : ', ') + multiDragElement.textContent; + }); + } else { + data = dragEl.textContent; + } + dataTransfer.setData('Text', data); + } + }; + } + MultiDrag.prototype = { + multiDragKeyDown: false, + isMultiDrag: false, + delayStartGlobal: function delayStartGlobal(_ref) { + var dragged = _ref.dragEl; + dragEl$1 = dragged; + }, + delayEnded: function delayEnded() { + this.isMultiDrag = ~multiDragElements.indexOf(dragEl$1); + }, + setupClone: function setupClone(_ref2) { + var sortable = _ref2.sortable, + cancel = _ref2.cancel; + if (!this.isMultiDrag) return; + for (var i = 0; i < multiDragElements.length; i++) { + multiDragClones.push(clone(multiDragElements[i])); + multiDragClones[i].sortableIndex = multiDragElements[i].sortableIndex; + multiDragClones[i].draggable = false; + multiDragClones[i].style['will-change'] = ''; + toggleClass(multiDragClones[i], this.options.selectedClass, false); + multiDragElements[i] === dragEl$1 && toggleClass(multiDragClones[i], this.options.chosenClass, false); + } + sortable._hideClone(); + cancel(); + }, + clone: function clone(_ref3) { + var sortable = _ref3.sortable, + rootEl = _ref3.rootEl, + dispatchSortableEvent = _ref3.dispatchSortableEvent, + cancel = _ref3.cancel; + if (!this.isMultiDrag) return; + if (!this.options.removeCloneOnHide) { + if (multiDragElements.length && multiDragSortable === sortable) { + insertMultiDragClones(true, rootEl); + dispatchSortableEvent('clone'); + cancel(); + } + } + }, + showClone: function showClone(_ref4) { + var cloneNowShown = _ref4.cloneNowShown, + rootEl = _ref4.rootEl, + cancel = _ref4.cancel; + if (!this.isMultiDrag) return; + insertMultiDragClones(false, rootEl); + multiDragClones.forEach(function (clone) { + css(clone, 'display', ''); + }); + cloneNowShown(); + clonesHidden = false; + cancel(); + }, + hideClone: function hideClone(_ref5) { + var _this = this; + var sortable = _ref5.sortable, + cloneNowHidden = _ref5.cloneNowHidden, + cancel = _ref5.cancel; + if (!this.isMultiDrag) return; + multiDragClones.forEach(function (clone) { + css(clone, 'display', 'none'); + if (_this.options.removeCloneOnHide && clone.parentNode) { + clone.parentNode.removeChild(clone); + } + }); + cloneNowHidden(); + clonesHidden = true; + cancel(); + }, + dragStartGlobal: function dragStartGlobal(_ref6) { + var sortable = _ref6.sortable; + if (!this.isMultiDrag && multiDragSortable) { + multiDragSortable.multiDrag._deselectMultiDrag(); + } + multiDragElements.forEach(function (multiDragElement) { + multiDragElement.sortableIndex = index(multiDragElement); + }); + + // Sort multi-drag elements + multiDragElements = multiDragElements.sort(function (a, b) { + return a.sortableIndex - b.sortableIndex; + }); + dragStarted = true; + }, + dragStarted: function dragStarted(_ref7) { + var _this2 = this; + var sortable = _ref7.sortable; + if (!this.isMultiDrag) return; + if (this.options.sort) { + // Capture rects, + // hide multi drag elements (by positioning them absolute), + // set multi drag elements rects to dragRect, + // show multi drag elements, + // animate to rects, + // unset rects & remove from DOM + + sortable.captureAnimationState(); + if (this.options.animation) { + multiDragElements.forEach(function (multiDragElement) { + if (multiDragElement === dragEl$1) return; + css(multiDragElement, 'position', 'absolute'); + }); + var dragRect = getRect(dragEl$1, false, true, true); + multiDragElements.forEach(function (multiDragElement) { + if (multiDragElement === dragEl$1) return; + setRect(multiDragElement, dragRect); + }); + folding = true; + initialFolding = true; + } + } + sortable.animateAll(function () { + folding = false; + initialFolding = false; + if (_this2.options.animation) { + multiDragElements.forEach(function (multiDragElement) { + unsetRect(multiDragElement); + }); + } + + // Remove all auxiliary multidrag items from el, if sorting enabled + if (_this2.options.sort) { + removeMultiDragElements(); + } + }); + }, + dragOver: function dragOver(_ref8) { + var target = _ref8.target, + completed = _ref8.completed, + cancel = _ref8.cancel; + if (folding && ~multiDragElements.indexOf(target)) { + completed(false); + cancel(); + } + }, + revert: function revert(_ref9) { + var fromSortable = _ref9.fromSortable, + rootEl = _ref9.rootEl, + sortable = _ref9.sortable, + dragRect = _ref9.dragRect; + if (multiDragElements.length > 1) { + // Setup unfold animation + multiDragElements.forEach(function (multiDragElement) { + sortable.addAnimationState({ + target: multiDragElement, + rect: folding ? getRect(multiDragElement) : dragRect + }); + unsetRect(multiDragElement); + multiDragElement.fromRect = dragRect; + fromSortable.removeAnimationState(multiDragElement); + }); + folding = false; + insertMultiDragElements(!this.options.removeCloneOnHide, rootEl); + } + }, + dragOverCompleted: function dragOverCompleted(_ref10) { + var sortable = _ref10.sortable, + isOwner = _ref10.isOwner, + insertion = _ref10.insertion, + activeSortable = _ref10.activeSortable, + parentEl = _ref10.parentEl, + putSortable = _ref10.putSortable; + var options = this.options; + if (insertion) { + // Clones must be hidden before folding animation to capture dragRectAbsolute properly + if (isOwner) { + activeSortable._hideClone(); + } + initialFolding = false; + // If leaving sort:false root, or already folding - Fold to new location + if (options.animation && multiDragElements.length > 1 && (folding || !isOwner && !activeSortable.options.sort && !putSortable)) { + // Fold: Set all multi drag elements's rects to dragEl's rect when multi-drag elements are invisible + var dragRectAbsolute = getRect(dragEl$1, false, true, true); + multiDragElements.forEach(function (multiDragElement) { + if (multiDragElement === dragEl$1) return; + setRect(multiDragElement, dragRectAbsolute); + + // Move element(s) to end of parentEl so that it does not interfere with multi-drag clones insertion if they are inserted + // while folding, and so that we can capture them again because old sortable will no longer be fromSortable + parentEl.appendChild(multiDragElement); + }); + folding = true; + } + + // Clones must be shown (and check to remove multi drags) after folding when interfering multiDragElements are moved out + if (!isOwner) { + // Only remove if not folding (folding will remove them anyways) + if (!folding) { + removeMultiDragElements(); + } + if (multiDragElements.length > 1) { + var clonesHiddenBefore = clonesHidden; + activeSortable._showClone(sortable); + + // Unfold animation for clones if showing from hidden + if (activeSortable.options.animation && !clonesHidden && clonesHiddenBefore) { + multiDragClones.forEach(function (clone) { + activeSortable.addAnimationState({ + target: clone, + rect: clonesFromRect + }); + clone.fromRect = clonesFromRect; + clone.thisAnimationDuration = null; + }); + } + } else { + activeSortable._showClone(sortable); + } + } + } + }, + dragOverAnimationCapture: function dragOverAnimationCapture(_ref11) { + var dragRect = _ref11.dragRect, + isOwner = _ref11.isOwner, + activeSortable = _ref11.activeSortable; + multiDragElements.forEach(function (multiDragElement) { + multiDragElement.thisAnimationDuration = null; + }); + if (activeSortable.options.animation && !isOwner && activeSortable.multiDrag.isMultiDrag) { + clonesFromRect = _extends({}, dragRect); + var dragMatrix = matrix(dragEl$1, true); + clonesFromRect.top -= dragMatrix.f; + clonesFromRect.left -= dragMatrix.e; + } + }, + dragOverAnimationComplete: function dragOverAnimationComplete() { + if (folding) { + folding = false; + removeMultiDragElements(); + } + }, + drop: function drop(_ref12) { + var evt = _ref12.originalEvent, + rootEl = _ref12.rootEl, + parentEl = _ref12.parentEl, + sortable = _ref12.sortable, + dispatchSortableEvent = _ref12.dispatchSortableEvent, + oldIndex = _ref12.oldIndex, + putSortable = _ref12.putSortable; + var toSortable = putSortable || this.sortable; + if (!evt) return; + var options = this.options, + children = parentEl.children; + + // Multi-drag selection + if (!dragStarted) { + if (options.multiDragKey && !this.multiDragKeyDown) { + this._deselectMultiDrag(); + } + toggleClass(dragEl$1, options.selectedClass, !~multiDragElements.indexOf(dragEl$1)); + if (!~multiDragElements.indexOf(dragEl$1)) { + multiDragElements.push(dragEl$1); + dispatchEvent({ + sortable: sortable, + rootEl: rootEl, + name: 'select', + targetEl: dragEl$1, + originalEvent: evt + }); + + // Modifier activated, select from last to dragEl + if (evt.shiftKey && lastMultiDragSelect && sortable.el.contains(lastMultiDragSelect)) { + var lastIndex = index(lastMultiDragSelect), + currentIndex = index(dragEl$1); + if (~lastIndex && ~currentIndex && lastIndex !== currentIndex) { + // Must include lastMultiDragSelect (select it), in case modified selection from no selection + // (but previous selection existed) + var n, i; + if (currentIndex > lastIndex) { + i = lastIndex; + n = currentIndex; + } else { + i = currentIndex; + n = lastIndex + 1; + } + for (; i < n; i++) { + if (~multiDragElements.indexOf(children[i])) continue; + toggleClass(children[i], options.selectedClass, true); + multiDragElements.push(children[i]); + dispatchEvent({ + sortable: sortable, + rootEl: rootEl, + name: 'select', + targetEl: children[i], + originalEvent: evt + }); + } + } + } else { + lastMultiDragSelect = dragEl$1; + } + multiDragSortable = toSortable; + } else { + multiDragElements.splice(multiDragElements.indexOf(dragEl$1), 1); + lastMultiDragSelect = null; + dispatchEvent({ + sortable: sortable, + rootEl: rootEl, + name: 'deselect', + targetEl: dragEl$1, + originalEvent: evt + }); + } + } + + // Multi-drag drop + if (dragStarted && this.isMultiDrag) { + folding = false; + // Do not "unfold" after around dragEl if reverted + if ((parentEl[expando].options.sort || parentEl !== rootEl) && multiDragElements.length > 1) { + var dragRect = getRect(dragEl$1), + multiDragIndex = index(dragEl$1, ':not(.' + this.options.selectedClass + ')'); + if (!initialFolding && options.animation) dragEl$1.thisAnimationDuration = null; + toSortable.captureAnimationState(); + if (!initialFolding) { + if (options.animation) { + dragEl$1.fromRect = dragRect; + multiDragElements.forEach(function (multiDragElement) { + multiDragElement.thisAnimationDuration = null; + if (multiDragElement !== dragEl$1) { + var rect = folding ? getRect(multiDragElement) : dragRect; + multiDragElement.fromRect = rect; + + // Prepare unfold animation + toSortable.addAnimationState({ + target: multiDragElement, + rect: rect + }); + } + }); + } + + // Multi drag elements are not necessarily removed from the DOM on drop, so to reinsert + // properly they must all be removed + removeMultiDragElements(); + multiDragElements.forEach(function (multiDragElement) { + if (children[multiDragIndex]) { + parentEl.insertBefore(multiDragElement, children[multiDragIndex]); + } else { + parentEl.appendChild(multiDragElement); + } + multiDragIndex++; + }); + + // If initial folding is done, the elements may have changed position because they are now + // unfolding around dragEl, even though dragEl may not have his index changed, so update event + // must be fired here as Sortable will not. + if (oldIndex === index(dragEl$1)) { + var update = false; + multiDragElements.forEach(function (multiDragElement) { + if (multiDragElement.sortableIndex !== index(multiDragElement)) { + update = true; + return; + } + }); + if (update) { + dispatchSortableEvent('update'); + dispatchSortableEvent('sort'); + } + } + } + + // Must be done after capturing individual rects (scroll bar) + multiDragElements.forEach(function (multiDragElement) { + unsetRect(multiDragElement); + }); + toSortable.animateAll(); + } + multiDragSortable = toSortable; + } + + // Remove clones if necessary + if (rootEl === parentEl || putSortable && putSortable.lastPutMode !== 'clone') { + multiDragClones.forEach(function (clone) { + clone.parentNode && clone.parentNode.removeChild(clone); + }); + } + }, + nullingGlobal: function nullingGlobal() { + this.isMultiDrag = dragStarted = false; + multiDragClones.length = 0; + }, + destroyGlobal: function destroyGlobal() { + this._deselectMultiDrag(); + off(document, 'pointerup', this._deselectMultiDrag); + off(document, 'mouseup', this._deselectMultiDrag); + off(document, 'touchend', this._deselectMultiDrag); + off(document, 'keydown', this._checkKeyDown); + off(document, 'keyup', this._checkKeyUp); + }, + _deselectMultiDrag: function _deselectMultiDrag(evt) { + if (typeof dragStarted !== "undefined" && dragStarted) return; + + // Only deselect if selection is in this sortable + if (multiDragSortable !== this.sortable) return; + + // Only deselect if target is not item in this sortable + if (evt && closest(evt.target, this.options.draggable, this.sortable.el, false)) return; + + // Only deselect if left click + if (evt && evt.button !== 0) return; + while (multiDragElements.length) { + var el = multiDragElements[0]; + toggleClass(el, this.options.selectedClass, false); + multiDragElements.shift(); + dispatchEvent({ + sortable: this.sortable, + rootEl: this.sortable.el, + name: 'deselect', + targetEl: el, + originalEvent: evt + }); + } + }, + _checkKeyDown: function _checkKeyDown(evt) { + if (evt.key === this.options.multiDragKey) { + this.multiDragKeyDown = true; + } + }, + _checkKeyUp: function _checkKeyUp(evt) { + if (evt.key === this.options.multiDragKey) { + this.multiDragKeyDown = false; + } + } + }; + return _extends(MultiDrag, { + // Static methods & properties + pluginName: 'multiDrag', + utils: { + /** + * Selects the provided multi-drag item + * @param {HTMLElement} el The element to be selected + */ + select: function select(el) { + var sortable = el.parentNode[expando]; + if (!sortable || !sortable.options.multiDrag || ~multiDragElements.indexOf(el)) return; + if (multiDragSortable && multiDragSortable !== sortable) { + multiDragSortable.multiDrag._deselectMultiDrag(); + multiDragSortable = sortable; + } + toggleClass(el, sortable.options.selectedClass, true); + multiDragElements.push(el); + }, + /** + * Deselects the provided multi-drag item + * @param {HTMLElement} el The element to be deselected + */ + deselect: function deselect(el) { + var sortable = el.parentNode[expando], + index = multiDragElements.indexOf(el); + if (!sortable || !sortable.options.multiDrag || !~index) return; + toggleClass(el, sortable.options.selectedClass, false); + multiDragElements.splice(index, 1); + } + }, + eventProperties: function eventProperties() { + var _this3 = this; + var oldIndicies = [], + newIndicies = []; + multiDragElements.forEach(function (multiDragElement) { + oldIndicies.push({ + multiDragElement: multiDragElement, + index: multiDragElement.sortableIndex + }); + + // multiDragElements will already be sorted if folding + var newIndex; + if (folding && multiDragElement !== dragEl$1) { + newIndex = -1; + } else if (folding) { + newIndex = index(multiDragElement, ':not(.' + _this3.options.selectedClass + ')'); + } else { + newIndex = index(multiDragElement); + } + newIndicies.push({ + multiDragElement: multiDragElement, + index: newIndex + }); + }); + return { + items: _toConsumableArray(multiDragElements), + clones: [].concat(multiDragClones), + oldIndicies: oldIndicies, + newIndicies: newIndicies + }; + }, + optionListeners: { + multiDragKey: function multiDragKey(key) { + key = key.toLowerCase(); + if (key === 'ctrl') { + key = 'Control'; + } else if (key.length > 1) { + key = key.charAt(0).toUpperCase() + key.substr(1); + } + return key; + } + } + }); + } + function insertMultiDragElements(clonesInserted, rootEl) { + multiDragElements.forEach(function (multiDragElement, i) { + var target = rootEl.children[multiDragElement.sortableIndex + (clonesInserted ? Number(i) : 0)]; + if (target) { + rootEl.insertBefore(multiDragElement, target); + } else { + rootEl.appendChild(multiDragElement); + } + }); + } + + /** + * Insert multi-drag clones + * @param {[Boolean]} elementsInserted Whether the multi-drag elements are inserted + * @param {HTMLElement} rootEl + */ + function insertMultiDragClones(elementsInserted, rootEl) { + multiDragClones.forEach(function (clone, i) { + var target = rootEl.children[clone.sortableIndex + (elementsInserted ? Number(i) : 0)]; + if (target) { + rootEl.insertBefore(clone, target); + } else { + rootEl.appendChild(clone); + } + }); + } + function removeMultiDragElements() { + multiDragElements.forEach(function (multiDragElement) { + if (multiDragElement === dragEl$1) return; + multiDragElement.parentNode && multiDragElement.parentNode.removeChild(multiDragElement); + }); + } + + Sortable.mount(new AutoScrollPlugin()); + Sortable.mount(Remove, Revert); + + Sortable.mount(new SwapPlugin()); + Sortable.mount(new MultiDragPlugin()); + + return Sortable; + +}))); \ No newline at end of file diff --git a/src/assets/src/js/sortable.min.js b/src/assets/src/js/sortable.min.js new file mode 100644 index 0000000..bb99533 --- /dev/null +++ b/src/assets/src/js/sortable.min.js @@ -0,0 +1,2 @@ +/*! Sortable 1.15.2 - MIT | git://github.com/SortableJS/Sortable.git */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Sortable=e()}(this,function(){"use strict";function e(e,t){var n,o=Object.keys(e);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(e),t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),o.push.apply(o,n)),o}function I(o){for(var t=1;tt.length)&&(e=t.length);for(var n=0,o=new Array(e);n"===e[0]&&(e=e.substring(1)),t))try{if(t.matches)return t.matches(e);if(t.msMatchesSelector)return t.msMatchesSelector(e);if(t.webkitMatchesSelector)return t.webkitMatchesSelector(e)}catch(t){return}}function P(t,e,n,o){if(t){n=n||document;do{if(null!=e&&(">"!==e[0]||t.parentNode===n)&&p(t,e)||o&&t===n)return t}while(t!==n&&(t=(i=t).host&&i!==document&&i.host.nodeType?i.host:i.parentNode))}var i;return null}var g,m=/\s+/g;function k(t,e,n){var o;t&&e&&(t.classList?t.classList[n?"add":"remove"](e):(o=(" "+t.className+" ").replace(m," ").replace(" "+e+" "," "),t.className=(o+(n?" "+e:"")).replace(m," ")))}function R(t,e,n){var o=t&&t.style;if(o){if(void 0===n)return document.defaultView&&document.defaultView.getComputedStyle?n=document.defaultView.getComputedStyle(t,""):t.currentStyle&&(n=t.currentStyle),void 0===e?n:n[e];o[e=!(e in o||-1!==e.indexOf("webkit"))?"-webkit-"+e:e]=n+("string"==typeof n?"":"px")}}function v(t,e){var n="";if("string"==typeof t)n=t;else do{var o=R(t,"transform")}while(o&&"none"!==o&&(n=o+" "+n),!e&&(t=t.parentNode));var i=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return i&&new i(n)}function b(t,e,n){if(t){var o=t.getElementsByTagName(e),i=0,r=o.length;if(n)for(;i=n.left-e&&i<=n.right+e,e=r>=n.top-e&&r<=n.bottom+e;return o&&e?a=t:void 0}}),a);if(e){var n,o={};for(n in t)t.hasOwnProperty(n)&&(o[n]=t[n]);o.target=o.rootEl=e,o.preventDefault=void 0,o.stopPropagation=void 0,e[K]._onDragOver(o)}}var i,r,a}function Bt(t){V&&V.parentNode[K]._isOutsideThisEl(t.target)}function Ft(t,e){if(!t||!t.nodeType||1!==t.nodeType)throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(t));this.el=t,this.options=e=a({},e),t[K]=this;var n,o,i={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(t.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return Pt(t,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(t,e){t.setData("Text",e.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:!1!==Ft.supportPointer&&"PointerEvent"in window&&!u,emptyInsertThreshold:5};for(n in W.initializePlugins(this,t,i),i)n in e||(e[n]=i[n]);for(o in kt(e),this)"_"===o.charAt(0)&&"function"==typeof this[o]&&(this[o]=this[o].bind(this));this.nativeDraggable=!e.forceFallback&&Nt,this.nativeDraggable&&(this.options.touchStartThreshold=1),e.supportPointer?h(t,"pointerdown",this._onTapStart):(h(t,"mousedown",this._onTapStart),h(t,"touchstart",this._onTapStart)),this.nativeDraggable&&(h(t,"dragover",this),h(t,"dragenter",this)),Dt.push(this.el),e.store&&e.store.get&&this.sort(e.store.get(this)||[]),a(this,x())}function jt(t,e,n,o,i,r,a,l){var s,c,u=t[K],d=u.options.onMove;return!window.CustomEvent||y||w?(s=document.createEvent("Event")).initEvent("move",!0,!0):s=new CustomEvent("move",{bubbles:!0,cancelable:!0}),s.to=e,s.from=t,s.dragged=n,s.draggedRect=o,s.related=i||e,s.relatedRect=r||X(e),s.willInsertAfter=l,s.originalEvent=a,t.dispatchEvent(s),c=d?d.call(u,s,a):c}function Ht(t){t.draggable=!1}function Lt(){Tt=!1}function Kt(t){return setTimeout(t,0)}function Wt(t){return clearTimeout(t)}Ft.prototype={constructor:Ft,_isOutsideThisEl:function(t){this.el.contains(t)||t===this.el||(mt=null)},_getDirection:function(t,e){return"function"==typeof this.options.direction?this.options.direction.call(this,t,e,V):this.options.direction},_onTapStart:function(e){if(e.cancelable){var n=this,o=this.el,t=this.options,i=t.preventOnFilter,r=e.type,a=e.touches&&e.touches[0]||e.pointerType&&"touch"===e.pointerType&&e,l=(a||e).target,s=e.target.shadowRoot&&(e.path&&e.path[0]||e.composedPath&&e.composedPath()[0])||l,c=t.filter;if(!function(t){xt.length=0;var e=t.getElementsByTagName("input"),n=e.length;for(;n--;){var o=e[n];o.checked&&xt.push(o)}}(o),!V&&!(/mousedown|pointerdown/.test(r)&&0!==e.button||t.disabled)&&!s.isContentEditable&&(this.nativeDraggable||!u||!l||"SELECT"!==l.tagName.toUpperCase())&&!((l=P(l,t.draggable,o,!1))&&l.animated||tt===l)){if(ot=j(l),rt=j(l,t.draggable),"function"==typeof c){if(c.call(this,e,l,this))return q({sortable:n,rootEl:s,name:"filter",targetEl:l,toEl:o,fromEl:o}),G("filter",n,{evt:e}),void(i&&e.cancelable&&e.preventDefault())}else if(c=c&&c.split(",").some(function(t){if(t=P(s,t.trim(),o,!1))return q({sortable:n,rootEl:t,name:"filter",targetEl:l,fromEl:o,toEl:o}),G("filter",n,{evt:e}),!0}))return void(i&&e.cancelable&&e.preventDefault());t.handle&&!P(s,t.handle,o,!1)||this._prepareDragStart(e,a,l)}}},_prepareDragStart:function(t,e,n){var o,i=this,r=i.el,a=i.options,l=r.ownerDocument;n&&!V&&n.parentNode===r&&(o=X(n),Q=r,Z=(V=n).parentNode,J=V.nextSibling,tt=n,lt=a.group,ct={target:Ft.dragged=V,clientX:(e||t).clientX,clientY:(e||t).clientY},ft=ct.clientX-o.left,pt=ct.clientY-o.top,this._lastX=(e||t).clientX,this._lastY=(e||t).clientY,V.style["will-change"]="all",o=function(){G("delayEnded",i,{evt:t}),Ft.eventCanceled?i._onDrop():(i._disableDelayedDragEvents(),!s&&i.nativeDraggable&&(V.draggable=!0),i._triggerDragStart(t,e),q({sortable:i,name:"choose",originalEvent:t}),k(V,a.chosenClass,!0))},a.ignore.split(",").forEach(function(t){b(V,t.trim(),Ht)}),h(l,"dragover",Yt),h(l,"mousemove",Yt),h(l,"touchmove",Yt),h(l,"mouseup",i._onDrop),h(l,"touchend",i._onDrop),h(l,"touchcancel",i._onDrop),s&&this.nativeDraggable&&(this.options.touchStartThreshold=4,V.draggable=!0),G("delayStart",this,{evt:t}),!a.delay||a.delayOnTouchOnly&&!e||this.nativeDraggable&&(w||y)?o():Ft.eventCanceled?this._onDrop():(h(l,"mouseup",i._disableDelayedDrag),h(l,"touchend",i._disableDelayedDrag),h(l,"touchcancel",i._disableDelayedDrag),h(l,"mousemove",i._delayedDragTouchMoveHandler),h(l,"touchmove",i._delayedDragTouchMoveHandler),a.supportPointer&&h(l,"pointermove",i._delayedDragTouchMoveHandler),i._dragStartTimer=setTimeout(o,a.delay)))},_delayedDragTouchMoveHandler:function(t){t=t.touches?t.touches[0]:t;Math.max(Math.abs(t.clientX-this._lastX),Math.abs(t.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){V&&Ht(V),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var t=this.el.ownerDocument;f(t,"mouseup",this._disableDelayedDrag),f(t,"touchend",this._disableDelayedDrag),f(t,"touchcancel",this._disableDelayedDrag),f(t,"mousemove",this._delayedDragTouchMoveHandler),f(t,"touchmove",this._delayedDragTouchMoveHandler),f(t,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(t,e){e=e||"touch"==t.pointerType&&t,!this.nativeDraggable||e?this.options.supportPointer?h(document,"pointermove",this._onTouchMove):h(document,e?"touchmove":"mousemove",this._onTouchMove):(h(V,"dragend",this),h(Q,"dragstart",this._onDragStart));try{document.selection?Kt(function(){document.selection.empty()}):window.getSelection().removeAllRanges()}catch(t){}},_dragStarted:function(t,e){var n;wt=!1,Q&&V?(G("dragStarted",this,{evt:e}),this.nativeDraggable&&h(document,"dragover",Bt),n=this.options,t||k(V,n.dragClass,!1),k(V,n.ghostClass,!0),Ft.active=this,t&&this._appendGhost(),q({sortable:this,name:"start",originalEvent:e})):this._nulling()},_emulateDragOver:function(){if(ut){this._lastX=ut.clientX,this._lastY=ut.clientY,Rt();for(var t=document.elementFromPoint(ut.clientX,ut.clientY),e=t;t&&t.shadowRoot&&(t=t.shadowRoot.elementFromPoint(ut.clientX,ut.clientY))!==e;)e=t;if(V.parentNode[K]._isOutsideThisEl(t),e)do{if(e[K])if(e[K]._onDragOver({clientX:ut.clientX,clientY:ut.clientY,target:t,rootEl:e})&&!this.options.dragoverBubble)break}while(e=(t=e).parentNode);Xt()}},_onTouchMove:function(t){if(ct){var e=this.options,n=e.fallbackTolerance,o=e.fallbackOffset,i=t.touches?t.touches[0]:t,r=$&&v($,!0),a=$&&r&&r.a,l=$&&r&&r.d,e=Mt&&yt&&E(yt),a=(i.clientX-ct.clientX+o.x)/(a||1)+(e?e[0]-Ct[0]:0)/(a||1),l=(i.clientY-ct.clientY+o.y)/(l||1)+(e?e[1]-Ct[1]:0)/(l||1);if(!Ft.active&&!wt){if(n&&Math.max(Math.abs(i.clientX-this._lastX),Math.abs(i.clientY-this._lastY))D.right+10||S.clientY>x.bottom&&S.clientX>x.left:S.clientY>D.bottom+10||S.clientX>x.right&&S.clientY>x.top)||m.animated)){if(m&&(t=n,e=r,C=X(B((_=this).el,0,_.options,!0)),_=L(_.el,_.options,$),e?t.clientX<_.left-10||t.clientYgetJsSortableOptions()); - $js = "$('#{$this->id} .multiple-input-list').sorting($options);"; + $js = "new Sortable(document.getElementById('{$this->id}').querySelector('.multiple-input-list tbody'), {$options});"; $view->registerJs($js); } /** * Returns an array of JQuery sortable plugin options. * You can override this method extend plugin behaviour. - * + * * @return array */ protected function getJsSortableOptions() { return [ - 'containerSelector' => 'table', - 'itemPath' => '> tbody', - 'itemSelector' => 'tr', - 'placeholder' => '', - 'handle' => '.drag-handle', - 'onDrop' => new \yii\web\JsExpression(" - function(item, container, _super, event) { - _super(item, container, _super, event); - - var wrapper = item.closest('.multiple-input').first(); - event = $.Event('afterDropRow'); - wrapper.trigger(event, [item]); + 'handle' => '.drag-handle', + 'draggable' => '.multiple-input-list__item', + 'onEnd' => new \yii\web\JsExpression(" + function(event) { + var item = $(event.item), + wrapper = item.closest('.multiple-input').first(), + trigeredEvent = $.Event('afterDropRow'); + wrapper.trigger(trigeredEvent, [item]); } ") ]; From 67137629ce2b14ccca7c6c3ed574296ce3c402db Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 30 Mar 2024 21:47:41 +0400 Subject: [PATCH 241/247] fix addind active form fields doesn't work properly in case of 10 rows and more --- CHANGELOG.md | 1 + src/assets/src/js/jquery.multipleInput.js | 4 ++-- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b58aa62..19d2ab2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Yii2 multiple input change log 2.29.0 (in development) ======================= +- fix addind active form fields doesn't work properly in case of 10 rows and more (unclead) 2.28.0 ======================= diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index cbaaa63..3c6bceb 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -464,8 +464,8 @@ } else { // fallback in case of using flatten widget - just remove all digital indexes // and check whether attribute exists or not. - bareId = replaceAll(/-\d-/, '-', bareId); - bareId = replaceAll(/-\d/, '', bareId); + bareId = replaceAll(/-\d+-/, '-', bareId); + bareId = replaceAll(/-\d+/, '', bareId); if (data.settings.attributes.hasOwnProperty(bareId)) { attributeOptions = data.settings.attributes[bareId]; } diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index fa71f5a..cae7b24 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){"use strict";t.fn.multipleInput=function(e){return o[e]?o[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):o.init.apply(this,arguments)};var e="afterInit",i="beforeAddRow",n="afterAddRow",l="beforeDeleteRow",r="afterDeleteRow",a={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},u=!1,o={init:function(i){if("object"==typeof i){var n=t.extend(!0,{},a,i||{}),l=t("#"+n.id),r=l.closest("form"),o=n.inputId;for(f in n.jsInit)window.eval(n.jsInit[f]);l.data("multipleInput",{settings:n,currentIndex:0}),l.on("click.multipleInput",".js-input-remove",(function(e){e.stopPropagation(),c(t(this))})),l.on("click.multipleInput",".js-input-plus",(function(e){e.stopPropagation(),p(t(this))})),l.on("click.multipleInput",".js-input-clone",(function(e){e.stopPropagation(),s(t(this))}));var f=0,m=t.Event(e),v=setInterval((function(){if("object"==typeof r.data("yiiActiveForm")){var e=r.yiiActiveForm("find",o),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,(function(t,e){-1===["id","input","container"].indexOf(t)&&(i[t]=e)})),n.showGeneralError||r.yiiActiveForm("remove",o)),t.each(n.attributes,(function(e,l){l=t.extend({},i,l),n.attributes[e]=l})),l.data("multipleInput").settings=n,l.find(".multiple-input-list").find("input, select, textarea").each((function(){d(t(this))})),l.data("multipleInput").currentIndex=g(l),u=!0,clearInterval(v),l.trigger(m)}else f++;(0===r.length||f>10)&&(clearInterval(v),u=!1,void 0!==l.data("multipleInput")&&(l.data("multipleInput").currentIndex=g(l)),l.trigger(m))}),100)}else console.error("Options must be an object")},add:function(e){p(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),c(i)},clear:function(){t(this).find(".js-input-remove").each((function(){c(t(this))}))},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},s=function(e){let i=t(e).closest(".multiple-input").first().data("multipleInput").settings,n={};e.closest(".multiple-input-list__item").find("input, select, textarea").each((function(e,l){let r=t(l),a=m(r);if(a){let t=a.replace(i.inputId,"").replace(/-\d+-/,"");r.is(":checkbox")?(n.hasOwnProperty(t)||(n[t]=[]),r.is(":checked")&&n[t].push(r.val())):n[t]=r.val()}})),p(e,n)},p=function(e,l){l=l||{};let r=t(e).closest(".multiple-input").first(),a=r.data("multipleInput"),o=a.settings,s=r.children(".multiple-input-list").first();if(null!==o.max&&v(r)>=o.max)return;let p=a.currentIndex+1,c=I("{"+o.indexPlaceholder+"}",p,o.template),f=t(c);var h=t.Event(i);if(r.trigger(h,[f,p]),!1===h.result)return;f.find("input, select, textarea").each((function(e,i){let n=t(i),r=m(n);if(r){let t=r.replace(o.inputId,"").replace(/-\d+-?/,"");if(l.hasOwnProperty(t)){let e=n.get(0).tagName,i=l[t];switch(e){case"INPUT":n.is(":checkbox")?-1!==i.indexOf(n.val())&&n.prop("checked",!0):n.val(i);break;case"TEXTAREA":n.val(i);break;case"SELECT":if(i&&-1!==i.indexOf("option"))n.append(i);else n.find('option[value="'+i+'"]').length&&n.val(i)}}}})),o.prepend?f.hide().prependTo(s).fadeIn(300):f.hide().appendTo(s).fadeIn(300),u&&f.find("input, select, textarea").each((function(e,i){let n=t(i);d(n)}));let g=null;for(var x in o.jsTemplates)g=o.jsTemplates[x],g=I("{"+o.indexPlaceholder+"}",p,g),g=I("%7B"+o.indexPlaceholder+"%7D",p,g),window.eval(g);r.data("multipleInput").currentIndex=p;var y=t.Event(n);r.trigger(y,[f,p])},c=function(e){var i=e.closest(".multiple-input").first(),n=e.closest(".multiple-input-list__item"),a=i.data("multipleInput"),o=a.settings,s=v(i);if(s>o.min){var p=t.Event(l);if(i.trigger(p,[n,a.currentIndex]),!1===p.result)return;u&&n.find("input, select, textarea").each((function(e,i){f(t(i))})),n.fadeOut(300,(function(){t(this).remove(),p=t.Event(r),i.trigger(p,[n,s])}))}},d=function(e){var i=m(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!==l.length&&void 0===r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),u={},o=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(o)?u=a.settings.attributes[o]:(o=I(/-\d-/,"-",o),o=I(/-\d/,"",o),a.settings.attributes.hasOwnProperty(o)&&(u=a.settings.attributes[o])),r.yiiActiveForm("add",t.extend({},u,{id:i,input:"#"+i,container:".field-"+i}))}}},f=function(e){var i=m(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},m=function(t){var e=t.attr("id");return void 0===e&&(e=t.data("id")),void 0===e?null:e},v=function(t){return h(t).length},h=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter((function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}))},g=function(e){let i=0;return h(e).each((function(e,n){let l=t(n).data("index");l>i&&(i=l)})),i},I=function(t,e,i){return i instanceof String||"string"==typeof i?i.split(t).join(e):(console.warn("Call replaceAll for non-string value: "+i),i)}}(window.jQuery); \ No newline at end of file +!function(t){"use strict";t.fn.multipleInput=function(e){return o[e]?o[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):o.init.apply(this,arguments)};var e="afterInit",i="beforeAddRow",n="afterAddRow",l="beforeDeleteRow",r="afterDeleteRow",a={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},u=!1,o={init:function(i){if("object"==typeof i){var n=t.extend(!0,{},a,i||{}),l=t("#"+n.id),r=l.closest("form"),o=n.inputId;for(f in n.jsInit)window.eval(n.jsInit[f]);l.data("multipleInput",{settings:n,currentIndex:0}),l.on("click.multipleInput",".js-input-remove",(function(e){e.stopPropagation(),c(t(this))})),l.on("click.multipleInput",".js-input-plus",(function(e){e.stopPropagation(),p(t(this))})),l.on("click.multipleInput",".js-input-clone",(function(e){e.stopPropagation(),s(t(this))}));var f=0,m=t.Event(e),v=setInterval((function(){if("object"==typeof r.data("yiiActiveForm")){var e=r.yiiActiveForm("find",o),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,(function(t,e){-1===["id","input","container"].indexOf(t)&&(i[t]=e)})),n.showGeneralError||r.yiiActiveForm("remove",o)),t.each(n.attributes,(function(e,l){l=t.extend({},i,l),n.attributes[e]=l})),l.data("multipleInput").settings=n,l.find(".multiple-input-list").find("input, select, textarea").each((function(){d(t(this))})),l.data("multipleInput").currentIndex=g(l),u=!0,clearInterval(v),l.trigger(m)}else f++;(0===r.length||f>10)&&(clearInterval(v),u=!1,void 0!==l.data("multipleInput")&&(l.data("multipleInput").currentIndex=g(l)),l.trigger(m))}),100)}else console.error("Options must be an object")},add:function(e){p(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),c(i)},clear:function(){t(this).find(".js-input-remove").each((function(){c(t(this))}))},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},s=function(e){let i=t(e).closest(".multiple-input").first().data("multipleInput").settings,n={};e.closest(".multiple-input-list__item").find("input, select, textarea").each((function(e,l){let r=t(l),a=m(r);if(a){let t=a.replace(i.inputId,"").replace(/-\d+-/,"");r.is(":checkbox")?(n.hasOwnProperty(t)||(n[t]=[]),r.is(":checked")&&n[t].push(r.val())):n[t]=r.val()}})),p(e,n)},p=function(e,l){l=l||{};let r=t(e).closest(".multiple-input").first(),a=r.data("multipleInput"),o=a.settings,s=r.children(".multiple-input-list").first();if(null!==o.max&&v(r)>=o.max)return;let p=a.currentIndex+1,c=I("{"+o.indexPlaceholder+"}",p,o.template),f=t(c);var h=t.Event(i);if(r.trigger(h,[f,p]),!1===h.result)return;f.find("input, select, textarea").each((function(e,i){let n=t(i),r=m(n);if(r){let t=r.replace(o.inputId,"").replace(/-\d+-?/,"");if(l.hasOwnProperty(t)){let e=n.get(0).tagName,i=l[t];switch(e){case"INPUT":n.is(":checkbox")?-1!==i.indexOf(n.val())&&n.prop("checked",!0):n.val(i);break;case"TEXTAREA":n.val(i);break;case"SELECT":if(i&&-1!==i.indexOf("option"))n.append(i);else n.find('option[value="'+i+'"]').length&&n.val(i)}}}})),o.prepend?f.hide().prependTo(s).fadeIn(300):f.hide().appendTo(s).fadeIn(300),u&&f.find("input, select, textarea").each((function(e,i){let n=t(i);d(n)}));let g=null;for(var x in o.jsTemplates)g=o.jsTemplates[x],g=I("{"+o.indexPlaceholder+"}",p,g),g=I("%7B"+o.indexPlaceholder+"%7D",p,g),window.eval(g);r.data("multipleInput").currentIndex=p;var y=t.Event(n);r.trigger(y,[f,p])},c=function(e){var i=e.closest(".multiple-input").first(),n=e.closest(".multiple-input-list__item"),a=i.data("multipleInput"),o=a.settings,s=v(i);if(s>o.min){var p=t.Event(l);if(i.trigger(p,[n,a.currentIndex]),!1===p.result)return;u&&n.find("input, select, textarea").each((function(e,i){f(t(i))})),n.fadeOut(300,(function(){t(this).remove(),p=t.Event(r),i.trigger(p,[n,s])}))}},d=function(e){var i=m(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!==l.length&&void 0===r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),u={},o=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(o)?u=a.settings.attributes[o]:(o=I(/-\d+-/,"-",o),o=I(/-\d+/,"",o),a.settings.attributes.hasOwnProperty(o)&&(u=a.settings.attributes[o])),r.yiiActiveForm("add",t.extend({},u,{id:i,input:"#"+i,container:".field-"+i}))}}},f=function(e){var i=m(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},m=function(t){var e=t.attr("id");return void 0===e&&(e=t.data("id")),void 0===e?null:e},v=function(t){return h(t).length},h=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter((function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}))},g=function(e){let i=0;return h(e).each((function(e,n){let l=t(n).data("index");l>i&&(i=l)})),i},I=function(t,e,i){return i instanceof String||"string"==typeof i?i.split(t).join(e):(console.warn("Call replaceAll for non-string value: "+i),i)}}(window.jQuery); \ No newline at end of file From e00d527aac2b783fe67c266032c346f993c61638 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 30 Mar 2024 21:53:58 +0400 Subject: [PATCH 242/247] Revert changes in normalize method because it affected ajax validation Ajax validation uses Html::getInputId to generate the input ID, which in turn converts the string to lower case. This is why if we remove the same behavior from BaseColumn::normalize, the ajax validation will not work because the input (attribute) IDs from the ajax response will not match the form input IDs generated by our widget. --- CHANGELOG.md | 1 + src/components/BaseColumn.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 19d2ab2..208e583 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ Yii2 multiple input change log 2.29.0 (in development) ======================= - fix addind active form fields doesn't work properly in case of 10 rows and more (unclead) +- revert changes in normalize method because it affected ajax validation 2.28.0 ======================= diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index bf65c97..d4a9928 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -280,7 +280,7 @@ abstract public function getElementName($index, $withPrefix = true); * @return mixed */ private function normalize($name) { - return str_replace(['[]', '][', '[', ']', ' ', '.'], ['', '-', '-', '', '-', '-'], $name); + return str_replace(['[]', '][', '[', ']', ' ', '.'], ['', '-', '-', '', '-', '-'], strtolower($name)); } /** From e8074f943dfa36ff5a09af4e85cc2a34d4f24ff3 Mon Sep 17 00:00:00 2001 From: Evgeny Tupikov Date: Sat, 30 Mar 2024 22:29:00 +0400 Subject: [PATCH 243/247] Call addActiveFormAttribute only after applying JS templates. Call addActiveFormAttribute only after applying JS templates to properly initialize all JS widgets, including nested MultipleInputs. Otherwise, trying to get the settings of an embedded widget will result in a JS error because the widget itself has not yet been initialized. --- CHANGELOG.md | 1 + src/assets/src/js/jquery.multipleInput.js | 18 +++++++++--------- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 208e583..76152b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Yii2 multiple input change log ======================= - fix addind active form fields doesn't work properly in case of 10 rows and more (unclead) - revert changes in normalize method because it affected ajax validation +- fix addind active form fields in nested columns (unclead) 2.28.0 ======================= diff --git a/src/assets/src/js/jquery.multipleInput.js b/src/assets/src/js/jquery.multipleInput.js index 3c6bceb..596d3ec 100644 --- a/src/assets/src/js/jquery.multipleInput.js +++ b/src/assets/src/js/jquery.multipleInput.js @@ -371,15 +371,6 @@ $newRow.hide().appendTo(inputList).fadeIn(300); } - // in order to initialize an active form attribute we need to find an input wrapper and we can do it - // only after adding a new rows to dom tree - if (isActiveFormEnabled) { - $newRow.find('input, select, textarea').each(function (index, element) { - let $element = $(element); - addActiveFormAttribute($element); - }); - } - let jsTemplate = null; for (var i in settings.jsTemplates) { jsTemplate = settings.jsTemplates[i]; @@ -389,6 +380,15 @@ window.eval(jsTemplate); } + // in order to initialize an active form attribute we need to find an input wrapper and we can do it + // only after adding a new rows to dom tree + if (isActiveFormEnabled) { + $newRow.find('input, select, textarea').each(function (index, element) { + let $element = $(element); + addActiveFormAttribute($element); + }); + } + $wrapper.data('multipleInput').currentIndex = newRowIndex; var afterAddEvent = $.Event(events.afterAddRow); diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index cae7b24..636f288 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){"use strict";t.fn.multipleInput=function(e){return o[e]?o[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):o.init.apply(this,arguments)};var e="afterInit",i="beforeAddRow",n="afterAddRow",l="beforeDeleteRow",r="afterDeleteRow",a={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},u=!1,o={init:function(i){if("object"==typeof i){var n=t.extend(!0,{},a,i||{}),l=t("#"+n.id),r=l.closest("form"),o=n.inputId;for(f in n.jsInit)window.eval(n.jsInit[f]);l.data("multipleInput",{settings:n,currentIndex:0}),l.on("click.multipleInput",".js-input-remove",(function(e){e.stopPropagation(),c(t(this))})),l.on("click.multipleInput",".js-input-plus",(function(e){e.stopPropagation(),p(t(this))})),l.on("click.multipleInput",".js-input-clone",(function(e){e.stopPropagation(),s(t(this))}));var f=0,m=t.Event(e),v=setInterval((function(){if("object"==typeof r.data("yiiActiveForm")){var e=r.yiiActiveForm("find",o),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,(function(t,e){-1===["id","input","container"].indexOf(t)&&(i[t]=e)})),n.showGeneralError||r.yiiActiveForm("remove",o)),t.each(n.attributes,(function(e,l){l=t.extend({},i,l),n.attributes[e]=l})),l.data("multipleInput").settings=n,l.find(".multiple-input-list").find("input, select, textarea").each((function(){d(t(this))})),l.data("multipleInput").currentIndex=g(l),u=!0,clearInterval(v),l.trigger(m)}else f++;(0===r.length||f>10)&&(clearInterval(v),u=!1,void 0!==l.data("multipleInput")&&(l.data("multipleInput").currentIndex=g(l)),l.trigger(m))}),100)}else console.error("Options must be an object")},add:function(e){p(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),c(i)},clear:function(){t(this).find(".js-input-remove").each((function(){c(t(this))}))},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},s=function(e){let i=t(e).closest(".multiple-input").first().data("multipleInput").settings,n={};e.closest(".multiple-input-list__item").find("input, select, textarea").each((function(e,l){let r=t(l),a=m(r);if(a){let t=a.replace(i.inputId,"").replace(/-\d+-/,"");r.is(":checkbox")?(n.hasOwnProperty(t)||(n[t]=[]),r.is(":checked")&&n[t].push(r.val())):n[t]=r.val()}})),p(e,n)},p=function(e,l){l=l||{};let r=t(e).closest(".multiple-input").first(),a=r.data("multipleInput"),o=a.settings,s=r.children(".multiple-input-list").first();if(null!==o.max&&v(r)>=o.max)return;let p=a.currentIndex+1,c=I("{"+o.indexPlaceholder+"}",p,o.template),f=t(c);var h=t.Event(i);if(r.trigger(h,[f,p]),!1===h.result)return;f.find("input, select, textarea").each((function(e,i){let n=t(i),r=m(n);if(r){let t=r.replace(o.inputId,"").replace(/-\d+-?/,"");if(l.hasOwnProperty(t)){let e=n.get(0).tagName,i=l[t];switch(e){case"INPUT":n.is(":checkbox")?-1!==i.indexOf(n.val())&&n.prop("checked",!0):n.val(i);break;case"TEXTAREA":n.val(i);break;case"SELECT":if(i&&-1!==i.indexOf("option"))n.append(i);else n.find('option[value="'+i+'"]').length&&n.val(i)}}}})),o.prepend?f.hide().prependTo(s).fadeIn(300):f.hide().appendTo(s).fadeIn(300),u&&f.find("input, select, textarea").each((function(e,i){let n=t(i);d(n)}));let g=null;for(var x in o.jsTemplates)g=o.jsTemplates[x],g=I("{"+o.indexPlaceholder+"}",p,g),g=I("%7B"+o.indexPlaceholder+"%7D",p,g),window.eval(g);r.data("multipleInput").currentIndex=p;var y=t.Event(n);r.trigger(y,[f,p])},c=function(e){var i=e.closest(".multiple-input").first(),n=e.closest(".multiple-input-list__item"),a=i.data("multipleInput"),o=a.settings,s=v(i);if(s>o.min){var p=t.Event(l);if(i.trigger(p,[n,a.currentIndex]),!1===p.result)return;u&&n.find("input, select, textarea").each((function(e,i){f(t(i))})),n.fadeOut(300,(function(){t(this).remove(),p=t.Event(r),i.trigger(p,[n,s])}))}},d=function(e){var i=m(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!==l.length&&void 0===r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),u={},o=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(o)?u=a.settings.attributes[o]:(o=I(/-\d+-/,"-",o),o=I(/-\d+/,"",o),a.settings.attributes.hasOwnProperty(o)&&(u=a.settings.attributes[o])),r.yiiActiveForm("add",t.extend({},u,{id:i,input:"#"+i,container:".field-"+i}))}}},f=function(e){var i=m(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},m=function(t){var e=t.attr("id");return void 0===e&&(e=t.data("id")),void 0===e?null:e},v=function(t){return h(t).length},h=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter((function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}))},g=function(e){let i=0;return h(e).each((function(e,n){let l=t(n).data("index");l>i&&(i=l)})),i},I=function(t,e,i){return i instanceof String||"string"==typeof i?i.split(t).join(e):(console.warn("Call replaceAll for non-string value: "+i),i)}}(window.jQuery); \ No newline at end of file +!function(t){"use strict";t.fn.multipleInput=function(e){return o[e]?o[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):o.init.apply(this,arguments)};var e="afterInit",i="beforeAddRow",n="afterAddRow",l="beforeDeleteRow",r="afterDeleteRow",a={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index",showGeneralError:!1,prepend:!1},u=!1,o={init:function(i){if("object"==typeof i){var n=t.extend(!0,{},a,i||{}),l=t("#"+n.id),r=l.closest("form"),o=n.inputId;for(f in n.jsInit)window.eval(n.jsInit[f]);l.data("multipleInput",{settings:n,currentIndex:0}),l.on("click.multipleInput",".js-input-remove",(function(e){e.stopPropagation(),c(t(this))})),l.on("click.multipleInput",".js-input-plus",(function(e){e.stopPropagation(),p(t(this))})),l.on("click.multipleInput",".js-input-clone",(function(e){e.stopPropagation(),s(t(this))}));var f=0,m=t.Event(e),v=setInterval((function(){if("object"==typeof r.data("yiiActiveForm")){var e=r.yiiActiveForm("find",o),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,(function(t,e){-1===["id","input","container"].indexOf(t)&&(i[t]=e)})),n.showGeneralError||r.yiiActiveForm("remove",o)),t.each(n.attributes,(function(e,l){l=t.extend({},i,l),n.attributes[e]=l})),l.data("multipleInput").settings=n,l.find(".multiple-input-list").find("input, select, textarea").each((function(){d(t(this))})),l.data("multipleInput").currentIndex=g(l),u=!0,clearInterval(v),l.trigger(m)}else f++;(0===r.length||f>10)&&(clearInterval(v),u=!1,void 0!==l.data("multipleInput")&&(l.data("multipleInput").currentIndex=g(l)),l.trigger(m))}),100)}else console.error("Options must be an object")},add:function(e){p(t(this),e)},remove:function(e){var i=null;i=void 0!==e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),c(i)},clear:function(){t(this).find(".js-input-remove").each((function(){c(t(this))}))},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),l=n.settings;if(null===i){if(!l.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return l[e]}l.hasOwnProperty(e)&&(l[e]=i,n.settings=l,t(this).data("multipleInput",n))}},s=function(e){let i=t(e).closest(".multiple-input").first().data("multipleInput").settings,n={};e.closest(".multiple-input-list__item").find("input, select, textarea").each((function(e,l){let r=t(l),a=m(r);if(a){let t=a.replace(i.inputId,"").replace(/-\d+-/,"");r.is(":checkbox")?(n.hasOwnProperty(t)||(n[t]=[]),r.is(":checked")&&n[t].push(r.val())):n[t]=r.val()}})),p(e,n)},p=function(e,l){l=l||{};let r=t(e).closest(".multiple-input").first(),a=r.data("multipleInput"),o=a.settings,s=r.children(".multiple-input-list").first();if(null!==o.max&&v(r)>=o.max)return;let p=a.currentIndex+1,c=I("{"+o.indexPlaceholder+"}",p,o.template),f=t(c);var h=t.Event(i);if(r.trigger(h,[f,p]),!1===h.result)return;f.find("input, select, textarea").each((function(e,i){let n=t(i),r=m(n);if(r){let t=r.replace(o.inputId,"").replace(/-\d+-?/,"");if(l.hasOwnProperty(t)){let e=n.get(0).tagName,i=l[t];switch(e){case"INPUT":n.is(":checkbox")?-1!==i.indexOf(n.val())&&n.prop("checked",!0):n.val(i);break;case"TEXTAREA":n.val(i);break;case"SELECT":if(i&&-1!==i.indexOf("option"))n.append(i);else n.find('option[value="'+i+'"]').length&&n.val(i)}}}})),o.prepend?f.hide().prependTo(s).fadeIn(300):f.hide().appendTo(s).fadeIn(300);let g=null;for(var x in o.jsTemplates)g=o.jsTemplates[x],g=I("{"+o.indexPlaceholder+"}",p,g),g=I("%7B"+o.indexPlaceholder+"%7D",p,g),window.eval(g);u&&f.find("input, select, textarea").each((function(e,i){let n=t(i);d(n)})),r.data("multipleInput").currentIndex=p;var y=t.Event(n);r.trigger(y,[f,p])},c=function(e){var i=e.closest(".multiple-input").first(),n=e.closest(".multiple-input-list__item"),a=i.data("multipleInput"),o=a.settings,s=v(i);if(s>o.min){var p=t.Event(l);if(i.trigger(p,[n,a.currentIndex]),!1===p.result)return;u&&n.find("input, select, textarea").each((function(e,i){f(t(i))})),n.fadeOut(300,(function(){t(this).remove(),p=t.Event(r),i.trigger(p,[n,s])}))}},d=function(e){var i=m(e);if(null!==i){var n=t("#"+i),l=n.closest(".multiple-input").first(),r=n.closest("form");if(0!==l.length&&void 0===r.yiiActiveForm("find",i)){var a=l.data("multipleInput"),u={},o=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(o)?u=a.settings.attributes[o]:(o=I(/-\d+-/,"-",o),o=I(/-\d+/,"",o),a.settings.attributes.hasOwnProperty(o)&&(u=a.settings.attributes[o])),r.yiiActiveForm("add",t.extend({},u,{id:i,input:"#"+i,container:".field-"+i}))}}},f=function(e){var i=m(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},m=function(t){var e=t.attr("id");return void 0===e&&(e=t.data("id")),void 0===e?null:e},v=function(t){return h(t).length},h=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter((function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}))},g=function(e){let i=0;return h(e).each((function(e,n){let l=t(n).data("index");l>i&&(i=l)})),i},I=function(t,e,i){return i instanceof String||"string"==typeof i?i.split(t).join(e):(console.warn("Call replaceAll for non-string value: "+i),i)}}(window.jQuery); \ No newline at end of file From c9a236db60726f70eb41bf6c31ce97fdb6d44a04 Mon Sep 17 00:00:00 2001 From: Evgeny Tupikov Date: Sun, 31 Mar 2024 14:35:13 +0400 Subject: [PATCH 244/247] release 2.29.0 --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76152b8..eb31e0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ Yii2 multiple input change log ============================== -2.29.0 (in development) +2.30.0 (in development) +======================= + +2.29.0 ======================= - fix addind active form fields doesn't work properly in case of 10 rows and more (unclead) - revert changes in normalize method because it affected ajax validation From 9e4bca001d0bac5a0010ae6fa41a4e94325f1e5d Mon Sep 17 00:00:00 2001 From: Evgeny Tupikov Date: Sat, 20 Apr 2024 15:08:42 +0400 Subject: [PATCH 245/247] fix rendering action buttons in case the dataset contains non-numeric indices --- src/renderers/BaseRenderer.php | 32 ++++++++++++++++ src/renderers/DivRenderer.php | 51 ++++++++++--------------- src/renderers/ListRenderer.php | 43 +++++++-------------- src/renderers/TableRenderer.php | 67 +++++++++++++++------------------ 4 files changed, 95 insertions(+), 98 deletions(-) diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index b78d0c0..5a701a1 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -514,6 +514,11 @@ protected function isAddButtonPositionRowBegin() return in_array(self::POS_ROW_BEGIN, $this->addButtonPosition); } + protected function isFixedNumberOfRows() + { + return $this->max === $this->min; + } + private function prepareIndexPlaceholder() { $this->indexPlaceholder = 'multiple_index_' . $this->id; @@ -579,4 +584,31 @@ public function isBootstrapTheme() return $this->theme === self::THEME_BS; } + protected function renderRows() + { + $rows = []; + + $rowIndex = 0; + if ($this->data) { + foreach ($this->data as $index => $item) { + if ($rowIndex <= $this->max) { + $rows[] = $this->renderRowContent($index, $item, $rowIndex); + } else { + break; + } + $rowIndex++; + } + for (; $rowIndex < $this->min; $rowIndex++) { + $rows[] = $this->renderRowContent($rowIndex, null, $rowIndex); + } + } elseif ($this->min > 0) { + for (; $rowIndex < $this->min; $rowIndex++) { + $rows[] = $this->renderRowContent($rowIndex, null, $rowIndex); + } + } + + return $rows; + } + + abstract protected function renderRowContent($index = null, $item = null, $rowIndex = null); } diff --git a/src/renderers/DivRenderer.php b/src/renderers/DivRenderer.php index c717fa0..80e3056 100644 --- a/src/renderers/DivRenderer.php +++ b/src/renderers/DivRenderer.php @@ -98,27 +98,7 @@ public function renderFooter() */ protected function renderBody() { - $rows = []; - - if ($this->data) { - $j = 0; - foreach ($this->data as $index => $item) { - if ($j++ <= $this->max) { - $rows[] = $this->renderRowContent($index, $item); - } else { - break; - } - } - for ($i = $j; $i < $this->min; $i++) { - $rows[] = $this->renderRowContent($i); - } - } elseif ($this->min > 0) { - for ($i = 0; $i < $this->min; $i++) { - $rows[] = $this->renderRowContent($i); - } - } - - return implode("\n", $rows); + return implode("\n", $this->renderRows()); } /** @@ -128,14 +108,16 @@ protected function renderBody() * @param ActiveRecordInterface|array $item * @return mixed */ - private function renderRowContent($index = null, $item = null) + protected function renderRowContent($index = null, $item = null, $rowIndex = null) { $elements = []; + $columnIndex = 0; foreach ($this->columns as $column) { /* @var $column BaseColumn */ $column->setModel($item); - $elements[] = $this->renderCellContent($column, $index, $columnIndex++); + $elements[] = $this->renderCellContent($column, $index, $columnIndex, $rowIndex); + $columnIndex++; } $content = Html::tag('div', implode("\n", $elements), $this->prepareRowOptions($index, $item)); @@ -174,10 +156,11 @@ protected function prepareRowOptions($index, $item) * @param BaseColumn $column * @param int|null $index * @param int|null $columnIndex + * @param int|null $rowIndex * @return string * @throws \Exception */ - public function renderCellContent($column, $index, $columnIndex = null) + public function renderCellContent($column, $index = null, $columnIndex = null, $rowIndex = null) { $id = $column->getElementId($index); $name = $column->getElementName($index); @@ -263,9 +246,10 @@ public function renderCellContent($column, $index, $columnIndex = null) // first line if ($columnIndex == 0) { - if ($this->max !== $this->min) { - $content .= $this->renderActionColumn($index); + if (!$this->isFixedNumberOfRows()) { + $content .= $this->renderActionColumn($index, $column->getModel(), $rowIndex); } + if ($this->cloneButton) { $content .= $this->renderCloneColumn(); } @@ -287,14 +271,15 @@ public function renderCellContent($column, $index, $columnIndex = null) * @param null|ActiveRecordInterface|array $item * @return string */ - private function renderActionColumn($index = null, $item = null) + private function renderActionColumn($index = null, $item = null, $rowIndex = null) { - $content = $this->getActionButton($index) . $this->getExtraButtons($index, $item); + $content = $this->getActionButton($index, $rowIndex) . $this->getExtraButtons($index, $item); $options = ['class' => 'list-cell__button']; $layoutConfig = array_merge([ 'buttonActionClass' => $this->isBootstrapTheme() ? 'col-sm-offset-0 col-sm-2' : '', ], $this->layoutConfig); + Html::addCssClass($options, $layoutConfig['buttonActionClass']); return Html::tag('div', $content, $options); @@ -317,18 +302,20 @@ private function renderCloneColumn() return Html::tag('div', $this->renderCloneButton(), $options); } - private function getActionButton($index) + private function getActionButton($index, $rowIndex) { if ($index === null || $this->min === 0) { return $this->renderRemoveButton(); } - $index++; - if ($index < $this->min) { + // rowIndex is zero-based, so we have to increment it to properly cpmpare it with min number of rows + $rowIndex++; + + if ($rowIndex < $this->min) { return ''; } - if ($index === $this->min) { + if ($rowIndex === $this->min) { return $this->isAddButtonPositionRow() ? $this->renderAddButton() : ''; } diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index a34ad63..7661a41 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -102,27 +102,7 @@ public function renderFooter() */ protected function renderBody() { - $rows = []; - - if ($this->data) { - $j = 0; - foreach ($this->data as $index => $item) { - if ($j++ <= $this->max) { - $rows[] = $this->renderRowContent($index, $item); - } else { - break; - } - } - for ($i = $j; $i < $this->min; $i++) { - $rows[] = $this->renderRowContent($i); - } - } elseif ($this->min > 0) { - for ($i = 0; $i < $this->min; $i++) { - $rows[] = $this->renderRowContent($i); - } - } - - return Html::tag('tbody', implode("\n", $rows)); + return Html::tag('tbody', implode("\n", $this->renderRows())); } /** @@ -133,7 +113,7 @@ protected function renderBody() * @return mixed * @throws InvalidConfigException */ - private function renderRowContent($index = null, $item = null) + protected function renderRowContent($index = null, $item = null, $rowIndex = null) { $elements = []; @@ -146,8 +126,8 @@ private function renderRowContent($index = null, $item = null) $content = []; $content[] = Html::tag('td', implode("\n", $elements)); - if ($this->max !== $this->min) { - $content[] = $this->renderActionColumn($index); + if (!$this->isFixedNumberOfRows()) { + $content[] = $this->renderActionColumn($index, $item, $rowIndex); } if ($this->cloneButton) { @@ -290,12 +270,13 @@ public function renderCellContent($column, $index, $columnIndex = null) * * @param null|int $index * @param null|ActiveRecordInterface|array $item + * @param null|int $rowIndex * @return string * @throws \Exception */ - private function renderActionColumn($index = null, $item = null) + private function renderActionColumn($index = null, $item = null, $rowIndex = null) { - $content = $this->getActionButton($index) . $this->getExtraButtons($index, $item); + $content = $this->getActionButton($index, $rowIndex) . $this->getExtraButtons($index, $item); return Html::tag('td', $content, [ 'class' => 'list-cell__button', @@ -315,18 +296,20 @@ private function renderCloneColumn() ]); } - private function getActionButton($index) + private function getActionButton($index, $rowIndex) { if ($index === null || $this->min === 0) { return $this->renderRemoveButton(); } - $index++; - if ($index < $this->min) { + // rowIndex is zero-based, so we have to increment it to properly cpmpare it with min number of rows + $rowIndex++; + + if ($rowIndex < $this->min) { return ''; } - if ($index === $this->min) { + if ($rowIndex === $this->min) { return $this->isAddButtonPositionRow() ? $this->renderAddButton() : ''; } diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 3394b0b..55d5f3b 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -167,27 +167,7 @@ private function renderButtonHeaderCell($button = '') */ protected function renderBody() { - $rows = []; - - if ($this->data) { - $j = 0; - foreach ($this->data as $index => $item) { - if ($j++ <= $this->max) { - $rows[] = $this->renderRowContent($index, $item); - } else { - break; - } - } - for ($i = $j; $i < $this->min; $i++) { - $rows[] = $this->renderRowContent($i); - } - } elseif ($this->min > 0) { - for ($i = 0; $i < $this->min; $i++) { - $rows[] = $this->renderRowContent($i); - } - } - - return Html::tag('tbody', implode("\n", $rows)); + return Html::tag('tbody', implode("\n", $this->renderRows())); } /** @@ -198,13 +178,13 @@ protected function renderBody() * @return mixed * @throws InvalidConfigException */ - private function renderRowContent($index = null, $item = null) + protected function renderRowContent($index = null, $item = null, $rowIndex = null) { $cells = []; $hiddenInputs = []; - $isLastRow = $this->max === $this->min; - if (!$isLastRow && $this->isAddButtonPositionRowBegin()) { - $cells[] = $this->renderActionColumn($index, $item, true); + + if (!$this->isFixedNumberOfRows() && $this->isAddButtonPositionRowBegin()) { + $cells[] = $this->renderActionColumn($index, $item, $rowIndex, true); } $columnIndex = 0; @@ -217,12 +197,13 @@ private function renderRowContent($index = null, $item = null) $cells[] = $this->renderCellContent($column, $index, $columnIndex++); } } + if ($this->cloneButton) { $cells[] = $this->renderCloneColumn(); } - if (!$isLastRow) { - $cells[] = $this->renderActionColumn($index, $item); + if (!$this->isFixedNumberOfRows()) { + $cells[] = $this->renderActionColumn($index, $item, $rowIndex); } if ($hiddenInputs) { @@ -268,6 +249,8 @@ protected function prepareRowOptions($index, $item) * @param int|null $index * @param int|null $columnIndex * @return string + * + * @todo rethink visibility level (make it private) */ public function renderCellContent($column, $index, $columnIndex = null) { @@ -284,7 +267,6 @@ public function renderCellContent($column, $index, $columnIndex = null) $options = ArrayHelper::merge($options, ['class' => $this->iconMap['drag-handle']]); } - $input = $column->renderInput($name, $options, [ 'id' => $id, 'name' => $name, @@ -336,14 +318,19 @@ public function renderCellContent($column, $index, $columnIndex = null) /** * Renders the action column. * - * @param null|int $index + * @param null|int|string $index * @param null|ActiveRecordInterface|array $item - * @param bool $isFirstColumn + * @param int $rowIndex * @return string */ - private function renderActionColumn($index = null, $item = null, $isFirstColumn = false) + private function renderActionColumn( + $index = null, + $item = null, + $rowIndex = null, + $isFirstColumn = false + ) { - $content = $this->getActionButton($index, $isFirstColumn) . $this->getExtraButtons($index, $item); + $content = $this->getActionButton($index, $rowIndex, $isFirstColumn) . $this->getExtraButtons($index, $item); return Html::tag('td', $content, [ 'class' => 'list-cell__button', @@ -362,7 +349,12 @@ private function renderCloneColumn() ]); } - private function getActionButton($index, $isFirstColumn) + /** + * @param int|string|null $index + * @param int $rowIndex + * @return string + */ + private function getActionButton($index = null, $rowIndex = null, $isFirstColumn = false) { if ($index === null || $this->min === 0) { if ($isFirstColumn) { @@ -372,16 +364,19 @@ private function getActionButton($index, $isFirstColumn) return $this->isAddButtonPositionRowBegin() ? '' : $this->renderRemoveButton(); } - $index++; - if ($index < $this->min) { + // rowIndex is zero-based, so we have to increment it to properly cpmpare it with min number of rows + $rowIndex++; + + if ($rowIndex < $this->min) { return ''; } - if ($index === $this->min) { + if ($rowIndex === $this->min) { if ($isFirstColumn) { return $this->isAddButtonPositionRowBegin() ? $this->renderAddButton() : ''; } + return $this->isAddButtonPositionRow() ? $this->renderAddButton() : ''; } From fa5ac6a0d78c89369468aeb55eadb7347b9e67c3 Mon Sep 17 00:00:00 2001 From: Evgeny Tupikov Date: Sat, 20 Apr 2024 15:11:05 +0400 Subject: [PATCH 246/247] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb31e0b..9124a39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Yii2 multiple input change log 2.30.0 (in development) ======================= +2.30.0 +======================= +- #369 fix rendering action buttons in case the dataset contains non-numeric indices (unclead) + 2.29.0 ======================= - fix addind active form fields doesn't work properly in case of 10 rows and more (unclead) From 7022e484a120a3d5473e27748bed2c8a4b7ce75a Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sun, 21 Apr 2024 17:52:05 +0400 Subject: [PATCH 247/247] fix rendering header and footer in case of using POS_ROWS_BEGIN in TableRenderer --- CHANGELOG.md | 1 + src/renderers/TableRenderer.php | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9124a39..3eecd95 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Yii2 multiple input change log 2.30.0 (in development) ======================= +- #363 fix rendering header and footer in case of using POS_ROWS_BEGIN in TableRenderer (unclead) 2.30.0 ======================= diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 55d5f3b..b078378 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -57,6 +57,10 @@ protected function internalRender() public function renderHeader() { $cells = []; + if ($this->isAddButtonPositionRowBegin()) { + $cells[] = $this->renderButtonHeaderCell(); + } + foreach ($this->columns as $column) { /* @var $column BaseColumn */ $cells[] = $this->renderHeaderCell($column); @@ -97,6 +101,10 @@ public function renderFooter() $columnsCount++; } + if ($this->isAddButtonPositionRowBegin()) { + $columnsCount++; + } + $cells = []; $cells[] = Html::tag('td', ' ', ['colspan' => $columnsCount]); $cells[] = Html::tag('td', $this->renderAddButton(), [

    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 literal 0 HcmV?d00001 From a79b6d681894b3d522d3d921cf216da837ca6679 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 30 May 2018 17:57:37 +0300 Subject: [PATCH 100/247] update minified version --- src/assets/src/js/jquery.multipleInput.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets/src/js/jquery.multipleInput.min.js b/src/assets/src/js/jquery.multipleInput.min.js index 60089c2..8694eda 100644 --- a/src/assets/src/js/jquery.multipleInput.min.js +++ b/src/assets/src/js/jquery.multipleInput.min.js @@ -1 +1 @@ -!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),d=p.closest("form"),f=u.inputId;for(m in u.jsInit){var c=document.createElement("script");c.innerHTML=u.jsInit[m],document.body.appendChild(c)}p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))});var m=0,v=t.Event(e.afterInit),h=setInterval(function(){if("object"==typeof d.data("yiiActiveForm")){var e=d.yiiActiveForm("find",f),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){-1==["id","input","container"].indexOf(t)&&(i[t]=e)}),d.yiiActiveForm("remove",f)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(h),p.trigger(v)}else m++;(0===d.length||m>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(h),p.trigger(v))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!=u.max&&s(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),m=t.Event(e.beforeAddRow);if(l.trigger(m,[c]),m.result!==!1){if(c.hide().appendTo(f).fadeIn(300),r instanceof Object){var v=[];for(var h in r)r.hasOwnProperty(h)&&v.push(r[h]);r=v}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var y=0;t(d).find("input, select, textarea").each(function(e,i){var l=t(i),a=i.tagName,u=p(l),s=t("#"+u);if(r){var d=r[y];if("INPUT"==a||"TEXTAREA"==a)s.val(d);else if("SELECT"==a)if(d&&-1!=d.indexOf("option"))s.append(d);else{var f=s.find('option[value="'+d+'"]');f.length&&s.val(d)}}n&&o(l),y++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[c])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(e,i){u(t(i))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!=r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(e){var i=p(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file +!function(t){t.fn.multipleInput=function(e){return r[e]?r[e].apply(this,Array.prototype.slice.call(arguments,1)):"object"!=typeof e&&e?(t.error("Method "+e+" does not exist on jQuery.multipleInput"),!1):r.init.apply(this,arguments)};var e={afterInit:"afterInit",beforeAddRow:"beforeAddRow",afterAddRow:"afterAddRow",beforeDeleteRow:"beforeDeleteRow",afterDeleteRow:"afterDeleteRow"},i={id:null,inputId:null,template:null,jsTemplates:[],jsInit:[],max:1,min:1,attributes:{},indexPlaceholder:"multiple_index"},n=!1,r={init:function(r){if("object"!=typeof r)return void console.error("Options must be an object");var u=t.extend(!0,{},i,r||{}),p=t("#"+u.id),f=p.closest("form"),c=u.inputId;for(m in u.jsInit){var v=document.createElement("script");v.innerHTML=u.jsInit[m],document.body.appendChild(v)}p.data("multipleInput",{settings:u,currentIndex:0}),p.on("click.multipleInput",".js-input-remove",function(e){e.stopPropagation(),a(t(this))}),p.on("click.multipleInput",".js-input-plus",function(e){e.stopPropagation(),l(t(this))}),p.on("click.multipleInput",".js-input-clone",function(e){e.stopPropagation(),l(t(this),d(t(this)))});var m=0,h=t.Event(e.afterInit),I=setInterval(function(){if("object"==typeof f.data("yiiActiveForm")){var e=f.yiiActiveForm("find",c),i={enableAjaxValidation:!1,validateOnBlur:!1,validateOnChange:!1,validateOnType:!1,validationDelay:500};"object"==typeof e&&(t.each(e,function(t,e){["id","input","container"].indexOf(t)==-1&&(i[t]=e)}),f.yiiActiveForm("remove",c)),t.each(u.attributes,function(e,n){n=t.extend({},i,n),u.attributes[e]=n}),p.data("multipleInput").settings=u,p.find(".multiple-input-list").find("input, select, textarea").each(function(){o(t(this))}),p.data("multipleInput").currentIndex=s(p),n=!0,clearInterval(I),p.trigger(h)}else m++;(0===f.length||m>10)&&(p.data("multipleInput").currentIndex=s(p),n=!1,clearInterval(I),p.trigger(h))},100)},add:function(e){l(t(this),e)},remove:function(e){var i=null;i=void 0!=e?t(this).find(".js-input-remove:eq("+e+")"):t(this).find(".js-input-remove").last(),a(i)},clear:function(){t(".js-input-remove").each(function(){a(t(this))})},option:function(e,i){i=i||null;var n=t(this).data("multipleInput"),r=n.settings;if(null===i){if(!r.hasOwnProperty(e))throw new Error('Option "'+e+'" does not exist');return r[e]}r.hasOwnProperty(e)&&(r[e]=i,n.settings=r,t(this).data("multipleInput",n))}},l=function(i,r){var l=t(i).closest(".multiple-input").first(),a=l.data("multipleInput"),u=a.settings,d=u.template,f=l.children(".multiple-input-list").first();if(!(null!=u.max&&s(l)>=u.max)){d=d.replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex);var c=t(d),v=t.Event(e.beforeAddRow);if(l.trigger(v,[c]),v.result!==!1){if(c.hide().appendTo(f).fadeIn(300),r instanceof Object){var m=[];for(var h in r)r.hasOwnProperty(h)&&m.push(r[h]);r=m}var I;for(var g in u.jsTemplates)I=u.jsTemplates[g].replaceAll("{"+u.indexPlaceholder+"}",a.currentIndex).replaceAll("%7B"+u.indexPlaceholder+"%7D",a.currentIndex),window.eval(I);var y=0;t(d).find("input, select, textarea").each(function(e,i){var l=t(i),a=i.tagName,u=p(l),s=t("#"+u);if(r){var d=r[y];if("INPUT"==a||"TEXTAREA"==a)s.val(d);else if("SELECT"==a)if(d&&d.indexOf("option")!=-1)s.append(d);else{var f=s.find('option[value="'+d+'"]');f.length&&s.val(d)}}n&&o(l),y++}),l.data("multipleInput").currentIndex++;var x=t.Event(e.afterAddRow);l.trigger(x,[c])}}},a=function(i){var r=i.closest(".multiple-input").first(),l=i.closest(".multiple-input-list__item"),a=r.data("multipleInput"),o=a.settings;if(s(r)>o.min){var p=t.Event(e.beforeDeleteRow);if(r.trigger(p,[l]),p.result===!1)return;n&&l.find("input, select, textarea").each(function(e,i){u(t(i))}),l.fadeOut(300,function(){t(this).remove(),p=t.Event(e.afterDeleteRow),r.trigger(p,[l])})}},o=function(e){var i=p(e);if(null!==i){var n=t("#"+i),r=n.closest(".multiple-input").first(),l=n.closest("form");if(0!=r.length&&"undefined"==typeof l.yiiActiveForm("find",i)){var a=r.data("multipleInput"),o={},u=i.replace(/-\d+-([^\d]+)$/,"-$1");a.settings.attributes.hasOwnProperty(u)?o=a.settings.attributes[u]:(u=u.replaceAll(/-\d-/,"-").replaceAll(/-\d/,""),a.settings.attributes.hasOwnProperty(u)&&(o=a.settings.attributes[u])),l.yiiActiveForm("add",t.extend({},o,{id:i,input:"#"+i,container:".field-"+i}))}}},u=function(e){var i=p(e);if(null!==i){var n=t("#"+i).closest("form");0!==n.length&&n.yiiActiveForm("remove",i)}},p=function(t){var e=t.attr("id");return"undefined"==typeof e&&(e=t.data("id")),"undefined"==typeof e?null:e},s=function(e){return e.find(".multiple-input-list .multiple-input-list__item").filter(function(){return t(this).parents(".multiple-input").first().attr("id")===e.attr("id")}).length},d=function(e){var i={};return e.closest("tr").find("td").each(function(e,n){t(n).find("input, select, textarea").each(function(e,n){var r=t(n),l=p(r),a=t("#"+l);i[l]=a.val()})}),i};String.prototype.replaceAll=function(t,e){return this.split(t).join(e)}}(window.jQuery); \ No newline at end of file From ef58ed8bba9a21537e158407d065aac65f55dd3a Mon Sep 17 00:00:00 2001 From: Anton Kazarinov Date: Tue, 12 Jun 2018 14:40:43 +0500 Subject: [PATCH 101/247] Fixed #217 Added 'horizontalCssClasses' property for ListRenderer --- CHANGELOG.md | 1 + examples/actions/TabularInputAction.php | 2 +- examples/views/tabular-input.php | 8 ++++++ src/TabularInput.php | 10 +++++++ src/renderers/BaseRenderer.php | 9 +++++++ src/renderers/ListRenderer.php | 35 ++++++++++++++++++------- 6 files changed, 55 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f63d79d..6e4b178 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ Yii2 multiple input change log 2.15.0 (in development) ======================= +- #217 added `layoutConfig` property for the ListRenderer 2.14.0 ====== diff --git a/examples/actions/TabularInputAction.php b/examples/actions/TabularInputAction.php index 06ab2c4..70edaee 100644 --- a/examples/actions/TabularInputAction.php +++ b/examples/actions/TabularInputAction.php @@ -23,7 +23,7 @@ class TabularInputAction extends Action { public function run() { - Yii::setAlias('@unclead-examples', realpath(__DIR__ . '/../')); + Yii::setAlias('@unclead-examples', dirname(__DIR__) . '/'); $models = $this->getItems(); diff --git a/examples/views/tabular-input.php b/examples/views/tabular-input.php index 5d0e975..2618306 100644 --- a/examples/views/tabular-input.php +++ b/examples/views/tabular-input.php @@ -1,5 +1,6 @@ $models, 'modelClass' => Item::class, + 'rendererClass' => ListRenderer::class, 'min' => 0, + 'layoutConfig' => [ + 'offsetClass' => 'col-sm-offset-4', + 'labelClass' => 'col-sm-4', + 'wrapperClass' => 'col-sm-4', + 'errorClass' => 'col-sm-4' + ], 'attributeOptions' => [ 'enableAjaxValidation' => true, 'enableClientValidation' => false, diff --git a/src/TabularInput.php b/src/TabularInput.php index d38d9f2..79375cd 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -139,6 +139,15 @@ class TabularInput extends Widget */ public $modelClass; + /** + * @var array CSS grid classes for horizontal layout. This must be an array with these keys: + * - 'offsetClass' the offset grid class to append to the wrapper if no label is rendered + * - 'labelClass' the label grid class + * - 'wrapperClass' the wrapper grid class + * - 'errorClass' the error grid class + */ + public $layoutConfig = []; + /** * Initialization. * @@ -206,6 +215,7 @@ private function createRenderer() 'form' => $this->form, 'sortable' => $this->sortable, 'enableError' => $this->enableError, + 'layoutConfig' => $this->layoutConfig, ]; if ($this->removeButtonOptions !== null) { diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 0b94b5a..19560e2 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -157,6 +157,15 @@ abstract class BaseRenderer extends BaseObject implements RendererInterface */ public $extraButtons; + /** + * @var array CSS grid classes for horizontal layout. This must be an array with these keys: + * - 'offsetClass' the offset grid class to append to the wrapper if no label is rendered + * - 'labelClass' the label grid class + * - 'wrapperClass' the wrapper grid class + * - 'errorClass' the error grid class + */ + public $layoutConfig = []; + /** * @inheritdoc */ diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index cbcd4be..cdf9edc 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -194,16 +194,20 @@ public function renderCellContent($column, $index) $hasError = false; $error = ''; + $layoutConfig = array_merge([ + 'offsetClass' => 'col-sm-offset-3', + 'labelClass' => 'col-sm-3', + 'wrapperClass' => 'col-sm-6', + 'errorClass' => 'col-sm-offset-3 col-sm-6', + ], $this->layoutConfig); + + Html::addCssClass($column->errorOptions, $layoutConfig['errorClass']); if ($index !== null) { $error = $column->getFirstError($index); $hasError = !empty($error); } - if ($column->enableError) { - $input .= "\n" . $column->renderError($error); - } - $wrapperOptions = [ 'class' => 'field-' . $id ]; @@ -212,13 +216,26 @@ public function renderCellContent($column, $index) Html::addCssClass($wrapperOptions, 'has-error'); } - $input = Html::tag('div', $input, $wrapperOptions); + Html::addCssClass($wrapperOptions, $layoutConfig['wrapperClass']); - $content = Html::beginTag('div', ['class' => 'form-group list-cell__' . $column->name]); - $content .= Html::label($column->title, $id, [ - 'class' => 'col-sm-2 control-label' . (empty($column->title) ? ' sr-only' : '') + $content = Html::beginTag('div', [ + 'class' => 'form-group list-cell__' . $column->name . ($hasError ? ' has-error' : '') ]); - $content .= Html::tag('div', $input, ['class' => 'col-sm-10']); + + if (empty($column->title)) { + Html::addCssClass($wrapperOptions, $layoutConfig['offsetClass']); + } else { + $content .= Html::label($column->title, $id, [ + 'class' => $layoutConfig['labelClass'] . ' control-label' + ]); + } + + $content .= Html::tag('div', $input, $wrapperOptions); + + if ($column->enableError) { + $content .= "\n" . $column->renderError($error); + } + $content .= Html::endTag('div'); return $content; From 20fc127075bfbe080229b6c1c248f37ba35cfa61 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Wed, 13 Jun 2018 15:34:18 +0300 Subject: [PATCH 102/247] prepare ro 2.15.0 --- CHANGELOG.md | 5 ++++- README.md | 2 +- composer.json | 2 +- package.json | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e4b178..50c010e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ Yii2 multiple input change log ============================== -2.15.0 (in development) +2.16.0 (in development) +======================= + +2.15.0 ======================= - #217 added `layoutConfig` property for the ListRenderer diff --git a/README.md b/README.md index 4360ffb..1770c4c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Yii2 widget for handle multiple inputs for an attribute of model and tabular inp [![License](https://poser.pugx.org/unclead/yii2-multiple-input/license)](https://packagist.org/packages/unclead/yii2-multiple-input) ## Latest release -The latest stable version of the extension is v2.14.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions +The latest stable version of the extension is v2.15.0 Follow the [instruction](./UPGRADE.md) for upgrading from previous versions ## Installation The preferred way to install this extension is through [composer](http://getcomposer.org/download/). diff --git a/composer.json b/composer.json index f4df911..1691178 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,7 @@ "yii2 tabular input" ], "type": "yii2-extension", - "version": "2.14.0", + "version": "2.15.0", "license": "BSD-3-Clause", "support": { "issues": "https://github.com/unclead/yii2-multiple-input/issues?state=open", diff --git a/package.json b/package.json index 3b29d35..e8388f9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.multipleInput", - "version": "2.14.0", + "version": "2.15.0", "description": "jQuery multipleInput", "scripts": { "build": "npm install && (gulp || node node_modules/gulp/bin/gulp.js)" From ac33d11815119c25a5de92d35ec4a37ebe70d8ac Mon Sep 17 00:00:00 2001 From: Anton Kazarinov Date: Sat, 28 Jul 2018 00:56:03 +0500 Subject: [PATCH 103/247] fixed #220 --- CHANGELOG.md | 5 +++-- src/renderers/ListRenderer.php | 7 ++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50c010e..4a87db0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,13 @@ Yii2 multiple input change log ============================== -2.16.0 (in development) +2.15.1 (in development) ======================= +- #220 fixed error message for clientValidation and ajaxValidation (antkaz) 2.15.0 ======================= -- #217 added `layoutConfig` property for the ListRenderer +- #217 added `layoutConfig` property for the ListRenderer (antkaz) 2.14.0 ====== diff --git a/src/renderers/ListRenderer.php b/src/renderers/ListRenderer.php index cdf9edc..95a3704 100644 --- a/src/renderers/ListRenderer.php +++ b/src/renderers/ListRenderer.php @@ -194,6 +194,7 @@ public function renderCellContent($column, $index) $hasError = false; $error = ''; + $wrapperOptions = []; $layoutConfig = array_merge([ 'offsetClass' => 'col-sm-offset-3', 'labelClass' => 'col-sm-3', @@ -208,10 +209,6 @@ public function renderCellContent($column, $index) $hasError = !empty($error); } - $wrapperOptions = [ - 'class' => 'field-' . $id - ]; - if ($hasError) { Html::addCssClass($wrapperOptions, 'has-error'); } @@ -219,7 +216,7 @@ public function renderCellContent($column, $index) Html::addCssClass($wrapperOptions, $layoutConfig['wrapperClass']); $content = Html::beginTag('div', [ - 'class' => 'form-group list-cell__' . $column->name . ($hasError ? ' has-error' : '') + 'class' => "form-group field-$id list-cell__$column->name" . ($hasError ? ' has-error' : '') ]); if (empty($column->title)) { From 374ac4dda65f656b02c13d945d697abc0a8b7a87 Mon Sep 17 00:00:00 2001 From: dbd5 Date: Sat, 11 Aug 2018 13:08:12 +0100 Subject: [PATCH 104/247] implement changes to address #228 --- CHANGELOG.md | 8 ++++++++ README.md | 35 +++++++++++++++++++++++++++++++++ src/MultipleInput.php | 31 ++++++++++++++++++++++++++++- src/TabularInput.php | 29 +++++++++++++++++++++++++++ src/assets/FontAwesomeAsset.php | 26 ++++++++++++++++++++++++ src/components/BaseColumn.php | 13 +++++++++++- src/renderers/BaseRenderer.php | 17 +++++++++++++--- src/renderers/TableRenderer.php | 14 ++++++++++--- 8 files changed, 165 insertions(+), 8 deletions(-) create mode 100644 src/assets/FontAwesomeAsset.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a87db0..94ebac0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ Yii2 multiple input change log ======================= - #220 fixed error message for clientValidation and ajaxValidation (antkaz) +2.15.1 (for review) +======================= +- #228 added `fontMap` and `fontSource`property for MultipleInput and TabularInput +- #228 changed the following methods to support preferred icon class + - BaseColumn->renderDragColumn() + - TableRenderer->renderCellContent() + - BaseRenderer->prepareButtons() + 2.15.0 ======================= - #217 added `layoutConfig` property for the ListRenderer (antkaz) diff --git a/README.md b/README.md index 1770c4c..16b2bf9 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,41 @@ use unclead\multipleinput\MultipleInput; ])->label(false); ``` +## Using other icon libraries +Multiple input and Tabular input widgets now support FontAwesome and indeed any other icon library you chose to integrate to your project. + +To take advantage of this, please proceed as follows: +1. Include the preferred icon library into your project. If you wish to use fontAwesome, you can use the included FontAwesomeAsset which will integrate the free fa from their CDN; +2. Add a mapping for your preferred icon library if its not in the fontMap array of the widget, like the following; +``` +public $fontMap = [ + 'glyphicons' => [ + 'drag-handle' => 'glyphicon glyphicon-menu-hamburger', + 'remove' => 'glyphicon glyphicon-remove', + 'add' => 'glyphicon glyphicon-plus', + 'clone' => 'glyphicon glyphicon-duplicate', + ], + 'fa' => [ + 'drag-handle' => 'fa fa-bars', + 'remove' => 'fa fa-times', + 'add' => 'fa fa-plus', + 'clone' => 'fa fa-files-o', + ], + 'my-amazing-icons' => [ + 'drag-handle' => 'my my-bars', + 'remove' => 'my my-times', + 'add' => 'my my-plus', + 'clone' => 'my fa-files-o', + ] +]; +``` +3. Set the preffered font source +``` + public $fontSource = 'my-amazing-icons'; +``` +If you do none of the above, the default behavior which assumes you are using glyphicons is retained. + + ## Documentation You can find a full version of documentation in [wiki](https://github.com/unclead/yii2-multiple-input/wiki/) diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 3cb0998..9877021 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -160,6 +160,30 @@ class MultipleInput extends InputWidget */ public $extraButtons; + /** + * @var array + * --icon library classes mapped for various controls + */ + public $fontMap = [ + 'glyphicons' => [ + 'drag-handle' => 'glyphicon glyphicon-menu-hamburger', + 'remove' => 'glyphicon glyphicon-remove', + 'add' => 'glyphicon glyphicon-plus', + 'clone' => 'glyphicon glyphicon-duplicate', + ], + 'fa' => [ + 'drag-handle' => 'fa fa-bars', + 'remove' => 'fa fa-times', + 'add' => 'fa fa-plus', + 'clone' => 'fa fa-files-o', + ], + ]; + /** + * @var string + * --name of default icon library + */ + public $fontSource = 'glyphicons'; + /** * Initialization. * @@ -255,7 +279,11 @@ private function createRenderer() array_unshift($this->columns, $drag); } - + + /** + * set default font map + */ + $fontmap = array_key_exists($this->fontSource, $this->fontMap)?$this->fontMap[$this->fontSource]:$this->fontMap['glyphicons']; $config = [ 'id' => $this->getId(), 'columns' => $this->columns, @@ -273,6 +301,7 @@ private function createRenderer() 'enableError' => $this->enableError, 'cloneButton' => $this->cloneButton, 'extraButtons' => $this->extraButtons, + 'fontMap' => $fontmap, ]; if ($this->removeButtonOptions !== null) { diff --git a/src/TabularInput.php b/src/TabularInput.php index 79375cd..4e1dac5 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -148,6 +148,30 @@ class TabularInput extends Widget */ public $layoutConfig = []; + /** + * @var array + * --icon library classes mapped for various controls + */ + public $fontMap = [ + 'glyphicons' => [ + 'drag-handle' => 'glyphicon glyphicon-menu-hamburger', + 'remove' => 'glyphicon glyphicon-remove', + 'add' => 'glyphicon glyphicon-plus', + 'clone' => 'glyphicon glyphicon-duplicate', + ], + 'fa' => [ + 'drag-handle' => 'fa fa-bars', + 'remove' => 'fa fa-times', + 'add' => 'fa fa-plus', + 'clone' => 'fa fa-files-o', + ], + ]; + /** + * @var string + * --name of default icon library + */ + public $fontSource = 'glyphicons'; + /** * Initialization. * @@ -200,6 +224,10 @@ public function run() */ private function createRenderer() { + /** + * set default font map + */ + $fontmap = array_key_exists($this->fontSource, $this->fontMap)?$this->fontMap[$this->fontSource]:$this->fontMap['glyphicons']; $config = [ 'id' => $this->getId(), 'columns' => $this->columns, @@ -216,6 +244,7 @@ private function createRenderer() 'sortable' => $this->sortable, 'enableError' => $this->enableError, 'layoutConfig' => $this->layoutConfig, + 'fontMap' => $fontmap, ]; if ($this->removeButtonOptions !== null) { diff --git a/src/assets/FontAwesomeAsset.php b/src/assets/FontAwesomeAsset.php new file mode 100644 index 0000000..47061dc --- /dev/null +++ b/src/assets/FontAwesomeAsset.php @@ -0,0 +1,26 @@ +'text/css', 'integrity'=>'sha384-hWVjflwFxL6sNzntih27bfxkr27PmbbK/iSvJ+a4+0owXq79v+lsFkW54bOGbiDQ', 'crossorigin'=>'anonymous', 'media'=>'all', 'id'=>'font-awesome', 'rel'=>'stylesheet'], + ]; + +} \ No newline at end of file diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index 352334b..a970114 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -467,7 +467,18 @@ protected function renderStatic($name, $value, $options) */ protected function renderDragColumn($name, $value, $options) { - return Html::tag('span', null, ['class' => 'glyphicon glyphicon-menu-hamburger drag-handle']); + // return Html::tag('span', null, ['class' => 'glyphicon glyphicon-menu-hamburger drag-handle']); + /** + * Class was passed into options by TableRendered->renderCellContent(), + * we can extract it here + */ + $class = ''; + if (array_key_exists('class', $options)) { + $class = ArrayHelper::remove($options, 'class'); + } + $dragClass = implode(" ", [$class, 'drag-handle']); + + return Html::tag('span', null, ['class'=>$dragClass]); } /** diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 19560e2..d1f60c6 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -248,7 +248,7 @@ private function prepareButtons() } if (!array_key_exists('label', $this->removeButtonOptions)) { - $this->removeButtonOptions['label'] = Html::tag('i', null, ['class' => 'glyphicon glyphicon-remove']); + $this->removeButtonOptions['label'] = Html::tag('i', null, ['class' => $this->getFontClass('remove')]); } if (!array_key_exists('class', $this->addButtonOptions)) { @@ -256,7 +256,7 @@ private function prepareButtons() } if (!array_key_exists('label', $this->addButtonOptions)) { - $this->addButtonOptions['label'] = Html::tag('i', null, ['class' => 'glyphicon glyphicon-plus']); + $this->addButtonOptions['label'] = Html::tag('i', null, ['class' => $this->getFontClass('add')]); } if (!array_key_exists('class', $this->cloneButtonOptions)) { @@ -264,7 +264,7 @@ private function prepareButtons() } if (!array_key_exists('label', $this->cloneButtonOptions)) { - $this->cloneButtonOptions['label'] = Html::tag('i', null, ['class' => 'glyphicon glyphicon-duplicate']); + $this->cloneButtonOptions['label'] = Html::tag('i', null, ['class' => $this->getFontClass('clone')]); } } @@ -489,4 +489,15 @@ protected function prepareJsAttributes() return $attributes; } + + /** + * @param $action - the control parameter, used as key into allowed types + * @return string - the relevant icon class + */ + protected function getFontClass($action) { + if (in_array($action, ['add', 'remove', 'clone', 'drag-handle'])) { + return $this->fontMap[$action]; + } + return ''; + } } diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index 71a5c71..a36cfa1 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -253,9 +253,17 @@ public function renderCellContent($column, $index) { $id = $column->getElementId($index); $name = $column->getElementName($index); - $input = $column->renderInput($name, [ - 'id' => $id - ]); + + /** + * This class inherits fontMap from BaseRenderer + * If the input to be rendered is a drag column, we give it the appropriate icon class + * via the $options array + */ + $options = ['id' => $id]; + if (substr($id, -4)=='drag') { + $options = ArrayHelper::merge($options, ['class'=>$this->fontMap['drag-handle']]); + } + $input = $column->renderInput($name, $options); if ($column->isHiddenInput()) { return $input; From 3fbc5578967cffe0de6d59007bf27e4e845a04ad Mon Sep 17 00:00:00 2001 From: dbd5 Date: Sat, 11 Aug 2018 13:17:44 +0100 Subject: [PATCH 105/247] implement changes to address #228 --- src/renderers/BaseRenderer.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index d1f60c6..ea0dff3 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -166,6 +166,8 @@ abstract class BaseRenderer extends BaseObject implements RendererInterface */ public $layoutConfig = []; + public $fontMap; + /** * @inheritdoc */ From 276acb324150527c46d3a58625b679ce04743c52 Mon Sep 17 00:00:00 2001 From: dbd5 Date: Sat, 11 Aug 2018 13:34:11 +0100 Subject: [PATCH 106/247] implement changes to address #228 --- CHANGELOG.md | 6 ++---- src/components/BaseColumn.php | 2 +- src/renderers/BaseRenderer.php | 2 +- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94ebac0..b6806b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,10 +8,8 @@ Yii2 multiple input change log 2.15.1 (for review) ======================= - #228 added `fontMap` and `fontSource`property for MultipleInput and TabularInput -- #228 changed the following methods to support preferred icon class - - BaseColumn->renderDragColumn() - - TableRenderer->renderCellContent() - - BaseRenderer->prepareButtons() +- #228 changed the following methods to support icon class: + BaseColumn->renderDragColumn(), TableRenderer->renderCellContent(), BaseRenderer->prepareButtons() 2.15.0 ======================= diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index a970114..e31c523 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -469,7 +469,7 @@ protected function renderDragColumn($name, $value, $options) { // return Html::tag('span', null, ['class' => 'glyphicon glyphicon-menu-hamburger drag-handle']); /** - * Class was passed into options by TableRendered->renderCellContent(), + * Class was passed into options by TableRenderer->renderCellContent(), * we can extract it here */ $class = ''; diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index ea0dff3..60399ed 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -167,7 +167,7 @@ abstract class BaseRenderer extends BaseObject implements RendererInterface public $layoutConfig = []; public $fontMap; - + /** * @inheritdoc */ From 3c84bc5ee717d3fd687adb11d3017fc7e48beecc Mon Sep 17 00:00:00 2001 From: dbd5 Date: Sun, 12 Aug 2018 15:31:49 +0100 Subject: [PATCH 107/247] implement changes to address #228, updated --- CHANGELOG.md | 2 +- README.md | 10 +++++----- src/MultipleInput.php | 10 +++++----- src/TabularInput.php | 10 +++++----- src/components/BaseColumn.php | 3 +-- src/renderers/BaseRenderer.php | 16 ++++++++++------ src/renderers/TableRenderer.php | 6 +++--- 7 files changed, 30 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b6806b8..44c3685 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ Yii2 multiple input change log 2.15.1 (for review) ======================= -- #228 added `fontMap` and `fontSource`property for MultipleInput and TabularInput +- #228 added `iconMap` and `iconSource`property for MultipleInput and TabularInput - #228 changed the following methods to support icon class: BaseColumn->renderDragColumn(), TableRenderer->renderCellContent(), BaseRenderer->prepareButtons() diff --git a/README.md b/README.md index 16b2bf9..a1ab56a 100644 --- a/README.md +++ b/README.md @@ -158,9 +158,9 @@ Multiple input and Tabular input widgets now support FontAwesome and indeed any To take advantage of this, please proceed as follows: 1. Include the preferred icon library into your project. If you wish to use fontAwesome, you can use the included FontAwesomeAsset which will integrate the free fa from their CDN; -2. Add a mapping for your preferred icon library if its not in the fontMap array of the widget, like the following; +2. Add a mapping for your preferred icon library if its not in the iconMap array of the widget, like the following; ``` -public $fontMap = [ +public $iconMap = [ 'glyphicons' => [ 'drag-handle' => 'glyphicon glyphicon-menu-hamburger', 'remove' => 'glyphicon glyphicon-remove', @@ -177,13 +177,13 @@ public $fontMap = [ 'drag-handle' => 'my my-bars', 'remove' => 'my my-times', 'add' => 'my my-plus', - 'clone' => 'my fa-files-o', + 'clone' => 'my my-files', ] ]; ``` -3. Set the preffered font source +3. Set the preffered icon source ``` - public $fontSource = 'my-amazing-icons'; + public $iconSource = 'my-amazing-icons'; ``` If you do none of the above, the default behavior which assumes you are using glyphicons is retained. diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 9877021..2fec47b 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -164,7 +164,7 @@ class MultipleInput extends InputWidget * @var array * --icon library classes mapped for various controls */ - public $fontMap = [ + public $iconMap = [ 'glyphicons' => [ 'drag-handle' => 'glyphicon glyphicon-menu-hamburger', 'remove' => 'glyphicon glyphicon-remove', @@ -182,7 +182,7 @@ class MultipleInput extends InputWidget * @var string * --name of default icon library */ - public $fontSource = 'glyphicons'; + public $iconSource = 'glyphicons'; /** * Initialization. @@ -281,9 +281,9 @@ private function createRenderer() } /** - * set default font map + * set default icon map */ - $fontmap = array_key_exists($this->fontSource, $this->fontMap)?$this->fontMap[$this->fontSource]:$this->fontMap['glyphicons']; + $iconmap = array_key_exists($this->iconSource, $this->iconMap) ? $this->iconMap[$this->iconSource] : $this->iconMap['glyphicons']; $config = [ 'id' => $this->getId(), 'columns' => $this->columns, @@ -301,7 +301,7 @@ private function createRenderer() 'enableError' => $this->enableError, 'cloneButton' => $this->cloneButton, 'extraButtons' => $this->extraButtons, - 'fontMap' => $fontmap, + 'iconMap' => $iconmap, ]; if ($this->removeButtonOptions !== null) { diff --git a/src/TabularInput.php b/src/TabularInput.php index 4e1dac5..7bd1b74 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -152,7 +152,7 @@ class TabularInput extends Widget * @var array * --icon library classes mapped for various controls */ - public $fontMap = [ + public $iconMap = [ 'glyphicons' => [ 'drag-handle' => 'glyphicon glyphicon-menu-hamburger', 'remove' => 'glyphicon glyphicon-remove', @@ -170,7 +170,7 @@ class TabularInput extends Widget * @var string * --name of default icon library */ - public $fontSource = 'glyphicons'; + public $iconSource = 'glyphicons'; /** * Initialization. @@ -225,9 +225,9 @@ public function run() private function createRenderer() { /** - * set default font map + * set default icon map */ - $fontmap = array_key_exists($this->fontSource, $this->fontMap)?$this->fontMap[$this->fontSource]:$this->fontMap['glyphicons']; + $iconmap = array_key_exists($this->iconSource, $this->iconMap) ? $this->iconMap[$this->iconSource] : $this->iconMap['glyphicons']; $config = [ 'id' => $this->getId(), 'columns' => $this->columns, @@ -244,7 +244,7 @@ private function createRenderer() 'sortable' => $this->sortable, 'enableError' => $this->enableError, 'layoutConfig' => $this->layoutConfig, - 'fontMap' => $fontmap, + 'iconMap' => $iconmap, ]; if ($this->removeButtonOptions !== null) { diff --git a/src/components/BaseColumn.php b/src/components/BaseColumn.php index e31c523..93249f5 100644 --- a/src/components/BaseColumn.php +++ b/src/components/BaseColumn.php @@ -467,7 +467,6 @@ protected function renderStatic($name, $value, $options) */ protected function renderDragColumn($name, $value, $options) { - // return Html::tag('span', null, ['class' => 'glyphicon glyphicon-menu-hamburger drag-handle']); /** * Class was passed into options by TableRenderer->renderCellContent(), * we can extract it here @@ -478,7 +477,7 @@ protected function renderDragColumn($name, $value, $options) } $dragClass = implode(" ", [$class, 'drag-handle']); - return Html::tag('span', null, ['class'=>$dragClass]); + return Html::tag('span', null, ['class' => $dragClass]); } /** diff --git a/src/renderers/BaseRenderer.php b/src/renderers/BaseRenderer.php index 60399ed..1f6c4f9 100644 --- a/src/renderers/BaseRenderer.php +++ b/src/renderers/BaseRenderer.php @@ -166,7 +166,7 @@ abstract class BaseRenderer extends BaseObject implements RendererInterface */ public $layoutConfig = []; - public $fontMap; + public $iconMap; /** * @inheritdoc @@ -250,7 +250,7 @@ private function prepareButtons() } if (!array_key_exists('label', $this->removeButtonOptions)) { - $this->removeButtonOptions['label'] = Html::tag('i', null, ['class' => $this->getFontClass('remove')]); + $this->removeButtonOptions['label'] = Html::tag('i', null, ['class' => $this->getIconClass('remove')]); } if (!array_key_exists('class', $this->addButtonOptions)) { @@ -258,7 +258,7 @@ private function prepareButtons() } if (!array_key_exists('label', $this->addButtonOptions)) { - $this->addButtonOptions['label'] = Html::tag('i', null, ['class' => $this->getFontClass('add')]); + $this->addButtonOptions['label'] = Html::tag('i', null, ['class' => $this->getIconClass('add')]); } if (!array_key_exists('class', $this->cloneButtonOptions)) { @@ -266,7 +266,7 @@ private function prepareButtons() } if (!array_key_exists('label', $this->cloneButtonOptions)) { - $this->cloneButtonOptions['label'] = Html::tag('i', null, ['class' => $this->getFontClass('clone')]); + $this->cloneButtonOptions['label'] = Html::tag('i', null, ['class' => $this->getIconClass('clone')]); } } @@ -496,9 +496,13 @@ protected function prepareJsAttributes() * @param $action - the control parameter, used as key into allowed types * @return string - the relevant icon class */ - protected function getFontClass($action) { + protected function getIconClass($action) { if (in_array($action, ['add', 'remove', 'clone', 'drag-handle'])) { - return $this->fontMap[$action]; + return $this->iconMap[$action]; + } + + if (YII_DEBUG) { + throw new InvalidConfigException('Out of bounds, "' . $action . '" not found in your iconMap'); } return ''; } diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index a36cfa1..b2e25e9 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -255,13 +255,13 @@ public function renderCellContent($column, $index) $name = $column->getElementName($index); /** - * This class inherits fontMap from BaseRenderer + * This class inherits iconMap from BaseRenderer * If the input to be rendered is a drag column, we give it the appropriate icon class * via the $options array */ $options = ['id' => $id]; - if (substr($id, -4)=='drag') { - $options = ArrayHelper::merge($options, ['class'=>$this->fontMap['drag-handle']]); + if (substr($id, -4) === 'drag') { + $options = ArrayHelper::merge($options, ['class' => $this->iconMap['drag-handle']]); } $input = $column->renderInput($name, $options); From 5db3e19386028d9d7c09d3c82d198eb60677dbbb Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Mon, 13 Aug 2018 20:54:34 +0300 Subject: [PATCH 108/247] Update CHANGELOG.md --- CHANGELOG.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 44c3685..644e87c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,9 +4,6 @@ Yii2 multiple input change log 2.15.1 (in development) ======================= - #220 fixed error message for clientValidation and ajaxValidation (antkaz) - -2.15.1 (for review) -======================= - #228 added `iconMap` and `iconSource`property for MultipleInput and TabularInput - #228 changed the following methods to support icon class: BaseColumn->renderDragColumn(), TableRenderer->renderCellContent(), BaseRenderer->prepareButtons() From e7e966aca448881d8eb9be414c29044a008fa8ba Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 29 Sep 2018 21:57:34 +0300 Subject: [PATCH 109/247] sync options + fixed markup --- examples/views/tabular-input.php | 22 +++++++++++------ src/MultipleInput.php | 17 +++++++++++-- src/TabularInput.php | 23 ++++++++++++++++-- src/assets/src/css/multiple-input.css | 35 ++++++++++++++++++++++++--- src/renderers/TableRenderer.php | 21 +++++++++++++--- 5 files changed, 99 insertions(+), 19 deletions(-) diff --git a/examples/views/tabular-input.php b/examples/views/tabular-input.php index 2618306..7c651a9 100644 --- a/examples/views/tabular-input.php +++ b/examples/views/tabular-input.php @@ -23,19 +23,25 @@ 'models' => $models, 'modelClass' => Item::class, 'rendererClass' => ListRenderer::class, + 'cloneButton' => true, 'min' => 0, + 'addButtonPosition' => [ + TabularInput::POS_HEADER, + TabularInput::POS_FOOTER, + TabularInput::POS_ROW + ], 'layoutConfig' => [ - 'offsetClass' => 'col-sm-offset-4', - 'labelClass' => 'col-sm-4', - 'wrapperClass' => 'col-sm-4', - 'errorClass' => 'col-sm-4' + 'offsetClass' => 'col-sm-offset-4', + 'labelClass' => 'col-sm-2', + 'wrapperClass' => 'col-sm-10', + 'errorClass' => 'col-sm-4' ], 'attributeOptions' => [ - 'enableAjaxValidation' => true, + 'enableAjaxValidation' => true, 'enableClientValidation' => false, - 'validateOnChange' => false, - 'validateOnSubmit' => true, - 'validateOnBlur' => false, + 'validateOnChange' => false, + 'validateOnSubmit' => true, + 'validateOnBlur' => false, ], 'form' => $form, 'columns' => [ diff --git a/src/MultipleInput.php b/src/MultipleInput.php index 2fec47b..0101eb0 100644 --- a/src/MultipleInput.php +++ b/src/MultipleInput.php @@ -160,6 +160,15 @@ class MultipleInput extends InputWidget */ public $extraButtons; + /** + * @var array CSS grid classes for horizontal layout. This must be an array with these keys: + * - 'offsetClass' the offset grid class to append to the wrapper if no label is rendered + * - 'labelClass' the label grid class + * - 'wrapperClass' the wrapper grid class + * - 'errorClass' the error grid class + */ + public $layoutConfig = []; + /** * @var array * --icon library classes mapped for various controls @@ -283,7 +292,10 @@ private function createRenderer() /** * set default icon map */ - $iconmap = array_key_exists($this->iconSource, $this->iconMap) ? $this->iconMap[$this->iconSource] : $this->iconMap['glyphicons']; + $iconMap = array_key_exists($this->iconSource, $this->iconMap) + ? $this->iconMap[$this->iconSource] + : $this->iconMap['glyphicons']; + $config = [ 'id' => $this->getId(), 'columns' => $this->columns, @@ -301,7 +313,8 @@ private function createRenderer() 'enableError' => $this->enableError, 'cloneButton' => $this->cloneButton, 'extraButtons' => $this->extraButtons, - 'iconMap' => $iconmap, + 'layoutConfig' => $this->layoutConfig, + 'iconMap' => $iconMap, ]; if ($this->removeButtonOptions !== null) { diff --git a/src/TabularInput.php b/src/TabularInput.php index 7bd1b74..ef66dda 100644 --- a/src/TabularInput.php +++ b/src/TabularInput.php @@ -132,6 +132,20 @@ class TabularInput extends Widget */ public $cloneButton = false; + /** + * @var string|\Closure the HTML content that will be rendered after the buttons. + * + * ```php + * function ($model, $index, $context) + * ``` + * + * - `$model`: the current data model being rendered + * - `$index`: the zero-based index of the data model in the model array + * - `$context`: the MultipleInput widget object + * + */ + public $extraButtons; + /** * @var string a class of model which is used to render the widget. * You have to specify this property in case you set `min` property to 0 (when you want to allow an empty list) @@ -227,7 +241,10 @@ private function createRenderer() /** * set default icon map */ - $iconmap = array_key_exists($this->iconSource, $this->iconMap) ? $this->iconMap[$this->iconSource] : $this->iconMap['glyphicons']; + $iconMap = array_key_exists($this->iconSource, $this->iconMap) + ? $this->iconMap[$this->iconSource] + : $this->iconMap['glyphicons']; + $config = [ 'id' => $this->getId(), 'columns' => $this->columns, @@ -243,8 +260,10 @@ private function createRenderer() 'form' => $this->form, 'sortable' => $this->sortable, 'enableError' => $this->enableError, + 'cloneButton' => $this->cloneButton, + 'extraButtons' => $this->extraButtons, 'layoutConfig' => $this->layoutConfig, - 'iconMap' => $iconmap, + 'iconMap' => $iconMap, ]; if ($this->removeButtonOptions !== null) { diff --git a/src/assets/src/css/multiple-input.css b/src/assets/src/css/multiple-input.css index c7e55a0..c7ef1e0 100644 --- a/src/assets/src/css/multiple-input.css +++ b/src/assets/src/css/multiple-input.css @@ -1,56 +1,85 @@ -.multiple-input-list {} +.multiple-input-list { +} + .multiple-input-list__item { margin-bottom: 5px; } + .multiple-input-list__input { display: inline-block; width: 80%; } + .multiple-input-list.no-buttons .multiple-input-list__input { display: block; width: 100%; } + table.multiple-input-list { margin: 0; } + table.multiple-input-list.table-renderer tbody tr > td { border: 0 !important; } + table.multiple-input-list.table-renderer tr > td:first-child { padding-left: 0; } + table.multiple-input-list.table-renderer tr > td:last-child { padding-right: 0; } + table.multiple-input-list tr > th { border-bottom: 1px solid #dddddd; } + .multiple-input-list.table-renderer .form-group { margin: 0 !important; } + .multiple-input-list.list-renderer .form-group { margin-bottom: 10px !important; } + .multiple-input-list.table-renderer .multiple-input-list__item .label { display: block; font-size: 13px; } + .multiple-input-list.table-renderer .list-cell__button { width: 40px; } + .multiple-input-list.list-renderer .list-cell__button { - width: 70px; + width: 40px; text-align: right; - padding-right: 15px; + padding-right: 0; + padding-left: 5px; } + +.multiple-input-list.list-renderer tbody .list-cell__button .btn{ + margin-top: 25px; +} + .multiple-input-list__item .radio, .multiple-input-list__item .checkbox { margin: 7px 0 7px 0; } + .multiple-input-list__item .radio-list .radio, .multiple-input-list__item .checkbox-list .checkbox { margin: 0; } + .multiple-input-list .multiple-input-list { margin-top: -5px; +} + +@media (min-width: 768px) { + .multiple-input-list.list-renderer tbody .list-cell__button .btn { + margin-top: 0; + } + } \ No newline at end of file diff --git a/src/renderers/TableRenderer.php b/src/renderers/TableRenderer.php index b2e25e9..0968231 100644 --- a/src/renderers/TableRenderer.php +++ b/src/renderers/TableRenderer.php @@ -58,14 +58,16 @@ public function renderHeader() $cells[] = $this->renderHeaderCell($column); } + + if ($this->max === null || ($this->max >= 1 && $this->max !== $this->min)) { $button = $this->isAddButtonPositionHeader() ? $this->renderAddButton() : ''; - $cells[] = $this->renderButtonHeaderCell($button); - if ($this->cloneButton) { $cells[] = $this->renderButtonHeaderCell(); } + + $cells[] = $this->renderButtonHeaderCell($button); } return Html::tag('thead', Html::tag('tr', implode("\n", $cells))); @@ -82,8 +84,19 @@ public function renderFooter() return ''; } + $columnsCount = 0; + foreach ($this->columns as $column) { + if (!$column->isHiddenInput()) { + $columnsCount++; + } + } + + if ($this->cloneButton) { + $columnsCount++; + } + $cells = []; - $cells[] = Html::tag('td', ' ', ['colspan' => count($this->columns)]); + $cells[] = Html::tag('td', ' ', ['colspan' => $columnsCount]); $cells[] = Html::tag('td', $this->renderAddButton(), [ 'class' => 'list-cell__button' ]); @@ -203,7 +216,7 @@ private function renderRowContent($index = null, $item = null) if ($this->cloneButton) { $cells[] = $this->renderCloneColumn(); } - + if (!$isLastRow) { $cells[] = $this->renderActionColumn($index, $item); } From 7779c7fbf5441efd4492e77275106549fd585e69 Mon Sep 17 00:00:00 2001 From: Eugene Tupikov Date: Sat, 29 Sep 2018 22:19:46 +0300 Subject: [PATCH 110/247] fixed markup #2 --- examples/views/multiple-input.php | 3 ++- src/assets/src/css/multiple-input.css | 4 ++++ src/assets/src/js/jquery.multipleInput.js | 14 +++++++------- src/assets/src/js/jquery.multipleInput.min.js | 2 +- src/renderers/ListRenderer.php | 5 +++++ src/renderers/TableRenderer.php | 2 -- 6 files changed, 19 insertions(+), 11 deletions(-) diff --git a/examples/views/multiple-input.php b/examples/views/multiple-input.php index 09e116b..7109550 100644 --- a/examples/views/multiple-input.php +++ b/examples/views/multiple-input.php @@ -46,7 +46,7 @@