You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by ma...@apache.org on 2006/01/04 21:49:33 UTC
svn commit: r365979 [1/2] - in
/myfaces/tomahawk/trunk/src/main/resources/org/apache/myfaces/custom/prototype/resource:
controls.js dragdrop.js effects.js prototype.js
Author: matzew
Date: Wed Jan 4 12:49:21 2006
New Revision: 365979
URL: http://svn.apache.org/viewcvs?rev=365979&view=rev
Log:
MYFACES-871
Modified:
myfaces/tomahawk/trunk/src/main/resources/org/apache/myfaces/custom/prototype/resource/controls.js
myfaces/tomahawk/trunk/src/main/resources/org/apache/myfaces/custom/prototype/resource/dragdrop.js
myfaces/tomahawk/trunk/src/main/resources/org/apache/myfaces/custom/prototype/resource/effects.js
myfaces/tomahawk/trunk/src/main/resources/org/apache/myfaces/custom/prototype/resource/prototype.js
Modified: myfaces/tomahawk/trunk/src/main/resources/org/apache/myfaces/custom/prototype/resource/controls.js
URL: http://svn.apache.org/viewcvs/myfaces/tomahawk/trunk/src/main/resources/org/apache/myfaces/custom/prototype/resource/controls.js?rev=365979&r1=365978&r2=365979&view=diff
==============================================================================
--- myfaces/tomahawk/trunk/src/main/resources/org/apache/myfaces/custom/prototype/resource/controls.js (original)
+++ myfaces/tomahawk/trunk/src/main/resources/org/apache/myfaces/custom/prototype/resource/controls.js Wed Jan 4 12:49:21 2006
@@ -1,52 +1,23 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
-//
-// 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.collectTextNodesIgnoreClass = function(element, ignoreclass) {
- var children = $(element).childNodes;
- var text = "";
- var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i");
-
- for (var i = 0; i < children.length; i++) {
- if(children[i].nodeType==3) {
- text+=children[i].nodeValue;
- } else {
- if((!children[i].className.match(classtest)) && children[i].hasChildNodes())
- text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass);
- }
- }
+// (c) 2005 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+// Richard Livsey
+// Rahul Bhargava
+// Rob Wills
+//
+// See scriptaculous.js for full license.
- return text;
-}
-
-// Autocompleter.Base handles all the autocompletion functionality
+// Autocompleter.Base handles all the autocompletion functionality
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
-// Specific autocompleters need to provide, at the very least,
+// Specific autocompleters need to provide, at the very least,
// a getUpdatedChoices function that will be invoked every time
-// the text inside the monitored textbox changes. This method
+// the text inside the monitored textbox changes. This method
// should get the text for which to provide autocompletion by
-// invoking this.getEntry(), NOT by directly accessing
+// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
@@ -57,52 +28,49 @@
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
-// a token array, e.g. { tokens: new Array (',', '\n') } which
-// enables autocompletion on multiple tokens. This is most
-// useful when one of the tokens is \n (a newline), as it
+// a token array, e.g. { tokens: [',', '\n'] } which
+// enables autocompletion on multiple tokens. This is most
+// useful when one of the tokens is \n (a newline), as it
// allows smart autocompletion after linebreaks.
var Autocompleter = {}
Autocompleter.Base = function() {};
Autocompleter.Base.prototype = {
- base_initialize: function(element, update, options) {
- this.element = $(element);
- this.update = $(update);
- this.has_focus = false;
- this.changed = false;
- this.active = false;
- this.index = 0;
- this.entry_count = 0;
+ baseInitialize: function(element, update, options) {
+ this.element = $(element);
+ this.update = $(update);
+ this.hasFocus = false;
+ this.changed = false;
+ this.active = false;
+ this.index = 0;
+ this.entryCount = 0;
if (this.setOptions)
this.setOptions(options);
else
- this.options = {}
+ this.options = options || {};
- this.options.tokens = this.options.tokens || new Array();
+ this.options.paramName = this.options.paramName || this.element.name;
+ this.options.tokens = this.options.tokens || [];
this.options.frequency = this.options.frequency || 0.4;
- this.options.min_chars = this.options.min_chars || 1;
- this.options.onShow = this.options.onShow ||
- function(element, update){
+ this.options.minChars = this.options.minChars || 1;
+ this.options.onShow = this.options.onShow ||
+ function(element, update){
if(!update.style.position || update.style.position=='absolute') {
update.style.position = 'absolute';
- var offsets = Position.cumulativeOffset(element);
- update.style.left = offsets[0] + 'px';
- update.style.top = (offsets[1] + element.offsetHeight) + 'px';
- update.style.width = element.offsetWidth + 'px';
+ Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
}
- new 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}) };
- if(this.options.indicator)
- this.indicator = $(this.options.indicator);
-
- if (typeof(this.options.tokens) == 'string')
+ if (typeof(this.options.tokens) == 'string')
this.options.tokens = new Array(this.options.tokens);
this.observer = null;
+
+ this.element.setAttribute('autocomplete','off');
Element.hide(this.update);
@@ -110,42 +78,40 @@
Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
},
- resetWidth: function() {
- this.update.style.width = this.element.offsetWidth + 'px';
- if (this.update.scrollWidth - 8 > this.element.offsetWidth) {
- this.update.style.width = (this.update.scrollWidth - 6) + 'px';
- }
- },
-
show: function() {
- if(this.update.style.display=='none') this.options.onShow(this.element, this.update);
- this.resetWidth();
- if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') {
- new Insertion.After(this.update,
+ if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+ if(!this.iefix &&
+ (navigator.appVersion.indexOf('MSIE')>0) &&
+ (navigator.userAgent.indexOf('Opera')<0) &&
+ (Element.getStyle(this.update, 'position')=='absolute')) {
+ new Insertion.After(this.update,
'<iframe id="' + this.update.id + '_iefix" '+
- 'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
+ 'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
this.iefix = $(this.update.id+'_iefix');
}
- if(this.iefix) {
- Position.clone(this.update, this.iefix);
- this.iefix.style.zIndex = 1;
- this.update.style.zIndex = 2;
- Element.show(this.iefix);
- }
+ if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
+ },
+
+ fixIEOverlapping: function() {
+ Position.clone(this.update, this.iefix);
+ this.iefix.style.zIndex = 1;
+ this.update.style.zIndex = 2;
+ Element.show(this.iefix);
},
hide: function() {
- if(this.update.style.display=='') this.options.onHide(this.element, this.update);
+ this.stopIndicator();
+ if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
if(this.iefix) Element.hide(this.iefix);
},
startIndicator: function() {
- if(this.indicator) Element.show(this.indicator);
+ if(this.options.indicator) Element.show(this.options.indicator);
},
stopIndicator: function() {
- if(this.indicator) Element.hide(this.indicator);
+ if(this.options.indicator) Element.hide(this.options.indicator);
},
onKeyPress: function(event) {
@@ -153,133 +119,147 @@
switch(event.keyCode) {
case Event.KEY_TAB:
case Event.KEY_RETURN:
- this.select_entry();
+ this.selectEntry();
Event.stop(event);
case Event.KEY_ESC:
this.hide();
this.active = false;
+ Event.stop(event);
return;
case Event.KEY_LEFT:
case Event.KEY_RIGHT:
return;
case Event.KEY_UP:
- this.mark_previous();
+ this.markPrevious();
this.render();
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
return;
case Event.KEY_DOWN:
- this.mark_next();
+ this.markNext();
this.render();
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
return;
}
- else
- if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
+ else
+ if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN)
return;
this.changed = true;
- this.has_focus = true;
+ this.hasFocus = true;
if(this.observer) clearTimeout(this.observer);
- this.observer =
+ this.observer =
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
},
onHover: function(event) {
var element = Event.findElement(event, 'LI');
- if(this.index != element.autocompleteIndex)
+ if(this.index != element.autocompleteIndex)
{
this.index = element.autocompleteIndex;
this.render();
}
Event.stop(event);
},
-
+
onClick: function(event) {
var element = Event.findElement(event, 'LI');
this.index = element.autocompleteIndex;
- this.select_entry();
- Event.stop(event);
+ this.selectEntry();
+ this.hide();
},
-
+
onBlur: function(event) {
// needed to make click events working
setTimeout(this.hide.bind(this), 250);
- this.has_focus = false;
- this.active = false;
- },
-
+ this.hasFocus = false;
+ this.active = false;
+ },
+
render: function() {
- if(this.entry_count > 0) {
- for (var i = 0; i < this.entry_count; i++)
- this.index==i ?
- Element.addClassName(this.get_entry(i),"selected") :
- Element.removeClassName(this.get_entry(i),"selected");
-
- if(this.has_focus) {
- if(this.get_current_entry().scrollIntoView)
- this.get_current_entry().scrollIntoView(false);
-
+ if(this.entryCount > 0) {
+ for (var i = 0; i < this.entryCount; i++)
+ this.index==i ?
+ Element.addClassName(this.getEntry(i),"selected") :
+ Element.removeClassName(this.getEntry(i),"selected");
+
+ if(this.hasFocus) {
this.show();
this.active = true;
}
- } else this.hide();
+ } else {
+ this.active = false;
+ this.hide();
+ }
},
-
- mark_previous: function() {
+
+ markPrevious: function() {
if(this.index > 0) this.index--
- else this.index = this.entry_count-1;
+ else this.index = this.entryCount-1;
},
-
- mark_next: function() {
- if(this.index < this.entry_count-1) this.index++
+
+ markNext: function() {
+ if(this.index < this.entryCount-1) this.index++
else this.index = 0;
},
-
- get_entry: function(index) {
+
+ getEntry: function(index) {
return this.update.firstChild.childNodes[index];
},
-
- get_current_entry: function() {
- return this.get_entry(this.index);
+
+ getCurrentEntry: function() {
+ return this.getEntry(this.index);
},
-
- select_entry: function() {
+
+ selectEntry: function() {
this.active = false;
- value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML();
- this.updateElement(value);
- this.element.focus();
+ this.updateElement(this.getCurrentEntry());
},
- updateElement: function(value) {
- var last_token_pos = this.findLastToken();
- if (last_token_pos != -1) {
- var new_value = this.element.value.substr(0, last_token_pos + 1);
- var whitespace = this.element.value.substr(last_token_pos + 1).match(/^\s+/);
+ updateElement: function(selectedElement) {
+ if (this.options.updateElement) {
+ this.options.updateElement(selectedElement);
+ 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 lastTokenPos = this.findLastToken();
+ if (lastTokenPos != -1) {
+ var newValue = this.element.value.substr(0, lastTokenPos + 1);
+ var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
if (whitespace)
- new_value += whitespace[0];
- this.element.value = new_value + value;
+ newValue += whitespace[0];
+ this.element.value = newValue + value;
} else {
this.element.value = value;
}
+ this.element.focus();
+
+ if (this.options.afterUpdateElement)
+ this.options.afterUpdateElement(this.element, selectedElement);
},
updateChoices: function(choices) {
- if(!this.changed && this.has_focus) {
+ if(!this.changed && this.hasFocus) {
this.update.innerHTML = choices;
Element.cleanWhitespace(this.update);
Element.cleanWhitespace(this.update.firstChild);
if(this.update.firstChild && this.update.firstChild.childNodes) {
- this.entry_count =
+ this.entryCount =
this.update.firstChild.childNodes.length;
- for (var i = 0; i < this.entry_count; i++) {
- entry = this.get_entry(i);
+ for (var i = 0; i < this.entryCount; i++) {
+ var entry = this.getEntry(i);
entry.autocompleteIndex = i;
this.addObservers(entry);
}
- } else {
- this.entry_count = 0;
+ } else {
+ this.entryCount = 0;
}
this.stopIndicator();
@@ -295,8 +275,8 @@
},
onObserverEvent: function() {
- this.changed = false;
- if(this.getEntry().length>=this.options.min_chars) {
+ this.changed = false;
+ if(this.getToken().length>=this.options.minChars) {
this.startIndicator();
this.getUpdatedChoices();
} else {
@@ -305,10 +285,10 @@
}
},
- getEntry: function() {
- var token_pos = this.findLastToken();
- if (token_pos != -1)
- var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
+ getToken: function() {
+ var tokenPos = this.findLastToken();
+ if (tokenPos != -1)
+ var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
else
var ret = this.element.value;
@@ -316,34 +296,36 @@
},
findLastToken: function() {
- var last_token_pos = -1;
+ var lastTokenPos = -1;
for (var i=0; i<this.options.tokens.length; i++) {
- var this_token_pos = this.element.value.lastIndexOf(this.options.tokens[i]);
- if (this_token_pos > last_token_pos)
- last_token_pos = this_token_pos;
+ var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
+ if (thisTokenPos > lastTokenPos)
+ lastTokenPos = thisTokenPos;
}
- return last_token_pos;
+ return lastTokenPos;
}
}
Ajax.Autocompleter = Class.create();
-Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(),
-Object.extend(new Ajax.Base(), {
+Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
initialize: function(element, update, url, options) {
- this.base_initialize(element, update, options);
- this.options.asynchronous = true;
- this.options.onComplete = this.onComplete.bind(this)
- this.options.method = 'post';
- this.url = url;
+ this.baseInitialize(element, update, options);
+ this.options.asynchronous = true;
+ this.options.onComplete = this.onComplete.bind(this);
+ this.options.defaultParams = this.options.parameters || null;
+ this.url = url;
},
getUpdatedChoices: function() {
- entry = encodeURIComponent(this.element.name) + '=' +
- encodeURIComponent(this.getEntry());
+ entry = encodeURIComponent(this.options.paramName) + '=' +
+ encodeURIComponent(this.getToken());
+
+ this.options.parameters = this.options.callback ?
+ this.options.callback(this.element, entry) : entry;
- this.options.parameters = this.options.callback ?
- this.options.callback(this.element, entry) : entry;
+ if(this.options.defaultParams)
+ this.options.parameters += '&' + this.options.defaultParams;
new Ajax.Request(this.url, this.options);
},
@@ -352,7 +334,7 @@
this.updateChoices(request.responseText);
}
-}));
+});
// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
@@ -366,25 +348,25 @@
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
-// - partial_search - If false, the autocompleter will match entered
-// text only at the beginning of strings in the
+// - partialSearch - If false, the autocompleter will match entered
+// text only at the beginning of strings in the
// autocomplete array. Defaults to true, which will
// match text at the beginning of any *word* in the
// strings in the autocomplete array. If you want to
// search anywhere in the string, additionally set
-// the option full_search to true (default: off).
+// the option fullSearch to true (default: off).
//
-// - full_search - Search anywhere in autocomplete array strings.
+// - fullSsearch - Search anywhere in autocomplete array strings.
//
-// - partial_chars - How many characters to enter before triggering
-// a partial match (unlike min_chars, which defines
+// - partialChars - How many characters to enter before triggering
+// a partial match (unlike minChars, which defines
// how many characters are required to do any match
// at all). Defaults to 2.
//
-// - ignore_case - Whether to ignore case when autocompleting.
+// - ignoreCase - Whether to ignore case when autocompleting.
// Defaults to true.
//
-// It's possible to pass in a custom function as the 'selector'
+// It's possible to pass in a custom function as the 'selector'
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.
@@ -392,7 +374,7 @@
Autocompleter.Local = Class.create();
Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
initialize: function(element, update, array, options) {
- this.base_initialize(element, update, options);
+ this.baseInitialize(element, update, options);
this.options.array = array;
},
@@ -403,35 +385,43 @@
setOptions: function(options) {
this.options = Object.extend({
choices: 10,
- partial_search: true,
- partial_chars: 2,
- ignore_case: true,
- full_search: false,
+ partialSearch: true,
+ partialChars: 2,
+ ignoreCase: true,
+ fullSearch: false,
selector: function(instance) {
- var ret = new Array(); // Beginning matches
- var partial = new Array(); // Inside matches
- var entry = instance.getEntry();
+ var ret = []; // Beginning matches
+ var partial = []; // Inside matches
+ var entry = instance.getToken();
var count = 0;
- for (var i = 0; i < instance.options.array.length &&
- ret.length < instance.options.choices ; i++) {
+ for (var i = 0; i < instance.options.array.length &&
+ ret.length < instance.options.choices ; i++) {
+
var elem = instance.options.array[i];
- var found_pos = instance.options.ignore_case ?
- elem.toLowerCase().indexOf(entry.toLowerCase()) :
+ var foundPos = instance.options.ignoreCase ?
+ elem.toLowerCase().indexOf(entry.toLowerCase()) :
elem.indexOf(entry);
- if (found_pos == 0 && elem.length == entry.length)
- continue;
- else if (found_pos == 0)
- ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
- elem.substr(entry.length) + "</li>");
- else if (entry.length >= instance.options.partial_chars &&
- instance.options.partial_search && found_pos != -1) {
- if (!instance.options.full_search && !/\s/.test(elem.substr(found_pos-1,1)))
- continue;
- partial.push("<li>" + elem.substr(0, found_pos) + "<strong>" +
- elem.substr(found_pos, entry.length) + "</strong>" + elem.substr(
- found_pos + entry.length) + "</li>")
+ while (foundPos != -1) {
+ if (foundPos == 0 && elem.length != entry.length) {
+ ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
+ elem.substr(entry.length) + "</li>");
+ break;
+ } else if (entry.length >= instance.options.partialChars &&
+ instance.options.partialSearch && foundPos != -1) {
+ if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
+ partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
+ elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
+ foundPos + entry.length) + "</li>");
+ break;
+ }
+ }
+
+ foundPos = instance.options.ignoreCase ?
+ elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
+ elem.indexOf(entry, foundPos + 1);
+
}
}
if (partial.length)
@@ -441,3 +431,340 @@
}, options || {});
}
});
+
+// AJAX in-place editor
+//
+// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
+
+// Use this if you notice weird scrolling problems on some browsers,
+// the DOM might be a bit confused when this gets called so do this
+// waits 1 ms (with setTimeout) until it does the activation
+Field.scrollFreeActivate = function(field) {
+ setTimeout(function() {
+ Field.activate(field);
+ }, 1);
+}
+
+Ajax.InPlaceEditor = Class.create();
+Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
+Ajax.InPlaceEditor.prototype = {
+ initialize: function(element, url, options) {
+ this.url = url;
+ this.element = $(element);
+
+ this.options = Object.extend({
+ okButton: true,
+ okText: "ok",
+ cancelLink: true,
+ cancelText: "cancel",
+ savingText: "Saving...",
+ clickToEditText: "Click to edit",
+ okText: "ok",
+ rows: 1,
+ onComplete: function(transport, element) {
+ new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
+ },
+ onFailure: function(transport) {
+ alert("Error communicating with the server: " + transport.responseText.stripTags());
+ },
+ callback: function(form) {
+ return Form.serialize(form);
+ },
+ handleLineBreaks: true,
+ loadingText: 'Loading...',
+ savingClassName: 'inplaceeditor-saving',
+ loadingClassName: 'inplaceeditor-loading',
+ formClassName: 'inplaceeditor-form',
+ highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
+ highlightendcolor: "#FFFFFF",
+ externalControl: null,
+ submitOnBlur: false,
+ ajaxOptions: {}
+ }, options || {});
+
+ if(!this.options.formId && this.element.id) {
+ this.options.formId = this.element.id + "-inplaceeditor";
+ if ($(this.options.formId)) {
+ // there's already a form with that name, don't specify an id
+ this.options.formId = null;
+ }
+ }
+
+ if (this.options.externalControl) {
+ this.options.externalControl = $(this.options.externalControl);
+ }
+
+ this.originalBackground = Element.getStyle(this.element, 'background-color');
+ if (!this.originalBackground) {
+ this.originalBackground = "transparent";
+ }
+
+ this.element.title = this.options.clickToEditText;
+
+ this.onclickListener = this.enterEditMode.bindAsEventListener(this);
+ this.mouseoverListener = this.enterHover.bindAsEventListener(this);
+ this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
+ Event.observe(this.element, 'click', this.onclickListener);
+ Event.observe(this.element, 'mouseover', this.mouseoverListener);
+ Event.observe(this.element, 'mouseout', this.mouseoutListener);
+ if (this.options.externalControl) {
+ Event.observe(this.options.externalControl, 'click', this.onclickListener);
+ Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
+ Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
+ }
+ },
+ enterEditMode: function(evt) {
+ if (this.saving) return;
+ if (this.editing) return;
+ this.editing = true;
+ this.onEnterEditMode();
+ if (this.options.externalControl) {
+ Element.hide(this.options.externalControl);
+ }
+ Element.hide(this.element);
+ this.createForm();
+ this.element.parentNode.insertBefore(this.form, this.element);
+ Field.scrollFreeActivate(this.editField);
+ // stop the event to avoid a page refresh in Safari
+ if (evt) {
+ Event.stop(evt);
+ }
+ return false;
+ },
+ createForm: function() {
+ this.form = document.createElement("form");
+ this.form.id = this.options.formId;
+ Element.addClassName(this.form, this.options.formClassName)
+ this.form.onsubmit = this.onSubmit.bind(this);
+
+ this.createEditField();
+
+ if (this.options.textarea) {
+ var br = document.createElement("br");
+ this.form.appendChild(br);
+ }
+
+ if (this.options.okButton) {
+ okButton = document.createElement("input");
+ okButton.type = "submit";
+ okButton.value = this.options.okText;
+ this.form.appendChild(okButton);
+ }
+
+ if (this.options.cancelLink) {
+ cancelLink = document.createElement("a");
+ cancelLink.href = "#";
+ cancelLink.appendChild(document.createTextNode(this.options.cancelText));
+ cancelLink.onclick = this.onclickCancel.bind(this);
+ this.form.appendChild(cancelLink);
+ }
+ },
+ hasHTMLLineBreaks: function(string) {
+ if (!this.options.handleLineBreaks) return false;
+ return string.match(/<br/i) || string.match(/<p>/i);
+ },
+ convertHTMLLineBreaks: function(string) {
+ return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
+ },
+ createEditField: function() {
+ var text;
+ if(this.options.loadTextURL) {
+ text = this.options.loadingText;
+ } else {
+ text = this.getText();
+ }
+
+ var obj = this;
+
+ if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
+ this.options.textarea = false;
+ var textField = document.createElement("input");
+ textField.obj = this;
+ textField.type = "text";
+ textField.name = "value";
+ textField.value = text;
+ textField.style.backgroundColor = this.options.highlightcolor;
+ var size = this.options.size || this.options.cols || 0;
+ if (size != 0) textField.size = size;
+ if (this.options.submitOnBlur)
+ textField.onblur = this.onSubmit.bind(this);
+ this.editField = textField;
+ } else {
+ this.options.textarea = true;
+ var textArea = document.createElement("textarea");
+ textArea.obj = this;
+ textArea.name = "value";
+ textArea.value = this.convertHTMLLineBreaks(text);
+ textArea.rows = this.options.rows;
+ textArea.cols = this.options.cols || 40;
+ if (this.options.submitOnBlur)
+ textArea.onblur = this.onSubmit.bind(this);
+ this.editField = textArea;
+ }
+
+ if(this.options.loadTextURL) {
+ this.loadExternalText();
+ }
+ this.form.appendChild(this.editField);
+ },
+ getText: function() {
+ return this.element.innerHTML;
+ },
+ loadExternalText: function() {
+ Element.addClassName(this.form, this.options.loadingClassName);
+ this.editField.disabled = true;
+ new Ajax.Request(
+ this.options.loadTextURL,
+ Object.extend({
+ asynchronous: true,
+ onComplete: this.onLoadedExternalText.bind(this)
+ }, this.options.ajaxOptions)
+ );
+ },
+ onLoadedExternalText: function(transport) {
+ Element.removeClassName(this.form, this.options.loadingClassName);
+ this.editField.disabled = false;
+ this.editField.value = transport.responseText.stripTags();
+ },
+ onclickCancel: function() {
+ this.onComplete();
+ this.leaveEditMode();
+ return false;
+ },
+ onFailure: function(transport) {
+ this.options.onFailure(transport);
+ if (this.oldInnerHTML) {
+ this.element.innerHTML = this.oldInnerHTML;
+ this.oldInnerHTML = null;
+ }
+ return false;
+ },
+ onSubmit: function() {
+ // onLoading resets these so we need to save them away for the Ajax call
+ var form = this.form;
+ var value = this.editField.value;
+
+ // do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
+ // which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
+ // to be displayed indefinitely
+ this.onLoading();
+
+ new Ajax.Updater(
+ {
+ success: this.element,
+ // don't update on failure (this could be an option)
+ failure: null
+ },
+ this.url,
+ Object.extend({
+ parameters: this.options.callback(form, value),
+ onComplete: this.onComplete.bind(this),
+ onFailure: this.onFailure.bind(this)
+ }, this.options.ajaxOptions)
+ );
+ // stop the event to avoid a page refresh in Safari
+ if (arguments.length > 1) {
+ Event.stop(arguments[0]);
+ }
+ return false;
+ },
+ onLoading: function() {
+ this.saving = true;
+ this.removeForm();
+ this.leaveHover();
+ this.showSaving();
+ },
+ showSaving: function() {
+ this.oldInnerHTML = this.element.innerHTML;
+ this.element.innerHTML = this.options.savingText;
+ Element.addClassName(this.element, this.options.savingClassName);
+ this.element.style.backgroundColor = this.originalBackground;
+ Element.show(this.element);
+ },
+ removeForm: function() {
+ if(this.form) {
+ if (this.form.parentNode) Element.remove(this.form);
+ this.form = null;
+ }
+ },
+ enterHover: function() {
+ if (this.saving) return;
+ this.element.style.backgroundColor = this.options.highlightcolor;
+ if (this.effect) {
+ this.effect.cancel();
+ }
+ Element.addClassName(this.element, this.options.hoverClassName)
+ },
+ leaveHover: function() {
+ if (this.options.backgroundColor) {
+ this.element.style.backgroundColor = this.oldBackground;
+ }
+ Element.removeClassName(this.element, this.options.hoverClassName)
+ if (this.saving) return;
+ this.effect = new Effect.Highlight(this.element, {
+ startcolor: this.options.highlightcolor,
+ endcolor: this.options.highlightendcolor,
+ restorecolor: this.originalBackground
+ });
+ },
+ leaveEditMode: function() {
+ Element.removeClassName(this.element, this.options.savingClassName);
+ this.removeForm();
+ this.leaveHover();
+ this.element.style.backgroundColor = this.originalBackground;
+ Element.show(this.element);
+ if (this.options.externalControl) {
+ Element.show(this.options.externalControl);
+ }
+ this.editing = false;
+ this.saving = false;
+ this.oldInnerHTML = null;
+ this.onLeaveEditMode();
+ },
+ onComplete: function(transport) {
+ this.leaveEditMode();
+ this.options.onComplete.bind(this)(transport, this.element);
+ },
+ onEnterEditMode: function() {},
+ onLeaveEditMode: function() {},
+ dispose: function() {
+ if (this.oldInnerHTML) {
+ this.element.innerHTML = this.oldInnerHTML;
+ }
+ this.leaveEditMode();
+ Event.stopObserving(this.element, 'click', this.onclickListener);
+ Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
+ Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
+ if (this.options.externalControl) {
+ Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
+ Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
+ Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
+ }
+ }
+};
+
+// Delayed observer, like Form.Element.Observer,
+// but waits for delay after last key input
+// Ideal for live-search fields
+
+Form.Element.DelayedObserver = Class.create();
+Form.Element.DelayedObserver.prototype = {
+ initialize: function(element, delay, callback) {
+ this.delay = delay || 0.5;
+ this.element = $(element);
+ this.callback = callback;
+ this.timer = null;
+ this.lastValue = $F(this.element);
+ Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
+ },
+ delayedListener: function(event) {
+ if(this.lastValue == $F(this.element)) return;
+ if(this.timer) clearTimeout(this.timer);
+ this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
+ this.lastValue = $F(this.element);
+ },
+ onTimerEvent: function() {
+ this.timer = null;
+ this.callback(this.element, $F(this.element));
+ }
+};
Modified: myfaces/tomahawk/trunk/src/main/resources/org/apache/myfaces/custom/prototype/resource/dragdrop.js
URL: http://svn.apache.org/viewcvs/myfaces/tomahawk/trunk/src/main/resources/org/apache/myfaces/custom/prototype/resource/dragdrop.js?rev=365979&r1=365978&r2=365979&view=diff
==============================================================================
--- myfaces/tomahawk/trunk/src/main/resources/org/apache/myfaces/custom/prototype/resource/dragdrop.js (original)
+++ myfaces/tomahawk/trunk/src/main/resources/org/apache/myfaces/custom/prototype/resource/dragdrop.js Wed Jan 4 12:49:21 2006
@@ -1,212 +1,94 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-//
-// 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(d) { return d.element==$(element) });
},
add: function(element) {
- var element = $(element);
+ element = $(element);
var options = Object.extend({
greedy: true,
- hoverclass: null
+ hoverclass: null
}, arguments[1] || {});
// cache containers
if(options.containment) {
- options._containers = new Array();
+ options._containers = [];
var containment = options.containment;
- if((typeof containment == 'object') &&
+ 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;
}
+
+ if(options.accept) options.accept = [options.accept].flatten();
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(point, 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) );
+ (Element.classNames(element).detect(
+ function(v) { return drop.accept.include(v) } ) )) &&
+ Position.within(drop.element, point[0], point[1]) );
},
deactivate: function(drop) {
- Element.Class.remove(drop.element, drop.hoverclass);
+ if(drop.hoverclass)
+ Element.removeClassName(drop.element, drop.hoverclass);
this.last_active = null;
},
activate: function(drop) {
- if(this.last_active) this.deactivate(this.last_active);
- if(drop.hoverclass) {
- Element.Class.add(drop.element, drop.hoverclass);
- this.last_active = drop;
- }
+ if(drop.hoverclass)
+ Element.addClassName(drop.element, drop.hoverclass);
+ this.last_active = drop;
},
- show: function(event, element) {
- if(!this.drops) 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)) {
+ show: function(point, element) {
+ if(!this.drops.length) return;
+
+ if(this.last_active) this.deactivate(this.last_active);
+ this.drops.each( function(drop) {
+ if(Droppables.isAffected(point, element, drop)) {
if(drop.onHover)
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
- if(drop.greedy) {
- this.activate(drop);
- return;
+ if(drop.greedy) {
+ Droppables.activate(drop);
+ throw $break;
}
}
- } while (i--);
+ });
},
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.last_active.onDrop)
- this.last_active.onDrop(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.element, event);
},
reset: function() {
@@ -215,224 +97,326 @@
}
}
-Draggables = {
- observers: new Array(),
+var Draggables = {
+ drags: [],
+ observers: [],
+
+ register: function(draggable) {
+ if(this.drags.length == 0) {
+ this.eventMouseUp = this.endDrag.bindAsEventListener(this);
+ this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
+ this.eventKeypress = this.keyPress.bindAsEventListener(this);
+
+ Event.observe(document, "mouseup", this.eventMouseUp);
+ Event.observe(document, "mousemove", this.eventMouseMove);
+ Event.observe(document, "keypress", this.eventKeypress);
+ }
+ this.drags.push(draggable);
+ },
+
+ unregister: function(draggable) {
+ this.drags = this.drags.reject(function(d) { return d==draggable });
+ if(this.drags.length == 0) {
+ Event.stopObserving(document, "mouseup", this.eventMouseUp);
+ Event.stopObserving(document, "mousemove", this.eventMouseMove);
+ Event.stopObserving(document, "keypress", this.eventKeypress);
+ }
+ },
+
+ activate: function(draggable) {
+ window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
+ this.activeDraggable = draggable;
+ },
+
+ deactivate: function(draggbale) {
+ this.activeDraggable = null;
+ },
+
+ updateDrag: function(event) {
+ if(!this.activeDraggable) return;
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ // Mozilla-based browsers fire successive mousemove events with
+ // the same coordinates, prevent needless redrawing (moz bug?)
+ if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
+ this._lastPointer = pointer;
+ this.activeDraggable.updateDrag(event, pointer);
+ },
+
+ endDrag: function(event) {
+ if(!this.activeDraggable) return;
+ this._lastPointer = null;
+ this.activeDraggable.endDrag(event);
+ this.activeDraggable = null;
+ },
+
+ keyPress: function(event) {
+ if(this.activeDraggable)
+ this.activeDraggable.keyPress(event);
+ },
+
addObserver: function(observer) {
this.observers.push(observer);
+ this._cacheObserverCallbacks();
},
- 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);
- },
- notify: function(eventName, draggable) { // 'onStart', 'onEnd'
- for(var i = 0; i < this.observers.length; i++)
- this.observers[i][eventName](draggable);
+
+ removeObserver: function(element) { // element instead of observer fixes mem leaks
+ this.observers = this.observers.reject( function(o) { return o.element==element });
+ this._cacheObserverCallbacks();
+ },
+
+ notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
+ if(this[eventName+'Count'] > 0)
+ this.observers.each( function(o) {
+ if(o[eventName]) o[eventName](eventName, draggable, event);
+ });
+ },
+
+ _cacheObserverCallbacks: function() {
+ ['onStart','onEnd','onDrag'].each( function(eventName) {
+ Draggables[eventName+'Count'] = Draggables.observers.select(
+ function(o) { return o[eventName]; }
+ ).length;
+ });
}
}
/*--------------------------------------------------------------------------*/
-Draggable = Class.create();
+var Draggable = Class.create();
Draggable.prototype = {
initialize: function(element) {
var options = Object.extend({
handle: false,
- starteffect: function(element) {
- new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
+ starteffect: function(element) {
+ 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;
+ element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur});
},
- endeffect: function(element) {
- new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
+ endeffect: function(element) {
+ new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
},
zindex: 1000,
- revert: false
+ revert: false,
+ snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] }
}, arguments[1] || {});
- this.element = $(element);
- this.handle = options.handle ? $(options.handle) : this.element;
-
- Element.makePositioned(this.element); // fix IE
-
- this.offsetX = 0;
- this.offsetY = 0;
- this.originalLeft = this.currentLeft();
- this.originalTop = this.currentTop();
- 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);
+ this.element = $(element);
+
+ if(options.handle && (typeof options.handle == 'string'))
+ this.handle = Element.childrenWithClassName(this.element, options.handle)[0];
+ if(!this.handle) this.handle = $(options.handle);
+ if(!this.handle) this.handle = this.element;
+
+ Element.makePositioned(this.element); // fix IE
+
+ this.delta = this.currentDelta();
+ this.options = options;
+ this.dragging = false;
+ this.eventMouseDown = this.initDrag.bindAsEventListener(this);
Event.observe(this.handle, "mousedown", this.eventMouseDown);
- Event.observe(document, "mouseup", this.eventMouseUp);
- Event.observe(document, "mousemove", this.eventMouseMove);
- Event.observe(document, "keypress", this.eventKeypress);
+
+ Draggables.register(this);
},
+
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);
- },
- currentLeft: function() {
- return parseInt(this.element.style.left || '0');
+ Draggables.unregister(this);
},
- currentTop: function() {
- return parseInt(this.element.style.top || '0')
+
+ currentDelta: function() {
+ return([
+ parseInt(Element.getStyle(this.element,'left') || '0'),
+ parseInt(Element.getStyle(this.element,'top') || '0')]);
+ },
+
+ initDrag: function(event) {
+ if(Event.isLeftClick(event)) {
+ // 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;
+
+ if(this.element._revert) {
+ this.element._revert.cancel();
+ this.element._revert = null;
+ }
+
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ var pos = Position.cumulativeOffset(this.element);
+ this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
+
+ Draggables.activate(this);
+ Event.stop(event);
+ }
},
+
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;
-
- Event.stop(event);
+ this.dragging = true;
+
+ if(this.options.zindex) {
+ this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
+ this.element.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, event);
+ if(this.options.starteffect) this.options.starteffect(this.element);
+ },
+
+ updateDrag: function(event, pointer) {
+ if(!this.dragging) this.startDrag(event);
+ Position.prepare();
+ Droppables.show(pointer, this.element);
+ Draggables.notify('onDrag', this, event);
+ this.draw(pointer);
+ if(this.options.change) this.options.change(this);
+
+ // fix AppleWebKit rendering
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+ Event.stop(event);
},
+
finishDrag: function(event, success) {
- 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);
+ Draggables.notify('onEnd', this, event);
var revert = this.options.revert;
if(revert && typeof revert == 'function') revert = revert(this.element);
-
+
+ var d = this.currentDelta();
if(revert && this.options.reverteffect) {
- this.options.reverteffect(this.element,
- this.currentTop()-this.originalTop,
- this.currentLeft()-this.originalLeft);
+ this.options.reverteffect(this.element,
+ d[1]-this.delta[1], d[0]-this.delta[0]);
} else {
- this.originalLeft = this.currentLeft();
- this.originalTop = this.currentTop();
+ this.delta = d;
}
- this.element.style.zIndex = this.originalZ;
+ if(this.options.zindex)
+ this.element.style.zIndex = this.originalZ;
- if(this.options.endeffect)
+ if(this.options.endeffect)
this.options.endeffect(this.element);
+ Draggables.deactivate(this);
Droppables.reset();
},
+
keyPress: function(event) {
- if(this.active) {
- if(event.keyCode==Event.KEY_ESC) {
- this.finishDrag(event, false);
- Event.stop(event);
- }
- }
+ if(!event.keyCode==Event.KEY_ESC) return;
+ this.finishDrag(event, false);
+ Event.stop(event);
},
+
endDrag: function(event) {
- if(this.active && this.dragging) {
- this.finishDrag(event, true);
- Event.stop(event);
- }
- this.active = false;
- this.dragging = false;
- },
- draw: function(event) {
+ if(!this.dragging) return;
+ this.finishDrag(event, true);
+ Event.stop(event);
+ },
+
+ draw: function(point) {
+ var pos = Position.cumulativeOffset(this.element);
+ var d = this.currentDelta();
+ 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.snap) {
+ if(typeof this.options.snap == 'function') {
+ p = this.options.snap(p[0],p[1]);
+ } else {
+ if(this.options.snap instanceof Array) {
+ p = p.map( function(v, i) {
+ return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
+ } else {
+ p = p.map( function(v) {
+ return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
+ }
+ }}
+
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 = p[0] + "px";
if((!this.options.constraint) || (this.options.constraint=='vertical'))
- style.top = ((event.clientY - this.originalY) - this.offsetY) + "px";
+ style.top = p[1] + "px";
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
- },
- update: function(event) {
- if(this.active) {
- if(!this.dragging) {
- var style = this.element.style;
- this.dragging = true;
- if(style.position=="") style.position = "relative";
- style.zIndex = this.options.zindex;
- 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);
- }
}
}
/*--------------------------------------------------------------------------*/
-SortableObserver = Class.create();
+var SortableObserver = Class.create();
SortableObserver.prototype = {
initialize: function(element, observer) {
this.element = $(element);
this.observer = observer;
this.lastValue = Sortable.serialize(this.element);
},
+
onStart: function() {
this.lastValue = Sortable.serialize(this.element);
},
+
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);
- var options = Object.extend({
+ 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,
- onChange: function() {},
- onUpdate: function() {}
+ ghosting: false,
+ format: null,
+ onChange: Prototype.emptyFunction,
+ onUpdate: Prototype.emptyFunction
}, arguments[1] || {});
// clear any old sortable with same element
@@ -441,70 +425,60 @@
// 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
+ // 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);
+ 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]);
+ // 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.childrenWithClassName(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);
@@ -513,25 +487,99 @@
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.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);
+ },
+
+ 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) {
+ var oldParentNode = element.parentNode;
+ dropon.appendChild(element);
+ Sortable.options(oldParentNode).onChange(element);
+ Sortable.options(dropon).onChange(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.addClassName(Sortable._marker, 'dropmarker');
+ Sortable._marker.style.position = 'absolute';
+ document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
+ }
+ var offsets = Position.cumulativeOffset(dropon);
+ Sortable._marker.style.left = offsets[0] + 'px';
+ Sortable._marker.style.top = offsets[1] + 'px';
+
+ if(position=='after')
+ if(sortable.overlap == 'horizontal')
+ Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
+ else
+ Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + '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
+ name: element.id,
+ format: sortableOptions.format || /^[^_]*_(.*)$/
}, 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 $(this.findElements(element, options) || []).map( function(item) {
+ return (encodeURIComponent(options.name) + "[]=" +
+ encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : ''));
+ }).join("&");
}
-}
\ No newline at end of file
+}
\ No newline at end of file