diff options
-rw-r--r-- | actionpack/CHANGELOG | 8 | ||||
-rw-r--r-- | actionpack/lib/action_view/helpers/javascript_helper.rb | 7 | ||||
-rw-r--r-- | actionpack/lib/action_view/helpers/javascripts/controls.js | 323 | ||||
-rw-r--r-- | actionpack/lib/action_view/helpers/javascripts/dragdrop.js | 27 | ||||
-rw-r--r-- | actionpack/lib/action_view/helpers/javascripts/effects.js | 60 | ||||
-rw-r--r-- | actionpack/test/template/javascript_helper_test.rb | 6 | ||||
-rw-r--r-- | railties/html/javascripts/controls.js | 323 | ||||
-rw-r--r-- | railties/html/javascripts/dragdrop.js | 27 | ||||
-rw-r--r-- | railties/html/javascripts/effects.js | 60 |
9 files changed, 611 insertions, 230 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 90ac2053e1..c692a3d40a 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -12,7 +12,7 @@ * Added :handle as an option to sortable_element to restrict the drag handle to a given class #1642 [thejohnny] -* Added a bunch of script.aculo.us features #1644, #1677 [Thomas Fuchs] +* Added a bunch of script.aculo.us features #1644, #1677, #1695 [Thomas Fuchs] * Effect.ScrollTo, to smoothly scroll the page to an element * Better Firefox flickering handling on SlideUp/SlideDown * Removed a possible memory leak in IE with draggables @@ -22,7 +22,11 @@ * Removed circular references from element to prevent memory leaks (still not completely gone in IE) * Changes to class extension in effects.js * Make Effect.Highlight restore any previously set background color when finishing (makes effect work with CSS classes that set a background color) - + * Fixed myriads of memory leaks in IE and Gecko-based browsers [David Zülke] + * Added incremental and local autocompleting and loads of documentation to controls.js [Ivan Krstic] + * Extended the auto_complete_field helper to accept tokens option + * Changed object extension mechanism to favor Object.extend to make script.aculo.us easily adaptable to support 3rd party libs like IE7.js [David Zülke] + * Fixed that named routes didn't use the default values for action and possible other parameters #1534 [Nicholas Seckar] * Fixed JavascriptHelper#visual_effect to use camelize such that :blind_up will work #1639 [pelletierm@eastmedia.net] diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index 7dd74f20ff..7034b95000 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -358,8 +358,13 @@ module ActionView function << "'#{field_id}', " function << "'" + (options[:update] || "#{field_id}_auto_complete") + "', " function << "'#{url_for(options[:url])}'" - + js_options = {} + if options[:tokens] and options[:tokens].kind_of?(Array) + js_options[:tokens] = "['#{options[:tokens].join('\',\'')}']" + elsif options[:tokens] + js_options[:tokens] = "'#{options[:tokens]}'" if options[:tokens] + end js_options[:callback] = "function(element, value) { return #{options[:with]} }" if options[:with] js_options[:indicator] = "'#{options[:indicator]}'" if options[:indicator] function << (', ' + options_for_javascript(js_options) + ')') diff --git a/actionpack/lib/action_view/helpers/javascripts/controls.js b/actionpack/lib/action_view/helpers/javascripts/controls.js index 60618838a8..cece0a914b 100644 --- a/actionpack/lib/action_view/helpers/javascripts/controls.js +++ b/actionpack/lib/action_view/helpers/javascripts/controls.js @@ -1,4 +1,5 @@ // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -19,7 +20,6 @@ // 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 = ""; @@ -37,42 +37,70 @@ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { return text; } -Ajax.Autocompleter = Class.create(); -Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({ - initialize: function(element, update, url, options) { +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// 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 +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: new Array (',', '\n') } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + base_initialize: function(element, update, options) { this.element = $(element); this.update = $(update); this.has_focus = false; this.changed = false; this.active = false; this.index = 0; - this.entry_count = 0; - this.url = url; + this.entry_count = 0; - this.setOptions(options); - this.options.asynchronous = true; - this.options.onComplete = this.onComplete.bind(this) + if (this.setOptions) + this.setOptions(options); + else + this.options = {} + + this.options.tokens = this.options.tokens || new Array(); this.options.frequency = this.options.frequency || 0.4; this.options.min_chars = this.options.min_chars || 1; - this.options.method = 'post'; - - this.options.onShow = this.options.onShow || - function(element, update){ - if(!update.style.position || update.style.position=='absolute') { - update.style.position = 'absolute'; + 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'; - } - new Effect.Appear(update,{duration:0.3}); - }; + } + new Effect.Appear(update,{duration:0.15}); + }; this.options.onHide = this.options.onHide || - function(element, update){ new Effect.Fade(update,{duration:0.3}) }; - + 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; @@ -81,14 +109,14 @@ Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({ 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') { new Insertion.After(this.update, '<iframe id="' + this.update.id + '_iefix" '+ - 'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(apacity=0);" ' + - 'src="javascript:;" frameborder="0" scrolling="no"></iframe>'); + 'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + + 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); this.iefix = $(this.update.id+'_iefix'); } if(this.iefix) { @@ -111,51 +139,7 @@ Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({ stopIndicator: function() { if(this.indicator) Element.hide(this.indicator); }, - - onObserverEvent: function() { - this.changed = false; - if(this.element.value.length>=this.options.min_chars) { - this.startIndicator(); - this.options.parameters = this.options.callback ? - this.options.callback(this.element, Form.Element.getValue(this.element)) : - Form.Element.serialize(this.element); - new Ajax.Request(this.url, this.options); - } else { - this.active = false; - this.hide(); - } - }, - - addObservers: function(element) { - Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); - Event.observe(element, "click", this.onClick.bindAsEventListener(this)); - }, - - onComplete: function(request) { - if(!this.changed && this.has_focus) { - this.update.innerHTML = request.responseText; - Element.cleanWhitespace(this.update); - Element.cleanWhitespace(this.update.firstChild); - if(this.update.firstChild && this.update.firstChild.childNodes) { - this.entry_count = - this.update.firstChild.childNodes.length; - for (var i = 0; i < this.entry_count; i++) { - entry = this.get_entry(i); - entry.autocompleteIndex = i; - this.addObservers(entry); - } - } else { - this.entry_count = 0; - } - - this.stopIndicator(); - - this.index = 0; - this.render(); - } - }, - onKeyPress: function(event) { if(this.active) switch(event.keyCode) { @@ -255,7 +239,208 @@ Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({ select_entry: function() { this.active = false; value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML(); - this.element.value = value; + this.updateElement(value); this.element.focus(); + }, + + 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+/); + if (whitespace) + new_value += whitespace[0]; + this.element.value = new_value + value; + } else { + this.element.value = value; + } + }, + + updateChoices: function(choices) { + if(!this.changed && this.has_focus) { + 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.update.firstChild.childNodes.length; + for (var i = 0; i < this.entry_count; i++) { + entry = this.get_entry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entry_count = 0; + } + + this.stopIndicator(); + + this.index = 0; + this.render(); + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getEntry().length>=this.options.min_chars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getEntry: function() { + var token_pos = this.findLastToken(); + if (token_pos != -1) + var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var last_token_pos = -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; + } + return last_token_pos; + } +} + +Ajax.Autocompleter = Class.create(); +Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(), +Object.extend(new Ajax.Base(), { + initialize: function(element, update, url, options) { + this.base_initialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this) + this.options.method = 'post'; + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.element.name) + '=' + + encodeURIComponent(this.getEntry()); + + 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 +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partial_search - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// 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). +// +// - full_search - Search anywhere in autocomplete array strings. +// +// - partial_chars - How many characters to enter before triggering +// a partial match (unlike min_chars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignore_case - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +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.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partial_search: true, + partial_chars: 2, + ignore_case: true, + full_search: false, + selector: function(instance) { + var ret = new Array(); // Beginning matches + var partial = new Array(); // Inside matches + var entry = instance.getEntry(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + var elem = instance.options.array[i]; + var found_pos = instance.options.ignore_case ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (found_pos != -1) { + if (found_pos == 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>"); + break; + } + } + + found_pos = instance.options.ignore_case ? + elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) : + elem.indexOf(entry, found_pos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "<ul>" + ret.join('') + "</ul>"; + } + }, options || {}); } -});
\ No newline at end of file +}); diff --git a/actionpack/lib/action_view/helpers/javascripts/dragdrop.js b/actionpack/lib/action_view/helpers/javascripts/dragdrop.js index 2bf84f1cb1..c0fd1d1e53 100644 --- a/actionpack/lib/action_view/helpers/javascripts/dragdrop.js +++ b/actionpack/lib/action_view/helpers/javascripts/dragdrop.js @@ -120,10 +120,10 @@ var Droppables = { add: function(element) { var element = $(element); - var options = { + var options = Object.extend({ greedy: true, hoverclass: null - }.extend(arguments[1] || {}); + }, arguments[1] || {}); // cache containers if(options.containment) { @@ -220,9 +220,9 @@ Draggables = { addObserver: function(observer) { this.observers.push(observer); }, - removeObserver: function(observer) { + removeObserver: function(element) { // element instead of obsever fixes mem leaks for(var i = 0; i < this.observers.length; i++) - if(this.observers[i] = observer) + if(this.observers[i].element && (this.observers[i].element == element)) this.observers.splice(i,1); }, notify: function(eventName, draggable) { // 'onStart', 'onEnd' @@ -236,7 +236,7 @@ Draggables = { Draggable = Class.create(); Draggable.prototype = { initialize: function(element) { - var options = { + var options = Object.extend({ handle: false, starteffect: function(element) { new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); @@ -249,7 +249,7 @@ Draggable.prototype = { }, zindex: 1000, revert: false - }.extend(arguments[1] || {}); + }, arguments[1] || {}); this.element = $(element); this.handle = options.handle ? $(options.handle) : this.element; @@ -411,7 +411,7 @@ Sortable = { for(var i=0;i<this.sortables.length;i++) { if(this.sortables[i].element == element) { var s = this.sortables[i]; - Draggables.removeObserver(s.observer); + 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++) @@ -422,7 +422,7 @@ Sortable = { }, create: function(element) { var element = $(element); - var options = { + var options = Object.extend({ element: element, tag: 'li', // assumes li children, override with tag: 'tagname' overlap: 'vertical', // one of 'vertical', 'horizontal' @@ -433,7 +433,7 @@ Sortable = { hoverclass: null, onChange: function() {}, onUpdate: function() {} - }.extend(arguments[1] || {}); + }, arguments[1] || {}); // clear any old sortable with same element this.destroy(element); @@ -499,7 +499,7 @@ Sortable = { var handle = options.handle ? Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i]; - options.draggables.push(new Draggable(elements[i], options_for_draggable.extend({ handle: handle }))); + 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]); @@ -510,18 +510,17 @@ Sortable = { this.sortables.push(options); // for onupdate - options.observer = new SortableObserver(element, options.onUpdate); - Draggables.addObserver(options.observer); + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); }, serialize: function(element) { var element = $(element); var sortableOptions = this.options(element); - var options = { + var options = Object.extend({ tag: sortableOptions.tag, only: sortableOptions.only, name: element.id - }.extend(arguments[1] || {}); + }, arguments[1] || {}); var items = $(element).childNodes; var queryComponents = new Array(); diff --git a/actionpack/lib/action_view/helpers/javascripts/effects.js b/actionpack/lib/action_view/helpers/javascripts/effects.js index abc706673b..a8735f507c 100644 --- a/actionpack/lib/action_view/helpers/javascripts/effects.js +++ b/actionpack/lib/action_view/helpers/javascripts/effects.js @@ -80,14 +80,14 @@ Element.undoClipping = function(element) { Effect.Base = function() {}; Effect.Base.prototype = { setOptions: function(options) { - this.options = { + this.options = Object.extend({ transition: Effect.Transitions.sinoidal, duration: 1.0, // seconds fps: 25.0, // max. 100fps sync: false, // true for combining from: 0.0, to: 1.0 - }.extend(options || {}); + }, options || {}); }, start: function(options) { this.setOptions(options || {}); @@ -127,7 +127,7 @@ Effect.Base.prototype = { } Effect.Parallel = Class.create(); -Effect.Parallel.prototype.extend(Effect.Base.prototype).extend({ +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { initialize: function(effects) { this.effects = effects || []; this.start(arguments[1]); @@ -146,13 +146,13 @@ Effect.Parallel.prototype.extend(Effect.Base.prototype).extend({ // a 'layout', meaning having a given width or height. // There is no way to safely set this automatically. Effect.Opacity = Class.create(); -Effect.Opacity.prototype.extend(Effect.Base.prototype).extend({ +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); - options = { + options = Object.extend({ from: 0.0, to: 1.0 - }.extend(arguments[1] || {}); + }, arguments[1] || {}); this.start(options); }, update: function(position) { @@ -166,7 +166,7 @@ Effect.Opacity.prototype.extend(Effect.Base.prototype).extend({ }); Effect.MoveBy = Class.create(); -Effect.MoveBy.prototype.extend(Effect.Base.prototype).extend({ +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'); @@ -188,17 +188,17 @@ Effect.MoveBy.prototype.extend(Effect.Base.prototype).extend({ }); Effect.Scale = Class.create(); -Effect.Scale.prototype.extend(Effect.Base.prototype).extend({ +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { initialize: function(element, percent) { this.element = $(element) - options = { + options = Object.extend({ scaleX: true, scaleY: true, scaleContent: true, scaleFromCenter: false, scaleMode: 'box', // 'box' or 'contents' or {} with provided values scaleFrom: 100.0 - }.extend(arguments[2] || {}); + }, arguments[2] || {}); this.originalTop = this.element.offsetTop; this.originalLeft = this.element.offsetLeft; if(this.element.style.fontSize=="") this.sizeEm = 1.0; @@ -246,7 +246,7 @@ Effect.Scale.prototype.extend(Effect.Base.prototype).extend({ }); Effect.Highlight = Class.create(); -Effect.Highlight.prototype.extend(Effect.Base.prototype).extend({ +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); @@ -259,11 +259,11 @@ Effect.Highlight.prototype.extend(Effect.Base.prototype).extend({ var cols = current.slice(4,current.length-1).split(','); var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3); } - var options = { + var options = Object.extend({ startcolor: "#ffff99", endcolor: endcolor, restorecolor: current - }.extend(arguments[1] || {}); + }, arguments[1] || {}); // init color calculations this.colors_base = [ @@ -291,7 +291,7 @@ Effect.Highlight.prototype.extend(Effect.Base.prototype).extend({ }); Effect.ScrollTo = Class.create(); -Effect.ScrollTo.prototype.extend(Effect.Base.prototype).extend({ +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); Position.prepare(); @@ -315,18 +315,18 @@ Effect.ScrollTo.prototype.extend(Effect.Base.prototype).extend({ /* ------------- prepackaged effects ------------- */ Effect.Fade = function(element) { - options = { + options = Object.extend({ from: 1.0, to: 0.0, afterFinish: function(effect) { Element.hide(effect.element); effect.setOpacity(1); } - }.extend(arguments[1] || {}); + }, arguments[1] || {}); new Effect.Opacity(element,options); } Effect.Appear = function(element) { - options = { + options = Object.extend({ from: 0.0, to: 1.0, beforeStart: function(effect) @@ -334,7 +334,7 @@ Effect.Appear = function(element) { Element.show(effect.element); }, afterUpdate: function(effect) { Element.show(effect.element); } - }.extend(arguments[1] || {}); + }, arguments[1] || {}); new Effect.Opacity(element,options); } @@ -354,14 +354,14 @@ Effect.Puff = function(element) { Effect.BlindUp = function(element) { Element.makeClipping(element); new Effect.Scale(element, 0, - { scaleContent: false, + Object.extend({ scaleContent: false, scaleX: false, afterFinish: function(effect) { Element.hide(effect.element); Element.undoClipping(effect.element); } - }.extend(arguments[1] || {}) + }, arguments[1] || {}) ); } @@ -370,14 +370,14 @@ Effect.BlindDown = function(element) { Element.makeClipping(element); Element.show(element); new Effect.Scale(element, 100, - { scaleContent: false, + Object.extend({ scaleContent: false, scaleX: false, scaleMode: 'contents', scaleFrom: 0, afterFinish: function(effect) { Element.undoClipping(effect.element); } - }.extend(arguments[1] || {}) + }, arguments[1] || {}) ); } @@ -433,7 +433,7 @@ Effect.SlideDown = function(element) { Element.makePositioned(element.firstChild); Element.show(element); new Effect.Scale(element, 100, - { scaleContent: false, + Object.extend({ scaleContent: false, scaleX: false, scaleMode: 'contents', scaleFrom: 0, @@ -442,7 +442,7 @@ Effect.SlideDown = function(element) { (effect.originalHeight - effect.element.clientHeight) + 'px'; }, afterFinish: function(effect) { Element.undoClipping(effect.element); } - }.extend(arguments[1] || {}) + }, arguments[1] || {}) ); } @@ -453,7 +453,7 @@ Effect.SlideUp = function(element) { Element.makePositioned(element.firstChild); Element.show(element); new Effect.Scale(element, 0, - { scaleContent: false, + Object.extend({ scaleContent: false, scaleX: false, afterUpdate: function(effect) { effect.element.firstChild.style.bottom = @@ -463,7 +463,7 @@ Effect.SlideUp = function(element) { Element.hide(effect.element); Element.undoClipping(effect.element); } - }.extend(arguments[1] || {}) + }, arguments[1] || {}) ); } @@ -582,14 +582,14 @@ Effect.Pulsate = function(element) { var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; reverser.bind(transition); new Effect.Opacity(element, - { duration: 3.0, + Object.extend(Object.extend({ duration: 3.0, afterFinish: function(effect) { Element.show(effect.element); } - }.extend(options).extend({transition: reverser})); + }, options), {transition: reverser})); } Effect.Fold = function(element) { $(element).style.overflow = 'hidden'; - new Effect.Scale(element, 5, { + new Effect.Scale(element, 5, Object.extend({ scaleContent: false, scaleTo: 100, scaleX: false, @@ -599,7 +599,7 @@ Effect.Fold = function(element) { scaleTo: 0, scaleY: false, afterFinish: function(effect) { Element.hide(effect.element) } }); - }}.extend(arguments[1] || {})); + }}, arguments[1] || {})); } // old: new Effect.ContentZoom(element, percent) diff --git a/actionpack/test/template/javascript_helper_test.rb b/actionpack/test/template/javascript_helper_test.rb index b98115b652..6cd690e268 100644 --- a/actionpack/test/template/javascript_helper_test.rb +++ b/actionpack/test/template/javascript_helper_test.rb @@ -74,7 +74,11 @@ class JavaScriptHelperTest < Test::Unit::TestCase def test_auto_complete_field assert_equal %(<script type=\"text/javascript\">new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {})</script>), - auto_complete_field("some_input", :url => { :action => "autocomplete" }); + auto_complete_field("some_input", :url => { :action => "autocomplete" }); + assert_equal %(<script type=\"text/javascript\">new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {tokens:','})</script>), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => ','); + assert_equal %(<script type=\"text/javascript\">new Ajax.Autocompleter('some_input', 'some_input_auto_complete', 'http://www.example.com/autocomplete', {tokens:[',']})</script>), + auto_complete_field("some_input", :url => { :action => "autocomplete" }, :tokens => [',']); end def test_auto_complete_result diff --git a/railties/html/javascripts/controls.js b/railties/html/javascripts/controls.js index 60618838a8..cece0a914b 100644 --- a/railties/html/javascripts/controls.js +++ b/railties/html/javascripts/controls.js @@ -1,4 +1,5 @@ // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the @@ -19,7 +20,6 @@ // 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 = ""; @@ -37,42 +37,70 @@ Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { return text; } -Ajax.Autocompleter = Class.create(); -Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({ - initialize: function(element, update, url, options) { +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// 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 +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: new Array (',', '\n') } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + base_initialize: function(element, update, options) { this.element = $(element); this.update = $(update); this.has_focus = false; this.changed = false; this.active = false; this.index = 0; - this.entry_count = 0; - this.url = url; + this.entry_count = 0; - this.setOptions(options); - this.options.asynchronous = true; - this.options.onComplete = this.onComplete.bind(this) + if (this.setOptions) + this.setOptions(options); + else + this.options = {} + + this.options.tokens = this.options.tokens || new Array(); this.options.frequency = this.options.frequency || 0.4; this.options.min_chars = this.options.min_chars || 1; - this.options.method = 'post'; - - this.options.onShow = this.options.onShow || - function(element, update){ - if(!update.style.position || update.style.position=='absolute') { - update.style.position = 'absolute'; + 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'; - } - new Effect.Appear(update,{duration:0.3}); - }; + } + new Effect.Appear(update,{duration:0.15}); + }; this.options.onHide = this.options.onHide || - function(element, update){ new Effect.Fade(update,{duration:0.3}) }; - + 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; @@ -81,14 +109,14 @@ Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({ 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') { new Insertion.After(this.update, '<iframe id="' + this.update.id + '_iefix" '+ - 'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(apacity=0);" ' + - 'src="javascript:;" frameborder="0" scrolling="no"></iframe>'); + 'style="display:none;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' + + 'src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); this.iefix = $(this.update.id+'_iefix'); } if(this.iefix) { @@ -111,51 +139,7 @@ Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({ stopIndicator: function() { if(this.indicator) Element.hide(this.indicator); }, - - onObserverEvent: function() { - this.changed = false; - if(this.element.value.length>=this.options.min_chars) { - this.startIndicator(); - this.options.parameters = this.options.callback ? - this.options.callback(this.element, Form.Element.getValue(this.element)) : - Form.Element.serialize(this.element); - new Ajax.Request(this.url, this.options); - } else { - this.active = false; - this.hide(); - } - }, - - addObservers: function(element) { - Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); - Event.observe(element, "click", this.onClick.bindAsEventListener(this)); - }, - - onComplete: function(request) { - if(!this.changed && this.has_focus) { - this.update.innerHTML = request.responseText; - Element.cleanWhitespace(this.update); - Element.cleanWhitespace(this.update.firstChild); - if(this.update.firstChild && this.update.firstChild.childNodes) { - this.entry_count = - this.update.firstChild.childNodes.length; - for (var i = 0; i < this.entry_count; i++) { - entry = this.get_entry(i); - entry.autocompleteIndex = i; - this.addObservers(entry); - } - } else { - this.entry_count = 0; - } - - this.stopIndicator(); - - this.index = 0; - this.render(); - } - }, - onKeyPress: function(event) { if(this.active) switch(event.keyCode) { @@ -255,7 +239,208 @@ Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({ select_entry: function() { this.active = false; value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML(); - this.element.value = value; + this.updateElement(value); this.element.focus(); + }, + + 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+/); + if (whitespace) + new_value += whitespace[0]; + this.element.value = new_value + value; + } else { + this.element.value = value; + } + }, + + updateChoices: function(choices) { + if(!this.changed && this.has_focus) { + 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.update.firstChild.childNodes.length; + for (var i = 0; i < this.entry_count; i++) { + entry = this.get_entry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entry_count = 0; + } + + this.stopIndicator(); + + this.index = 0; + this.render(); + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getEntry().length>=this.options.min_chars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getEntry: function() { + var token_pos = this.findLastToken(); + if (token_pos != -1) + var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var last_token_pos = -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; + } + return last_token_pos; + } +} + +Ajax.Autocompleter = Class.create(); +Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(), +Object.extend(new Ajax.Base(), { + initialize: function(element, update, url, options) { + this.base_initialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this) + this.options.method = 'post'; + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.element.name) + '=' + + encodeURIComponent(this.getEntry()); + + 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 +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partial_search - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// 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). +// +// - full_search - Search anywhere in autocomplete array strings. +// +// - partial_chars - How many characters to enter before triggering +// a partial match (unlike min_chars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignore_case - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +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.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partial_search: true, + partial_chars: 2, + ignore_case: true, + full_search: false, + selector: function(instance) { + var ret = new Array(); // Beginning matches + var partial = new Array(); // Inside matches + var entry = instance.getEntry(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + var elem = instance.options.array[i]; + var found_pos = instance.options.ignore_case ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (found_pos != -1) { + if (found_pos == 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>"); + break; + } + } + + found_pos = instance.options.ignore_case ? + elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) : + elem.indexOf(entry, found_pos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + return "<ul>" + ret.join('') + "</ul>"; + } + }, options || {}); } -});
\ No newline at end of file +}); diff --git a/railties/html/javascripts/dragdrop.js b/railties/html/javascripts/dragdrop.js index 2bf84f1cb1..c0fd1d1e53 100644 --- a/railties/html/javascripts/dragdrop.js +++ b/railties/html/javascripts/dragdrop.js @@ -120,10 +120,10 @@ var Droppables = { add: function(element) { var element = $(element); - var options = { + var options = Object.extend({ greedy: true, hoverclass: null - }.extend(arguments[1] || {}); + }, arguments[1] || {}); // cache containers if(options.containment) { @@ -220,9 +220,9 @@ Draggables = { addObserver: function(observer) { this.observers.push(observer); }, - removeObserver: function(observer) { + removeObserver: function(element) { // element instead of obsever fixes mem leaks for(var i = 0; i < this.observers.length; i++) - if(this.observers[i] = observer) + if(this.observers[i].element && (this.observers[i].element == element)) this.observers.splice(i,1); }, notify: function(eventName, draggable) { // 'onStart', 'onEnd' @@ -236,7 +236,7 @@ Draggables = { Draggable = Class.create(); Draggable.prototype = { initialize: function(element) { - var options = { + var options = Object.extend({ handle: false, starteffect: function(element) { new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7}); @@ -249,7 +249,7 @@ Draggable.prototype = { }, zindex: 1000, revert: false - }.extend(arguments[1] || {}); + }, arguments[1] || {}); this.element = $(element); this.handle = options.handle ? $(options.handle) : this.element; @@ -411,7 +411,7 @@ Sortable = { for(var i=0;i<this.sortables.length;i++) { if(this.sortables[i].element == element) { var s = this.sortables[i]; - Draggables.removeObserver(s.observer); + 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++) @@ -422,7 +422,7 @@ Sortable = { }, create: function(element) { var element = $(element); - var options = { + var options = Object.extend({ element: element, tag: 'li', // assumes li children, override with tag: 'tagname' overlap: 'vertical', // one of 'vertical', 'horizontal' @@ -433,7 +433,7 @@ Sortable = { hoverclass: null, onChange: function() {}, onUpdate: function() {} - }.extend(arguments[1] || {}); + }, arguments[1] || {}); // clear any old sortable with same element this.destroy(element); @@ -499,7 +499,7 @@ Sortable = { var handle = options.handle ? Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i]; - options.draggables.push(new Draggable(elements[i], options_for_draggable.extend({ handle: handle }))); + 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]); @@ -510,18 +510,17 @@ Sortable = { this.sortables.push(options); // for onupdate - options.observer = new SortableObserver(element, options.onUpdate); - Draggables.addObserver(options.observer); + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); }, serialize: function(element) { var element = $(element); var sortableOptions = this.options(element); - var options = { + var options = Object.extend({ tag: sortableOptions.tag, only: sortableOptions.only, name: element.id - }.extend(arguments[1] || {}); + }, arguments[1] || {}); var items = $(element).childNodes; var queryComponents = new Array(); diff --git a/railties/html/javascripts/effects.js b/railties/html/javascripts/effects.js index abc706673b..a8735f507c 100644 --- a/railties/html/javascripts/effects.js +++ b/railties/html/javascripts/effects.js @@ -80,14 +80,14 @@ Element.undoClipping = function(element) { Effect.Base = function() {}; Effect.Base.prototype = { setOptions: function(options) { - this.options = { + this.options = Object.extend({ transition: Effect.Transitions.sinoidal, duration: 1.0, // seconds fps: 25.0, // max. 100fps sync: false, // true for combining from: 0.0, to: 1.0 - }.extend(options || {}); + }, options || {}); }, start: function(options) { this.setOptions(options || {}); @@ -127,7 +127,7 @@ Effect.Base.prototype = { } Effect.Parallel = Class.create(); -Effect.Parallel.prototype.extend(Effect.Base.prototype).extend({ +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { initialize: function(effects) { this.effects = effects || []; this.start(arguments[1]); @@ -146,13 +146,13 @@ Effect.Parallel.prototype.extend(Effect.Base.prototype).extend({ // a 'layout', meaning having a given width or height. // There is no way to safely set this automatically. Effect.Opacity = Class.create(); -Effect.Opacity.prototype.extend(Effect.Base.prototype).extend({ +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); - options = { + options = Object.extend({ from: 0.0, to: 1.0 - }.extend(arguments[1] || {}); + }, arguments[1] || {}); this.start(options); }, update: function(position) { @@ -166,7 +166,7 @@ Effect.Opacity.prototype.extend(Effect.Base.prototype).extend({ }); Effect.MoveBy = Class.create(); -Effect.MoveBy.prototype.extend(Effect.Base.prototype).extend({ +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'); @@ -188,17 +188,17 @@ Effect.MoveBy.prototype.extend(Effect.Base.prototype).extend({ }); Effect.Scale = Class.create(); -Effect.Scale.prototype.extend(Effect.Base.prototype).extend({ +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { initialize: function(element, percent) { this.element = $(element) - options = { + options = Object.extend({ scaleX: true, scaleY: true, scaleContent: true, scaleFromCenter: false, scaleMode: 'box', // 'box' or 'contents' or {} with provided values scaleFrom: 100.0 - }.extend(arguments[2] || {}); + }, arguments[2] || {}); this.originalTop = this.element.offsetTop; this.originalLeft = this.element.offsetLeft; if(this.element.style.fontSize=="") this.sizeEm = 1.0; @@ -246,7 +246,7 @@ Effect.Scale.prototype.extend(Effect.Base.prototype).extend({ }); Effect.Highlight = Class.create(); -Effect.Highlight.prototype.extend(Effect.Base.prototype).extend({ +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); @@ -259,11 +259,11 @@ Effect.Highlight.prototype.extend(Effect.Base.prototype).extend({ var cols = current.slice(4,current.length-1).split(','); var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3); } - var options = { + var options = Object.extend({ startcolor: "#ffff99", endcolor: endcolor, restorecolor: current - }.extend(arguments[1] || {}); + }, arguments[1] || {}); // init color calculations this.colors_base = [ @@ -291,7 +291,7 @@ Effect.Highlight.prototype.extend(Effect.Base.prototype).extend({ }); Effect.ScrollTo = Class.create(); -Effect.ScrollTo.prototype.extend(Effect.Base.prototype).extend({ +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); Position.prepare(); @@ -315,18 +315,18 @@ Effect.ScrollTo.prototype.extend(Effect.Base.prototype).extend({ /* ------------- prepackaged effects ------------- */ Effect.Fade = function(element) { - options = { + options = Object.extend({ from: 1.0, to: 0.0, afterFinish: function(effect) { Element.hide(effect.element); effect.setOpacity(1); } - }.extend(arguments[1] || {}); + }, arguments[1] || {}); new Effect.Opacity(element,options); } Effect.Appear = function(element) { - options = { + options = Object.extend({ from: 0.0, to: 1.0, beforeStart: function(effect) @@ -334,7 +334,7 @@ Effect.Appear = function(element) { Element.show(effect.element); }, afterUpdate: function(effect) { Element.show(effect.element); } - }.extend(arguments[1] || {}); + }, arguments[1] || {}); new Effect.Opacity(element,options); } @@ -354,14 +354,14 @@ Effect.Puff = function(element) { Effect.BlindUp = function(element) { Element.makeClipping(element); new Effect.Scale(element, 0, - { scaleContent: false, + Object.extend({ scaleContent: false, scaleX: false, afterFinish: function(effect) { Element.hide(effect.element); Element.undoClipping(effect.element); } - }.extend(arguments[1] || {}) + }, arguments[1] || {}) ); } @@ -370,14 +370,14 @@ Effect.BlindDown = function(element) { Element.makeClipping(element); Element.show(element); new Effect.Scale(element, 100, - { scaleContent: false, + Object.extend({ scaleContent: false, scaleX: false, scaleMode: 'contents', scaleFrom: 0, afterFinish: function(effect) { Element.undoClipping(effect.element); } - }.extend(arguments[1] || {}) + }, arguments[1] || {}) ); } @@ -433,7 +433,7 @@ Effect.SlideDown = function(element) { Element.makePositioned(element.firstChild); Element.show(element); new Effect.Scale(element, 100, - { scaleContent: false, + Object.extend({ scaleContent: false, scaleX: false, scaleMode: 'contents', scaleFrom: 0, @@ -442,7 +442,7 @@ Effect.SlideDown = function(element) { (effect.originalHeight - effect.element.clientHeight) + 'px'; }, afterFinish: function(effect) { Element.undoClipping(effect.element); } - }.extend(arguments[1] || {}) + }, arguments[1] || {}) ); } @@ -453,7 +453,7 @@ Effect.SlideUp = function(element) { Element.makePositioned(element.firstChild); Element.show(element); new Effect.Scale(element, 0, - { scaleContent: false, + Object.extend({ scaleContent: false, scaleX: false, afterUpdate: function(effect) { effect.element.firstChild.style.bottom = @@ -463,7 +463,7 @@ Effect.SlideUp = function(element) { Element.hide(effect.element); Element.undoClipping(effect.element); } - }.extend(arguments[1] || {}) + }, arguments[1] || {}) ); } @@ -582,14 +582,14 @@ Effect.Pulsate = function(element) { var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; reverser.bind(transition); new Effect.Opacity(element, - { duration: 3.0, + Object.extend(Object.extend({ duration: 3.0, afterFinish: function(effect) { Element.show(effect.element); } - }.extend(options).extend({transition: reverser})); + }, options), {transition: reverser})); } Effect.Fold = function(element) { $(element).style.overflow = 'hidden'; - new Effect.Scale(element, 5, { + new Effect.Scale(element, 5, Object.extend({ scaleContent: false, scaleTo: 100, scaleX: false, @@ -599,7 +599,7 @@ Effect.Fold = function(element) { scaleTo: 0, scaleY: false, afterFinish: function(effect) { Element.hide(effect.element) } }); - }}.extend(arguments[1] || {})); + }}, arguments[1] || {})); } // old: new Effect.ContentZoom(element, percent) |