diff options
author | David Heinemeier Hansson <david@loudthinking.com> | 2005-06-27 17:40:00 +0000 |
---|---|---|
committer | David Heinemeier Hansson <david@loudthinking.com> | 2005-06-27 17:40:00 +0000 |
commit | 938a8fea27a5271403edc6a3e6ab76dda15d9a5b (patch) | |
tree | 11800e755ab7278f06859c5dbdd25cc3841e8da0 | |
parent | 05ef7898bd42970a45a6727b1cf44338982ab2c6 (diff) | |
download | rails-938a8fea27a5271403edc6a3e6ab76dda15d9a5b.tar.gz rails-938a8fea27a5271403edc6a3e6ab76dda15d9a5b.tar.bz2 rails-938a8fea27a5271403edc6a3e6ab76dda15d9a5b.zip |
Added support for graceful error handling of Ajax calls #1217 [Jamis Buck/Thomas Fuchs]
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1545 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
-rw-r--r-- | actionpack/CHANGELOG | 10 | ||||
-rw-r--r-- | actionpack/lib/action_view/helpers/javascript_helper.rb | 65 | ||||
-rw-r--r-- | actionpack/lib/action_view/helpers/javascripts/prototype.js | 32 |
3 files changed, 85 insertions, 22 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 8c8da2fda2..2d39682253 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,15 @@ *SVN* +* Added support for graceful error handling of Ajax calls #1217 [Jamis Buck/Thomas Fuchs]. Example: + + link_to_remote( + "test", + :url => { :action => "faulty" }, + :update => { :success => "good", :failure => "bad" }, + 403 => "alert('Forbidden- got ya!')", + 404 => "alert('Nothing there...?')", + :failure => "alert('Unkown error ' + request.status)") + * Attempt to explicitly flush the output at the end of CgiProcess#out * Fixed assert_redirected_to to handle absolute controller paths properly #1472 [Rick Olson/Nicholas Seckar] diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index aeabef6a5d..6495ebf655 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, :failure].push((100..599).to_a).flatten AJAX_OPTIONS = [ :url, :asynchronous, :method, :insertion, :form, :with, :update ].concat(CALLBACKS) JAVASCRIPT_PATH = File.join(File.dirname(__FILE__), 'javascripts') end @@ -45,9 +46,25 @@ module ActionView # link_to_remote "Delete this post", :update => "posts", :url => { :action => "destroy", :id => post.id } # link_to_remote(image_tag("refresh"), :update => "emails", :url => { :action => "list_emails" }) # + # You can also specify a hash for <tt>options[:update]</tt> to allow for + # easy redirection of output to an other DOM element if a server-side error occurs: + # + # Example: + # link_to_remote "Delete this post", + # :url => { :action => "destroy", :id => post.id }, + # :update => { :success => "posts", :failure => "error" } + # + # Optionally, you can use the <tt>options[:position]</tt> parameter to influence + # how the target DOM element is updated. It must be one of + # <tt>:before</tt>, <tt>:top</tt>, <tt>:bottom</tt>, or <tt>:after</tt>. + # # By default, these remote requests are processed asynchronous during - # which various callbacks can be triggered (for progress indicators and - # the likes). + # which various JavaScript callbacks can be triggered (for progress indicators and + # the likes). All callbacks get access to the <tt>request</tt> object, + # which holds the underlying XMLHttpRequest. + # + # To access the server response, use <tt>request.responseText</tt>, to + # find out the HTTP status, use <tt>request.status</tt>. # # Example: # link_to_remote word, @@ -63,7 +80,21 @@ module ActionView # <tt>:interactive</tt>:: Called when the user can interact with the # remote document, even though it has not # finished loading. - # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete. + # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete, + # and the HTTP status code is 200 OK. + # <tt>:failure</tt>:: Called when the XMLHttpRequest is complete, + # and the HTTP status code is anything other than + # 200 OK. + # + # You can further refine <tt>:failure</tt> by adding additional + # callbacks for specific status codes: + # + # Example: + # link_to_remote word, + # :url => { :action => "action" }, + # 404 => "alert('Not found...? Wrong URL...?')", + # :failure => "alert('HTTP Error ' + request.status + '!')" + # # # If you for some reason or another need synchronous processing (that'll # block the browser while the request is happening), you can specify @@ -132,18 +163,26 @@ module ActionView def remote_function(options) #:nodoc: for now javascript_options = options_for_ajax(options) - function = options[:update] ? - "new Ajax.Updater('#{options[:update]}', " : - "new Ajax.Request(" + update = [] + if options[:update] and options[:update].is_a?Hash + update << "success:'#{options[:update][:success]}'" if options[:update][:success] + update << "failure:'#{options[:update][:failure]}'" if options[:update][:failure] + elsif options[:update] + update << "success:'#{options[:update]}'" + end + + function = update.empty? ? + "new Ajax.Request(" : + "new Ajax.Updater({#{update.join(',')}}, " function << "'#{url_for(options[:url])}'" function << ", #{javascript_options})" - + function = "#{options[:before]}; #{function}" if options[:before] function = "#{function}; #{options[:after]}" if options[:after] function = "if (#{options[:condition]}) { #{function}; }" if options[:condition] function = "if (confirm('#{escape_javascript(options[:confirm])}')) { #{function}; }" if options[:confirm] - + return function end @@ -359,14 +398,14 @@ module ActionView end def build_callbacks(options) - CALLBACKS.inject({}) do |callbacks, callback| - if options[callback] + callbacks = {} + options.each do |callback, code| + if CALLBACKS.include?(callback) name = 'on' + callback.to_s.capitalize - code = options[callback] callbacks[name] = "function(request){#{code}}" end - callbacks end + callbacks end def auto_complete_stylesheet diff --git a/actionpack/lib/action_view/helpers/javascripts/prototype.js b/actionpack/lib/action_view/helpers/javascripts/prototype.js index eefb6e82b8..bba61b91a0 100644 --- a/actionpack/lib/action_view/helpers/javascripts/prototype.js +++ b/actionpack/lib/action_view/helpers/javascripts/prototype.js @@ -236,14 +236,24 @@ Ajax.Request.prototype = (new Ajax.Base()).extend({ respondToReadyState: function(readyState) { var event = Ajax.Request.Events[readyState]; - (this.options['on' + event] || Prototype.emptyFunction)(this.transport); + + if (event == 'Complete' && this.transport.status != 200) + (this.options['on' + this.transport.status] || + this.options.onFailure || + Prototype.emptyFunction)(this.transport); + + (this.options['on' + event] || Prototype.emptyFunction)(this.transport); } }); Ajax.Updater = Class.create(); Ajax.Updater.prototype = (new Ajax.Base()).extend({ initialize: function(container, url, options) { - this.container = $(container); + this.containers = { + success: container.success ? $(container.success) : $(container), + failure: container.failure ? $(container.failure) : null + } + this.setOptions(options); if (this.options.asynchronous) { @@ -258,16 +268,20 @@ Ajax.Updater.prototype = (new Ajax.Base()).extend({ }, updateContent: function() { - if (this.request.transport.status == 200) { + var receiver = + (this.request.transport.status == 200) ? + this.containers.success : this.containers.failure; + + if (receiver) { if (this.options.insertion) { - new this.options.insertion(this.container, - this.request.transport.responseText); + new this.options.insertion(receiver, + this.request.transport.responseText); } else { - this.container.innerHTML = this.request.transport.responseText; + receiver.innerHTML = this.request.transport.responseText; } - } - - if (this.onComplete) { + } + + if (this.request.transport.status == 200 && this.onComplete) { setTimeout((function() {this.onComplete( this.request.transport)}).bind(this), 10); } |