/[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

Contents of /Webpacus/root/js/dragdrop.js

Parent Directory Parent Directory | Revision Log Revision Log


Revision 203 - (show 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 // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 //
3 // See scriptaculous.js for full license.
4
5 /*--------------------------------------------------------------------------*/
6
7 var Droppables = {
8 drops: [],
9
10 remove: function(element) {
11 this.drops = this.drops.reject(function(d) { return d.element==element });
12 },
13
14 add: function(element) {
15 element = $(element);
16 var options = Object.extend({
17 greedy: true,
18 hoverclass: null
19 }, arguments[1] || {});
20
21 // cache containers
22 if(options.containment) {
23 options._containers = [];
24 var containment = options.containment;
25 if((typeof containment == 'object') &&
26 (containment.constructor == Array)) {
27 containment.each( function(c) { options._containers.push($(c)) });
28 } else {
29 options._containers.push($(containment));
30 }
31 }
32
33 if(options.accept) options.accept = [options.accept].flatten();
34
35 Element.makePositioned(element); // fix IE
36 options.element = element;
37
38 this.drops.push(options);
39 },
40
41 isContained: function(element, drop) {
42 var parentNode = element.parentNode;
43 return drop._containers.detect(function(c) { return parentNode == c });
44 },
45
46 isAffected: function(pX, pY, element, drop) {
47 return (
48 (drop.element!=element) &&
49 ((!drop._containers) ||
50 this.isContained(element, drop)) &&
51 ((!drop.accept) ||
52 (Element.classNames(element).detect(
53 function(v) { return drop.accept.include(v) } ) )) &&
54 Position.within(drop.element, pX, pY) );
55 },
56
57 deactivate: function(drop) {
58 if(drop.hoverclass)
59 Element.removeClassName(drop.element, drop.hoverclass);
60 this.last_active = null;
61 },
62
63 activate: function(drop) {
64 if(this.last_active) this.deactivate(this.last_active);
65 if(drop.hoverclass)
66 Element.addClassName(drop.element, drop.hoverclass);
67 this.last_active = drop;
68 },
69
70 show: function(event, element) {
71 if(!this.drops.length) return;
72 var pX = Event.pointerX(event);
73 var pY = Event.pointerY(event);
74 Position.prepare();
75
76 var i = this.drops.length-1; do {
77 var drop = this.drops[i];
78 if(this.isAffected(pX, pY, element, drop)) {
79 if(drop.onHover)
80 drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
81 if(drop.greedy) {
82 this.activate(drop);
83 return;
84 }
85 }
86 } while (i--);
87
88 if(this.last_active) this.deactivate(this.last_active);
89 },
90
91 fire: function(event, element) {
92 if(!this.last_active) return;
93 Position.prepare();
94
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 },
99
100 reset: function() {
101 if(this.last_active)
102 this.deactivate(this.last_active);
103 }
104 }
105
106 var Draggables = {
107 observers: [],
108 addObserver: function(observer) {
109 this.observers.push(observer);
110 this._cacheObserverCallbacks();
111 },
112 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 },
116 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 }
129 }
130
131 /*--------------------------------------------------------------------------*/
132
133 var Draggable = Class.create();
134 Draggable.prototype = {
135 initialize: function(element) {
136 var options = Object.extend({
137 handle: false,
138 starteffect: function(element) {
139 new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
140 },
141 reverteffect: function(element, top_offset, left_offset) {
142 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 },
145 endeffect: function(element) {
146 new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
147 },
148 zindex: 1000,
149 revert: false
150 }, arguments[1] || {});
151
152 this.element = $(element);
153 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 Element.makePositioned(this.element); // fix IE
160
161 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
168 this.options = options;
169
170 this.active = false;
171 this.dragging = false;
172
173 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
178 this.registerEvents();
179 },
180 destroy: function() {
181 Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
182 this.unregisterEvents();
183 },
184 registerEvents: function() {
185 Event.observe(document, "mouseup", this.eventMouseUp);
186 Event.observe(document, "mousemove", this.eventMouseMove);
187 Event.observe(document, "keypress", this.eventKeypress);
188 Event.observe(this.handle, "mousedown", this.eventMouseDown);
189 },
190 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 },
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
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 this.active = true;
215 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 Event.stop(event);
220 }
221 },
222 finishDrag: function(event, success) {
223 // this.unregisterEvents();
224
225 this.active = false;
226 this.dragging = false;
227
228 if(this.options.ghosting) {
229 Position.relativize(this.element);
230 Element.remove(this._clone);
231 this._clone = null;
232 }
233
234 if(success) Droppables.fire(event, this.element);
235 Draggables.notify('onEnd', this, event);
236
237 var revert = this.options.revert;
238 if(revert && typeof revert == 'function') revert = revert(this.element);
239
240 if(revert && this.options.reverteffect) {
241 this.options.reverteffect(this.element,
242 this.currentTop()-this.originalTop,
243 this.currentLeft()-this.originalLeft);
244 } else {
245 this.originalLeft = this.currentLeft();
246 this.originalTop = this.currentTop();
247 }
248
249 if(this.options.zindex)
250 this.element.style.zIndex = this.originalZ;
251
252 if(this.options.endeffect)
253 this.options.endeffect(this.element);
254
255
256 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 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 var style = this.element.style;
280 if((!this.options.constraint) || (this.options.constraint=='horizontal'))
281 style.left = (pointer[0] - offsets[0] - this.offsetX) + "px";
282 if((!this.options.constraint) || (this.options.constraint=='vertical'))
283 style.top = (pointer[1] - offsets[1] - this.offsetY) + "px";
284 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
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 if(this.options.starteffect) this.options.starteffect(this.element);
308 }
309
310 Droppables.show(event, this.element);
311 Draggables.notify('onDrag', this, event);
312 this.draw(event);
313 if(this.options.change) this.options.change(this);
314
315 // fix AppleWebKit rendering
316 if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
317
318 Event.stop(event);
319 }
320 }
321 }
322
323 /*--------------------------------------------------------------------------*/
324
325 var SortableObserver = Class.create();
326 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 onEnd: function() {
336 Sortable.unmark();
337 if(this.lastValue != Sortable.serialize(this.element))
338 this.observer(this.element)
339 }
340 }
341
342 var Sortable = {
343 sortables: new Array(),
344 options: function(element){
345 element = $(element);
346 return this.sortables.detect(function(s) { return s.element == element });
347 },
348 destroy: function(element){
349 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 },
357 create: function(element) {
358 element = $(element);
359 var options = Object.extend({
360 element: element,
361 tag: 'li', // assumes li children, override with tag: 'tagname'
362 dropOnEmpty: false,
363 tree: false, // fixme: unimplemented
364 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 ghosting: false,
371 format: null,
372 onChange: Prototype.emptyFunction,
373 onUpdate: Prototype.emptyFunction
374 }, arguments[1] || {});
375
376 // clear any old sortable with same element
377 this.destroy(element);
378
379 // build options for the draggables
380 var options_for_draggable = {
381 revert: true,
382 ghosting: options.ghosting,
383 constraint: options.constraint,
384 handle: options.handle };
385
386 if(options.starteffect)
387 options_for_draggable.starteffect = options.starteffect;
388
389 if(options.reverteffect)
390 options_for_draggable.reverteffect = options.reverteffect;
391 else
392 if(options.ghosting) options_for_draggable.reverteffect = function(element) {
393 element.style.top = 0;
394 element.style.left = 0;
395 };
396
397 if(options.endeffect)
398 options_for_draggable.endeffect = options.endeffect;
399
400 if(options.zindex)
401 options_for_draggable.zindex = options.zindex;
402
403 // build options for the droppables
404 var options_for_droppable = {
405 overlap: options.overlap,
406 containment: options.containment,
407 hoverclass: options.hoverclass,
408 onHover: Sortable.onHover,
409 greedy: !options.dropOnEmpty
410 }
411
412 // fix for gecko engine
413 Element.cleanWhitespace(element);
414
415 options.draggables = [];
416 options.droppables = [];
417
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 // keep reference
438 this.sortables.push(options);
439
440 // for onupdate
441 Draggables.addObserver(new SortableObserver(element, options.onUpdate));
442
443 },
444
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 serialize: function(element) {
526 element = $(element);
527 var sortableOptions = this.options(element);
528 var options = Object.extend({
529 tag: sortableOptions.tag,
530 only: sortableOptions.only,
531 name: element.id,
532 format: sortableOptions.format || /^[^_]*_(.*)$/
533 }, arguments[1] || {});
534 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 }
539 }

Properties

Name Value
svn:mime-type text/cpp

  ViewVC Help
Powered by ViewVC 1.1.26