diff options
Diffstat (limited to 'docroot/lib/scriptaculous/dragdrop.js')
-rwxr-xr-x | docroot/lib/scriptaculous/dragdrop.js | 970 |
1 files changed, 970 insertions, 0 deletions
diff --git a/docroot/lib/scriptaculous/dragdrop.js b/docroot/lib/scriptaculous/dragdrop.js new file mode 100755 index 0000000..108dd66 --- /dev/null +++ b/docroot/lib/scriptaculous/dragdrop.js | |||
@@ -0,0 +1,970 @@ | |||
1 | // script.aculo.us dragdrop.js v1.7.1_beta3, Fri May 25 17:19:41 +0200 2007 | ||
2 | |||
3 | // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) | ||
4 | // (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) | ||
5 | // | ||
6 | // script.aculo.us is freely distributable under the terms of an MIT-style license. | ||
7 | // For details, see the script.aculo.us web site: http://script.aculo.us/ | ||
8 | |||
9 | if(typeof Effect == 'undefined') | ||
10 | throw("dragdrop.js requires including script.aculo.us' effects.js library"); | ||
11 | |||
12 | var Droppables = { | ||
13 | drops: [], | ||
14 | |||
15 | remove: function(element) { | ||
16 | this.drops = this.drops.reject(function(d) { return d.element==$(element) }); | ||
17 | }, | ||
18 | |||
19 | add: function(element) { | ||
20 | element = $(element); | ||
21 | var options = Object.extend({ | ||
22 | greedy: true, | ||
23 | hoverclass: null, | ||
24 | tree: false | ||
25 | }, arguments[1] || {}); | ||
26 | |||
27 | // cache containers | ||
28 | if(options.containment) { | ||
29 | options._containers = []; | ||
30 | var containment = options.containment; | ||
31 | if((typeof containment == 'object') && | ||
32 | (containment.constructor == Array)) { | ||
33 | containment.each( function(c) { options._containers.push($(c)) }); | ||
34 | } else { | ||
35 | options._containers.push($(containment)); | ||
36 | } | ||
37 | } | ||
38 | |||
39 | if(options.accept) options.accept = [options.accept].flatten(); | ||
40 | |||
41 | Element.makePositioned(element); // fix IE | ||
42 | options.element = element; | ||
43 | |||
44 | this.drops.push(options); | ||
45 | }, | ||
46 | |||
47 | findDeepestChild: function(drops) { | ||
48 | deepest = drops[0]; | ||
49 | |||
50 | for (i = 1; i < drops.length; ++i) | ||
51 | if (Element.isParent(drops[i].element, deepest.element)) | ||
52 | deepest = drops[i]; | ||
53 | |||
54 | return deepest; | ||
55 | }, | ||
56 | |||
57 | isContained: function(element, drop) { | ||
58 | var containmentNode; | ||
59 | if(drop.tree) { | ||
60 | containmentNode = element.treeNode; | ||
61 | } else { | ||
62 | containmentNode = element.parentNode; | ||
63 | } | ||
64 | return drop._containers.detect(function(c) { return containmentNode == c }); | ||
65 | }, | ||
66 | |||
67 | isAffected: function(point, element, drop) { | ||
68 | return ( | ||
69 | (drop.element!=element) && | ||
70 | ((!drop._containers) || | ||
71 | this.isContained(element, drop)) && | ||
72 | ((!drop.accept) || | ||
73 | (Element.classNames(element).detect( | ||
74 | function(v) { return drop.accept.include(v) } ) )) && | ||
75 | Position.within(drop.element, point[0], point[1]) ); | ||
76 | }, | ||
77 | |||
78 | deactivate: function(drop) { | ||
79 | if(drop.hoverclass) | ||
80 | Element.removeClassName(drop.element, drop.hoverclass); | ||
81 | this.last_active = null; | ||
82 | }, | ||
83 | |||
84 | activate: function(drop) { | ||
85 | if(drop.hoverclass) | ||
86 | Element.addClassName(drop.element, drop.hoverclass); | ||
87 | this.last_active = drop; | ||
88 | }, | ||
89 | |||
90 | show: function(point, element) { | ||
91 | if(!this.drops.length) return; | ||
92 | var affected = []; | ||
93 | |||
94 | if(this.last_active) this.deactivate(this.last_active); | ||
95 | this.drops.each( function(drop) { | ||
96 | if(Droppables.isAffected(point, element, drop)) | ||
97 | affected.push(drop); | ||
98 | }); | ||
99 | |||
100 | if(affected.length>0) { | ||
101 | drop = Droppables.findDeepestChild(affected); | ||
102 | Position.within(drop.element, point[0], point[1]); | ||
103 | if(drop.onHover) | ||
104 | drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); | ||
105 | |||
106 | Droppables.activate(drop); | ||
107 | } | ||
108 | }, | ||
109 | |||
110 | fire: function(event, element) { | ||
111 | if(!this.last_active) return; | ||
112 | Position.prepare(); | ||
113 | |||
114 | if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) | ||
115 | if (this.last_active.onDrop) { | ||
116 | this.last_active.onDrop(element, this.last_active.element, event); | ||
117 | return true; | ||
118 | } | ||
119 | }, | ||
120 | |||
121 | reset: function() { | ||
122 | if(this.last_active) | ||
123 | this.deactivate(this.last_active); | ||
124 | } | ||
125 | } | ||
126 | |||
127 | var Draggables = { | ||
128 | drags: [], | ||
129 | observers: [], | ||
130 | |||
131 | register: function(draggable) { | ||
132 | if(this.drags.length == 0) { | ||
133 | this.eventMouseUp = this.endDrag.bindAsEventListener(this); | ||
134 | this.eventMouseMove = this.updateDrag.bindAsEventListener(this); | ||
135 | this.eventKeypress = this.keyPress.bindAsEventListener(this); | ||
136 | |||
137 | Event.observe(document, "mouseup", this.eventMouseUp); | ||
138 | Event.observe(document, "mousemove", this.eventMouseMove); | ||
139 | Event.observe(document, "keypress", this.eventKeypress); | ||
140 | } | ||
141 | this.drags.push(draggable); | ||
142 | }, | ||
143 | |||
144 | unregister: function(draggable) { | ||
145 | this.drags = this.drags.reject(function(d) { return d==draggable }); | ||
146 | if(this.drags.length == 0) { | ||
147 | Event.stopObserving(document, "mouseup", this.eventMouseUp); | ||
148 | Event.stopObserving(document, "mousemove", this.eventMouseMove); | ||
149 | Event.stopObserving(document, "keypress", this.eventKeypress); | ||
150 | } | ||
151 | }, | ||
152 | |||
153 | activate: function(draggable) { | ||
154 | if(draggable.options.delay) { | ||
155 | this._timeout = setTimeout(function() { | ||
156 | Draggables._timeout = null; | ||
157 | window.focus(); | ||
158 | Draggables.activeDraggable = draggable; | ||
159 | }.bind(this), draggable.options.delay); | ||
160 | } else { | ||
161 | window.focus(); // allows keypress events if window isn't currently focused, fails for Safari | ||
162 | this.activeDraggable = draggable; | ||
163 | } | ||
164 | }, | ||
165 | |||
166 | deactivate: function() { | ||
167 | this.activeDraggable = null; | ||
168 | }, | ||
169 | |||
170 | updateDrag: function(event) { | ||
171 | if(!this.activeDraggable) return; | ||
172 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; | ||
173 | // Mozilla-based browsers fire successive mousemove events with | ||
174 | // the same coordinates, prevent needless redrawing (moz bug?) | ||
175 | if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; | ||
176 | this._lastPointer = pointer; | ||
177 | |||
178 | this.activeDraggable.updateDrag(event, pointer); | ||
179 | }, | ||
180 | |||
181 | endDrag: function(event) { | ||
182 | if(this._timeout) { | ||
183 | clearTimeout(this._timeout); | ||
184 | this._timeout = null; | ||
185 | } | ||
186 | if(!this.activeDraggable) return; | ||
187 | this._lastPointer = null; | ||
188 | this.activeDraggable.endDrag(event); | ||
189 | this.activeDraggable = null; | ||
190 | }, | ||
191 | |||
192 | keyPress: function(event) { | ||
193 | if(this.activeDraggable) | ||
194 | this.activeDraggable.keyPress(event); | ||
195 | }, | ||
196 | |||
197 | addObserver: function(observer) { | ||
198 | this.observers.push(observer); | ||
199 | this._cacheObserverCallbacks(); | ||
200 | }, | ||
201 | |||
202 | removeObserver: function(element) { // element instead of observer fixes mem leaks | ||
203 | this.observers = this.observers.reject( function(o) { return o.element==element }); | ||
204 | this._cacheObserverCallbacks(); | ||
205 | }, | ||
206 | |||
207 | notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' | ||
208 | if(this[eventName+'Count'] > 0) | ||
209 | this.observers.each( function(o) { | ||
210 | if(o[eventName]) o[eventName](eventName, draggable, event); | ||
211 | }); | ||
212 | if(draggable.options[eventName]) draggable.options[eventName](draggable, event); | ||
213 | }, | ||
214 | |||
215 | _cacheObserverCallbacks: function() { | ||
216 | ['onStart','onEnd','onDrag'].each( function(eventName) { | ||
217 | Draggables[eventName+'Count'] = Draggables.observers.select( | ||
218 | function(o) { return o[eventName]; } | ||
219 | ).length; | ||
220 | }); | ||
221 | } | ||
222 | } | ||
223 | |||
224 | /*--------------------------------------------------------------------------*/ | ||
225 | |||
226 | var Draggable = Class.create(); | ||
227 | Draggable._dragging = {}; | ||
228 | |||
229 | Draggable.prototype = { | ||
230 | initialize: function(element) { | ||
231 | var defaults = { | ||
232 | handle: false, | ||
233 | reverteffect: function(element, top_offset, left_offset) { | ||
234 | var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; | ||
235 | new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, | ||
236 | queue: {scope:'_draggable', position:'end'} | ||
237 | }); | ||
238 | }, | ||
239 | endeffect: function(element) { | ||
240 | var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0; | ||
241 | new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, | ||
242 | queue: {scope:'_draggable', position:'end'}, | ||
243 | afterFinish: function(){ | ||
244 | Draggable._dragging[element] = false | ||
245 | } | ||
246 | }); | ||
247 | }, | ||
248 | zindex: 1000, | ||
249 | revert: false, | ||
250 | quiet: false, | ||
251 | scroll: false, | ||
252 | scrollSensitivity: 20, | ||
253 | scrollSpeed: 15, | ||
254 | snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } | ||
255 | delay: 0 | ||
256 | }; | ||
257 | |||
258 | if(!arguments[1] || typeof arguments[1].endeffect == 'undefined') | ||
259 | Object.extend(defaults, { | ||
260 | starteffect: function(element) { | ||
261 | element._opacity = Element.getOpacity(element); | ||
262 | Draggable._dragging[element] = true; | ||
263 | new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); | ||
264 | } | ||
265 | }); | ||
266 | |||
267 | var options = Object.extend(defaults, arguments[1] || {}); | ||
268 | |||
269 | this.element = $(element); | ||
270 | |||
271 | if(options.handle && (typeof options.handle == 'string')) | ||
272 | this.handle = this.element.down('.'+options.handle, 0); | ||
273 | |||
274 | if(!this.handle) this.handle = $(options.handle); | ||
275 | if(!this.handle) this.handle = this.element; | ||
276 | |||
277 | if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { | ||
278 | options.scroll = $(options.scroll); | ||
279 | this._isScrollChild = Element.childOf(this.element, options.scroll); | ||
280 | } | ||
281 | |||
282 | Element.makePositioned(this.element); // fix IE | ||
283 | |||
284 | this.delta = this.currentDelta(); | ||
285 | this.options = options; | ||
286 | this.dragging = false; | ||
287 | |||
288 | this.eventMouseDown = this.initDrag.bindAsEventListener(this); | ||
289 | Event.observe(this.handle, "mousedown", this.eventMouseDown); | ||
290 | |||
291 | Draggables.register(this); | ||
292 | }, | ||
293 | |||
294 | destroy: function() { | ||
295 | Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); | ||
296 | Draggables.unregister(this); | ||
297 | }, | ||
298 | |||
299 | currentDelta: function() { | ||
300 | return([ | ||
301 | parseInt(Element.getStyle(this.element,'left') || '0'), | ||
302 | parseInt(Element.getStyle(this.element,'top') || '0')]); | ||
303 | }, | ||
304 | |||
305 | initDrag: function(event) { | ||
306 | if(typeof Draggable._dragging[this.element] != 'undefined' && | ||
307 | Draggable._dragging[this.element]) return; | ||
308 | if(Event.isLeftClick(event)) { | ||
309 | // abort on form elements, fixes a Firefox issue | ||
310 | var src = Event.element(event); | ||
311 | if((tag_name = src.tagName.toUpperCase()) && ( | ||
312 | tag_name=='INPUT' || | ||
313 | tag_name=='SELECT' || | ||
314 | tag_name=='OPTION' || | ||
315 | tag_name=='BUTTON' || | ||
316 | tag_name=='TEXTAREA')) return; | ||
317 | |||
318 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; | ||
319 | var pos = Position.cumulativeOffset(this.element); | ||
320 | this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); | ||
321 | |||
322 | Draggables.activate(this); | ||
323 | Event.stop(event); | ||
324 | } | ||
325 | }, | ||
326 | |||
327 | startDrag: function(event) { | ||
328 | this.dragging = true; | ||
329 | |||
330 | if(this.options.zindex) { | ||
331 | this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); | ||
332 | this.element.style.zIndex = this.options.zindex; | ||
333 | } | ||
334 | |||
335 | if(this.options.ghosting) { | ||
336 | this._clone = this.element.cloneNode(true); | ||
337 | Position.absolutize(this.element); | ||
338 | this.element.parentNode.insertBefore(this._clone, this.element); | ||
339 | } | ||
340 | |||
341 | if(this.options.scroll) { | ||
342 | if (this.options.scroll == window) { | ||
343 | var where = this._getWindowScroll(this.options.scroll); | ||
344 | this.originalScrollLeft = where.left; | ||
345 | this.originalScrollTop = where.top; | ||
346 | } else { | ||
347 | this.originalScrollLeft = this.options.scroll.scrollLeft; | ||
348 | this.originalScrollTop = this.options.scroll.scrollTop; | ||
349 | } | ||
350 | } | ||
351 | |||
352 | Draggables.notify('onStart', this, event); | ||
353 | |||
354 | if(this.options.starteffect) this.options.starteffect(this.element); | ||
355 | }, | ||
356 | |||
357 | updateDrag: function(event, pointer) { | ||
358 | if(!this.dragging) this.startDrag(event); | ||
359 | |||
360 | if(!this.options.quiet){ | ||
361 | Position.prepare(); | ||
362 | Droppables.show(pointer, this.element); | ||
363 | } | ||
364 | |||
365 | Draggables.notify('onDrag', this, event); | ||
366 | |||
367 | this.draw(pointer); | ||
368 | if(this.options.change) this.options.change(this); | ||
369 | |||
370 | if(this.options.scroll) { | ||
371 | this.stopScrolling(); | ||
372 | |||
373 | var p; | ||
374 | if (this.options.scroll == window) { | ||
375 | with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } | ||
376 | } else { | ||
377 | p = Position.page(this.options.scroll); | ||
378 | p[0] += this.options.scroll.scrollLeft + Position.deltaX; | ||
379 | p[1] += this.options.scroll.scrollTop + Position.deltaY; | ||
380 | p.push(p[0]+this.options.scroll.offsetWidth); | ||
381 | p.push(p[1]+this.options.scroll.offsetHeight); | ||
382 | } | ||
383 | var speed = [0,0]; | ||
384 | if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); | ||
385 | if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); | ||
386 | if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); | ||
387 | if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); | ||
388 | this.startScrolling(speed); | ||
389 | } | ||
390 | |||
391 | // fix AppleWebKit rendering | ||
392 | if(Prototype.Browser.WebKit) window.scrollBy(0,0); | ||
393 | |||
394 | Event.stop(event); | ||
395 | }, | ||
396 | |||
397 | finishDrag: function(event, success) { | ||
398 | this.dragging = false; | ||
399 | |||
400 | if(this.options.quiet){ | ||
401 | Position.prepare(); | ||
402 | var pointer = [Event.pointerX(event), Event.pointerY(event)]; | ||
403 | Droppables.show(pointer, this.element); | ||
404 | } | ||
405 | |||
406 | if(this.options.ghosting) { | ||
407 | Position.relativize(this.element); | ||
408 | Element.remove(this._clone); | ||
409 | this._clone = null; | ||
410 | } | ||
411 | |||
412 | var dropped = false; | ||
413 | if(success) { | ||
414 | dropped = Droppables.fire(event, this.element); | ||
415 | if (!dropped) dropped = false; | ||
416 | } | ||
417 | if(dropped && this.options.onDropped) this.options.onDropped(this.element); | ||
418 | Draggables.notify('onEnd', this, event); | ||
419 | |||
420 | var revert = this.options.revert; | ||
421 | if(revert && typeof revert == 'function') revert = revert(this.element); | ||
422 | |||
423 | var d = this.currentDelta(); | ||
424 | if(revert && this.options.reverteffect) { | ||
425 | if (dropped == 0 || revert != 'failure') | ||
426 | this.options.reverteffect(this.element, | ||
427 | d[1]-this.delta[1], d[0]-this.delta[0]); | ||
428 | } else { | ||
429 | this.delta = d; | ||
430 | } | ||
431 | |||
432 | if(this.options.zindex) | ||
433 | this.element.style.zIndex = this.originalZ; | ||
434 | |||
435 | if(this.options.endeffect) | ||
436 | this.options.endeffect(this.element); | ||
437 | |||
438 | Draggables.deactivate(this); | ||
439 | Droppables.reset(); | ||
440 | }, | ||
441 | |||
442 | keyPress: function(event) { | ||
443 | if(event.keyCode!=Event.KEY_ESC) return; | ||
444 | this.finishDrag(event, false); | ||
445 | Event.stop(event); | ||
446 | }, | ||
447 | |||
448 | endDrag: function(event) { | ||
449 | if(!this.dragging) return; | ||
450 | this.stopScrolling(); | ||
451 | this.finishDrag(event, true); | ||
452 | Event.stop(event); | ||
453 | }, | ||
454 | |||
455 | draw: function(point) { | ||
456 | var pos = Position.cumulativeOffset(this.element); | ||
457 | if(this.options.ghosting) { | ||
458 | var r = Position.realOffset(this.element); | ||
459 | pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; | ||
460 | } | ||
461 | |||
462 | var d = this.currentDelta(); | ||
463 | pos[0] -= d[0]; pos[1] -= d[1]; | ||
464 | |||
465 | if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { | ||
466 | pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; | ||
467 | pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; | ||
468 | } | ||
469 | |||
470 | var p = [0,1].map(function(i){ | ||
471 | return (point[i]-pos[i]-this.offset[i]) | ||
472 | }.bind(this)); | ||
473 | |||
474 | if(this.options.snap) { | ||
475 | if(typeof this.options.snap == 'function') { | ||
476 | p = this.options.snap(p[0],p[1],this); | ||
477 | } else { | ||
478 | if(this.options.snap instanceof Array) { | ||
479 | p = p.map( function(v, i) { | ||
480 | return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) | ||
481 | } else { | ||
482 | p = p.map( function(v) { | ||
483 | return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) | ||
484 | } | ||
485 | }} | ||
486 | |||
487 | var style = this.element.style; | ||
488 | if((!this.options.constraint) || (this.options.constraint=='horizontal')) | ||
489 | style.left = p[0] + "px"; | ||
490 | if((!this.options.constraint) || (this.options.constraint=='vertical')) | ||
491 | style.top = p[1] + "px"; | ||
492 | |||
493 | if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering | ||
494 | }, | ||
495 | |||
496 | stopScrolling: function() { | ||
497 | if(this.scrollInterval) { | ||
498 | clearInterval(this.scrollInterval); | ||
499 | this.scrollInterval = null; | ||
500 | Draggables._lastScrollPointer = null; | ||
501 | } | ||
502 | }, | ||
503 | |||
504 | startScrolling: function(speed) { | ||
505 | if(!(speed[0] || speed[1])) return; | ||
506 | this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; | ||
507 | this.lastScrolled = new Date(); | ||
508 | this.scrollInterval = setInterval(this.scroll.bind(this), 10); | ||
509 | }, | ||
510 | |||
511 | scroll: function() { | ||
512 | var current = new Date(); | ||
513 | var delta = current - this.lastScrolled; | ||
514 | this.lastScrolled = current; | ||
515 | if(this.options.scroll == window) { | ||
516 | with (this._getWindowScroll(this.options.scroll)) { | ||
517 | if (this.scrollSpeed[0] || this.scrollSpeed[1]) { | ||
518 | var d = delta / 1000; | ||
519 | this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); | ||
520 | } | ||
521 | } | ||
522 | } else { | ||
523 | this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; | ||
524 | this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; | ||
525 | } | ||
526 | |||
527 | Position.prepare(); | ||
528 | Droppables.show(Draggables._lastPointer, this.element); | ||
529 | Draggables.notify('onDrag', this); | ||
530 | if (this._isScrollChild) { | ||
531 | Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); | ||
532 | Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; | ||
533 | Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; | ||
534 | if (Draggables._lastScrollPointer[0] < 0) | ||
535 | Draggables._lastScrollPointer[0] = 0; | ||
536 | if (Draggables._lastScrollPointer[1] < 0) | ||
537 | Draggables._lastScrollPointer[1] = 0; | ||
538 | this.draw(Draggables._lastScrollPointer); | ||
539 | } | ||
540 | |||
541 | if(this.options.change) this.options.change(this); | ||
542 | }, | ||
543 | |||
544 | _getWindowScroll: function(w) { | ||
545 | var T, L, W, H; | ||
546 | with (w.document) { | ||
547 | if (w.document.documentElement && documentElement.scrollTop) { | ||
548 | T = documentElement.scrollTop; | ||
549 | L = documentElement.scrollLeft; | ||
550 | } else if (w.document.body) { | ||
551 | T = body.scrollTop; | ||
552 | L = body.scrollLeft; | ||
553 | } | ||
554 | if (w.innerWidth) { | ||
555 | W = w.innerWidth; | ||
556 | H = w.innerHeight; | ||
557 | } else if (w.document.documentElement && documentElement.clientWidth) { | ||
558 | W = documentElement.clientWidth; | ||
559 | H = documentElement.clientHeight; | ||
560 | } else { | ||
561 | W = body.offsetWidth; | ||
562 | H = body.offsetHeight | ||
563 | } | ||
564 | } | ||
565 | return { top: T, left: L, width: W, height: H }; | ||
566 | } | ||
567 | } | ||
568 | |||
569 | /*--------------------------------------------------------------------------*/ | ||
570 | |||
571 | var SortableObserver = Class.create(); | ||
572 | SortableObserver.prototype = { | ||
573 | initialize: function(element, observer) { | ||
574 | this.element = $(element); | ||
575 | this.observer = observer; | ||
576 | this.lastValue = Sortable.serialize(this.element); | ||
577 | }, | ||
578 | |||
579 | onStart: function() { | ||
580 | this.lastValue = Sortable.serialize(this.element); | ||
581 | }, | ||
582 | |||
583 | onEnd: function() { | ||
584 | Sortable.unmark(); | ||
585 | if(this.lastValue != Sortable.serialize(this.element)) | ||
586 | this.observer(this.element) | ||
587 | } | ||
588 | } | ||
589 | |||
590 | var Sortable = { | ||
591 | SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, | ||
592 | |||
593 | sortables: {}, | ||
594 | |||
595 | _findRootElement: function(element) { | ||
596 | while (element.tagName.toUpperCase() != "BODY") { | ||
597 | if(element.id && Sortable.sortables[element.id]) return element; | ||
598 | element = element.parentNode; | ||
599 | } | ||
600 | }, | ||
601 | |||
602 | options: function(element) { | ||
603 | element = Sortable._findRootElement($(element)); | ||
604 | if(!element) return; | ||
605 | return Sortable.sortables[element.id]; | ||
606 | }, | ||
607 | |||
608 | destroy: function(element){ | ||
609 | var s = Sortable.options(element); | ||
610 | |||
611 | if(s) { | ||
612 | Draggables.removeObserver(s.element); | ||
613 | s.droppables.each(function(d){ Droppables.remove(d) }); | ||
614 | s.draggables.invoke('destroy'); | ||
615 | |||
616 | delete Sortable.sortables[s.element.id]; | ||
617 | } | ||
618 | }, | ||
619 | |||
620 | create: function(element) { | ||
621 | element = $(element); | ||
622 | var options = Object.extend({ | ||
623 | element: element, | ||
624 | tag: 'li', // assumes li children, override with tag: 'tagname' | ||
625 | dropOnEmpty: false, | ||
626 | tree: false, | ||
627 | treeTag: 'ul', | ||
628 | overlap: 'vertical', // one of 'vertical', 'horizontal' | ||
629 | constraint: 'vertical', // one of 'vertical', 'horizontal', false | ||
630 | containment: element, // also takes array of elements (or id's); or false | ||
631 | handle: false, // or a CSS class | ||
632 | only: false, | ||
633 | delay: 0, | ||
634 | hoverclass: null, | ||
635 | ghosting: false, | ||
636 | quiet: false, | ||
637 | scroll: false, | ||
638 | scrollSensitivity: 20, | ||
639 | scrollSpeed: 15, | ||
640 | format: this.SERIALIZE_RULE, | ||
641 | |||
642 | // these take arrays of elements or ids and can be | ||
643 | // used for better initialization performance | ||
644 | elements: false, | ||
645 | handles: false, | ||
646 | |||
647 | onChange: Prototype.emptyFunction, | ||
648 | onUpdate: Prototype.emptyFunction | ||
649 | }, arguments[1] || {}); | ||
650 | |||
651 | // clear any old sortable with same element | ||
652 | this.destroy(element); | ||
653 | |||
654 | // build options for the draggables | ||
655 | var options_for_draggable = { | ||
656 | revert: true, | ||
657 | quiet: options.quiet, | ||
658 | scroll: options.scroll, | ||
659 | scrollSpeed: options.scrollSpeed, | ||
660 | scrollSensitivity: options.scrollSensitivity, | ||
661 | delay: options.delay, | ||
662 | ghosting: options.ghosting, | ||
663 | constraint: options.constraint, | ||
664 | handle: options.handle }; | ||
665 | |||
666 | if(options.starteffect) | ||
667 | options_for_draggable.starteffect = options.starteffect; | ||
668 | |||
669 | if(options.reverteffect) | ||
670 | options_for_draggable.reverteffect = options.reverteffect; | ||
671 | else | ||
672 | if(options.ghosting) options_for_draggable.reverteffect = function(element) { | ||
673 | element.style.top = 0; | ||
674 | element.style.left = 0; | ||
675 | }; | ||
676 | |||
677 | if(options.endeffect) | ||
678 | options_for_draggable.endeffect = options.endeffect; | ||
679 | |||
680 | if(options.zindex) | ||
681 | options_for_draggable.zindex = options.zindex; | ||
682 | |||
683 | // build options for the droppables | ||
684 | var options_for_droppable = { | ||
685 | overlap: options.overlap, | ||
686 | containment: options.containment, | ||
687 | tree: options.tree, | ||
688 | hoverclass: options.hoverclass, | ||
689 | onHover: Sortable.onHover | ||
690 | } | ||
691 | |||
692 | var options_for_tree = { | ||
693 | onHover: Sortable.onEmptyHover, | ||
694 | overlap: options.overlap, | ||
695 | containment: options.containment, | ||
696 | hoverclass: options.hoverclass | ||
697 | } | ||
698 | |||
699 | // fix for gecko engine | ||
700 | Element.cleanWhitespace(element); | ||
701 | |||
702 | options.draggables = []; | ||
703 | options.droppables = []; | ||
704 | |||
705 | // drop on empty handling | ||
706 | if(options.dropOnEmpty || options.tree) { | ||
707 | Droppables.add(element, options_for_tree); | ||
708 | options.droppables.push(element); | ||
709 | } | ||
710 | |||
711 | (options.elements || this.findElements(element, options) || []).each( function(e,i) { | ||
712 | var handle = options.handles ? $(options.handles[i]) : | ||
713 | (options.handle ? $(e).getElementsByClassName(options.handle)[0] : e); | ||
714 | options.draggables.push( | ||
715 | new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); | ||
716 | Droppables.add(e, options_for_droppable); | ||
717 | if(options.tree) e.treeNode = element; | ||
718 | options.droppables.push(e); | ||
719 | }); | ||
720 | |||
721 | if(options.tree) { | ||
722 | (Sortable.findTreeElements(element, options) || []).each( function(e) { | ||
723 | Droppables.add(e, options_for_tree); | ||
724 | e.treeNode = element; | ||
725 | options.droppables.push(e); | ||
726 | }); | ||
727 | } | ||
728 | |||
729 | // keep reference | ||
730 | this.sortables[element.id] = options; | ||
731 | |||
732 | // for onupdate | ||
733 | Draggables.addObserver(new SortableObserver(element, options.onUpdate)); | ||
734 | |||
735 | }, | ||
736 | |||
737 | // return all suitable-for-sortable elements in a guaranteed order | ||
738 | findElements: function(element, options) { | ||
739 | return Element.findChildren( | ||
740 | element, options.only, options.tree ? true : false, options.tag); | ||
741 | }, | ||
742 | |||
743 | findTreeElements: function(element, options) { | ||
744 | return Element.findChildren( | ||
745 | element, options.only, options.tree ? true : false, options.treeTag); | ||
746 | }, | ||
747 | |||
748 | onHover: function(element, dropon, overlap) { | ||
749 | if(Element.isParent(dropon, element)) return; | ||
750 | |||
751 | if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { | ||
752 | return; | ||
753 | } else if(overlap>0.5) { | ||
754 | Sortable.mark(dropon, 'before'); | ||
755 | if(dropon.previousSibling != element) { | ||
756 | var oldParentNode = element.parentNode; | ||
757 | element.style.visibility = "hidden"; // fix gecko rendering | ||
758 | dropon.parentNode.insertBefore(element, dropon); | ||
759 | if(dropon.parentNode!=oldParentNode) | ||
760 | Sortable.options(oldParentNode).onChange(element); | ||
761 | Sortable.options(dropon.parentNode).onChange(element); | ||
762 | } | ||
763 | } else { | ||
764 | Sortable.mark(dropon, 'after'); | ||
765 | var nextElement = dropon.nextSibling || null; | ||
766 | if(nextElement != element) { | ||
767 | var oldParentNode = element.parentNode; | ||
768 | element.style.visibility = "hidden"; // fix gecko rendering | ||
769 | dropon.parentNode.insertBefore(element, nextElement); | ||
770 | if(dropon.parentNode!=oldParentNode) | ||
771 | Sortable.options(oldParentNode).onChange(element); | ||
772 | Sortable.options(dropon.parentNode).onChange(element); | ||
773 | } | ||
774 | } | ||
775 | }, | ||
776 | |||
777 | onEmptyHover: function(element, dropon, overlap) { | ||
778 | var oldParentNode = element.parentNode; | ||
779 | var droponOptions = Sortable.options(dropon); | ||
780 | |||
781 | if(!Element.isParent(dropon, element)) { | ||
782 | var index; | ||
783 | |||
784 | var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); | ||
785 | var child = null; | ||
786 | |||
787 | if(children) { | ||
788 | var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); | ||
789 | |||
790 | for (index = 0; index < children.length; index += 1) { | ||
791 | if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { | ||
792 | offset -= Element.offsetSize (children[index], droponOptions.overlap); | ||
793 | } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { | ||
794 | child = index + 1 < children.length ? children[index + 1] : null; | ||
795 | break; | ||
796 | } else { | ||
797 | child = children[index]; | ||
798 | break; | ||
799 | } | ||
800 | } | ||
801 | } | ||
802 | |||
803 | dropon.insertBefore(element, child); | ||
804 | |||
805 | Sortable.options(oldParentNode).onChange(element); | ||
806 | droponOptions.onChange(element); | ||
807 | } | ||
808 | }, | ||
809 | |||
810 | unmark: function() { | ||
811 | if(Sortable._marker) Sortable._marker.hide(); | ||
812 | }, | ||
813 | |||
814 | mark: function(dropon, position) { | ||
815 | // mark on ghosting only | ||
816 | var sortable = Sortable.options(dropon.parentNode); | ||
817 | if(sortable && !sortable.ghosting) return; | ||
818 | |||
819 | if(!Sortable._marker) { | ||
820 | Sortable._marker = | ||
821 | ($('dropmarker') || Element.extend(document.createElement('DIV'))). | ||
822 | hide().addClassName('dropmarker').setStyle({position:'absolute'}); | ||
823 | document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); | ||
824 | } | ||
825 | var offsets = Position.cumulativeOffset(dropon); | ||
826 | Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); | ||
827 | |||
828 | if(position=='after') | ||
829 | if(sortable.overlap == 'horizontal') | ||
830 | Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); | ||
831 | else | ||
832 | Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); | ||
833 | |||
834 | Sortable._marker.show(); | ||
835 | }, | ||
836 | |||
837 | _tree: function(element, options, parent) { | ||
838 | var children = Sortable.findElements(element, options) || []; | ||
839 | |||
840 | for (var i = 0; i < children.length; ++i) { | ||
841 | var match = children[i].id.match(options.format); | ||
842 | |||
843 | if (!match) continue; | ||
844 | |||
845 | var child = { | ||
846 | id: encodeURIComponent(match ? match[1] : null), | ||
847 | element: element, | ||
848 | parent: parent, | ||
849 | children: [], | ||
850 | position: parent.children.length, | ||
851 | container: $(children[i]).down(options.treeTag) | ||
852 | } | ||
853 | |||
854 | /* Get the element containing the children and recurse over it */ | ||
855 | if (child.container) | ||
856 | this._tree(child.container, options, child) | ||
857 | |||
858 | parent.children.push (child); | ||
859 | } | ||
860 | |||
861 | return parent; | ||
862 | }, | ||
863 | |||
864 | tree: function(element) { | ||
865 | element = $(element); | ||
866 | var sortableOptions = this.options(element); | ||
867 | var options = Object.extend({ | ||
868 | tag: sortableOptions.tag, | ||
869 | treeTag: sortableOptions.treeTag, | ||
870 | only: sortableOptions.only, | ||
871 | name: element.id, | ||
872 | format: sortableOptions.format | ||
873 | }, arguments[1] || {}); | ||
874 | |||
875 | var root = { | ||
876 | id: null, | ||
877 | parent: null, | ||
878 | children: [], | ||
879 | container: element, | ||
880 | position: 0 | ||
881 | } | ||
882 | |||
883 | return Sortable._tree(element, options, root); | ||
884 | }, | ||
885 | |||
886 | /* Construct a [i] index for a particular node */ | ||
887 | _constructIndex: function(node) { | ||
888 | var index = ''; | ||
889 | do { | ||
890 | if (node.id) index = '[' + node.position + ']' + index; | ||
891 | } while ((node = node.parent) != null); | ||
892 | return index; | ||
893 | }, | ||
894 | |||
895 | sequence: function(element) { | ||
896 | element = $(element); | ||
897 | var options = Object.extend(this.options(element), arguments[1] || {}); | ||
898 | |||
899 | return $(this.findElements(element, options) || []).map( function(item) { | ||
900 | return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; | ||
901 | }); | ||
902 | }, | ||
903 | |||
904 | setSequence: function(element, new_sequence) { | ||
905 | element = $(element); | ||
906 | var options = Object.extend(this.options(element), arguments[2] || {}); | ||
907 | |||
908 | var nodeMap = {}; | ||
909 | this.findElements(element, options).each( function(n) { | ||
910 | if (n.id.match(options.format)) | ||
911 | nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; | ||
912 | n.parentNode.removeChild(n); | ||
913 | }); | ||
914 | |||
915 | new_sequence.each(function(ident) { | ||
916 | var n = nodeMap[ident]; | ||
917 | if (n) { | ||
918 | n[1].appendChild(n[0]); | ||
919 | delete nodeMap[ident]; | ||
920 | } | ||
921 | }); | ||
922 | }, | ||
923 | |||
924 | serialize: function(element) { | ||
925 | element = $(element); | ||
926 | var options = Object.extend(Sortable.options(element), arguments[1] || {}); | ||
927 | var name = encodeURIComponent( | ||
928 | (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); | ||
929 | |||
930 | if (options.tree) { | ||
931 | return Sortable.tree(element, arguments[1]).children.map( function (item) { | ||
932 | return [name + Sortable._constructIndex(item) + "[id]=" + | ||
933 | encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); | ||
934 | }).flatten().join('&'); | ||
935 | } else { | ||
936 | return Sortable.sequence(element, arguments[1]).map( function(item) { | ||
937 | return name + "[]=" + encodeURIComponent(item); | ||
938 | }).join('&'); | ||
939 | } | ||
940 | } | ||
941 | } | ||
942 | |||
943 | // Returns true if child is contained within element | ||
944 | Element.isParent = function(child, element) { | ||
945 | if (!child.parentNode || child == element) return false; | ||
946 | if (child.parentNode == element) return true; | ||
947 | return Element.isParent(child.parentNode, element); | ||
948 | } | ||
949 | |||
950 | Element.findChildren = function(element, only, recursive, tagName) { | ||
951 | if(!element.hasChildNodes()) return null; | ||
952 | tagName = tagName.toUpperCase(); | ||
953 | if(only) only = [only].flatten(); | ||
954 | var elements = []; | ||
955 | $A(element.childNodes).each( function(e) { | ||
956 | if(e.tagName && e.tagName.toUpperCase()==tagName && | ||
957 | (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) | ||
958 | elements.push(e); | ||
959 | if(recursive) { | ||
960 | var grandchildren = Element.findChildren(e, only, recursive, tagName); | ||
961 | if(grandchildren) elements.push(grandchildren); | ||
962 | } | ||
963 | }); | ||
964 | |||
965 | return (elements.length>0 ? elements.flatten() : []); | ||
966 | } | ||
967 | |||
968 | Element.offsetSize = function (element, type) { | ||
969 | return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; | ||
970 | } | ||