rails_admin_nestable 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NWZiZjYwMTllMGU0ZjEyYjNhYjU4ZmMwNDZjZWQ2MGYzMDVhNWY4ZQ==
5
+ data.tar.gz: !binary |-
6
+ ODBjYmVhY2IzMmFlY2Y5MmFhMjE0NjQ4MTA0OWZlODA5ZmFiOGQxOA==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ M2NiZDM2MmQ4MWQ1OWY5MGRiY2YyNTQzZThmZDJkMDRiNmM3ZjQ4MjU0NDQ5
10
+ YmI1MWI5MTczYjg0YTcxNTQxYmJlMTliZDZjNDJhYTEzNmQ1NDRmODFiMDg5
11
+ ZWY1YTI1MGE5NGJkOTY3NTk5NGRmMzFhODllZDhiNjBjMjg4YWQ=
12
+ data.tar.gz: !binary |-
13
+ NDdlYWFhOTYwYjJjMjRjMmJlODE3NTBhM2IyNWYxZGU0ZmU5NjlhMzkwODVl
14
+ NmJlM2IwNDBhNmJmMGE4ZjRkMzBjNjc3Yzg0MzhlZjliMTllYTlhZjQ4N2U3
15
+ YzFlNzQ4MGM4OTc1YjIzZjU3ZjBhMWEzM2UxMjlhYmU3ODQxNTc=
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2012 Andrea Dal Ponte
1
+ Copyright 2013 Andrea Dal Ponte
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -4,6 +4,7 @@ Reorganise model data with a drag and drop tree/list structure.
4
4
 
5
5
  Sample demo available at: https://github.com/dalpo/rails_admin_nestable_demo
6
6
 
