upload_progress.rb
上传用户:netsea168
上传日期:2022-07-22
资源大小:4652k
文件大小:11k
源码类别:

Ajax

开发平台:

Others

  1. module UploadProgress
  2.   def self.append_features(base) #:nodoc:
  3.     super
  4.     base.extend(ClassMethods)
  5.     base.helper_method :upload_progress, :next_upload_id, :last_upload_id, :current_upload_id
  6.   end
  7.   # == Action Pack Upload Progress for multipart uploads
  8.   #
  9.   # The UploadProgress module aids in the process of viewing an Ajax driven
  10.   # upload status when working with multipart forms.  It offers a macro that
  11.   # will prepare an action for handling the cleanup of the Ajax updating including
  12.   # passing the redirect URL and custom parameters to the Javascript finish handler.
  13.   #
  14.   # UploadProgress is available for all multipart uploads when the +upload_status_for+
  15.   # macro is called in one of your controllers.
  16.   #
  17.   # The progress is stored as an UploadProgress::Progress object in the session and
  18.   # is accessible in the controller and view with the +upload_progress+ method.
  19.   #
  20.   # For help rendering the UploadProgress enabled form and supported elements, see
  21.   # ActionView::Helpers::UploadProgressHelper.
  22.   # 
  23.   # === Automatic updating on upload actions
  24.   #
  25.   #   class DocumentController < ApplicationController   
  26.   #     upload_status_for  :create
  27.   #     
  28.   #     def create
  29.   #       # ... Your document creation action
  30.   #     end
  31.   #   end
  32.   #
  33.   # The +upload_status_for+ macro will override the rendering of the action passed
  34.   # if +upload_id+ is found in the query string.  This allows for default
  35.   # behavior if Javascript is disabled.  If you are tracking the upload progress
  36.   # then +create+ will now return the cleanup scripts that will terminate the polling
  37.   # of the upload status.
  38.   #
  39.   # === Customized status rendering
  40.   #
  41.   #   class DocumentController < ApplicationController   
  42.   #     upload_status_for  :create, :status => :custom_status
  43.   #     
  44.   #     def create
  45.   #       # ... Your document creation action
  46.   #     end
  47.   #
  48.   #     def custom_status
  49.   #       # ... Override this action to return content to be replaced in
  50.   #       # the status container
  51.   #       render :inline => "<%= upload_progress.completed_percent rescue 0 %> % complete", :layout => false
  52.   #   end
  53.   #
  54.   # The default status action is +upload_status+.  The results of this action
  55.   # are added used to replace the contents of the HTML elements defined in
  56.   # +upload_status_tag+.  Within +upload_status+, you can load the Progress
  57.   # object from the session with the +upload_progress+ method and display your own
  58.   # results.  
  59.   #
  60.   # Completion of the upload status updating occurs automatically with an +after_filter+ call to
  61.   # +finish_upload_status+.  Because the upload must be posted into a hidden IFRAME to enable
  62.   # Ajax updates during the upload, +finish_upload_status+ overwrites the results of any previous
  63.   # +render+ or +redirect_to+ so it can render the necessary Javascript that will properly terminate 
  64.   # the status updating loop, trigger the completion callback or redirect to the appropriate URL.
  65.   #
  66.   # ==== Basic Example (View):
  67.   #
  68.   #  <%= form_tag_with_upload_progress({:action => 'create'}, {:finish => 'alert("Document Uploaded")'}) %>
  69.   #  <%= upload_status_tag %>
  70.   #  <%= file_field 'document', 'file' %>
  71.   #  <%= end_form_tag %>
  72.   #
  73.   # ==== Basic Example (Controller):
  74.   #
  75.   #  class DocumentController < ApplicationController
  76.   #    upload_status_for :create
  77.   #
  78.   #    def create
  79.   #      @document = Document.create(params[:document])
  80.   #    end
  81.   #  end
  82.   #
  83.   # ==== Extended Example (View):
  84.   #
  85.   #  <%= form_tag_with_upload_progress({:action => 'create'}, {}, {:action => :custom_status}) %>
  86.   #  <%= upload_status_tag %>
  87.   #  <%= file_field 'document', 'file' %>
  88.   #  <%= submit_tag "Upload" %>
  89.   #  <%= end_form_tag %>
  90.   #
  91.   #  <%= form_tag_with_upload_progress({:action => 'add_preview'}, {:finish => 'alert(arguments[0])'}, {:action => :custom_status})  %>
  92.   #  <%= upload_status_tag %>
  93.   #  <%= submit_tag "Upload" %>
  94.   #  <%= file_field 'preview', 'file' %>
  95.   #  <%= end_form_tag %>
  96.   #
  97.   # ==== Extended Example (Controller):
  98.   #
  99.   #  class DocumentController < ApplicationController
  100.   #    upload_status_for :add_preview, :create, {:status => :custom_status}
  101.   #
  102.   #    def add_preview
  103.   #     @document = Document.find(params[:id])
  104.   #     @document.preview = Preview.create(params[:preview])
  105.   #     if @document.save
  106.   #       finish_upload_status "'Preview added'"
  107.   #     else
  108.   #       finish_upload_status "'Preview not added'"
  109.   #     end
  110.   #    end
  111.   #
  112.   #   def create
  113.   #     @document = Document.new(params[:document])
  114.   #
  115.   #     upload_progress.message = "Processing document..."
  116.   #     session.update
  117.   #
  118.   #     @document.save
  119.   #     redirect_to :action => 'show', :id => @document.id
  120.   #   end
  121.   #
  122.   #   def custom_status
  123.   #     render :inline => '<%= upload_progress_status %> <div>Updated at <%= Time.now %></div>', :layout => false
  124.   #   end
  125.   #
  126.   # ==== Environment checklist
  127.   #
  128.   # This is an experimental feature that requires a specific webserver environment.  Use the following checklist 
  129.   # to confirm that you have an environment that supports upload progress.
  130.   #
  131.   # ===== Ruby:
  132.   # 
  133.   # * Running the command `ruby -v` should print "ruby 1.8.2 (2004-12-25)" or older
  134.   # 
  135.   # ===== Web server:
  136.   # 
  137.   # * Apache 1.3, Apache 2.0 or Lighttpd *1.4* (need to build lighttpd from CVS)
  138.   # 
  139.   # ===== FastCGI bindings:
  140.   # 
  141.   # * > 0.8.6 and must be the compiled C version of the bindings
  142.   # * The command `ruby -e "p require('fcgi.so')"` should print "true"
  143.   # 
  144.   # ===== Apache/Lighttpd FastCGI directives:
  145.   # 
  146.   # * You must allow more than one FCGI server process to allow concurrent requests.
  147.   # * If there is only a single FCGI process you will not get the upload status updates.
  148.   # * You can check this by taking a look for running FCGI servers in your process list during a progress upload.
  149.   # * Apache directive: FastCGIConfig -minProcesses 2
  150.   # * Lighttpd directives taken from config/lighttpd.conf (min-procs):
  151.   # 
  152.   #     fastcgi.server = (
  153.   #      ".fcgi" => (
  154.   #       "APP_NAME" => (
  155.   #        "socket" => "/tmp/APP_NAME1.socket",
  156.   #        "bin-path" => "RAILS_ROOT/public/dispatch.fcgi",
  157.   #        "min-procs" => 2
  158.   #       )
  159.   #      )
  160.   #     )
  161.   # 
  162.   # ===== config/environment.rb:
  163.   # 
  164.   # * Add the following line to your config/environment.rb and restart your web server.
  165.   # * <tt>ActionController::Base.enable_upload_progress</tt>
  166.   # 
  167.   # ===== Development log:
  168.   # 
  169.   # * When the upload progress is enabled by you will find something the following lines:
  170.   # * "Multipart upload with progress (id: 1, size: 85464)"
  171.   # * "Finished processing multipart upload in 0.363729s"
  172.   # * If you are properly running multiple FCGI processes, then you will see multiple entries for rendering the "upload_status" action before the "Finish processing..." log entry.  This is a *good thing* :)
  173.   #
  174.   module ClassMethods
  175.     # Creates an +after_filter+ which will call +finish_upload_status+
  176.     # creating the document that will be loaded into the hidden IFRAME, terminating
  177.     # the status polling forms created with +form_with_upload_progress+.
  178.     #
  179.     # Also defines an action +upload_status+ or a action name passed as
  180.     # the <tt>:status</tt> option.  This status action must match the one expected
  181.     # in the +form_tag_with_upload_progress+ helper.
  182.     #
  183.     def upload_status_for(*actions)
  184.       after_filter :finish_upload_status, :only => actions
  185.       
  186.       define_method(actions.last.is_a?(Hash) && actions.last[:status] || :upload_status) do
  187.         render(:inline => '<%= upload_progress_status %>', :layout => false)
  188.       end
  189.     end
  190.   end
  191.   # Overwrites the body rendered if the upload comes from a form that tracks
  192.   # the progress of the upload.  After clearing the body and any redirects, this
  193.   # method then renders the helper +finish_upload_status+
  194.   #
  195.   # This method only needs to be called if you wish to pass a
  196.   # javascript parameter to your finish event handler that you optionally
  197.   # define in +form_with_upload_progress+
  198.   #
  199.   # === Parameter:
  200.   #
  201.   # client_js_argument:: a string containing a Javascript expression that will
  202.   #                      be evaluated and passed to your +finish+ handler of
  203.   #                      +form_tag_with_upload_progress+.
  204.   #
  205.   # You can pass a String, Number or Boolean.
  206.   #
  207.   # === Strings
  208.   #
  209.   # Strings contain Javascript code that will be evaluated on the client. If you 
  210.   # wish to pass a string to the client finish callback, you will need to include 
  211.   # quotes in the +client_js_argument+ you pass to this method.
  212.   #
  213.   # ==== Example
  214.   #
  215.   #   finish_upload_status(""Finished"")
  216.   #   finish_upload_status("'Finished #{@document.title}'")
  217.   #   finish_upload_status("{success: true, message: 'Done!'}")
  218.   #   finish_upload_status("function() { alert('Uploaded!'); }")
  219.   #
  220.   # === Numbers / Booleans
  221.   #
  222.   # Numbers and Booleans can either be passed as Number objects or string versions 
  223.   # of number objects as they are evaluated by Javascript the same way as in Ruby.
  224.   #
  225.   # ==== Example
  226.   #
  227.   #   finish_upload_status(0)
  228.   #   finish_upload_status(@document.file.size)
  229.   #   finish_upload_status("10")
  230.   #
  231.   # === Nil
  232.   #
  233.   # To pass +nil+ to the finish callback, use a string "undefined"
  234.   #
  235.   # ==== Example
  236.   #
  237.   #   finish_upload_status(@message || "undefined")
  238.   #
  239.   # == Redirection
  240.   #
  241.   # If you action performs a redirection then +finish_upload_status+ will recognize
  242.   # the redirection and properly create the Javascript to perform the redirection in
  243.   # the proper location.
  244.   #
  245.   # It is possible to redirect and pass a parameter to the finish callback.
  246.   #
  247.   # ==== Example
  248.   #
  249.   #   redirect_to :action => 'show', :id => @document.id
  250.   #   finish_upload_status("'Redirecting you to your new file'")
  251.   #
  252.   #
  253.   def finish_upload_status(client_js_argument='')
  254.     if not @rendered_finish_upload_status and params[:upload_id]
  255.       @rendered_finish_upload_status = true
  256.       erase_render_results
  257.       location = erase_redirect_results || ''
  258.       ## TODO determine if #inspect is the appropriate way to marshall values
  259.       ## in inline templates
  260.       template = "<%= finish_upload_status({"
  261.       template << ":client_js_argument => #{client_js_argument.inspect}, "
  262.       template << ":redirect_to => #{location.to_s.inspect}, "
  263.       template << "}) %>"
  264.       render({ :inline => template, :layout => false })
  265.     end
  266.   end
  267.   # Returns and saves the next unique +upload_id+ in the instance variable
  268.   # <tt>@upload_id</tt>
  269.   def next_upload_id
  270.     @upload_id = last_upload_id.succ
  271.   end
  272.   # Either returns the last saved +upload_id+ or looks in the session
  273.   # for the last used +upload_id+ and saves it as the intance variable
  274.   # <tt>@upload_id</tt>
  275.   def last_upload_id
  276.     @upload_id ||= ((session[:uploads] || {}).keys.map{|k| k.to_i}.sort.last || 0).to_s
  277.   end
  278.   # Returns the +upload_id+ from the query parameters or if it cannot be found
  279.   # in the query parameters, then return the +last_upload_id+
  280.   def current_upload_id
  281.     params[:upload_id] or last_upload_id
  282.   end
  283.   
  284.   # Get the UploadProgress::Progress object for the supplied +upload_id+ from the
  285.   # session. If no +upload_id+ is given, then use the +current_upload_id+
  286.   #
  287.   # If an UploadProgress::Progress object cannot be found, a new instance will be
  288.   # returned with <code>total_bytes == 0</code>, <code>started? == false</code>, 
  289.   # and <code>finished? == true</code>.
  290.   def upload_progress(upload_id = nil)
  291.     upload_id ||= current_upload_id
  292.     session[:uploads] && session[:uploads][upload_id] || UploadProgress::Progress.new(0)
  293.   end
  294. end