/[webpac2]/Webpacus/root/js/dragdrop.js
This is repository of my old source code which isn't updated any more. Go to git.rot13.org for current projects!
ViewVC logotype

Annotation of /Webpacus/root/js/dragdrop.js

Parent Directory Parent Directory | Revision Log Revision Log


Revision 203 - (hide annotations)
Fri Dec 2 23:01:25 2005 UTC (18 years, 5 months ago) by dpavlin
File MIME type: text/cpp
File size: 17555 byte(s)
 r11427@llin:  dpavlin | 2005-12-02 23:58:03 +0100
 huge update: new upstream HTML::Prototype and handling of Ajax.Autocompleter mode: enter
 now *ALWAYS* trigger search, while accepting suggestions is made using tab.
 Also, list doesn't wrap. [0.12]
 

1 dpavlin 83 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 dpavlin 203 //
3     // See scriptaculous.js for full license.
4 dpavlin 83
5     /*--------------------------------------------------------------------------*/
6    
7     var Droppables = {
8 dpavlin 203 drops: [],
9    
10 dpavlin 83 remove: function(element) {
11 dpavlin 203 this.drops = this.drops.reject(function(d) { return d.element==element });
12 dpavlin 83 },
13 dpavlin 203
14 dpavlin 83 add: function(element) {
15 dpavlin 203 element = $(element);
16 dpavlin 83 var options = Object.extend({
17     greedy: true,
18 dpavlin 203 hoverclass: null
19 dpavlin 83 }, arguments[1] || {});
20 dpavlin 203
21 dpavlin 83 // cache containers
22     if(options.containment) {
23 dpavlin 203 options._containers = [];
24 dpavlin 83 var containment = options.containment;
25 dpavlin 203 if((typeof containment == 'object') &&
26 dpavlin 83 (containment.constructor == Array)) {
27 dpavlin 203 containment.each( function(c) { options._containers.push($(c)) });
28 dpavlin 83 } else {
29     options._containers.push($(containment));
30     }
31     }
32 dpavlin 203
33     if(options.accept) options.accept = [options.accept].flatten();
34    
35 dpavlin 83 Element.makePositioned(element); // fix IE
36     options.element = element;
37 dpavlin 203
38 dpavlin 83 this.drops.push(options);
39     },
40 dpavlin 203
41     isContained: function(element, drop) {
42 dpavlin 83 var parentNode = element.parentNode;
43 dpavlin 203 return drop._containers.detect(function(c) { return parentNode == c });
44 dpavlin 83 },
45 dpavlin 203
46     isAffected: function(pX, pY, element, drop) {
47 dpavlin 83 return (
48     (drop.element!=element) &&
49     ((!drop._containers) ||
50 dpavlin 203 this.isContained(element, drop)) &&
51 dpavlin 83 ((!drop.accept) ||
52 dpavlin 203 (Element.classNames(element).detect(
53     function(v) { return drop.accept.include(v) } ) )) &&
54 dpavlin 83 Position.within(drop.element, pX, pY) );
55     },
56 dpavlin 203
57 dpavlin 83 deactivate: function(drop) {
58 dpavlin 203 if(drop.hoverclass)
59     Element.removeClassName(drop.element, drop.hoverclass);
60 dpavlin 83 this.last_active = null;
61     },
62 dpavlin 203
63 dpavlin 83 activate: function(drop) {
64     if(this.last_active) this.deactivate(this.last_active);
65 dpavlin 203 if(drop.hoverclass)
66     Element.addClassName(drop.element, drop.hoverclass);
67     this.last_active = drop;
68 dpavlin 83 },
69 dpavlin 203
70 dpavlin 83 show: function(event, element) {
71 dpavlin 203 if(!this.drops.length) return;
72 dpavlin 83 var pX = Event.pointerX(event);
73     var pY = Event.pointerY(event);
74     Position.prepare();
75 dpavlin 203
76 dpavlin 83 var i = this.drops.length-1; do {
77     var drop = this.drops[i];
78 dpavlin 203 if(this.isAffected(pX, pY, element, drop)) {
79 dpavlin 83 if(drop.onHover)
80     drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
81 dpavlin 203 if(drop.greedy) {
82 dpavlin 83 this.activate(drop);
83     return;
84     }
85     }
86     } while (i--);
87 dpavlin 203
88     if(this.last_active) this.deactivate(this.last_active);
89 dpavlin 83 },
90 dpavlin 203
91 dpavlin 83 fire: function(event, element) {
92     if(!this.last_active) return;
93     Position.prepare();
94 dpavlin 203
95     if (this.isAffected(Event.pointerX(event), Event.pointerY(event), element, this.last_active))
96     if (this.last_active.onDrop)
97     this.last_active.onDrop(element, this.last_active.element, event);
98 dpavlin 83 },
99 dpavlin 203
100 dpavlin 83 reset: function() {
101     if(this.last_active)
102     this.deactivate(this.last_active);
103     }
104     }
105    
106 dpavlin 203 var Draggables = {
107     observers: [],
108 dpavlin 83 addObserver: function(observer) {
109 dpavlin 203 this.observers.push(observer);
110     this._cacheObserverCallbacks();
111 dpavlin 83 },
112 dpavlin 203 removeObserver: function(element) { // element instead of observer fixes mem leaks
113     this.observers = this.observers.reject( function(o) { return o.element==element });
114     this._cacheObserverCallbacks();
115 dpavlin 83 },
116 dpavlin 203 notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
117     if(this[eventName+'Count'] > 0)
118     this.observers.each( function(o) {
119     if(o[eventName]) o[eventName](eventName, draggable, event);
120     });
121     },
122     _cacheObserverCallbacks: function() {
123     ['onStart','onEnd','onDrag'].each( function(eventName) {
124     Draggables[eventName+'Count'] = Draggables.observers.select(
125     function(o) { return o[eventName]; }
126     ).length;
127     });
128 dpavlin 83 }
129     }
130    
131     /*--------------------------------------------------------------------------*/
132    
133 dpavlin 203 var Draggable = Class.create();
134 dpavlin 83 Draggable.prototype = {
135     initialize: function(element) {
136     var options = Object.extend({
137     handle: false,
138 dpavlin 203 starteffect: function(element) {
139     new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
140 dpavlin 83 },
141     reverteffect: function(element, top_offset, left_offset) {
142 dpavlin 203 var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
143     new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
144 dpavlin 83 },
145 dpavlin 203 endeffect: function(element) {
146     new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
147 dpavlin 83 },
148     zindex: 1000,
149     revert: false
150     }, arguments[1] || {});
151 dpavlin 203
152 dpavlin 83 this.element = $(element);
153 dpavlin 203 if(options.handle && (typeof options.handle == 'string'))
154     this.handle = Element.childrenWithClassName(this.element, options.handle)[0];
155    
156     if(!this.handle) this.handle = $(options.handle);
157     if(!this.handle) this.handle = this.element;
158    
159 dpavlin 83 Element.makePositioned(this.element); // fix IE
160 dpavlin 203
161 dpavlin 83 this.offsetX = 0;
162     this.offsetY = 0;
163     this.originalLeft = this.currentLeft();
164     this.originalTop = this.currentTop();
165     this.originalX = this.element.offsetLeft;
166     this.originalY = this.element.offsetTop;
167 dpavlin 203
168 dpavlin 83 this.options = options;
169 dpavlin 203
170 dpavlin 83 this.active = false;
171 dpavlin 203 this.dragging = false;
172    
173 dpavlin 83 this.eventMouseDown = this.startDrag.bindAsEventListener(this);
174     this.eventMouseUp = this.endDrag.bindAsEventListener(this);
175     this.eventMouseMove = this.update.bindAsEventListener(this);
176     this.eventKeypress = this.keyPress.bindAsEventListener(this);
177 dpavlin 203
178     this.registerEvents();
179     },
180     destroy: function() {
181     Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
182     this.unregisterEvents();
183     },
184     registerEvents: function() {
185 dpavlin 83 Event.observe(document, "mouseup", this.eventMouseUp);
186     Event.observe(document, "mousemove", this.eventMouseMove);
187     Event.observe(document, "keypress", this.eventKeypress);
188 dpavlin 203 Event.observe(this.handle, "mousedown", this.eventMouseDown);
189 dpavlin 83 },
190 dpavlin 203 unregisterEvents: function() {
191     //if(!this.active) return;
192     //Event.stopObserving(document, "mouseup", this.eventMouseUp);
193     //Event.stopObserving(document, "mousemove", this.eventMouseMove);
194     //Event.stopObserving(document, "keypress", this.eventKeypress);
195 dpavlin 83 },
196     currentLeft: function() {
197     return parseInt(this.element.style.left || '0');
198     },
199     currentTop: function() {
200     return parseInt(this.element.style.top || '0')
201     },
202     startDrag: function(event) {
203     if(Event.isLeftClick(event)) {
204 dpavlin 203
205     // abort on form elements, fixes a Firefox issue
206     var src = Event.element(event);
207     if(src.tagName && (
208     src.tagName=='INPUT' ||
209     src.tagName=='SELECT' ||
210     src.tagName=='BUTTON' ||
211     src.tagName=='TEXTAREA')) return;
212    
213     // this.registerEvents();
214 dpavlin 83 this.active = true;
215 dpavlin 203 var pointer = [Event.pointerX(event), Event.pointerY(event)];
216     var offsets = Position.cumulativeOffset(this.element);
217     this.offsetX = (pointer[0] - offsets[0]);
218     this.offsetY = (pointer[1] - offsets[1]);
219 dpavlin 83 Event.stop(event);
220     }
221     },
222     finishDrag: function(event, success) {
223 dpavlin 203 // this.unregisterEvents();
224    
225 dpavlin 83 this.active = false;
226     this.dragging = false;
227 dpavlin 203
228     if(this.options.ghosting) {
229     Position.relativize(this.element);
230     Element.remove(this._clone);
231     this._clone = null;
232     }
233    
234 dpavlin 83 if(success) Droppables.fire(event, this.element);
235 dpavlin 203 Draggables.notify('onEnd', this, event);
236    
237 dpavlin 83 var revert = this.options.revert;
238     if(revert && typeof revert == 'function') revert = revert(this.element);
239 dpavlin 203
240 dpavlin 83 if(revert && this.options.reverteffect) {
241 dpavlin 203 this.options.reverteffect(this.element,
242 dpavlin 83 this.currentTop()-this.originalTop,
243     this.currentLeft()-this.originalLeft);
244     } else {
245     this.originalLeft = this.currentLeft();
246     this.originalTop = this.currentTop();
247     }
248 dpavlin 203
249     if(this.options.zindex)
250     this.element.style.zIndex = this.originalZ;
251    
252     if(this.options.endeffect)
253 dpavlin 83 this.options.endeffect(this.element);
254 dpavlin 203
255    
256 dpavlin 83 Droppables.reset();
257     },
258     keyPress: function(event) {
259     if(this.active) {
260     if(event.keyCode==Event.KEY_ESC) {
261     this.finishDrag(event, false);
262     Event.stop(event);
263     }
264     }
265     },
266     endDrag: function(event) {
267     if(this.active && this.dragging) {
268     this.finishDrag(event, true);
269     Event.stop(event);
270     }
271     this.active = false;
272     this.dragging = false;
273     },
274     draw: function(event) {
275 dpavlin 203 var pointer = [Event.pointerX(event), Event.pointerY(event)];
276     var offsets = Position.cumulativeOffset(this.element);
277     offsets[0] -= this.currentLeft();
278     offsets[1] -= this.currentTop();
279 dpavlin 83 var style = this.element.style;
280     if((!this.options.constraint) || (this.options.constraint=='horizontal'))
281 dpavlin 203 style.left = (pointer[0] - offsets[0] - this.offsetX) + "px";
282 dpavlin 83 if((!this.options.constraint) || (this.options.constraint=='vertical'))
283 dpavlin 203 style.top = (pointer[1] - offsets[1] - this.offsetY) + "px";
284 dpavlin 83 if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
285     },
286     update: function(event) {
287     if(this.active) {
288     if(!this.dragging) {
289     var style = this.element.style;
290     this.dragging = true;
291 dpavlin 203
292     if(Element.getStyle(this.element,'position')=='')
293     style.position = "relative";
294    
295     if(this.options.zindex) {
296     this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
297     style.zIndex = this.options.zindex;
298     }
299    
300     if(this.options.ghosting) {
301     this._clone = this.element.cloneNode(true);
302     Position.absolutize(this.element);
303     this.element.parentNode.insertBefore(this._clone, this.element);
304     }
305    
306     Draggables.notify('onStart', this, event);
307 dpavlin 83 if(this.options.starteffect) this.options.starteffect(this.element);
308     }
309 dpavlin 203
310 dpavlin 83 Droppables.show(event, this.element);
311 dpavlin 203 Draggables.notify('onDrag', this, event);
312 dpavlin 83 this.draw(event);
313     if(this.options.change) this.options.change(this);
314 dpavlin 203
315 dpavlin 83 // fix AppleWebKit rendering
316 dpavlin 203 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
317    
318 dpavlin 83 Event.stop(event);
319     }
320     }
321     }
322    
323     /*--------------------------------------------------------------------------*/
324    
325 dpavlin 203 var SortableObserver = Class.create();
326 dpavlin 83 SortableObserver.prototype = {
327     initialize: function(element, observer) {
328     this.element = $(element);
329     this.observer = observer;
330     this.lastValue = Sortable.serialize(this.element);
331     },
332     onStart: function() {
333     this.lastValue = Sortable.serialize(this.element);
334     },
335 dpavlin 203 onEnd: function() {
336     Sortable.unmark();
337 dpavlin 83 if(this.lastValue != Sortable.serialize(this.element))
338     this.observer(this.element)
339     }
340     }
341    
342 dpavlin 203 var Sortable = {
343 dpavlin 83 sortables: new Array(),
344     options: function(element){
345 dpavlin 203 element = $(element);
346     return this.sortables.detect(function(s) { return s.element == element });
347 dpavlin 83 },
348     destroy: function(element){
349 dpavlin 203 element = $(element);
350     this.sortables.findAll(function(s) { return s.element == element }).each(function(s){
351     Draggables.removeObserver(s.element);
352     s.droppables.each(function(d){ Droppables.remove(d) });
353     s.draggables.invoke('destroy');
354     });
355     this.sortables = this.sortables.reject(function(s) { return s.element == element });
356 dpavlin 83 },
357     create: function(element) {
358 dpavlin 203 element = $(element);
359     var options = Object.extend({
360 dpavlin 83 element: element,
361     tag: 'li', // assumes li children, override with tag: 'tagname'
362 dpavlin 203 dropOnEmpty: false,
363     tree: false, // fixme: unimplemented
364 dpavlin 83 overlap: 'vertical', // one of 'vertical', 'horizontal'
365     constraint: 'vertical', // one of 'vertical', 'horizontal', false
366     containment: element, // also takes array of elements (or id's); or false
367     handle: false, // or a CSS class
368     only: false,
369     hoverclass: null,
370 dpavlin 203 ghosting: false,
371     format: null,
372     onChange: Prototype.emptyFunction,
373     onUpdate: Prototype.emptyFunction
374 dpavlin 83 }, arguments[1] || {});
375 dpavlin 203
376 dpavlin 83 // clear any old sortable with same element
377     this.destroy(element);
378 dpavlin 203
379 dpavlin 83 // build options for the draggables
380     var options_for_draggable = {
381     revert: true,
382 dpavlin 203 ghosting: options.ghosting,
383 dpavlin 83 constraint: options.constraint,
384 dpavlin 203 handle: options.handle };
385    
386 dpavlin 83 if(options.starteffect)
387     options_for_draggable.starteffect = options.starteffect;
388 dpavlin 203
389 dpavlin 83 if(options.reverteffect)
390     options_for_draggable.reverteffect = options.reverteffect;
391 dpavlin 203 else
392     if(options.ghosting) options_for_draggable.reverteffect = function(element) {
393     element.style.top = 0;
394     element.style.left = 0;
395     };
396    
397 dpavlin 83 if(options.endeffect)
398     options_for_draggable.endeffect = options.endeffect;
399 dpavlin 203
400 dpavlin 83 if(options.zindex)
401     options_for_draggable.zindex = options.zindex;
402 dpavlin 203
403     // build options for the droppables
404 dpavlin 83 var options_for_droppable = {
405     overlap: options.overlap,
406     containment: options.containment,
407     hoverclass: options.hoverclass,
408 dpavlin 203 onHover: Sortable.onHover,
409     greedy: !options.dropOnEmpty
410 dpavlin 83 }
411    
412     // fix for gecko engine
413 dpavlin 203 Element.cleanWhitespace(element);
414    
415 dpavlin 83 options.draggables = [];
416     options.droppables = [];
417 dpavlin 203
418     // make it so
419    
420     // drop on empty handling
421     if(options.dropOnEmpty) {
422     Droppables.add(element,
423     {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false});
424     options.droppables.push(element);
425     }
426    
427     (this.findElements(element, options) || []).each( function(e) {
428     // handles are per-draggable
429     var handle = options.handle ?
430     Element.childrenWithClassName(e, options.handle)[0] : e;
431     options.draggables.push(
432     new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
433     Droppables.add(e, options_for_droppable);
434     options.droppables.push(e);
435     });
436    
437 dpavlin 83 // keep reference
438     this.sortables.push(options);
439 dpavlin 203
440 dpavlin 83 // for onupdate
441     Draggables.addObserver(new SortableObserver(element, options.onUpdate));
442    
443     },
444 dpavlin 203
445     // return all suitable-for-sortable elements in a guaranteed order
446     findElements: function(element, options) {
447     if(!element.hasChildNodes()) return null;
448     var elements = [];
449     $A(element.childNodes).each( function(e) {
450     if(e.tagName && e.tagName==options.tag.toUpperCase() &&
451     (!options.only || (Element.hasClassName(e, options.only))))
452     elements.push(e);
453     if(options.tree) {
454     var grandchildren = this.findElements(e, options);
455     if(grandchildren) elements.push(grandchildren);
456     }
457     });
458    
459     return (elements.length>0 ? elements.flatten() : null);
460     },
461    
462     onHover: function(element, dropon, overlap) {
463     if(overlap>0.5) {
464     Sortable.mark(dropon, 'before');
465     if(dropon.previousSibling != element) {
466     var oldParentNode = element.parentNode;
467     element.style.visibility = "hidden"; // fix gecko rendering
468     dropon.parentNode.insertBefore(element, dropon);
469     if(dropon.parentNode!=oldParentNode)
470     Sortable.options(oldParentNode).onChange(element);
471     Sortable.options(dropon.parentNode).onChange(element);
472     }
473     } else {
474     Sortable.mark(dropon, 'after');
475     var nextElement = dropon.nextSibling || null;
476     if(nextElement != element) {
477     var oldParentNode = element.parentNode;
478     element.style.visibility = "hidden"; // fix gecko rendering
479     dropon.parentNode.insertBefore(element, nextElement);
480     if(dropon.parentNode!=oldParentNode)
481     Sortable.options(oldParentNode).onChange(element);
482     Sortable.options(dropon.parentNode).onChange(element);
483     }
484     }
485     },
486    
487     onEmptyHover: function(element, dropon) {
488     if(element.parentNode!=dropon) {
489     var oldParentNode = element.parentNode;
490     dropon.appendChild(element);
491     Sortable.options(oldParentNode).onChange(element);
492     Sortable.options(dropon).onChange(element);
493     }
494     },
495    
496     unmark: function() {
497     if(Sortable._marker) Element.hide(Sortable._marker);
498     },
499    
500     mark: function(dropon, position) {
501     // mark on ghosting only
502     var sortable = Sortable.options(dropon.parentNode);
503     if(sortable && !sortable.ghosting) return;
504    
505     if(!Sortable._marker) {
506     Sortable._marker = $('dropmarker') || document.createElement('DIV');
507     Element.hide(Sortable._marker);
508     Element.addClassName(Sortable._marker, 'dropmarker');
509     Sortable._marker.style.position = 'absolute';
510     document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
511     }
512     var offsets = Position.cumulativeOffset(dropon);
513     Sortable._marker.style.left = offsets[0] + 'px';
514     Sortable._marker.style.top = offsets[1] + 'px';
515    
516     if(position=='after')
517     if(sortable.overlap == 'horizontal')
518     Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
519     else
520     Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
521    
522     Element.show(Sortable._marker);
523     },
524    
525 dpavlin 83 serialize: function(element) {
526 dpavlin 203 element = $(element);
527 dpavlin 83 var sortableOptions = this.options(element);
528     var options = Object.extend({
529     tag: sortableOptions.tag,
530     only: sortableOptions.only,
531 dpavlin 203 name: element.id,
532     format: sortableOptions.format || /^[^_]*_(.*)$/
533 dpavlin 83 }, arguments[1] || {});
534 dpavlin 203 return $(this.findElements(element, options) || []).collect( function(item) {
535     return (encodeURIComponent(options.name) + "[]=" +
536     encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : ''));
537     }).join("&");
538 dpavlin 83 }
539 dpavlin 203 }

Properties

Name Value
svn:mime-type text/cpp

  ViewVC Help
Powered by ViewVC 1.1.26