aboutsummaryrefslogtreecommitdiffstats
path: root/railties/html/javascripts
diff options
context:
space:
mode:
authorThomas Fuchs <thomas@fesch.at>2005-09-28 08:20:47 +0000
committerThomas Fuchs <thomas@fesch.at>2005-09-28 08:20:47 +0000
commit516dc2c0f16cf187f981b5e8648a7f7f1b31d190 (patch)
treed645e19a02ab5eb371302fe4742b7fd7e0a1a1e2 /railties/html/javascripts
parentdd21e9ae39a2dc4b7eb607ff2c200c864fa19b28 (diff)
downloadrails-516dc2c0f16cf187f981b5e8648a7f7f1b31d190.tar.gz
rails-516dc2c0f16cf187f981b5e8648a7f7f1b31d190.tar.bz2
rails-516dc2c0f16cf187f981b5e8648a7f7f1b31d190.zip
Update script.aculo.us to 1.5_rc2, and Prototype to 1.4.0_pre7
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@2386 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'railties/html/javascripts')
-rw-r--r--railties/html/javascripts/controls.js575
-rw-r--r--railties/html/javascripts/dragdrop.js509
-rw-r--r--railties/html/javascripts/effects.js716
-rw-r--r--railties/html/javascripts/prototype.js399
-rw-r--r--railties/html/javascripts/scriptaculous.js48
-rw-r--r--railties/html/javascripts/slider.js258
-rw-r--r--railties/html/javascripts/util.js521
7 files changed, 2291 insertions, 735 deletions
diff --git a/railties/html/javascripts/controls.js b/railties/html/javascripts/controls.js
index cece0a914b..77046d9c35 100644
--- a/railties/html/javascripts/controls.js
+++ b/railties/html/javascripts/controls.js
@@ -1,41 +1,12 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
+// (c) 2005 Jon Tirsen (http://www.tirsen.com)
+// Contributors:
+// Richard Livsey
+// Rahul Bhargava
+// Rob Wills
//
-// 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);
- }
- }
-
- return text;
-}
+// See scriptaculous.js for full license.
// Autocompleter.Base handles all the autocompletion functionality
// that's independent of the data source for autocompletion. This
@@ -46,7 +17,7 @@ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
// a getUpdatedChoices function that will be invoked every time
// 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,7 +28,7 @@ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
// 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
+// 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.
@@ -65,57 +36,54 @@ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) {
var Autocompleter = {}
Autocompleter.Base = function() {};
Autocompleter.Base.prototype = {
- base_initialize: function(element, update, options) {
+ baseInitialize: function(element, update, options) {
this.element = $(element);
this.update = $(update);
- this.has_focus = false;
+ this.hasFocus = false;
this.changed = false;
this.active = false;
this.index = 0;
- this.entry_count = 0;
+ this.entryCount = 0;
if (this.setOptions)
this.setOptions(options);
else
- this.options = {}
-
- this.options.tokens = this.options.tokens || new Array();
+ this.options = options || {};
+
+ 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.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});
};
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')
this.options.tokens = new Array(this.options.tokens);
-
+
this.observer = null;
+ this.element.setAttribute('autocomplete','off');
+
Element.hide(this.update);
-
+
Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
},
show: function() {
- if(this.update.style.display=='none') this.options.onShow(this.element, this.update);
- if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') {
+ if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
+ if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>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');
}
@@ -126,18 +94,18 @@ Autocompleter.Base.prototype = {
Element.show(this.iefix);
}
},
-
+
hide: function() {
- if(this.update.style.display=='') this.options.onHide(this.element, this.update);
+ 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) {
@@ -145,22 +113,23 @@ Autocompleter.Base.prototype = {
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;
@@ -168,15 +137,15 @@ Autocompleter.Base.prototype = {
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 =
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
},
-
+
onHover: function(event) {
var element = Event.findElement(event, 'LI');
if(this.index != element.autocompleteIndex)
@@ -190,92 +159,97 @@ Autocompleter.Base.prototype = {
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.hasFocus = false;
this.active = false;
},
render: function() {
- if(this.entry_count > 0) {
- for (var i = 0; i < this.entry_count; i++)
+ if(this.entryCount > 0) {
+ for (var i = 0; i < this.entryCount; 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);
+ Element.addClassName(this.getEntry(i),"selected") :
+ Element.removeClassName(this.getEntry(i),"selected");
+ if(this.hasFocus) {
this.show();
this.active = true;
}
} else this.hide();
},
- mark_previous: function() {
+ markPrevious: function() {
if(this.index > 0) this.index--
- else this.index = this.entry_count-1;
+ else this.index = this.entryCcount-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 = 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;
+ this.entryCount = 0;
}
-
+
this.stopIndicator();
-
+
this.index = 0;
this.render();
}
@@ -288,7 +262,7 @@ Autocompleter.Base.prototype = {
onObserverEvent: function() {
this.changed = false;
- if(this.getEntry().length>=this.options.min_chars) {
+ if(this.getToken().length>=this.options.minChars) {
this.startIndicator();
this.getUpdatedChoices();
} else {
@@ -297,58 +271,56 @@ Autocompleter.Base.prototype = {
}
},
- 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;
-
+
return /\n/.test(ret) ? '' : ret;
},
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.baseInitialize(element, update, options);
this.options.asynchronous = true;
- this.options.onComplete = this.onComplete.bind(this)
- this.options.method = 'post';
+ 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;
-
+
if(this.options.defaultParams)
this.options.parameters += '&' + this.options.defaultParams;
-
+
new Ajax.Request(this.url, this.options);
},
-
+
onComplete: function(request) {
this.updateChoices(request.responseText);
}
-}));
+});
// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
@@ -362,22 +334,22 @@ Object.extend(new Ajax.Base(), {
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
-// - partial_search - If false, the autocompleter will match entered
+// - 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'
@@ -388,7 +360,7 @@ Object.extend(new Ajax.Base(), {
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;
},
@@ -399,41 +371,42 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
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++) {
+ ret.length < instance.options.choices ; i++) {
+
var elem = instance.options.array[i];
- var found_pos = instance.options.ignore_case ?
+ var foundPos = instance.options.ignoreCase ?
elem.toLowerCase().indexOf(entry.toLowerCase()) :
elem.indexOf(entry);
- while (found_pos != -1) {
- if (found_pos == 0 && elem.length != entry.length) {
+ 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.partial_chars &&
- instance.options.partial_search && found_pos != -1) {
- if (instance.options.full_search || /\s/.test(elem.substr(found_pos-1,1))) {
- partial.push("<li>" + elem.substr(0, found_pos) + "<strong>" +
- elem.substr(found_pos, entry.length) + "</strong>" + elem.substr(
- found_pos + entry.length) + "</li>");
+ } 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;
}
}
- found_pos = instance.options.ignore_case ?
- elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) :
- elem.indexOf(entry, found_pos + 1);
+ foundPos = instance.options.ignoreCase ?
+ elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
+ elem.indexOf(entry, foundPos + 1);
}
}
@@ -444,3 +417,287 @@ Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
}, options || {});
}
});
+
+// AJAX in-place editor
+//
+// The constructor takes three parameters. The first is the element
+// that should support in-place editing. The second is the url to submit
+// the changed value to. The server should respond with the updated
+// value (the server might have post-processed it or validation might
+// have prevented it from changing). The third is a hash of options.
+//
+// Supported options are (all are optional and have sensible defaults):
+// - okText - The text of the submit button that submits the changed value
+// to the server (default: "ok")
+// - cancelText - The text of the link that cancels editing (default: "cancel")
+// - savingText - The text being displayed as the AJAX engine communicates
+// with the server (default: "Saving...")
+// - formId - The id given to the <form> element
+// (default: the id of the element to edit plus '-inplaceeditor')
+
+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({
+ okText: "ok",
+ 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',
+ formClassName: 'inplaceeditor-form',
+ highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
+ highlightendcolor: "#FFFFFF",
+ externalControl: null,
+ 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() {
+ 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.form = this.getForm();
+ this.element.parentNode.insertBefore(this.form, this.element);
+ Field.focus(this.editField);
+ // stop the event to avoid a page refresh in Safari
+ if (arguments.length > 1) {
+ Event.stop(arguments[0]);
+ }
+ },
+ getForm: function() {
+ form = document.createElement("form");
+ form.id = this.options.formId;
+ Element.addClassName(form, this.options.formClassName)
+ form.onsubmit = this.onSubmit.bind(this);
+
+ this.createEditField(form);
+
+ if (this.options.textarea) {
+ var br = document.createElement("br");
+ form.appendChild(br);
+ }
+
+ okButton = document.createElement("input");
+ okButton.type = "submit";
+ okButton.value = this.options.okText;
+ form.appendChild(okButton);
+
+ cancelLink = document.createElement("a");
+ cancelLink.href = "#";
+ cancelLink.appendChild(document.createTextNode(this.options.cancelText));
+ cancelLink.onclick = this.onclickCancel.bind(this);
+ form.appendChild(cancelLink);
+ return form;
+ },
+ 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(form) {
+ if (this.options.rows == 1 && !this.hasHTMLLineBreaks(this.getText())) {
+ this.options.textarea = false;
+ var textField = document.createElement("input");
+ textField.type = "text";
+ textField.name = "value";
+ textField.value = this.getText();
+ textField.style.backgroundColor = this.options.highlightcolor;
+ var size = this.options.size || this.options.cols || 0;
+ if (size != 0)
+ textField.size = size;
+ form.appendChild(textField);
+ this.editField = textField;
+ } else {
+ this.options.textarea = true;
+ var textArea = document.createElement("textarea");
+ textArea.name = "value";
+ textArea.value = this.convertHTMLLineBreaks(this.getText());
+ textArea.rows = this.options.rows;
+ textArea.cols = this.options.cols || 40;
+ form.appendChild(textArea);
+ this.editField = textArea;
+ }
+ },
+ getText: function() {
+ if (this.options.loadTextURL) {
+ this.loadExternalText();
+ return this.options.loadingText;
+ } else {
+ return this.element.innerHTML;
+ }
+ },
+ loadExternalText: function() {
+ new Ajax.Request(
+ this.options.loadTextURL,
+ {
+ asynchronous: true,
+ onComplete: this.onLoadedExternalText.bind(this)
+ }
+ );
+ },
+ onLoadedExternalText: function(transport) {
+ this.form.value.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() {
+ this.saving = true;
+ 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(this.form, this.editField.value),
+ onComplete: this.onComplete.bind(this),
+ onFailure: this.onFailure.bind(this)
+ }, this.options.ajaxOptions)
+ );
+ this.onLoading();
+ // 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) {
+ 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);
+ }
+ }
+}; \ No newline at end of file
diff --git a/railties/html/javascripts/dragdrop.js b/railties/html/javascripts/dragdrop.js
index c0fd1d1e53..a8ed953a7f 100644
--- a/railties/html/javascripts/dragdrop.js
+++ b/railties/html/javascripts/dragdrop.js
@@ -2,193 +2,79 @@
//
// Element.Class part Copyright (c) 2005 by Rick Olson
//
-// Permission is hereby granted, free of charge, to any person obtaining
-// a copy of this software and associated documentation files (the
-// "Software"), to deal in the Software without restriction, including
-// without limitation the rights to use, copy, modify, merge, publish,
-// distribute, sublicense, and/or sell copies of the Software, and to
-// permit persons to whom the Software is furnished to do so, subject to
-// the following conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-Element.Class = {
- // Element.toggleClass(element, className) toggles the class being on/off
- // Element.toggleClass(element, className1, className2) toggles between both classes,
- // defaulting to className1 if neither exist
- toggle: function(element, className) {
- if(Element.Class.has(element, className)) {
- Element.Class.remove(element, className);
- if(arguments.length == 3) Element.Class.add(element, arguments[2]);
- } else {
- Element.Class.add(element, className);
- if(arguments.length == 3) Element.Class.remove(element, arguments[2]);
- }
- },
-
- // gets space-delimited classnames of an element as an array
- get: function(element) {
- element = $(element);
- return element.className.split(' ');
- },
-
- // functions adapted from original functions by Gavin Kistner
- remove: function(element) {
- element = $(element);
- var regEx;
- for(var i = 1; i < arguments.length; i++) {
- regEx = new RegExp("^" + arguments[i] + "\\b\\s*|\\s*\\b" + arguments[i] + "\\b", 'g');
- element.className = element.className.replace(regEx, '')
- }
- },
-
- add: function(element) {
- element = $(element);
- for(var i = 1; i < arguments.length; i++) {
- Element.Class.remove(element, arguments[i]);
- element.className += (element.className.length > 0 ? ' ' : '') + arguments[i];
- }
- },
-
- // returns true if all given classes exist in said element
- has: function(element) {
- element = $(element);
- if(!element || !element.className) return false;
- var regEx;
- for(var i = 1; i < arguments.length; i++) {
- regEx = new RegExp("\\b" + arguments[i] + "\\b");
- if(!regEx.test(element.className)) return false;
- }
- return true;
- },
-
- // expects arrays of strings and/or strings as optional paramters
- // Element.Class.has_any(element, ['classA','classB','classC'], 'classD')
- has_any: function(element) {
- element = $(element);
- if(!element || !element.className) return false;
- var regEx;
- for(var i = 1; i < arguments.length; i++) {
- if((typeof arguments[i] == 'object') &&
- (arguments[i].constructor == Array)) {
- for(var j = 0; j < arguments[i].length; j++) {
- regEx = new RegExp("\\b" + arguments[i][j] + "\\b");
- if(regEx.test(element.className)) return true;
- }
- } else {
- regEx = new RegExp("\\b" + arguments[i] + "\\b");
- if(regEx.test(element.className)) return true;
- }
- }
- return false;
- },
-
- childrenWith: function(element, className) {
- var children = $(element).getElementsByTagName('*');
- var elements = new Array();
-
- for (var i = 0; i < children.length; i++) {
- if (Element.Class.has(children[i], className)) {
- elements.push(children[i]);
- break;
- }
- }
-
- return elements;
- }
-}
+// See scriptaculous.js for full license.
/*--------------------------------------------------------------------------*/
var Droppables = {
- drops: false,
-
+ drops: [],
+
remove: function(element) {
- for(var i = 0; i < this.drops.length; i++)
- if(this.drops[i].element == element)
- this.drops.splice(i,1);
+ this.drops = this.drops.reject(function(e) { return e==element });
},
-
+
add: function(element) {
- var element = $(element);
+ element = $(element);
var options = Object.extend({
greedy: true,
hoverclass: null
}, arguments[1] || {});
-
+
// cache containers
if(options.containment) {
- options._containers = new Array();
+ options._containers = [];
var containment = options.containment;
if((typeof containment == 'object') &&
(containment.constructor == Array)) {
- for(var i=0; i<containment.length; i++)
- options._containers.push($(containment[i]));
+ containment.each( function(c) { options._containers.push($(c)) });
} else {
options._containers.push($(containment));
}
- options._containers_length =
- options._containers.length-1;
}
-
+
Element.makePositioned(element); // fix IE
-
options.element = element;
-
- // activate the droppable
- if(!this.drops) this.drops = [];
+
this.drops.push(options);
},
-
- is_contained: function(element, drop) {
- var containers = drop._containers;
+
+ isContained: function(element, drop) {
var parentNode = element.parentNode;
- var i = drop._containers_length;
- do { if(parentNode==containers[i]) return true; } while (i--);
- return false;
+ return drop._containers.detect(function(c) { return parentNode == c });
},
-
- is_affected: function(pX, pY, element, drop) {
+
+ isAffected: function(pX, pY, element, drop) {
return (
(drop.element!=element) &&
((!drop._containers) ||
- this.is_contained(element, drop)) &&
+ this.isContained(element, drop)) &&
((!drop.accept) ||
(Element.Class.has_any(element, drop.accept))) &&
Position.within(drop.element, pX, pY) );
},
-
+
deactivate: function(drop) {
- Element.Class.remove(drop.element, drop.hoverclass);
+ if(drop.hoverclass)
+ Element.Class.remove(drop.element, drop.hoverclass);
this.last_active = null;
},
-
+
activate: function(drop) {
if(this.last_active) this.deactivate(this.last_active);
- if(drop.hoverclass) {
+ if(drop.hoverclass)
Element.Class.add(drop.element, drop.hoverclass);
- this.last_active = drop;
- }
+ this.last_active = drop;
},
-
+
show: function(event, element) {
- if(!this.drops) return;
+ if(!this.drops.length) return;
var pX = Event.pointerX(event);
var pY = Event.pointerY(event);
Position.prepare();
-
+
var i = this.drops.length-1; do {
var drop = this.drops[i];
- if(this.is_affected(pX, pY, element, drop)) {
+ if(this.isAffected(pX, pY, element, drop)) {
if(drop.onHover)
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
if(drop.greedy) {
@@ -197,43 +83,41 @@ var Droppables = {
}
}
} while (i--);
+
+ if(this.last_active) this.deactivate(this.last_active);
},
-
+
fire: function(event, element) {
if(!this.last_active) return;
Position.prepare();
-
- if (this.is_affected(Event.pointerX(event), Event.pointerY(event), element, this.last_active))
+
+ if (this.isAffected(Event.pointerX(event), Event.pointerY(event), element, this.last_active))
if (this.last_active.onDrop)
- this.last_active.onDrop(element, this.last_active);
-
+ this.last_active.onDrop(element, this.last_active.element);
},
-
+
reset: function() {
if(this.last_active)
this.deactivate(this.last_active);
}
}
-Draggables = {
- observers: new Array(),
+var Draggables = {
+ observers: [],
addObserver: function(observer) {
this.observers.push(observer);
},
removeObserver: function(element) { // element instead of obsever fixes mem leaks
- for(var i = 0; i < this.observers.length; i++)
- if(this.observers[i].element && (this.observers[i].element == element))
- this.observers.splice(i,1);
+ this.observers = this.observers.reject( function(o) { return o.element==element });
},
notify: function(eventName, draggable) { // 'onStart', 'onEnd'
- for(var i = 0; i < this.observers.length; i++)
- this.observers[i][eventName](draggable);
+ this.observers.invoke(eventName, draggable);
}
}
/*--------------------------------------------------------------------------*/
-Draggable = Class.create();
+var Draggable = Class.create();
Draggable.prototype = {
initialize: function(element) {
var options = Object.extend({
@@ -242,7 +126,8 @@ Draggable.prototype = {
new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
},
reverteffect: function(element, top_offset, left_offset) {
- new Effect.MoveBy(element, -top_offset, -left_offset, {duration:0.4});
+ var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
+ new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur});
},
endeffect: function(element) {
new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
@@ -250,12 +135,12 @@ Draggable.prototype = {
zindex: 1000,
revert: false
}, arguments[1] || {});
-
+
this.element = $(element);
this.handle = options.handle ? $(options.handle) : this.element;
-
- Element.makePositioned(this.element); // fix IE
-
+
+ Element.makePositioned(this.element); // fix IE
+
this.offsetX = 0;
this.offsetY = 0;
this.originalLeft = this.currentLeft();
@@ -263,27 +148,34 @@ Draggable.prototype = {
this.originalX = this.element.offsetLeft;
this.originalY = this.element.offsetTop;
this.originalZ = parseInt(this.element.style.zIndex || "0");
-
+
this.options = options;
-
+
this.active = false;
this.dragging = false;
-
+
this.eventMouseDown = this.startDrag.bindAsEventListener(this);
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
this.eventMouseMove = this.update.bindAsEventListener(this);
this.eventKeypress = this.keyPress.bindAsEventListener(this);
- Event.observe(this.handle, "mousedown", this.eventMouseDown);
+ this.registerEvents();
+ },
+ destroy: function() {
+ Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
+ this.unregisterEvents();
+ },
+ registerEvents: function() {
Event.observe(document, "mouseup", this.eventMouseUp);
Event.observe(document, "mousemove", this.eventMouseMove);
Event.observe(document, "keypress", this.eventKeypress);
+ Event.observe(this.handle, "mousedown", this.eventMouseDown);
},
- destroy: function() {
- Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
- Event.stopObserving(document, "mouseup", this.eventMouseUp);
- Event.stopObserving(document, "mousemove", this.eventMouseMove);
- Event.stopObserving(document, "keypress", this.eventKeypress);
+ unregisterEvents: function() {
+ //if(!this.active) return;
+ //Event.stopObserving(document, "mouseup", this.eventMouseUp);
+ //Event.stopObserving(document, "mousemove", this.eventMouseMove);
+ //Event.stopObserving(document, "keypress", this.eventKeypress);
},
currentLeft: function() {
return parseInt(this.element.style.left || '0');
@@ -293,27 +185,42 @@ Draggable.prototype = {
},
startDrag: function(event) {
if(Event.isLeftClick(event)) {
- this.active = true;
- var style = this.element.style;
- this.originalY = this.element.offsetTop - this.currentTop() - this.originalTop;
- this.originalX = this.element.offsetLeft - this.currentLeft() - this.originalLeft;
- this.offsetY = event.clientY - this.originalY - this.originalTop;
- this.offsetX = event.clientX - this.originalX - this.originalLeft;
+ // abort on form elements, fixes a Firefox issue
+ var src = Event.element(event);
+ if(src.tagName && (
+ src.tagName=='INPUT' ||
+ src.tagName=='SELECT' ||
+ src.tagName=='BUTTON' ||
+ src.tagName=='TEXTAREA')) return;
+ // this.registerEvents();
+ this.active = true;
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ var offsets = Position.cumulativeOffset(this.element);
+ this.offsetX = (pointer[0] - offsets[0]);
+ this.offsetY = (pointer[1] - offsets[1]);
Event.stop(event);
}
},
finishDrag: function(event, success) {
+ // this.unregisterEvents();
+
this.active = false;
this.dragging = false;
-
+
+ if(this.options.ghosting) {
+ Position.relativize(this.element);
+ Element.remove(this._clone);
+ this._clone = null;
+ }
+
if(success) Droppables.fire(event, this.element);
Draggables.notify('onEnd', this);
-
+
var revert = this.options.revert;
if(revert && typeof revert == 'function') revert = revert(this.element);
-
+
if(revert && this.options.reverteffect) {
this.options.reverteffect(this.element,
this.currentTop()-this.originalTop,
@@ -322,12 +229,13 @@ Draggable.prototype = {
this.originalLeft = this.currentLeft();
this.originalTop = this.currentTop();
}
-
+
this.element.style.zIndex = this.originalZ;
-
+
if(this.options.endeffect)
this.options.endeffect(this.element);
-
+
+
Droppables.reset();
},
keyPress: function(event) {
@@ -347,13 +255,15 @@ Draggable.prototype = {
this.dragging = false;
},
draw: function(event) {
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ var offsets = Position.cumulativeOffset(this.element);
+ offsets[0] -= this.currentLeft();
+ offsets[1] -= this.currentTop();
var style = this.element.style;
- this.originalX = this.element.offsetLeft - this.currentLeft() - this.originalLeft;
- this.originalY = this.element.offsetTop - this.currentTop() - this.originalTop;
if((!this.options.constraint) || (this.options.constraint=='horizontal'))
- style.left = ((event.clientX - this.originalX) - this.offsetX) + "px";
+ style.left = (pointer[0] - offsets[0] - this.offsetX) + "px";
if((!this.options.constraint) || (this.options.constraint=='vertical'))
- style.top = ((event.clientY - this.originalY) - this.offsetY) + "px";
+ style.top = (pointer[1] - offsets[1] - this.offsetY) + "px";
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
},
update: function(event) {
@@ -363,17 +273,24 @@ Draggable.prototype = {
this.dragging = true;
if(style.position=="") style.position = "relative";
style.zIndex = this.options.zindex;
+
+ if(this.options.ghosting) {
+ this._clone = this.element.cloneNode(true);
+ Position.absolutize(this.element);
+ this.element.parentNode.insertBefore(this._clone, this.element);
+ }
+
Draggables.notify('onStart', this);
if(this.options.starteffect) this.options.starteffect(this.element);
}
-
+
Droppables.show(event, this.element);
this.draw(event);
if(this.options.change) this.options.change(this);
-
+
// fix AppleWebKit rendering
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
-
+
Event.stop(event);
}
}
@@ -381,7 +298,7 @@ Draggable.prototype = {
/*--------------------------------------------------------------------------*/
-SortableObserver = Class.create();
+var SortableObserver = Class.create();
SortableObserver.prototype = {
initialize: function(element, observer) {
this.element = $(element);
@@ -391,147 +308,197 @@ SortableObserver.prototype = {
onStart: function() {
this.lastValue = Sortable.serialize(this.element);
},
- onEnd: function() {
+ onEnd: function() {
+ Sortable.unmark();
if(this.lastValue != Sortable.serialize(this.element))
this.observer(this.element)
}
}
-Sortable = {
+var Sortable = {
sortables: new Array(),
options: function(element){
- var element = $(element);
- for(var i=0;i<this.sortables.length;i++)
- if(this.sortables[i].element == element)
- return this.sortables[i];
- return null;
+ element = $(element);
+ return this.sortables.detect(function(s) { return s.element == element });
},
destroy: function(element){
- var element = $(element);
- for(var i=0;i<this.sortables.length;i++) {
- if(this.sortables[i].element == element) {
- var s = this.sortables[i];
- Draggables.removeObserver(s.element);
- for(var j=0;j<s.droppables.length;j++)
- Droppables.remove(s.droppables[j]);
- for(var j=0;j<s.draggables.length;j++)
- s.draggables[j].destroy();
- this.sortables.splice(i,1);
- }
- }
+ element = $(element);
+ this.sortables.findAll(function(s) { return s.element == element }).each(function(s){
+ Draggables.removeObserver(s.element);
+ s.droppables.each(function(d){ Droppables.remove(d) });
+ s.draggables.invoke('destroy');
+ });
+ this.sortables = this.sortables.reject(function(s) { return s.element == element });
},
create: function(element) {
- var element = $(element);
+ element = $(element);
var options = Object.extend({
element: element,
tag: 'li', // assumes li children, override with tag: 'tagname'
+ dropOnEmpty: false,
+ tree: false, // fixme: unimplemented
overlap: 'vertical', // one of 'vertical', 'horizontal'
constraint: 'vertical', // one of 'vertical', 'horizontal', false
containment: element, // also takes array of elements (or id's); or false
handle: false, // or a CSS class
only: false,
hoverclass: null,
+ ghosting: false,
onChange: function() {},
onUpdate: function() {}
}, arguments[1] || {});
-
+
// clear any old sortable with same element
this.destroy(element);
-
+
// build options for the draggables
var options_for_draggable = {
revert: true,
+ ghosting: options.ghosting,
constraint: options.constraint,
- handle: handle };
+ handle: options.handle };
+
if(options.starteffect)
options_for_draggable.starteffect = options.starteffect;
+
if(options.reverteffect)
options_for_draggable.reverteffect = options.reverteffect;
+ else
+ if(options.ghosting) options_for_draggable.reverteffect = function(element) {
+ element.style.top = 0;
+ element.style.left = 0;
+ };
+
if(options.endeffect)
options_for_draggable.endeffect = options.endeffect;
+
if(options.zindex)
options_for_draggable.zindex = options.zindex;
-
+
// build options for the droppables
var options_for_droppable = {
overlap: options.overlap,
containment: options.containment,
hoverclass: options.hoverclass,
- onHover: function(element, dropon, overlap) {
- if(overlap>0.5) {
- if(dropon.previousSibling != element) {
- var oldParentNode = element.parentNode;
- element.style.visibility = "hidden"; // fix gecko rendering
- dropon.parentNode.insertBefore(element, dropon);
- if(dropon.parentNode!=oldParentNode && oldParentNode.sortable)
- oldParentNode.sortable.onChange(element);
- if(dropon.parentNode.sortable)
- dropon.parentNode.sortable.onChange(element);
- }
- } else {
- var nextElement = dropon.nextSibling || null;
- if(nextElement != element) {
- var oldParentNode = element.parentNode;
- element.style.visibility = "hidden"; // fix gecko rendering
- dropon.parentNode.insertBefore(element, nextElement);
- if(dropon.parentNode!=oldParentNode && oldParentNode.sortable)
- oldParentNode.sortable.onChange(element);
- if(dropon.parentNode.sortable)
- dropon.parentNode.sortable.onChange(element);
- }
- }
- }
+ onHover: Sortable.onHover,
+ greedy: !options.dropOnEmpty
}
// fix for gecko engine
Element.cleanWhitespace(element);
-
+
options.draggables = [];
options.droppables = [];
-
- // make it so
- var elements = element.childNodes;
- for (var i = 0; i < elements.length; i++)
- if(elements[i].tagName && elements[i].tagName==options.tag.toUpperCase() &&
- (!options.only || (Element.Class.has(elements[i], options.only)))) {
-
- // handles are per-draggable
- var handle = options.handle ?
- Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i];
-
- options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle })));
-
- Droppables.add(elements[i], options_for_droppable);
- options.droppables.push(elements[i]);
-
- }
-
+
+ // make it so
+
+ // drop on empty handling
+ if(options.dropOnEmpty) {
+ Droppables.add(element,
+ {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false});
+ options.droppables.push(element);
+ }
+
+ (this.findElements(element, options) || []).each( function(e) {
+ // handles are per-draggable
+ var handle = options.handle ?
+ Element.Class.childrenWith(e, options.handle)[0] : e;
+ options.draggables.push(
+ new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
+ Droppables.add(e, options_for_droppable);
+ options.droppables.push(e);
+ });
+
// keep reference
this.sortables.push(options);
-
+
// for onupdate
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
},
+
+ // return all suitable-for-sortable elements in a guaranteed order
+ findElements: function(element, options) {
+ if(!element.hasChildNodes()) return null;
+ var elements = [];
+ $A(element.childNodes).each( function(e) {
+ if(e.tagName && e.tagName==options.tag.toUpperCase() &&
+ (!options.only || (Element.Class.has(e, options.only))))
+ elements.push(e);
+ if(options.tree) {
+ var grandchildren = this.findElements(e, options);
+ if(grandchildren) elements.push(grandchildren);
+ }
+ });
+
+ return (elements.length>0 ? elements.flatten() : null);
+ },
+
+ onHover: function(element, dropon, overlap) {
+ if(overlap>0.5) {
+ Sortable.mark(dropon, 'before');
+ if(dropon.previousSibling != element) {
+ var oldParentNode = element.parentNode;
+ element.style.visibility = "hidden"; // fix gecko rendering
+ dropon.parentNode.insertBefore(element, dropon);
+ if(dropon.parentNode!=oldParentNode)
+ Sortable.options(oldParentNode).onChange(element);
+ Sortable.options(dropon.parentNode).onChange(element);
+ }
+ } else {
+ Sortable.mark(dropon, 'after');
+ var nextElement = dropon.nextSibling || null;
+ if(nextElement != element) {
+ var oldParentNode = element.parentNode;
+ element.style.visibility = "hidden"; // fix gecko rendering
+ dropon.parentNode.insertBefore(element, nextElement);
+ if(dropon.parentNode!=oldParentNode)
+ Sortable.options(oldParentNode).onChange(element);
+ Sortable.options(dropon.parentNode).onChange(element);
+ }
+ }
+ },
+
+ onEmptyHover: function(element, dropon) {
+ if(element.parentNode!=dropon) {
+ dropon.appendChild(element);
+ }
+ },
+
+ unmark: function() {
+ if(Sortable._marker) Element.hide(Sortable._marker);
+ },
+
+ mark: function(dropon, position) {
+ // mark on ghosting only
+ var sortable = Sortable.options(dropon.parentNode);
+ if(sortable && !sortable.ghosting) return;
+
+ if(!Sortable._marker) {
+ Sortable._marker = $('dropmarker') || document.createElement('DIV');
+ Element.hide(Sortable._marker);
+ Element.Class.add(Sortable._marker, 'dropmarker');
+ Sortable._marker.style.position = 'absolute';
+ document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
+ }
+ var offsets = Position.cumulativeOffset(dropon);
+ Sortable._marker.style.top = offsets[1] + 'px';
+ if(position=='after') Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
+ Sortable._marker.style.left = offsets[0] + 'px';
+ Element.show(Sortable._marker);
+ },
+
serialize: function(element) {
- var element = $(element);
+ element = $(element);
var sortableOptions = this.options(element);
var options = Object.extend({
tag: sortableOptions.tag,
only: sortableOptions.only,
name: element.id
}, arguments[1] || {});
-
- var items = $(element).childNodes;
- var queryComponents = new Array();
-
- for(var i=0; i<items.length; i++)
- if(items[i].tagName && items[i].tagName==options.tag.toUpperCase() &&
- (!options.only || (Element.Class.has(items[i], options.only))))
- queryComponents.push(
- encodeURIComponent(options.name) + "[]=" +
- encodeURIComponent(items[i].id.split("_")[1]));
-
- return queryComponents.join("&");
+ return $A(element.childNodes).collect( function(item) {
+ return (encodeURIComponent(options.name) + "[]=" +
+ encodeURIComponent(item.id.split("_")[1]));
+ }).join("&");
}
-} \ No newline at end of file
+} \ No newline at end of file
diff --git a/railties/html/javascripts/effects.js b/railties/html/javascripts/effects.js
index a8735f507c..d6fbed805e 100644
--- a/railties/html/javascripts/effects.js
+++ b/railties/html/javascripts/effects.js
@@ -1,30 +1,51 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
-//
-// Parts (c) 2005 Justin Palmer (http://encytemedia.com/)
-// Parts (c) 2005 Mark Pilgrim (http://diveintomark.org/)
+// Contributors:
+// Justin Palmer (http://encytemedia.com/)
+// Mark Pilgrim (http://diveintomark.org/)
+// Martin Bialasinki
//
-// 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.
+// See scriptaculous.js for full license.
+
+var Effect = {
+ tagifyText: function(element) {
+ var tagifyStyle = "position:relative";
+ if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ";zoom:1";
+ element = $(element);
+ $A(element.childNodes).each( function(child) {
+ if(child.nodeType==3) {
+ child.nodeValue.toArray().each( function(character) {
+ element.insertBefore(
+ Builder.node('span',{style: tagifyStyle},
+ character == " " ? String.fromCharCode(160) : character),
+ child);
+ });
+ Element.remove(child);
+ }
+ });
+ },
+ multiple: function(element, effect) {
+ var elements;
+ if(((typeof element == 'object') ||
+ (typeof element == 'function')) &&
+ (element.length))
+ elements = element;
+ else
+ elements = $(element).childNodes;
+
+ var options = Object.extend({
+ speed: 0.1,
+ delay: 0.0
+ }, arguments[2] || {});
+ var speed = options.speed;
+ var delay = options.delay;
+ $A(elements).each( function(element, index) {
+ new effect(element, Object.extend(options, { delay: delay + index * speed }));
+ });
+ }
+};
-Effect = {}
-Effect2 = Effect; // deprecated
+var Effect2 = Effect; // deprecated
/* ------------- transitions ------------- */
@@ -40,7 +61,7 @@ Effect.Transitions.reverse = function(pos) {
return 1-pos;
}
Effect.Transitions.flicker = function(pos) {
- return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random(0.25);
+ return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
}
Effect.Transitions.wobble = function(pos) {
return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
@@ -56,73 +77,109 @@ Effect.Transitions.full = function(pos) {
return 1;
}
-/* ------------- element ext -------------- */
-
-Element.makePositioned = function(element) {
- element = $(element);
- if(element.style.position == "")
- element.style.position = "relative";
-}
-
-Element.makeClipping = function(element) {
- element = $(element);
- element._overflow = element.style.overflow || 'visible';
- if(element._overflow!='hidden') element.style.overflow = 'hidden';
-}
+/* ------------- core effects ------------- */
-Element.undoClipping = function(element) {
- element = $(element);
- if(element._overflow!='hidden') element.style.overflow = element._overflow;
+Effect.Queue = {
+ effects: [],
+ interval: null,
+ add: function(effect) {
+ var timestamp = new Date().getTime();
+
+ switch(effect.options.queue) {
+ case 'front':
+ // move unstarted effects after this effect
+ this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
+ e.startOn += effect.finishOn;
+ e.finishOn += effect.finishOn;
+ });
+ break;
+ case 'end':
+ // start effect after last queued effect has finished
+ timestamp = this.effects.pluck('finishOn').max() || timestamp;
+ break;
+ }
+
+ effect.startOn += timestamp;
+ effect.finishOn += timestamp;
+ this.effects.push(effect);
+ if(!this.interval)
+ this.interval = setInterval(this.loop.bind(this), 40);
+ },
+ remove: function(effect) {
+ this.effects = this.effects.reject(function(e) { return e==effect });
+ if(this.effects.length == 0) {
+ clearInterval(this.interval);
+ this.interval = null;
+ }
+ },
+ loop: function() {
+ var timePos = new Date().getTime();
+ this.effects.invoke('loop', timePos);
+ }
}
-/* ------------- core effects ------------- */
-
Effect.Base = function() {};
Effect.Base.prototype = {
setOptions: function(options) {
this.options = Object.extend({
transition: Effect.Transitions.sinoidal,
duration: 1.0, // seconds
- fps: 25.0, // max. 100fps
+ fps: 25.0, // max. 25fps due to Effect.Queue implementation
sync: false, // true for combining
from: 0.0,
- to: 1.0
+ to: 1.0,
+ delay: 0.0,
+ queue: 'parallel'
}, options || {});
},
start: function(options) {
this.setOptions(options || {});
this.currentFrame = 0;
- this.startOn = new Date().getTime();
+ this.state = 'idle';
+ this.startOn = this.options.delay*1000;
this.finishOn = this.startOn + (this.options.duration*1000);
- if(this.options.beforeStart) this.options.beforeStart(this);
- if(!this.options.sync) this.loop();
+ this.event('beforeStart');
+ if(!this.options.sync) Effect.Queue.add(this);
},
- loop: function() {
- var timePos = new Date().getTime();
- if(timePos >= this.finishOn) {
- this.render(this.options.to);
- if(this.finish) this.finish();
- if(this.options.afterFinish) this.options.afterFinish(this);
- return;
- }
- var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
- var frame = Math.round(pos * this.options.fps * this.options.duration);
- if(frame > this.currentFrame) {
- this.render(pos);
- this.currentFrame = frame;
+ loop: function(timePos) {
+ if(timePos >= this.startOn) {
+ if(timePos >= this.finishOn) {
+ this.render(1.0);
+ this.cancel();
+ this.event('beforeFinish');
+ if(this.finish) this.finish();
+ this.event('afterFinish');
+ return;
+ }
+ var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
+ var frame = Math.round(pos * this.options.fps * this.options.duration);
+ if(frame > this.currentFrame) {
+ this.render(pos);
+ this.currentFrame = frame;
+ }
}
- this.timeout = setTimeout(this.loop.bind(this), 10);
},
render: function(pos) {
+ if(this.state == 'idle') {
+ this.state = 'running';
+ this.event('beforeSetup');
+ if(this.setup) this.setup();
+ this.event('afterSetup');
+ }
if(this.options.transition) pos = this.options.transition(pos);
pos *= (this.options.to-this.options.from);
- pos += this.options.from;
- if(this.options.beforeUpdate) this.options.beforeUpdate(this);
+ pos += this.options.from;
+ this.event('beforeUpdate');
if(this.update) this.update(pos);
- if(this.options.afterUpdate) this.options.afterUpdate(this);
+ this.event('afterUpdate');
},
cancel: function() {
- if(this.timeout) clearTimeout(this.timeout);
+ if(!this.options.sync) Effect.Queue.remove(this);
+ this.state = 'finished';
+ },
+ event: function(eventName) {
+ if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
+ if(this.options[eventName]) this.options[eventName](this);
}
}
@@ -133,35 +190,36 @@ Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
this.start(arguments[1]);
},
update: function(position) {
- for (var i = 0; i < this.effects.length; i++)
- this.effects[i].render(position);
+ this.effects.invoke('render', position);
},
finish: function(position) {
- for (var i = 0; i < this.effects.length; i++)
- if(this.effects[i].finish) this.effects[i].finish(position);
+ this.effects.each( function(effect) {
+ effect.render(1.0);
+ effect.cancel();
+ effect.event('beforeFinish');
+ if(effect.finish) effect.finish(position);
+ effect.event('afterFinish');
+ });
}
});
-// Internet Explorer caveat: works only on elements the have
-// a 'layout', meaning having a given width or height.
-// There is no way to safely set this automatically.
Effect.Opacity = Class.create();
Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
initialize: function(element) {
this.element = $(element);
- options = Object.extend({
- from: 0.0,
+
+ // make this work on IE on elements without 'layout'
+ if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
+ this.element.style.zoom = 1;
+
+ var options = Object.extend({
+ from: Element.getOpacity(this.element) || 0.0,
to: 1.0
}, arguments[1] || {});
this.start(options);
},
update: function(position) {
- this.setOpacity(position);
- },
- setOpacity: function(opacity) {
- opacity = (opacity == 1) ? 0.99999 : opacity;
- this.element.style.opacity = opacity;
- this.element.style.filter = "alpha(opacity:"+opacity*100+")";
+ Element.setOpacity(this.element, position);
}
});
@@ -169,16 +227,23 @@ Effect.MoveBy = Class.create();
Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), {
initialize: function(element, toTop, toLeft) {
this.element = $(element);
- this.originalTop = parseFloat(this.element.style.top || '0');
- this.originalLeft = parseFloat(this.element.style.left || '0');
this.toTop = toTop;
this.toLeft = toLeft;
- Element.makePositioned(this.element);
this.start(arguments[3]);
},
+ setup: function() {
+ // Bug in Opera: Opera returns the "real" position of a static element or
+ // relative element that does not have top/left explicitly set.
+ // ==> Always set top and left for position relative elements in your stylesheets
+ // (to 0 if you do not need them)
+
+ Element.makePositioned(this.element);
+ this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0');
+ this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0');
+ },
update: function(position) {
- topd = this.toTop * position + this.originalTop;
- leftd = this.toLeft * position + this.originalLeft;
+ var topd = this.toTop * position + this.originalTop;
+ var leftd = this.toLeft * position + this.originalLeft;
this.setPosition(topd, leftd);
},
setPosition: function(topd, leftd) {
@@ -191,50 +256,76 @@ Effect.Scale = Class.create();
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
initialize: function(element, percent) {
this.element = $(element)
- options = Object.extend({
+ var options = Object.extend({
scaleX: true,
scaleY: true,
scaleContent: true,
scaleFromCenter: false,
scaleMode: 'box', // 'box' or 'contents' or {} with provided values
- scaleFrom: 100.0
+ scaleFrom: 100.0,
+ scaleTo: percent
}, arguments[2] || {});
+ this.start(options);
+ },
+ setup: function() {
+ this.restoreAfterFinish = this.options.restoreAfterFinish || false;
+ this.elementPositioning = Element.getStyle(this.element,'position');
+ this.elementStyleTop = this.element.style.top;
+ this.elementStyleLeft = this.element.style.left;
+ this.elementStyleWidth = this.element.style.width;
+ this.elementStyleHeight = this.element.style.height;
+ this.elementStyleFontSize = this.element.style.fontSize;
this.originalTop = this.element.offsetTop;
this.originalLeft = this.element.offsetLeft;
- if(this.element.style.fontSize=="") this.sizeEm = 1.0;
- if(this.element.style.fontSize && this.element.style.fontSize.indexOf("em")>0)
- this.sizeEm = parseFloat(this.element.style.fontSize);
- this.factor = (percent/100.0) - (options.scaleFrom/100.0);
- if(options.scaleMode=='box') {
+ var fontSize = Element.getStyle(this.element,'font-size') || "100%";
+ if(fontSize.indexOf("em")>0) {
+ this.fontSize = parseFloat(fontSize);
+ this.fontSizeType = "em";
+ } else if(fontSize.indexOf("px")>0) {
+ this.fontSize = parseFloat(fontSize);
+ this.fontSizeType = "px";
+ } else if(fontSize.indexOf("%")>0) {
+ this.fontSize = parseFloat(fontSize);
+ this.fontSizeType = "%";
+ }
+ this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
+ if(this.options.scaleMode=='box') {
this.originalHeight = this.element.clientHeight;
this.originalWidth = this.element.clientWidth;
- } else
- if(options.scaleMode=='contents') {
+ } else if(this.options.scaleMode=='contents') {
this.originalHeight = this.element.scrollHeight;
this.originalWidth = this.element.scrollWidth;
} else {
- this.originalHeight = options.scaleMode.originalHeight;
- this.originalWidth = options.scaleMode.originalWidth;
+ this.originalHeight = this.options.scaleMode.originalHeight;
+ this.originalWidth = this.options.scaleMode.originalWidth;
}
- this.start(options);
},
-
update: function(position) {
- currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
- if(this.options.scaleContent && this.sizeEm)
- this.element.style.fontSize = this.sizeEm*currentScale + "em";
+ var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
+ if(this.options.scaleContent && this.fontSize)
+ this.element.style.fontSize = this.fontSize*currentScale + this.fontSizeType;
this.setDimensions(
this.originalWidth * currentScale,
this.originalHeight * currentScale);
},
-
+ finish: function(position) {
+ if (this.restoreAfterFinish) {
+ var els = this.element.style;
+ els.top = this.elementStyleTop;
+ els.left = this.elementStyleLeft;
+ els.width = this.elementStyleWidth;
+ els.height = this.elementStyleHeight;
+ els.height = this.elementStyleHeight;
+ els.fontSize = this.elementStyleFontSize;
+ }
+ },
setDimensions: function(width, height) {
if(this.options.scaleX) this.element.style.width = width + 'px';
if(this.options.scaleY) this.element.style.height = height + 'px';
if(this.options.scaleFromCenter) {
- topd = (height - this.originalHeight)/2;
- leftd = (width - this.originalWidth)/2;
- if(this.element.style.position=='absolute') {
+ var topd = (height - this.originalHeight)/2;
+ var leftd = (width - this.originalWidth)/2;
+ if(this.elementPositioning == 'absolute') {
if(this.options.scaleY) this.element.style.top = this.originalTop-topd + "px";
if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + "px";
} else {
@@ -249,33 +340,28 @@ Effect.Highlight = Class.create();
Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
initialize: function(element) {
this.element = $(element);
-
- // try to parse current background color as default for endcolor
- // browser stores this as: "rgb(255, 255, 255)", convert to "#ffffff" format
- var endcolor = "#ffffff";
- var current = this.element.style.backgroundColor;
- if(current && current.slice(0,4) == "rgb(") {
- endcolor = "#";
- var cols = current.slice(4,current.length-1).split(',');
- var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3); }
-
var options = Object.extend({
- startcolor: "#ffff99",
- endcolor: endcolor,
- restorecolor: current
+ startcolor: "#ffff99"
}, arguments[1] || {});
-
+ this.start(options);
+ },
+ setup: function() {
+ // Disable background image during the effect
+ this.oldBgImage = this.element.style.backgroundImage;
+ this.element.style.backgroundImage = "none";
+ if(!this.options.endcolor)
+ this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff');
+ if (typeof this.options.restorecolor == "undefined")
+ this.options.restorecolor = this.element.style.backgroundColor;
// init color calculations
this.colors_base = [
- parseInt(options.startcolor.slice(1,3),16),
- parseInt(options.startcolor.slice(3,5),16),
- parseInt(options.startcolor.slice(5),16) ];
+ parseInt(this.options.startcolor.slice(1,3),16),
+ parseInt(this.options.startcolor.slice(3,5),16),
+ parseInt(this.options.startcolor.slice(5),16) ];
this.colors_delta = [
- parseInt(options.endcolor.slice(1,3),16)-this.colors_base[0],
- parseInt(options.endcolor.slice(3,5),16)-this.colors_base[1],
- parseInt(options.endcolor.slice(5),16)-this.colors_base[2] ];
-
- this.start(options);
+ parseInt(this.options.endcolor.slice(1,3),16)-this.colors_base[0],
+ parseInt(this.options.endcolor.slice(3,5),16)-this.colors_base[1],
+ parseInt(this.options.endcolor.slice(5),16)-this.colors_base[2]];
},
update: function(position) {
var colors = [
@@ -287,6 +373,7 @@ Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype),
},
finish: function() {
this.element.style.backgroundColor = this.options.restorecolor;
+ this.element.style.backgroundImage = this.oldBgImage;
}
});
@@ -294,6 +381,9 @@ Effect.ScrollTo = Class.create();
Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
initialize: function(element) {
this.element = $(element);
+ this.start(arguments[1] || {});
+ },
+ setup: function() {
Position.prepare();
var offsets = Position.cumulativeOffset(this.element);
var max = window.innerHeight ?
@@ -302,8 +392,7 @@ Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
(document.documentElement.clientHeight ?
document.documentElement.clientHeight : document.body.clientHeight);
this.scrollStart = Position.deltaY;
- this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
- this.start(arguments[1] || {});
+ this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
},
update: function(position) {
Position.prepare();
@@ -312,51 +401,61 @@ Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
}
});
-/* ------------- prepackaged effects ------------- */
+/* ------------- combination effects ------------- */
Effect.Fade = function(element) {
- options = Object.extend({
- from: 1.0,
+ var oldOpacity = Element.getInlineOpacity(element);
+ var options = Object.extend({
+ from: Element.getOpacity(element) || 1.0,
to: 0.0,
- afterFinish: function(effect)
- { Element.hide(effect.element);
- effect.setOpacity(1); }
+ afterFinishInternal: function(effect)
+ { if (effect.options.to == 0) {
+ Element.hide(effect.element);
+ Element.setInlineOpacity(effect.element, oldOpacity);
+ }
+ }
}, arguments[1] || {});
- new Effect.Opacity(element,options);
+ return new Effect.Opacity(element,options);
}
Effect.Appear = function(element) {
- options = Object.extend({
- from: 0.0,
+ var options = Object.extend({
+ from: (Element.getStyle(element, "display") == "none" ? 0.0 : Element.getOpacity(element) || 0.0),
to: 1.0,
- beforeStart: function(effect)
- { effect.setOpacity(0);
- Element.show(effect.element); },
- afterUpdate: function(effect)
- { Element.show(effect.element); }
+ beforeSetup: function(effect)
+ { Element.setOpacity(effect.element, effect.options.from);
+ Element.show(effect.element); }
}, arguments[1] || {});
- new Effect.Opacity(element,options);
+ return new Effect.Opacity(element,options);
}
Effect.Puff = function(element) {
- new Effect.Parallel(
- [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true }),
- new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ],
- { duration: 1.0,
- afterUpdate: function(effect)
+ element = $(element);
+ var oldOpacity = Element.getInlineOpacity(element);
+ var oldPosition = element.style.position;
+ return new Effect.Parallel(
+ [ new Effect.Scale(element, 200,
+ { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
+ new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
+ Object.extend({ duration: 1.0,
+ beforeSetupInternal: function(effect)
{ effect.effects[0].element.style.position = 'absolute'; },
- afterFinish: function(effect)
- { Element.hide(effect.effects[0].element); }
- }
+ afterFinishInternal: function(effect)
+ { Element.hide(effect.effects[0].element);
+ effect.effects[0].element.style.position = oldPosition;
+ Element.setInlineOpacity(effect.effects[0].element, oldOpacity); }
+ }, arguments[1] || {})
);
}
Effect.BlindUp = function(element) {
+ element = $(element);
Element.makeClipping(element);
- new Effect.Scale(element, 0,
+ return new Effect.Scale(element, 0,
Object.extend({ scaleContent: false,
scaleX: false,
- afterFinish: function(effect)
+ restoreAfterFinish: true,
+ afterFinishInternal: function(effect)
{
Element.hide(effect.element);
Element.undoClipping(effect.element);
@@ -366,120 +465,179 @@ Effect.BlindUp = function(element) {
}
Effect.BlindDown = function(element) {
- $(element).style.height = '0px';
- Element.makeClipping(element);
- Element.show(element);
- new Effect.Scale(element, 100,
+ element = $(element);
+ var oldHeight = element.style.height;
+ var elementDimensions = Element.getDimensions(element);
+ return new Effect.Scale(element, 100,
Object.extend({ scaleContent: false,
- scaleX: false,
- scaleMode: 'contents',
+ scaleX: false,
scaleFrom: 0,
- afterFinish: function(effect) {
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+ restoreAfterFinish: true,
+ afterSetup: function(effect) {
+ Element.makeClipping(effect.element);
+ effect.element.style.height = "0px";
+ Element.show(effect.element);
+ },
+ afterFinishInternal: function(effect) {
Element.undoClipping(effect.element);
+ effect.element.style.height = oldHeight;
}
}, arguments[1] || {})
);
}
Effect.SwitchOff = function(element) {
- new Effect.Appear(element,
- { duration: 0.4,
- transition: Effect.Transitions.flicker,
- afterFinish: function(effect)
- { effect.element.style.overflow = 'hidden';
- new Effect.Scale(effect.element, 1,
- { duration: 0.3, scaleFromCenter: true,
- scaleX: false, scaleContent: false,
- afterUpdate: function(effect) {
- if(effect.element.style.position=="")
- effect.element.style.position = 'relative'; },
- afterFinish: function(effect) { Element.hide(effect.element); }
- } )
- }
- } );
+ element = $(element);
+ var oldOpacity = Element.getInlineOpacity(element);
+ return new Effect.Appear(element, {
+ duration: 0.4,
+ from: 0,
+ transition: Effect.Transitions.flicker,
+ afterFinishInternal: function(effect) {
+ new Effect.Scale(effect.element, 1, {
+ duration: 0.3, scaleFromCenter: true,
+ scaleX: false, scaleContent: false, restoreAfterFinish: true,
+ beforeSetup: function(effect) {
+ Element.makePositioned(effect.element);
+ Element.makeClipping(effect.element);
+ },
+ afterFinishInternal: function(effect) {
+ Element.hide(effect.element);
+ Element.undoClipping(effect.element);
+ Element.undoPositioned(effect.element);
+ Element.setInlineOpacity(effect.element, oldOpacity);
+ }
+ })
+ }
+ });
}
Effect.DropOut = function(element) {
- new Effect.Parallel(
+ element = $(element);
+ var oldTop = element.style.top;
+ var oldLeft = element.style.left;
+ var oldOpacity = Element.getInlineOpacity(element);
+ return new Effect.Parallel(
[ new Effect.MoveBy(element, 100, 0, { sync: true }),
- new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ],
- { duration: 0.5,
- afterFinish: function(effect)
- { Element.hide(effect.effects[0].element); }
- });
+ new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
+ Object.extend(
+ { duration: 0.5,
+ beforeSetup: function(effect) {
+ Element.makePositioned(effect.effects[0].element); },
+ afterFinishInternal: function(effect) {
+ Element.hide(effect.effects[0].element);
+ Element.undoPositioned(effect.effects[0].element);
+ effect.effects[0].element.style.left = oldLeft;
+ effect.effects[0].element.style.top = oldTop;
+ Element.setInlineOpacity(effect.effects[0].element, oldOpacity); }
+ }, arguments[1] || {}));
}
Effect.Shake = function(element) {
- new Effect.MoveBy(element, 0, 20,
- { duration: 0.05, afterFinish: function(effect) {
+ element = $(element);
+ var oldTop = element.style.top;
+ var oldLeft = element.style.left;
+ return new Effect.MoveBy(element, 0, 20,
+ { duration: 0.05, afterFinishInternal: function(effect) {
new Effect.MoveBy(effect.element, 0, -40,
- { duration: 0.1, afterFinish: function(effect) {
+ { duration: 0.1, afterFinishInternal: function(effect) {
new Effect.MoveBy(effect.element, 0, 40,
- { duration: 0.1, afterFinish: function(effect) {
+ { duration: 0.1, afterFinishInternal: function(effect) {
new Effect.MoveBy(effect.element, 0, -40,
- { duration: 0.1, afterFinish: function(effect) {
+ { duration: 0.1, afterFinishInternal: function(effect) {
new Effect.MoveBy(effect.element, 0, 40,
- { duration: 0.1, afterFinish: function(effect) {
+ { duration: 0.1, afterFinishInternal: function(effect) {
new Effect.MoveBy(effect.element, 0, -20,
- { duration: 0.05, afterFinish: function(effect) {
+ { duration: 0.05, afterFinishInternal: function(effect) {
+ Element.undoPositioned(effect.element);
+ effect.element.style.left = oldLeft;
+ effect.element.style.top = oldTop;
}}) }}) }}) }}) }}) }});
}
Effect.SlideDown = function(element) {
element = $(element);
- element.style.height = '0px';
- Element.makeClipping(element);
Element.cleanWhitespace(element);
- Element.makePositioned(element.firstChild);
- Element.show(element);
- new Effect.Scale(element, 100,
+ // SlideDown need to have the content of the element wrapped in a container element with fixed height!
+ var oldInnerBottom = element.firstChild.style.bottom;
+ var elementDimensions = Element.getDimensions(element);
+ return new Effect.Scale(element, 100,
Object.extend({ scaleContent: false,
scaleX: false,
- scaleMode: 'contents',
scaleFrom: 0,
- afterUpdate: function(effect)
- { effect.element.firstChild.style.bottom =
- (effect.originalHeight - effect.element.clientHeight) + 'px'; },
- afterFinish: function(effect)
- { Element.undoClipping(effect.element); }
+ scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
+ restoreAfterFinish: true,
+ afterSetup: function(effect) {
+ Element.makePositioned(effect.element.firstChild);
+ if (window.opera) effect.element.firstChild.style.top = "";
+ Element.makeClipping(effect.element);
+ element.style.height = '0';
+ Element.show(element);
+ },
+ afterUpdateInternal: function(effect) {
+ effect.element.firstChild.style.bottom =
+ (effect.originalHeight - effect.element.clientHeight) + 'px'; },
+ afterFinishInternal: function(effect) {
+ Element.undoClipping(effect.element);
+ Element.undoPositioned(effect.element.firstChild);
+ effect.element.firstChild.style.bottom = oldInnerBottom; }
}, arguments[1] || {})
);
}
Effect.SlideUp = function(element) {
element = $(element);
- Element.makeClipping(element);
Element.cleanWhitespace(element);
- Element.makePositioned(element.firstChild);
- Element.show(element);
- new Effect.Scale(element, 0,
+ var oldInnerBottom = element.firstChild.style.bottom;
+ return new Effect.Scale(element, 0,
Object.extend({ scaleContent: false,
scaleX: false,
- afterUpdate: function(effect)
- { effect.element.firstChild.style.bottom =
- (effect.originalHeight - effect.element.clientHeight) + 'px'; },
- afterFinish: function(effect)
- {
+ scaleMode: 'box',
+ scaleFrom: 100,
+ restoreAfterFinish: true,
+ beforeStartInternal: function(effect) {
+ Element.makePositioned(effect.element.firstChild);
+ if (window.opera) effect.element.firstChild.style.top = "";
+ Element.makeClipping(effect.element);
+ Element.show(element);
+ },
+ afterUpdateInternal: function(effect) {
+ effect.element.firstChild.style.bottom =
+ (effect.originalHeight - effect.element.clientHeight) + 'px'; },
+ afterFinishInternal: function(effect) {
Element.hide(effect.element);
- Element.undoClipping(effect.element);
- }
+ Element.undoClipping(effect.element);
+ Element.undoPositioned(effect.element.firstChild);
+ effect.element.firstChild.style.bottom = oldInnerBottom; }
}, arguments[1] || {})
);
}
Effect.Squish = function(element) {
- new Effect.Scale(element, 0,
- { afterFinish: function(effect) { Element.hide(effect.element); } });
+ // Bug in opera makes the TD containing this element expand for a instance after finish
+ return new Effect.Scale(element, window.opera ? 1 : 0,
+ { restoreAfterFinish: true,
+ beforeSetup: function(effect) {
+ Element.makeClipping(effect.element); },
+ afterFinishInternal: function(effect) {
+ Element.hide(effect.element);
+ Element.undoClipping(effect.element); }
+ });
}
Effect.Grow = function(element) {
element = $(element);
var options = arguments[1] || {};
- var originalWidth = element.clientWidth;
- var originalHeight = element.clientHeight;
- element.style.overflow = 'hidden';
- Element.show(element);
+ var elementDimensions = Element.getDimensions(element);
+ var originalWidth = elementDimensions.width;
+ var originalHeight = elementDimensions.height;
+ var oldTop = element.style.top;
+ var oldLeft = element.style.left;
+ var oldHeight = element.style.height;
+ var oldWidth = element.style.width;
+ var oldOpacity = Element.getInlineOpacity(element);
var direction = options.direction || 'center';
var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
@@ -517,18 +675,40 @@ Effect.Grow = function(element) {
break;
}
- new Effect.MoveBy(element, initialMoveY, initialMoveX, {
+ return new Effect.MoveBy(element, initialMoveY, initialMoveX, {
duration: 0.01,
- beforeUpdate: function(effect) { $(element).style.height = '0px'; },
- afterFinish: function(effect) {
+ beforeSetup: function(effect) {
+ Element.hide(effect.element);
+ Element.makeClipping(effect.element);
+ Element.makePositioned(effect.element);
+ },
+ afterFinishInternal: function(effect) {
new Effect.Parallel(
- [ new Effect.Opacity(element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }),
- new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }),
- new Effect.Scale(element, 100, {
+ [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }),
+ new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: moveTransition }),
+ new Effect.Scale(effect.element, 100, {
scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth },
- sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })],
- options); }
- });
+ sync: true, scaleFrom: window.opera ? 1 : 0, transition: scaleTransition, restoreAfterFinish: true})
+ ], Object.extend({
+ beforeSetup: function(effect) {
+ effect.effects[0].element.style.height = 0;
+ Element.show(effect.effects[0].element);
+ },
+ afterFinishInternal: function(effect) {
+ var el = effect.effects[0].element;
+ var els = el.style;
+ Element.undoClipping(el);
+ Element.undoPositioned(el);
+ els.top = oldTop;
+ els.left = oldLeft;
+ els.height = oldHeight;
+ els.width = originalWidth;
+ Element.setInlineOpacity(el, oldOpacity);
+ }
+ }, options)
+ )
+ }
+ });
}
Effect.Shrink = function(element) {
@@ -537,8 +717,11 @@ Effect.Shrink = function(element) {
var originalWidth = element.clientWidth;
var originalHeight = element.clientHeight;
- element.style.overflow = 'hidden';
- Element.show(element);
+ var oldTop = element.style.top;
+ var oldLeft = element.style.left;
+ var oldHeight = element.style.height;
+ var oldWidth = element.style.width;
+ var oldOpacity = Element.getInlineOpacity(element);
var direction = options.direction || 'center';
var moveTransition = options.moveTransition || Effect.Transitions.sinoidal;
@@ -569,44 +752,65 @@ Effect.Shrink = function(element) {
break;
}
- new Effect.Parallel(
+ return new Effect.Parallel(
[ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }),
- new Effect.Scale(element, 0, { sync: true, transition: moveTransition }),
- new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: scaleTransition }) ],
- options);
+ new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: scaleTransition, restoreAfterFinish: true}),
+ new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition })
+ ], Object.extend({
+ beforeStartInternal: function(effect) {
+ Element.makePositioned(effect.effects[0].element);
+ Element.makeClipping(effect.effects[0].element);
+ },
+ afterFinishInternal: function(effect) {
+ var el = effect.effects[0].element;
+ var els = el.style;
+ Element.hide(el);
+ Element.undoClipping(el);
+ Element.undoPositioned(el);
+ els.top = oldTop;
+ els.left = oldLeft;
+ els.height = oldHeight;
+ els.width = oldWidth;
+ Element.setInlineOpacity(el, oldOpacity);
+ }
+ }, options)
+ );
}
Effect.Pulsate = function(element) {
+ element = $(element);
var options = arguments[1] || {};
+ var oldOpacity = Element.getInlineOpacity(element);
var transition = options.transition || Effect.Transitions.sinoidal;
var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
reverser.bind(transition);
- new Effect.Opacity(element,
- Object.extend(Object.extend({ duration: 3.0,
- afterFinish: function(effect) { Element.show(effect.element); }
+ return new Effect.Opacity(element,
+ Object.extend(Object.extend({ duration: 3.0, from: 0,
+ afterFinishInternal: function(effect) { Element.setInlineOpacity(effect.element, oldOpacity); }
}, options), {transition: reverser}));
}
Effect.Fold = function(element) {
- $(element).style.overflow = 'hidden';
- new Effect.Scale(element, 5, Object.extend({
- scaleContent: false,
- scaleTo: 100,
- scaleX: false,
- afterFinish: function(effect) {
- new Effect.Scale(element, 1, {
- scaleContent: false,
- scaleTo: 0,
- scaleY: false,
- afterFinish: function(effect) { Element.hide(effect.element) } });
- }}, arguments[1] || {}));
-}
-
-// old: new Effect.ContentZoom(element, percent)
-// new: Element.setContentZoom(element, percent)
-
-Element.setContentZoom = function(element, percent) {
- var element = $(element);
- element.style.fontSize = (percent/100) + "em";
- if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+ element = $(element);
+ var originalTop = element.style.top;
+ var originalLeft = element.style.left;
+ var originalWidth = element.style.width;
+ var originalHeight = element.style.height;
+ Element.makeClipping(element);
+ return new Effect.Scale(element, 5, Object.extend({
+ scaleContent: false,
+ scaleX: false,
+ afterFinishInternal: function(effect) {
+ new Effect.Scale(element, 1, {
+ scaleContent: false,
+ scaleY: false,
+ afterFinishInternal: function(effect) {
+ Element.hide(effect.element);
+ Element.undoClipping(effect.element);
+ effect.element.style.top = originalTop;
+ effect.element.style.left = originalLeft;
+ effect.element.style.width = originalWidth;
+ effect.element.style.height = originalHeight;
+ } });
+ }}, arguments[1] || {}));
}
diff --git a/railties/html/javascripts/prototype.js b/railties/html/javascripts/prototype.js
index 5feddb6dda..c0ec65450d 100644
--- a/railties/html/javascripts/prototype.js
+++ b/railties/html/javascripts/prototype.js
@@ -1,4 +1,4 @@
-/* Prototype JavaScript framework, version 1.3.1
+/* Prototype JavaScript framework, version 1.4.0_pre7
* (c) 2005 Sam Stephenson <sam@conio.net>
*
* THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff
@@ -11,8 +11,10 @@
/*--------------------------------------------------------------------------*/
var Prototype = {
- Version: '1.3.1',
- emptyFunction: function() {}
+ Version: '1.4.0_pre7',
+
+ emptyFunction: function() {},
+ K: function(x) {return x}
}
var Class = {
@@ -32,29 +34,36 @@ Object.extend = function(destination, source) {
return destination;
}
-Object.prototype.extend = function(object) {
- return Object.extend.apply(this, [this, object]);
-}
-
Function.prototype.bind = function(object) {
var __method = this;
return function() {
- __method.apply(object, arguments);
+ return __method.apply(object, arguments);
}
}
Function.prototype.bindAsEventListener = function(object) {
var __method = this;
return function(event) {
- __method.call(object, event || window.event);
+ return __method.call(object, event || window.event);
}
}
-Number.prototype.toColorPart = function() {
- var digits = this.toString(16);
- if (this < 16) return '0' + digits;
- return digits;
-}
+Object.extend(Number.prototype, {
+ toColorPart: function() {
+ var digits = this.toString(16);
+ if (this < 16) return '0' + digits;
+ return digits;
+ },
+
+ succ: function() {
+ return this + 1;
+ },
+
+ times: function(iterator) {
+ $R(0, this, true).each(iterator);
+ return this;
+ }
+});
var Try = {
these: function() {
@@ -140,14 +149,14 @@ if (!Function.prototype.apply) {
object.__apply__ = this;
var result = eval('object.__apply__(' +
- parameterStrings[i].join(', ') + ')');
+ parameterStrings.join(', ') + ')');
object.__apply__ = null;
return result;
}
}
-String.prototype.extend({
+Object.extend(String.prototype, {
stripTags: function() {
return this.replace(/<\/?[^>]+>/gi, '');
},
@@ -163,9 +172,262 @@ String.prototype.extend({
var div = document.createElement('div');
div.innerHTML = this.stripTags();
return div.childNodes[0].nodeValue;
+ },
+
+ parseQuery: function() {
+ var str = this;
+ if (str.substring(0,1) == '?') {
+ str = this.substring(1);
+ }
+ var result = {};
+ var pairs = str.split('&');
+ for (var i = 0; i < pairs.length; i++) {
+ var pair = pairs[i].split('=');
+ result[pair[0]] = pair[1];
+ }
+ return result;
}
});
+
+var _break = new Object();
+var _continue = new Object();
+
+var Enumerable = {
+ each: function(iterator) {
+ var index = 0;
+ try {
+ this._each(function(value) {
+ try {
+ iterator(value, index++);
+ } catch (e) {
+ if (e != _continue) throw e;
+ }
+ });
+ } catch (e) {
+ if (e != _break) throw e;
+ }
+ },
+
+ all: function(iterator) {
+ var result = true;
+ this.each(function(value, index) {
+ if (!(result &= (iterator || Prototype.K)(value, index)))
+ throw _break;
+ });
+ return result;
+ },
+
+ any: function(iterator) {
+ var result = true;
+ this.each(function(value, index) {
+ if (result &= (iterator || Prototype.K)(value, index))
+ throw _break;
+ });
+ return result;
+ },
+
+ collect: function(iterator) {
+ var results = [];
+ this.each(function(value, index) {
+ results.push(iterator(value, index));
+ });
+ return results;
+ },
+
+ detect: function (iterator) {
+ var result;
+ this.each(function(value, index) {
+ if (iterator(value, index)) {
+ result = value;
+ throw _break;
+ }
+ });
+ return result;
+ },
+
+ findAll: function(iterator) {
+ var results = [];
+ this.each(function(value, index) {
+ if (iterator(value, index))
+ results.push(value);
+ });
+ return results;
+ },
+
+ grep: function(pattern, iterator) {
+ var results = [];
+ this.each(function(value, index) {
+ var stringValue = value.toString();
+ if (stringValue.match(pattern))
+ results.push((iterator || Prototype.K)(value, index));
+ })
+ return results;
+ },
+
+ include: function(object) {
+ var found = false;
+ this.each(function(value) {
+ if (value == object) {
+ found = true;
+ throw _break;
+ }
+ });
+ return found;
+ },
+
+ inject: function(memo, iterator) {
+ this.each(function(value, index) {
+ memo = iterator(memo, value, index);
+ });
+ return memo;
+ },
+
+ invoke: function(method) {
+ var args = $A(arguments).slice(1);
+ return this.collect(function(value) {
+ return value[method].apply(value, args);
+ });
+ },
+
+ max: function(iterator) {
+ var result;
+ this.each(function(value, index) {
+ value = (iterator || Prototype.K)(value, index);
+ if (value >= (result || value))
+ result = value;
+ });
+ return result;
+ },
+
+ min: function(iterator) {
+ var result;
+ this.each(function(value, index) {
+ value = (iterator || Prototype.K)(value, index);
+ if (value <= (result || value))
+ result = value;
+ });
+ return result;
+ },
+
+ partition: function(iterator) {
+ var trues = [], falses = [];
+ this.each(function(value, index) {
+ ((iterator || Prototype.K)(value, index) ?
+ trues : falses).push(value);
+ });
+ return [trues, falses];
+ },
+
+ pluck: function(property) {
+ var results = [];
+ this.each(function(value, index) {
+ results.push(value[property]);
+ });
+ return results;
+ },
+
+ reject: function(iterator) {
+ var results = [];
+ this.each(function(value, index) {
+ if (!iterator(value, index))
+ results.push(value);
+ });
+ return results;
+ },
+
+ sortBy: function(iterator) {
+ return this.collect(function(value, index) {
+ return {value: value, criteria: iterator(value, index)};
+ }).sort(function(left, right) {
+ var a = left.criteria, b = right.criteria;
+ return a < b ? -1 : a > b ? 1 : 0;
+ }).pluck('value');
+ },
+
+ toArray: function() {
+ return this.collect(Prototype.K);
+ },
+
+ zip: function() {
+ var iterator = Prototype.K, args = $A(arguments);
+ if (typeof args.last() == 'function')
+ iterator = args.pop();
+
+ var collections = [this].concat(args).map($A);
+ return this.map(function(value, index) {
+ iterator(value = collections.pluck(index));
+ return value;
+ });
+ }
+}
+
+Object.extend(Enumerable, {
+ map: Enumerable.collect,
+ find: Enumerable.detect,
+ select: Enumerable.findAll,
+ member: Enumerable.include,
+ entries: Enumerable.toArray
+});
+
+var $A = Array.from = function(iterable) {
+ if (iterable.toArray) {
+ return iterable.toArray();
+ } else {
+ var results = [];
+ for (var i = 0; i < iterable.length; i++)
+ results.push(iterable[i]);
+ return results;
+ }
+}
+
+Object.extend(Array.prototype, {
+ _each: function(iterator) {
+ for (var i = 0; i < this.length; i++)
+ iterator(this[i]);
+ },
+
+ first: function() {
+ return this[0];
+ },
+
+ last: function() {
+ return this[this.length - 1];
+ }
+});
+
+Object.extend(Array.prototype, Enumerable);
+
+var Range = Class.create();
+Object.extend(Range.prototype, Enumerable);
+Object.extend(Range.prototype, {
+ initialize: function(start, end, exclusive) {
+ this.start = start;
+ this.end = end;
+ this.exclusive = exclusive;
+ },
+
+ _each: function(iterator) {
+ var value = this.start;
+ do {
+ iterator(value);
+ value = value.succ();
+ } while (this.include(value));
+ },
+
+ include: function(value) {
+ if (value < this.start)
+ return false;
+ if (this.exclusive)
+ return value < this.end;
+ return value <= this.end;
+ }
+});
+
+var $R = function(start, end, exclusive) {
+ return new Range(start, end, exclusive);
+}
+
var Ajax = {
getTransport: function() {
return Try.these(
@@ -183,7 +445,8 @@ Ajax.Base.prototype = {
method: 'post',
asynchronous: true,
parameters: ''
- }.extend(options || {});
+ }
+ Object.extend(this.options, options || {});
},
responseIsSuccess: function() {
@@ -201,7 +464,7 @@ Ajax.Request = Class.create();
Ajax.Request.Events =
['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
-Ajax.Request.prototype = (new Ajax.Base()).extend({
+Ajax.Request.prototype = Object.extend(new Ajax.Base(), {
initialize: function(url, options) {
this.transport = Ajax.getTransport();
this.setOptions(options);
@@ -262,16 +525,26 @@ Ajax.Request.prototype = (new Ajax.Base()).extend({
if (readyState != 1)
this.respondToReadyState(this.transport.readyState);
},
+
+ evalJSON: function() {
+ try {
+ var json = this.transport.getResponseHeader('X-JSON'), object;
+ object = eval(json);
+ return object;
+ } catch (e) {
+ }
+ },
respondToReadyState: function(readyState) {
var event = Ajax.Request.Events[readyState];
+ var transport = this.transport, json = this.evalJSON();
if (event == 'Complete')
(this.options['on' + this.transport.status]
|| this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')]
- || Prototype.emptyFunction)(this.transport);
+ || Prototype.emptyFunction)(transport, json);
- (this.options['on' + event] || Prototype.emptyFunction)(this.transport);
+ (this.options['on' + event] || Prototype.emptyFunction)(transport, json);
/* Avoid memory leak in MSIE: clean up the oncomplete event handler */
if (event == 'Complete')
@@ -282,7 +555,7 @@ Ajax.Request.prototype = (new Ajax.Base()).extend({
Ajax.Updater = Class.create();
Ajax.Updater.ScriptFragment = '(?:<script.*?>)((\n|.)*?)(?:<\/script>)';
-Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({
+Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), {
initialize: function(container, url, options) {
this.containers = {
success: container.success ? $(container.success) : $(container),
@@ -294,9 +567,9 @@ Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({
this.setOptions(options);
var onComplete = this.options.onComplete || Prototype.emptyFunction;
- this.options.onComplete = (function() {
+ this.options.onComplete = (function(transport, object) {
this.updateContent();
- onComplete(this.transport);
+ onComplete(transport, object);
}).bind(this);
this.request(url);
@@ -320,8 +593,7 @@ Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({
if (this.responseIsSuccess()) {
if (this.onComplete)
- setTimeout((function() {this.onComplete(
- this.transport)}).bind(this), 10);
+ setTimeout(this.onComplete.bind(this), 10);
}
if (this.options.evalScripts && scripts) {
@@ -335,7 +607,7 @@ Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({
});
Ajax.PeriodicalUpdater = Class.create();
-Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({
+Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), {
initialize: function(container, url, options) {
this.setOptions(options);
this.onComplete = this.options.onComplete;
@@ -494,20 +766,35 @@ Abstract.Insertion.prototype = {
this.content = content;
if (this.adjacency && this.element.insertAdjacentHTML) {
- this.element.insertAdjacentHTML(this.adjacency, this.content);
+ try {
+ this.element.insertAdjacentHTML(this.adjacency, this.content);
+ } catch (e) {
+ if (this.element.tagName.toLowerCase() == 'tbody') {
+ this.fragment = this.contentFromAnonymousTable();
+ this.insertContent();
+ } else {
+ throw e;
+ }
+ }
} else {
this.range = this.element.ownerDocument.createRange();
if (this.initializeRange) this.initializeRange();
this.fragment = this.range.createContextualFragment(this.content);
this.insertContent();
}
+ },
+
+ contentFromAnonymousTable: function() {
+ var div = document.createElement('div');
+ div.innerHTML = '<table><tbody>' + this.content + '</tbody></table>';
+ return div.childNodes[0].childNodes[0].childNodes[0];
}
}
var Insertion = new Object();
Insertion.Before = Class.create();
-Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({
+Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), {
initializeRange: function() {
this.range.setStartBefore(this.element);
},
@@ -518,7 +805,7 @@ Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({
});
Insertion.Top = Class.create();
-Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({
+Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), {
initializeRange: function() {
this.range.selectNodeContents(this.element);
this.range.collapse(true);
@@ -530,7 +817,7 @@ Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({
});
Insertion.Bottom = Class.create();
-Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({
+Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), {
initializeRange: function() {
this.range.selectNodeContents(this.element);
this.range.collapse(this.element);
@@ -542,7 +829,7 @@ Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({
});
Insertion.After = Class.create();
-Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({
+Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), {
initializeRange: function() {
this.range.setStartAfter(this.element);
},
@@ -704,19 +991,32 @@ Form.Element.Serializers = {
textarea: function(element) {
return [element.name, element.value];
},
-
+
select: function(element) {
- var value = '';
- if (element.type == 'select-one') {
- var index = element.selectedIndex;
- if (index >= 0)
- value = element.options[index].value || element.options[index].text;
- } else {
- value = new Array();
- for (var i = 0; i < element.length; i++) {
- var opt = element.options[i];
- if (opt.selected)
- value.push(opt.value || opt.text);
+ return Form.Element.Serializers[element.type == 'select-one' ?
+ 'selectOne' : 'selectMany'](element);
+ },
+
+ selectOne: function(element) {
+ var value = '', opt, index = element.selectedIndex;
+ if (index >= 0) {
+ opt = element.options[index];
+ value = opt.value;
+ if (!value && !('value' in opt))
+ value = opt.text;
+ }
+ return [element.name, value];
+ },
+
+ selectMany: function(element) {
+ var value = new Array();
+ for (var i = 0; i < element.length; i++) {
+ var opt = element.options[i];
+ if (opt.selected) {
+ var optValue = opt.value;
+ if (!optValue && !('value' in opt))
+ optValue = opt.text;
+ value.push(optValue);
}
}
return [element.name, value];
@@ -754,14 +1054,14 @@ Abstract.TimedObserver.prototype = {
}
Form.Element.Observer = Class.create();
-Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({
+Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
getValue: function() {
return Form.Element.getValue(this.element);
}
});
Form.Observer = Class.create();
-Form.Observer.prototype = (new Abstract.TimedObserver()).extend({
+Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), {
getValue: function() {
return Form.serialize(this.element);
}
@@ -826,14 +1126,14 @@ Abstract.EventObserver.prototype = {
}
Form.Element.EventObserver = Class.create();
-Form.Element.EventObserver.prototype = (new Abstract.EventObserver()).extend({
+Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
getValue: function() {
return Form.Element.getValue(this.element);
}
});
Form.EventObserver = Class.create();
-Form.EventObserver.prototype = (new Abstract.EventObserver()).extend({
+Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), {
getValue: function() {
return Form.serialize(this.element);
}
@@ -880,6 +1180,7 @@ Object.extend(Event, {
event.stopPropagation();
} else {
event.returnValue = false;
+ event.cancelBubble = true;
}
},
@@ -920,7 +1221,7 @@ Object.extend(Event, {
useCapture = useCapture || false;
if (name == 'keypress' &&
- ((navigator.appVersion.indexOf('AppleWebKit') > 0)
+ (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
|| element.attachEvent))
name = 'keydown';
@@ -932,7 +1233,7 @@ Object.extend(Event, {
useCapture = useCapture || false;
if (name == 'keypress' &&
- ((navigator.appVersion.indexOf('AppleWebKit') > 0)
+ (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
|| element.detachEvent))
name = 'keydown';
diff --git a/railties/html/javascripts/scriptaculous.js b/railties/html/javascripts/scriptaculous.js
new file mode 100644
index 0000000000..f94af80c47
--- /dev/null
+++ b/railties/html/javascripts/scriptaculous.js
@@ -0,0 +1,48 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// 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.
+
+var Scriptaculous = {
+ Version: '1.5_rc2',
+ require: function(libraryName) {
+ // inserting via DOM fails in Safari 2.0, so brute force approach
+ document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
+ },
+ load: function() {
+ if((typeof Prototype=='undefined') ||
+ parseFloat(Prototype.Version.split(".")[0] + "." +
+ Prototype.Version.split(".")[1]) < 1.4)
+ throw("script.aculo.us requires the Prototype JavaScript framework >= 1.4.0");
+ var scriptTags = document.getElementsByTagName("script");
+ for(var i=0;i<scriptTags.length;i++) {
+ if(scriptTags[i].src && scriptTags[i].src.match(/scriptaculous\.js(\?.*)?$/)) {
+ var path = scriptTags[i].src.replace(/scriptaculous\.js(\?.*)?$/,'');
+ this.require(path + 'util.js');
+ this.require(path + 'effects.js');
+ this.require(path + 'dragdrop.js');
+ this.require(path + 'controls.js');
+ this.require(path + 'slider.js');
+ break;
+ }
+ }
+ }
+}
+
+Scriptaculous.load(); \ No newline at end of file
diff --git a/railties/html/javascripts/slider.js b/railties/html/javascripts/slider.js
new file mode 100644
index 0000000000..1712b98943
--- /dev/null
+++ b/railties/html/javascripts/slider.js
@@ -0,0 +1,258 @@
+// Copyright (c) 2005 Marty Haught
+//
+// See scriptaculous.js for full license.
+
+if(!Control) var Control = {};
+Control.Slider = Class.create();
+
+// options:
+// axis: 'vertical', or 'horizontal' (default)
+// increment: (default: 1)
+// step: (default: 1)
+//
+// callbacks:
+// onChange(value)
+// onSlide(value)
+Control.Slider.prototype = {
+ initialize: function(handle, track, options) {
+ this.handle = $(handle);
+ this.track = $(track);
+
+ this.options = options || {};
+
+ this.axis = this.options.axis || 'horizontal';
+ this.increment = this.options.increment || 1;
+ this.step = parseInt(this.options.step) || 1;
+ this.value = 0;
+
+ var defaultMaximum = Math.round(this.track.offsetWidth / this.increment);
+ if(this.isVertical()) defaultMaximum = Math.round(this.track.offsetHeight / this.increment);
+
+ this.maximum = this.options.maximum || defaultMaximum;
+ this.minimum = this.options.minimum || 0;
+
+ // Will be used to align the handle onto the track, if necessary
+ this.alignX = parseInt (this.options.alignX) || 0;
+ this.alignY = parseInt (this.options.alignY) || 0;
+
+ // Zero out the slider position
+ this.setCurrentLeft(Position.cumulativeOffset(this.track)[0] - Position.cumulativeOffset(this.handle)[0] + this.alignX);
+ this.setCurrentTop(this.trackTop() - Position.cumulativeOffset(this.handle)[1] + this.alignY);
+
+ this.offsetX = 0;
+ this.offsetY = 0;
+
+ this.originalLeft = this.currentLeft();
+ this.originalTop = this.currentTop();
+ this.originalZ = parseInt(this.handle.style.zIndex || "0");
+
+ // Prepopulate Slider value
+ this.setSliderValue(parseInt(this.options.sliderValue) || 0);
+
+ this.active = false;
+ this.dragging = false;
+ this.disabled = false;
+
+ // FIXME: use css
+ this.handleImage = $(this.options.handleImage) || false;
+ this.handleDisabled = this.options.handleDisabled || false;
+ this.handleEnabled = false;
+ if(this.handleImage)
+ this.handleEnabled = this.handleImage.src || false;
+
+ if(this.options.disabled)
+ this.setDisabled();
+
+ // Value Array
+ this.values = this.options.values || false; // Add method to validate and sort??
+
+ Element.makePositioned(this.handle); // fix IE
+
+ this.eventMouseDown = this.startDrag.bindAsEventListener(this);
+ this.eventMouseUp = this.endDrag.bindAsEventListener(this);
+ this.eventMouseMove = this.update.bindAsEventListener(this);
+ this.eventKeypress = this.keyPress.bindAsEventListener(this);
+
+ Event.observe(this.handle, "mousedown", this.eventMouseDown);
+ Event.observe(document, "mouseup", this.eventMouseUp);
+ Event.observe(document, "mousemove", this.eventMouseMove);
+ Event.observe(document, "keypress", this.eventKeypress);
+ },
+ dispose: 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);
+ },
+ setDisabled: function(){
+ this.disabled = true;
+ if(this.handleDisabled)
+ this.handleImage.src = this.handleDisabled;
+ },
+ setEnabled: function(){
+ this.disabled = false;
+ if(this.handleEnabled)
+ this.handleImage.src = this.handleEnabled;
+ },
+ currentLeft: function() {
+ return parseInt(this.handle.style.left || '0');
+ },
+ currentTop: function() {
+ return parseInt(this.handle.style.top || '0');
+ },
+ setCurrentLeft: function(left) {
+ this.handle.style.left = left +"px";
+ },
+ setCurrentTop: function(top) {
+ this.handle.style.top = top +"px";
+ },
+ trackLeft: function(){
+ return Position.cumulativeOffset(this.track)[0];
+ },
+ trackTop: function(){
+ return Position.cumulativeOffset(this.track)[1];
+ },
+ getNearestValue: function(value){
+ if(this.values){
+ var i = 0;
+ var offset = Math.abs(this.values[0] - value);
+ var newValue = this.values[0];
+
+ for(i=0; i < this.values.length; i++){
+ var currentOffset = Math.abs(this.values[i] - value);
+ if(currentOffset < offset){
+ newValue = this.values[i];
+ offset = currentOffset;
+ }
+ }
+ return newValue;
+ }
+ return value;
+ },
+ setSliderValue: function(sliderValue){
+ // First check our max and minimum and nearest values
+ sliderValue = this.getNearestValue(sliderValue);
+ if(sliderValue > this.maximum) sliderValue = this.maximum;
+ if(sliderValue < this.minimum) sliderValue = this.minimum;
+ var offsetDiff = (sliderValue - (this.value||this.minimum)) * this.increment;
+
+ if(this.isVertical()){
+ this.setCurrentTop(offsetDiff + this.currentTop());
+ } else {
+ this.setCurrentLeft(offsetDiff + this.currentLeft());
+ }
+ this.value = sliderValue;
+ this.updateFinished();
+ },
+ minimumOffset: function(){
+ return(this.isVertical() ?
+ this.trackTop() + this.alignY :
+ this.trackLeft() + this.alignX);
+ },
+ maximumOffset: function(){
+ return(this.isVertical() ?
+ this.trackTop() + this.alignY + (this.maximum - this.minimum) * this.increment :
+ this.trackLeft() + this.alignX + (this.maximum - this.minimum) * this.increment);
+ },
+ isVertical: function(){
+ return (this.axis == 'vertical');
+ },
+ startDrag: function(event) {
+ if(Event.isLeftClick(event)) {
+ if(!this.disabled){
+ this.active = true;
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ var offsets = Position.cumulativeOffset(this.handle);
+ this.offsetX = (pointer[0] - offsets[0]);
+ this.offsetY = (pointer[1] - offsets[1]);
+ this.originalLeft = this.currentLeft();
+ this.originalTop = this.currentTop();
+ }
+ Event.stop(event);
+ }
+ },
+ update: function(event) {
+ if(this.active) {
+ if(!this.dragging) {
+ var style = this.handle.style;
+ this.dragging = true;
+ if(style.position=="") style.position = "relative";
+ style.zIndex = this.options.zindex;
+ }
+ this.draw(event);
+ // fix AppleWebKit rendering
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+ Event.stop(event);
+ }
+ },
+ draw: function(event) {
+ var pointer = [Event.pointerX(event), Event.pointerY(event)];
+ var offsets = Position.cumulativeOffset(this.handle);
+
+ offsets[0] -= this.currentLeft();
+ offsets[1] -= this.currentTop();
+
+ // Adjust for the pointer's position on the handle
+ pointer[0] -= this.offsetX;
+ pointer[1] -= this.offsetY;
+ var style = this.handle.style;
+
+ if(this.isVertical()){
+ if(pointer[1] > this.maximumOffset())
+ pointer[1] = this.maximumOffset();
+ if(pointer[1] < this.minimumOffset())
+ pointer[1] = this.minimumOffset();
+
+ // Increment by values
+ if(this.values){
+ this.value = this.getNearestValue(Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum);
+ pointer[1] = this.trackTop() + this.alignY + (this.value - this.minimum) * this.increment;
+ } else {
+ this.value = Math.round((pointer[1] - this.minimumOffset()) / this.increment) + this.minimum;
+ }
+ style.top = pointer[1] - offsets[1] + "px";
+ } else {
+ if(pointer[0] > this.maximumOffset()) pointer[0] = this.maximumOffset();
+ if(pointer[0] < this.minimumOffset()) pointer[0] = this.minimumOffset();
+ // Increment by values
+ if(this.values){
+ this.value = this.getNearestValue(Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum);
+ pointer[0] = this.trackLeft() + this.alignX + (this.value - this.minimum) * this.increment;
+ } else {
+ this.value = Math.round((pointer[0] - this.minimumOffset()) / this.increment) + this.minimum;
+ }
+ style.left = (pointer[0] - offsets[0]) + "px";
+ }
+ if(this.options.onSlide) this.options.onSlide(this.value);
+ },
+ endDrag: function(event) {
+ if(this.active && this.dragging) {
+ this.finishDrag(event, true);
+ Event.stop(event);
+ }
+ this.active = false;
+ this.dragging = false;
+ },
+ finishDrag: function(event, success) {
+ this.active = false;
+ this.dragging = false;
+ this.handle.style.zIndex = this.originalZ;
+ this.originalLeft = this.currentLeft();
+ this.originalTop = this.currentTop();
+ this.updateFinished();
+ },
+ updateFinished: function() {
+ if(this.options.onChange) this.options.onChange(this.value);
+ },
+ keyPress: function(event) {
+ if(this.active && !this.disabled) {
+ switch(event.keyCode) {
+ case Event.KEY_ESC:
+ this.finishDrag(event, false);
+ Event.stop(event);
+ break;
+ }
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
+ }
+ }
+}
diff --git a/railties/html/javascripts/util.js b/railties/html/javascripts/util.js
new file mode 100644
index 0000000000..c83101b7d2
--- /dev/null
+++ b/railties/html/javascripts/util.js
@@ -0,0 +1,521 @@
+// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
+//
+// See scriptaculous.js for full license.
+
+Object.inspect = function(obj) {
+ var info = [];
+
+ if(typeof obj in ["string","number"]) {
+ return obj;
+ } else {
+ for(property in obj)
+ if(typeof obj[property]!="function")
+ info.push(property + ' => ' +
+ (typeof obj[property] == "string" ?
+ '"' + obj[property] + '"' :
+ obj[property]));
+ }
+
+ return ("'" + obj + "' #" + typeof obj +
+ ": {" + info.join(", ") + "}");
+}
+
+// borrowed from http://www.schuerig.de/michael/javascript/stdext.js
+// Copyright (c) 2005, Michael Schuerig, michael@schuerig.de
+
+Array.flatten = function(array, excludeUndefined) {
+ if (excludeUndefined === undefined) {
+ excludeUndefined = false;
+ }
+ var result = [];
+ var len = array.length;
+ for (var i = 0; i < len; i++) {
+ var el = array[i];
+ if (el instanceof Array) {
+ var flat = el.flatten(excludeUndefined);
+ result = result.concat(flat);
+ } else if (!excludeUndefined || el != undefined) {
+ result.push(el);
+ }
+ }
+ return result;
+};
+
+if (!Array.prototype.flatten) {
+ Array.prototype.flatten = function(excludeUndefined) {
+ return Array.flatten(this, excludeUndefined);
+ }
+}
+
+String.prototype.toArray = function() {
+ var results = [];
+ for (var i = 0; i < this.length; i++)
+ results.push(this.charAt(i));
+ return results;
+}
+
+/*--------------------------------------------------------------------------*/
+
+var Builder = {
+ node: function(elementName) {
+ var element = document.createElement('div');
+ element.innerHTML =
+ "<" + elementName + "></" + elementName + ">";
+
+ // attributes (or text)
+ if(arguments[1])
+ if(this._isStringOrNumber(arguments[1]) ||
+ (arguments[1] instanceof Array)) {
+ this._children(element.firstChild, arguments[1]);
+ } else {
+ var attrs = this._attributes(arguments[1]);
+ if(attrs.length)
+ element.innerHTML = "<" +elementName + " " +
+ attrs + "></" + elementName + ">";
+ }
+
+ // text, or array of children
+ if(arguments[2])
+ this._children(element.firstChild, arguments[2]);
+
+ return element.firstChild;
+ },
+ _text: function(text) {
+ return document.createTextNode(text);
+ },
+ _attributes: function(attributes) {
+ var attrs = [];
+ for(attribute in attributes)
+ attrs.push((attribute=='className' ? 'class' : attribute) +
+ '="' + attributes[attribute].toString().escapeHTML() + '"');
+ return attrs.join(" ");
+ },
+ _children: function(element, children) {
+ if(typeof children=='object') { // array can hold nodes and text
+ children.flatten().each( function(e) {
+ if(typeof e=='object')
+ element.appendChild(e)
+ else
+ if(Builder._isStringOrNumber(e))
+ element.appendChild(Builder._text(e));
+ });
+ } else
+ if(Builder._isStringOrNumber(children))
+ element.appendChild(Builder._text(children));
+ },
+ _isStringOrNumber: function(param) {
+ return(typeof param=='string' || typeof param=='number');
+ }
+}
+
+/* ------------- element ext -------------- */
+
+// adapted from http://dhtmlkitchen.com/learn/js/setstyle/index4.jsp
+// note: Safari return null on elements with display:none; see http://bugzilla.opendarwin.org/show_bug.cgi?id=4125
+// instead of "auto" values returns null so it's easier to use with || constructs
+
+String.prototype.camelize = function() {
+ var oStringList = this.split('-');
+ if(oStringList.length == 1)
+ return oStringList[0];
+ var ret = this.indexOf("-") == 0 ?
+ oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) : oStringList[0];
+ for(var i = 1, len = oStringList.length; i < len; i++){
+ var s = oStringList[i];
+ ret += s.charAt(0).toUpperCase() + s.substring(1)
+ }
+ return ret;
+}
+
+Element.getStyle = function(element, style) {
+ element = $(element);
+ var value = element.style[style.camelize()];
+ if(!value)
+ if(document.defaultView && document.defaultView.getComputedStyle) {
+ var css = document.defaultView.getComputedStyle(element, null);
+ value = (css!=null) ? css.getPropertyValue(style) : null;
+ } else if(element.currentStyle) {
+ value = element.currentStyle[style.camelize()];
+ }
+
+ // If top, left, bottom, or right values have been queried, return "auto" for consistency resaons
+ // if position is "static", as Opera (and others?) returns the pixel values relative to root element
+ // (or positioning context?)
+ if (window.opera && (style == "left" || style == "top" || style == "right" || style == "bottom"))
+ if (Element.getStyle(element, "position") == "static") value = "auto";
+
+ if(value=='auto') value = null;
+ return value;
+}
+
+// converts rgb() and #xxx to #xxxxxx format,
+// returns self (or first argument) if not convertable
+String.prototype.parseColor = function() {
+ color = "#";
+ if(this.slice(0,4) == "rgb(") {
+ var cols = this.slice(4,this.length-1).split(',');
+ var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
+ } else {
+ if(this.slice(0,1) == '#') {
+ if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
+ if(this.length==7) color = this.toLowerCase();
+ }
+ }
+ return(color.length==7 ? color : (arguments[0] || this));
+}
+
+Element.makePositioned = function(element) {
+ element = $(element);
+ var pos = Element.getStyle(element, 'position');
+ if(pos =='static' || !pos) {
+ element._madePositioned = true;
+ element.style.position = "relative";
+ // Opera returns the offset relative to the positioning context, when an element is position relative
+ // but top and left have not been defined
+ if (window.opera){
+ element.style.top = 0;
+ element.style.left = 0;
+ }
+ }
+}
+
+Element.undoPositioned = function(element) {
+ element = $(element);
+ if(typeof element._madePositioned != "undefined"){
+ element._madePositioned = undefined;
+ element.style.position = "";
+ element.style.top = "";
+ element.style.left = "";
+ element.style.bottom = "";
+ element.style.right = "";
+ }
+}
+
+Element.makeClipping = function(element) {
+ element = $(element);
+ if (typeof element._overflow != 'undefined') return;
+ element._overflow = element.style.overflow;
+ if((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') element.style.overflow = 'hidden';
+}
+
+Element.undoClipping = function(element) {
+ element = $(element);
+ if (typeof element._overflow == 'undefined') return;
+ element.style.overflow = element._overflow;
+ element._overflow = undefined;
+}
+
+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);
+ }
+ }
+
+ return text;
+}
+
+Element.setContentZoom = function(element, percent) {
+ element = $(element);
+ element.style.fontSize = (percent/100) + "em";
+ if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
+}
+
+Element.getOpacity = function(element){
+ return parseFloat(Element.getStyle(element, "opacity") || '1');
+}
+
+Element.setOpacity = function(element, value){
+ element= $(element);
+ var els = element.style;
+ if (value == 1){
+ els.opacity = '0.999999';
+ els.filter = null;
+ } else {
+ if(value < 0.00001) value = 0;
+ els.opacity = value;
+ els.filter = "alpha(opacity:"+value*100+")";
+ }
+}
+
+Element.getInlineOpacity = function(element){
+ element= $(element);
+ var op;
+ op = element.style.opacity;
+ if (typeof op != "undefined" && op != "") return op;
+ return "";
+}
+
+Element.setInlineOpacity = function(element, value){
+ element= $(element);
+ var els = element.style;
+ els.opacity = value;
+}
+
+Element.getDimensions = function(element){
+ element = $(element);
+ // All *Width and *Height properties give 0 on elements with display "none", so enable the element temporarily
+ if (element.style.display == "none"){
+ var originalVisibility = element.style.visibility;
+ var originalPosition = element.style.position;
+ element.style.visibility = "hidden";
+ element.style.position = "absolute";
+ element.style.display = "";
+ var originalWidth = element.clientWidth;
+ var originalHeight = element.clientHeight;
+ element.style.display = "none";
+ element.style.position = originalPosition;
+ element.style.visibility = originalVisibility;
+ return {width: originalWidth, height: originalHeight};
+ } else {
+ return {width: element.offsetWidth, height: element.offsetHeight};
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+Position.positionedOffset = function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+ element = element.offsetParent;
+ if (element) {
+ p = Element.getStyle(element,'position');
+ if(p == 'relative' || p == 'absolute') break;
+ }
+ } while (element);
+ return [valueL, valueT];
+}
+
+// Safari returns margins on body which is incorrect if the child is absolutely positioned.
+// for performance reasons, we create a specialized version of Position.cumulativeOffset for
+// KHTML/WebKit only
+
+if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
+ Position.cumulativeOffset = function(element) {
+ var valueT = 0, valueL = 0;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+
+ if (element.offsetParent==document.body)
+ if (Element.getStyle(element,'position')=='absolute') break;
+
+ element = element.offsetParent;
+ } while (element);
+ return [valueL, valueT];
+ }
+}
+
+Position.page = function(forElement) {
+ var valueT = 0, valueL = 0;
+
+ var element = forElement;
+ do {
+ valueT += element.offsetTop || 0;
+ valueL += element.offsetLeft || 0;
+
+ // Safari fix
+ if (element.offsetParent==document.body)
+ if (Element.getStyle(element,'position')=='absolute') break;
+
+ } while (element = element.offsetParent);
+
+ element = forElement;
+ do {
+ valueT -= element.scrollTop || 0;
+ valueL -= element.scrollLeft || 0;
+ } while (element = element.parentNode);
+
+ return [valueL, valueT];
+}
+
+// elements with display:none don't return an offsetParent,
+// fall back to manual calculation
+Position.offsetParent = function(element) {
+ if(element.offsetParent) return element.offsetParent;
+ if(element == document.body) return element;
+
+ while ((element = element.parentNode) && element != document.body)
+ if (Element.getStyle(element,'position')!='static')
+ return element;
+
+ return document.body;
+}
+
+Position.clone = function(source, target) {
+ var options = Object.extend({
+ setLeft: true,
+ setTop: true,
+ setWidth: true,
+ setHeight: true,
+ offsetTop: 0,
+ offsetLeft: 0
+ }, arguments[2] || {})
+
+ // find page position of source
+ source = $(source);
+ var p = Position.page(source);
+
+ // find coordinate system to use
+ target = $(target);
+ var delta = [0, 0];
+ var parent = null;
+ // delta [0,0] will do fine with position: fixed elements,
+ // position:absolute needs offsetParent deltas
+ if (Element.getStyle(target,'position') == 'absolute') {
+ parent = Position.offsetParent(target);
+ delta = Position.page(parent);
+ }
+
+ // correct by body offsets (fixes Safari)
+ if (parent==document.body) {
+ delta[0] -= document.body.offsetLeft;
+ delta[1] -= document.body.offsetTop;
+ }
+
+ // set position
+ if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + "px";
+ if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + "px";
+ if(options.setWidth) target.style.width = source.offsetWidth + "px";
+ if(options.setHeight) target.style.height = source.offsetHeight + "px";
+}
+
+Position.absolutize = function(element) {
+ element = $(element);
+ if(element.style.position=='absolute') return;
+ Position.prepare();
+
+ var offsets = Position.positionedOffset(element);
+ var top = offsets[1];
+ var left = offsets[0];
+ var width = element.clientWidth;
+ var height = element.clientHeight;
+
+ element._originalLeft = left - parseFloat(element.style.left || 0);
+ element._originalTop = top - parseFloat(element.style.top || 0);
+ element._originalWidth = element.style.width;
+ element._originalHeight = element.style.height;
+
+ element.style.position = 'absolute';
+ element.style.top = top + 'px';;
+ element.style.left = left + 'px';;
+ element.style.width = width + 'px';;
+ element.style.height = height + 'px';;
+}
+
+Position.relativize = function(element) {
+ element = $(element);
+ if(element.style.position=='relative') return;
+ Position.prepare();
+
+ element.style.position = 'relative';
+ var top = parseFloat(element.style.top || 0) - (element._originalTop || 0);
+ var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
+
+ element.style.top = top + 'px';
+ element.style.left = left + 'px';
+ element.style.height = element._originalHeight;
+ element.style.width = element._originalWidth;
+}
+
+/*--------------------------------------------------------------------------*/
+
+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("(^|\\s)" + arguments[i] + "(\\s|$)", '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++) {
+ if((typeof arguments[i] == 'object') &&
+ (arguments[i].constructor == Array)) {
+ for(var j = 0; j < arguments[i].length; j++) {
+ regEx = new RegExp("(^|\\s)" + arguments[i][j] + "(\\s|$)");
+ if(!regEx.test(element.className)) return false;
+ }
+ } else {
+ regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
+ 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("(^|\\s)" + arguments[i][j] + "(\\s|$)");
+ if(regEx.test(element.className)) return true;
+ }
+ } else {
+ regEx = new RegExp("(^|\\s)" + arguments[i] + "(\\s|$)");
+ 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]);
+
+ return elements;
+ }
+} \ No newline at end of file