Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
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
dhh committed Jun 27, 2005
1 parent 05ef789 commit 938a8fe
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 22 deletions.
10 changes: 10 additions & 0 deletions 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]
Expand Down
65 changes: 52 additions & 13 deletions actionpack/lib/action_view/helpers/javascript_helper.rb
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
32 changes: 23 additions & 9 deletions actionpack/lib/action_view/helpers/javascripts/prototype.js
Expand Up @@ -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) {
Expand All @@ -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);
}
Expand Down

0 comments on commit 938a8fe

Please sign in to comment.