Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

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
  • Loading branch information...
commit 938a8fea27a5271403edc6a3e6ab76dda15d9a5b 1 parent 05ef789
@dhh dhh authored
View
10 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]
View
65 actionpack/lib/action_view/helpers/javascript_helper.rb
@@ -16,7 +16,8 @@ module Helpers
# 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 @@ def link_to_function(name, function, html_options = {})
# 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 @@ def link_to_function(name, function, html_options = {})
# <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 @@ def submit_to_remote(name, value, options = {})
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 @@ def build_observer(klass, name, options = {})
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
View
32 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);
}

0 comments on commit 938a8fe

Please sign in to comment.
Something went wrong with that request. Please try again.