Module | ActionView::Helpers::UploadProgressHelper |
In: |
vendor/rails/actionpack/lib/action_view/helpers/upload_progress_helper.rb
|
Which means that it doesn’t yet work on all systems. We‘re still working on full compatibility. It’s thus not advised to use this unless you’ve verified it to work fully on all the systems that is a part of your environment. Consider this an extended preview.
Provides a set of methods to be used in your views to help with the rendering of Ajax enabled status updating during a multipart upload.
The basic mechanism for upload progress is that the form will post to a hidden <iframe> element, then poll a status action that will replace the contents of a status container. Client Javascript hooks are provided for begin and finish of the update.
If you wish to have a DTD that will validate this page, use XHTML Transitional because this DTD supports the <iframe> element.
In your upload view:
<%= form_tag_with_upload_progress({ :action => 'create' }) %> <%= file_field "document", "file" %> <%= submit_tag "Upload" %> <%= upload_status_tag %> <%= end_form_tag %>
In your controller:
class DocumentController < ApplicationController upload_status_for :create def create # ... Your document creation action end end
In your upload view:
<%= form_tag_with_upload_progress({ :action => 'create' }, { :begin => "alert('upload beginning'), :finish => "alert('upload finished')}) %> <%= file_field "document", "file" %> <%= submit_tag "Upload" %> <%= upload_status_tag %> <%= end_form_tag %>
See upload_status_text_tag and upload_status_progress_bar_tag for references of the IDs and CSS classes used.
Default styling is included with the scaffolding CSS.
FREQUENCY | = | 2.0 | Default number of seconds between client updates | |
FREQUENCY_DECAY | = | 1.8 | Factor to decrease the frequency when the upload_status action returns the same results To disable update decay, set this constant to false |
This method must be called by the action that receives the form post with the upload_progress. By default this method is rendered when the controller declares that the action is the receiver of a form_tag_with_upload_progress posting.
This template will do a javascript redirect to the URL specified in redirect_to if this method is called anywhere in the controller action. When the action performs a redirect, the finish handler will not be called.
If there are errors in the action then you should set the controller instance variable +@errors+. The +@errors+ object will be converted to a javascript array from +@errors.full_messages+ and passed to the finish handler of form_tag_with_upload_progress
If no errors have occured, the parameter to the finish handler will be undefined.
<script> function do_finish(errors) { if (errors) { alert(errors); } } </script> <%= form_tag_with_upload_progress {:action => 'create'}, {finish => 'do_finish(arguments[0])'} %>
# File vendor/rails/actionpack/lib/action_view/helpers/upload_progress_helper.rb, line 261 261: def finish_upload_status(options = {}) 262: # Always trigger the stop/finish callback 263: js = "parent.#{upload_update_object}.stop(#{options[:client_js_argument]});\n" 264: 265: # Redirect if redirect_to was called in controller 266: js << "parent.location.replace('#{escape_javascript options[:redirect_to]}');\n" unless options[:redirect_to].blank? 267: 268: # Guard against multiple triggers/redirects on back 269: js = "if (parent.#{upload_update_object}) { #{js} }\n" 270: 271: content_tag("html", 272: content_tag("head", 273: content_tag("script", "function finish() { #{js} }", 274: {:type => "text/javascript", :language => "javascript"})) + 275: content_tag("body", '', :onload => 'finish()')) 276: end
Creates a form tag and hidden <iframe> necessary for the upload progress status messages to be displayed in a designated div on your page.
When the upload starts, the content created by upload_status_tag will be filled out with "Upload starting…". When the upload is finished, "Upload finished." will be used. Every update inbetween the begin and finish events will be determined by the server upload_status action. Doing this automatically means that the user can use the same form to upload multiple files without refreshing page while still displaying a reasonable progress.
For the view and the controller to know about the same upload they must share a common upload_id. form_tag_with_upload_progress prepares the next available upload_id when called. There are other methods which use the upload_id so the order in which you include your content is important. Any content that depends on the upload_id will use the one defined form_tag_with_upload_progress otherwise you will need to explicitly declare the upload_id shared among your progress elements.
Status container after the form:
<%= form_tag_with_upload_progress %> <%= end_form_tag %> <%= upload_status_tag %>
Status container before form:
<% my_upload_id = next_upload_id %> <%= upload_status_tag %> <%= form_tag_with_upload_progress :upload_id => my_upload_id %> <%= end_form_tag %>
It is recommended that the helpers manage the upload_id parameter.
form_tag_with_upload_progress uses similar options as form_tag yet accepts another hash for the options used for the upload_status action.
url_for_options: | The same options used by form_tag including: |
:upload_id: | the upload id used to uniquely identify this upload |
options: | similar options to form_tag including: |
:begin: | Javascript code that executes before the first status update occurs. |
:finish: | Javascript code that executes after the action that receives the post returns. |
:frequency: | number of seconds between polls to the upload status action. |
status_url_for_options: | options passed to url_for to build the url |
for the upload status action.
:controller: | defines the controller to be used for a custom update status action |
:action: | defines the action to be used for a custom update status action |
Parameters passed to the action defined by status_url_for_options
:upload_id: | the upload_id automatically generated by form_tag_with_upload_progress or the user defined id passed to this method. |
# File vendor/rails/actionpack/lib/action_view/helpers/upload_progress_helper.rb, line 149 149: def form_tag_with_upload_progress(url_for_options = {}, options = {}, status_url_for_options = {}, *parameters_for_url_method) 150: 151: ## Setup the action URL and the server-side upload_status action for 152: ## polling of status during the upload 153: 154: options = options.dup 155: 156: upload_id = url_for_options.delete(:upload_id) || next_upload_id 157: upload_action_url = url_for(url_for_options) 158: 159: if status_url_for_options.is_a? Hash 160: status_url_for_options = status_url_for_options.merge({ 161: :action => 'upload_status', 162: :upload_id => upload_id}) 163: end 164: 165: status_url = url_for(status_url_for_options) 166: 167: ## Prepare the form options. Dynamically change the target and URL to enable the status 168: ## updating only if javascript is enabled, otherwise perform the form submission in the same 169: ## frame. 170: 171: upload_target = options[:target] || upload_target_id 172: upload_id_param = "#{upload_action_url.include?('?') ? '&' : '?'}upload_id=#{upload_id}" 173: 174: ## Externally :begin and :finish are the entry and exit points 175: ## Internally, :finish is called :complete 176: 177: js_options = { 178: :decay => options[:decay] || FREQUENCY_DECAY, 179: :frequency => options[:frequency] || FREQUENCY, 180: } 181: 182: updater_options = '{' + js_options.map {|k, v| "#{k}:#{v}"}.sort.join(',') + '}' 183: 184: ## Finish off the updating by forcing the progress bar to 100% and status text because the 185: ## results of the post may load and finish in the IFRAME before the last status update 186: ## is loaded. 187: 188: options[:complete] = "$('#{status_tag_id}').innerHTML='#{escape_javascript upload_progress_text(:finish)}';" 189: options[:complete] << "#{upload_progress_update_bar_js(100)};" 190: options[:complete] << "#{upload_update_object} = null" 191: options[:complete] = "#{options[:complete]}; #{options[:finish]}" if options[:finish] 192: 193: options[:script] = true 194: 195: ## Prepare the periodic updater, clearing any previous updater 196: 197: updater = "if (#{upload_update_object}) { #{upload_update_object}.stop(); }" 198: updater << "#{upload_update_object} = new Ajax.PeriodicalUpdater('#{status_tag_id}'," 199: updater << "'#{status_url}', #{options_for_ajax(options)}.extend(#{updater_options}))" 200: 201: updater = "#{options[:begin]}; #{updater}" if options[:begin] 202: updater = "#{upload_progress_update_bar_js(0)}; #{updater}" 203: updater = "$('#{status_tag_id}').innerHTML='#{escape_javascript upload_progress_text(:begin)}'; #{updater}" 204: 205: ## Touch up the form action and target to use the given target instead of the 206: ## default one. Then start the updater 207: 208: options[:onsubmit] = "if (this.action.indexOf('upload_id') < 0){ this.action += '#{escape_javascript upload_id_param}'; }" 209: options[:onsubmit] << "this.target = '#{escape_javascript upload_target}';" 210: options[:onsubmit] << "#{updater}; return true" 211: options[:multipart] = true 212: 213: [:begin, :finish, :complete, :frequency, :decay, :script].each { |sym| options.delete(sym) } 214: 215: ## Create the tags 216: ## If a target for the form is given then avoid creating the hidden IFRAME 217: 218: tag = form_tag(upload_action_url, options, *parameters_for_url_method) 219: 220: unless options[:target] 221: tag << content_tag('iframe', '', { 222: :id => upload_target, 223: :name => upload_target, 224: :src => '', 225: :style => 'width:0px;height:0px;border:0' 226: }) 227: end 228: 229: tag 230: end
The text and Javascript returned by the default upload_status controller action which will replace the contents of the div created by upload_status_text_tag and grow the progress bar background to the appropriate width.
See upload_progress_text and upload_progress_update_bar_js
# File vendor/rails/actionpack/lib/action_view/helpers/upload_progress_helper.rb, line 362 362: def upload_progress_status 363: "#{upload_progress_text}<script>#{upload_progress_update_bar_js}</script>" 364: end
Generates a nicely formatted string of current upload progress for +UploadProgress::Progress+ object progress. Addtionally, it will return "Upload starting…" if progress has not been initialized, "Receiving data…" if there is no received data yet, and "Upload finished" when all data has been sent.
You can overload this method to add you own output to the
Example return: 223.5 KB of 1.5 MB at 321.2 KB/s; less than 10 seconds remaining
# File vendor/rails/actionpack/lib/action_view/helpers/upload_progress_helper.rb, line 400 400: def upload_progress_text(state=nil) 401: eval case 402: when state then @@default_messages[state.to_sym] 403: when upload_progress.nil? || !upload_progress.started? then @@default_messages[:begin] 404: when upload_progress.finished? then @@default_messages[:finish] 405: else @@default_messages[:update] 406: end 407: end
Javascript helper that will create a script that will change the width of the background progress bar container. Include this in the script portion of your view rendered by your upload_status action to automatically find and update the progress bar.
Example (in controller):
def upload_status render :inline => "<script><%= update_upload_progress_bar_js %></script>", :layout => false end
# File vendor/rails/actionpack/lib/action_view/helpers/upload_progress_helper.rb, line 378 378: def upload_progress_update_bar_js(percent=nil) 379: progress = upload_progress 380: percent ||= case 381: when progress.nil? || !progress.started? then 0 382: when progress.finished? then 100 383: else progress.completed_percent 384: end.to_i 385: 386: # TODO do animation instead of jumping 387: "$('#{progress_bar_id}').firstChild.firstChild.style.width='#{percent}%'" 388: end
Content helper that will create the element tree that can be easily styled with CSS to create a progress bar effect. The containers are defined as:
<div class="progressBar" id="#{progress_bar_id}"> <div class="border"> <div class="background"> <div class="content"> </div> </div> </div> </div>
The content parameter will be included in the inner most div when rendered.
The options will create attributes on the outer most div. To use a different CSS class, pass a different class option.
Example:
<%= upload_status_progress_bar_tag('', {:class => 'progress'}) %>
Example CSS:
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%; }
# File vendor/rails/actionpack/lib/action_view/helpers/upload_progress_helper.rb, line 345 345: def upload_status_progress_bar_tag(content='', options={}) 346: css = [options[:class], 'progressBar'].compact.join(' ') 347: 348: content_tag("div", 349: content_tag("div", 350: content_tag("div", 351: content_tag("div", content, :class => 'foreground'), 352: :class => 'background'), 353: :class => 'border'), 354: {:id => progress_bar_id}.merge(options).merge({:class => css})) 355: end
Renders the HTML to contain the upload progress bar above the default messages
Use this method to display the upload status after your form_tag_with_upload_progress
# File vendor/rails/actionpack/lib/action_view/helpers/upload_progress_helper.rb, line 282 282: def upload_status_tag(content='', options={}) 283: upload_status_progress_bar_tag + upload_status_text_tag(content, options) 284: end
Content helper that will create a div with the proper ID and class that will contain the contents returned by the upload_status action. The container is defined as
<div id="#{status_tag_id}" class="uploadStatus"> </div>
Style this container by selecting the +.uploadStatus+ CSS class.
The content parameter will be included in the inner most div when rendered.
The options will create attributes on the outer most div. To use a different CSS class, pass a different class option.
Example CSS:
.uploadStatus { font-size: 10px; color: grey; }
# File vendor/rails/actionpack/lib/action_view/helpers/upload_progress_helper.rb, line 303 303: def upload_status_text_tag(content=nil, options={}) 304: content_tag("div", content, {:id => status_tag_id, :class => 'uploadStatus'}.merge(options)) 305: end