├── .github
├── CONTRIBUTING.md
├── GIT-WORKFLOW.md
├── ISSUE_TEMPLATE.md
├── PULL_REQUEST_TEMPLATE.md
└── stale.yml
├── CHANGE.md
├── LICENSE.md
├── README.md
├── composer.json
└── src
├── Sortable.php
├── SortableAsset.php
└── assets
├── css
├── kv-html5-sortable.css
└── kv-html5-sortable.min.css
└── js
├── html5sortable.js
├── html5sortable.min.js
├── kv-html5-sortable.js
└── kv-html5-sortable.min.js
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing to yii2-sortable
2 | =============================
3 | Looking to contribute something to yii2-sortable? **Here's how you can help.**
4 |
5 | Using the issue tracker
6 | -----------------------
7 | When [reporting bugs][reporting-bugs] or
8 | [requesting features][requesting-features], the
9 | [issue tracker on GitHub][issue-tracker] is the recommended channel to use.
10 |
11 | The issue tracker **is not** a place for support requests. Refer the
12 | [extension documentation and demos](http://demos.krajee.com/sortable) and/or refer to the
13 | [webtips Q & A forum](http://webtips.krajee.com/questions) which are the better places to get help.
14 |
15 | How to contribute via a pull request?
16 | -------------------------------------
17 | Refer this [git workflow for contributors](.github/GIT-WORKFLOW.md).
18 |
19 | Reporting bugs with yii2-sortable
20 | ---------------------------------
21 | We really appreciate clear bug reports that _consistently_ show an issue
22 | within _yii2-sortable_.
23 |
24 | The ideal bug report follows these guidelines:
25 |
26 | 1. **Use the [GitHub issue search][issue-search]** — Check if the issue
27 | has already been reported.
28 | 2. **Check if the issue has been fixed** — Try to reproduce the problem
29 | using the code in the `master` branch.
30 | 3. **Isolate the problem** — Try to share a demo or a test case that
31 | consistently reproduces the problem.
32 |
33 | Please try to be as detailed as possible in your bug report, especially if an
34 | isolated test case cannot be made. Some useful questions to include the answer
35 | to are:
36 |
37 | - What steps can be used to reproduce the issue?
38 | - What is the bug and what is the expected outcome?
39 | - What browser(s) and Operating System have you tested with?
40 | - Does the bug happen consistently across all tested browsers?
41 | - What version of jQuery are you using? And what version of yii2-sortable?
42 | - Are you using yii2-sortable with other plugins?
43 |
44 | All of these questions will help others fix and identify any potential bugs.
45 |
46 | Requesting features in yii2-sortable
47 | ------------------------------------
48 | Before starting work on a major feature for yii2-sortable, **read the
49 | [documentation](http://demos.krajee.com/sortable) first** or you may risk spending a considerable amount of
50 | time on something which the project developers are not interested in bringing into the project.
51 |
52 | Licensing
53 | ---------
54 |
55 | It should also be made clear that **all code contributed to yii2-sortable** must be
56 | licensable under the [BSD-3 license][licensing]. Code that cannot be released
57 | under this license **cannot be accepted** into the project.
58 |
59 | [issue-search]: https://github.com/kartik-v/yii2-sortable/search?q=&type=Issues
60 | [issue-tracker]: https://github.com/kartik-v/yii2-sortable/issues
61 | [licensing]: https://github.com/kartik-v/yii2-sortable/blob/master/LICENSE.md
62 | [reporting-bugs]: #reporting-bugs-with-yii2-sortable
63 | [requesting-features]: #requesting-features-in-yii2-sortable
--------------------------------------------------------------------------------
/.github/GIT-WORKFLOW.md:
--------------------------------------------------------------------------------
1 | Git workflow for yii2-sortable contributors
2 | =========================================
3 |
4 | So you want to contribute to yii2-sortable? Great! But to increase the chances of your changes being accepted quickly, please
5 | follow the following steps. If you are new to Git and GitHub, you might want to first check out [GitHub help](http://help.github.com/), [try Git](https://try.github.com)
6 | or learn something about [Git internal data model](http://nfarina.com/post/9868516270/git-is-simpler).
7 |
8 | Setup the development environment
9 | ---------------------------------
10 |
11 | Assuming you already have a yii2 development environment, carry out the following steps to create a development environment for the repo.
12 |
13 | ### 1. [Fork](http://help.github.com/fork-a-repo/) the yii2-sortable repository on GitHub and clone your fork to your development environment
14 |
15 | ```
16 | git clone git@github.com:YOUR-GITHUB-USERNAME/yii2-sortable.git
17 | ```
18 |
19 | If you have trouble setting up Git with GitHub in Linux, or are getting errors like "Permission Denied (publickey)",
20 | then you must [setup your Git installation to work with GitHub](http://help.github.com/linux-set-up-git/)
21 |
22 | > Tip: if you're not fluent with Git, we recommend reading excellent free [Pro Git book](https://git-scm.com/book/en/v2).
23 |
24 | ### 2. Add the main yii2-sortable repository as an additional git remote called "upstream"
25 |
26 | Change to the directory where you cloned yii2-sortable, normally, "yii2-sortable". Then enter the following command:
27 |
28 | ```
29 | git remote add upstream git://github.com/kartik-v/yii2-sortable.git
30 | ```
31 |
32 | ### 3. Prepare the testing environment
33 |
34 | - You should have a working yii 2 development environment in which you have already installed `yii2-sortable` and includes latest and updated `yii2-sortable` fork from source.
35 | - Ensure you have the latest `dev-master` releases of all dependent extensions via your composer updates
36 | - Ensure you use the above cloned latest `yii2-sortable` code in your testing environment
37 |
38 | **Now you have a working playground for hacking on yii2-sortable.**
39 |
40 | Working on bugs and features
41 | ----------------------------
42 |
43 | Having prepared your development environment as explained above you can now start working on the feature or bugfix.
44 |
45 | ### 1. Make sure there is an issue created for the thing you are working on if it requires significant effort to fix
46 |
47 | All new features and bug fixes should have an associated issue to provide a single point of reference for discussion
48 | and documentation. Take a few minutes to look through the existing issue list for one that matches the contribution you
49 | intend to make. If you find one already on the issue list, then please leave a comment on that issue indicating you
50 | intend to work on that item. If you do not find an existing issue matching what you intend to work on, please
51 | open a new issue or create a pull request directly if it is straightforward fix. This will allow the team to
52 | review your suggestion, and provide appropriate feedback along the way.
53 |
54 | > For small changes or documentation issues or straightforward fixes, you don't need to create an issue, a pull request is enough in this case.
55 |
56 | ### 2. Fetch the latest code from the main yii2-sortable branch
57 |
58 | ```
59 | git fetch upstream
60 | ```
61 |
62 | You should start at this point for every new contribution to make sure you are working on the latest code.
63 |
64 | ### 3. Create a new branch for your feature based on the current yii2-sortable master branch
65 |
66 | > That's very important since you will not be able to submit more than one pull request from your account if you'll
67 | use master.
68 |
69 | Each separate bug fix or change should go in its own branch. Branch names should be descriptive and start with
70 | the sortable of the issue that your code relates to. If you aren't fixing any particular issue, just skip sortable.
71 | For example:
72 |
73 | ```
74 | git checkout upstream/master
75 | git checkout -b 999-name-of-your-branch-goes-here
76 | ```
77 |
78 | ### 4. Do your magic, write your code
79 |
80 | Make sure you have first updated the testing environment as mentioned in [prepare-the-testing-environment][prepare-the-testing-environment].
81 |
82 | Then make sure you have the updated code with your change and it works :).
83 |
84 | Unit tests are always welcome. Tested and well covered code greatly simplifies the task of checking your contributions.
85 | Failing unit tests as issue description are also accepted.
86 |
87 | ### 5. Update the CHANGE log
88 |
89 | Edit the `CHANGE.md` file to include your change, you should insert this at the top of the file under the
90 | first heading (the version that is currently under development), the line in the change log should look like one of the following:
91 |
92 | ```
93 | Bug #999: a description of the bug fix (Your Name)
94 | Enh #999: a description of the enhancement (Your Name)
95 | ```
96 |
97 | `#999` is the issue sortable that the `Bug` or `Enh` is referring to.
98 | The changelog should be grouped by type (`Bug`,`Enh`) and ordered by issue sortable.
99 |
100 | For very small fixes, e.g. typos and documentation changes, there is no need to update the `CHANGE.md`.
101 |
102 | ### 6. Commit your changes
103 |
104 | add the files/changes you want to commit to the [staging area](http://gitref.org/basic/#add) with
105 |
106 | ```
107 | git add path/to/my/file.php
108 | ```
109 |
110 | You can use the `-p` option to select the changes you want to have in your commit.
111 |
112 | Commit your changes with a descriptive commit message. Make sure to mention the ticket sortable with `#XXX` so GitHub will
113 | automatically link your commit with the ticket:
114 |
115 | ```
116 | git commit -m "A brief description of this change which fixes #999 goes here"
117 | ```
118 |
119 | ### 7. Pull the latest yii2-sortable code from upstream into your branch
120 |
121 | ```
122 | git pull upstream master
123 | ```
124 |
125 | This ensures you have the latest code in your branch before you open your pull request. If there are any merge conflicts,
126 | you should fix them now and commit the changes again. This ensures that it's easy for the yii2-sortable team to merge your changes
127 | with one click.
128 |
129 | ### 8. Having resolved any conflicts, push your code to GitHub
130 |
131 | ```
132 | git push -u origin 999-name-of-your-branch-goes-here
133 | ```
134 |
135 | The `-u` parameter ensures that your branch will now automatically push and pull from the GitHub branch. That means
136 | if you type `git push` the next time it will know where to push to. This is useful if you want to later add more commits
137 | to the pull request.
138 |
139 | ### 9. Open a [pull request](http://help.github.com/send-pull-requests/) against upstream.
140 |
141 | Go to your repository on GitHub and click "Pull Request", choose your branch on the right and enter some more details
142 | in the comment box. To link the pull request to the issue put anywhere in the pull comment `#999` where 999 is the
143 | issue sortable.
144 |
145 | > Note that each pull-request should fix a single change. For multiple, unrelated changes, please open multiple pull requests.
146 |
147 | ### 10. Someone will review your code
148 |
149 | Someone will review your code, and you might be asked to make some changes, if so go to step #6 (you don't need to open
150 | another pull request if your current one is still open). If your code is accepted it will be merged into the main branch
151 | and become part of the next yii2-sortable release. If not, don't be disheartened, different people need different features and yii2-sortable
152 | can't be everything to everyone, your code will still be available on GitHub as a reference for people who need it.
153 |
154 | ### 11. Cleaning it up
155 |
156 | After your code was either accepted or declined you can delete branches you've worked with from your local repository
157 | and `origin`.
158 |
159 | ```
160 | git checkout master
161 | git branch -D 999-name-of-your-branch-goes-here
162 | git push origin --delete 999-name-of-your-branch-goes-here
163 | ```
164 |
165 | ### Command overview (for advanced contributors)
166 |
167 | ```
168 | git clone git@github.com:YOUR-GITHUB-USERNAME/yii2-sortable.git
169 | git remote add upstream git://github.com/kartik-v/yii2-sortable.git
170 | ```
171 |
172 | ```
173 | git fetch upstream
174 | git checkout upstream/master
175 | git checkout -b 999-name-of-your-branch-goes-here
176 |
177 | /* do your magic, update changelog if needed */
178 |
179 | git add path/to/my/file.php
180 | git commit -m "A brief description of this change which fixes #999 goes here"
181 | git pull upstream master
182 | git push -u origin 999-name-of-your-branch-goes-here
183 | ```
184 |
185 | [prepare-the-testing-environment]: #3-prepare-the-testing-environment
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Prerequisites
2 |
3 | - [ ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate.
4 | - [ ] The issue still exists against the latest `master` branch of yii2-sortable.
5 | - [ ] This is not an usage question. I confirm having gone through and read the [documentation and demos](http://demos.krajee.com/sortable).
6 | - [ ] This is not a general programming / coding question. (Those should be directed to the [webtips Q & A forum](http://webtips.krajee.com/questions)).
7 | - [ ] I have attempted to find the simplest possible steps to reproduce the issue.
8 | - [ ] I have included a failing test as a pull request (Optional).
9 |
10 | ## Steps to reproduce the issue
11 |
12 | 1.
13 | 2.
14 | 3.
15 |
16 | ## Expected behavior and actual behavior
17 |
18 | When I follow those steps, I see...
19 |
20 | I was expecting...
21 |
22 | ## Environment
23 |
24 | #### Browsers
25 |
26 | - [ ] Google Chrome
27 | - [ ] Mozilla Firefox
28 | - [ ] Internet Explorer
29 | - [ ] Safari
30 |
31 | #### Operating System
32 |
33 | - [ ] Windows
34 | - [ ] Mac OS X
35 | - [ ] Linux
36 | - [ ] Mobile
37 |
38 | #### Libraries
39 |
40 | - jQuery version:
41 | - yii2-sortable version:
42 |
43 | ## Isolating the problem
44 |
45 | - [ ] This bug happens [on the docs and demos page](https://demos.krajee.com/sortable)
46 | - [ ] The bug happens consistently across all tested browsers
47 | - [ ] This bug happens when using yii2-sortable without other plugins.
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Scope
2 | This pull request includes a
3 |
4 | - [ ] Bug fix
5 | - [ ] New feature
6 | - [ ] Translation
7 |
8 | ## Changes
9 | The following changes were made (this change is also documented in the [change log](https://github.com/kartik-v/yii2-sortable/blob/master/CHANGE.md)):
10 |
11 | -
12 | -
13 | -
14 |
15 | ## Related Issues
16 | If this is related to an existing ticket, include a link to it as well.
--------------------------------------------------------------------------------
/.github/stale.yml:
--------------------------------------------------------------------------------
1 | # Number of days of inactivity before an issue becomes stale
2 | daysUntilStale: 60
3 | # Number of days of inactivity before a stale issue is closed
4 | daysUntilClose: 7
5 | # Issues with these labels will never be considered stale
6 | exemptLabels:
7 | - bug
8 | - enhancement
9 | - pinned
10 | - security
11 | # Label to use when marking an issue as stale
12 | staleLabel: wontfix
13 | # Comment to post when marking an issue as stale. Set to `false` to disable
14 | markComment: >
15 | This issue has been automatically marked as stale because it has not had
16 | recent activity. It will be closed if no further activity occurs. Thank you
17 | for your contributions.
18 | # Comment to post when closing a stale issue. Set to `false` to disable
19 | closeComment: false
--------------------------------------------------------------------------------
/CHANGE.md:
--------------------------------------------------------------------------------
1 | Change Log: `yii2-sortable`
2 | ===========================
3 |
4 | ## Version 1.2.2
5 |
6 | **Date:** 09-Oct-2018
7 |
8 | - Bump up composer dependencies.
9 |
10 | ## Version 1.2.1
11 |
12 | **Date:** 03-Sep-2018
13 |
14 | - (enh #17): Update to use newer plugin methods.
15 | - Updates to support bootstrap v4.x.
16 | - (enh #15, #16): Create new jquery plugin `kvHtml5Sortable` based on `html5sortable`.
17 | - Update to use latest release library of [html5sortable](https://github.com/lukasoppermann/html5sortable).
18 | - Move all relevant code to new `src` directory.
19 | - Add github contribution and issue/PR logging templates.
20 |
21 | ## Version 1.2.0
22 |
23 | **Date:** 17-Jun-2015
24 |
25 | - (enh #9): Set composer ## Version dependencies.
26 | - (enh #7): Correct documentation link.
27 | - (enh #5): Allow multiple connected sortables on single page. With this enhancement, the `connected`
28 | property will follow these rules:
29 | - if set to `false` or null/empty this widget will not be connected to any other sortable widget.
30 | - if set to `true`, this widget will be connected to all other sortable widgets on the page with `connected` property set to `true`.
31 | - if set to a string - this widget will be connected with other sortable widgets matching the same connected string value.
32 | - Upgrade sortable plugin to [use new fork]((https://github.com/voidberg/html5sortable).
33 |
34 | ## Version 1.1.0
35 |
36 | **Date:** 10-Nov-2014
37 |
38 | - Set dependency on Krajee base components
39 | - Set release to stable
40 |
41 | ## Version 1.0.0
42 |
43 | **Date:** 01-Jul-2014
44 |
45 | - Initial release
46 | - PSR4 alias change
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 - 2018, Kartik Visweswaran
2 | Krajee.com
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without modification,
6 | are permitted provided that the following conditions are met:
7 |
8 | * Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | * Redistributions in binary form must reproduce the above copyright notice, this
12 | list of conditions and the following disclaimer in the documentation and/or
13 | other materials provided with the distribution.
14 |
15 | * Neither the names of Kartik Visweswaran or Krajee nor the names of its
16 | contributors may be used to endorse or promote products derived from
17 | this software without specific prior written permission.
18 |
19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | yii2-sortable
7 |
8 |
10 |
11 |
12 |
13 |
14 | [](https://packagist.org/packages/kartik-v/yii2-widget-sortable)
15 | [](https://packagist.org/packages/kartik-v/yii2-widget-sortable)
16 | [](https://packagist.org/packages/kartik-v/yii2-sortable)
17 | [](https://packagist.org/packages/kartik-v/yii2-sortable)
18 | [](https://packagist.org/packages/kartik-v/yii2-sortable)
19 | [](https://packagist.org/packages/kartik-v/yii2-sortable)
20 |
21 | A Yii 2.0 widget that allows you to create sortable lists and grids and manipulate them using simple drag and drop.
22 | It is based on the lightweight [html5sortable](https://github.com/voidberg/html5sortable) jQuery plugin, which uses native HTML5 API for drag and drop.
23 | It is a leaner alternative for the JUI Sortable plugin and offers very similar functionality. The **yii2-sortable widget** offers these features:
24 |
25 | - Less than 1KB of javascript used (minified and gzipped).
26 | - Built using native HTML5 drag and drop API.
27 | - Supports both list and grid style layouts.
28 | - Similar API and behaviour to jquery-ui sortable plugin.
29 | - Works in IE 5.5+, Firefox 3.5+, Chrome 3+, Safari 3+ and, Opera 12+.
30 |
31 | ### Demo
32 | You can see detailed [documentation](http://demos.krajee.com/sortable) on usage of the extension.
33 |
34 | ## Installation
35 |
36 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
37 |
38 | > NOTE: Check the [composer.json](https://github.com/kartik-v/yii2-sortable/blob/master/composer.json) for this extension's requirements and dependencies. Read this [web tip /wiki](http://webtips.krajee.com/setting-composer-minimum-stability-application/) on setting the `minimum-stability` settings for your application's composer.json.
39 |
40 | Either run
41 |
42 | ```
43 | $ php composer.phar require kartik-v/yii2-sortable "@dev"
44 | ```
45 |
46 | or add
47 |
48 | ```
49 | "kartik-v/yii2-sortable": "@dev"
50 | ```
51 |
52 | to the ```require``` section of your `composer.json` file.
53 |
54 | ## Usage
55 |
56 | ### Sortable
57 |
58 | ```php
59 | use kartik\sortable\Sortable;
60 | echo Sortable::widget([
61 | 'type' => Sortable::TYPE_LIST,
62 | 'items' => [
63 | ['content' => 'Item # 1'],
64 | ['content' => 'Item # 2'],
65 | ['content' => 'Item # 3'],
66 | ]
67 | ]);
68 | ```
69 |
70 | ## License
71 |
72 | **yii2-sortable** is released under the BSD-3-Clause License. See the bundled `LICENSE.md` for details.
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "kartik-v/yii2-sortable",
3 | "description": "Create sortable lists and grids using HTML5 drag and drop API for Yii 2.0.",
4 | "keywords": ["yii2", "extension", "widget", "sortable", "range", "jquery", "bootstrap"],
5 | "homepage": "https://github.com/kartik-v/yii2-sortable",
6 | "type": "yii2-extension",
7 | "license": "BSD-3-Clause",
8 | "authors": [
9 | {
10 | "name": "Kartik Visweswaran",
11 | "email": "kartikv2@gmail.com",
12 | "homepage": "http://www.krajee.com/"
13 | }
14 | ],
15 | "require": {
16 | "kartik-v/yii2-krajee-base": ">=2.0.0"
17 | },
18 | "autoload": {
19 | "psr-4": {
20 | "kartik\\sortable\\": "src"
21 | }
22 | },
23 | "extra": {
24 | "branch-alias": {
25 | "dev-master": "1.2.x-dev"
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/Sortable.php:
--------------------------------------------------------------------------------
1 |
22 | * @since 1.0
23 | */
24 | class Sortable extends Widget
25 | {
26 | /**
27 | * @var string list type sortable
28 | */
29 | const TYPE_LIST = 'list';
30 |
31 | /**
32 | * @var string grid type sortable
33 | */
34 | const TYPE_GRID = 'grid';
35 |
36 | /**
37 | * @var string the type of the sortable widget
38 | * Should be one of [[Sortable::TYPE]] constants.
39 | * Defaults to Sortable::TYPE_LIST
40 | */
41 | public $type = self::TYPE_LIST;
42 |
43 | /**
44 | * @var boolean|string, whether this widget is connected to another Sortable widget. Defaults to `false`.
45 | * - if set to `false` or null/empty this widget will not be connected to any other sortable widget.
46 | * - if set to `true`, this widget will be connected to all other sortable widgets on the page with
47 | * `connected` property set to `true`.
48 | * - if set to a string - this widget will be connected with other sortable widgets matching
49 | * the same connected string value.
50 | */
51 | public $connected = false;
52 |
53 | /**
54 | * @var boolean, whether this widget is disabled
55 | */
56 | public $disabled = false;
57 |
58 | /**
59 | * @var boolean, whether to show handle for each sortable item.
60 | */
61 | public $showHandle = false;
62 |
63 | /**
64 | * @var string, the handle label, this is not HTML encoded. If not set this will default to:
65 | * - ' ' if [[bsVersion]] is `4.x`
66 | * - ' ' if [[bsVersion]] is `3.x`
67 | */
68 | public $handleLabel;
69 |
70 | /**
71 | * @var array the HTML attributes to be applied to all items.
72 | * This will be overridden by the [[options]] property within [[$items]].
73 | */
74 | public $itemOptions = [];
75 |
76 | /**
77 | * @var array the sortable items configuration for rendering elements within the sortable
78 | * list / grid. You can set the following properties:
79 | * - content: string, the list item content (this is not HTML encoded)
80 | * - disabled: bool, whether the list item is disabled
81 | * - options: array, the HTML attributes for the list item.
82 | */
83 | public $items = [];
84 |
85 | /**
86 | * Initializes the widget
87 | * @throws InvalidConfigException
88 | */
89 | public function init()
90 | {
91 | parent::init();
92 | if (!isset($this->handleLabel)) {
93 | $icon = $this->isBs4() ? '' : ' ';
94 | $this->handleLabel = $icon . ' ';
95 | }
96 | Html::addCssClass($this->options, 'sortable ' . $this->type);
97 | if (($this->connected && is_string($this->connected)) || $this->connected === true) {
98 | $css = ($this->connected === true) ? 'kv-connected' : $this->connected;
99 | Html::addCssClass($this->options, $css);
100 | $this->pluginOptions['acceptFrom'] = ".{$css}";
101 | }
102 | if ($this->showHandle) {
103 | $this->pluginOptions['handle'] = '.handle';
104 | } else {
105 | Html::addCssClass($this->options, 'cursor-move');
106 | }
107 | if ($this->hasDisabledItem() && empty($this->pluginOptions['items'])) {
108 | $this->pluginOptions['items'] = ':not(.disabled)';
109 | }
110 | $this->registerAssets();
111 | echo Html::beginTag('ul', $this->options);
112 | }
113 |
114 | /**
115 | * Runs the widget
116 | *
117 | * @return string|void
118 | */
119 | public function run()
120 | {
121 | echo $this->renderItems();
122 | echo Html::endTag('ul');
123 | }
124 |
125 | /**
126 | * Check if there is any disabled item
127 | *
128 | * @return bool
129 | */
130 | protected function hasDisabledItem()
131 | {
132 | foreach ($this->items as $item) {
133 | if (ArrayHelper::getValue($item, 'disabled', false)) {
134 | return true;
135 | }
136 | }
137 | return false;
138 | }
139 |
140 | /**
141 | * Render the list items for the sortable widget
142 | *
143 | * @return string
144 | */
145 | protected function renderItems()
146 | {
147 | $items = '';
148 | $handle = ($this->showHandle) ? Html::tag('span', $this->handleLabel, ['class' => 'handle']) : '';
149 | foreach ($this->items as $item) {
150 | $options = ArrayHelper::getValue($item, 'options', []);
151 | $options = ArrayHelper::merge($this->itemOptions, $options);
152 | if (ArrayHelper::getValue($item, 'disabled', false)) {
153 | Html::addCssClass($options, 'disabled');
154 | }
155 | $content = $handle . ArrayHelper::getValue($item, 'content', '');
156 | $items .= Html::tag('li', $content, $options) . PHP_EOL;
157 | }
158 | return $items;
159 | }
160 |
161 | /**
162 | * Register client assets
163 | */
164 | public function registerAssets()
165 | {
166 | $view = $this->getView();
167 | SortableAsset::register($view);
168 | $this->registerPlugin('kvHtml5Sortable');
169 | $id = 'jQuery("#' . $this->options['id'] . '")';
170 | if ($this->disabled) {
171 | $js = "{$id}.kvHtml5Sortable('disable');";
172 | } else {
173 | $js = "{$id}.kvHtml5Sortable('enable');";
174 | }
175 | $view->registerJs($js);
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/src/SortableAsset.php:
--------------------------------------------------------------------------------
1 |
16 | * @since 1.0
17 | */
18 | class SortableAsset extends AssetBundle
19 | {
20 | /**
21 | * @inheritdoc
22 | */
23 | public function init()
24 | {
25 | $this->setSourcePath(__DIR__ . '/assets');
26 | $this->setupAssets('css', ['css/kv-html5-sortable']);
27 | $this->setupAssets('js', ['js/html5sortable', 'js/kv-html5-sortable']);
28 | parent::init();
29 | }
30 |
31 | }
--------------------------------------------------------------------------------
/src/assets/css/kv-html5-sortable.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2018
3 | * @package yii2-sortable
4 | * @version 1.2.2
5 | *
6 | * Sortable widget styling using Bootstrap 3.0 theme
7 | * Built for Yii Framework 2.0
8 | * Author: Kartik Visweswaran
9 | * Year: 2014
10 | * For more Yii related demos visit http://demos.krajee.com
11 | */
12 | .sortable {
13 | -moz-user-select: none;
14 | padding:0;
15 | border-radius: 4px;
16 | border: 1px solid #ddd;
17 | }
18 |
19 | .sortable li {
20 | border: 1px solid #ddd;
21 | list-style: none outside none;
22 | margin: 4px;
23 | padding: 8px;
24 | cursor: normal;
25 | }
26 |
27 | .cursor-move li{
28 | cursor: move;
29 | }
30 |
31 | .sortable .handle {
32 | cursor: move;
33 | }
34 |
35 | .sortable li.disabled {
36 | cursor: not-allowed;
37 | }
38 |
39 | .sortable li:focus, .sortable li:hover {
40 | background-color: #eee;
41 | }
42 |
43 | .sortable.grid {
44 | overflow: hidden;
45 | }
46 |
47 | .sortable.grid li {
48 | float: left;
49 | min-width: 80px;
50 | min-height: 80px;
51 | text-align: center;
52 | }
53 |
54 | .sortable .handle {
55 | cursor: move;
56 | }
57 |
58 | .sortable.connected {
59 | min-height: 100px;
60 | min-width: 200px;
61 | }
62 |
63 | .sortable li.disabled {
64 | opacity: 0.5;
65 | }
66 |
67 | .sortable li.sortable-placeholder {
68 | background: none repeat scroll 0 0 rgba(0, 0, 0, 0);
69 | border: 1px dashed #CCCCCC;
70 | padding: 16px;
71 | }
72 |
73 | .sortable li.sortable-dragging {
74 | }
--------------------------------------------------------------------------------
/src/assets/css/kv-html5-sortable.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2018
3 | * @package yii2-sortable
4 | * @version 1.2.2
5 | *
6 | * Sortable widget styling using Bootstrap 3.0 theme
7 | * Built for Yii Framework 2.0
8 | * Author: Kartik Visweswaran
9 | * Year: 2014
10 | * For more Yii related demos visit http://demos.krajee.com
11 | */.sortable{-moz-user-select:none;padding:0;border-radius:4px;border:1px solid #ddd}.sortable li{border:1px solid #ddd;list-style:none outside none;margin:4px;padding:8px;cursor:normal}.cursor-move li{cursor:move}.sortable li.disabled{cursor:not-allowed}.sortable li:focus,.sortable li:hover{background-color:#eee}.sortable.grid{overflow:hidden}.sortable.grid li{float:left;min-width:80px;min-height:80px;text-align:center}.sortable .handle{cursor:move}.sortable.connected{min-height:100px;min-width:200px}.sortable li.disabled{opacity:.5}.sortable li.sortable-placeholder{background:none repeat scroll 0 0 rgba(0,0,0,0);border:1px dashed #CCC;padding:16px}
--------------------------------------------------------------------------------
/src/assets/js/html5sortable.js:
--------------------------------------------------------------------------------
1 | /*
2 | * HTML5Sortable package
3 | * https://github.com/lukasoppermann/html5sortable
4 | *
5 | * Maintained by Lukas Oppermann
6 | *
7 | * Released under the MIT license.
8 | */
9 | var sortable = (function () {
10 | 'use strict';
11 |
12 | /**
13 | * Get or set data on element
14 | * @param {HTMLElement} element
15 | * @param {string} key
16 | * @param {any} value
17 | * @return {*}
18 | */
19 | function addData(element, key, value) {
20 | if (value === undefined) {
21 | return element && element.h5s && element.h5s.data && element.h5s.data[key];
22 | }
23 | else {
24 | element.h5s = element.h5s || {};
25 | element.h5s.data = element.h5s.data || {};
26 | element.h5s.data[key] = value;
27 | }
28 | }
29 | /**
30 | * Remove data from element
31 | * @param {HTMLElement} element
32 | */
33 | function removeData(element) {
34 | if (element.h5s) {
35 | delete element.h5s.data;
36 | }
37 | }
38 |
39 | function _filter (nodes, selector) {
40 | if (!(nodes instanceof NodeList || nodes instanceof HTMLCollection || nodes instanceof Array)) {
41 | throw new Error('You must provide a nodeList/HTMLCollection/Array of elements to be filtered.');
42 | }
43 | if (typeof selector !== 'string') {
44 | return Array.from(nodes);
45 | }
46 | return Array.from(nodes).filter(function (item) { return item.nodeType === 1 && item.matches(selector); });
47 | }
48 |
49 | /* eslint-env browser */
50 | var stores = new Map();
51 | /**
52 | * Stores data & configurations per Sortable
53 | * @param {Object} config
54 | */
55 | var Store = (function () {
56 | function Store() {
57 | this._config = new Map(); // eslint-disable-line no-undef
58 | this._placeholder = undefined; // eslint-disable-line no-undef
59 | this._data = new Map(); // eslint-disable-line no-undef
60 | }
61 | Object.defineProperty(Store.prototype, "config", {
62 | /**
63 | * get the configuration map of a class instance
64 | * @method config
65 | * @return {object}
66 | */
67 | get: function () {
68 | // transform Map to object
69 | var config = {};
70 | this._config.forEach(function (value, key) {
71 | config[key] = value;
72 | });
73 | // return object
74 | return config;
75 | },
76 | /**
77 | * set the configuration of a class instance
78 | * @method config
79 | * @param {object} config object of configurations
80 | */
81 | set: function (config) {
82 | if (typeof config !== 'object') {
83 | throw new Error('You must provide a valid configuration object to the config setter.');
84 | }
85 | // combine config with default
86 | var mergedConfig = Object.assign({}, config);
87 | // add config to map
88 | this._config = new Map(Object.entries(mergedConfig));
89 | },
90 | enumerable: true,
91 | configurable: true
92 | });
93 | /**
94 | * set individual configuration of a class instance
95 | * @method setConfig
96 | * @param key valid configuration key
97 | * @param value any value
98 | * @return void
99 | */
100 | Store.prototype.setConfig = function (key, value) {
101 | if (!this._config.has(key)) {
102 | throw new Error("Trying to set invalid configuration item: " + key);
103 | }
104 | // set config
105 | this._config.set(key, value);
106 | };
107 | /**
108 | * get an individual configuration of a class instance
109 | * @method getConfig
110 | * @param key valid configuration key
111 | * @return any configuration value
112 | */
113 | Store.prototype.getConfig = function (key) {
114 | if (!this._config.has(key)) {
115 | throw new Error("Invalid configuration item requested: " + key);
116 | }
117 | return this._config.get(key);
118 | };
119 | Object.defineProperty(Store.prototype, "placeholder", {
120 | /**
121 | * get the placeholder for a class instance
122 | * @method placeholder
123 | * @return {HTMLElement|null}
124 | */
125 | get: function () {
126 | return this._placeholder;
127 | },
128 | /**
129 | * set the placeholder for a class instance
130 | * @method placeholder
131 | * @param {HTMLElement} placeholder
132 | * @return {void}
133 | */
134 | set: function (placeholder) {
135 | if (!(placeholder instanceof HTMLElement) && placeholder !== null) {
136 | throw new Error('A placeholder must be an html element or null.');
137 | }
138 | this._placeholder = placeholder;
139 | },
140 | enumerable: true,
141 | configurable: true
142 | });
143 | /**
144 | * set an data entry
145 | * @method setData
146 | * @param {string} key
147 | * @param {any} value
148 | * @return {void}
149 | */
150 | Store.prototype.setData = function (key, value) {
151 | if (typeof key !== 'string') {
152 | throw new Error("The key must be a string.");
153 | }
154 | this._data.set(key, value);
155 | };
156 | /**
157 | * get an data entry
158 | * @method getData
159 | * @param {string} key an existing key
160 | * @return {any}
161 | */
162 | Store.prototype.getData = function (key) {
163 | if (typeof key !== 'string') {
164 | throw new Error("The key must be a string.");
165 | }
166 | return this._data.get(key);
167 | };
168 | /**
169 | * delete an data entry
170 | * @method deleteData
171 | * @param {string} key an existing key
172 | * @return {boolean}
173 | */
174 | Store.prototype.deleteData = function (key) {
175 | if (typeof key !== 'string') {
176 | throw new Error("The key must be a string.");
177 | }
178 | return this._data.delete(key);
179 | };
180 | return Store;
181 | }());
182 | function store (sortableElement) {
183 | // if sortableElement is wrong type
184 | if (!(sortableElement instanceof HTMLElement)) {
185 | throw new Error('Please provide a sortable to the store function.');
186 | }
187 | // create new instance if not avilable
188 | if (!stores.has(sortableElement)) {
189 | stores.set(sortableElement, new Store());
190 | }
191 | // return instance
192 | return stores.get(sortableElement);
193 | }
194 |
195 | /**
196 | * @param {Array|HTMLElement} element
197 | * @param {Function} callback
198 | * @param {string} event
199 | */
200 | function addEventListener(element, eventName, callback) {
201 | if (element instanceof Array) {
202 | for (var i = 0; i < element.length; ++i) {
203 | addEventListener(element[i], eventName, callback);
204 | }
205 | return;
206 | }
207 | element.addEventListener(eventName, callback);
208 | store(element).setData("event" + eventName, callback);
209 | }
210 | /**
211 | * @param {Array|HTMLElement} element
212 | * @param {string} eventName
213 | */
214 | function removeEventListener(element, eventName) {
215 | if (element instanceof Array) {
216 | for (var i = 0; i < element.length; ++i) {
217 | removeEventListener(element[i], eventName);
218 | }
219 | return;
220 | }
221 | element.removeEventListener(eventName, store(element).getData("event" + eventName));
222 | store(element).deleteData("event" + eventName);
223 | }
224 |
225 | /**
226 | * @param {Array|HTMLElement} element
227 | * @param {string} attribute
228 | * @param {string} value
229 | */
230 | function addAttribute(element, attribute, value) {
231 | if (element instanceof Array) {
232 | for (var i = 0; i < element.length; ++i) {
233 | addAttribute(element[i], attribute, value);
234 | }
235 | return;
236 | }
237 | element.setAttribute(attribute, value);
238 | }
239 | /**
240 | * @param {Array|HTMLElement} element
241 | * @param {string} attribute
242 | */
243 | function removeAttribute(element, attribute) {
244 | if (element instanceof Array) {
245 | for (var i = 0; i < element.length; ++i) {
246 | removeAttribute(element[i], attribute);
247 | }
248 | return;
249 | }
250 | element.removeAttribute(attribute);
251 | }
252 |
253 | function offset (element) {
254 | if (!element.parentElement || element.getClientRects().length === 0) {
255 | throw new Error('target element must be part of the dom');
256 | }
257 | var rect = element.getClientRects()[0];
258 | return {
259 | left: rect.left + window.scrollX,
260 | right: rect.right + window.scrollX,
261 | top: rect.top + window.scrollY,
262 | bottom: rect.bottom + window.scrollY
263 | };
264 | }
265 |
266 | function _debounce (func, wait) {
267 | if (wait === void 0) { wait = 0; }
268 | var timeout;
269 | return function () {
270 | var args = [];
271 | for (var _i = 0; _i < arguments.length; _i++) {
272 | args[_i - 0] = arguments[_i];
273 | }
274 | clearTimeout(timeout);
275 | timeout = setTimeout(function () {
276 | func.apply(void 0, args);
277 | }, wait);
278 | };
279 | }
280 |
281 | function index (element, elementList) {
282 | if (!(element instanceof HTMLElement) || !(elementList instanceof NodeList || elementList instanceof HTMLCollection || elementList instanceof Array)) {
283 | throw new Error('You must provide an element and a list of elements.');
284 | }
285 | return Array.from(elementList).indexOf(element);
286 | }
287 |
288 | function isInDom (element) {
289 | if (!(element instanceof HTMLElement)) {
290 | throw new Error('Element is not a node element.');
291 | }
292 | return element.parentNode !== null;
293 | }
294 |
295 | /* eslint-env browser */
296 | /**
297 | * Insert node before or after target
298 | * @param {HTMLElement} referenceNode - reference element
299 | * @param {HTMLElement} newElement - element to be inserted
300 | * @param {String} position - insert before or after reference element
301 | */
302 | var insertNode = function (referenceNode, newElement, position) {
303 | if (!(referenceNode instanceof HTMLElement) || !(referenceNode.parentElement instanceof HTMLElement)) {
304 | throw new Error('target and element must be a node');
305 | }
306 | referenceNode.parentElement.insertBefore(newElement, (position === 'before' ? referenceNode : referenceNode.nextElementSibling));
307 | };
308 | /**
309 | * Insert before target
310 | * @param {HTMLElement} target
311 | * @param {HTMLElement} element
312 | */
313 | var insertBefore = function (target, element) { return insertNode(target, element, 'before'); };
314 | /**
315 | * Insert after target
316 | * @param {HTMLElement} target
317 | * @param {HTMLElement} element
318 | */
319 | var insertAfter = function (target, element) { return insertNode(target, element, 'after'); };
320 |
321 | function _serialize (sortableContainer, customItemSerializer, customContainerSerializer) {
322 | if (customItemSerializer === void 0) { customItemSerializer = function (serializedItem, sortableContainer) { return serializedItem; }; }
323 | if (customContainerSerializer === void 0) { customContainerSerializer = function (serializedContainer) { return serializedContainer; }; }
324 | // check for valid sortableContainer
325 | if (!(sortableContainer instanceof HTMLElement) || !sortableContainer.isSortable === true) {
326 | throw new Error('You need to provide a sortableContainer to be serialized.');
327 | }
328 | // check for valid serializers
329 | if (typeof customItemSerializer !== 'function' || typeof customContainerSerializer !== 'function') {
330 | throw new Error('You need to provide a valid serializer for items and the container.');
331 | }
332 | // get options
333 | var options = addData(sortableContainer, 'opts');
334 | var item = options.items;
335 | // serialize container
336 | var items = _filter(sortableContainer.children, item);
337 | var serializedItems = items.map(function (item) {
338 | return {
339 | parent: sortableContainer,
340 | node: item,
341 | html: item.outerHTML,
342 | index: index(item, items)
343 | };
344 | });
345 | // serialize container
346 | var container = {
347 | node: sortableContainer,
348 | itemCount: serializedItems.length
349 | };
350 | return {
351 | container: customContainerSerializer(container),
352 | items: serializedItems.map(function (item) { return customItemSerializer(item, sortableContainer); })
353 | };
354 | }
355 |
356 | function _makePlaceholder (sortableElement, placeholder, placeholderClass) {
357 | if (placeholderClass === void 0) { placeholderClass = 'sortable-placeholder'; }
358 | if (!(sortableElement instanceof HTMLElement)) {
359 | throw new Error('You must provide a valid element as a sortable.');
360 | }
361 | // if placeholder is not an element
362 | if (!(placeholder instanceof HTMLElement) && placeholder !== undefined) {
363 | throw new Error('You must provide a valid element as a placeholder or set ot to undefined.');
364 | }
365 | // if no placeholder element is given
366 | if (placeholder === undefined) {
367 | if (['UL', 'OL'].includes(sortableElement.tagName)) {
368 | placeholder = document.createElement('li');
369 | }
370 | else if (['TABLE', 'TBODY'].includes(sortableElement.tagName)) {
371 | placeholder = document.createElement('tr');
372 | // set colspan to always all rows, otherwise the item can only be dropped in first column
373 | placeholder.innerHTML = ' | ';
374 | }
375 | else {
376 | placeholder = document.createElement('div');
377 | }
378 | }
379 | // add classes to placeholder
380 | if (typeof placeholderClass === 'string') {
381 | (_a = placeholder.classList).add.apply(_a, placeholderClass.split(' '));
382 | }
383 | return placeholder;
384 | var _a;
385 | }
386 |
387 | function _getElementHeight (element) {
388 | if (!(element instanceof HTMLElement)) {
389 | throw new Error('You must provide a valid dom element');
390 | }
391 | // get calculated style of element
392 | var style = window.getComputedStyle(element);
393 | // pick applicable properties, convert to int and reduce by adding
394 | return ['height', 'padding-top', 'padding-bottom']
395 | .map(function (key) {
396 | var int = parseInt(style.getPropertyValue(key), 10);
397 | return isNaN(int) ? 0 : int;
398 | })
399 | .reduce(function (sum, value) { return sum + value; });
400 | }
401 |
402 | function _getHandles (items, selector) {
403 | if (!(items instanceof Array)) {
404 | throw new Error('You must provide a Array of HTMLElements to be filtered.');
405 | }
406 | if (typeof selector !== 'string') {
407 | return items;
408 | }
409 | return items
410 | .filter(function (item) {
411 | return item.querySelector(selector) instanceof HTMLElement;
412 | })
413 | .map(function (item) {
414 | return item.querySelector(selector);
415 | });
416 | }
417 |
418 | /**
419 | * defaultDragImage returns the current item as dragged image
420 | * @param {HTMLElement} draggedElement - the item that the user drags
421 | * @param {object} elementOffset - an object with the offsets top, left, right & bottom
422 | * @param {Event} event - the original drag event object
423 | * @return {object} with element, posX and posY properties
424 | */
425 | var defaultDragImage = function (draggedElement, elementOffset, event) {
426 | return {
427 | element: draggedElement,
428 | posX: event.pageX - elementOffset.left,
429 | posY: event.pageY - elementOffset.top
430 | };
431 | };
432 | function setDragImage (event, draggedElement, customDragImage) {
433 | // check if event is provided
434 | if (!(event instanceof Event)) {
435 | throw new Error('setDragImage requires a DragEvent as the first argument.');
436 | }
437 | // check if draggedElement is provided
438 | if (!(draggedElement instanceof HTMLElement)) {
439 | throw new Error('setDragImage requires the dragged element as the second argument.');
440 | }
441 | // set default function of none provided
442 | if (!customDragImage) {
443 | customDragImage = defaultDragImage;
444 | }
445 | // check if setDragImage method is available
446 | if (event.dataTransfer && event.dataTransfer.setDragImage) {
447 | // get the elements offset
448 | var elementOffset = offset(draggedElement);
449 | // get the dragImage
450 | var dragImage = customDragImage(draggedElement, elementOffset, event);
451 | // check if custom function returns correct values
452 | if (!(dragImage.element instanceof HTMLElement) || typeof dragImage.posX !== 'number' || typeof dragImage.posY !== 'number') {
453 | throw new Error('The customDragImage function you provided must return and object with the properties element[string], posX[integer], posY[integer].');
454 | }
455 | // needs to be set for HTML5 drag & drop to work
456 | event.dataTransfer.effectAllowed = 'copyMove';
457 | // Firefox requires arbitrary content in setData for the drag & drop functionality to work
458 | event.dataTransfer.setData('text/plain', 'arbitrary');
459 | // set the drag image on the event
460 | event.dataTransfer.setDragImage(dragImage.element, dragImage.posX, dragImage.posY);
461 | }
462 | }
463 |
464 | function _listsConnected (destination, origin) {
465 | // check if valid sortable
466 | if (destination.isSortable === true) {
467 | var acceptFrom = store(destination).getConfig('acceptFrom');
468 | // check if acceptFrom is valid
469 | if (acceptFrom !== null && acceptFrom !== false && typeof acceptFrom !== 'string') {
470 | throw new Error('HTML5Sortable: Wrong argument, "acceptFrom" must be "null", "false", or a valid selector string.');
471 | }
472 | if (acceptFrom !== null) {
473 | return acceptFrom !== false && acceptFrom.split(',').filter(function (sel) {
474 | return sel.length > 0 && origin.matches(sel);
475 | }).length > 0;
476 | }
477 | // drop in same list
478 | if (destination === origin) {
479 | return true;
480 | }
481 | // check if lists are connected with connectWith
482 | if (store(destination).getConfig('connectWith') !== undefined && store(destination).getConfig('connectWith') !== null) {
483 | return store(destination).getConfig('connectWith') === store(origin).getConfig('connectWith');
484 | }
485 | }
486 | return false;
487 | }
488 |
489 | var defaultConfiguration = {
490 | items: null,
491 | // deprecated
492 | connectWith: null,
493 | // deprecated
494 | disableIEFix: null,
495 | acceptFrom: null,
496 | copy: false,
497 | placeholder: null,
498 | placeholderClass: 'sortable-placeholder',
499 | draggingClass: 'sortable-dragging',
500 | hoverClass: false,
501 | debounce: 0,
502 | throttleTime: 100,
503 | maxItems: 0,
504 | itemSerializer: undefined,
505 | containerSerializer: undefined,
506 | customDragImage: null
507 | };
508 |
509 | /**
510 | * make sure a function is only called once within the given amount of time
511 | * @param {Function} fn the function to throttle
512 | * @param {number} threshold time limit for throttling
513 | */
514 | // must use function to keep this context
515 | function _throttle (fn, threshold) {
516 | var _this = this;
517 | if (threshold === void 0) { threshold = 250; }
518 | // check function
519 | if (typeof fn !== 'function') {
520 | throw new Error('You must provide a function as the first argument for throttle.');
521 | }
522 | // check threshold
523 | if (typeof threshold !== 'number') {
524 | throw new Error('You must provide a number as the second argument for throttle.');
525 | }
526 | var lastEventTimestamp = null;
527 | return function () {
528 | var args = [];
529 | for (var _i = 0; _i < arguments.length; _i++) {
530 | args[_i - 0] = arguments[_i];
531 | }
532 | var now = Date.now();
533 | if (lastEventTimestamp === null || now - lastEventTimestamp >= threshold) {
534 | lastEventTimestamp = now;
535 | fn.apply(_this, args);
536 | }
537 | };
538 | }
539 |
540 | function enableHoverClass (sortableContainer, enable) {
541 | if (typeof store(sortableContainer).getConfig('hoverClass') === 'string') {
542 | var hoverClasses_1 = store(sortableContainer).getConfig('hoverClass').split(' ');
543 | // add class on hover
544 | if (enable === true) {
545 | addEventListener(sortableContainer, 'mousemove', _throttle(function (event) {
546 | // check of no mouse button was pressed when mousemove started == no drag
547 | if (event.buttons === 0) {
548 | _filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) {
549 | if (item !== event.target) {
550 | (_a = item.classList).remove.apply(_a, hoverClasses_1);
551 | }
552 | else {
553 | (_b = item.classList).add.apply(_b, hoverClasses_1);
554 | }
555 | var _a, _b;
556 | });
557 | }
558 | }, store(sortableContainer).getConfig('throttleTime')));
559 | // remove class on leave
560 | addEventListener(sortableContainer, 'mouseleave', function () {
561 | _filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) {
562 | (_a = item.classList).remove.apply(_a, hoverClasses_1);
563 | var _a;
564 | });
565 | });
566 | }
567 | else {
568 | removeEventListener(sortableContainer, 'mousemove');
569 | removeEventListener(sortableContainer, 'mouseleave');
570 | }
571 | }
572 | }
573 |
574 | /* eslint-env browser */
575 | /*
576 | * variables global to the plugin
577 | */
578 | var dragging;
579 | var draggingHeight;
580 | /*
581 | * Keeps track of the initialy selected list, where 'dragstart' event was triggered
582 | * It allows us to move the data in between individual Sortable List instances
583 | */
584 | // Origin List - data from before any item was changed
585 | var originContainer;
586 | var originIndex;
587 | var originElementIndex;
588 | var originItemsBeforeUpdate;
589 | // Destination List - data from before any item was changed
590 | var destinationItemsBeforeUpdate;
591 | /**
592 | * remove event handlers from items
593 | * @param {Array|NodeList} items
594 | */
595 | var _removeItemEvents = function (items) {
596 | removeEventListener(items, 'dragstart');
597 | removeEventListener(items, 'dragend');
598 | removeEventListener(items, 'dragover');
599 | removeEventListener(items, 'dragenter');
600 | removeEventListener(items, 'drop');
601 | removeEventListener(items, 'mouseenter');
602 | removeEventListener(items, 'mouseleave');
603 | };
604 | /**
605 | * _getDragging returns the current element to drag or
606 | * a copy of the element.
607 | * Is Copy Active for sortable
608 | * @param {HTMLElement} draggedItem - the item that the user drags
609 | * @param {HTMLElement} sortable a single sortable
610 | */
611 | var _getDragging = function (draggedItem, sortable) {
612 | var ditem = draggedItem;
613 | if (store(sortable).getConfig('copy') === true) {
614 | ditem = draggedItem.cloneNode(true);
615 | addAttribute(ditem, 'aria-copied', 'true');
616 | draggedItem.parentElement.appendChild(ditem);
617 | ditem.style.display = 'none';
618 | ditem.oldDisplay = draggedItem.style.display;
619 | }
620 | return ditem;
621 | };
622 | /**
623 | * Remove data from sortable
624 | * @param {HTMLElement} sortable a single sortable
625 | */
626 | var _removeSortableData = function (sortable) {
627 | removeData(sortable);
628 | removeAttribute(sortable, 'aria-dropeffect');
629 | };
630 | /**
631 | * Remove data from items
632 | * @param {Array|HTMLElement} items
633 | */
634 | var _removeItemData = function (items) {
635 | removeAttribute(items, 'aria-grabbed');
636 | removeAttribute(items, 'aria-copied');
637 | removeAttribute(items, 'draggable');
638 | removeAttribute(items, 'role');
639 | };
640 | /**
641 | * find sortable from element. travels up parent element until found or null.
642 | * @param {HTMLElement} element a single sortable
643 | */
644 | function findSortable(element) {
645 | while (element.isSortable !== true) {
646 | element = element.parentElement;
647 | }
648 | return element;
649 | }
650 | /**
651 | * Dragging event is on the sortable element. finds the top child that
652 | * contains the element.
653 | * @param {HTMLElement} sortableElement a single sortable
654 | * @param {HTMLElement} element is that being dragged
655 | */
656 | function findDragElement(sortableElement, element) {
657 | var options = addData(sortableElement, 'opts');
658 | var items = _filter(sortableElement.children, options.items);
659 | var itemlist = items.filter(function (ele) {
660 | return ele.contains(element);
661 | });
662 | return itemlist.length > 0 ? itemlist[0] : element;
663 | }
664 | /**
665 | * Destroy the sortable
666 | * @param {HTMLElement} sortableElement a single sortable
667 | */
668 | var _destroySortable = function (sortableElement) {
669 | var opts = addData(sortableElement, 'opts') || {};
670 | var items = _filter(sortableElement.children, opts.items);
671 | var handles = _getHandles(items, opts.handle);
672 | // remove event handlers & data from sortable
673 | removeEventListener(sortableElement, 'dragover');
674 | removeEventListener(sortableElement, 'dragenter');
675 | removeEventListener(sortableElement, 'drop');
676 | // remove event data from sortable
677 | _removeSortableData(sortableElement);
678 | // remove event handlers & data from items
679 | removeEventListener(handles, 'mousedown');
680 | _removeItemEvents(items);
681 | _removeItemData(items);
682 | };
683 | /**
684 | * Enable the sortable
685 | * @param {HTMLElement} sortableElement a single sortable
686 | */
687 | var _enableSortable = function (sortableElement) {
688 | var opts = addData(sortableElement, 'opts');
689 | var items = _filter(sortableElement.children, opts.items);
690 | var handles = _getHandles(items, opts.handle);
691 | addAttribute(sortableElement, 'aria-dropeffect', 'move');
692 | addData(sortableElement, '_disabled', 'false');
693 | addAttribute(handles, 'draggable', 'true');
694 | // @todo: remove this fix
695 | // IE FIX for ghost
696 | // can be disabled as it has the side effect that other events
697 | // (e.g. click) will be ignored
698 | if (opts.disableIEFix === false) {
699 | var spanEl = (document || window.document).createElement('span');
700 | if (typeof spanEl.dragDrop === 'function') {
701 | addEventListener(handles, 'mousedown', function () {
702 | if (items.indexOf(this) !== -1) {
703 | this.dragDrop();
704 | }
705 | else {
706 | var parent = this.parentElement;
707 | while (items.indexOf(parent) === -1) {
708 | parent = parent.parentElement;
709 | }
710 | parent.dragDrop();
711 | }
712 | });
713 | }
714 | }
715 | };
716 | /**
717 | * Disable the sortable
718 | * @param {HTMLElement} sortableElement a single sortable
719 | */
720 | var _disableSortable = function (sortableElement) {
721 | var opts = addData(sortableElement, 'opts');
722 | var items = _filter(sortableElement.children, opts.items);
723 | var handles = _getHandles(items, opts.handle);
724 | addAttribute(sortableElement, 'aria-dropeffect', 'none');
725 | addData(sortableElement, '_disabled', 'true');
726 | addAttribute(handles, 'draggable', 'false');
727 | removeEventListener(handles, 'mousedown');
728 | };
729 | /**
730 | * Reload the sortable
731 | * @param {HTMLElement} sortableElement a single sortable
732 | * @description events need to be removed to not be double bound
733 | */
734 | var _reloadSortable = function (sortableElement) {
735 | var opts = addData(sortableElement, 'opts');
736 | var items = _filter(sortableElement.children, opts.items);
737 | var handles = _getHandles(items, opts.handle);
738 | addData(sortableElement, '_disabled', 'false');
739 | // remove event handlers from items
740 | _removeItemEvents(items);
741 | removeEventListener(handles, 'mousedown');
742 | // remove event handlers from sortable
743 | removeEventListener(sortableElement, 'dragover');
744 | removeEventListener(sortableElement, 'dragenter');
745 | removeEventListener(sortableElement, 'drop');
746 | };
747 | /**
748 | * Public sortable object
749 | * @param {Array|NodeList} sortableElements
750 | * @param {object|string} options|method
751 | */
752 | function sortable(sortableElements, options) {
753 | // get method string to see if a method is called
754 | var method = String(options);
755 | // merge user options with defaultss
756 | options = Object.assign({
757 | connectWith: null,
758 | acceptFrom: null,
759 | copy: false,
760 | placeholder: null,
761 | disableIEFix: null,
762 | placeholderClass: 'sortable-placeholder',
763 | draggingClass: 'sortable-dragging',
764 | hoverClass: false,
765 | debounce: 0,
766 | maxItems: 0,
767 | itemSerializer: undefined,
768 | containerSerializer: undefined,
769 | customDragImage: null,
770 | items: null
771 | }, (typeof options === 'object') ? options : {});
772 | // check if the user provided a selector instead of an element
773 | if (typeof sortableElements === 'string') {
774 | sortableElements = document.querySelectorAll(sortableElements);
775 | }
776 | // if the user provided an element, return it in an array to keep the return value consistant
777 | if (sortableElements instanceof HTMLElement) {
778 | sortableElements = [sortableElements];
779 | }
780 | sortableElements = Array.prototype.slice.call(sortableElements);
781 | if (/serialize/.test(method)) {
782 | return sortableElements.map(function (sortableContainer) {
783 | var opts = addData(sortableContainer, 'opts');
784 | return _serialize(sortableContainer, opts.itemSerializer, opts.containerSerializer);
785 | });
786 | }
787 | sortableElements.forEach(function (sortableElement) {
788 | if (/enable|disable|destroy/.test(method)) {
789 | return sortable[method](sortableElement);
790 | }
791 | // log deprecation
792 | ['connectWith', 'disableIEFix'].forEach(function (configKey) {
793 | if (options.hasOwnProperty(configKey) && options[configKey] !== null) {
794 | console.warn("HTML5Sortable: You are using the deprecated configuration \"" + configKey + "\". This will be removed in an upcoming version, make sure to migrate to the new options when updating.");
795 | }
796 | });
797 | // merge options with default options
798 | options = Object.assign({}, defaultConfiguration, options);
799 | // init data store for sortable
800 | store(sortableElement).config = options;
801 | // get options & set options on sortable
802 | options = addData(sortableElement, 'opts') || options;
803 | addData(sortableElement, 'opts', options);
804 | // property to define as sortable
805 | sortableElement.isSortable = true;
806 | // reset sortable
807 | _reloadSortable(sortableElement);
808 | // initialize
809 | var listItems = _filter(sortableElement.children, options.items);
810 | // create element if user defined a placeholder element as a string
811 | var customPlaceholder;
812 | if (options.placeholder !== null && options.placeholder !== undefined) {
813 | var tempContainer = document.createElement(sortableElement.tagName);
814 | tempContainer.innerHTML = options.placeholder;
815 | customPlaceholder = tempContainer.children[0];
816 | }
817 | // add placeholder
818 | store(sortableElement).placeholder = _makePlaceholder(sortableElement, customPlaceholder, options.placeholderClass);
819 | addData(sortableElement, 'items', options.items);
820 | if (options.acceptFrom) {
821 | addData(sortableElement, 'acceptFrom', options.acceptFrom);
822 | }
823 | else if (options.connectWith) {
824 | addData(sortableElement, 'connectWith', options.connectWith);
825 | }
826 | _enableSortable(sortableElement);
827 | addAttribute(listItems, 'role', 'option');
828 | addAttribute(listItems, 'aria-grabbed', 'false');
829 | // enable hover class
830 | enableHoverClass(sortableElement, true);
831 | /*
832 | Handle drag events on draggable items
833 | Handle is set at the sortableElement level as it will bubble up
834 | from the item
835 | */
836 | addEventListener(sortableElement, 'dragstart', function (e) {
837 | // ignore dragstart events
838 | if (e.target.isSortable === true) {
839 | return;
840 | }
841 | e.stopImmediatePropagation();
842 | if ((options.handle && !e.target.matches(options.handle)) || e.target.getAttribute('draggable') === 'false') {
843 | return;
844 | }
845 | var sortableContainer = findSortable(e.target);
846 | var dragItem = findDragElement(sortableContainer, e.target);
847 | // grab values
848 | originItemsBeforeUpdate = _filter(sortableContainer.children, options.items);
849 | originIndex = originItemsBeforeUpdate.indexOf(dragItem);
850 | originElementIndex = index(dragItem, sortableContainer.children);
851 | originContainer = sortableContainer;
852 | // add transparent clone or other ghost to cursor
853 | setDragImage(e, dragItem, options.customDragImage);
854 | // cache selsection & add attr for dragging
855 | draggingHeight = _getElementHeight(dragItem);
856 | dragItem.classList.add(options.draggingClass);
857 | dragging = _getDragging(dragItem, sortableContainer);
858 | addAttribute(dragging, 'aria-grabbed', 'true');
859 | // dispatch sortstart event on each element in group
860 | sortableContainer.dispatchEvent(new CustomEvent('sortstart', {
861 | detail: {
862 | origin: {
863 | elementIndex: originElementIndex,
864 | index: originIndex,
865 | container: originContainer
866 | },
867 | item: dragging
868 | }
869 | }));
870 | });
871 | /*
872 | We are capturing targetSortable before modifications with 'dragenter' event
873 | */
874 | addEventListener(sortableElement, 'dragenter', function (e) {
875 | if (e.target.isSortable === true) {
876 | return;
877 | }
878 | var sortableContainer = findSortable(e.target);
879 | destinationItemsBeforeUpdate = _filter(sortableContainer.children, addData(sortableContainer, 'items'))
880 | .filter(function (item) { return item !== store(sortableElement).placeholder; });
881 | });
882 | /*
883 | * Dragend Event - https://developer.mozilla.org/en-US/docs/Web/Events/dragend
884 | * Fires each time dragEvent end, or ESC pressed
885 | * We are using it to clean up any draggable elements and placeholders
886 | */
887 | addEventListener(sortableElement, 'dragend', function (e) {
888 | if (!dragging) {
889 | return;
890 | }
891 | dragging.classList.remove(options.draggingClass);
892 | addAttribute(dragging, 'aria-grabbed', 'false');
893 | if (dragging.getAttribute('aria-copied') === 'true' && addData(dragging, 'dropped') !== 'true') {
894 | dragging.remove();
895 | }
896 | dragging.style.display = dragging.oldDisplay;
897 | delete dragging.oldDisplay;
898 | var visiblePlaceholder = Array.from(stores.values()).map(function (data) { return data.placeholder; })
899 | .filter(function (placeholder) { return placeholder instanceof HTMLElement; })
900 | .filter(isInDom)[0];
901 | if (visiblePlaceholder) {
902 | visiblePlaceholder.remove();
903 | }
904 | // dispatch sortstart event on each element in group
905 | sortableElement.dispatchEvent(new CustomEvent('sortstop', {
906 | detail: {
907 | origin: {
908 | elementIndex: originElementIndex,
909 | index: originIndex,
910 | container: originContainer
911 | },
912 | item: dragging
913 | }
914 | }));
915 | dragging = null;
916 | draggingHeight = null;
917 | });
918 | /*
919 | * Drop Event - https://developer.mozilla.org/en-US/docs/Web/Events/drop
920 | * Fires when valid drop target area is hit
921 | */
922 | addEventListener(sortableElement, 'drop', function (e) {
923 | if (!_listsConnected(sortableElement, dragging.parentElement)) {
924 | return;
925 | }
926 | e.preventDefault();
927 | e.stopPropagation();
928 | addData(dragging, 'dropped', 'true');
929 | // get the one placeholder that is currently visible
930 | var visiblePlaceholder = Array.from(stores.values()).map(function (data) {
931 | return data.placeholder;
932 | })
933 | .filter(function (placeholder) { return placeholder instanceof HTMLElement; })
934 | .filter(isInDom)[0];
935 | // attach element after placeholder
936 | insertAfter(visiblePlaceholder, dragging);
937 | // remove placeholder from dom
938 | visiblePlaceholder.remove();
939 | /*
940 | * Fires Custom Event - 'sortstop'
941 | */
942 | sortableElement.dispatchEvent(new CustomEvent('sortstop', {
943 | detail: {
944 | origin: {
945 | elementIndex: originElementIndex,
946 | index: originIndex,
947 | container: originContainer
948 | },
949 | item: dragging
950 | }
951 | }));
952 | var placeholder = store(sortableElement).placeholder;
953 | var originItems = _filter(originContainer.children, options.items)
954 | .filter(function (item) { return item !== placeholder; });
955 | var destinationContainer = this.isSortable === true ? this : this.parentElement;
956 | var destinationItems = _filter(destinationContainer.children, addData(destinationContainer, 'items'))
957 | .filter(function (item) { return item !== placeholder; });
958 | var destinationElementIndex = index(dragging, Array.from(dragging.parentElement.children)
959 | .filter(function (item) { return item !== placeholder; }));
960 | var destinationIndex = index(dragging, destinationItems);
961 | /*
962 | * When a list item changed container lists or index within a list
963 | * Fires Custom Event - 'sortupdate'
964 | */
965 | if (originElementIndex !== destinationElementIndex || originContainer !== destinationContainer) {
966 | sortableElement.dispatchEvent(new CustomEvent('sortupdate', {
967 | detail: {
968 | origin: {
969 | elementIndex: originElementIndex,
970 | index: originIndex,
971 | container: originContainer,
972 | itemsBeforeUpdate: originItemsBeforeUpdate,
973 | items: originItems
974 | },
975 | destination: {
976 | index: destinationIndex,
977 | elementIndex: destinationElementIndex,
978 | container: destinationContainer,
979 | itemsBeforeUpdate: destinationItemsBeforeUpdate,
980 | items: destinationItems
981 | },
982 | item: dragging
983 | }
984 | }));
985 | }
986 | });
987 | var debouncedDragOverEnter = _debounce(function (sortableElement, element, pageY) {
988 | if (!dragging) {
989 | return;
990 | }
991 | // set placeholder height if forcePlaceholderSize option is set
992 | if (options.forcePlaceholderSize) {
993 | store(sortableElement).placeholder.style.height = draggingHeight + 'px';
994 | }
995 | // if element the draggedItem is dragged onto is within the array of all elements in list
996 | // (not only items, but also disabled, etc.)
997 | if (Array.from(sortableElement.children).indexOf(element) > -1) {
998 | var thisHeight = _getElementHeight(element);
999 | var placeholderIndex = index(store(sortableElement).placeholder, element.parentElement.children);
1000 | var thisIndex = index(element, element.parentElement.children);
1001 | // Check if `element` is bigger than the draggable. If it is, we have to define a dead zone to prevent flickering
1002 | if (thisHeight > draggingHeight) {
1003 | // Dead zone?
1004 | var deadZone = thisHeight - draggingHeight;
1005 | var offsetTop = offset(element).top;
1006 | if (placeholderIndex < thisIndex && pageY < offsetTop) {
1007 | return;
1008 | }
1009 | if (placeholderIndex > thisIndex &&
1010 | pageY > offsetTop + thisHeight - deadZone) {
1011 | return;
1012 | }
1013 | }
1014 | if (dragging.oldDisplay === undefined) {
1015 | dragging.oldDisplay = dragging.style.display;
1016 | }
1017 | if (dragging.style.display !== 'none') {
1018 | dragging.style.display = 'none';
1019 | }
1020 | // To avoid flicker, determine where to position the placeholder
1021 | // based on where the mouse pointer is relative to the elements
1022 | // vertical center.
1023 | var placeAfter = false;
1024 | try {
1025 | var elementMiddle = offset(element).top + element.offsetHeight / 2;
1026 | placeAfter = pageY >= elementMiddle;
1027 | }
1028 | catch (e) {
1029 | placeAfter = placeholderIndex < thisIndex;
1030 | }
1031 | if (placeAfter) {
1032 | insertAfter(element, store(sortableElement).placeholder);
1033 | }
1034 | else {
1035 | insertBefore(element, store(sortableElement).placeholder);
1036 | }
1037 | // get placeholders from all stores & remove all but current one
1038 | Array.from(stores.values())
1039 | .filter(function (data) { return data.placeholder !== undefined; })
1040 | .forEach(function (data) {
1041 | if (data.placeholder !== store(sortableElement).placeholder) {
1042 | data.placeholder.remove();
1043 | }
1044 | });
1045 | }
1046 | else {
1047 | // get all placeholders from store
1048 | var placeholders = Array.from(stores.values())
1049 | .filter(function (data) { return data.placeholder !== undefined; })
1050 | .map(function (data) {
1051 | return data.placeholder;
1052 | });
1053 | // check if element is not in placeholders
1054 | if (placeholders.indexOf(element) === -1 && sortableElement === element && !_filter(element.children, options.items).length) {
1055 | placeholders.forEach(function (element) { return element.remove(); });
1056 | element.appendChild(store(sortableElement).placeholder);
1057 | }
1058 | }
1059 | }, options.debounce);
1060 | // Handle dragover and dragenter events on draggable items
1061 | var onDragOverEnter = function (e) {
1062 | var element = e.target;
1063 | var sortableElement = element.isSortable === true ? element : findSortable(element);
1064 | element = findDragElement(sortableElement, element);
1065 | if (!dragging || !_listsConnected(sortableElement, dragging.parentElement) || addData(sortableElement, '_disabled') === 'true') {
1066 | return;
1067 | }
1068 | var options = addData(sortableElement, 'opts');
1069 | if (parseInt(options.maxItems) && _filter(sortableElement.children, addData(sortableElement, 'items')).length >= parseInt(options.maxItems) && dragging.parentElement !== sortableElement) {
1070 | return;
1071 | }
1072 | e.preventDefault();
1073 | e.stopPropagation();
1074 | e.dataTransfer.dropEffect = store(sortableElement).getConfig('copy') === true ? 'copy' : 'move';
1075 | debouncedDragOverEnter(sortableElement, element, e.pageY);
1076 | };
1077 | addEventListener(listItems.concat(sortableElement), 'dragover', onDragOverEnter);
1078 | addEventListener(listItems.concat(sortableElement), 'dragenter', onDragOverEnter);
1079 | });
1080 | return sortableElements;
1081 | }
1082 | sortable.destroy = function (sortableElement) {
1083 | _destroySortable(sortableElement);
1084 | };
1085 | sortable.enable = function (sortableElement) {
1086 | _enableSortable(sortableElement);
1087 | };
1088 | sortable.disable = function (sortableElement) {
1089 | _disableSortable(sortableElement);
1090 | };
1091 |
1092 | return sortable;
1093 |
1094 | }());
--------------------------------------------------------------------------------
/src/assets/js/html5sortable.min.js:
--------------------------------------------------------------------------------
1 | var sortable=function(){"use strict";function d(e,t,n){if(void 0===n)return e&&e.h5s&&e.h5s.data&&e.h5s.data[t];e.h5s=e.h5s||{},e.h5s.data=e.h5s.data||{},e.h5s.data[t]=n}function u(e,t){if(!(e instanceof NodeList||e instanceof HTMLCollection||e instanceof Array))throw new Error("You must provide a nodeList/HTMLCollection/Array of elements to be filtered.");return"string"!=typeof t?Array.from(e):Array.from(e).filter(function(e){return 1===e.nodeType&&e.matches(t)})}var p=new Map,t=function(){function e(){this._config=new Map,this._placeholder=void 0,this._data=new Map}return Object.defineProperty(e.prototype,"config",{get:function(){var n={};return this._config.forEach(function(e,t){n[t]=e}),n},set:function(e){if("object"!=typeof e)throw new Error("You must provide a valid configuration object to the config setter.");var t=Object.assign({},e);this._config=new Map(Object.entries(t))},enumerable:!0,configurable:!0}),e.prototype.setConfig=function(e,t){if(!this._config.has(e))throw new Error("Trying to set invalid configuration item: "+e);this._config.set(e,t)},e.prototype.getConfig=function(e){if(!this._config.has(e))throw new Error("Invalid configuration item requested: "+e);return this._config.get(e)},Object.defineProperty(e.prototype,"placeholder",{get:function(){return this._placeholder},set:function(e){if(!(e instanceof HTMLElement)&&null!==e)throw new Error("A placeholder must be an html element or null.");this._placeholder=e},enumerable:!0,configurable:!0}),e.prototype.setData=function(e,t){if("string"!=typeof e)throw new Error("The key must be a string.");this._data.set(e,t)},e.prototype.getData=function(e){if("string"!=typeof e)throw new Error("The key must be a string.");return this._data.get(e)},e.prototype.deleteData=function(e){if("string"!=typeof e)throw new Error("The key must be a string.");return this._data.delete(e)},e}();function m(e){if(!(e instanceof HTMLElement))throw new Error("Please provide a sortable to the store function.");return p.has(e)||p.set(e,new t),p.get(e)}function g(e,t,n){if(e instanceof Array)for(var r=0;r':t=document.createElement("div")),"string"==typeof n&&(r=t.classList).add.apply(r,n.split(" ")),t;var r}(s,e,f.placeholderClass),d(s,"items",f.items),f.acceptFrom?d(s,"acceptFrom",f.acceptFrom):f.connectWith&&d(s,"connectWith",f.connectWith),j(s),h(t,"role","option"),h(t,"aria-grabbed","false"),_(s,!0),g(s,"dragstart",function(e){if(!0!==e.target.isSortable&&(e.stopImmediatePropagation(),(!f.handle||e.target.matches(f.handle))&&"false"!==e.target.getAttribute("draggable"))){var t=F(e.target),n=W(t,e.target);I=u(t.children,f.items),A=I.indexOf(n),H=y(n,t.children),D=t,function(e,t,n){if(!(e instanceof Event))throw new Error("setDragImage requires a DragEvent as the first argument.");if(!(t instanceof HTMLElement))throw new Error("setDragImage requires the dragged element as the second argument.");if(n||(n=C),e.dataTransfer&&e.dataTransfer.setDragImage){var r=n(t,v(t),e);if(!(r.element instanceof HTMLElement)||"number"!=typeof r.posX||"number"!=typeof r.posY)throw new Error("The customDragImage function you provided must return and object with the properties element[string], posX[integer], posY[integer].");e.dataTransfer.effectAllowed="copyMove",e.dataTransfer.setData("text/plain","arbitrary"),e.dataTransfer.setDragImage(r.element,r.posX,r.posY)}}(e,n,f.customDragImage),x=T(n),n.classList.add(f.draggingClass),h(M=O(n,t),"aria-grabbed","true"),t.dispatchEvent(new CustomEvent("sortstart",{detail:{origin:{elementIndex:H,index:A,container:D},item:M}}))}}),g(s,"dragenter",function(e){if(!0!==e.target.isSortable){var t=F(e.target);S=u(t.children,d(t,"items")).filter(function(e){return e!==m(s).placeholder})}}),g(s,"dragend",function(e){if(M){M.classList.remove(f.draggingClass),h(M,"aria-grabbed","false"),"true"===M.getAttribute("aria-copied")&&"true"!==d(M,"dropped")&&M.remove(),M.style.display=M.oldDisplay,delete M.oldDisplay;var t=Array.from(p.values()).map(function(e){return e.placeholder}).filter(function(e){return e instanceof HTMLElement}).filter(E)[0];t&&t.remove(),s.dispatchEvent(new CustomEvent("sortstop",{detail:{origin:{elementIndex:H,index:A,container:D},item:M}})),x=M=null}}),g(s,"drop",function(e){if(L(s,M.parentElement)){e.preventDefault(),e.stopPropagation(),d(M,"dropped","true");var t=Array.from(p.values()).map(function(e){return e.placeholder}).filter(function(e){return e instanceof HTMLElement}).filter(E)[0];w(t,M),t.remove(),s.dispatchEvent(new CustomEvent("sortstop",{detail:{origin:{elementIndex:H,index:A,container:D},item:M}}));var n=m(s).placeholder,r=u(D.children,f.items).filter(function(e){return e!==n}),o=!0===this.isSortable?this:this.parentElement,i=u(o.children,d(o,"items")).filter(function(e){return e!==n}),a=y(M,Array.from(M.parentElement.children).filter(function(e){return e!==n})),l=y(M,i);H===a&&D===o||s.dispatchEvent(new CustomEvent("sortupdate",{detail:{origin:{elementIndex:H,index:A,container:D,itemsBeforeUpdate:I,items:r},destination:{index:l,elementIndex:a,container:o,itemsBeforeUpdate:S,items:i},item:M}}))}});var r,o,i,a=(r=function(t,e,n){if(M)if(f.forcePlaceholderSize&&(m(t).placeholder.style.height=x+"px"),-1=parseInt(r.maxItems)&&M.parentElement!==n||(e.preventDefault(),e.stopPropagation(),e.dataTransfer.dropEffect=!0===m(n).getConfig("copy")?"copy":"move",a(n,t,e.pageY))}};g(t.concat(s),"dragover",l),g(t.concat(s),"dragenter",l)}),e)}return N.destroy=function(e){r(e)},N.enable=function(e){j(e)},N.disable=function(e){var t,n,r;n=d(t=e,"opts"),r=c(u(t.children,n.items),n.handle),h(t,"aria-dropeffect","none"),d(t,"_disabled","true"),h(r,"draggable","false"),l(r,"mousedown")},N}();
--------------------------------------------------------------------------------
/src/assets/js/kv-html5-sortable.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2018
3 | * @package yii2-sortable
4 | * @version 1.2.2
5 | *
6 | * jQuery Plugin Wrapper for HTML5 Sortable
7 | *
8 | * Author: Kartik Visweswaran
9 | * Licensed under the BSD 3-Clause
10 | */
11 | (function (factory) {
12 | "use strict";
13 | //noinspection JSUnresolvedVariable
14 | if (typeof define === 'function' && define.amd) { // jshint ignore:line
15 | // AMD. Register as an anonymous module.
16 | define(['jquery'], factory); // jshint ignore:line
17 | } else { // noinspection JSUnresolvedVariable
18 | if (typeof module === 'object' && module.exports) { // jshint ignore:line
19 | // Node/CommonJS
20 | // noinspection JSUnresolvedVariable
21 | module.exports = factory(require('jquery')); // jshint ignore:line
22 | } else {
23 | // Browser globals
24 | factory(window.jQuery);
25 | }
26 | }
27 | }(function ($) {
28 | "use strict";
29 | var KvHtml5Sortable;
30 |
31 | KvHtml5Sortable = function (element, options) {
32 | var self = this;
33 | self.$element = $(element);
34 | self._init(options);
35 | };
36 |
37 | KvHtml5Sortable.prototype = {
38 | constructor: KvHtml5Sortable,
39 | _init: function (options) {
40 | var self = this, f, $el = self.$element, sLib = window['sortable'],
41 | methods = ['destroy', 'disable', 'enable', 'serialize', 'reload'];
42 | self.options = options;
43 | $.each(options, function (opt, value) {
44 | self[opt] = value;
45 | });
46 | if (!sLib) {
47 | throw "Sortable Library not initialized!";
48 | return;
49 | }
50 | self.id = '#' + $el.attr('id');
51 | sLib(self.id, self.options);
52 | $.each(methods, function(key, method) {
53 | self[method] = function() {
54 | sLib(self.id, method);
55 | };
56 | });
57 | }
58 | };
59 |
60 | $.fn.kvHtml5Sortable = function (option) {
61 | var args = Array.apply(null, arguments), retvals = [];
62 | args.shift();
63 | this.each(function () {
64 | var self = $(this), data = self.data('kvHtml5Sortable'), options = typeof option === 'object' && option, opt;
65 | if (!data) {
66 | opt = $.extend(true, {}, $.fn.kvHtml5Sortable.defaults, options, self.data());
67 | data = new KvHtml5Sortable(this, opt);
68 | self.data('kvHtml5Sortable', data);
69 | }
70 | if (typeof option === 'string') {
71 | retvals.push(data[option].apply(data, args));
72 | }
73 | });
74 | switch (retvals.length) {
75 | case 0:
76 | return this;
77 | case 1:
78 | return retvals[0];
79 | default:
80 | return retvals;
81 | }
82 | };
83 | $.fn.kvHtml5Sortable.defaults = {};
84 | $.fn.kvHtml5Sortable.Constructor = KvHtml5Sortable;
85 | }));
--------------------------------------------------------------------------------
/src/assets/js/kv-html5-sortable.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2018
3 | * @package yii2-sortable
4 | * @version 1.2.2
5 | *
6 | * jQuery Plugin Wrapper for HTML5 Sortable
7 | *
8 | * Author: Kartik Visweswaran
9 | * Licensed under the BSD 3-Clause
10 | */!function(t){"use strict";"function"==typeof define&&define.amd?define(["jquery"],t):"object"==typeof module&&module.exports?module.exports=t(require("jquery")):t(window.jQuery)}(function(t){"use strict";var e;(e=function(e,n){this.$element=t(e),this._init(n)}).prototype={constructor:e,_init:function(e){var n=this,i=n.$element,o=window.sortable;if(n.options=e,t.each(e,function(t,e){n[t]=e}),!o)throw"Sortable Library not initialized!";n.id="#"+i.attr("id"),o(n.id,n.options),t.each(["destroy","disable","enable","serialize","reload"],function(t,e){n[e]=function(){o(n.id,e)}})}},t.fn.kvHtml5Sortable=function(n){var i=Array.apply(null,arguments),o=[];switch(i.shift(),this.each(function(){var r,a=t(this),l=a.data("kvHtml5Sortable"),s="object"==typeof n&&n;l||(r=t.extend(!0,{},t.fn.kvHtml5Sortable.defaults,s,a.data()),l=new e(this,r),a.data("kvHtml5Sortable",l)),"string"==typeof n&&o.push(l[n].apply(l,i))}),o.length){case 0:return this;case 1:return o[0];default:return o}},t.fn.kvHtml5Sortable.defaults={},t.fn.kvHtml5Sortable.Constructor=e});
--------------------------------------------------------------------------------