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