7
+ [![Gem Version](https://badge.fury.io/rb/rails_admin_nestable.png)](http://badge.fury.io/rb/rails_admin_nestable)
7
8
 
8
9
  ## Installation
9
10
 
@@ -49,6 +50,7 @@ The `nestable_tree` methods supports the following options:
49
50
  * `position_field`: (symbol) default => `nil`
50
51
  * `max_depth`: (integer) default => `nil`
51
52
  * `enable_callback`: (boolean) default => `false`
53
+ * `scope`: (symbol | proc) default => `nil`, must be a `ActiveRecord::Relation` object of models
52
54
 
53
55
  In your `config/initializers/rails_admin.rb` initializer:
54
56
  ```ruby
@@ -73,6 +75,7 @@ To use this configuration, you need a position field
73
75
  The `nestable_list` methods supports the following options:
74
76
  * `position_field`: (symbol) default `:position`
75
77
  * `enable_callback`: (boolean) default => `false`
78
+ * `scope`: (symbol | proc) default => `nil`, must be a `ActiveRecord::Relation` object of models
76
79
 
77
80
  In your `config/initializers/rails_admin.rb` initializer:
78
81
  ```ruby
@@ -120,13 +123,50 @@ end
120
123
 
121
124
  ![Nestable view](https://github.com/dalpo/rails_admin_nestable/raw/master/screenshots/nestable_tree.jpg "nestable view")
122
125
 
126
+
127
+ ## Contributing
128
+ Submitting a Pull Request:
129
+
130
+ 1. [Fork the repository.][fork]
131
+ 2. [Create a topic branch.][branch]
132
+ 3. Implement your feature or bug fix.
133
+ 4. Add, commit, and push your changes.
134
+ 5. [Submit a pull request.][pr]
135
+
136
+ [fork]: http://help.github.com/fork-a-repo/
137
+ [branch]: http://learn.github.com/p/branching.html
138
+ [pr]: http://help.github.com/send-pull-requests/
139
+
140
+
123
141
  ## Thanks
124
142
 
125
143
  * [Carlo Scortegagna](https://github.com/carloscortegagna)
126
144
  * [Andrea Zaupa](https://github.com/andreazaupa)
127
145
  * [Rails Admin](https://github.com/sferik/rails_admin)
128
146
  * [Nestable](http://dbushell.github.com/Nestable)
147
+ * [To see the generous people who have contributed code, take a look at the contributors page](https://github.com/dalpo/rails_admin_nestable/graphs/contributors)
129
148
 
149
+ ## License
150
+ **This project rocks and uses MIT-LICENSE.**
130
151
 
152
+ Copyright 2013 Andrea Dal Ponte
153
+
154
+ Permission is hereby granted, free of charge, to any person obtaining
155
+ a copy of this software and associated documentation files (the
156
+ "Software"), to deal in the Software without restriction, including
157
+ without limitation the rights to use, copy, modify, merge, publish,
158
+ distribute, sublicense, and/or sell copies of the Software, and to
159
+ permit persons to whom the Software is furnished to do so, subject to
160
+ the following conditions:
161
+
162
+ The above copyright notice and this permission notice shall be
163
+ included in all copies or substantial portions of the Software.
164
+
165
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
166
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
167
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
168
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
169
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
170
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
171
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
131
172
 
132
- **This project rocks and uses MIT-LICENSE.**
@@ -4,478 +4,482 @@
4
4
  */
5
5
  ;(function($, window, document, undefined)
6
6
  {
7
- var hasTouch = 'ontouchstart' in window;
8
-
9
- /**
10
- * Detect CSS pointer-events property
11
- * events are normally disabled on the dragging element to avoid conflicts
12
- * https://github.com/ausi/Feature-detection-technique-for-pointer-events/blob/master/modernizr-pointerevents.js
13
- */
14
- var hasPointerEvents = (function()
15
- {
16
- var el = document.createElement('div'),
17
- docEl = document.documentElement;
18
- if (!('pointerEvents' in el.style)) {
19
- return false;
20
- }
21
- el.style.pointerEvents = 'auto';
22
- el.style.pointerEvents = 'x';
23
- docEl.appendChild(el);
24
- var supports = window.getComputedStyle && window.getComputedStyle(el, '').pointerEvents === 'auto';
25
- docEl.removeChild(el);
26
- return !!supports;
27
- })();
28
-
29
- var eStart = hasTouch ? 'touchstart' : 'mousedown',
30
- eMove = hasTouch ? 'touchmove' : 'mousemove',
31
- eEnd = hasTouch ? 'touchend' : 'mouseup';
32
- eCancel = hasTouch ? 'touchcancel' : 'mouseup';
33
-
34
- var defaults = {
35
- listNodeName : 'ol',
36
- itemNodeName : 'li',
37
- rootClass : 'dd',
38
- listClass : 'dd-list',
39
- itemClass : 'dd-item',
40
- dragClass : 'dd-dragel',
41
- handleClass : 'dd-handle',
42
- collapsedClass : 'dd-collapsed',
43
- placeClass : 'dd-placeholder',
44
- emptyClass : 'dd-empty',
45
- expandBtnHTML : '<button data-action="expand" type="button">Expand</button>',
46
- collapseBtnHTML : '<button data-action="collapse" type="button">Collapse</button>',
47
- group : 0,
48
- maxDepth : 5,
49
- threshold : 20
50
- };
51
-
52
- function Plugin(element, options)
53
- {
54
- this.w = $(window);
55
- this.el = $(element);
56
- this.options = $.extend({}, defaults, options);
57
- this.init();
58
- }
59
-
60
- Plugin.prototype = {
61
-
62
- init: function()
63
- {
64
- var list = this;
65
-
66
- list.reset();
67
-
68
- list.el.data('nestable-group', this.options.group);
69
-
70
- list.placeEl = $('<div class="' + list.options.placeClass + '"/>');
71
-
72
- $.each(this.el.find(list.options.itemNodeName), function(k, el) {
73
- list.setParent($(el));
74
- });
75
-
76
- list.el.on('click', 'button', function(e) {
77
- if (list.dragEl || (!hasTouch && e.button !== 0)) {
78
- return;
79
- }
80
- var target = $(e.currentTarget),
81
- action = target.data('action'),
82
- item = target.parent(list.options.itemNodeName);
83
- if (action === 'collapse') {
84
- list.collapseItem(item);
85
- }
86
- if (action === 'expand') {
87
- list.expandItem(item);
88
- }
89
- });
90
-
91
- var onStartEvent = function(e)
92
- {
93
- var handle = $(e.target);
94
- if (!handle.hasClass(list.options.handleClass)) {
95
- handle = handle.parents('.' + list.options.handleClass).first();
96
- }
97
- if (!handle.length || list.dragEl || (!hasTouch && e.button !== 0) || (hasTouch && e.touches.length !== 1)) {
98
- return;
99
- }
100
- e.preventDefault();
101
- list.dragStart(hasTouch ? e.touches[0] : e);
102
- };
103
-
104
- var onMoveEvent = function(e)
105
- {
106
- if (list.dragEl) {
107
- e.preventDefault();
108
- list.dragMove(hasTouch ? e.touches[0] : e);
109
- }
110
- };
111
-
112
- var onEndEvent = function(e)
113
- {
114
- if (list.dragEl) {
115
- e.preventDefault();
116
- list.dragStop(hasTouch ? e.touches[0] : e);
117
- }
118
- };
119
-
120
- if (hasTouch) {
121
- list.el[0].addEventListener(eStart, onStartEvent, false);
122
- window.addEventListener(eMove, onMoveEvent, false);
123
- window.addEventListener(eEnd, onEndEvent, false);
124
- window.addEventListener(eCancel, onEndEvent, false);
125
- } else {
126
- list.el.on(eStart, onStartEvent);
127
- list.w.on(eMove, onMoveEvent);
128
- list.w.on(eEnd, onEndEvent);
129
- }
130
-
131
- },
132
-
133
- serialize: function()
134
- {
135
- var data,
136
- depth = 0,
137
- list = this;
138
- step = function(level, depth)
139
- {
140
- var array = [ ],
141
- items = level.children(list.options.itemNodeName);
142
- items.each(function()
143
- {
144
- var li = $(this),
145
- item = $.extend({}, li.data()),
146
- sub = li.children(list.options.listNodeName);
147
- if (sub.length) {
148
- item.children = step(sub, depth + 1);
149
- }
150
- array.push(item);
151
- });
152
- return array;
153
- };
154
- data = step(list.el.find(list.options.listNodeName).first(), depth);
155
- return data;
156
- },
157
-
158
- serialise: function()
159
- {
160
- return this.serialize();
161
- },
162
-
163
- reset: function()
164
- {
165
- this.mouse = {
166
- offsetX : 0,
167
- offsetY : 0,
168
- startX : 0,
169
- startY : 0,
170
- lastX : 0,
171
- lastY : 0,
172
- nowX : 0,
173
- nowY : 0,
174
- distX : 0,
175
- distY : 0,
176
- dirAx : 0,
177
- dirX : 0,
178
- dirY : 0,
179
- lastDirX : 0,
180
- lastDirY : 0,
181
- distAxX : 0,
182
- distAxY : 0
183
- };
184
- this.moving = false;
185
- this.dragEl = null;
186
- this.dragRootEl = null;
187
- this.dragDepth = 0;
188
- this.hasNewRoot = false;
189
- this.pointEl = null;
190
- },
191
-
192
- expandItem: function(li)
193
- {
194
- li.removeClass(this.options.collapsedClass);
195
- li.children('[data-action="expand"]').hide();
196
- li.children('[data-action="collapse"]').show();
197
- li.children(this.options.listNodeName).show();
198
- },
199
-
200
- collapseItem: function(li)
201
- {
202
- var lists = li.children(this.options.listNodeName);
203
- if (lists.length) {
204
- li.addClass(this.options.collapsedClass);
205
- li.children('[data-action="collapse"]').hide();
206
- li.children('[data-action="expand"]').show();
207
- li.children(this.options.listNodeName).hide();
208
- }
209
- },
210
-
211
- expandAll: function()
212
- {
213
- var list = this;
214
- list.el.find(list.options.itemNodeName).each(function() {
215
- list.expandItem($(this));
216
- });
217
- },
218
-
219
- collapseAll: function()
220
- {
221
- var list = this;
222
- list.el.find(list.options.itemNodeName).each(function() {
223
- list.collapseItem($(this));
224
- });
225
- },
226
-
227
- setParent: function(li)
228
- {
229
- if (li.children(this.options.listNodeName).length) {
230
- li.prepend($(this.options.expandBtnHTML));
231
- li.prepend($(this.options.collapseBtnHTML));
232
- }
233
- li.children('[data-action="expand"]').hide();
234
- },
235
-
236
- unsetParent: function(li)
237
- {
238
- li.removeClass(this.options.collapsedClass);
239
- li.children('[data-action]').remove();
240
- li.children(this.options.listNodeName).remove();
241
- },
242
-
243
- dragStart: function(e)
244
- {
245
- var mouse = this.mouse,
246
- target = $(e.target),
247
- dragItem = target.parents(this.options.itemNodeName).first();
248
-
249
- this.placeEl.css('height', dragItem.height());
250
-
251
- mouse.offsetX = e.offsetX !== undefined ? e.offsetX : e.pageX - target.offset().left;
252
- mouse.offsetY = e.offsetY !== undefined ? e.offsetY : e.pageY - target.offset().top;
253
- mouse.startX = mouse.lastX = e.pageX;
254
- mouse.startY = mouse.lastY = e.pageY;
255
-
256
- this.dragRootEl = this.el;
257
-
258
- this.dragEl = $(document.createElement(this.options.listNodeName)).addClass(this.options.listClass + ' ' + this.options.dragClass);
259
- this.dragEl.css('width', dragItem.width());
260
-
261
- // fix for zepto.js
262
- //dragItem.after(this.placeEl).detach().appendTo(this.dragEl);
263
- dragItem.after(this.placeEl);
264
- dragItem[0].parentNode.removeChild(dragItem[0]);
265
- dragItem.appendTo(this.dragEl);
266
-
267
- $(document.body).append(this.dragEl);
268
- this.dragEl.css({
269
- 'left' : e.pageX - mouse.offsetX,
270
- 'top' : e.pageY - mouse.offsetY
271
- });
272
- // total depth of dragging item
273
- var i, depth,
274
- items = this.dragEl.find(this.options.itemNodeName);
275
- for (i = 0; i < items.length; i++) {
276
- depth = $(items[i]).parents(this.options.listNodeName).length;
277
- if (depth > this.dragDepth) {
278
- this.dragDepth = depth;
279
- }
280
- }
281
- },
282
-
283
- dragStop: function(e)
284
- {
285
- // fix for zepto.js
286
- //this.placeEl.replaceWith(this.dragEl.children(this.options.itemNodeName + ':first').detach());
287
- var el = this.dragEl.children(this.options.itemNodeName).first();
288
- el[0].parentNode.removeChild(el[0]);
289
- this.placeEl.replaceWith(el);
290
-
291
- this.dragEl.remove();
292
- this.el.trigger('change');
293
- if (this.hasNewRoot) {
294
- this.dragRootEl.trigger('change');
295
- }
296
- this.reset();
297
- },
298
-
299
- dragMove: function(e)
300
- {
301
- var list, parent, prev, next, depth,
302
- opt = this.options,
303
- mouse = this.mouse;
304
-
305
- this.dragEl.css({
306
- 'left' : e.pageX - mouse.offsetX,
307
- 'top' : e.pageY - mouse.offsetY
308
- });
309
-
310
- // mouse position last events
311
- mouse.lastX = mouse.nowX;
312
- mouse.lastY = mouse.nowY;
313
- // mouse position this events
314
- mouse.nowX = e.pageX;
315
- mouse.nowY = e.pageY;
316
- // distance mouse moved between events
317
- mouse.distX = mouse.nowX - mouse.lastX;
318
- mouse.distY = mouse.nowY - mouse.lastY;
319
- // direction mouse was moving
320
- mouse.lastDirX = mouse.dirX;
321
- mouse.lastDirY = mouse.dirY;
322
- // direction mouse is now moving (on both axis)
323
- mouse.dirX = mouse.distX === 0 ? 0 : mouse.distX > 0 ? 1 : -1;
324
- mouse.dirY = mouse.distY === 0 ? 0 : mouse.distY > 0 ? 1 : -1;
325
- // axis mouse is now moving on
326
- var newAx = Math.abs(mouse.distX) > Math.abs(mouse.distY) ? 1 : 0;
327
-
328
- // do nothing on first move
329
- if (!mouse.moving) {
330
- mouse.dirAx = newAx;
331
- mouse.moving = true;
332
- return;
333
- }
334
-
335
- // calc distance moved on this axis (and direction)
336
- if (mouse.dirAx !== newAx) {
337
- mouse.distAxX = 0;
338
- mouse.distAxY = 0;
339
- } else {
340
- mouse.distAxX += Math.abs(mouse.distX);
341
- if (mouse.dirX !== 0 && mouse.dirX !== mouse.lastDirX) {
342
- mouse.distAxX = 0;
343
- }
344
- mouse.distAxY += Math.abs(mouse.distY);
345
- if (mouse.dirY !== 0 && mouse.dirY !== mouse.lastDirY) {
346
- mouse.distAxY = 0;
347
- }
348
- }
349
- mouse.dirAx = newAx;
350
-
351
- /**
352
- * move horizontal
353
- */
354
- if (mouse.dirAx && mouse.distAxX >= opt.threshold) {
355
- // reset move distance on x-axis for new phase
356
- mouse.distAxX = 0;
357
- prev = this.placeEl.prev(opt.itemNodeName);
358
- // increase horizontal level if previous sibling exists and is not collapsed
359
- if (mouse.distX > 0 && prev.length && !prev.hasClass(opt.collapsedClass)) {
360
- // cannot increase level when item above is collapsed
361
- list = prev.find(opt.listNodeName).last();
362
- // check if depth limit has reached
363
- depth = this.placeEl.parents(opt.listNodeName).length;
364
- if (depth + this.dragDepth <= opt.maxDepth) {
365
- // create new sub-level if one doesn't exist
366
- if (!list.length) {
367
- list = $('<' + opt.listNodeName + '/>').addClass(opt.listClass);
368
- list.append(this.placeEl);
369
- prev.append(list);
370
- this.setParent(prev);
371
- } else {
372
- // else append to next level up
373
- list = prev.children(opt.listNodeName).last();
374
- list.append(this.placeEl);
375
- }
376
- }
377
- }
378
- // decrease horizontal level
379
- if (mouse.distX < 0) {
380
- // we can't decrease a level if an item preceeds the current one
381
- next = this.placeEl.next(opt.itemNodeName);
382
- if (!next.length) {
383
- parent = this.placeEl.parent();
384
- this.placeEl.parents(opt.itemNodeName).first().after(this.placeEl);
385
- if (!parent.children().length) {
386
- this.unsetParent(parent.parent());
387
- }
388
- }
389
- }
390
- }
391
-
392
- var isEmpty = false;
393
-
394
- // find list item under cursor
395
- if (!hasPointerEvents) {
396
- this.dragEl[0].style.visibility = 'hidden';
397
- }
398
- this.pointEl = $(document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - document.body.scrollTop));
399
- if (!hasPointerEvents) {
400
- this.dragEl[0].style.visibility = 'visible';
401
- }
402
- if (this.pointEl.hasClass(opt.handleClass)) {
403
- this.pointEl = this.pointEl.parent(opt.itemNodeName);
404
- }
405
- if (this.pointEl.hasClass(opt.emptyClass)) {
406
- isEmpty = true;
407
- }
408
- else if (!this.pointEl.length || !this.pointEl.hasClass(opt.itemClass)) {
409
- return;
410
- }
411
-
412
- // find parent list of item under cursor
413
- var pointElRoot = this.pointEl.parents('.' + opt.rootClass).first(),
414
- isNewRoot = this.dragRootEl.data('nestable-id') !== pointElRoot.data('nestable-id');
415
-
416
- /**
417
- * move vertical
418
- */
419
- if (!mouse.dirAx || isNewRoot || isEmpty) {
420
- // check if groups match if dragging over new root
421
- if (isNewRoot && opt.group !== pointElRoot.data('nestable-group')) {
422
- return;
423
- }
424
- // check depth limit
425
- depth = this.dragDepth - 1 + this.pointEl.parents(opt.listNodeName).length;
426
- if (depth > opt.maxDepth) {
427
- return;
428
- }
429
- var before = e.pageY < (this.pointEl.offset().top + this.pointEl.height() / 2);
430
- parent = this.placeEl.parent();
431
- // if empty create new list to replace empty placeholder
432
- if (isEmpty) {
433
- list = $(document.createElement(opt.listNodeName)).addClass(opt.listClass);
434
- list.append(this.placeEl);
435
- this.pointEl.replaceWith(list);
436
- }
437
- else if (before) {
438
- this.pointEl.before(this.placeEl);
439
- }
440
- else {
441
- this.pointEl.after(this.placeEl);
442
- }
443
- if (!parent.children().length) {
444
- this.unsetParent(parent.parent());
445
- }
446
- if (!this.dragRootEl.find(opt.itemNodeName).length) {
447
- this.dragRootEl.append('<div class="' + opt.emptyClass + '"/>');
448
- }
449
- // parent root list has changed
450
- if (isNewRoot) {
451
- this.hasNewRoot = true;
452
- this.dragRootEl = pointElRoot;
453
- }
454
- }
455
- }
456
-
457
- };
458
-
459
- $.fn.nestable = function(params)
460
- {
461
- var lists = this,
462
- retval = this;
463
-
464
- lists.each(function()
465
- {
466
- var plugin = $(this).data("nestable");
467
-
468
- if (!plugin) {
469
- $(this).data("nestable", new Plugin(this, params));
470
- $(this).data("nestable-id", new Date().getTime());
471
- } else {
472
- if (typeof params === 'string' && typeof plugin[params] === 'function') {
473
- retval = plugin[params]();
474
- }
475
- }
476
- });
477
-
478
- return retval || lists;
479
- };
7
+ var hasTouch = 'ontouchstart' in window;
8
+
9
+ /**
10
+ * Detect CSS pointer-events property
11
+ * events are normally disabled on the dragging element to avoid conflicts
12
+ * https://github.com/ausi/Feature-detection-technique-for-pointer-events/blob/master/modernizr-pointerevents.js
13
+ */
14
+ var hasPointerEvents = (function()
15
+ {
16
+ var el = document.createElement('div'),
17
+ docEl = document.documentElement;
18
+ if (!('pointerEvents' in el.style)) {
19
+ return false;
20
+ }
21
+ el.style.pointerEvents = 'auto';
22
+ el.style.pointerEvents = 'x';
23
+ docEl.appendChild(el);
24
+ var supports = window.getComputedStyle && window.getComputedStyle(el, '').pointerEvents === 'auto';
25
+ docEl.removeChild(el);
26
+ return !!supports;
27
+ })();
28
+
29
+ var eStart = hasTouch ? 'touchstart' : 'mousedown',
30
+ eMove = hasTouch ? 'touchmove' : 'mousemove',
31
+ eEnd = hasTouch ? 'touchend' : 'mouseup';
32
+ eCancel = hasTouch ? 'touchcancel' : 'mouseup';
33
+
34
+ var defaults = {
35
+ listNodeName : 'ol',
36
+ itemNodeName : 'li',
37
+ rootClass : 'dd',
38
+ listClass : 'dd-list',
39
+ itemClass : 'dd-item',
40
+ dragClass : 'dd-dragel',
41
+ handleClass : 'dd-handle',
42
+ collapsedClass : 'dd-collapsed',
43
+ placeClass : 'dd-placeholder',
44
+ noDragClass : 'dd-nodrag',
45
+ emptyClass : 'dd-empty',
46
+ expandBtnHTML : '<button data-action="expand" type="button">Expand</button>',
47
+ collapseBtnHTML : '<button data-action="collapse" type="button">Collapse</button>',
48
+ group : 0,
49
+ maxDepth : 5,
50
+ threshold : 20
51
+ };
52
+
53
+ function Plugin(element, options)
54
+ {
55
+ this.w = $(window);
56
+ this.el = $(element);
57
+ this.options = $.extend({}, defaults, options);
58
+ this.init();
59
+ }
60
+
61
+ Plugin.prototype = {
62
+
63
+ init: function()
64
+ {
65
+ var list = this;
66
+
67
+ list.reset();
68
+
69
+ list.el.data('nestable-group', this.options.group);
70
+
71
+ list.placeEl = $('<div class="' + list.options.placeClass + '"/>');
72
+
73
+ $.each(this.el.find(list.options.itemNodeName), function(k, el) {
74
+ list.setParent($(el));
75
+ });
76
+
77
+ list.el.on('click', 'button', function(e) {
78
+ if (list.dragEl || (!hasTouch && e.button !== 0)) {
79
+ return;
80
+ }
81
+ var target = $(e.currentTarget),
82
+ action = target.data('action'),
83
+ item = target.parent(list.options.itemNodeName);
84
+ if (action === 'collapse') {
85
+ list.collapseItem(item);
86
+ }
87
+ if (action === 'expand') {
88
+ list.expandItem(item);
89
+ }
90
+ });
91
+
92
+ var onStartEvent = function(e)
93
+ {
94
+ var handle = $(e.target);
95
+ if (!handle.hasClass(list.options.handleClass)) {
96
+ if (handle.closest('.' + list.options.noDragClass).length) {
97
+ return;
98
+ }
99
+ handle = handle.closest('.' + list.options.handleClass);
100
+ }
101
+ if (!handle.length || list.dragEl || (!hasTouch && e.button !== 0) || (hasTouch && e.touches.length !== 1)) {
102
+ return;
103
+ }
104
+ e.preventDefault();
105
+ list.dragStart(hasTouch ? e.touches[0] : e);
106
+ };
107
+
108
+ var onMoveEvent = function(e)
109
+ {
110
+ if (list.dragEl) {
111
+ e.preventDefault();
112
+ list.dragMove(hasTouch ? e.touches[0] : e);
113
+ }
114
+ };
115
+
116
+ var onEndEvent = function(e)
117
+ {
118
+ if (list.dragEl) {
119
+ e.preventDefault();
120
+ list.dragStop(hasTouch ? e.touches[0] : e);
121
+ }
122
+ };
123
+
124
+ if (hasTouch) {
125
+ list.el[0].addEventListener(eStart, onStartEvent, false);
126
+ window.addEventListener(eMove, onMoveEvent, false);
127
+ window.addEventListener(eEnd, onEndEvent, false);
128
+ window.addEventListener(eCancel, onEndEvent, false);
129
+ } else {
130
+ list.el.on(eStart, onStartEvent);
131
+ list.w.on(eMove, onMoveEvent);
132
+ list.w.on(eEnd, onEndEvent);
133
+ }
134
+
135
+ },
136
+
137
+ serialize: function()
138
+ {
139
+ var data,
140
+ depth = 0,
141
+ list = this;
142
+ step = function(level, depth)
143
+ {
144
+ var array = [ ],
145
+ items = level.children(list.options.itemNodeName);
146
+ items.each(function()
147
+ {
148
+ var li = $(this),
149
+ item = $.extend({}, li.data()),
150
+ sub = li.children(list.options.listNodeName);
151
+ if (sub.length) {
152
+ item.children = step(sub, depth + 1);
153
+ }
154
+ array.push(item);
155
+ });
156
+ return array;
157
+ };
158
+ data = step(list.el.find(list.options.listNodeName).first(), depth);
159
+ return data;
160
+ },
161
+
162
+ serialise: function()
163
+ {
164
+ return this.serialize();
165
+ },
166
+
167
+ reset: function()
168
+ {
169
+ this.mouse = {
170
+ offsetX : 0,
171
+ offsetY : 0,
172
+ startX : 0,
173
+ startY : 0,
174
+ lastX : 0,
175
+ lastY : 0,
176
+ nowX : 0,
177
+ nowY : 0,
178
+ distX : 0,
179
+ distY : 0,
180
+ dirAx : 0,
181
+ dirX : 0,
182
+ dirY : 0,
183
+ lastDirX : 0,
184
+ lastDirY : 0,
185
+ distAxX : 0,
186
+ distAxY : 0
187
+ };
188
+ this.moving = false;
189
+ this.dragEl = null;
190
+ this.dragRootEl = null;
191
+ this.dragDepth = 0;
192
+ this.hasNewRoot = false;
193
+ this.pointEl = null;
194
+ },
195
+
196
+ expandItem: function(li)
197
+ {
198
+ li.removeClass(this.options.collapsedClass);
199
+ li.children('[data-action="expand"]').hide();
200
+ li.children('[data-action="collapse"]').show();
201
+ li.children(this.options.listNodeName).show();
202
+ },
203
+
204
+ collapseItem: function(li)
205
+ {
206
+ var lists = li.children(this.options.listNodeName);
207
+ if (lists.length) {
208
+ li.addClass(this.options.collapsedClass);
209
+ li.children('[data-action="collapse"]').hide();
210
+ li.children('[data-action="expand"]').show();
211
+ li.children(this.options.listNodeName).hide();
212
+ }
213
+ },
214
+
215
+ expandAll: function()
216
+ {
217
+ var list = this;
218
+ list.el.find(list.options.itemNodeName).each(function() {
219
+ list.expandItem($(this));
220
+ });
221
+ },
222
+
223
+ collapseAll: function()
224
+ {
225
+ var list = this;
226
+ list.el.find(list.options.itemNodeName).each(function() {
227
+ list.collapseItem($(this));
228
+ });
229
+ },
230
+
231
+ setParent: function(li)
232
+ {
233
+ if (li.children(this.options.listNodeName).length) {
234
+ li.prepend($(this.options.expandBtnHTML));
235
+ li.prepend($(this.options.collapseBtnHTML));
236
+ }
237
+ li.children('[data-action="expand"]').hide();
238
+ },
239
+
240
+ unsetParent: function(li)
241
+ {
242
+ li.removeClass(this.options.collapsedClass);
243
+ li.children('[data-action]').remove();
244
+ li.children(this.options.listNodeName).remove();
245
+ },
246
+
247
+ dragStart: function(e)
248
+ {
249
+ var mouse = this.mouse,
250
+ target = $(e.target),
251
+ dragItem = target.closest(this.options.itemNodeName);
252
+
253
+ this.placeEl.css('height', dragItem.height());
254
+
255
+ mouse.offsetX = e.offsetX !== undefined ? e.offsetX : e.pageX - target.offset().left;
256
+ mouse.offsetY = e.offsetY !== undefined ? e.offsetY : e.pageY - target.offset().top;
257
+ mouse.startX = mouse.lastX = e.pageX;
258
+ mouse.startY = mouse.lastY = e.pageY;
259
+
260
+ this.dragRootEl = this.el;
261
+
262
+ this.dragEl = $(document.createElement(this.options.listNodeName)).addClass(this.options.listClass + ' ' + this.options.dragClass);
263
+ this.dragEl.css('width', dragItem.width());
264
+
265
+ // fix for zepto.js
266
+ //dragItem.after(this.placeEl).detach().appendTo(this.dragEl);
267
+ dragItem.after(this.placeEl);
268
+ dragItem[0].parentNode.removeChild(dragItem[0]);
269
+ dragItem.appendTo(this.dragEl);
270
+
271
+ $(document.body).append(this.dragEl);
272
+ this.dragEl.css({
273
+ 'left' : e.pageX - mouse.offsetX,
274
+ 'top' : e.pageY - mouse.offsetY
275
+ });
276
+ // total depth of dragging item
277
+ var i, depth,
278
+ items = this.dragEl.find(this.options.itemNodeName);
279
+ for (i = 0; i < items.length; i++) {
280
+ depth = $(items[i]).parents(this.options.listNodeName).length;
281
+ if (depth > this.dragDepth) {
282
+ this.dragDepth = depth;
283
+ }
284
+ }
285
+ },
286
+
287
+ dragStop: function(e)
288
+ {
289
+ // fix for zepto.js
290
+ //this.placeEl.replaceWith(this.dragEl.children(this.options.itemNodeName + ':first').detach());
291
+ var el = this.dragEl.children(this.options.itemNodeName).first();
292
+ el[0].parentNode.removeChild(el[0]);
293
+ this.placeEl.replaceWith(el);
294
+
295
+ this.dragEl.remove();
296
+ this.el.trigger('change');
297
+ if (this.hasNewRoot) {
298
+ this.dragRootEl.trigger('change');
299
+ }
300
+ this.reset();
301
+ },
302
+
303
+ dragMove: function(e)
304
+ {
305
+ var list, parent, prev, next, depth,
306
+ opt = this.options,
307
+ mouse = this.mouse;
308
+
309
+ this.dragEl.css({
310
+ 'left' : e.pageX - mouse.offsetX,
311
+ 'top' : e.pageY - mouse.offsetY
312
+ });
313
+
314
+ // mouse position last events
315
+ mouse.lastX = mouse.nowX;
316
+ mouse.lastY = mouse.nowY;
317
+ // mouse position this events
318
+ mouse.nowX = e.pageX;
319
+ mouse.nowY = e.pageY;
320
+ // distance mouse moved between events
321
+ mouse.distX = mouse.nowX - mouse.lastX;
322
+ mouse.distY = mouse.nowY - mouse.lastY;
323
+ // direction mouse was moving
324
+ mouse.lastDirX = mouse.dirX;
325
+ mouse.lastDirY = mouse.dirY;
326
+ // direction mouse is now moving (on both axis)
327
+ mouse.dirX = mouse.distX === 0 ? 0 : mouse.distX > 0 ? 1 : -1;
328
+ mouse.dirY = mouse.distY === 0 ? 0 : mouse.distY > 0 ? 1 : -1;
329
+ // axis mouse is now moving on
330
+ var newAx = Math.abs(mouse.distX) > Math.abs(mouse.distY) ? 1 : 0;
331
+
332
+ // do nothing on first move
333
+ if (!mouse.moving) {
334
+ mouse.dirAx = newAx;
335
+ mouse.moving = true;
336
+ return;
337
+ }
338
+
339
+ // calc distance moved on this axis (and direction)
340
+ if (mouse.dirAx !== newAx) {
341
+ mouse.distAxX = 0;
342
+ mouse.distAxY = 0;
343
+ } else {
344
+ mouse.distAxX += Math.abs(mouse.distX);
345
+ if (mouse.dirX !== 0 && mouse.dirX !== mouse.lastDirX) {
346
+ mouse.distAxX = 0;
347
+ }
348
+ mouse.distAxY += Math.abs(mouse.distY);
349
+ if (mouse.dirY !== 0 && mouse.dirY !== mouse.lastDirY) {
350
+ mouse.distAxY = 0;
351
+ }
352
+ }
353
+ mouse.dirAx = newAx;
354
+
355
+ /**
356
+ * move horizontal
357
+ */
358
+ if (mouse.dirAx && mouse.distAxX >= opt.threshold) {
359
+ // reset move distance on x-axis for new phase
360
+ mouse.distAxX = 0;
361
+ prev = this.placeEl.prev(opt.itemNodeName);
362
+ // increase horizontal level if previous sibling exists and is not collapsed
363
+ if (mouse.distX > 0 && prev.length && !prev.hasClass(opt.collapsedClass)) {
364
+ // cannot increase level when item above is collapsed
365
+ list = prev.find(opt.listNodeName).last();
366
+ // check if depth limit has reached
367
+ depth = this.placeEl.parents(opt.listNodeName).length;
368
+ if (depth + this.dragDepth <= opt.maxDepth) {
369
+ // create new sub-level if one doesn't exist
370
+ if (!list.length) {
371
+ list = $('<' + opt.listNodeName + '/>').addClass(opt.listClass);
372
+ list.append(this.placeEl);
373
+ prev.append(list);
374
+ this.setParent(prev);
375
+ } else {
376
+ // else append to next level up
377
+ list = prev.children(opt.listNodeName).last();
378
+ list.append(this.placeEl);
379
+ }
380
+ }
381
+ }
382
+ // decrease horizontal level
383
+ if (mouse.distX < 0) {
384
+ // we can't decrease a level if an item preceeds the current one
385
+ next = this.placeEl.next(opt.itemNodeName);
386
+ if (!next.length) {
387
+ parent = this.placeEl.parent();
388
+ this.placeEl.closest(opt.itemNodeName).after(this.placeEl);
389
+ if (!parent.children().length) {
390
+ this.unsetParent(parent.parent());
391
+ }
392
+ }
393
+ }
394
+ }
395
+
396
+ var isEmpty = false;
397
+
398
+ // find list item under cursor
399
+ if (!hasPointerEvents) {
400
+ this.dragEl[0].style.visibility = 'hidden';
401
+ }
402
+ this.pointEl = $(document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - (window.pageYOffset || document.documentElement.scrollTop)));
403
+ if (!hasPointerEvents) {
404
+ this.dragEl[0].style.visibility = 'visible';
405
+ }
406
+ if (this.pointEl.hasClass(opt.handleClass)) {
407
+ this.pointEl = this.pointEl.parent(opt.itemNodeName);
408
+ }
409
+ if (this.pointEl.hasClass(opt.emptyClass)) {
410
+ isEmpty = true;
411
+ }
412
+ else if (!this.pointEl.length || !this.pointEl.hasClass(opt.itemClass)) {
413
+ return;
414
+ }
415
+
416
+ // find parent list of item under cursor
417
+ var pointElRoot = this.pointEl.closest('.' + opt.rootClass),
418
+ isNewRoot = this.dragRootEl.data('nestable-id') !== pointElRoot.data('nestable-id');
419
+
420
+ /**
421
+ * move vertical
422
+ */
423
+ if (!mouse.dirAx || isNewRoot || isEmpty) {
424
+ // check if groups match if dragging over new root
425
+ if (isNewRoot && opt.group !== pointElRoot.data('nestable-group')) {
426
+ return;
427
+ }
428
+ // check depth limit
429
+ depth = this.dragDepth - 1 + this.pointEl.parents(opt.listNodeName).length;
430
+ if (depth > opt.maxDepth) {
431
+ return;
432
+ }
433
+ var before = e.pageY < (this.pointEl.offset().top + this.pointEl.height() / 2);
434
+ parent = this.placeEl.parent();
435
+ // if empty create new list to replace empty placeholder
436
+ if (isEmpty) {
437
+ list = $(document.createElement(opt.listNodeName)).addClass(opt.listClass);
438
+ list.append(this.placeEl);
439
+ this.pointEl.replaceWith(list);
440
+ }
441
+ else if (before) {
442
+ this.pointEl.before(this.placeEl);
443
+ }
444
+ else {
445
+ this.pointEl.after(this.placeEl);
446
+ }
447
+ if (!parent.children().length) {
448
+ this.unsetParent(parent.parent());
449
+ }
450
+ if (!this.dragRootEl.find(opt.itemNodeName).length) {
451
+ this.dragRootEl.append('<div class="' + opt.emptyClass + '"/>');
452
+ }
453
+ // parent root list has changed
454
+ if (isNewRoot) {
455
+ this.dragRootEl = pointElRoot;
456
+ this.hasNewRoot = this.el[0] !== this.dragRootEl[0];
457
+ }
458
+ }
459
+ }
460
+
461
+ };
462
+
463
+ $.fn.nestable = function(params)
464
+ {
465
+ var lists = this,
466
+ retval = this;
467
+
468
+ lists.each(function()
469
+ {
470
+ var plugin = $(this).data("nestable");
471
+
472
+ if (!plugin) {
473
+ $(this).data("nestable", new Plugin(this, params));
474
+ $(this).data("nestable-id", new Date().getTime());
475
+ } else {
476
+ if (typeof params === 'string' && typeof plugin[params] === 'function') {
477
+ retval = plugin[params]();
478
+ }
479
+ }
480
+ });
481
+
482
+ return retval || lists;
483
+ };
480
484
 
481
485
  })(window.jQuery || window.Zepto, window, document);