diff options
Diffstat (limited to 'actionpack')
6 files changed, 97 insertions, 46 deletions
diff --git a/actionpack/lib/action_controller/auto_complete.rb b/actionpack/lib/action_controller/auto_complete.rb index c2590c66b4..685bb63ff9 100644 --- a/actionpack/lib/action_controller/auto_complete.rb +++ b/actionpack/lib/action_controller/auto_complete.rb @@ -8,6 +8,17 @@ module ActionController # # # View # <%= text_field_with_auto_complete :post, title %> + # + # By default, auto_complete_for limits the results to 10 entries, + # and sorts by the given field. + # + # auto_complete_for takes a third parameter, an options hash to + # the find method used to search for the records: + # + # auto_complete_for :post, :title, :limit => 15, :order => 'created_at DESC' + # + # For help on defining text input fields with autocompletion, + # see ActionView::Helpers::JavascriptHelper. module AutoComplete def self.append_features(base) #:nodoc: super diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index c506757119..c6c5c58abf 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -3,7 +3,7 @@ require File.dirname(__FILE__) + '/tag_helper' module ActionView module Helpers # Provides a set of helpers for calling JavaScript functions and, most importantly, to call remote methods using what has - # been labelled Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php]. This means that you can call + # been labelled AJAX[http://www.adaptivepath.com/publications/essays/archives/000385.php]. This means that you can call # actions in your controllers without reloading the page, but still update certain parts of it using injections into the # DOM. The common use case is having a form that adds a new element to a list without reloading the page. # @@ -12,7 +12,7 @@ module ActionView # <tt><%= javascript_include_tag "prototype" %></tt> (which looks for the library in /javascripts/prototype.js). The latter is # recommended as the browser can then cache the library instead of fetching all the functions anew on every request. # - # If you're the visual type, there's an Ajax movie[http://www.rubyonrails.com/media/video/rails-ajax.mov] demonstrating + # If you're the visual type, there's an AJAX movie[http://www.rubyonrails.com/media/video/rails-ajax.mov] demonstrating # the use of form_remote_tag. module JavaScriptHelper unless const_defined? :CALLBACKS @@ -212,7 +212,7 @@ module ActionView end # Observes the field with the DOM ID specified by +field_id+ and makes - # an Ajax call when its contents have changed. + # an AJAX call when its contents have changed. # # Required +options+ are: # <tt>:url</tt>:: +url_for+-style options for the action to call @@ -254,7 +254,7 @@ module ActionView end - # Adds Ajax autocomplete functionality to the text input field with the + # 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, @@ -272,7 +272,7 @@ module ActionView # 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. + # entries returned by the AJAX request. # Defaults to field_id + '_auto_complete' # <tt>:with</tt>:: A JavaScript expression specifying the # parameters for the XMLHttpRequest. This defaults @@ -294,13 +294,14 @@ module ActionView javascript_tag(function) end - # Use this method in your view to generate a return for the Ajax automplete requests. + # Use this method in your view to generate a return for the AJAX automplete requests. # # Example action: # # def auto_complete_for_item_title - # @items = Item.find(:all, :conditions => [ 'LOWER(description) LIKE ?', - # '%' + params[:for].downcase + '%' ], 'description ASC') + # @items = Item.find(:all, + # :conditions => [ 'LOWER(description) LIKE ?', + # '%' + request.raw_post.downcase + '%' ]) # render :inline => '<%= auto_complete_result(@items, 'description') %>' # end # @@ -312,6 +313,12 @@ module ActionView content_tag("ul", items) end + # Wrapper for text_field with added AJAX autocompletion functionality. + # + # In your controller, you'll need to define an action called + # auto_complete_for_object_method to respond the AJAX calls, + # + # See the RDoc on ActionController::AutoComplete to learn more about this. def text_field_with_auto_complete(object, method, tag_options = {}, completion_options = {}) (completion_options[:skip_style] ? "" : auto_complete_stylesheet) + text_field(object, method, { :autocomplete => "off" }.merge!(tag_options)) + @@ -319,7 +326,7 @@ module ActionView auto_complete_field("#{object}_#{method}", { :url => { :action => "auto_complete_for_#{object}_#{method}" } }.update(completion_options)) end - # Returns a JavaScript snippet to be used on the Ajax callbacks for starting + # Returns a JavaScript snippet to be used on the AJAX callbacks for starting # visual effects. # # Example: @@ -334,12 +341,12 @@ module ActionView 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 + # 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" }) %> + # <%= sortable_element("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 @@ -378,7 +385,7 @@ module ActionView js_options['method'] = method_option_to_s(options[:method]) if options[:method] js_options['insertion'] = "Insertion.#{options[:position].to_s.camelize}" if options[:position] js_options['evalScripts'] = options[:script] == true if options[:script] - + if options[:form] js_options['parameters'] = 'Form.serialize(this)' elsif options[:with] diff --git a/actionpack/lib/action_view/helpers/javascripts/controls.js b/actionpack/lib/action_view/helpers/javascripts/controls.js index 90a5f1132b..f4be26b289 100644 --- a/actionpack/lib/action_view/helpers/javascripts/controls.js +++ b/actionpack/lib/action_view/helpers/javascripts/controls.js @@ -56,6 +56,21 @@ Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({ 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'; + 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}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.3}) }; + + if(this.options.indicator) this.indicator = $(this.options.indicator); @@ -63,28 +78,28 @@ 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)); - 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>'); + 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>'); 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); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; Element.show(this.iefix); } }, hide: function() { + if(this.update.style.display=='') this.options.onHide(this.element, this.update); if(this.iefix) Element.hide(this.iefix); - Element.hide(this.update); }, startIndicator: function() { @@ -194,21 +209,18 @@ Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({ }, onBlur: function(event) { - var element = Event.element(event); - if(element==this.update) return; - while(element.parentNode) - { element = element.parentNode; if(element==this.update) return; } - this.hide(); + // needed to make click events working + setTimeout(this.hide.bind(this), 250); this.has_focus = false; - this.active = false; + 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"); + Element.addClassName(this.get_entry(i),"selected") : + Element.removeClassName(this.get_entry(i),"selected"); if(this.has_focus) { if(this.get_current_entry().scrollIntoView) @@ -239,7 +251,6 @@ Ajax.Autocompleter.prototype = (new Ajax.Base()).extend({ }, select_entry: function() { - this.hide(); this.active = false; value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML(); this.element.value = value; diff --git a/actionpack/lib/action_view/helpers/javascripts/dragdrop.js b/actionpack/lib/action_view/helpers/javascripts/dragdrop.js index 6086b44ee0..78f82bcd23 100644 --- a/actionpack/lib/action_view/helpers/javascripts/dragdrop.js +++ b/actionpack/lib/action_view/helpers/javascripts/dragdrop.js @@ -221,9 +221,9 @@ Draggables = { addObserver: function(observer) { this.observers.push(observer); }, - notify: function(eventName) { // 'onStart', 'onEnd' + notify: function(eventName, draggable) { // 'onStart', 'onEnd' for(var i = 0; i < this.observers.length; i++) - this.observers[i][eventName](); + this.observers[i][eventName](draggable); } } @@ -243,7 +243,8 @@ Draggable.prototype = { endeffect: function(element) { new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); }, - zindex: 1000 + zindex: 1000, + revert: false }.extend(arguments[1] || {}); this.element = $(element); @@ -278,8 +279,8 @@ Draggable.prototype = { 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.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; @@ -292,9 +293,12 @@ Draggable.prototype = { this.dragging = false; Droppables.fire(event, this.element); - Draggables.notify('onEnd'); + Draggables.notify('onEnd', this); - if(this.options.revert && this.options.reverteffect) { + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + if(revert && this.options.reverteffect) { this.options.reverteffect(this.element, this.currentTop()-this.originalTop, this.currentLeft()-this.originalLeft); @@ -330,7 +334,7 @@ Draggable.prototype = { this.dragging = true; if(style.position=="") style.position = "relative"; style.zIndex = this.options.zindex; - Draggables.notify('onStart'); + Draggables.notify('onStart', this); if(this.options.starteffect) this.options.starteffect(this.element); } diff --git a/actionpack/lib/action_view/helpers/javascripts/effects.js b/actionpack/lib/action_view/helpers/javascripts/effects.js index 9a26c5d712..756c95cfeb 100644 --- a/actionpack/lib/action_view/helpers/javascripts/effects.js +++ b/actionpack/lib/action_view/helpers/javascripts/effects.js @@ -307,25 +307,33 @@ Effect.Puff = function(element) { } Effect.BlindUp = function(element) { + $(element)._overflow = $(element).style.overflow || 'visible'; $(element).style.overflow = 'hidden'; new Effect.Scale(element, 0, { scaleContent: false, scaleX: false, afterFinish: function(effect) - { Element.hide(effect.element) } + { + Element.hide(effect.element); + effect.element.style.overflow = effect.element._overflow; + } }.extend(arguments[1] || {}) ); } Effect.BlindDown = function(element) { $(element).style.height = '0px'; + $(element)._overflow = $(element).style.overflow || 'visible'; $(element).style.overflow = 'hidden'; Element.show(element); new Effect.Scale(element, 100, { scaleContent: false, scaleX: false, scaleMode: 'contents', - scaleFrom: 0 + scaleFrom: 0, + afterFinish: function(effect) { + effect.element.style.overflow = effect.element._overflow; + } }.extend(arguments[1] || {}) ); } @@ -375,6 +383,7 @@ Effect.Shake = function(element) { } Effect.SlideDown = function(element) { + $(element)._overflow = $(element).style.overflow || 'visible'; $(element).style.height = '0px'; $(element).style.overflow = 'hidden'; $(element).firstChild.style.position = 'relative'; @@ -386,12 +395,15 @@ Effect.SlideDown = function(element) { scaleFrom: 0, afterUpdate: function(effect) { effect.element.firstChild.style.bottom = - (effect.originalHeight - effect.element.clientHeight) + 'px'; } + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinish: function(effect) + { effect.element.style.overflow = effect.element._overflow; } }.extend(arguments[1] || {}) ); } Effect.SlideUp = function(element) { + $(element)._overflow = $(element).style.overflow || 'visible'; $(element).style.overflow = 'hidden'; $(element).firstChild.style.position = 'relative'; Element.show(element); @@ -402,7 +414,10 @@ Effect.SlideUp = function(element) { { effect.element.firstChild.style.bottom = (effect.originalHeight - effect.element.clientHeight) + 'px'; }, afterFinish: function(effect) - { Element.hide(effect.element); } + { + Element.hide(effect.element); + effect.element.style.overflow = effect.element._overflow; + } }.extend(arguments[1] || {}) ); } diff --git a/actionpack/lib/action_view/helpers/javascripts/prototype.js b/actionpack/lib/action_view/helpers/javascripts/prototype.js index a76d3e62b6..9225f13125 100644 --- a/actionpack/lib/action_view/helpers/javascripts/prototype.js +++ b/actionpack/lib/action_view/helpers/javascripts/prototype.js @@ -221,11 +221,12 @@ Ajax.Request.prototype = (new Ajax.Base()).extend({ }, setRequestHeaders: function() { - var requestHeaders = ['X-Requested-With', 'XMLHttpRequest', + var requestHeaders = [ + 'X-Requested-With', 'XMLHttpRequest', 'X-Prototype-Version', Prototype.Version]; if (this.options.method == 'post') - requestHeaders.push('Connection', 'close', + requestHeaders.push(//'Connection', 'close', 'Content-type', 'application/x-www-form-urlencoded'); if (this.options.requestHeaders) @@ -987,8 +988,10 @@ var Position = { clone: function(source, target) { source = $(source); target = $(target); - target.style.top = source.style.top; - target.style.left = source.style.left; + target.style.position = 'absolute'; + var offsets = this.cumulativeOffset(source); + target.style.top = offsets[1] + 'px'; + target.style.left = offsets[0] + 'px'; target.style.width = source.offsetWidth + 'px'; target.style.height = source.offsetHeight + 'px'; } |