spina 0.11.0 → 0.11.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of spina might be problematic. Click here for more details.

@@ -0,0 +1,485 @@
1
+ /*!
2
+ * Nestable jQuery Plugin - Copyright (c) 2012 David Bushell - http://dbushell.com/
3
+ * Dual-licensed under the BSD or MIT licenses
4
+ */
5
+ ;(function($, window, document, undefined)
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
+ 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
+ };
484
+
485
+ })(window.jQuery || window.Zepto, window, document);