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