summaryrefslogtreecommitdiff
path: root/frontend/delta/js/MochiKit/Sortable.js
Unidiff
Diffstat (limited to 'frontend/delta/js/MochiKit/Sortable.js') (more/less context) (ignore whitespace changes)
-rw-r--r--frontend/delta/js/MochiKit/Sortable.js592
1 files changed, 592 insertions, 0 deletions
diff --git a/frontend/delta/js/MochiKit/Sortable.js b/frontend/delta/js/MochiKit/Sortable.js
new file mode 100644
index 0000000..49ea390
--- a/dev/null
+++ b/frontend/delta/js/MochiKit/Sortable.js
@@ -0,0 +1,592 @@
1/*
2
3Copyright 2008-2013 Clipperz Srl
4
5This file is part of Clipperz, the online password manager.
6For further information about its features and functionalities please
7refer to http://www.clipperz.com.
8
9* Clipperz is free software: you can redistribute it and/or modify it
10 under the terms of the GNU Affero General Public License as published
11 by the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14* Clipperz is distributed in the hope that it will be useful, but
15 WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 See the GNU Affero General Public License for more details.
18
19* You should have received a copy of the GNU Affero General Public
20 License along with Clipperz. If not, see http://www.gnu.org/licenses/.
21
22*/
23
24/***
25Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
26 Mochi-ized By Thomas Herve (_firstname_@nimail.org)
27
28See scriptaculous.js for full license.
29
30***/
31
32MochiKit.Base.module(MochiKit, 'Sortable', '1.5', ['Base', 'Iter', 'DOM', 'Position', 'DragAndDrop']);
33
34MochiKit.Base.update(MochiKit.Sortable, {
35 __export__: false,
36
37 /***
38
39 Manage sortables. Mainly use the create function to add a sortable.
40
41 ***/
42 sortables: {},
43
44 _findRootElement: function (element) {
45 while (element.tagName.toUpperCase() != "BODY") {
46 if (element.id && MochiKit.Sortable.sortables[element.id]) {
47 return element;
48 }
49 element = element.parentNode;
50 }
51 },
52
53 _createElementId: function(element) {
54 if (element.id == null || element.id == "") {
55 var d = MochiKit.DOM;
56 var id;
57 var count = 1;
58 while (d.getElement(id = "sortable" + count) != null) {
59 count += 1;
60 }
61 d.setNodeAttribute(element, "id", id);
62 }
63 },
64
65 /** @id MochiKit.Sortable.options */
66 options: function (element) {
67 element = MochiKit.Sortable._findRootElement(MochiKit.DOM.getElement(element));
68 if (!element) {
69 return;
70 }
71 return MochiKit.Sortable.sortables[element.id];
72 },
73
74 /** @id MochiKit.Sortable.destroy */
75 destroy: function (element){
76 var s = MochiKit.Sortable.options(element);
77 var b = MochiKit.Base;
78 var d = MochiKit.DragAndDrop;
79
80 if (s) {
81 MochiKit.Signal.disconnect(s.startHandle);
82 MochiKit.Signal.disconnect(s.endHandle);
83 b.map(function (dr) {
84 d.Droppables.remove(dr);
85 }, s.droppables);
86 b.map(function (dr) {
87 dr.destroy();
88 }, s.draggables);
89
90 delete MochiKit.Sortable.sortables[s.element.id];
91 }
92 },
93
94 /** @id MochiKit.Sortable.create */
95 create: function (element, options) {
96 element = MochiKit.DOM.getElement(element);
97 var self = MochiKit.Sortable;
98 self._createElementId(element);
99
100 /** @id MochiKit.Sortable.options */
101 options = MochiKit.Base.update({
102
103 /** @id MochiKit.Sortable.element */
104 element: element,
105
106 /** @id MochiKit.Sortable.tag */
107 tag: 'li', // assumes li children, override with tag: 'tagname'
108
109 /** @id MochiKit.Sortable.dropOnEmpty */
110 dropOnEmpty: false,
111
112 /** @id MochiKit.Sortable.tree */
113 tree: false,
114
115 /** @id MochiKit.Sortable.treeTag */
116 treeTag: 'ul',
117
118 /** @id MochiKit.Sortable.overlap */
119 overlap: 'vertical', // one of 'vertical', 'horizontal'
120
121 /** @id MochiKit.Sortable.constraint */
122 constraint: 'vertical', // one of 'vertical', 'horizontal', false
123 // also takes array of elements (or ids); or false
124
125 /** @id MochiKit.Sortable.containment */
126 containment: [element],
127
128 /** @id MochiKit.Sortable.handle */
129 handle: false, // or a CSS class
130
131 /** @id MochiKit.Sortable.only */
132 only: false,
133
134 /** @id MochiKit.Sortable.hoverclass */
135 hoverclass: null,
136
137 /** @id MochiKit.Sortable.ghosting */
138 ghosting: false,
139
140 /** @id MochiKit.Sortable.scroll */
141 scroll: false,
142
143 /** @id MochiKit.Sortable.scrollSensitivity */
144 scrollSensitivity: 20,
145
146 /** @id MochiKit.Sortable.scrollSpeed */
147 scrollSpeed: 15,
148
149 /** @id MochiKit.Sortable.format */
150 format: /^[^_]*_(.*)$/,
151
152 /** @id MochiKit.Sortable.onChange */
153 onChange: MochiKit.Base.noop,
154
155 /** @id MochiKit.Sortable.onUpdate */
156 onUpdate: MochiKit.Base.noop,
157
158 /** @id MochiKit.Sortable.accept */
159 accept: null
160 }, options);
161
162 // clear any old sortable with same element
163 self.destroy(element);
164
165 // build options for the draggables
166 var options_for_draggable = {
167 revert: true,
168 ghosting: options.ghosting,
169 scroll: options.scroll,
170 scrollSensitivity: options.scrollSensitivity,
171 scrollSpeed: options.scrollSpeed,
172 constraint: options.constraint,
173 handle: options.handle
174 };
175
176 if (options.starteffect) {
177 options_for_draggable.starteffect = options.starteffect;
178 }
179
180 if (options.reverteffect) {
181 options_for_draggable.reverteffect = options.reverteffect;
182 } else if (options.ghosting) {
183 options_for_draggable.reverteffect = function (innerelement) {
184 innerelement.style.top = 0;
185 innerelement.style.left = 0;
186 };
187 }
188
189 if (options.endeffect) {
190 options_for_draggable.endeffect = options.endeffect;
191 }
192
193 if (options.zindex) {
194 options_for_draggable.zindex = options.zindex;
195 }
196
197 // build options for the droppables
198 var options_for_droppable = {
199 overlap: options.overlap,
200 containment: options.containment,
201 hoverclass: options.hoverclass,
202 onhover: self.onHover,
203 tree: options.tree,
204 accept: options.accept
205 };
206
207 var options_for_tree = {
208 onhover: self.onEmptyHover,
209 overlap: options.overlap,
210 containment: options.containment,
211 hoverclass: options.hoverclass,
212 accept: options.accept
213 };
214
215 // fix for gecko engine
216 MochiKit.DOM.removeEmptyTextNodes(element);
217
218 options.draggables = [];
219 options.droppables = [];
220
221 // drop on empty handling
222 if (options.dropOnEmpty || options.tree) {
223 new MochiKit.DragAndDrop.Droppable(element, options_for_tree);
224 options.droppables.push(element);
225 }
226 MochiKit.Base.map(function (e) {
227 // handles are per-draggable
228 var handle = options.handle ?
229 MochiKit.DOM.getFirstElementByTagAndClassName(null,
230 options.handle, e) : e;
231 options.draggables.push(
232 new MochiKit.DragAndDrop.Draggable(e,
233 MochiKit.Base.update(options_for_draggable,
234 {handle: handle})));
235 new MochiKit.DragAndDrop.Droppable(e, options_for_droppable);
236 if (options.tree) {
237 e.treeNode = element;
238 }
239 options.droppables.push(e);
240 }, (self.findElements(element, options) || []));
241
242 if (options.tree) {
243 MochiKit.Base.map(function (e) {
244 new MochiKit.DragAndDrop.Droppable(e, options_for_tree);
245 e.treeNode = element;
246 options.droppables.push(e);
247 }, (self.findTreeElements(element, options) || []));
248 }
249
250 // keep reference
251 self.sortables[element.id] = options;
252
253 options.lastValue = self.serialize(element);
254 options.startHandle = MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables, 'start',
255 MochiKit.Base.partial(self.onStart, element));
256 options.endHandle = MochiKit.Signal.connect(MochiKit.DragAndDrop.Draggables, 'end',
257 MochiKit.Base.partial(self.onEnd, element));
258 },
259
260 /** @id MochiKit.Sortable.onStart */
261 onStart: function (element, draggable) {
262 var self = MochiKit.Sortable;
263 var options = self.options(element);
264 options.lastValue = self.serialize(options.element);
265 },
266
267 /** @id MochiKit.Sortable.onEnd */
268 onEnd: function (element, draggable) {
269 var self = MochiKit.Sortable;
270 self.unmark();
271 var options = self.options(element);
272 if (options.lastValue != self.serialize(options.element)) {
273 options.onUpdate(options.element);
274 }
275 },
276
277 // return all suitable-for-sortable elements in a guaranteed order
278
279 /** @id MochiKit.Sortable.findElements */
280 findElements: function (element, options) {
281 return MochiKit.Sortable.findChildren(element, options.only, options.tree, options.tag);
282 },
283
284 /** @id MochiKit.Sortable.findTreeElements */
285 findTreeElements: function (element, options) {
286 return MochiKit.Sortable.findChildren(
287 element, options.only, options.tree ? true : false, options.treeTag);
288 },
289
290 /** @id MochiKit.Sortable.findChildren */
291 findChildren: function (element, only, recursive, tagName) {
292 if (!element.hasChildNodes()) {
293 return null;
294 }
295 tagName = tagName.toUpperCase();
296 if (only) {
297 only = MochiKit.Base.flattenArray([only]);
298 }
299 var elements = [];
300 MochiKit.Base.map(function (e) {
301 if (e.tagName &&
302 e.tagName.toUpperCase() == tagName &&
303 (!only ||
304 MochiKit.Iter.some(only, function (c) {
305 return MochiKit.DOM.hasElementClass(e, c);
306 }))) {
307 elements.push(e);
308 }
309 if (recursive) {
310 var grandchildren = MochiKit.Sortable.findChildren(e, only, recursive, tagName);
311 if (grandchildren && grandchildren.length > 0) {
312 elements = elements.concat(grandchildren);
313 }
314 }
315 }, element.childNodes);
316 return elements;
317 },
318
319 /** @id MochiKit.Sortable.onHover */
320 onHover: function (element, dropon, overlap) {
321 if (MochiKit.DOM.isChildNode(dropon, element)) {
322 return;
323 }
324 var self = MochiKit.Sortable;
325
326 if (overlap > .33 && overlap < .66 && self.options(dropon).tree) {
327 return;
328 } else if (overlap > 0.5) {
329 self.mark(dropon, 'before');
330 if (dropon.previousSibling != element) {
331 var oldParentNode = element.parentNode;
332 element.style.visibility = 'hidden'; // fix gecko rendering
333 dropon.parentNode.insertBefore(element, dropon);
334 if (dropon.parentNode != oldParentNode) {
335 self.options(oldParentNode).onChange(element);
336 }
337 self.options(dropon.parentNode).onChange(element);
338 }
339 } else {
340 self.mark(dropon, 'after');
341 var nextElement = dropon.nextSibling || null;
342 if (nextElement != element) {
343 var oldParentNode = element.parentNode;
344 element.style.visibility = 'hidden'; // fix gecko rendering
345 dropon.parentNode.insertBefore(element, nextElement);
346 if (dropon.parentNode != oldParentNode) {
347 self.options(oldParentNode).onChange(element);
348 }
349 self.options(dropon.parentNode).onChange(element);
350 }
351 }
352 },
353
354 _offsetSize: function (element, type) {
355 if (type == 'vertical' || type == 'height') {
356 return element.offsetHeight;
357 } else {
358 return element.offsetWidth;
359 }
360 },
361
362 /** @id MochiKit.Sortable.onEmptyHover */
363 onEmptyHover: function (element, dropon, overlap) {
364 var oldParentNode = element.parentNode;
365 var self = MochiKit.Sortable;
366 var droponOptions = self.options(dropon);
367
368 if (!MochiKit.DOM.isChildNode(dropon, element)) {
369 var index;
370
371 var children = self.findElements(dropon, {tag: droponOptions.tag,
372 only: droponOptions.only});
373 var child = null;
374
375 if (children) {
376 var offset = self._offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
377
378 for (index = 0; index < children.length; index += 1) {
379 if (offset - self._offsetSize(children[index], droponOptions.overlap) >= 0) {
380 offset -= self._offsetSize(children[index], droponOptions.overlap);
381 } else if (offset - (self._offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
382 child = index + 1 < children.length ? children[index + 1] : null;
383 break;
384 } else {
385 child = children[index];
386 break;
387 }
388 }
389 }
390
391 dropon.insertBefore(element, child);
392
393 self.options(oldParentNode).onChange(element);
394 droponOptions.onChange(element);
395 }
396 },
397
398 /** @id MochiKit.Sortable.unmark */
399 unmark: function () {
400 var m = MochiKit.Sortable._marker;
401 if (m) {
402 MochiKit.Style.hideElement(m);
403 }
404 },
405
406 /** @id MochiKit.Sortable.mark */
407 mark: function (dropon, position) {
408 // mark on ghosting only
409 var d = MochiKit.DOM;
410 var self = MochiKit.Sortable;
411 var sortable = self.options(dropon.parentNode);
412 if (sortable && !sortable.ghosting) {
413 return;
414 }
415
416 if (!self._marker) {
417 self._marker = d.getElement('dropmarker') ||
418 document.createElement('DIV');
419 MochiKit.Style.hideElement(self._marker);
420 d.addElementClass(self._marker, 'dropmarker');
421 self._marker.style.position = 'absolute';
422 document.getElementsByTagName('body').item(0).appendChild(self._marker);
423 }
424 var offsets = MochiKit.Position.cumulativeOffset(dropon);
425 self._marker.style.left = offsets.x + 'px';
426 self._marker.style.top = offsets.y + 'px';
427
428 if (position == 'after') {
429 if (sortable.overlap == 'horizontal') {
430 self._marker.style.left = (offsets.x + dropon.clientWidth) + 'px';
431 } else {
432 self._marker.style.top = (offsets.y + dropon.clientHeight) + 'px';
433 }
434 }
435 MochiKit.Style.showElement(self._marker);
436 },
437
438 _tree: function (element, options, parent) {
439 var self = MochiKit.Sortable;
440 var children = self.findElements(element, options) || [];
441
442 for (var i = 0; i < children.length; ++i) {
443 var match = children[i].id.match(options.format);
444
445 if (!match) {
446 continue;
447 }
448
449 var child = {
450 id: encodeURIComponent(match ? match[1] : null),
451 element: element,
452 parent: parent,
453 children: [],
454 position: parent.children.length,
455 container: self._findChildrenElement(children[i], options.treeTag.toUpperCase())
456 };
457
458 /* Get the element containing the children and recurse over it */
459 if (child.container) {
460 self._tree(child.container, options, child);
461 }
462
463 parent.children.push (child);
464 }
465
466 return parent;
467 },
468
469 /* Finds the first element of the given tag type within a parent element.
470 Used for finding the first LI[ST] within a L[IST]I[TEM].*/
471 _findChildrenElement: function (element, containerTag) {
472 if (element && element.hasChildNodes) {
473 containerTag = containerTag.toUpperCase();
474 for (var i = 0; i < element.childNodes.length; ++i) {
475 if (element.childNodes[i].tagName.toUpperCase() == containerTag) {
476 return element.childNodes[i];
477 }
478 }
479 }
480 return null;
481 },
482
483 /** @id MochiKit.Sortable.tree */
484 tree: function (element, options) {
485 element = MochiKit.DOM.getElement(element);
486 var sortableOptions = MochiKit.Sortable.options(element);
487 options = MochiKit.Base.update({
488 tag: sortableOptions.tag,
489 treeTag: sortableOptions.treeTag,
490 only: sortableOptions.only,
491 name: element.id,
492 format: sortableOptions.format
493 }, options || {});
494
495 var root = {
496 id: null,
497 parent: null,
498 children: new Array,
499 container: element,
500 position: 0
501 };
502
503 return MochiKit.Sortable._tree(element, options, root);
504 },
505
506 /**
507 * Specifies the sequence for the Sortable.
508 * @param {Node} element Element to use as the Sortable.
509 * @param {Object} newSequence New sequence to use.
510 * @param {Object} options Options to use fro the Sortable.
511 */
512 setSequence: function (element, newSequence, options) {
513 var self = MochiKit.Sortable;
514 var b = MochiKit.Base;
515 element = MochiKit.DOM.getElement(element);
516 options = b.update(self.options(element), options || {});
517
518 var nodeMap = {};
519 b.map(function (n) {
520 var m = n.id.match(options.format);
521 if (m) {
522 nodeMap[m[1]] = [n, n.parentNode];
523 }
524 n.parentNode.removeChild(n);
525 }, self.findElements(element, options));
526
527 b.map(function (ident) {
528 var n = nodeMap[ident];
529 if (n) {
530 n[1].appendChild(n[0]);
531 delete nodeMap[ident];
532 }
533 }, newSequence);
534 },
535
536 /* Construct a [i] index for a particular node */
537 _constructIndex: function (node) {
538 var index = '';
539 do {
540 if (node.id) {
541 index = '[' + node.position + ']' + index;
542 }
543 } while ((node = node.parent) != null);
544 return index;
545 },
546
547 /** @id MochiKit.Sortable.sequence */
548 sequence: function (element, options) {
549 element = MochiKit.DOM.getElement(element);
550 var self = MochiKit.Sortable;
551 var options = MochiKit.Base.update(self.options(element), options || {});
552
553 return MochiKit.Base.map(function (item) {
554 return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
555 }, MochiKit.DOM.getElement(self.findElements(element, options) || []));
556 },
557
558 /**
559 * Serializes the content of a Sortable. Useful to send this content through a XMLHTTPRequest.
560 * These options override the Sortable options for the serialization only.
561 * @param {Node} element Element to serialize.
562 * @param {Object} options Serialization options.
563 */
564 serialize: function (element, options) {
565 element = MochiKit.DOM.getElement(element);
566 var self = MochiKit.Sortable;
567 options = MochiKit.Base.update(self.options(element), options || {});
568 var name = encodeURIComponent(options.name || element.id);
569
570 if (options.tree) {
571 return MochiKit.Base.flattenArray(MochiKit.Base.map(function (item) {
572 return [name + self._constructIndex(item) + "[id]=" +
573 encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
574 }, self.tree(element, options).children)).join('&');
575 } else {
576 return MochiKit.Base.map(function (item) {
577 return name + "[]=" + encodeURIComponent(item);
578 }, self.sequence(element, options)).join('&');
579 }
580 }
581});
582
583// trunk compatibility
584MochiKit.Sortable.Sortable = MochiKit.Sortable;
585
586MochiKit.Sortable.__new__ = function () {
587 MochiKit.Base.nameFunctions(this);
588};
589
590MochiKit.Sortable.__new__();
591
592MochiKit.Base._exportSymbols(this, MochiKit.Sortable);