OSDN Git Service

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