diff options
author | Thomas Fuchs <thomas@fesch.at> | 2005-09-28 08:20:47 +0000 |
---|---|---|
committer | Thomas Fuchs <thomas@fesch.at> | 2005-09-28 08:20:47 +0000 |
commit | 516dc2c0f16cf187f981b5e8648a7f7f1b31d190 (patch) | |
tree | d645e19a02ab5eb371302fe4742b7fd7e0a1a1e2 /actionpack/lib/action_view/helpers/javascripts/dragdrop.js | |
parent | dd21e9ae39a2dc4b7eb607ff2c200c864fa19b28 (diff) | |
download | rails-516dc2c0f16cf187f981b5e8648a7f7f1b31d190.tar.gz rails-516dc2c0f16cf187f981b5e8648a7f7f1b31d190.tar.bz2 rails-516dc2c0f16cf187f981b5e8648a7f7f1b31d190.zip |
Update script.aculo.us to 1.5_rc2, and Prototype to 1.4.0_pre7
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2386 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'actionpack/lib/action_view/helpers/javascripts/dragdrop.js')
-rw-r--r-- | actionpack/lib/action_view/helpers/javascripts/dragdrop.js | 509 |
1 files changed, 238 insertions, 271 deletions
diff --git a/actionpack/lib/action_view/helpers/javascripts/dragdrop.js b/actionpack/lib/action_view/helpers/javascripts/dragdrop.js index c0fd1d1e53..a8ed953a7f 100644 --- a/actionpack/lib/action_view/helpers/javascripts/dragdrop.js +++ b/actionpack/lib/action_view/helpers/javascripts/dragdrop.js @@ -2,193 +2,79 @@ // // Element.Class part Copyright (c) 2005 by Rick Olson // -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Element.Class = { - // Element.toggleClass(element, className) toggles the class being on/off - // Element.toggleClass(element, className1, className2) toggles between both classes, - // defaulting to className1 if neither exist - toggle: function(element, className) { - if(Element.Class.has(element, className)) { - Element.Class.remove(element, className); - if(arguments.length == 3) Element.Class.add(element, arguments[2]); - } else { - Element.Class.add(element, className); - if(arguments.length == 3) Element.Class.remove(element, arguments[2]); - } - }, - - // gets space-delimited classnames of an element as an array - get: function(element) { - element = $(element); - return element.className.split(' '); - }, - - // functions adapted from original functions by Gavin Kistner - remove: function(element) { - element = $(element); - var regEx; - for(var i = 1; i < arguments.length; i++) { - regEx = new RegExp("^" + arguments[i] + "\\b\\s*|\\s*\\b" + arguments[i] + "\\b", 'g'); - element.className = element.className.replace(regEx, '') - } - }, - - add: function(element) { - element = $(element); - for(var i = 1; i < arguments.length; i++) { - Element.Class.remove(element, arguments[i]); - element.className += (element.className.length > 0 ? ' ' : '') + arguments[i]; - } - }, - - // returns true if all given classes exist in said element - has: function(element) { - element = $(element); - if(!element || !element.className) return false; - var regEx; - for(var i = 1; i < arguments.length; i++) { - regEx = new RegExp("\\b" + arguments[i] + "\\b"); - if(!regEx.test(element.className)) return false; - } - return true; - }, - - // expects arrays of strings and/or strings as optional paramters - // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') - has_any: function(element) { - element = $(element); - if(!element || !element.className) return false; - var regEx; - for(var i = 1; i < arguments.length; i++) { - if((typeof arguments[i] == 'object') && - (arguments[i].constructor == Array)) { - for(var j = 0; j < arguments[i].length; j++) { - regEx = new RegExp("\\b" + arguments[i][j] + "\\b"); - if(regEx.test(element.className)) return true; - } - } else { - regEx = new RegExp("\\b" + arguments[i] + "\\b"); - if(regEx.test(element.className)) return true; - } - } - return false; - }, - - childrenWith: function(element, className) { - var children = $(element).getElementsByTagName('*'); - var elements = new Array(); - - for (var i = 0; i < children.length; i++) { - if (Element.Class.has(children[i], className)) { - elements.push(children[i]); - break; - } - } - - return elements; - } -} +// See scriptaculous.js for full license. /*--------------------------------------------------------------------------*/ var Droppables = { - drops: false, - + drops: [], + remove: function(element) { - for(var i = 0; i < this.drops.length; i++) - if(this.drops[i].element == element) - this.drops.splice(i,1); + this.drops = this.drops.reject(function(e) { return e==element }); }, - + add: function(element) { - var element = $(element); + element = $(element); var options = Object.extend({ greedy: true, hoverclass: null }, arguments[1] || {}); - + // cache containers if(options.containment) { - options._containers = new Array(); + options._containers = []; var containment = options.containment; if((typeof containment == 'object') && (containment.constructor == Array)) { - for(var i=0; i<containment.length; i++) - options._containers.push($(containment[i])); + containment.each( function(c) { options._containers.push($(c)) }); } else { options._containers.push($(containment)); } - options._containers_length = - options._containers.length-1; } - + Element.makePositioned(element); // fix IE - options.element = element; - - // activate the droppable - if(!this.drops) this.drops = []; + this.drops.push(options); }, - - is_contained: function(element, drop) { - var containers = drop._containers; + + isContained: function(element, drop) { var parentNode = element.parentNode; - var i = drop._containers_length; - do { if(parentNode==containers[i]) return true; } while (i--); - return false; + return drop._containers.detect(function(c) { return parentNode == c }); }, - - is_affected: function(pX, pY, element, drop) { + + isAffected: function(pX, pY, element, drop) { return ( (drop.element!=element) && ((!drop._containers) || - this.is_contained(element, drop)) && + this.isContained(element, drop)) && ((!drop.accept) || (Element.Class.has_any(element, drop.accept))) && Position.within(drop.element, pX, pY) ); }, - + deactivate: function(drop) { - Element.Class.remove(drop.element, drop.hoverclass); + if(drop.hoverclass) + Element.Class.remove(drop.element, drop.hoverclass); this.last_active = null; }, - + activate: function(drop) { if(this.last_active) this.deactivate(this.last_active); - if(drop.hoverclass) { + if(drop.hoverclass) Element.Class.add(drop.element, drop.hoverclass); - this.last_active = drop; - } + this.last_active = drop; }, - + show: function(event, element) { - if(!this.drops) return; + if(!this.drops.length) return; var pX = Event.pointerX(event); var pY = Event.pointerY(event); Position.prepare(); - + var i = this.drops.length-1; do { var drop = this.drops[i]; - if(this.is_affected(pX, pY, element, drop)) { + if(this.isAffected(pX, pY, element, drop)) { if(drop.onHover) drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); if(drop.greedy) { @@ -197,43 +83,41 @@ var Droppables = { } } } while (i--); + + if(this.last_active) this.deactivate(this.last_active); }, - + fire: function(event, element) { if(!this.last_active) return; Position.prepare(); - - if (this.is_affected(Event.pointerX(event), Event.pointerY(event), element, this.last_active)) + + if (this.isAffected(Event.pointerX(event), Event.pointerY(event), element, this.last_active)) if (this.last_active.onDrop) - this.last_active.onDrop(element, this.last_active); - + this.last_active.onDrop(element, this.last_active.element); }, - + reset: function() { if(this.last_active) this.deactivate(this.last_active); } } -Draggables = { - observers: new Array(), +var Draggables = { + observers: [], addObserver: function(observer) { this.observers.push(observer); }, removeObserver: function(element) { // element instead of obsever fixes mem leaks - for(var i = 0; i < this.observers.length; i++) - if(this.observers[i].element && (this.observers[i].element == element)) - this.observers.splice(i,1); + this.observers = this.observers.reject( function(o) { return o.element==element }); }, notify: function(eventName, draggable) { // 'onStart', 'onEnd' - for(var i = 0; i < this.observers.length; i++) - this.observers[i][eventName](draggable); + this.observers.invoke(eventName, draggable); } } /*--------------------------------------------------------------------------*/ -Draggable = Class.create(); +var Draggable = Class.create(); Draggable.prototype = { initialize: function(element) { var options = Object.extend({ @@ -242,7 +126,8 @@ Draggable.prototype = { new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); }, reverteffect: function(element, top_offset, left_offset) { - new Effect.MoveBy(element, -top_offset, -left_offset, {duration:0.4}); + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur}); }, endeffect: function(element) { new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); @@ -250,12 +135,12 @@ Draggable.prototype = { zindex: 1000, revert: false }, arguments[1] || {}); - + this.element = $(element); this.handle = options.handle ? $(options.handle) : this.element; - - Element.makePositioned(this.element); // fix IE - + + Element.makePositioned(this.element); // fix IE + this.offsetX = 0; this.offsetY = 0; this.originalLeft = this.currentLeft(); @@ -263,27 +148,34 @@ Draggable.prototype = { this.originalX = this.element.offsetLeft; this.originalY = this.element.offsetTop; this.originalZ = parseInt(this.element.style.zIndex || "0"); - + this.options = options; - + this.active = false; this.dragging = false; - + this.eventMouseDown = this.startDrag.bindAsEventListener(this); this.eventMouseUp = this.endDrag.bindAsEventListener(this); this.eventMouseMove = this.update.bindAsEventListener(this); this.eventKeypress = this.keyPress.bindAsEventListener(this); - Event.observe(this.handle, "mousedown", this.eventMouseDown); + this.registerEvents(); + }, + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + this.unregisterEvents(); + }, + registerEvents: function() { Event.observe(document, "mouseup", this.eventMouseUp); Event.observe(document, "mousemove", this.eventMouseMove); Event.observe(document, "keypress", this.eventKeypress); + Event.observe(this.handle, "mousedown", this.eventMouseDown); }, - destroy: function() { - Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); - Event.stopObserving(document, "mouseup", this.eventMouseUp); - Event.stopObserving(document, "mousemove", this.eventMouseMove); - Event.stopObserving(document, "keypress", this.eventKeypress); + unregisterEvents: function() { + //if(!this.active) return; + //Event.stopObserving(document, "mouseup", this.eventMouseUp); + //Event.stopObserving(document, "mousemove", this.eventMouseMove); + //Event.stopObserving(document, "keypress", this.eventKeypress); }, currentLeft: function() { return parseInt(this.element.style.left || '0'); @@ -293,27 +185,42 @@ Draggable.prototype = { }, startDrag: function(event) { if(Event.isLeftClick(event)) { - this.active = true; - var style = this.element.style; - this.originalY = this.element.offsetTop - this.currentTop() - this.originalTop; - this.originalX = this.element.offsetLeft - this.currentLeft() - this.originalLeft; - this.offsetY = event.clientY - this.originalY - this.originalTop; - this.offsetX = event.clientX - this.originalX - this.originalLeft; + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if(src.tagName && ( + src.tagName=='INPUT' || + src.tagName=='SELECT' || + src.tagName=='BUTTON' || + src.tagName=='TEXTAREA')) return; + // this.registerEvents(); + this.active = true; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var offsets = Position.cumulativeOffset(this.element); + this.offsetX = (pointer[0] - offsets[0]); + this.offsetY = (pointer[1] - offsets[1]); Event.stop(event); } }, finishDrag: function(event, success) { + // this.unregisterEvents(); + this.active = false; this.dragging = false; - + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + if(success) Droppables.fire(event, this.element); Draggables.notify('onEnd', this); - + var revert = this.options.revert; if(revert && typeof revert == 'function') revert = revert(this.element); - + if(revert && this.options.reverteffect) { this.options.reverteffect(this.element, this.currentTop()-this.originalTop, @@ -322,12 +229,13 @@ Draggable.prototype = { this.originalLeft = this.currentLeft(); this.originalTop = this.currentTop(); } - + this.element.style.zIndex = this.originalZ; - + if(this.options.endeffect) this.options.endeffect(this.element); - + + Droppables.reset(); }, keyPress: function(event) { @@ -347,13 +255,15 @@ Draggable.prototype = { this.dragging = false; }, draw: function(event) { + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var offsets = Position.cumulativeOffset(this.element); + offsets[0] -= this.currentLeft(); + offsets[1] -= this.currentTop(); var style = this.element.style; - this.originalX = this.element.offsetLeft - this.currentLeft() - this.originalLeft; - this.originalY = this.element.offsetTop - this.currentTop() - this.originalTop; if((!this.options.constraint) || (this.options.constraint=='horizontal')) - style.left = ((event.clientX - this.originalX) - this.offsetX) + "px"; + style.left = (pointer[0] - offsets[0] - this.offsetX) + "px"; if((!this.options.constraint) || (this.options.constraint=='vertical')) - style.top = ((event.clientY - this.originalY) - this.offsetY) + "px"; + style.top = (pointer[1] - offsets[1] - this.offsetY) + "px"; if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering }, update: function(event) { @@ -363,17 +273,24 @@ Draggable.prototype = { this.dragging = true; if(style.position=="") style.position = "relative"; style.zIndex = this.options.zindex; + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + Draggables.notify('onStart', this); if(this.options.starteffect) this.options.starteffect(this.element); } - + Droppables.show(event, this.element); this.draw(event); if(this.options.change) this.options.change(this); - + // fix AppleWebKit rendering if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); - + Event.stop(event); } } @@ -381,7 +298,7 @@ Draggable.prototype = { /*--------------------------------------------------------------------------*/ -SortableObserver = Class.create(); +var SortableObserver = Class.create(); SortableObserver.prototype = { initialize: function(element, observer) { this.element = $(element); @@ -391,147 +308,197 @@ SortableObserver.prototype = { onStart: function() { this.lastValue = Sortable.serialize(this.element); }, - onEnd: function() { + onEnd: function() { + Sortable.unmark(); if(this.lastValue != Sortable.serialize(this.element)) this.observer(this.element) } } -Sortable = { +var Sortable = { sortables: new Array(), options: function(element){ - var element = $(element); - for(var i=0;i<this.sortables.length;i++) - if(this.sortables[i].element == element) - return this.sortables[i]; - return null; + element = $(element); + return this.sortables.detect(function(s) { return s.element == element }); }, destroy: function(element){ - var element = $(element); - for(var i=0;i<this.sortables.length;i++) { - if(this.sortables[i].element == element) { - var s = this.sortables[i]; - Draggables.removeObserver(s.element); - for(var j=0;j<s.droppables.length;j++) - Droppables.remove(s.droppables[j]); - for(var j=0;j<s.draggables.length;j++) - s.draggables[j].destroy(); - this.sortables.splice(i,1); - } - } + element = $(element); + this.sortables.findAll(function(s) { return s.element == element }).each(function(s){ + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + }); + this.sortables = this.sortables.reject(function(s) { return s.element == element }); }, create: function(element) { - var element = $(element); + element = $(element); var options = Object.extend({ element: element, tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, // fixme: unimplemented overlap: 'vertical', // one of 'vertical', 'horizontal' constraint: 'vertical', // one of 'vertical', 'horizontal', false containment: element, // also takes array of elements (or id's); or false handle: false, // or a CSS class only: false, hoverclass: null, + ghosting: false, onChange: function() {}, onUpdate: function() {} }, arguments[1] || {}); - + // clear any old sortable with same element this.destroy(element); - + // build options for the draggables var options_for_draggable = { revert: true, + ghosting: options.ghosting, constraint: options.constraint, - handle: handle }; + handle: options.handle }; + if(options.starteffect) options_for_draggable.starteffect = options.starteffect; + if(options.reverteffect) options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + if(options.endeffect) options_for_draggable.endeffect = options.endeffect; + if(options.zindex) options_for_draggable.zindex = options.zindex; - + // build options for the droppables var options_for_droppable = { overlap: options.overlap, containment: options.containment, hoverclass: options.hoverclass, - onHover: function(element, dropon, overlap) { - if(overlap>0.5) { - if(dropon.previousSibling != element) { - var oldParentNode = element.parentNode; - element.style.visibility = "hidden"; // fix gecko rendering - dropon.parentNode.insertBefore(element, dropon); - if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) - oldParentNode.sortable.onChange(element); - if(dropon.parentNode.sortable) - dropon.parentNode.sortable.onChange(element); - } - } else { - var nextElement = dropon.nextSibling || null; - if(nextElement != element) { - var oldParentNode = element.parentNode; - element.style.visibility = "hidden"; // fix gecko rendering - dropon.parentNode.insertBefore(element, nextElement); - if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) - oldParentNode.sortable.onChange(element); - if(dropon.parentNode.sortable) - dropon.parentNode.sortable.onChange(element); - } - } - } + onHover: Sortable.onHover, + greedy: !options.dropOnEmpty } // fix for gecko engine Element.cleanWhitespace(element); - + options.draggables = []; options.droppables = []; - - // make it so - var elements = element.childNodes; - for (var i = 0; i < elements.length; i++) - if(elements[i].tagName && elements[i].tagName==options.tag.toUpperCase() && - (!options.only || (Element.Class.has(elements[i], options.only)))) { - - // handles are per-draggable - var handle = options.handle ? - Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i]; - - options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle }))); - - Droppables.add(elements[i], options_for_droppable); - options.droppables.push(elements[i]); - - } - + + // make it so + + // drop on empty handling + if(options.dropOnEmpty) { + Droppables.add(element, + {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false}); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + Element.Class.childrenWith(e, options.handle)[0] : e; + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + options.droppables.push(e); + }); + // keep reference this.sortables.push(options); - + // for onupdate Draggables.addObserver(new SortableObserver(element, options.onUpdate)); }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + if(!element.hasChildNodes()) return null; + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName==options.tag.toUpperCase() && + (!options.only || (Element.Class.has(e, options.only)))) + elements.push(e); + if(options.tree) { + var grandchildren = this.findElements(e, options); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : null); + }, + + onHover: function(element, dropon, overlap) { + if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon) { + if(element.parentNode!=dropon) { + dropon.appendChild(element); + } + }, + + unmark: function() { + if(Sortable._marker) Element.hide(Sortable._marker); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = $('dropmarker') || document.createElement('DIV'); + Element.hide(Sortable._marker); + Element.Class.add(Sortable._marker, 'dropmarker'); + Sortable._marker.style.position = 'absolute'; + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.style.top = offsets[1] + 'px'; + if(position=='after') Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; + Sortable._marker.style.left = offsets[0] + 'px'; + Element.show(Sortable._marker); + }, + serialize: function(element) { - var element = $(element); + element = $(element); var sortableOptions = this.options(element); var options = Object.extend({ tag: sortableOptions.tag, only: sortableOptions.only, name: element.id }, arguments[1] || {}); - - var items = $(element).childNodes; - var queryComponents = new Array(); - - for(var i=0; i<items.length; i++) - if(items[i].tagName && items[i].tagName==options.tag.toUpperCase() && - (!options.only || (Element.Class.has(items[i], options.only)))) - queryComponents.push( - encodeURIComponent(options.name) + "[]=" + - encodeURIComponent(items[i].id.split("_")[1])); - - return queryComponents.join("&"); + return $A(element.childNodes).collect( function(item) { + return (encodeURIComponent(options.name) + "[]=" + + encodeURIComponent(item.id.split("_")[1])); + }).join("&"); } -}
\ No newline at end of file +}
\ No newline at end of file |