├── nestable
├── NestableAsset.php
├── NestableBehavior.php
├── NodeMoveAction.php
└── Nestable.php
├── composer.json
├── LICENSE
├── README.md
└── assets
├── css
├── nestable.min.css
└── nestable.css
└── js
├── jquery.nestable.min.js
└── jquery.nestable.js
/nestable/NestableAsset.php:
--------------------------------------------------------------------------------
1 |
15 | * @since 1.0
16 | */
17 | class NestableAsset extends \kartik\base\AssetBundle {
18 |
19 | public function init() {
20 | $this->setSourcePath(__DIR__ . '/../assets');
21 | $this->setupAssets('js', ['js/jquery.nestable']);
22 | $this->setupAssets('css', ['css/nestable']);
23 | parent::init();
24 | }
25 |
26 | }
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "slatiusa/yii2-nestable",
3 | "description": "Yii 2.0 implementation of nested set behavior using jquery.nestable plugin.",
4 | "keywords": ["yii2", "extension", "widget", "nestable", "sortable", "nested set", "behavior", "jquery"],
5 | "homepage": "https://github.com/Hommer101/yii2-nestable",
6 | "type": "yii2-extension",
7 | "license": "BSD 3-Clause",
8 | "authors": [
9 | {
10 | "name": "Arno Slatius",
11 | "email": "a.slatius@gmail.com"
12 | }
13 | ],
14 | "require": {
15 | "kartik-v/yii2-krajee-base": "*",
16 | "creocoder/yii2-nested-sets": "*"
17 | },
18 | "autoload": {
19 | "psr-4": {
20 | "slatiusa\\": ""
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/nestable/NestableBehavior.php:
--------------------------------------------------------------------------------
1 |
18 | * @since 1.0
19 | */
20 | class NestableBehavior extends NestedSetsBehavior
21 | {
22 | /**
23 | * Wrapper function to be able to use the protected method of the NestedSetsBehavior
24 | *
25 | * @param integer $value
26 | * @param integer $depth
27 | */
28 | public function nodeMove($value, $depth) {
29 | $this->node = $this->owner;
30 | parent::moveNode($value, $depth);
31 | }
32 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014, iksnimak
2 | Copyright (c) 2015, slatiusa
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, 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,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | * Neither the name of the {organization} 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"
20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | yii2-nestable
2 | =============
3 |
4 | Yii 2.0 implementation of nested set behavior using jquery.nestable plugin.
5 | - jquery.nestable plugin: http://dbushell.github.io/Nestable/
6 | - Nested Sets Behavior for Yii 2: https://github.com/creocoder/yii2-nested-sets
7 |
8 | ## Installation
9 |
10 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/).
11 |
12 | Either run
13 |
14 | ```
15 | $ php composer.phar require slatiusa/yii2-nestable "dev-master"
16 | ```
17 |
18 | or add
19 |
20 | ```
21 | "slatiusa/yii2-nestable": "dev-master"
22 | ```
23 |
24 | to the ```require``` section of your `composer.json` file.
25 |
26 | ## Usage
27 |
28 | Make sure you've attached the NestedSetsBehavior (creocoder/yii2-nested-sets) correctly to your model.
29 | Then add the node move handler to you controller by attaching the supplied action;
30 |
31 | ```
32 | use slatiusa\nestable\Nestable;
33 |
34 | class yourClass extends Controller
35 | {
36 | public function actions() {
37 | return [
38 | 'nodeMove' => [
39 | 'class' => 'slatiusa\nestable\NodeMoveAction',
40 | 'modelName' => TreeModel::className(),
41 | ],
42 | ];
43 | }
44 |
45 | ```
46 |
47 | And then render the widget in your view;
48 |
49 | ```
50 | echo Nestable::widget([
51 | 'type' => Nestable::TYPE_WITH_HANDLE,
52 | 'query' => TreeModel::find()->where([ top of tree ]),
53 | 'modelOptions' => [
54 | 'name' => 'name'
55 | ],
56 | 'pluginEvents' => [
57 | 'change' => 'function(e) {}',
58 | ],
59 | 'pluginOptions' => [
60 | 'maxDepth' => 7,
61 | ],
62 | ]);
63 |
64 | ```
65 |
66 | You can either supply an ActiveQuery object in `query` from which a tree will be built.
67 | You can also supply an item list;
68 | ```
69 | ...
70 | 'items' => [
71 | ['content' => 'Item # 1', 'id' => 1],
72 | ['content' => 'Item # 2', 'id' => 2],
73 | ['content' => 'Item # 3', 'id' => 3],
74 | ['content' => 'Item # 4 with children', 'id' => 4, 'children' => [
75 | ['content' => 'Item # 4.1', 'id' => 5],
76 | ['content' => 'Item # 4.2', 'id' => 6],
77 | ['content' => 'Item # 4.3', 'id' => 7],
78 | ]],
79 | ],
80 | ```
81 |
82 | The `modelOptions['name']` should hold an attribute name that will be used to name on the items in the list.
83 | You can alternatively supply an unnamed `function($model)` to build your own content string.
84 |
85 | Supply a `pluginEvents['change']` with some JavaScript code to catch the change event fired by jquery.nestable plugin.
86 | The `pluginOptions` accepts all the options for the original jquery.nestable plugin.
--------------------------------------------------------------------------------
/assets/css/nestable.min.css:
--------------------------------------------------------------------------------
1 | .dd,.dd-list{display:block;padding:0;list-style:none}.dd,.dd-empty,.dd-item,.dd-placeholder{margin:0;font-size:13px;line-height:20px;position:relative}.dd,.dd-item>button,.dd-list{position:relative}.dd-handle,.dd-item>button{font-weight:700;margin:5px 0}.dd-item>button,.dd3-handle{cursor:pointer;white-space:nowrap;overflow:hidden}.dd-list{margin:0}.dd-list .dd-list{padding-left:30px}.dd-collapsed .dd-list{display:none}.dd-empty,.dd-item,.dd-placeholder{display:block;padding:0;min-height:20px}.dd-handle{display:block;height:30px;padding:5px 10px;color:#333;text-decoration:none;border:1px solid #ccc;background:#fafafa;background:-webkit-linear-gradient(top,#fafafa 0,#eee 100%);background:-moz-linear-gradient(top,#fafafa 0,#eee 100%);background:linear-gradient(top,#fafafa 0,#eee 100%);-webkit-border-radius:3px;border-radius:3px;box-sizing:border-box;-moz-box-sizing:border-box}.dd-handle:hover{color:#2ea8e5;background:#fff}.dd-item>button{display:block;float:left;width:25px;height:20px;padding:0;text-indent:100%;border:0;background:0 0;font-size:12px;line-height:1;text-align:center}.dd-item>button:before{content:'+';display:block;position:absolute;width:100%;text-align:center;text-indent:0}.dd-item>button[data-action=collapse]:before{content:'-'}.dd-empty,.dd-placeholder{margin:5px 0;padding:0;min-height:30px;background:#f2fbff;border:1px dashed #b6bcbf;box-sizing:border-box;-moz-box-sizing:border-box}.dd-empty{border:1px dashed #bbb;min-height:100px;background-color:#e5e5e5;background-image:-webkit-linear-gradient(45deg,#fff 25%,transparent 25%,transparent 75%,#fff 75%,#fff),-webkit-linear-gradient(45deg,#fff 25%,transparent 25%,transparent 75%,#fff 75%,#fff);background-image:-moz-linear-gradient(45deg,#fff 25%,transparent 25%,transparent 75%,#fff 75%,#fff),-moz-linear-gradient(45deg,#fff 25%,transparent 25%,transparent 75%,#fff 75%,#fff);background-image:linear-gradient(45deg,#fff 25%,transparent 25%,transparent 75%,#fff 75%,#fff),linear-gradient(45deg,#fff 25%,transparent 25%,transparent 75%,#fff 75%,#fff);background-size:60px 60px;background-position:0 0,30px 30px}.dd-dragel{position:absolute;pointer-events:none;z-index:9999}.dd-dragel>.dd-item .dd-handle{margin-top:0}.dd-dragel .dd-handle{-webkit-box-shadow:2px 4px 6px 0 rgba(0,0,0,.1);box-shadow:2px 4px 6px 0 rgba(0,0,0,.1)}.dd-hover>.dd-handle{background:#2ea8e5!important}.dd3-content{display:block;height:30px;margin:5px 0;padding:5px 10px 5px 40px;color:#333;text-decoration:none;font-weight:700;border:1px solid #ccc;background:#fafafa;background:-webkit-linear-gradient(top,#fafafa 0,#eee 100%);background:-moz-linear-gradient(top,#fafafa 0,#eee 100%);background:linear-gradient(top,#fafafa 0,#eee 100%);-webkit-border-radius:3px;border-radius:3px;box-sizing:border-box;-moz-box-sizing:border-box}.dd3-content:hover{color:#2ea8e5;background:#fff}.dd-dragel>.dd3-item>.dd3-content{margin:0}.dd3-item>button{margin-left:30px}.dd3-handle{position:absolute;margin:0;left:0;top:0;width:30px;text-indent:100%;border:1px solid #aaa;background:#ddd;background:-webkit-linear-gradient(top,#ddd 0,#bbb 100%);background:-moz-linear-gradient(top,#ddd 0,#bbb 100%);background:linear-gradient(top,#ddd 0,#bbb 100%);border-top-right-radius:0;border-bottom-right-radius:0}.dd3-handle:before{content:'≡';display:block;position:absolute;left:0;top:3px;width:100%;text-align:center;text-indent:0;color:#fff;font-size:20px;font-weight:400}.dd3-handle:hover{background:#ddd}
--------------------------------------------------------------------------------
/nestable/NodeMoveAction.php:
--------------------------------------------------------------------------------
1 |
22 | * @since 1.0
23 | */
24 | class NodeMoveAction extends Action
25 | {
26 | /** @var string class to use to locate the supplied data ids */
27 | public $modelName;
28 |
29 | /** @var bool variable to support editing without possibility of creating a root elements */
30 | public $rootable = true;
31 |
32 | /** @vars string the attribute names of the model that hold these attributes */
33 | private $leftAttribute;
34 | private $rightAttribute;
35 | private $treeAttribute;
36 | private $depthAttribute;
37 |
38 | /**
39 | * Move a node (model) below the parent and in between left and right
40 | *
41 | * @param integer $id the primaryKey of the moved node
42 | * @param integer $lft the primaryKey of the node left of the moved node
43 | * @param integer $rgt the primaryKey of the node right to the moved node
44 | * @param integer $par the primaryKey of the parent of the moved node
45 | */
46 | public function run($id=0, $lft=0, $rgt=0, $par=0)
47 | {
48 | if (null == $this->modelName) {
49 | throw new \yii\base\InvalidConfigException("No 'modelName' supplied on action initialization.");
50 | }
51 |
52 | /* response will be in JSON format */
53 | Yii::$app->response->format = 'json';
54 |
55 | /* Locate the supplied model, left, right and parent models */
56 | $model = Yii::createObject(ActiveQuery::className(), [$this->modelName])->where(['id' => $id])->one();
57 | $lft = Yii::createObject(ActiveQuery::className(), [$this->modelName])->where(['id' => $lft])->one();
58 | $rgt = Yii::createObject(ActiveQuery::className(), [$this->modelName])->where(['id' => $rgt])->one();
59 | $par = Yii::createObject(ActiveQuery::className(), [$this->modelName])->where(['id' => $par])->one();
60 |
61 | /* Get attribute names from model behaviour config */
62 | foreach($model->behaviors as $behavior) {
63 | if ($behavior instanceof NestedSetsBehavior) {
64 | $this->leftAttribute = $behavior->leftAttribute;
65 | $this->rightAttribute = $behavior->rightAttribute;
66 | $this->treeAttribute = $behavior->treeAttribute;
67 | $this->depthAttribute = $behavior->depthAttribute;
68 | break;
69 | }
70 | }
71 |
72 | /* attach our bahaviour to be able to call the moveNode() function of the NestedSetsBehavior */
73 | $model->attachBehavior('nestable', [
74 | 'class' => \slatiusa\nestable\NestableBehavior::className(),
75 | 'leftAttribute' => $this->leftAttribute,
76 | 'rightAttribute' => $this->rightAttribute,
77 | 'treeAttribute' => $this->treeAttribute,
78 | 'depthAttribute' => $this->depthAttribute,
79 | ]);
80 |
81 | /* Root/Append/Left/Right change */
82 | if($this->rootable&&$this->treeAttribute&&is_null($par)&&!$model->isRoot()){
83 | $model->makeRoot();
84 | } else if(is_null($par)){
85 | if(!is_null($rgt))
86 | $model->insertBefore($rgt);
87 | else if(!is_null($lft))
88 | $model->insertAfter($lft);
89 | }else{
90 | if(!is_null($rgt))
91 | $model->insertBefore($rgt);
92 | else if(!is_null($lft))
93 | $model->insertAfter($lft);
94 | else
95 | $model->appendTo($par);
96 | }
97 |
98 | /* report new position */
99 | return ['updated' => [
100 | 'id' => $model->id,
101 | 'depth' => $model->{$this->depthAttribute},
102 | 'lft' => $model->{$this->leftAttribute},
103 | 'rgt' => $model->{$this->rightAttribute},
104 | ]];
105 | }
106 |
107 | }
108 |
--------------------------------------------------------------------------------
/assets/css/nestable.css:
--------------------------------------------------------------------------------
1 | /**
2 | * Nestable
3 | */
4 | .dd {
5 | position: relative;
6 | display: block;
7 | margin: 0;
8 | padding: 0;
9 | list-style: none;
10 | font-size: 13px;
11 | line-height: 20px;
12 | }
13 | .dd-list {
14 | display: block;
15 | position: relative;
16 | margin: 0;
17 | padding: 0;
18 | list-style: none;
19 | }
20 | .dd-list .dd-list {
21 | padding-left: 30px;
22 | }
23 | .dd-collapsed .dd-list {
24 | display: none;
25 | }
26 | .dd-item,
27 | .dd-empty,
28 | .dd-placeholder {
29 | display: block;
30 | position: relative;
31 | margin: 0;
32 | padding: 0;
33 | min-height: 20px;
34 | font-size: 13px;
35 | line-height: 20px;
36 | }
37 | .dd-handle {
38 | display: block;
39 | height: 30px;
40 | margin: 5px 0;
41 | padding: 5px 10px;
42 | color: #333;
43 | text-decoration: none;
44 | font-weight: bold;
45 | border: 1px solid #ccc;
46 | background: #fafafa;
47 | background: -webkit-linear-gradient(top, #fafafa 0%, #eee 100%);
48 | background: -moz-linear-gradient(top, #fafafa 0%, #eee 100%);
49 | background: linear-gradient(top, #fafafa 0%, #eee 100%);
50 | -webkit-border-radius: 3px;
51 | border-radius: 3px;
52 | box-sizing: border-box;
53 | -moz-box-sizing: border-box;
54 | }
55 | .dd-handle:hover {
56 | color: #2ea8e5;
57 | background: #fff;
58 | }
59 | .dd-item > button {
60 | display: block;
61 | position: relative;
62 | cursor: pointer;
63 | float: left;
64 | width: 25px;
65 | height: 20px;
66 | margin: 5px 0;
67 | padding: 0;
68 | text-indent: 100%;
69 | white-space: nowrap;
70 | overflow: hidden;
71 | border: 0;
72 | background: transparent;
73 | font-size: 12px;
74 | line-height: 1;
75 | text-align: center;
76 | font-weight: bold;
77 | }
78 | .dd-item > button:before {
79 | content: '+';
80 | display: block;
81 | position: absolute;
82 | width: 100%;
83 | text-align: center;
84 | text-indent: 0;
85 | }
86 | .dd-item > button[data-action="collapse"]:before {
87 | content: '-';
88 | }
89 | .dd-placeholder,
90 | .dd-empty {
91 | margin: 5px 0;
92 | padding: 0;
93 | min-height: 30px;
94 | background: #f2fbff;
95 | border: 1px dashed #b6bcbf;
96 | box-sizing: border-box;
97 | -moz-box-sizing: border-box;
98 | }
99 | .dd-empty {
100 | border: 1px dashed #bbb;
101 | min-height: 100px;
102 | background-color: #e5e5e5;
103 | background-image: -webkit-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff), -webkit-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);
104 | background-image: -moz-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff), -moz-linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);
105 | background-image: linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff), linear-gradient(45deg, #fff 25%, transparent 25%, transparent 75%, #fff 75%, #fff);
106 | background-size: 60px 60px;
107 | background-position: 0 0, 30px 30px;
108 | }
109 | .dd-dragel {
110 | position: absolute;
111 | pointer-events: none;
112 | z-index: 9999;
113 | }
114 | .dd-dragel > .dd-item .dd-handle {
115 | margin-top: 0;
116 | }
117 | .dd-dragel .dd-handle {
118 | -webkit-box-shadow: 2px 4px 6px 0 rgba(0, 0, 0, .1);
119 | box-shadow: 2px 4px 6px 0 rgba(0, 0, 0, .1);
120 | }
121 | .dd-hover > .dd-handle {
122 | background: #2ea8e5 !important;
123 | }
124 |
125 | /**
126 | * Nestable Draggable Handles
127 | */
128 | .dd3-content {
129 | display: block;
130 | height: 30px;
131 | margin: 5px 0;
132 | padding: 5px 10px 5px 40px;
133 | color: #333;
134 | text-decoration: none;
135 | font-weight: bold;
136 | border: 1px solid #ccc;
137 | background: #fafafa;
138 | background: -webkit-linear-gradient(top, #fafafa 0%, #eee 100%);
139 | background: -moz-linear-gradient(top, #fafafa 0%, #eee 100%);
140 | background: linear-gradient(top, #fafafa 0%, #eee 100%);
141 | -webkit-border-radius: 3px;
142 | border-radius: 3px;
143 | box-sizing: border-box;
144 | -moz-box-sizing: border-box;
145 | }
146 | .dd3-content:hover {
147 | color: #2ea8e5;
148 | background: #fff;
149 | }
150 | .dd-dragel > .dd3-item > .dd3-content {
151 | margin: 0;
152 | }
153 | .dd3-item > button {
154 | margin-left: 30px;
155 | }
156 | .dd3-handle {
157 | position: absolute;
158 | margin: 0;
159 | left: 0;
160 | top: 0;
161 | cursor: pointer;
162 | width: 30px;
163 | text-indent: 100%;
164 | white-space: nowrap;
165 | overflow: hidden;
166 | border: 1px solid #aaa;
167 | background: #ddd;
168 | background: -webkit-linear-gradient(top, #ddd 0%, #bbb 100%);
169 | background: -moz-linear-gradient(top, #ddd 0%, #bbb 100%);
170 | background: linear-gradient(top, #ddd 0%, #bbb 100%);
171 | border-top-right-radius: 0;
172 | border-bottom-right-radius: 0;
173 | }
174 | .dd3-handle:before {
175 | content: '≡';
176 | display: block;
177 | position: absolute;
178 | left: 0;
179 | top: 3px;
180 | width: 100%;
181 | text-align: center;
182 | text-indent: 0;
183 | color: #fff;
184 | font-size: 20px;
185 | font-weight: normal;
186 | }
187 | .dd3-handle:hover {
188 | background: #ddd;
189 | }
--------------------------------------------------------------------------------
/nestable/Nestable.php:
--------------------------------------------------------------------------------
1 |
20 | * @since 1.0
21 | */
22 | class Nestable extends \kartik\base\Widget
23 | {
24 | const TYPE_LIST = 'list';
25 | const TYPE_WITH_HANDLE = 'list-handle';
26 |
27 | /**
28 | * @var string the type of the sortable widget
29 | * Defaults to Nestable::TYPE_WITH_HANDLE
30 | */
31 | public $type = self::TYPE_WITH_HANDLE;
32 |
33 | /**
34 | * @var string, the handle label, this is not HTML encoded
35 | */
36 | public $handleLabel = '
';
37 |
38 | /**
39 | * @var array the HTML attributes to be applied to list.
40 | * This will be overridden by the [[options]] property within [[$items]].
41 | */
42 | public $listOptions = [];
43 |
44 | /**
45 | * @var array the HTML attributes to be applied to all items.
46 | * This will be overridden by the [[options]] property within [[$items]].
47 | */
48 | public $itemOptions = [];
49 |
50 | /**
51 | * @var array the sortable items configuration for rendering elements within the sortable
52 | * list / grid. You can set the following properties:
53 | * - id: integer, the id of the item. This will get returned on change
54 | * - content: string, the list item content (this is not HTML encoded)
55 | * - disabled: bool, whether the list item is disabled
56 | * - options: array, the HTML attributes for the list item.
57 | * - contentOptions: array, the HTML attributes for the content
58 | * - children: array, with item children
59 | */
60 | public $items = [];
61 |
62 | /**
63 | * @var string the URL to send the callback to. Defaults to current controller / actionNodeMove which
64 | * can be provided by \slatiusa\nestable\nestableNodeMoveAction by registering that as an action in the
65 | * controller rendering the Widget.
66 | * ```
67 | * public function actions() {
68 | * return [
69 | * 'nodeMove' => [
70 | * 'class' => 'slatiusa\nestable\NestableNodeMoveAction',
71 | * ],
72 | * ];
73 | * }
74 | * ```
75 | * Defaults to [current controller/nodeMove] if not set.
76 | */
77 | public $url;
78 |
79 | /**
80 | * @var ActiveQuery that holds the data for the tree to show.
81 | */
82 | public $query;
83 |
84 | /**
85 | * @var array options to be used with the model on list preparation. Supporten properties:
86 | * - name: {string|function}, attribute name for the item title or unnamed function($model) that returns a
87 | * string for each item.
88 | */
89 | public $modelOptions = [];
90 |
91 | /**
92 | * Initializes the widget
93 | */
94 | public function init() {
95 | if (null != $this->url) {
96 | $this->pluginOptions['url'] = $this->url;
97 | } else {
98 | $this->pluginOptions['url'] = Url::to([$this->view->context->id.'/nodeMove']);
99 | }
100 |
101 | parent::init();
102 | $this->registerAssets();
103 |
104 | Html::addCssClass($this->options, 'dd');
105 | echo Html::beginTag('div', $this->options);
106 |
107 | if (null != $this->query) {
108 | $this->items = $this->prepareItems($this->query);
109 | }
110 | if (count($this->items) === 0) {
111 | echo Html::tag('div', '', ['class' => 'dd-empty']);
112 | }
113 | }
114 |
115 | /**
116 | * Runs the widget
117 | *
118 | * @return string|void
119 | */
120 | public function run() {
121 | if (count($this->items) > 0) {
122 | echo Html::beginTag('ol', ['class' => 'dd-list']);
123 | echo $this->renderItems();
124 | echo Html::endTag('ol');
125 | }
126 | echo Html::endTag('div');
127 | }
128 |
129 | /**
130 | * Render the list items for the sortable widget
131 | *
132 | * @return string
133 | */
134 | protected function renderItems($_items = NULL) {
135 | $_items = is_null($_items) ? $this->items : $_items;
136 | $items = '';
137 | $dataid = 0;
138 | foreach ($_items as $item) {
139 | $options = ArrayHelper::getValue($item, 'options', ['class' => 'dd-item dd3-item']);
140 | $options = ArrayHelper::merge($this->itemOptions, $options);
141 | $dataId = ArrayHelper::getValue($item, 'id', $dataid++);
142 | $options = ArrayHelper::merge($options, ['data-id' => $dataId]);
143 |
144 | $contentOptions = ArrayHelper::getValue($item, 'contentOptions', ['class' => 'dd3-content']);
145 | $content = $this->handleLabel;
146 | $content .= Html::tag('div', ArrayHelper::getValue($item, 'content', ''), $contentOptions);
147 |
148 | $children = ArrayHelper::getValue($item, 'children', []);
149 | if (!empty($children)) {
150 | // recursive rendering children items
151 | $content .= Html::beginTag('ol', ['class' => 'dd-list']);
152 | $content .= $this->renderItems($children);
153 | $content .= Html::endTag('ol');
154 | }
155 |
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 | $view = $this->getView();
166 | NestableAsset::register($view);
167 | $this->registerPlugin('nestable');
168 | }
169 |
170 | /**
171 | * @param $partial
172 | * @param $arguments
173 | */
174 | public function renderContent($partial, $arguments) {
175 | return $this->render($partial, $arguments);
176 | }
177 |
178 | /**
179 | * put your comment there...
180 | *
181 | * @param $activeQuery \yii\db\ActiveQuery
182 | * @return array
183 | */
184 | protected function prepareItems($activeQuery)
185 | {
186 | $items = [];
187 | foreach ($activeQuery->all() as $model) {
188 | $name = ArrayHelper::getValue($this->modelOptions, 'name', 'name');
189 | $items[] = [
190 | 'id' => $model->getPrimaryKey(),
191 | 'content' => (is_callable($name) ? call_user_func($name, $model) : $model->{$name}),
192 | 'children' => $this->prepareItems($model->children(1)),
193 | ];
194 | }
195 | return $items;
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/assets/js/jquery.nestable.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Nestable jQuery Plugin - Copyright (c) 2012 David Bushell - http://dbushell.com/
3 | * Modified for yii2-nestable - Copyright (c) 2015 Arno Slatius - http://slatius.nl/
4 | *
5 | * Dual-licensed under the BSD or MIT licenses
6 | */
7 | !function(t,e,s,i){function a(s,i){this.w=t(e),this.el=t(s),this.options=t.extend({},h,i),this.init()}var o="ontouchstart"in e,n=function(){var t=s.createElement("div"),i=s.documentElement;if(!("pointerEvents"in t.style))return!1;t.style.pointerEvents="auto",t.style.pointerEvents="x",i.appendChild(t);var a=e.getComputedStyle&&"auto"===e.getComputedStyle(t,"").pointerEvents;return i.removeChild(t),!!a}(),l=o?"touchstart":"mousedown",d=o?"touchmove":"mousemove",r=o?"touchend":"mouseup";eCancel=o?"touchcancel":"mouseup";var h={listNodeName:"ol",itemNodeName:"li",rootClass:"dd",listClass:"dd-list",itemClass:"dd-item",dragClass:"dd-dragel",handleClass:"dd-handle",collapsedClass:"dd-collapsed",placeClass:"dd-placeholder",noDragClass:"dd-nodrag",emptyClass:"dd-empty",expandBtnHTML:'',collapseBtnHTML:'',group:0,maxDepth:5,threshold:20,url:""};a.prototype={init:function(){var s=this;s.reset(),s.el.data("nestable-group",this.options.group),s.placeEl=t(''),t.each(this.el.find(s.options.itemNodeName),function(e,i){s.setParent(t(i))}),s.el.on("click","button",function(e){if(!s.dragEl&&(o||0===e.button)){var i=t(e.currentTarget),a=i.data("action"),n=i.parent(s.options.itemNodeName);"collapse"===a&&s.collapseItem(n),"expand"===a&&s.expandItem(n)}});var i=function(e){var i=t(e.target);if(!i.hasClass(s.options.handleClass)){if(i.closest("."+s.options.noDragClass).length)return;i=i.closest("."+s.options.handleClass)}!i.length||s.dragEl||!o&&0!==e.button||o&&1!==e.touches.length||(e.preventDefault(),s.dragStart(o?e.touches[0]:e))},a=function(t){s.dragEl&&(t.preventDefault(),s.dragMove(o?t.touches[0]:t))},n=function(t){s.dragEl&&(t.preventDefault(),s.dragStop(o?t.touches[0]:t))};o?(s.el[0].addEventListener(l,i,!1),e.addEventListener(d,a,!1),e.addEventListener(r,n,!1),e.addEventListener(eCancel,n,!1)):(s.el.on(l,i),s.w.on(d,a),s.w.on(r,n))},serialize:function(){var e,s=0,i=this;return step=function(e,s){var a=[],o=e.children(i.options.itemNodeName);return o.each(function(){var e=t(this),o=t.extend({},e.data()),n=e.children(i.options.listNodeName);n.length&&(o.children=step(n,s+1)),a.push(o)}),a},e=step(i.el.find(i.options.listNodeName).first(),s)},serialise:function(){return this.serialize()},reset:function(){this.mouse={offsetX:0,offsetY:0,startX:0,startY:0,lastX:0,lastY:0,nowX:0,nowY:0,distX:0,distY:0,dirAx:0,dirX:0,dirY:0,lastDirX:0,lastDirY:0,distAxX:0,distAxY:0},this.moving=!1,this.dragEl=null,this.dragRootEl=null,this.dragDepth=0,this.hasNewRoot=!1,this.pointEl=null},expandItem:function(t){t.removeClass(this.options.collapsedClass),t.children('[data-action="expand"]').hide(),t.children('[data-action="collapse"]').show(),t.children(this.options.listNodeName).show()},collapseItem:function(t){var e=t.children(this.options.listNodeName);e.length&&(t.addClass(this.options.collapsedClass),t.children('[data-action="collapse"]').hide(),t.children('[data-action="expand"]').show(),t.children(this.options.listNodeName).hide())},expandAll:function(){var e=this;e.el.find(e.options.itemNodeName).each(function(){e.expandItem(t(this))})},collapseAll:function(){var e=this;e.el.find(e.options.itemNodeName).each(function(){e.collapseItem(t(this))})},setParent:function(e){e.children(this.options.listNodeName).length&&(e.prepend(t(this.options.expandBtnHTML)),e.prepend(t(this.options.collapseBtnHTML))),e.children('[data-action="expand"]').hide()},unsetParent:function(t){t.removeClass(this.options.collapsedClass),t.children("[data-action]").remove(),t.children(this.options.listNodeName).remove()},dragStart:function(e){{var a=this.mouse,o=t(e.target),n=o.closest(this.options.itemNodeName);this.node}this.placeEl.css("height",n.height()),a.offsetX=e.offsetX!==i?e.offsetX:e.pageX-o.offset().left,a.offsetY=e.offsetY!==i?e.offsetY:e.pageY-o.offset().top,a.startX=a.lastX=e.pageX,a.startY=a.lastY=e.pageY,this.dragRootEl=this.el,this.dragEl=t(s.createElement(this.options.listNodeName)).addClass(this.options.listClass+" "+this.options.dragClass),this.dragEl.css("width",n.width()),n.after(this.placeEl),n[0].parentNode.removeChild(n[0]),n.appendTo(this.dragEl),t(s.body).append(this.dragEl),this.dragEl.css({left:e.pageX-a.offsetX,top:e.pageY-a.offsetY});var l,d,r=this.dragEl.find(this.options.itemNodeName);for(l=0;lthis.dragDepth&&(this.dragDepth=d)},dragStop:function(){var e=this.dragEl.children(this.options.itemNodeName).first();e[0].parentNode.removeChild(e[0]),this.placeEl.replaceWith(e),this.dragEl.remove();var i=e.prev(this.options.itemNodeName),a=e.next(this.options.itemNodeName),o=e.parents(this.options.itemNodeName);t.ajax({url:this.options.url,context:s.body,data:{id:e.data("id"),par:t(o).data("id"),lft:i.length?i.data("id"):0,rgt:a.length?a.data("id"):0}}).fail(function(jqXHR){alert(jqXHR.responseText);}),this.el.trigger("change"),this.hasNewRoot&&this.dragRootEl.trigger("change"),this.reset()},dragMove:function(i){var a,o,l,d,r,h=this.options,p=this.mouse;this.dragEl.css({left:i.pageX-p.offsetX,top:i.pageY-p.offsetY}),p.lastX=p.nowX,p.lastY=p.nowY,p.nowX=i.pageX,p.nowY=i.pageY,p.distX=p.nowX-p.lastX,p.distY=p.nowY-p.lastY,p.lastDirX=p.dirX,p.lastDirY=p.dirY,p.dirX=0===p.distX?0:p.distX>0?1:-1,p.dirY=0===p.distY?0:p.distY>0?1:-1;var c=Math.abs(p.distX)>Math.abs(p.distY)?1:0;if(!p.moving)return p.dirAx=c,void(p.moving=!0);p.dirAx!==c?(p.distAxX=0,p.distAxY=0):(p.distAxX+=Math.abs(p.distX),0!==p.dirX&&p.dirX!==p.lastDirX&&(p.distAxX=0),p.distAxY+=Math.abs(p.distY),0!==p.dirY&&p.dirY!==p.lastDirY&&(p.distAxY=0)),p.dirAx=c,p.dirAx&&p.distAxX>=h.threshold&&(p.distAxX=0,l=this.placeEl.prev(h.itemNodeName),p.distX>0&&l.length&&!l.hasClass(h.collapsedClass)&&(a=l.find(h.listNodeName).last(),r=this.placeEl.parents(h.listNodeName).length,r+this.dragDepth<=h.maxDepth&&(a.length?(a=l.children(h.listNodeName).last(),a.append(this.placeEl)):(a=t("<"+h.listNodeName+"/>").addClass(h.listClass),a.append(this.placeEl),l.append(a),this.setParent(l)))),p.distX<0&&(d=this.placeEl.next(h.itemNodeName),d.length||(o=this.placeEl.parent(),this.placeEl.closest(h.itemNodeName).after(this.placeEl),o.children().length||this.unsetParent(o.parent()))));var g=!1;if(n||(this.dragEl[0].style.visibility="hidden"),this.pointEl=t(s.elementFromPoint(i.pageX-s.body.scrollLeft,i.pageY-(e.pageYOffset||s.documentElement.scrollTop))),n||(this.dragEl[0].style.visibility="visible"),this.pointEl.hasClass(h.handleClass)&&(this.pointEl=this.pointEl.parent(h.itemNodeName)),this.pointEl.hasClass(h.emptyClass))g=!0;else if(!this.pointEl.length||!this.pointEl.hasClass(h.itemClass))return;var m=this.pointEl.closest("."+h.rootClass),f=this.dragRootEl.data("nestable-id")!==m.data("nestable-id");if(!p.dirAx||f||g){if(f&&h.group!==m.data("nestable-group"))return;if(r=this.dragDepth-1+this.pointEl.parents(h.listNodeName).length,r>h.maxDepth)return;var u=i.pageY'),f&&(this.dragRootEl=m,this.hasNewRoot=this.el[0]!==this.dragRootEl[0])}}},t.fn.nestable=function(e){var s=this,i=this;return s.each(function(){var s=t(this).data("nestable");s?"string"==typeof e&&"function"==typeof s[e]&&(i=s[e]()):(t(this).data("nestable",new a(this,e)),t(this).data("nestable-id",(new Date).getTime()))}),i||s}}(window.jQuery||window.Zepto,window,document);
--------------------------------------------------------------------------------
/assets/js/jquery.nestable.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Nestable jQuery Plugin - Copyright (c) 2012 David Bushell - http://dbushell.com/
3 | * Modified for yii2-nestable - Copyright (c) 2015 Arno Slatius - http://slatius.nl/
4 | *
5 | * Dual-licensed under the BSD or MIT licenses
6 | */
7 | ;(function($, window, document, undefined)
8 | {
9 | var hasTouch = 'ontouchstart' in window;
10 |
11 | /**
12 | * Detect CSS pointer-events property
13 | * events are normally disabled on the dragging element to avoid conflicts
14 | * https://github.com/ausi/Feature-detection-technique-for-pointer-events/blob/master/modernizr-pointerevents.js
15 | */
16 | var hasPointerEvents = (function()
17 | {
18 | var el = document.createElement('div'),
19 | docEl = document.documentElement;
20 | if (!('pointerEvents' in el.style)) {
21 | return false;
22 | }
23 | el.style.pointerEvents = 'auto';
24 | el.style.pointerEvents = 'x';
25 | docEl.appendChild(el);
26 | var supports = window.getComputedStyle && window.getComputedStyle(el, '').pointerEvents === 'auto';
27 | docEl.removeChild(el);
28 | return !!supports;
29 | })();
30 |
31 | var eStart = hasTouch ? 'touchstart' : 'mousedown',
32 | eMove = hasTouch ? 'touchmove' : 'mousemove',
33 | eEnd = hasTouch ? 'touchend' : 'mouseup';
34 | eCancel = hasTouch ? 'touchcancel' : 'mouseup';
35 |
36 | var defaults = {
37 | listNodeName : 'ol',
38 | itemNodeName : 'li',
39 | rootClass : 'dd',
40 | listClass : 'dd-list',
41 | itemClass : 'dd-item',
42 | dragClass : 'dd-dragel',
43 | handleClass : 'dd-handle',
44 | collapsedClass : 'dd-collapsed',
45 | placeClass : 'dd-placeholder',
46 | noDragClass : 'dd-nodrag',
47 | emptyClass : 'dd-empty',
48 | expandBtnHTML : '',
49 | collapseBtnHTML : '',
50 | group : 0,
51 | maxDepth : 5,
52 | threshold : 20,
53 | url : ''
54 | };
55 |
56 | function Plugin(element, options)
57 | {
58 | this.w = $(window);
59 | this.el = $(element);
60 | this.options = $.extend({}, defaults, options);
61 | this.init();
62 | }
63 |
64 | Plugin.prototype = {
65 |
66 | init: function()
67 | {
68 | var list = this;
69 |
70 | list.reset();
71 |
72 | list.el.data('nestable-group', this.options.group);
73 |
74 | list.placeEl = $('');
75 |
76 | $.each(this.el.find(list.options.itemNodeName), function(k, el) {
77 | list.setParent($(el));
78 | });
79 |
80 | list.el.on('click', 'button', function(e) {
81 | if (list.dragEl || (!hasTouch && e.button !== 0)) {
82 | return;
83 | }
84 | var target = $(e.currentTarget),
85 | action = target.data('action'),
86 | item = target.parent(list.options.itemNodeName);
87 | if (action === 'collapse') {
88 | list.collapseItem(item);
89 | }
90 | if (action === 'expand') {
91 | list.expandItem(item);
92 | }
93 | });
94 |
95 | var onStartEvent = function(e)
96 | {
97 | var handle = $(e.target);
98 | if (!handle.hasClass(list.options.handleClass)) {
99 | if (handle.closest('.' + list.options.noDragClass).length) {
100 | return;
101 | }
102 | handle = handle.closest('.' + list.options.handleClass);
103 | }
104 | if (!handle.length || list.dragEl || (!hasTouch && e.button !== 0) || (hasTouch && e.touches.length !== 1)) {
105 | return;
106 | }
107 | e.preventDefault();
108 | list.dragStart(hasTouch ? e.touches[0] : e);
109 | };
110 |
111 | var onMoveEvent = function(e)
112 | {
113 | if (list.dragEl) {
114 | e.preventDefault();
115 | list.dragMove(hasTouch ? e.touches[0] : e);
116 | }
117 | };
118 |
119 | var onEndEvent = function(e)
120 | {
121 | if (list.dragEl) {
122 | e.preventDefault();
123 | list.dragStop(hasTouch ? e.touches[0] : e);
124 | }
125 | };
126 |
127 | if (hasTouch) {
128 | list.el[0].addEventListener(eStart, onStartEvent, false);
129 | window.addEventListener(eMove, onMoveEvent, false);
130 | window.addEventListener(eEnd, onEndEvent, false);
131 | window.addEventListener(eCancel, onEndEvent, false);
132 | } else {
133 | list.el.on(eStart, onStartEvent);
134 | list.w.on(eMove, onMoveEvent);
135 | list.w.on(eEnd, onEndEvent);
136 | }
137 |
138 | },
139 |
140 | serialize: function()
141 | {
142 | var data,
143 | depth = 0,
144 | list = this;
145 | step = function(level, depth)
146 | {
147 | var array = [ ],
148 | items = level.children(list.options.itemNodeName);
149 | items.each(function()
150 | {
151 | var li = $(this),
152 | item = $.extend({}, li.data()),
153 | sub = li.children(list.options.listNodeName);
154 | if (sub.length) {
155 | item.children = step(sub, depth + 1);
156 | }
157 | array.push(item);
158 | });
159 | return array;
160 | };
161 | data = step(list.el.find(list.options.listNodeName).first(), depth);
162 | return data;
163 | },
164 |
165 | serialise: function()
166 | {
167 | return this.serialize();
168 | },
169 |
170 | reset: function()
171 | {
172 | this.mouse = {
173 | offsetX : 0,
174 | offsetY : 0,
175 | startX : 0,
176 | startY : 0,
177 | lastX : 0,
178 | lastY : 0,
179 | nowX : 0,
180 | nowY : 0,
181 | distX : 0,
182 | distY : 0,
183 | dirAx : 0,
184 | dirX : 0,
185 | dirY : 0,
186 | lastDirX : 0,
187 | lastDirY : 0,
188 | distAxX : 0,
189 | distAxY : 0
190 | };
191 | this.moving = false;
192 | this.dragEl = null;
193 | this.dragRootEl = null;
194 | this.dragDepth = 0;
195 | this.hasNewRoot = false;
196 | this.pointEl = null;
197 | },
198 |
199 | expandItem: function(li)
200 | {
201 | li.removeClass(this.options.collapsedClass);
202 | li.children('[data-action="expand"]').hide();
203 | li.children('[data-action="collapse"]').show();
204 | li.children(this.options.listNodeName).show();
205 | },
206 |
207 | collapseItem: function(li)
208 | {
209 | var lists = li.children(this.options.listNodeName);
210 | if (lists.length) {
211 | li.addClass(this.options.collapsedClass);
212 | li.children('[data-action="collapse"]').hide();
213 | li.children('[data-action="expand"]').show();
214 | li.children(this.options.listNodeName).hide();
215 | }
216 | },
217 |
218 | expandAll: function()
219 | {
220 | var list = this;
221 | list.el.find(list.options.itemNodeName).each(function() {
222 | list.expandItem($(this));
223 | });
224 | },
225 |
226 | collapseAll: function()
227 | {
228 | var list = this;
229 | list.el.find(list.options.itemNodeName).each(function() {
230 | list.collapseItem($(this));
231 | });
232 | },
233 |
234 | setParent: function(li)
235 | {
236 | if (li.children(this.options.listNodeName).length) {
237 | li.prepend($(this.options.expandBtnHTML));
238 | li.prepend($(this.options.collapseBtnHTML));
239 | }
240 | li.children('[data-action="expand"]').hide();
241 | },
242 |
243 | unsetParent: function(li)
244 | {
245 | li.removeClass(this.options.collapsedClass);
246 | li.children('[data-action]').remove();
247 | li.children(this.options.listNodeName).remove();
248 | },
249 |
250 | dragStart: function(e)
251 | {
252 | var mouse = this.mouse,
253 | target = $(e.target),
254 | dragItem = target.closest(this.options.itemNodeName),
255 | node = this.node;
256 |
257 | this.placeEl.css('height', dragItem.height());
258 |
259 | mouse.offsetX = e.offsetX !== undefined ? e.offsetX : e.pageX - target.offset().left;
260 | mouse.offsetY = e.offsetY !== undefined ? e.offsetY : e.pageY - target.offset().top;
261 | mouse.startX = mouse.lastX = e.pageX;
262 | mouse.startY = mouse.lastY = e.pageY;
263 |
264 | this.dragRootEl = this.el;
265 |
266 | this.dragEl = $(document.createElement(this.options.listNodeName)).addClass(this.options.listClass + ' ' + this.options.dragClass);
267 | this.dragEl.css('width', dragItem.width());
268 |
269 | // fix for zepto.js
270 | //dragItem.after(this.placeEl).detach().appendTo(this.dragEl);
271 | dragItem.after(this.placeEl);
272 | dragItem[0].parentNode.removeChild(dragItem[0]);
273 | dragItem.appendTo(this.dragEl);
274 |
275 | $(document.body).append(this.dragEl);
276 | this.dragEl.css({
277 | 'left' : e.pageX - mouse.offsetX,
278 | 'top' : e.pageY - mouse.offsetY
279 | });
280 | // total depth of dragging item
281 | var i, depth,
282 | items = this.dragEl.find(this.options.itemNodeName);
283 | for (i = 0; i < items.length; i++) {
284 | depth = $(items[i]).parents(this.options.listNodeName).length;
285 | if (depth > this.dragDepth) {
286 | this.dragDepth = depth;
287 | }
288 | }
289 | },
290 |
291 | dragStop: function(e)
292 | {
293 | var el = this.dragEl.children(this.options.itemNodeName).first();
294 | el[0].parentNode.removeChild(el[0]);
295 | this.placeEl.replaceWith(el);
296 |
297 | this.dragEl.remove();
298 |
299 | var prev = el.prev(this.options.itemNodeName);
300 | var next = el.next(this.options.itemNodeName);
301 | var parent = el.parents(this.options.itemNodeName);
302 | $.ajax({
303 | url: this.options.url,
304 | context: document.body,
305 | data: {
306 | id : el.data('id'),
307 | par : $(parent).data('id'),
308 | lft : (prev.length ? prev.data('id') : 0),
309 | rgt : (next.length ? next.data('id') : 0)
310 | }
311 | }).fail(function(jqXHR){
312 | alert(jqXHR.responseText);
313 | });
314 |
315 | this.el.trigger('change');
316 | if (this.hasNewRoot) {
317 | this.dragRootEl.trigger('change');
318 | }
319 | this.reset();
320 | },
321 |
322 | dragMove: function(e)
323 | {
324 | var list, parent, prev, next, depth,
325 | opt = this.options,
326 | mouse = this.mouse;
327 |
328 | this.dragEl.css({
329 | 'left' : e.pageX - mouse.offsetX,
330 | 'top' : e.pageY - mouse.offsetY
331 | });
332 |
333 | // mouse position last events
334 | mouse.lastX = mouse.nowX;
335 | mouse.lastY = mouse.nowY;
336 | // mouse position this events
337 | mouse.nowX = e.pageX;
338 | mouse.nowY = e.pageY;
339 | // distance mouse moved between events
340 | mouse.distX = mouse.nowX - mouse.lastX;
341 | mouse.distY = mouse.nowY - mouse.lastY;
342 | // direction mouse was moving
343 | mouse.lastDirX = mouse.dirX;
344 | mouse.lastDirY = mouse.dirY;
345 | // direction mouse is now moving (on both axis)
346 | mouse.dirX = mouse.distX === 0 ? 0 : mouse.distX > 0 ? 1 : -1;
347 | mouse.dirY = mouse.distY === 0 ? 0 : mouse.distY > 0 ? 1 : -1;
348 | // axis mouse is now moving on
349 | var newAx = Math.abs(mouse.distX) > Math.abs(mouse.distY) ? 1 : 0;
350 |
351 | // do nothing on first move
352 | if (!mouse.moving) {
353 | mouse.dirAx = newAx;
354 | mouse.moving = true;
355 | return;
356 | }
357 |
358 | // calc distance moved on this axis (and direction)
359 | if (mouse.dirAx !== newAx) {
360 | mouse.distAxX = 0;
361 | mouse.distAxY = 0;
362 | } else {
363 | mouse.distAxX += Math.abs(mouse.distX);
364 | if (mouse.dirX !== 0 && mouse.dirX !== mouse.lastDirX) {
365 | mouse.distAxX = 0;
366 | }
367 | mouse.distAxY += Math.abs(mouse.distY);
368 | if (mouse.dirY !== 0 && mouse.dirY !== mouse.lastDirY) {
369 | mouse.distAxY = 0;
370 | }
371 | }
372 | mouse.dirAx = newAx;
373 |
374 | /**
375 | * move horizontal
376 | */
377 | if (mouse.dirAx && mouse.distAxX >= opt.threshold) {
378 | // reset move distance on x-axis for new phase
379 | mouse.distAxX = 0;
380 | prev = this.placeEl.prev(opt.itemNodeName);
381 | // increase horizontal level if previous sibling exists and is not collapsed
382 | if (mouse.distX > 0 && prev.length && !prev.hasClass(opt.collapsedClass)) {
383 | // cannot increase level when item above is collapsed
384 | list = prev.find(opt.listNodeName).last();
385 | // check if depth limit has reached
386 | depth = this.placeEl.parents(opt.listNodeName).length;
387 | if (depth + this.dragDepth <= opt.maxDepth) {
388 | // create new sub-level if one doesn't exist
389 | if (!list.length) {
390 | list = $('<' + opt.listNodeName + '/>').addClass(opt.listClass);
391 | list.append(this.placeEl);
392 | prev.append(list);
393 | this.setParent(prev);
394 | } else {
395 | // else append to next level up
396 | list = prev.children(opt.listNodeName).last();
397 | list.append(this.placeEl);
398 | }
399 | }
400 | }
401 | // decrease horizontal level
402 | if (mouse.distX < 0) {
403 | // we can't decrease a level if an item preceeds the current one
404 | next = this.placeEl.next(opt.itemNodeName);
405 | if (!next.length) {
406 | parent = this.placeEl.parent();
407 | this.placeEl.closest(opt.itemNodeName).after(this.placeEl);
408 | if (!parent.children().length) {
409 | this.unsetParent(parent.parent());
410 | }
411 | }
412 | }
413 | }
414 |
415 | var isEmpty = false;
416 |
417 | // find list item under cursor
418 | if (!hasPointerEvents) {
419 | this.dragEl[0].style.visibility = 'hidden';
420 | }
421 | this.pointEl = $(document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - (window.pageYOffset || document.documentElement.scrollTop)));
422 | if (!hasPointerEvents) {
423 | this.dragEl[0].style.visibility = 'visible';
424 | }
425 | if (this.pointEl.hasClass(opt.handleClass)) {
426 | this.pointEl = this.pointEl.parent(opt.itemNodeName);
427 | }
428 | if (this.pointEl.hasClass(opt.emptyClass)) {
429 | isEmpty = true;
430 | }
431 | else if (!this.pointEl.length || !this.pointEl.hasClass(opt.itemClass)) {
432 | return;
433 | }
434 |
435 | // find parent list of item under cursor
436 | var pointElRoot = this.pointEl.closest('.' + opt.rootClass),
437 | isNewRoot = this.dragRootEl.data('nestable-id') !== pointElRoot.data('nestable-id');
438 |
439 | /**
440 | * move vertical
441 | */
442 | if (!mouse.dirAx || isNewRoot || isEmpty) {
443 | // check if groups match if dragging over new root
444 | if (isNewRoot && opt.group !== pointElRoot.data('nestable-group')) {
445 | return;
446 | }
447 | // check depth limit
448 | depth = this.dragDepth - 1 + this.pointEl.parents(opt.listNodeName).length;
449 | if (depth > opt.maxDepth) {
450 | return;
451 | }
452 | var before = e.pageY < (this.pointEl.offset().top + this.pointEl.height() / 2);
453 | parent = this.placeEl.parent();
454 | // if empty create new list to replace empty placeholder
455 | if (isEmpty) {
456 | list = $(document.createElement(opt.listNodeName)).addClass(opt.listClass);
457 | list.append(this.placeEl);
458 | this.pointEl.replaceWith(list);
459 | }
460 | else if (before) {
461 | this.pointEl.before(this.placeEl);
462 | }
463 | else {
464 | this.pointEl.after(this.placeEl);
465 | }
466 | if (!parent.children().length) {
467 | this.unsetParent(parent.parent());
468 | }
469 | if (!this.dragRootEl.find(opt.itemNodeName).length) {
470 | this.dragRootEl.append('');
471 | }
472 | // parent root list has changed
473 | if (isNewRoot) {
474 | this.dragRootEl = pointElRoot;
475 | this.hasNewRoot = this.el[0] !== this.dragRootEl[0];
476 | }
477 | }
478 | }
479 |
480 | };
481 |
482 | $.fn.nestable = function(params)
483 | {
484 | var lists = this,
485 | retval = this;
486 |
487 | lists.each(function()
488 | {
489 | var plugin = $(this).data("nestable");
490 |
491 | if (!plugin) {
492 | $(this).data("nestable", new Plugin(this, params));
493 | $(this).data("nestable-id", new Date().getTime());
494 | } else {
495 | if (typeof params === 'string' && typeof plugin[params] === 'function') {
496 | retval = plugin[params]();
497 | }
498 | }
499 | });
500 |
501 | return retval || lists;
502 | };
503 |
504 | })(window.jQuery || window.Zepto, window, document);
505 |
--------------------------------------------------------------------------------