aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_view
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_view')
-rw-r--r--actionpack/lib/action_view/helpers/javascript_helper.rb65
-rw-r--r--actionpack/lib/action_view/helpers/javascripts/prototype.js32
2 files changed, 75 insertions, 22 deletions
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);
}