diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG index 2d39682253ec13bf1bf70d6220dd5c4dde4dc272..f124a935c049222984cfdbb9f1d8f8b8a0d11306 100644 --- a/actionpack/CHANGELOG +++ b/actionpack/CHANGELOG @@ -1,5 +1,8 @@ *SVN* +* Added support for upload progress indicators in Apache and lighttpd 1.4.x (won't work in WEBrick or lighttpd 1.3.x) #1475 [Sean Treadway] + See http://sean.treadway.info/files/howto-upload-progress-2.mov for example. + * Added support for graceful error handling of Ajax calls #1217 [Jamis Buck/Thomas Fuchs]. Example: link_to_remote( diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index deead18b5a0f1b14eeeb398667adbf80724a4185..7cc0d605ae6ca1ad8ceb6d1862ad02b35a3da6e2 100755 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -48,6 +48,7 @@ require 'action_controller/caching' require 'action_controller/components' require 'action_controller/verification' +require 'action_controller/upload_progress' require 'action_controller/streaming' require 'action_controller/auto_complete' @@ -69,6 +70,7 @@ include ActionController::Caching include ActionController::Components include ActionController::Verification + include ActionController::UploadProgress include ActionController::Streaming include ActionController::AutoComplete end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index bd0e78719d9c8a3b12b32a3d15f4c94466c2ac4e..2e0d554a55b9285e3bb9b8fa3b208329e122cd2b 100755 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -508,12 +508,24 @@ def render_to_string(options = {}) #:doc: return result end - # Clears the rendered results, allowing for another render or redirect to be performed. + # Clears the rendered results, allowing for another render to be performed. def erase_render_results #:nodoc: @response.body = nil @performed_render = false end + # Clears the redirected results from the headers, resetting the status to 200 and returns + # the URL that was used to redirect or nil if there was no redirected URL + # Note that +redirect_to+ will change the body of the response to indicate a redirection. + # The response body is not reset here, see +erase_render_results+ + def erase_redirect_results #:nodoc: + @performed_redirect = false + response.redirected_to = nil + response.redirected_to_method_params = nil + response.headers['Status'] = DEFAULT_RENDER_STATUS_CODE + response.headers.delete('location') + end + def rewrite_options(options) if defaults = default_url_options(options) defaults.merge(options) diff --git a/actionpack/lib/action_controller/cgi_ext/raw_post_data_fix.rb b/actionpack/lib/action_controller/cgi_ext/raw_post_data_fix.rb index 5cb13c2456499ec2865a62ca656c519f841607ee..b2194eb07b29ed2cbe7edc26fb1a6f32130ffaac 100644 --- a/actionpack/lib/action_controller/cgi_ext/raw_post_data_fix.rb +++ b/actionpack/lib/action_controller/cgi_ext/raw_post_data_fix.rb @@ -6,6 +6,8 @@ module QueryExtension # Handles multipart forms (in particular, forms that involve file uploads). # Reads query parameters in the @params field, and cookies into @cookies. def initialize_query() + @cookies = CGI::Cookie::parse((env_table['HTTP_COOKIE'] || env_table['COOKIE'])) + if boundary = multipart_form_boundary @multipart = true @params = read_multipart(boundary, Integer(env_table['CONTENT_LENGTH'])) @@ -13,8 +15,6 @@ def initialize_query() @multipart = false @params = CGI::parse(read_query_params || "") end - - @cookies = CGI::Cookie::parse((env_table['HTTP_COOKIE'] || env_table['COOKIE'])) end private @@ -28,16 +28,32 @@ def multipart_form_boundary end end + def read_params_from_query + if defined? MOD_RUBY + Apache::request.args || '' + else + # fixes CGI querystring parsing for POSTs + if env_table['QUERY_STRING'].blank? && !env_table['REQUEST_URI'].blank? + env_table['QUERY_STRING'] = env_table['REQUEST_URI'].split('?', 2)[1] || '' + end + env_table['QUERY_STRING'] + end + end + + def read_params_from_post + stdinput.binmode if stdinput.respond_to?(:binmode) + content = stdinput.read(Integer(env_table['CONTENT_LENGTH'])) || '' + env_table['RAW_POST_DATA'] = content.split("&_").first.to_s.freeze # &_ is a fix for Safari Ajax postings that always append \000 + end + def read_query_params case env_table['REQUEST_METHOD'].to_s.upcase when 'CMD' read_from_cmdline when 'POST', 'PUT' - stdinput.binmode if stdinput.respond_to?(:binmode) - content = stdinput.read(Integer(env_table['CONTENT_LENGTH'])) || '' - env_table['RAW_POST_DATA'] = content.split("&_").first.to_s.freeze # &_ is a fix for Safari Ajax postings that always append \000 + read_params_from_post else # when 'GET', 'HEAD', 'DELETE', 'OPTIONS' - (defined?(MOD_RUBY) ? Apache::request.args : env_table['QUERY_STRING']) || '' + read_params_from_query end end end # module QueryExtension diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index 6495ebf65521dbf4373fe35a8f0de6c4edee4721..a0a4076103b0ab82298397c04452fed2752dd56d 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -124,7 +124,7 @@ def periodically_call_remote(options = {}) code = "new PeriodicalExecuter(function() {#{remote_function(options)}}, #{frequency})" content_tag("script", code, options[:html_options] || {}) end - + # Returns a form tag that will submit using XMLHttpRequest in the background instead of the regular # reloading POST arrangement. Even though it's using Javascript to serialize the form elements, the form submission # will work just like a regular submission as viewed by the receiving side (all elements available in @params). @@ -373,6 +373,7 @@ def options_for_ajax(options) js_options['asynchronous'] = options[:type] != :synchronous js_options['method'] = method_option_to_s(options[:method]) if options[:method] js_options['insertion'] = "Insertion.#{options[:position].to_s.camelize}" if options[:position] + js_options['script'] = options[:script] == true if options[:script] if options[:form] js_options['parameters'] = 'Form.serialize(this)' @@ -382,7 +383,7 @@ def options_for_ajax(options) options_for_javascript(js_options) end - + def method_option_to_s(method) (method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'" end diff --git a/actionpack/lib/action_view/helpers/javascripts/prototype.js b/actionpack/lib/action_view/helpers/javascripts/prototype.js index 02d103498ec2a8f5fb0b8a1d36d42a88720714aa..0d23a344d99a99355b2d33eec99103747cfd748a 100644 --- a/actionpack/lib/action_view/helpers/javascripts/prototype.js +++ b/actionpack/lib/action_view/helpers/javascripts/prototype.js @@ -254,6 +254,7 @@ Ajax.Updater.prototype = (new Ajax.Base()).extend({ failure: container.failure ? $(container.failure) : null } + this.script_re = /((?:\n|.)*?)<\/script>/im; this.setOptions(options); if (this.options.asynchronous) { @@ -271,23 +272,79 @@ Ajax.Updater.prototype = (new Ajax.Base()).extend({ var receiver = (this.request.transport.status == 200) ? this.containers.success : this.containers.failure; + + var response = this.request.transport.responseText.replace( + this.script_re, ''); + + var scripts = this.request.transport.responseText.match( + this.script_re); if (receiver) { if (this.options.insertion) { - new this.options.insertion(receiver, - this.request.transport.responseText); + new this.options.insertion(receiver, response); } else { - receiver.innerHTML = this.request.transport.responseText; + receiver.innerHTML = response; } } - - if (this.request.transport.status == 200 && this.onComplete) { - setTimeout((function() {this.onComplete( - this.request.transport)}).bind(this), 10); + + if (this.request.transport.status == 200) { + if (this.onComplete) { + setTimeout((function() {this.onComplete( + this.request.transport)}).bind(this), 10); + } + if (this.options.script && scripts) { + setTimeout((function() { eval(scripts[1]) }).bind(this), 10); + } + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({ + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = 1; + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Ajax.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); } + }); +/*--------------------------------------------------------------------------*/ + document.getElementsByClassName = function(className) { var children = document.getElementsByTagName('*') || document.all; var elements = new Array(); diff --git a/railties/lib/rails_generator/generators/components/scaffold/templates/style.css b/railties/lib/rails_generator/generators/components/scaffold/templates/style.css index d17eda753ba236641340f9dbdcb4a72c61e06aa4..79100833222b3e4a4516ca5d0dc9ca5c4fb39f77 100644 --- a/railties/lib/rails_generator/generators/components/scaffold/templates/style.css +++ b/railties/lib/rails_generator/generators/components/scaffold/templates/style.css @@ -51,3 +51,24 @@ a:hover { color: #fff; background-color:#000; } font-size: 12px; list-style: square; } + +div.uploadStatus { + margin: 5px; +} + +div.progressBar { + margin: 5px; +} + +div.progressBar div.border { + background-color: #fff; + border: 1px solid grey; + width: 100%; +} + +div.progressBar div.background { + background-color: #333; + height: 18px; + width: 0%; +} +