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