From 938a8fea27a5271403edc6a3e6ab76dda15d9a5b Mon Sep 17 00:00:00 2001 From: David Heinemeier Hansson Date: Mon, 27 Jun 2005 17:40:00 +0000 Subject: 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 --- actionpack/CHANGELOG | 10 ++++ .../lib/action_view/helpers/javascript_helper.rb | 65 +++++++++++++++++----- .../action_view/helpers/javascripts/prototype.js | 32 ++++++++--- 3 files changed, 85 insertions(+), 22 deletions(-) (limited to 'actionpack') 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 options[:update] 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 options[:position] parameter to influence + # how the target DOM element is updated. It must be one of + # :before, :top, :bottom, or :after. + # # 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 request object, + # which holds the underlying XMLHttpRequest. + # + # To access the server response, use request.responseText, to + # find out the HTTP status, use request.status. # # Example: # link_to_remote word, @@ -63,7 +80,21 @@ module ActionView # :interactive:: Called when the user can interact with the # remote document, even though it has not # finished loading. - # :complete:: Called when the XMLHttpRequest is complete. + # :complete:: Called when the XMLHttpRequest is complete, + # and the HTTP status code is 200 OK. + # :failure:: Called when the XMLHttpRequest is complete, + # and the HTTP status code is anything other than + # 200 OK. + # + # You can further refine :failure 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); } -- cgit v1.2.3