diff options
Diffstat (limited to 'actionpack/lib')
5 files changed, 1533 insertions, 188 deletions
diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index d5e2a8b2dc..31c15d222c 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -16,7 +16,8 @@ module ActionView # the use of form_remote_tag. module JavascriptHelper unless const_defined? :CALLBACKS - CALLBACKS = [:uninitialized, :loading, :loaded, :interactive, :complete] + CALLBACKS = [ :uninitialized, :loading, :loaded, :interactive, :complete ] + AJAX_OPTIONS = [ :url, :asynchronous, :method, :insertion, :form, :with ].concat(CALLBACKS) JAVASCRIPT_PATH = File.join(File.dirname(__FILE__), 'javascripts') end @@ -146,8 +147,10 @@ module ActionView return function end - # Includes the Action Pack Javascript library inside a single <script> - # tag. + # Includes the Action Pack Javascript libraries inside a single <script> + # tag. The function first includes prototype.js and then its core extensions, + # (determined by filenames starting with "prototype"). + # Afterwards, any additional scripts will be included in random order. # # Note: The recommended approach is to copy the contents of # lib/action_view/helpers/javascripts/ into your application's @@ -155,12 +158,22 @@ module ActionView # create remote <script> links. def define_javascript_functions javascript = '<script type="text/javascript">' - Dir.glob(File.join(JAVASCRIPT_PATH, '*')).each { |filename| javascript << "\n" << IO.read(filename) } + + # load prototype.js and its extensions first + prototype_libs = Dir.glob(File.join(JAVASCRIPT_PATH, 'prototype*')).sort.reverse + prototype_libs.each do |filename| + javascript << "\n" << IO.read(filename) + end + + # load other librairies + (Dir.glob(File.join(JAVASCRIPT_PATH, '*')) - prototype_libs).each do |filename| + javascript << "\n" << IO.read(filename) + end javascript << '</script>' end # Observes the field with the DOM ID specified by +field_id+ and makes - # an Ajax when its contents have changed. + # an Ajax call when its contents have changed. # # Required +options+ are: # <tt>:frequency</tt>:: The frequency (in seconds) at which changes to @@ -190,13 +203,111 @@ module ActionView def observe_form(form_id, options = {}) build_observer('Form.Observer', form_id, options) end + + + # Adds Ajax autocomplete functionality to the text input field with the + # DOM ID specified by +field_id+. + # + # This function expects that the called action returns a HTML <ul> list, + # or nothing if no entries should be displayed for autocompletion. + # + # Required +options+ are: + # <tt>:url</tt>:: Specifies the DOM ID of the element whose + # innerHTML should be updated with the autocomplete + # entries returned by XMLHttpRequest. + # + # Addtional +options+ are: + # <tt>:update</tt>:: Specifies the DOM ID of the element whose + # innerHTML should be updated with the autocomplete + # entries returned by the Ajax request. + # Defaults to field_id + '_autocomplete' + # <tt>:with</tt>:: A Javascript expression specifying the + # parameters for the XMLHttpRequest. This defaults + # to 'value', which in the evaluated context + # refers to the new field value. + # <tt>:indicator</tt>:: Specifies the DOM ID of an elment which will be + # displayed while autocomplete is running. + # + def remote_autocomplete(field_id, options = {}) + function = "new Ajax.Autocompleter(" + function << "'#{field_id}', " + function << "'" + (options[:update] || "#{field_id}_autocomplete") + "', " + function << "'#{url_for(options[:url])}'" + + js_options = {} + 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) + ')') + + javascript_tag(function) + end + + # Use this method in your view to generate a return for the Ajax automplete requests. + # + # Example Action: + # @items = Item.find(:all, :conditions => [ 'LOWER(description) LIKE ?', + # '%' + params[:for].downcase + '%' ], 'description ASC') + # render :layout => false + # + # Example View: + # <%= autocomplete_responder @items, 'description' %> + def autocomplete_responder(entries, field, phrase = nil) + "<ul>#{entries.map { |entry| '<li>' + (phrase ? highlight(entry[field],phrase) : h(entry[field])) + '</li>' }.join}</ul>" if entries + end + + # Returns a JavaScript snippet to be used on the Ajax callbacks for starting + # visual effects. + # + # Example: + # <%= link_to_remote "Reload", :update => "posts", + # :url => { :action => "reload" }, + # :complete => visual_effect(:highlight, "posts", :duration => 0.5 ) + # + # You can change the behaviour with various options, see + # http://script.aculo.us for more documentation. + def visual_effect(name, element_id, js_options = {}) + "new Effect.#{name.to_s.capitalize}('#{element_id}',#{options_for_javascript(js_options)});" + end + + # Makes the element with the DOM ID specified by +element_id+ sortable + # by drag-and-drop and make an Ajax call whenever the sort order has + # changed. By default, the action called gets the serialized sortable + # element as parameters. + # + # Example: + # <%= remote_sortable("my_list", :url => { :action => "order" }) %> + # + # In the example, the action gets a "my_list" array parameter + # containing the values of the ids of elements the sortable consists + # of, in the current order. + # + # You can change the behaviour with various options, see + # http://script.aculo.us for more documentation. + # + def remote_sortable(element_id, options = {}) + options[:with] ||= "Sortable.serialize('#{element_id}')" + options[:onUpdate] ||= "function(){" + remote_function(options) + "}" + options.delete_if { |key, value| AJAX_OPTIONS.include?(key) } + + javascript_tag("Sortable.create('#{element_id}', #{options_for_javascript(options)})") + end # Escape carrier returns and single and double quotes for Javascript segments. def escape_javascript(javascript) (javascript || '').gsub(/\r\n|\n|\r/, "\\n").gsub(/["']/) { |m| "\\#{m}" } end + # Returns a Javascript tag with the +content+ inside. Example: + # javascript_tag "alert('All is good')" # => <script type="text/javascript">alert('All is good')</script> + def javascript_tag(content) + content_tag("script", content, :type => "text/javascript") + end + private + def options_for_javascript(options) + '{' + options.map {|k, v| "#{k}:#{v}"}.join(', ') + '}' + end + def options_for_ajax(options) js_options = build_callbacks(options) @@ -210,7 +321,7 @@ module ActionView js_options['parameters'] = options[:with] end - '{' + js_options.map {|k, v| "#{k}:#{v}"}.join(', ') + '}' + options_for_javascript(js_options) end def method_option_to_s(method) diff --git a/actionpack/lib/action_view/helpers/javascripts/controls.js b/actionpack/lib/action_view/helpers/javascripts/controls.js new file mode 100644 index 0000000000..94d438a784 --- /dev/null +++ b/actionpack/lib/action_view/helpers/javascripts/controls.js @@ -0,0 +1,218 @@ +// 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. + +Ajax.Autocompleter = Class.create(); +Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({ + initialize: function(element, update, url, 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.setOptions(options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this) + this.options.frequency = this.options.frequency || 0.4; + this.options.min_chars = this.options.min_chars || 1; + this.options.method = 'post'; + + if(this.options.indicator) + this.indicator = $(this.options.indicator); + + this.observer = null; + + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + Event.observe(document, "click", this.onBlur.bindAsEventListener(this)); + }, + + show: function() { + Element.show(this.update); + if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0)) { + new Insertion.Before(this.update, + '<iframe id="' + this.update.id + '_iefix" style="display:none;" src="javascript:false;" frameborder="0" scrolling="no"></iframe>'); + this.iefix = $(this.update.id+'_iefix'); + this.iefix.style.position = 'absolute'; + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + } + if(this.iefix) { + Position.clone(this.update, this.iefix); + Element.show(this.iefix); + } + }, + + hide: function() { + if(this.iefix) Element.hide(this.iefix); + Element.hide(this.update); + }, + + startIndicator: function() { + if(this.indicator) Element.show(this.indicator); + }, + + 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.getValue(this.element); + new Ajax.Request(this.url, this.options); + } else { + this.active = false; + this.hide(); + } + }, + + onComplete: function(request) { + if(!this.changed) { + this.update.innerHTML = request.responseText; + + 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; + Event.observe(entry, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(entry, "click", this.onClick.bindAsEventListener(this)); + } + } else { + this.entry_count = 0; + } + + this.stopIndicator(); + + this.index = 0; + this.render(); + } + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.select_entry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.mark_previous(); + this.render(); + return; + case Event.KEY_DOWN: + this.mark_next(); + this.render(); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) + return; + + this.changed = true; + this.has_focus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + onHover: function(event) { + element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + }, + + onClick: function(event) { + element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.select_entry(); + }, + + onBlur: function(event) { + element = Event.element(event); + if(element==this.update) return; + while(element.parentNode) + { element = element.parentNode; if(element==this.update) return; } + this.hide(); + this.active = false; + }, + + render: function() { + if(this.entry_count > 0) { + for (var i = 0; i < this.entry_count; i++) + this.index==i ? + Element.Class.add(this.get_entry(i),"selected") : + Element.Class.remove(this.get_entry(i),"selected"); + + if(this.has_focus) { + if(this.get_current_entry().scrollIntoView) + this.get_current_entry().scrollIntoView(false); + + this.show(); + this.active = true; + } + } else this.hide(); + }, + + mark_previous: function() { + if(this.index > 0) this.index-- + else this.index = this.entry_count-1; + }, + + mark_next: function() { + if(this.index < this.entry_count-1) this.index++ + else this.index = 0; + }, + + get_entry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + get_current_entry: function() { + return this.get_entry(this.index); + }, + + select_entry: function() { + this.hide(); + this.active = false; + value = Text.decodeHTML(Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal')); + this.element.value = value; + this.element.focus(); + } +});
\ 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 new file mode 100644 index 0000000000..f9ed0b7898 --- /dev/null +++ b/actionpack/lib/action_view/helpers/javascripts/dragdrop.js @@ -0,0 +1,385 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// Element.Class part Copyright (c) 2005 by Rick Olson +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +/*--------------------------------------------------------------------------*/ + +var Droppables = { + drops: false, + include_scroll_offsets: false, + + add: function(element) { + var element = $(element); + var options = { + greedy: true, + hoverclass: null + }.extend(arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = new Array(); + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + for(var i=0; i<containment.length; i++) + options._containers.push($(containment[i])); + } else { + options._containers.push($(containment)); + } + options._containers_length = + options._containers.length-1; + } + + if(element.style.position=='') //fix IE + element.style.position = 'relative'; + + // activate the droppable + element.droppable = options; + + if(!this.drops) this.drops = []; + this.drops.push(element); + }, + + is_contained: function(element, drop) { + var containers = drop.droppable._containers; + var parentNode = element.parentNode; + var i = drop.droppable._containers_length; + do { if(parentNode==containers[i]) return true; } while (i--); + return false; + }, + + is_affected: function(pX, pY, element, drop) { + return ( + (drop!=element) && + ((!drop.droppable._containers) || + this.is_contained(element, drop)) && + ((!drop.droppable.accept) || + (Element.Class.has_any(element, drop.droppable.accept))) && + Position.within(drop, pX, pY) ); + }, + + deactivate: function(drop) { + Element.Class.remove(drop, drop.droppable.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(this.last_active) this.deactivate(this.last_active); + if(drop.droppable.hoverclass) { + Element.Class.add(drop, drop.droppable.hoverclass); + this.last_active = drop; + } + }, + + show: function(event, element) { + if(!this.drops) return; + var pX = Event.pointerX(event); + var pY = Event.pointerY(event); + if(this.include_scroll_offsets) Position.prepare(); + + var i = this.drops.length-1; do { + var drop = this.drops[i]; + if(this.is_affected(pX, pY, element, drop)) { + if(drop.droppable.onHover) + drop.droppable.onHover( + element, drop, Position.overlap(drop.droppable.overlap, drop)); + if(drop.droppable.greedy) { + this.activate(drop); + return; + } + } + } while (i--); + }, + + fire: function(event, element) { + if(!this.drops) return; + var pX = Event.pointerX(event); + var pY = Event.pointerY(event); + if(this.include_scroll_offsets) Position.prepare(); + + var i = this.drops.length-1; do { + var drop = this.drops[i]; + if(this.is_affected(pX, pY, element, drop)) + if(drop.droppable.onDrop) + drop.droppable.onDrop(element); + } while (i--); + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +} + +Draggables = { + observers: new Array(), + addObserver: function(observer) { + this.observers.push(observer); + }, + notify: function(eventName) { // 'onStart', 'onEnd' + for(var i = 0; i < this.observers.length; i++) + this.observers[i][eventName](); + } +} + +/*--------------------------------------------------------------------------*/ + +Draggable = Class.create(); +Draggable.prototype = { + initialize: function(element) { + var options = { + handle: false, + starteffect: function(element) { + new Effect2.Opacity(element, {duration:0.2, from:1.0, to:0.7}); + }, + reverteffect: function(element, top_offset, left_offset) { + new Effect2.MoveBy(element, -top_offset, -left_offset, {duration:0.4}); + }, + endeffect: function(element) { + new Effect2.Opacity(element, {duration:0.2, from:0.7, to:1.0}); + }, + zindex: 1000 + }.extend(arguments[1] || {}); + + this.element = $(element); + this.element.drag = this; + this.handle = options.handle ? $(options.handle) : this.element; + + this.offsetX = 0; + this.offsetY = 0; + this.originalLeft = this.currentLeft(); + this.originalTop = this.currentTop(); + this.originalX = this.element.offsetLeft; + this.originalY = this.element.offsetTop; + this.originalZ = parseInt(this.element.style.zIndex || "0"); + + this.options = options; + + this.active = false; + this.dragging = false; + + Event.observe(this.handle, "mousedown", this.startDrag.bindAsEventListener(this)); + Event.observe(document, "mouseup", this.endDrag.bindAsEventListener(this)); + Event.observe(document, "mousemove", this.update.bindAsEventListener(this)); + }, + currentLeft: function() { + return parseInt(this.element.style.left || '0'); + }, + currentTop: function() { + return parseInt(this.element.style.top || '0') + }, + startDrag: function(event) { + if(Event.isLeftClick(event)) { + this.active = true; + + var style = this.element.style; + this.originalY = this.element.offsetTop - this.currentTop(); - this.originalTop; + this.originalX = this.element.offsetLeft - this.currentLeft(); - this.originalLeft; + this.offsetY = event.clientY - this.originalY - this.originalTop; + this.offsetX = event.clientX - this.originalX - this.originalLeft; + + Event.stop(event); + } + }, + endDrag: function(event) { + if(this.active && this.dragging) { + this.active = false; + this.dragging = false; + + Droppables.fire(event, this.element); + Draggables.notify('onEnd'); + + if(this.options.revert && this.options.reverteffect) { + this.options.reverteffect(this.element, + this.currentTop()-this.originalTop, + this.currentLeft()-this.originalLeft); + } else { + 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(); + Event.stop(event); + } + this.active = false; + this.dragging = false; + }, + draw: function(event) { + 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"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = ((event.clientY - this.originalY) - this.offsetY) + "px"; + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + update: function(event) { + if(this.active) { + if(!this.dragging) { + var style = this.element.style; + this.dragging = true; + if(style.position=="") style.position = "relative"; + style.zIndex = this.options.zindex; + Draggables.notify('onStart'); + if(this.options.starteffect) this.options.starteffect(this.element); + } + + Droppables.show(event, this.element); + this.draw(event); + if(this.options.change) this.options.change(this); + + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + + Event.stop(event); + } + } +} + +/*--------------------------------------------------------------------------*/ + +SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + onEnd: function() { + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +} + +Sortable = { + create: function(element) { + var element = $(element); + var options = { + tag: 'li', // assumes li children, override with tag: 'tagname' + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + hoverclass: null, + onChange: function() {}, + onUpdate: function() {} + }.extend(arguments[1] || {}); + element.sortable = options; + + // build options for the draggables + var options_for_draggable = { + revert: true, + constraint: options.constraint, + handle: handle }; + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + 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); + } + } + } + } + + // fix for gecko engine + Element.cleanWhitespace(element); + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + // 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]; + + new Draggable(elements[i], options_for_draggable.extend({ handle: handle })); + Droppables.add(elements[i], options_for_droppable); + } + + }, + serialize: function(element) { + var element = $(element); + var options = { + tag: element.sortable.tag, + only: element.sortable.only, + name: element.id + }.extend(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("&"); + } +} + +/*--------------------------------------------------------------------------*/
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/javascripts/effects.js b/actionpack/lib/action_view/helpers/javascripts/effects.js new file mode 100644 index 0000000000..82f1fd53ed --- /dev/null +++ b/actionpack/lib/action_view/helpers/javascripts/effects.js @@ -0,0 +1,557 @@ +// 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/) +// +// 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. + + +Effect = {} +Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = {} + +Effect.Transitions.linear = function(pos) { + return pos; +} +Effect.Transitions.sinoidal = function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; +} +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); +} +Effect.Transitions.wobble = function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; +} +Effect.Transitions.pulse = function(pos) { + return (Math.floor(pos*10) % 2 == 0 ? + (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10))); +} + +Effect.Transitions.none = function(pos) { + return 0; +} +Effect.Transitions.full = function(pos) { + return 1; +} + +/* ------------- core effects ------------- */ + +Effect.Base = function() {}; +Effect.Base.prototype = { + setOptions: function(options) { + this.options = { + 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 || {}); + }, + start: function(options) { + this.setOptions(options || {}); + this.currentFrame = 0; + this.startOn = new Date().getTime(); + this.finishOn = this.startOn + (this.options.duration*1000); + if(this.options.beforeStart) this.options.beforeStart(this); + if(!this.options.sync) this.loop(); + }, + loop: function() { + 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; + } + pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + 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.options.transition) pos = this.options.transition(pos); + pos = pos * (this.options.to-this.options.from); + pos += this.options.from; + if(this.options.beforeUpdate) this.options.beforeUpdate(this); + if(this.update) this.update(pos); + if(this.options.afterUpdate) this.options.afterUpdate(this); + }, + cancel: function() { + if(this.timeout) clearTimeout(this.timeout); + } +} + +Effect.Parallel = Class.create(); + Effect.Parallel.prototype = (new Effect.Base()).extend({ + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + for (var i = 0; i < this.effects.length; i++) + this.effects[i].render(position); + }, + finish: function(position) { + for (var i = 0; i < this.effects.length; i++) + if(this.effects[i].finish) this.effects[i].finish(position); + } + }); + +Effect.Opacity = Class.create(); +Effect.Opacity.prototype = (new Effect.Base()).extend({ + initialize: function(element) { + this.element = $(element); + options = { + from: 0.0, + to: 1.0 + }.extend(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+")"; + } +}); + +Effect.MoveBy = Class.create(); + Effect.MoveBy.prototype = (new Effect.Base()).extend({ + 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; + if(this.element.style.position == "") + this.element.style.position = "relative"; + this.start(arguments[3]); + }, + update: function(position) { + topd = this.toTop * position + this.originalTop; + leftd = this.toLeft * position + this.originalLeft; + this.setPosition(topd, leftd); + }, + setPosition: function(topd, leftd) { + this.element.style.top = topd + "px"; + this.element.style.left = leftd + "px"; + } +}); + +Effect.Scale = Class.create(); +Effect.Scale.prototype = (new Effect.Base()).extend({ + initialize: function(element, percent) { + this.element = $(element) + options = { + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0 + }.extend(arguments[2] || {}); + 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') { + this.originalHeight = this.element.clientHeight; + this.originalWidth = this.element.clientWidth; + } else + if(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.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"; + this.setDimensions( + this.originalWidth * currentScale, + this.originalHeight * currentScale); + }, + + 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') { + 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 { + if(this.options.scaleY) this.element.style.top = -topd + "px"; + if(this.options.scaleX) this.element.style.left = -leftd + "px"; + } + } + } +}); + +Effect.Highlight = Class.create(); +Effect.Highlight.prototype = (new Effect.Base()).extend({ + 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 = { + startcolor: "#ffff99", + endcolor: endcolor + }.extend(arguments[1] || {}); + + // 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) ]; + 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); + }, + update: function(position) { + var colors = [ + Math.round(this.colors_base[0]+(this.colors_delta[0]*position)), + Math.round(this.colors_base[1]+(this.colors_delta[1]*position)), + Math.round(this.colors_base[2]+(this.colors_delta[2]*position)) ]; + this.element.style.backgroundColor = "#" + + colors[0].toColorPart() + colors[1].toColorPart() + colors[2].toColorPart(); + } +}); + + +/* ------------- prepackaged effects ------------- */ + +Effect.Fade = function(element) { + options = { + from: 1.0, + to: 0.0, + afterFinish: function(effect) + { Element.hide(effect.element); + effect.setOpacity(1); } + }.extend(arguments[1] || {}); + new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + options = { + from: 0.0, + to: 1.0, + beforeStart: function(effect) + { effect.setOpacity(0); + Element.show(effect.element); }, + afterUpdate: function(effect) + { Element.show(effect.element); } + }.extend(arguments[1] || {}); + 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) + { effect.effects[0].element.style.position = 'absolute'; }, + afterFinish: function(effect) + { Element.hide(effect.effects[0].element); } + } + ); +} + +Effect.BlindUp = function(element) { + $(element).style.overflow = 'hidden'; + new Effect.Scale(element, 0, + { scaleContent: false, + scaleX: false, + afterFinish: function(effect) + { Element.hide(effect.element) } + }.extend(arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + $(element).style.height = '0px'; + $(element).style.overflow = 'hidden'; + Element.show(element); + new Effect.Scale(element, 100, + { scaleContent: false, + scaleX: false, + scaleMode: 'contents', + scaleFrom: 0 + }.extend(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); } + } ) + } + } ) +} + +Effect.DropOut = function(element) { + 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); } + }); +} + +Effect.Shake = function(element) { + new Effect.MoveBy(element, 0, 20, + { duration: 0.05, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, -40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, 40, + { duration: 0.1, afterFinish: function(effect) { + new Effect.MoveBy(effect.element, 0, -20, + { duration: 0.05, afterFinish: function(effect) { + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + $(element).style.height = '0px'; + $(element).style.overflow = 'hidden'; + $(element).firstChild.style.position = 'relative'; + Element.show(element); + new Effect.Scale(element, 100, + { scaleContent: false, + scaleX: false, + scaleMode: 'contents', + scaleFrom: 0, + afterUpdate: function(effect) + { effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; } + }.extend(arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + $(element).style.overflow = 'hidden'; + $(element).firstChild.style.position = 'relative'; + Element.show(element); + new Effect.Scale(element, 0, + { scaleContent: false, + scaleX: false, + afterUpdate: function(effect) + { effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinish: function(effect) + { Element.hide(effect.element); } + }.extend(arguments[1] || {}) + ); +} + +Effect.Squish = function(element) { + new Effect.Scale(element, 0, + { afterFinish: function(effect) { Element.hide(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 direction = options.direction || 'center'; + var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; + var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; + var opacityTransition = options.opacityTransition || Effect.Transitions.full; + + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = originalWidth; + initialMoveY = moveY = 0; + moveX = -originalWidth; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = originalHeight; + moveY = -originalHeight; + break; + case 'bottom-right': + initialMoveX = originalWidth; + initialMoveY = originalHeight; + moveX = -originalWidth; + moveY = -originalHeight; + break; + case 'center': + initialMoveX = originalWidth / 2; + initialMoveY = originalHeight / 2; + moveX = -originalWidth / 2; + moveY = -originalHeight / 2; + break; + } + + new Effect.MoveBy(element, initialMoveY, initialMoveX, { + duration: 0.01, + beforeUpdate: function(effect) { $(element).style.height = '0px'; }, + afterFinish: 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, { + scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth }, + sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })], + options); } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = arguments[1] || {}; + + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + element.style.overflow = 'hidden'; + Element.show(element); + + var direction = options.direction || 'center'; + var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; + var scaleTransition = options.scaleTransition || Effect.Transitions.sinoidal; + var opacityTransition = options.opacityTransition || Effect.Transitions.none; + + var moveX, moveY; + + switch (direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = originalWidth; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = originalHeight; + break; + case 'bottom-right': + moveX = originalWidth; + moveY = originalHeight; + break; + case 'center': + moveX = originalWidth / 2; + moveY = originalHeight / 2; + break; + } + + 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); +} + +Effect.Pulsate = function(element) { + var options = arguments[1] || {}; + 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, + { duration: 3.0, + afterFinish: function(effect) { Element.show(effect.element); } + }.extend(options).extend({transition: reverser})); +} + +Effect.Fold = function(element) { + $(element).style.overflow = 'hidden'; + new Effect2.Scale(element, 5, { + 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) } }); + }}.extend(arguments[1] || {})); +} + +// old: new Effect.ContentZoom(element, percent) +// new: Element.setContentZoom(element, percent) + +Element.setContentZoom = function(element, percent) { + var element = $(element); + + var sizeEm = 1.0; + if (element.style.fontSize.indexOf("em")>0) + sizeEm = parseFloat(element.style.fontSize); + + element.style.fontSize = sizeEm*(percent/100) + "em"; + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); +}
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/javascripts/prototype.js b/actionpack/lib/action_view/helpers/javascripts/prototype.js index ca772aa8fa..1ebb2a46b7 100644 --- a/actionpack/lib/action_view/helpers/javascripts/prototype.js +++ b/actionpack/lib/action_view/helpers/javascripts/prototype.js @@ -559,205 +559,279 @@ Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({ } }); -/*--------------------------------------------------------------------------*/ - -var Effect = new Object(); -Effect.Highlight = Class.create(); -Effect.Highlight.prototype = { - initialize: function(element) { - this.element = $(element); - this.start = 153; - this.finish = 255; - this.current = this.start; - this.fade(); - }, - - fade: function() { - if (this.isFinished()) return; - if (this.timer) clearTimeout(this.timer); - this.highlight(this.element, this.current); - this.current += 17; - this.timer = setTimeout(this.fade.bind(this), 250); - }, - - isFinished: function() { - return this.current > this.finish; - }, - - highlight: function(element, current) { - element.style.backgroundColor = "#ffff" + current.toColorPart(); +// === Prototype Extension ==================================================== + +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// Element.Class part Copyright (c) 2005 by Rick Olson +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +var Event = { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + + element: function(event) { + return event.srcElement || event.currentTarget; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if(event.preventDefault) + { event.preventDefault(); event.stopPropagation(); } + else + event.returnValue = false; + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on, traverses the DOM upwards + findElement: function(event, tagName) { + element = Event.element(event); + while(element.tagName.toUpperCase() != tagName.toUpperCase() && element.parentNode) + element = element.parentNode; + return element; + }, + + observe: function(element, name, observer) { + if(name=='keypress') { + if(navigator.appVersion.indexOf('AppleWebKit')>0) + { $(element).addEventListener("keydown",observer,false); return; } + if($(element).addEventListener) $(element).addEventListener("keypress",observer,false) + else if($(element).attachEvent) $(element).attachEvent("onkeydown",observer); + } else { + if($(element).addEventListener) $(element).addEventListener(name,observer,false) + else if($(element).attachEvent) $(element).attachEvent("on" + name,observer); + } } } +/*--------------------------------------------------------------------------*/ -Effect.Fade = Class.create(); -Effect.Fade.prototype = { - initialize: function(element) { - this.element = $(element); - this.start = 100; - this.finish = 0; - this.current = this.start; - this.fade(); - }, - - fade: function() { - if (this.isFinished()) { this.element.style.display = 'none'; return; } - if (this.timer) clearTimeout(this.timer); - this.setOpacity(this.element, this.current); - this.current -= 10; - this.timer = setTimeout(this.fade.bind(this), 50); - }, - - isFinished: function() { - return this.current <= this.finish; - }, - - setOpacity: function(element, opacity) { - opacity = (opacity == 100) ? 99.999 : opacity; - element.style.filter = "alpha(opacity:"+opacity+")"; - element.style.opacity = opacity/100 /*//*/; +// removes whitespace-only text node children +// needed to make Gecko-based browsers happy +Element.cleanWhitespace = function(element) { + var element = $(element); + for(var i=0;i<element.childNodes.length;i++) { + var node = element.childNodes[i]; + if(node.nodeType==3 && !/\S/.test(node.nodeValue)) + Element.remove(node); } } -Effect.Scale = Class.create(); -Effect.Scale.prototype = { - initialize: function(element, percent) { - this.element = $(element); - this.startScale = 1.0; - this.startHeight = this.element.offsetHeight; - this.startWidth = this.element.offsetWidth; - this.currentHeight = this.startHeight; - this.currentWidth = this.startWidth; - this.finishScale = (percent/100) /*//*/; - if (this.element.style.fontSize=="") this.sizeEm = 1.0; - if (this.element.style.fontSize.indexOf("em")>0) - this.sizeEm = parseFloat(this.element.style.fontSize); - if(this.element.effect_scale) { - clearTimeout(this.element.effect_scale.timer); - this.startScale = this.element.effect_scale.currentScale; - this.startHeight = this.element.effect_scale.startHeight; - this.startWidth = this.element.effect_scale.startWidth; - if(this.element.effect_scale.sizeEm) - this.sizeEm = this.element.effect_scale.sizeEm; - } - this.element.effect_scale = this; - this.currentScale = this.startScale; - this.factor = this.finishScale - this.startScale; - this.options = arguments[2] || {}; - this.scale(); - }, - - scale: function() { - if (this.isFinished()) { - this.setDimensions(this.element, this.startWidth*this.finishScale, this.startHeight*this.finishScale); - if(this.sizeEm) this.element.style.fontSize = this.sizeEm*this.finishScale + "em"; - if(this.options.complete) this.options.complete(this); - return; - } - if (this.timer) clearTimeout(this.timer); - if (this.options.step) this.options.step(this); - this.setDimensions(this.element, this.currentWidth, this.currentHeight); - if(this.sizeEm) this.element.style.fontSize = this.sizeEm*this.currentScale + "em"; - this.currentScale += (this.factor/10) /*//*/; - this.currentWidth = this.startWidth * this.currentScale; - this.currentHeight = this.startHeight * this.currentScale; - this.timer = setTimeout(this.scale.bind(this), 50); - }, - - isFinished: function() { - return (this.factor < 0) ? - this.currentScale <= this.finishScale : this.currentScale >= this.finishScale; - }, +Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { + var children = $(element).childNodes; + var text = ""; + var classtest = new RegExp("^([^ ]+ )*" + ignoreclass+ "( [^ ]+)*$","i"); - setDimensions: function(element, width, height) { - element.style.width = width + 'px'; - element.style.height = height + 'px'; + 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; } -Effect.Squish = Class.create(); -Effect.Squish.prototype = { - initialize: function(element) { - this.element = $(element); - new Effect.Scale(this.element, 1, { complete: this.hide.bind(this) } ); - }, - hide: function() { - this.element.style.display = 'none'; - } -} +/*--------------------------------------------------------------------------*/ -Effect.Puff = Class.create(); -Effect.Puff.prototype = { - initialize: function(element) { - this.element = $(element); - this.opacity = 100; - this.startTop = this.element.top || this.element.offsetTop; - this.startLeft = this.element.left || this.element.offsetLeft; - new Effect.Scale(this.element, 200, { step: this.fade.bind(this), complete: this.hide.bind(this) } ); - }, - fade: function(effect) { - topd = (((effect.currentScale)*effect.startHeight) - effect.startHeight)/2; - leftd = (((effect.currentScale)*effect.startWidth) - effect.startWidth)/2; - this.element.style.position='absolute'; - this.element.style.top = this.startTop-topd + "px"; - this.element.style.left = this.startLeft-leftd + "px"; - this.opacity -= 10; - this.setOpacity(this.element, this.opacity); - if(navigator.appVersion.indexOf('AppleWebKit')>0) this.element.innerHTML += ''; //force redraw on safari - }, - hide: function() { - this.element.style.display = 'none'; +Text = { + stripTags: function(htmlstr) { + return htmlstr.replace(/<\/?[^>]+>/gi,""); }, - setOpacity: function(element, opacity) { - opacity = (opacity == 100) ? 99.999 : opacity; - element.style.filter = "alpha(opacity:"+opacity+")"; - element.style.opacity = opacity/100 /*//*/; + decodeHTML: function(htmlstr) { + return htmlstr.replace(/</gi,"<").replace(/>/gi,">").replace(/"/gi,'"').replace(/'/gi,"'").replace(/&/gi,"&").replace(/[\n\r]/gi,""); } } -Effect.Appear = Class.create(); -Effect.Appear.prototype = { - initialize: function(element) { - this.element = $(element); - this.start = 0; - this.finish = 100; - this.current = this.start; - this.fade(); - }, - - fade: function() { - if (this.isFinished()) return; - if (this.timer) clearTimeout(this.timer); - this.setOpacity(this.element, this.current); - this.current += 10; - this.timer = setTimeout(this.fade.bind(this), 50); - }, - - isFinished: function() { - return this.current > this.finish; - }, - - setOpacity: function(element, opacity) { - opacity = (opacity == 100) ? 99.999 : opacity; - element.style.filter = "alpha(opacity:"+opacity+")"; - element.style.opacity = opacity/100 /*//*/; - element.style.display = ''; - } -} +/*--------------------------------------------------------------------------*/ -Effect.ContentZoom = Class.create(); -Effect.ContentZoom.prototype = { - initialize: function(element, percent) { - this.element = $(element); - if (this.element.style.fontSize=="") this.sizeEm = 1.0; - if (this.element.style.fontSize.indexOf("em")>0) - this.sizeEm = parseFloat(this.element.style.fontSize); - if(this.element.effect_contentzoom) { - this.sizeEm = this.element.effect_contentzoom.sizeEm; +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; } - this.element.effect_contentzoom = this; - this.element.style.fontSize = this.sizeEm*(percent/100) + "em" /*//*/; - if(navigator.appVersion.indexOf('AppleWebKit')>0) { this.element.scrollTop -= 1; }; - } } + +/*--------------------------------------------------------------------------*/ + +var Position = { + // must be called before calling within_including_scrolloffset, every time the page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft || 0; + this.deltaY = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0; + this.include_scroll_offsets = true; + }, + + real_offset: function(element) { + var valueT = 0; var valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while(element); + return [valueL, valueT]; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if(this.include_scroll_offsets) + return within_including_scrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + var offsettop = element.offsetTop; + var offsetleft = element.offsetLeft; + return (y>=offsettop && + y<offsettop+element.offsetHeight && + x>=offsetleft && + x<offsetleft+element.offsetWidth); + }, + + within_including_scrolloffsets: function(element, x, y) { + var offsetcache = this.real_offset(element); + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.xcomp = x; + this.ycomp = y; + var offsettop = element.offsetTop; + var offsetleft = element.offsetLeft; + return (y>=offsettop && + y<offsettop+element.offsetHeight && + x>=offsetleft && + x<offsetleft+element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if(!mode) return 0; + if(mode == 'vertical') + return ((element.offsetTop+element.offsetHeight)-this.ycomp) / element.offsetHeight; + if(mode == 'horizontal') + return ((element.offsetLeft+element.offsetWidth)-this.xcomp) / element.offsetWidth; + }, + + clone: function(source, target) { + $(target).style.top = $(source).style.top; + $(target).style.left = $(source).style.left; + $(target).style.width = $(source).offsetWidth + "px"; + $(target).style.height = $(source).offsetHeight + "px"; + } +}
\ No newline at end of file |