updated prototype (1.50) and script.aculo.us javascripts

git-svn-id: http://redmine.rubyforge.org/svn/trunk@241 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Jean-Philippe Lang 2007-02-05 18:31:27 +00:00
parent 9845bbddce
commit 2e72f2eca8
4 changed files with 1489 additions and 618 deletions

View File

@ -1,12 +1,13 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) // (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
// (c) 2005 Jon Tirsen (http://www.tirsen.com) // (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com)
// Contributors: // Contributors:
// Richard Livsey // Richard Livsey
// Rahul Bhargava // Rahul Bhargava
// Rob Wills // Rob Wills
// //
// See scriptaculous.js for full license. // script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
// Autocompleter.Base handles all the autocompletion functionality // Autocompleter.Base handles all the autocompletion functionality
// that's independent of the data source for autocompletion. This // that's independent of the data source for autocompletion. This
@ -33,6 +34,9 @@
// useful when one of the tokens is \n (a newline), as it // useful when one of the tokens is \n (a newline), as it
// allows smart autocompletion after linebreaks. // allows smart autocompletion after linebreaks.
if(typeof Effect == 'undefined')
throw("controls.js requires including script.aculo.us' effects.js library");
var Autocompleter = {} var Autocompleter = {}
Autocompleter.Base = function() {}; Autocompleter.Base = function() {};
Autocompleter.Base.prototype = { Autocompleter.Base.prototype = {
@ -45,7 +49,7 @@ Autocompleter.Base.prototype = {
this.index = 0; this.index = 0;
this.entryCount = 0; this.entryCount = 0;
if (this.setOptions) if(this.setOptions)
this.setOptions(options); this.setOptions(options);
else else
this.options = options || {}; this.options = options || {};
@ -58,14 +62,17 @@ Autocompleter.Base.prototype = {
function(element, update){ function(element, update){
if(!update.style.position || update.style.position=='absolute') { if(!update.style.position || update.style.position=='absolute') {
update.style.position = 'absolute'; update.style.position = 'absolute';
Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight}); Position.clone(element, update, {
setHeight: false,
offsetTop: element.offsetHeight
});
} }
Effect.Appear(update,{duration:0.15}); Effect.Appear(update,{duration:0.15});
}; };
this.options.onHide = this.options.onHide || this.options.onHide = this.options.onHide ||
function(element, update){ new Effect.Fade(update,{duration:0.15}) }; function(element, update){ new Effect.Fade(update,{duration:0.15}) };
if (typeof(this.options.tokens) == 'string') if(typeof(this.options.tokens) == 'string')
this.options.tokens = new Array(this.options.tokens); this.options.tokens = new Array(this.options.tokens);
this.observer = null; this.observer = null;
@ -94,7 +101,7 @@ Autocompleter.Base.prototype = {
}, },
fixIEOverlapping: function() { fixIEOverlapping: function() {
Position.clone(this.update, this.iefix); Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
this.iefix.style.zIndex = 1; this.iefix.style.zIndex = 1;
this.update.style.zIndex = 2; this.update.style.zIndex = 2;
Element.show(this.iefix); Element.show(this.iefix);
@ -141,8 +148,8 @@ Autocompleter.Base.prototype = {
return; return;
} }
else else
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
return; (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
this.changed = true; this.changed = true;
this.hasFocus = true; this.hasFocus = true;
@ -152,6 +159,12 @@ Autocompleter.Base.prototype = {
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
}, },
activate: function() {
this.changed = false;
this.hasFocus = true;
this.getUpdatedChoices();
},
onHover: function(event) { onHover: function(event) {
var element = Event.findElement(event, 'LI'); var element = Event.findElement(event, 'LI');
if(this.index != element.autocompleteIndex) if(this.index != element.autocompleteIndex)
@ -196,11 +209,13 @@ Autocompleter.Base.prototype = {
markPrevious: function() { markPrevious: function() {
if(this.index > 0) this.index-- if(this.index > 0) this.index--
else this.index = this.entryCount-1; else this.index = this.entryCount-1;
this.getEntry(this.index).scrollIntoView(true);
}, },
markNext: function() { markNext: function() {
if(this.index < this.entryCount-1) this.index++ if(this.index < this.entryCount-1) this.index++
else this.index = 0; else this.index = 0;
this.getEntry(this.index).scrollIntoView(false);
}, },
getEntry: function(index) { getEntry: function(index) {
@ -221,8 +236,13 @@ Autocompleter.Base.prototype = {
this.options.updateElement(selectedElement); this.options.updateElement(selectedElement);
return; return;
} }
var value = '';
if (this.options.select) {
var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
} else
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
var lastTokenPos = this.findLastToken(); var lastTokenPos = this.findLastToken();
if (lastTokenPos != -1) { if (lastTokenPos != -1) {
var newValue = this.element.value.substr(0, lastTokenPos + 1); var newValue = this.element.value.substr(0, lastTokenPos + 1);
@ -243,11 +263,11 @@ Autocompleter.Base.prototype = {
if(!this.changed && this.hasFocus) { if(!this.changed && this.hasFocus) {
this.update.innerHTML = choices; this.update.innerHTML = choices;
Element.cleanWhitespace(this.update); Element.cleanWhitespace(this.update);
Element.cleanWhitespace(this.update.firstChild); Element.cleanWhitespace(this.update.down());
if(this.update.firstChild && this.update.firstChild.childNodes) { if(this.update.firstChild && this.update.down().childNodes) {
this.entryCount = this.entryCount =
this.update.firstChild.childNodes.length; this.update.down().childNodes.length;
for (var i = 0; i < this.entryCount; i++) { for (var i = 0; i < this.entryCount; i++) {
var entry = this.getEntry(i); var entry = this.getEntry(i);
entry.autocompleteIndex = i; entry.autocompleteIndex = i;
@ -258,10 +278,15 @@ Autocompleter.Base.prototype = {
} }
this.stopIndicator(); this.stopIndicator();
this.index = 0; this.index = 0;
if(this.entryCount==1 && this.options.autoSelect) {
this.selectEntry();
this.hide();
} else {
this.render(); this.render();
} }
}
}, },
addObservers: function(element) { addObservers: function(element) {
@ -448,7 +473,10 @@ Ajax.InPlaceEditor.prototype = {
this.element = $(element); this.element = $(element);
this.options = Object.extend({ this.options = Object.extend({
paramName: "value",
okButton: true,
okText: "ok", okText: "ok",
cancelLink: true,
cancelText: "cancel", cancelText: "cancel",
savingText: "Saving...", savingText: "Saving...",
clickToEditText: "Click to edit", clickToEditText: "Click to edit",
@ -471,7 +499,9 @@ Ajax.InPlaceEditor.prototype = {
highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
highlightendcolor: "#FFFFFF", highlightendcolor: "#FFFFFF",
externalControl: null, externalControl: null,
ajaxOptions: {} submitOnBlur: false,
ajaxOptions: {},
evalScripts: false
}, options || {}); }, options || {});
if(!this.options.formId && this.element.id) { if(!this.options.formId && this.element.id) {
@ -516,7 +546,7 @@ Ajax.InPlaceEditor.prototype = {
Element.hide(this.element); Element.hide(this.element);
this.createForm(); this.createForm();
this.element.parentNode.insertBefore(this.form, this.element); this.element.parentNode.insertBefore(this.form, this.element);
Field.scrollFreeActivate(this.editField); if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField);
// stop the event to avoid a page refresh in Safari // stop the event to avoid a page refresh in Safari
if (evt) { if (evt) {
Event.stop(evt); Event.stop(evt);
@ -536,16 +566,22 @@ Ajax.InPlaceEditor.prototype = {
this.form.appendChild(br); this.form.appendChild(br);
} }
if (this.options.okButton) {
okButton = document.createElement("input"); okButton = document.createElement("input");
okButton.type = "submit"; okButton.type = "submit";
okButton.value = this.options.okText; okButton.value = this.options.okText;
okButton.className = 'editor_ok_button';
this.form.appendChild(okButton); this.form.appendChild(okButton);
}
if (this.options.cancelLink) {
cancelLink = document.createElement("a"); cancelLink = document.createElement("a");
cancelLink.href = "#"; cancelLink.href = "#";
cancelLink.appendChild(document.createTextNode(this.options.cancelText)); cancelLink.appendChild(document.createTextNode(this.options.cancelText));
cancelLink.onclick = this.onclickCancel.bind(this); cancelLink.onclick = this.onclickCancel.bind(this);
cancelLink.className = 'editor_cancel';
this.form.appendChild(cancelLink); this.form.appendChild(cancelLink);
}
}, },
hasHTMLLineBreaks: function(string) { hasHTMLLineBreaks: function(string) {
if (!this.options.handleLineBreaks) return false; if (!this.options.handleLineBreaks) return false;
@ -562,23 +598,33 @@ Ajax.InPlaceEditor.prototype = {
text = this.getText(); text = this.getText();
} }
var obj = this;
if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
this.options.textarea = false; this.options.textarea = false;
var textField = document.createElement("input"); var textField = document.createElement("input");
textField.obj = this;
textField.type = "text"; textField.type = "text";
textField.name = "value"; textField.name = this.options.paramName;
textField.value = text; textField.value = text;
textField.style.backgroundColor = this.options.highlightcolor; textField.style.backgroundColor = this.options.highlightcolor;
textField.className = 'editor_field';
var size = this.options.size || this.options.cols || 0; var size = this.options.size || this.options.cols || 0;
if (size != 0) textField.size = size; if (size != 0) textField.size = size;
if (this.options.submitOnBlur)
textField.onblur = this.onSubmit.bind(this);
this.editField = textField; this.editField = textField;
} else { } else {
this.options.textarea = true; this.options.textarea = true;
var textArea = document.createElement("textarea"); var textArea = document.createElement("textarea");
textArea.name = "value"; textArea.obj = this;
textArea.name = this.options.paramName;
textArea.value = this.convertHTMLLineBreaks(text); textArea.value = this.convertHTMLLineBreaks(text);
textArea.rows = this.options.rows; textArea.rows = this.options.rows;
textArea.cols = this.options.cols || 40; textArea.cols = this.options.cols || 40;
textArea.className = 'editor_field';
if (this.options.submitOnBlur)
textArea.onblur = this.onSubmit.bind(this);
this.editField = textArea; this.editField = textArea;
} }
@ -605,6 +651,7 @@ Ajax.InPlaceEditor.prototype = {
Element.removeClassName(this.form, this.options.loadingClassName); Element.removeClassName(this.form, this.options.loadingClassName);
this.editField.disabled = false; this.editField.disabled = false;
this.editField.value = transport.responseText.stripTags(); this.editField.value = transport.responseText.stripTags();
Field.scrollFreeActivate(this.editField);
}, },
onclickCancel: function() { onclickCancel: function() {
this.onComplete(); this.onComplete();
@ -629,19 +676,26 @@ Ajax.InPlaceEditor.prototype = {
// to be displayed indefinitely // to be displayed indefinitely
this.onLoading(); this.onLoading();
if (this.options.evalScripts) {
new Ajax.Request(
this.url, Object.extend({
parameters: this.options.callback(form, value),
onComplete: this.onComplete.bind(this),
onFailure: this.onFailure.bind(this),
asynchronous:true,
evalScripts:true
}, this.options.ajaxOptions));
} else {
new Ajax.Updater( new Ajax.Updater(
{ { success: this.element,
success: this.element,
// don't update on failure (this could be an option) // don't update on failure (this could be an option)
failure: null failure: null },
}, this.url, Object.extend({
this.url,
Object.extend({
parameters: this.options.callback(form, value), parameters: this.options.callback(form, value),
onComplete: this.onComplete.bind(this), onComplete: this.onComplete.bind(this),
onFailure: this.onFailure.bind(this) onFailure: this.onFailure.bind(this)
}, this.options.ajaxOptions) }, this.options.ajaxOptions));
); }
// stop the event to avoid a page refresh in Safari // stop the event to avoid a page refresh in Safari
if (arguments.length > 1) { if (arguments.length > 1) {
Event.stop(arguments[0]); Event.stop(arguments[0]);
@ -723,6 +777,35 @@ Ajax.InPlaceEditor.prototype = {
} }
}; };
Ajax.InPlaceCollectionEditor = Class.create();
Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
createEditField: function() {
if (!this.cached_selectTag) {
var selectTag = document.createElement("select");
var collection = this.options.collection || [];
var optionTag;
collection.each(function(e,i) {
optionTag = document.createElement("option");
optionTag.value = (e instanceof Array) ? e[0] : e;
if((typeof this.options.value == 'undefined') &&
((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true;
if(this.options.value==optionTag.value) optionTag.selected = true;
optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
selectTag.appendChild(optionTag);
}.bind(this));
this.cached_selectTag = selectTag;
}
this.editField = this.cached_selectTag;
if(this.options.loadTextURL) this.loadExternalText();
this.form.appendChild(this.editField);
this.options.callback = function(form, value) {
return "value=" + encodeURIComponent(value);
}
}
});
// Delayed observer, like Form.Element.Observer, // Delayed observer, like Form.Element.Observer,
// but waits for delay after last key input // but waits for delay after last key input
// Ideal for live-search fields // Ideal for live-search fields

View File

@ -1,8 +1,11 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
// //
// See scriptaculous.js for full license. // script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
/*--------------------------------------------------------------------------*/ if(typeof Effect == 'undefined')
throw("dragdrop.js requires including script.aculo.us' effects.js library");
var Droppables = { var Droppables = {
drops: [], drops: [],
@ -15,7 +18,8 @@ var Droppables = {
element = $(element); element = $(element);
var options = Object.extend({ var options = Object.extend({
greedy: true, greedy: true,
hoverclass: null hoverclass: null,
tree: false
}, arguments[1] || {}); }, arguments[1] || {});
// cache containers // cache containers
@ -38,9 +42,24 @@ var Droppables = {
this.drops.push(options); this.drops.push(options);
}, },
findDeepestChild: function(drops) {
deepest = drops[0];
for (i = 1; i < drops.length; ++i)
if (Element.isParent(drops[i].element, deepest.element))
deepest = drops[i];
return deepest;
},
isContained: function(element, drop) { isContained: function(element, drop) {
var parentNode = element.parentNode; var containmentNode;
return drop._containers.detect(function(c) { return parentNode == c }); if(drop.tree) {
containmentNode = element.treeNode;
} else {
containmentNode = element.parentNode;
}
return drop._containers.detect(function(c) { return containmentNode == c });
}, },
isAffected: function(point, element, drop) { isAffected: function(point, element, drop) {
@ -68,18 +87,22 @@ var Droppables = {
show: function(point, element) { show: function(point, element) {
if(!this.drops.length) return; if(!this.drops.length) return;
var affected = [];
if(this.last_active) this.deactivate(this.last_active); if(this.last_active) this.deactivate(this.last_active);
this.drops.each( function(drop) { this.drops.each( function(drop) {
if(Droppables.isAffected(point, element, drop)) { if(Droppables.isAffected(point, element, drop))
affected.push(drop);
});
if(affected.length>0) {
drop = Droppables.findDeepestChild(affected);
Position.within(drop.element, point[0], point[1]);
if(drop.onHover) if(drop.onHover)
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
if(drop.greedy) {
Droppables.activate(drop); Droppables.activate(drop);
throw $break;
} }
}
});
}, },
fire: function(event, element) { fire: function(event, element) {
@ -124,11 +147,19 @@ var Draggables = {
}, },
activate: function(draggable) { activate: function(draggable) {
if(draggable.options.delay) {
this._timeout = setTimeout(function() {
Draggables._timeout = null;
window.focus();
Draggables.activeDraggable = draggable;
}.bind(this), draggable.options.delay);
} else {
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
this.activeDraggable = draggable; this.activeDraggable = draggable;
}
}, },
deactivate: function(draggbale) { deactivate: function() {
this.activeDraggable = null; this.activeDraggable = null;
}, },
@ -139,13 +170,19 @@ var Draggables = {
// the same coordinates, prevent needless redrawing (moz bug?) // the same coordinates, prevent needless redrawing (moz bug?)
if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
this._lastPointer = pointer; this._lastPointer = pointer;
this.activeDraggable.updateDrag(event, pointer); this.activeDraggable.updateDrag(event, pointer);
}, },
endDrag: function(event) { endDrag: function(event) {
if(this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}
if(!this.activeDraggable) return; if(!this.activeDraggable) return;
this._lastPointer = null; this._lastPointer = null;
this.activeDraggable.endDrag(event); this.activeDraggable.endDrag(event);
this.activeDraggable = null;
}, },
keyPress: function(event) { keyPress: function(event) {
@ -168,6 +205,7 @@ var Draggables = {
this.observers.each( function(o) { this.observers.each( function(o) {
if(o[eventName]) o[eventName](eventName, draggable, event); if(o[eventName]) o[eventName](eventName, draggable, event);
}); });
if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
}, },
_cacheObserverCallbacks: function() { _cacheObserverCallbacks: function() {
@ -182,32 +220,60 @@ var Draggables = {
/*--------------------------------------------------------------------------*/ /*--------------------------------------------------------------------------*/
var Draggable = Class.create(); var Draggable = Class.create();
Draggable._dragging = {};
Draggable.prototype = { Draggable.prototype = {
initialize: function(element) { initialize: function(element) {
var options = Object.extend({ var defaults = {
handle: false, handle: false,
starteffect: function(element) {
new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
},
reverteffect: function(element, top_offset, left_offset) { reverteffect: function(element, top_offset, left_offset) {
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
element._revert = new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur}); new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
queue: {scope:'_draggable', position:'end'}
});
}, },
endeffect: function(element) { endeffect: function(element) {
new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0;
new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
queue: {scope:'_draggable', position:'end'},
afterFinish: function(){
Draggable._dragging[element] = false
}
});
}, },
zindex: 1000, zindex: 1000,
revert: false, revert: false,
snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] } scroll: false,
}, arguments[1] || {}); scrollSensitivity: 20,
scrollSpeed: 15,
snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
delay: 0
};
if(!arguments[1] || typeof arguments[1].endeffect == 'undefined')
Object.extend(defaults, {
starteffect: function(element) {
element._opacity = Element.getOpacity(element);
Draggable._dragging[element] = true;
new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
}
});
var options = Object.extend(defaults, arguments[1] || {});
this.element = $(element); this.element = $(element);
if(options.handle && (typeof options.handle == 'string')) if(options.handle && (typeof options.handle == 'string'))
this.handle = Element.childrenWithClassName(this.element, options.handle)[0]; this.handle = this.element.down('.'+options.handle, 0);
if(!this.handle) this.handle = $(options.handle); if(!this.handle) this.handle = $(options.handle);
if(!this.handle) this.handle = this.element; if(!this.handle) this.handle = this.element;
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
options.scroll = $(options.scroll);
this._isScrollChild = Element.childOf(this.element, options.scroll);
}
Element.makePositioned(this.element); // fix IE Element.makePositioned(this.element); // fix IE
this.delta = this.currentDelta(); this.delta = this.currentDelta();
@ -227,25 +293,23 @@ Draggable.prototype = {
currentDelta: function() { currentDelta: function() {
return([ return([
parseInt(this.element.style.left || '0'), parseInt(Element.getStyle(this.element,'left') || '0'),
parseInt(this.element.style.top || '0')]); parseInt(Element.getStyle(this.element,'top') || '0')]);
}, },
initDrag: function(event) { initDrag: function(event) {
if(typeof Draggable._dragging[this.element] != 'undefined' &&
Draggable._dragging[this.element]) return;
if(Event.isLeftClick(event)) { if(Event.isLeftClick(event)) {
// abort on form elements, fixes a Firefox issue // abort on form elements, fixes a Firefox issue
var src = Event.element(event); var src = Event.element(event);
if(src.tagName && ( if(src.tagName && (
src.tagName=='INPUT' || src.tagName=='INPUT' ||
src.tagName=='SELECT' || src.tagName=='SELECT' ||
src.tagName=='OPTION' ||
src.tagName=='BUTTON' || src.tagName=='BUTTON' ||
src.tagName=='TEXTAREA')) return; src.tagName=='TEXTAREA')) return;
if(this.element._revert) {
this.element._revert.cancel();
this.element._revert = null;
}
var pointer = [Event.pointerX(event), Event.pointerY(event)]; var pointer = [Event.pointerX(event), Event.pointerY(event)];
var pos = Position.cumulativeOffset(this.element); var pos = Position.cumulativeOffset(this.element);
this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
@ -269,7 +333,19 @@ Draggable.prototype = {
this.element.parentNode.insertBefore(this._clone, this.element); this.element.parentNode.insertBefore(this._clone, this.element);
} }
if(this.options.scroll) {
if (this.options.scroll == window) {
var where = this._getWindowScroll(this.options.scroll);
this.originalScrollLeft = where.left;
this.originalScrollTop = where.top;
} else {
this.originalScrollLeft = this.options.scroll.scrollLeft;
this.originalScrollTop = this.options.scroll.scrollTop;
}
}
Draggables.notify('onStart', this, event); Draggables.notify('onStart', this, event);
if(this.options.starteffect) this.options.starteffect(this.element); if(this.options.starteffect) this.options.starteffect(this.element);
}, },
@ -278,11 +354,34 @@ Draggable.prototype = {
Position.prepare(); Position.prepare();
Droppables.show(pointer, this.element); Droppables.show(pointer, this.element);
Draggables.notify('onDrag', this, event); Draggables.notify('onDrag', this, event);
this.draw(pointer); this.draw(pointer);
if(this.options.change) this.options.change(this); if(this.options.change) this.options.change(this);
if(this.options.scroll) {
this.stopScrolling();
var p;
if (this.options.scroll == window) {
with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
} else {
p = Position.page(this.options.scroll);
p[0] += this.options.scroll.scrollLeft + Position.deltaX;
p[1] += this.options.scroll.scrollTop + Position.deltaY;
p.push(p[0]+this.options.scroll.offsetWidth);
p.push(p[1]+this.options.scroll.offsetHeight);
}
var speed = [0,0];
if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
this.startScrolling(speed);
}
// fix AppleWebKit rendering // fix AppleWebKit rendering
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
Event.stop(event); Event.stop(event);
}, },
@ -320,27 +419,40 @@ Draggable.prototype = {
}, },
keyPress: function(event) { keyPress: function(event) {
if(!event.keyCode==Event.KEY_ESC) return; if(event.keyCode!=Event.KEY_ESC) return;
this.finishDrag(event, false); this.finishDrag(event, false);
Event.stop(event); Event.stop(event);
}, },
endDrag: function(event) { endDrag: function(event) {
if(!this.dragging) return; if(!this.dragging) return;
this.stopScrolling();
this.finishDrag(event, true); this.finishDrag(event, true);
Event.stop(event); Event.stop(event);
}, },
draw: function(point) { draw: function(point) {
var pos = Position.cumulativeOffset(this.element); var pos = Position.cumulativeOffset(this.element);
if(this.options.ghosting) {
var r = Position.realOffset(this.element);
pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
}
var d = this.currentDelta(); var d = this.currentDelta();
pos[0] -= d[0]; pos[1] -= d[1]; pos[0] -= d[0]; pos[1] -= d[1];
var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this)); if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
}
var p = [0,1].map(function(i){
return (point[i]-pos[i]-this.offset[i])
}.bind(this));
if(this.options.snap) { if(this.options.snap) {
if(typeof this.options.snap == 'function') { if(typeof this.options.snap == 'function') {
p = this.options.snap(p[0],p[1]); p = this.options.snap(p[0],p[1],this);
} else { } else {
if(this.options.snap instanceof Array) { if(this.options.snap instanceof Array) {
p = p.map( function(v, i) { p = p.map( function(v, i) {
@ -356,7 +468,80 @@ Draggable.prototype = {
style.left = p[0] + "px"; style.left = p[0] + "px";
if((!this.options.constraint) || (this.options.constraint=='vertical')) if((!this.options.constraint) || (this.options.constraint=='vertical'))
style.top = p[1] + "px"; style.top = p[1] + "px";
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
},
stopScrolling: function() {
if(this.scrollInterval) {
clearInterval(this.scrollInterval);
this.scrollInterval = null;
Draggables._lastScrollPointer = null;
}
},
startScrolling: function(speed) {
if(!(speed[0] || speed[1])) return;
this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
this.lastScrolled = new Date();
this.scrollInterval = setInterval(this.scroll.bind(this), 10);
},
scroll: function() {
var current = new Date();
var delta = current - this.lastScrolled;
this.lastScrolled = current;
if(this.options.scroll == window) {
with (this._getWindowScroll(this.options.scroll)) {
if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
var d = delta / 1000;
this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
}
}
} else {
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
}
Position.prepare();
Droppables.show(Draggables._lastPointer, this.element);
Draggables.notify('onDrag', this);
if (this._isScrollChild) {
Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
if (Draggables._lastScrollPointer[0] < 0)
Draggables._lastScrollPointer[0] = 0;
if (Draggables._lastScrollPointer[1] < 0)
Draggables._lastScrollPointer[1] = 0;
this.draw(Draggables._lastScrollPointer);
}
if(this.options.change) this.options.change(this);
},
_getWindowScroll: function(w) {
var T, L, W, H;
with (w.document) {
if (w.document.documentElement && documentElement.scrollTop) {
T = documentElement.scrollTop;
L = documentElement.scrollLeft;
} else if (w.document.body) {
T = body.scrollTop;
L = body.scrollLeft;
}
if (w.innerWidth) {
W = w.innerWidth;
H = w.innerHeight;
} else if (w.document.documentElement && documentElement.clientWidth) {
W = documentElement.clientWidth;
H = documentElement.clientHeight;
} else {
W = body.offsetWidth;
H = body.offsetHeight
}
}
return { top: T, left: L, width: W, height: H };
} }
} }
@ -382,21 +567,33 @@ SortableObserver.prototype = {
} }
var Sortable = { var Sortable = {
sortables: new Array(), SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
options: function(element){ sortables: {},
element = $(element);
return this.sortables.detect(function(s) { return s.element == element }); _findRootElement: function(element) {
while (element.tagName != "BODY") {
if(element.id && Sortable.sortables[element.id]) return element;
element = element.parentNode;
}
},
options: function(element) {
element = Sortable._findRootElement($(element));
if(!element) return;
return Sortable.sortables[element.id];
}, },
destroy: function(element){ destroy: function(element){
element = $(element); var s = Sortable.options(element);
this.sortables.findAll(function(s) { return s.element == element }).each(function(s){
if(s) {
Draggables.removeObserver(s.element); Draggables.removeObserver(s.element);
s.droppables.each(function(d){ Droppables.remove(d) }); s.droppables.each(function(d){ Droppables.remove(d) });
s.draggables.invoke('destroy'); s.draggables.invoke('destroy');
});
this.sortables = this.sortables.reject(function(s) { return s.element == element }); delete Sortable.sortables[s.element.id];
}
}, },
create: function(element) { create: function(element) {
@ -405,15 +602,20 @@ var Sortable = {
element: element, element: element,
tag: 'li', // assumes li children, override with tag: 'tagname' tag: 'li', // assumes li children, override with tag: 'tagname'
dropOnEmpty: false, dropOnEmpty: false,
tree: false, // fixme: unimplemented tree: false,
treeTag: 'ul',
overlap: 'vertical', // one of 'vertical', 'horizontal' overlap: 'vertical', // one of 'vertical', 'horizontal'
constraint: 'vertical', // one of 'vertical', 'horizontal', false constraint: 'vertical', // one of 'vertical', 'horizontal', false
containment: element, // also takes array of elements (or id's); or false containment: element, // also takes array of elements (or id's); or false
handle: false, // or a CSS class handle: false, // or a CSS class
only: false, only: false,
delay: 0,
hoverclass: null, hoverclass: null,
ghosting: false, ghosting: false,
format: null, scroll: false,
scrollSensitivity: 20,
scrollSpeed: 15,
format: this.SERIALIZE_RULE,
onChange: Prototype.emptyFunction, onChange: Prototype.emptyFunction,
onUpdate: Prototype.emptyFunction onUpdate: Prototype.emptyFunction
}, arguments[1] || {}); }, arguments[1] || {});
@ -424,6 +626,10 @@ var Sortable = {
// build options for the draggables // build options for the draggables
var options_for_draggable = { var options_for_draggable = {
revert: true, revert: true,
scroll: options.scroll,
scrollSpeed: options.scrollSpeed,
scrollSensitivity: options.scrollSensitivity,
delay: options.delay,
ghosting: options.ghosting, ghosting: options.ghosting,
constraint: options.constraint, constraint: options.constraint,
handle: options.handle }; handle: options.handle };
@ -449,9 +655,16 @@ var Sortable = {
var options_for_droppable = { var options_for_droppable = {
overlap: options.overlap, overlap: options.overlap,
containment: options.containment, containment: options.containment,
tree: options.tree,
hoverclass: options.hoverclass, hoverclass: options.hoverclass,
onHover: Sortable.onHover, onHover: Sortable.onHover
greedy: !options.dropOnEmpty }
var options_for_tree = {
onHover: Sortable.onEmptyHover,
overlap: options.overlap,
containment: options.containment,
hoverclass: options.hoverclass
} }
// fix for gecko engine // fix for gecko engine
@ -460,27 +673,33 @@ var Sortable = {
options.draggables = []; options.draggables = [];
options.droppables = []; options.droppables = [];
// make it so
// drop on empty handling // drop on empty handling
if(options.dropOnEmpty) { if(options.dropOnEmpty || options.tree) {
Droppables.add(element, Droppables.add(element, options_for_tree);
{containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false});
options.droppables.push(element); options.droppables.push(element);
} }
(this.findElements(element, options) || []).each( function(e) { (this.findElements(element, options) || []).each( function(e) {
// handles are per-draggable // handles are per-draggable
var handle = options.handle ? var handle = options.handle ?
Element.childrenWithClassName(e, options.handle)[0] : e; $(e).down('.'+options.handle,0) : e;
options.draggables.push( options.draggables.push(
new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
Droppables.add(e, options_for_droppable); Droppables.add(e, options_for_droppable);
if(options.tree) e.treeNode = element;
options.droppables.push(e); options.droppables.push(e);
}); });
if(options.tree) {
(Sortable.findTreeElements(element, options) || []).each( function(e) {
Droppables.add(e, options_for_tree);
e.treeNode = element;
options.droppables.push(e);
});
}
// keep reference // keep reference
this.sortables.push(options); this.sortables[element.id] = options;
// for onupdate // for onupdate
Draggables.addObserver(new SortableObserver(element, options.onUpdate)); Draggables.addObserver(new SortableObserver(element, options.onUpdate));
@ -489,23 +708,21 @@ var Sortable = {
// return all suitable-for-sortable elements in a guaranteed order // return all suitable-for-sortable elements in a guaranteed order
findElements: function(element, options) { findElements: function(element, options) {
if(!element.hasChildNodes()) return null; return Element.findChildren(
var elements = []; element, options.only, options.tree ? true : false, options.tag);
$A(element.childNodes).each( function(e) { },
if(e.tagName && e.tagName.toUpperCase()==options.tag.toUpperCase() &&
(!options.only || (Element.hasClassName(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); findTreeElements: function(element, options) {
return Element.findChildren(
element, options.only, options.tree ? true : false, options.treeTag);
}, },
onHover: function(element, dropon, overlap) { onHover: function(element, dropon, overlap) {
if(overlap>0.5) { if(Element.isParent(dropon, element)) return;
if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
return;
} else if(overlap>0.5) {
Sortable.mark(dropon, 'before'); Sortable.mark(dropon, 'before');
if(dropon.previousSibling != element) { if(dropon.previousSibling != element) {
var oldParentNode = element.parentNode; var oldParentNode = element.parentNode;
@ -529,17 +746,41 @@ var Sortable = {
} }
}, },
onEmptyHover: function(element, dropon) { onEmptyHover: function(element, dropon, overlap) {
if(element.parentNode!=dropon) {
var oldParentNode = element.parentNode; var oldParentNode = element.parentNode;
dropon.appendChild(element); var droponOptions = Sortable.options(dropon);
if(!Element.isParent(dropon, element)) {
var index;
var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
var child = null;
if(children) {
var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
for (index = 0; index < children.length; index += 1) {
if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
offset -= Element.offsetSize (children[index], droponOptions.overlap);
} else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
child = index + 1 < children.length ? children[index + 1] : null;
break;
} else {
child = children[index];
break;
}
}
}
dropon.insertBefore(element, child);
Sortable.options(oldParentNode).onChange(element); Sortable.options(oldParentNode).onChange(element);
Sortable.options(dropon).onChange(element); droponOptions.onChange(element);
} }
}, },
unmark: function() { unmark: function() {
if(Sortable._marker) Element.hide(Sortable._marker); if(Sortable._marker) Sortable._marker.hide();
}, },
mark: function(dropon, position) { mark: function(dropon, position) {
@ -548,37 +789,154 @@ var Sortable = {
if(sortable && !sortable.ghosting) return; if(sortable && !sortable.ghosting) return;
if(!Sortable._marker) { if(!Sortable._marker) {
Sortable._marker = $('dropmarker') || document.createElement('DIV'); Sortable._marker =
Element.hide(Sortable._marker); ($('dropmarker') || Element.extend(document.createElement('DIV'))).
Element.addClassName(Sortable._marker, 'dropmarker'); hide().addClassName('dropmarker').setStyle({position:'absolute'});
Sortable._marker.style.position = 'absolute';
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
} }
var offsets = Position.cumulativeOffset(dropon); var offsets = Position.cumulativeOffset(dropon);
Sortable._marker.style.left = offsets[0] + 'px'; Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
Sortable._marker.style.top = offsets[1] + 'px';
if(position=='after') if(position=='after')
if(sortable.overlap == 'horizontal') if(sortable.overlap == 'horizontal')
Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px'; Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
else else
Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
Element.show(Sortable._marker); Sortable._marker.show();
}, },
serialize: function(element) { _tree: function(element, options, parent) {
var children = Sortable.findElements(element, options) || [];
for (var i = 0; i < children.length; ++i) {
var match = children[i].id.match(options.format);
if (!match) continue;
var child = {
id: encodeURIComponent(match ? match[1] : null),
element: element,
parent: parent,
children: [],
position: parent.children.length,
container: $(children[i]).down(options.treeTag)
}
/* Get the element containing the children and recurse over it */
if (child.container)
this._tree(child.container, options, child)
parent.children.push (child);
}
return parent;
},
tree: function(element) {
element = $(element); element = $(element);
var sortableOptions = this.options(element); var sortableOptions = this.options(element);
var options = Object.extend({ var options = Object.extend({
tag: sortableOptions.tag, tag: sortableOptions.tag,
treeTag: sortableOptions.treeTag,
only: sortableOptions.only, only: sortableOptions.only,
name: element.id, name: element.id,
format: sortableOptions.format || /^[^_]*_(.*)$/ format: sortableOptions.format
}, arguments[1] || {}); }, arguments[1] || {});
var root = {
id: null,
parent: null,
children: [],
container: element,
position: 0
}
return Sortable._tree(element, options, root);
},
/* Construct a [i] index for a particular node */
_constructIndex: function(node) {
var index = '';
do {
if (node.id) index = '[' + node.position + ']' + index;
} while ((node = node.parent) != null);
return index;
},
sequence: function(element) {
element = $(element);
var options = Object.extend(this.options(element), arguments[1] || {});
return $(this.findElements(element, options) || []).map( function(item) { return $(this.findElements(element, options) || []).map( function(item) {
return (encodeURIComponent(options.name) + "[]=" + return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : '')); });
}).join("&"); },
setSequence: function(element, new_sequence) {
element = $(element);
var options = Object.extend(this.options(element), arguments[2] || {});
var nodeMap = {};
this.findElements(element, options).each( function(n) {
if (n.id.match(options.format))
nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
n.parentNode.removeChild(n);
});
new_sequence.each(function(ident) {
var n = nodeMap[ident];
if (n) {
n[1].appendChild(n[0]);
delete nodeMap[ident];
}
});
},
serialize: function(element) {
element = $(element);
var options = Object.extend(Sortable.options(element), arguments[1] || {});
var name = encodeURIComponent(
(arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
if (options.tree) {
return Sortable.tree(element, arguments[1]).children.map( function (item) {
return [name + Sortable._constructIndex(item) + "[id]=" +
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
}).flatten().join('&');
} else {
return Sortable.sequence(element, arguments[1]).map( function(item) {
return name + "[]=" + encodeURIComponent(item);
}).join('&');
}
} }
} }
// Returns true if child is contained within element
Element.isParent = function(child, element) {
if (!child.parentNode || child == element) return false;
if (child.parentNode == element) return true;
return Element.isParent(child.parentNode, element);
}
Element.findChildren = function(element, only, recursive, tagName) {
if(!element.hasChildNodes()) return null;
tagName = tagName.toUpperCase();
if(only) only = [only].flatten();
var elements = [];
$A(element.childNodes).each( function(e) {
if(e.tagName && e.tagName.toUpperCase()==tagName &&
(!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
elements.push(e);
if(recursive) {
var grandchildren = Element.findChildren(e, only, recursive, tagName);
if(grandchildren) elements.push(grandchildren);
}
});
return (elements.length>0 ? elements.flatten() : []);
}
Element.offsetSize = function (element, type) {
return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff