actionpack 1.8.1 → 1.9.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionpack might be problematic. Click here for more details.
- data/CHANGELOG +309 -16
- data/README +1 -1
- data/lib/action_controller.rb +5 -0
- data/lib/action_controller/assertions.rb +57 -12
- data/lib/action_controller/auto_complete.rb +47 -0
- data/lib/action_controller/base.rb +288 -258
- data/lib/action_controller/benchmarking.rb +8 -3
- data/lib/action_controller/caching.rb +88 -42
- data/lib/action_controller/cgi_ext/cgi_ext.rb +1 -1
- data/lib/action_controller/cgi_ext/cgi_methods.rb +41 -11
- data/lib/action_controller/cgi_ext/multipart_progress.rb +169 -0
- data/lib/action_controller/cgi_ext/raw_post_data_fix.rb +30 -12
- data/lib/action_controller/cgi_process.rb +39 -11
- data/lib/action_controller/code_generation.rb +235 -0
- data/lib/action_controller/cookies.rb +14 -8
- data/lib/action_controller/deprecated_renders_and_redirects.rb +76 -0
- data/lib/action_controller/filters.rb +8 -7
- data/lib/action_controller/helpers.rb +41 -6
- data/lib/action_controller/layout.rb +45 -16
- data/lib/action_controller/request.rb +86 -23
- data/lib/action_controller/rescue.rb +1 -0
- data/lib/action_controller/response.rb +1 -1
- data/lib/action_controller/routing.rb +536 -272
- data/lib/action_controller/scaffolding.rb +30 -25
- data/lib/action_controller/session/active_record_store.rb +251 -50
- data/lib/action_controller/streaming.rb +133 -0
- data/lib/action_controller/templates/rescues/_request_and_response.rhtml +0 -7
- data/lib/action_controller/templates/scaffolds/edit.rhtml +2 -2
- data/lib/action_controller/templates/scaffolds/layout.rhtml +22 -18
- data/lib/action_controller/templates/scaffolds/list.rhtml +3 -3
- data/lib/action_controller/templates/scaffolds/new.rhtml +2 -2
- data/lib/action_controller/templates/scaffolds/show.rhtml +1 -1
- data/lib/action_controller/test_process.rb +68 -47
- data/lib/action_controller/upload_progress.rb +421 -0
- data/lib/action_controller/url_rewriter.rb +8 -11
- data/lib/action_controller/vendor/html-scanner/html/document.rb +6 -5
- data/lib/action_controller/vendor/html-scanner/html/node.rb +70 -14
- data/lib/action_controller/vendor/html-scanner/html/tokenizer.rb +17 -10
- data/lib/action_controller/vendor/html-scanner/html/version.rb +3 -3
- data/lib/action_controller/vendor/xml_simple.rb +1019 -0
- data/lib/action_controller/verification.rb +36 -30
- data/lib/action_view/base.rb +21 -14
- data/lib/action_view/helpers/active_record_helper.rb +15 -13
- data/lib/action_view/helpers/asset_tag_helper.rb +26 -9
- data/lib/action_view/helpers/benchmark_helper.rb +24 -0
- data/lib/action_view/helpers/capture_helper.rb +7 -5
- data/lib/action_view/helpers/date_helper.rb +63 -46
- data/lib/action_view/helpers/form_helper.rb +7 -1
- data/lib/action_view/helpers/form_options_helper.rb +19 -11
- data/lib/action_view/helpers/form_tag_helper.rb +5 -1
- data/lib/action_view/helpers/javascript_helper.rb +403 -35
- data/lib/action_view/helpers/javascripts/controls.js +261 -0
- data/lib/action_view/helpers/javascripts/dragdrop.js +476 -0
- data/lib/action_view/helpers/javascripts/effects.js +570 -0
- data/lib/action_view/helpers/javascripts/prototype.js +633 -371
- data/lib/action_view/helpers/number_helper.rb +11 -13
- data/lib/action_view/helpers/tag_helper.rb +1 -2
- data/lib/action_view/helpers/text_helper.rb +69 -6
- data/lib/action_view/helpers/upload_progress_helper.rb +433 -0
- data/lib/action_view/helpers/url_helper.rb +98 -3
- data/lib/action_view/partials.rb +14 -8
- data/lib/action_view/vendor/builder/xmlmarkup.rb +11 -0
- data/rakefile +13 -5
- data/test/abstract_unit.rb +1 -1
- data/test/controller/action_pack_assertions_test.rb +52 -9
- data/test/controller/active_record_assertions_test.rb +119 -120
- data/test/controller/active_record_store_test.rb +111 -0
- data/test/controller/addresses_render_test.rb +45 -0
- data/test/controller/caching_filestore.rb +92 -0
- data/test/controller/capture_test.rb +39 -0
- data/test/controller/cgi_test.rb +40 -3
- data/test/controller/helper_test.rb +65 -13
- data/test/controller/multipart_progress_testx.rb +365 -0
- data/test/controller/new_render_test.rb +263 -0
- data/test/controller/redirect_test.rb +64 -0
- data/test/controller/render_test.rb +20 -21
- data/test/controller/request_test.rb +83 -3
- data/test/controller/routing_test.rb +702 -0
- data/test/controller/send_file_test.rb +2 -0
- data/test/controller/test_test.rb +44 -8
- data/test/controller/upload_progress_testx.rb +89 -0
- data/test/controller/verification_test.rb +94 -29
- data/test/fixtures/addresses/list.rhtml +1 -0
- data/test/fixtures/test/capturing.rhtml +4 -0
- data/test/fixtures/test/list.rhtml +1 -1
- data/test/fixtures/test/update_element_with_capture.rhtml +9 -0
- data/test/template/active_record_helper_test.rb +30 -15
- data/test/template/asset_tag_helper_test.rb +12 -5
- data/test/template/benchmark_helper_test.rb +72 -0
- data/test/template/date_helper_test.rb +69 -0
- data/test/template/form_helper_test.rb +18 -10
- data/test/template/form_options_helper_test.rb +40 -5
- data/test/template/javascript_helper.rb +149 -2
- data/test/template/number_helper_test.rb +2 -0
- data/test/template/tag_helper_test.rb +4 -0
- data/test/template/text_helper_test.rb +36 -0
- data/test/template/upload_progress_helper_testx.rb +272 -0
- data/test/template/url_helper_test.rb +30 -0
- metadata +30 -6
- data/test/controller/layout_test.rb +0 -49
- data/test/controller/routing_tests.rb +0 -543
@@ -8,20 +8,18 @@ module ActionView
|
|
8
8
|
# The area code can be surrounded by parenthesis by setting +:area_code+ to true; default is false
|
9
9
|
# The delimiter can be set using +:delimiter+; default is "-"
|
10
10
|
# Examples:
|
11
|
-
# number_to_phone(1235551234)
|
12
|
-
# number_to_phone(1235551234, {:area_code => true})
|
13
|
-
# number_to_phone(1235551234, {:delimiter => " "})
|
11
|
+
# number_to_phone(1235551234) => 123-555-1234
|
12
|
+
# number_to_phone(1235551234, {:area_code => true}) => (123) 555-1234
|
13
|
+
# number_to_phone(1235551234, {:delimiter => " "}) => 123 555 1234
|
14
|
+
# number_to_phone(1235551234, {:area_code => true, :extension => 555}) => (123) 555-1234 x 555
|
14
15
|
def number_to_phone(number, options = {})
|
15
|
-
options
|
16
|
+
options = options.stringify_keys
|
16
17
|
area_code = options.delete("area_code") { false }
|
17
18
|
delimiter = options.delete("delimiter") { "-" }
|
19
|
+
extension = options.delete("extension") { "" }
|
18
20
|
begin
|
19
|
-
str = number.to_s
|
20
|
-
|
21
|
-
str.gsub!(/([0-9]{3})([0-9]{3})([0-9]{4})/,"(\\1) \\2#{delimiter}\\3")
|
22
|
-
else
|
23
|
-
str.gsub!(/([0-9]{3})([0-9]{3})([0-9]{4})/,"\\1#{delimiter}\\2#{delimiter}\\3")
|
24
|
-
end
|
21
|
+
str = area_code == true ? number.to_s.gsub(/([0-9]{3})([0-9]{3})([0-9]{4})/,"(\\1) \\2#{delimiter}\\3") : number.to_s.gsub(/([0-9]{3})([0-9]{3})([0-9]{4})/,"\\1#{delimiter}\\2#{delimiter}\\3")
|
22
|
+
extension.to_s.strip.empty? ? str : "#{str} x #{extension.to_s.strip}"
|
25
23
|
rescue
|
26
24
|
number
|
27
25
|
end
|
@@ -51,9 +49,9 @@ module ActionView
|
|
51
49
|
# The +number+ can contain a level of precision using the +precision+ key; default is 3
|
52
50
|
# The unit separator can be set using the +separator+ key; default is "."
|
53
51
|
# Examples:
|
54
|
-
#
|
55
|
-
#
|
56
|
-
#
|
52
|
+
# number_to_percentage(100) => 100.000%
|
53
|
+
# number_to_percentage(100, {:precision => 0}) => 100%
|
54
|
+
# number_to_percentage(302.0574, {:precision => 2}) => 302.06%
|
57
55
|
def number_to_percentage(number, options = {})
|
58
56
|
options = options.stringify_keys
|
59
57
|
precision, separator = options.delete("precision") { 3 }, options.delete("separator") { "." }
|
@@ -25,8 +25,7 @@ module ActionView
|
|
25
25
|
private
|
26
26
|
def tag_options(options)
|
27
27
|
unless options.empty?
|
28
|
-
options.symbolize_keys
|
29
|
-
" " + options.map { |key, value|
|
28
|
+
" " + options.symbolize_keys.map { |key, value|
|
30
29
|
%(#{key}="#{html_escape(value.to_s)}")
|
31
30
|
}.sort.join(" ")
|
32
31
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/tag_helper'
|
2
|
+
|
1
3
|
module ActionView
|
2
4
|
module Helpers #:nodoc:
|
3
5
|
# Provides a set of methods for working with text strings that can help unburden the level of inline Ruby code in the
|
@@ -26,7 +28,7 @@ module ActionView
|
|
26
28
|
# passing +highlighter+ as single-quoted string with \1 where the phrase is supposed to be inserted.
|
27
29
|
# N.B.: The +phrase+ is sanitized to include only letters, digits, and spaces before use.
|
28
30
|
def highlight(text, phrase, highlighter = '<strong class="highlight">\1</strong>')
|
29
|
-
if
|
31
|
+
if phrase.blank? then return text end
|
30
32
|
text.gsub(/(#{escape_regexp(phrase)})/i, highlighter) unless text.nil?
|
31
33
|
end
|
32
34
|
|
@@ -63,6 +65,11 @@ module ActionView
|
|
63
65
|
end
|
64
66
|
end
|
65
67
|
|
68
|
+
# Word wrap long lines to line_width.
|
69
|
+
def word_wrap(text, line_width = 80)
|
70
|
+
text.gsub(/\n/, "\n\n").gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip
|
71
|
+
end
|
72
|
+
|
66
73
|
begin
|
67
74
|
require "redcloth"
|
68
75
|
|
@@ -116,11 +123,11 @@ module ActionView
|
|
116
123
|
# auto_link("Go to http://www.rubyonrails.com and say hello to david@loudthinking.com") =>
|
117
124
|
# Go to <a href="http://www.rubyonrails.com">http://www.rubyonrails.com</a> and
|
118
125
|
# say hello to <a href="mailto:david@loudthinking.com">david@loudthinking.com</a>
|
119
|
-
def auto_link(text, link = :all)
|
126
|
+
def auto_link(text, link = :all, href_options = {})
|
120
127
|
case link
|
121
|
-
when :all then auto_link_urls(auto_link_email_addresses(text))
|
128
|
+
when :all then auto_link_urls(auto_link_email_addresses(text), href_options)
|
122
129
|
when :email_addresses then auto_link_email_addresses(text)
|
123
|
-
when :urls then auto_link_urls(text)
|
130
|
+
when :urls then auto_link_urls(text, href_options)
|
124
131
|
end
|
125
132
|
end
|
126
133
|
|
@@ -128,6 +135,61 @@ module ActionView
|
|
128
135
|
def strip_links(text)
|
129
136
|
text.gsub(/<a.*>(.*)<\/a>/m, '\1')
|
130
137
|
end
|
138
|
+
|
139
|
+
# Try to require the html-scanner library
|
140
|
+
begin
|
141
|
+
require 'html/tokenizer'
|
142
|
+
require 'html/node'
|
143
|
+
rescue LoadError
|
144
|
+
# if there isn't a copy installed, use the vendor version in
|
145
|
+
# action controller
|
146
|
+
$:.unshift File.join(File.dirname(__FILE__), "..", "..",
|
147
|
+
"action_controller", "vendor", "html-scanner")
|
148
|
+
require 'html/tokenizer'
|
149
|
+
require 'html/node'
|
150
|
+
end
|
151
|
+
|
152
|
+
VERBOTEN_TAGS = %w(form script) unless defined?(VERBOTEN_TAGS)
|
153
|
+
VERBOTEN_ATTRS = /^on/i unless defined?(VERBOTEN_ATTRS)
|
154
|
+
|
155
|
+
# Sanitizes the given HTML by making form and script tags into regular
|
156
|
+
# text, and removing all "onxxx" attributes (so that arbitrary Javascript
|
157
|
+
# cannot be executed). Also removes href attributes that start with
|
158
|
+
# "javascript:".
|
159
|
+
#
|
160
|
+
# Returns the sanitized text.
|
161
|
+
def sanitize(html)
|
162
|
+
# only do this if absolutely necessary
|
163
|
+
if html.index("<")
|
164
|
+
tokenizer = HTML::Tokenizer.new(html)
|
165
|
+
new_text = ""
|
166
|
+
|
167
|
+
while token = tokenizer.next
|
168
|
+
node = HTML::Node.parse(nil, 0, 0, token, false)
|
169
|
+
new_text << case node
|
170
|
+
when HTML::Tag
|
171
|
+
if VERBOTEN_TAGS.include?(node.name)
|
172
|
+
node.to_s.gsub(/</, "<")
|
173
|
+
else
|
174
|
+
if node.closing != :close
|
175
|
+
node.attributes.delete_if { |attr,v| attr =~ VERBOTEN_ATTRS }
|
176
|
+
if node.attributes["href"] =~ /^javascript:/i
|
177
|
+
node.attributes.delete "href"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
node.to_s
|
181
|
+
end
|
182
|
+
else
|
183
|
+
node.to_s.gsub(/</, "<")
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
html = new_text
|
188
|
+
end
|
189
|
+
|
190
|
+
html
|
191
|
+
end
|
192
|
+
|
131
193
|
|
132
194
|
private
|
133
195
|
# Returns a version of the text that's safe to use in a regular expression without triggering engine features.
|
@@ -136,13 +198,14 @@ module ActionView
|
|
136
198
|
end
|
137
199
|
|
138
200
|
# Turns all urls into clickable links.
|
139
|
-
def auto_link_urls(text)
|
201
|
+
def auto_link_urls(text, href_options = {})
|
140
202
|
text.gsub(/(<\w+.*?>|[^=!:'"\/]|^)((?:http[s]?:\/\/)|(?:www\.))([^\s<]+\/?)([[:punct:]]|\s|<|$)/) do
|
141
203
|
all, a, b, c, d = $&, $1, $2, $3, $4
|
142
204
|
if a =~ /<a\s/i # don't replace URL's that are already linked
|
143
205
|
all
|
144
206
|
else
|
145
|
-
|
207
|
+
extra_options = tag_options(href_options.stringify_keys) || ""
|
208
|
+
%(#{a}<a href="#{b=="www."?"http://www.":b}#{c}"#{extra_options}>#{b}#{c}</a>#{d})
|
146
209
|
end
|
147
210
|
end
|
148
211
|
end
|
@@ -0,0 +1,433 @@
|
|
1
|
+
module ActionView
|
2
|
+
module Helpers
|
3
|
+
# == THIS IS AN EXPERIMENTAL FEATURE
|
4
|
+
#
|
5
|
+
# Which means that it doesn't yet work on all systems. We're still working on full
|
6
|
+
# compatibility. It's thus not advised to use this unless you've verified it to work
|
7
|
+
# fully on all the systems that is a part of your environment. Consider this an extended
|
8
|
+
# preview.
|
9
|
+
#
|
10
|
+
# Provides a set of methods to be used in your views to help with the
|
11
|
+
# rendering of Ajax enabled status updating during a multipart upload.
|
12
|
+
#
|
13
|
+
# The basic mechanism for upload progress is that the form will post to a
|
14
|
+
# hidden <iframe> element, then poll a status action that will replace the
|
15
|
+
# contents of a status container. Client Javascript hooks are provided for
|
16
|
+
# +begin+ and +finish+ of the update.
|
17
|
+
#
|
18
|
+
# If you wish to have a DTD that will validate this page, use XHTML
|
19
|
+
# Transitional because this DTD supports the <iframe> element.
|
20
|
+
#
|
21
|
+
# == Typical usage
|
22
|
+
#
|
23
|
+
# In your upload view:
|
24
|
+
#
|
25
|
+
# <%= form_tag_with_upload_progress({ :action => 'create' }) %>
|
26
|
+
# <%= file_field "document", "file" %>
|
27
|
+
# <%= submit_tag "Upload" %>
|
28
|
+
# <%= upload_status_tag %>
|
29
|
+
# <%= end_form_tag %>
|
30
|
+
#
|
31
|
+
# In your controller:
|
32
|
+
#
|
33
|
+
# class DocumentController < ApplicationController
|
34
|
+
# upload_status_for :create
|
35
|
+
#
|
36
|
+
# def create
|
37
|
+
# # ... Your document creation action
|
38
|
+
# end
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# == Javascript callback on begin and finished
|
42
|
+
#
|
43
|
+
# In your upload view:
|
44
|
+
#
|
45
|
+
# <%= form_tag_with_upload_progress({ :action => 'create' }, {
|
46
|
+
# :begin => "alert('upload beginning'),
|
47
|
+
# :finish => "alert('upload finished')}) %>
|
48
|
+
# <%= file_field "document", "file" %>
|
49
|
+
# <%= submit_tag "Upload" %>
|
50
|
+
# <%= upload_status_tag %>
|
51
|
+
# <%= end_form_tag %>
|
52
|
+
#
|
53
|
+
#
|
54
|
+
# == CSS Styling of the status text and progress bar
|
55
|
+
#
|
56
|
+
# See +upload_status_text_tag+ and +upload_status_progress_bar_tag+ for references
|
57
|
+
# of the IDs and CSS classes used.
|
58
|
+
#
|
59
|
+
# Default styling is included with the scaffolding CSS.
|
60
|
+
module UploadProgressHelper
|
61
|
+
unless const_defined? :FREQUENCY
|
62
|
+
# Default number of seconds between client updates
|
63
|
+
FREQUENCY = 2.0
|
64
|
+
|
65
|
+
# Factor to decrease the frequency when the +upload_status+ action returns the same results
|
66
|
+
# To disable update decay, set this constant to +false+
|
67
|
+
FREQUENCY_DECAY = 1.8
|
68
|
+
end
|
69
|
+
|
70
|
+
# Contains a hash of status messages used for localization of
|
71
|
+
# +upload_progress_status+ and +upload_progress_text+. Each string is
|
72
|
+
# evaluated in the helper method context so you can include your own
|
73
|
+
# calculations and string iterpolations.
|
74
|
+
#
|
75
|
+
# The following keys are defined:
|
76
|
+
#
|
77
|
+
# <tt>:begin</tt>:: Displayed before the first byte is received on the server
|
78
|
+
# <tt>:update</tt>:: Contains a human representation of the upload progress
|
79
|
+
# <tt>:finish</tt>:: Displayed when the file upload is complete, before the action has completed. If you are performing extra activity in your action such as processing of the upload, then inform the user of what you are doing by setting +upload_progress.message+
|
80
|
+
#
|
81
|
+
@@default_messages = {
|
82
|
+
:begin => '"Upload starting..."',
|
83
|
+
:update => '"#{human_size(upload_progress.received_bytes)} of #{human_size(upload_progress.total_bytes)} at #{human_size(upload_progress.bitrate)}/s; #{distance_of_time_in_words(0,upload_progress.remaining_seconds,true)} remaining"',
|
84
|
+
:finish => 'upload_progress.message.blank? ? "Upload finished." : upload_progress.message',
|
85
|
+
}
|
86
|
+
|
87
|
+
|
88
|
+
# Creates a form tag and hidden <iframe> necessary for the upload progress
|
89
|
+
# status messages to be displayed in a designated +div+ on your page.
|
90
|
+
#
|
91
|
+
# == Initializations
|
92
|
+
#
|
93
|
+
# When the upload starts, the content created by +upload_status_tag+ will be filled out with
|
94
|
+
# "Upload starting...". When the upload is finished, "Upload finished." will be used. Every
|
95
|
+
# update inbetween the begin and finish events will be determined by the server +upload_status+
|
96
|
+
# action. Doing this automatically means that the user can use the same form to upload multiple
|
97
|
+
# files without refreshing page while still displaying a reasonable progress.
|
98
|
+
#
|
99
|
+
# == Upload IDs
|
100
|
+
#
|
101
|
+
# For the view and the controller to know about the same upload they must share
|
102
|
+
# a common +upload_id+. +form_tag_with_upload_progress+ prepares the next available
|
103
|
+
# +upload_id+ when called. There are other methods which use the +upload_id+ so the
|
104
|
+
# order in which you include your content is important. Any content that depends on the
|
105
|
+
# +upload_id+ will use the one defined +form_tag_with_upload_progress+
|
106
|
+
# otherwise you will need to explicitly declare the +upload_id+ shared among
|
107
|
+
# your progress elements.
|
108
|
+
#
|
109
|
+
# Status container after the form:
|
110
|
+
#
|
111
|
+
# <%= form_tag_with_upload_progress %>
|
112
|
+
# <%= end_form_tag %>
|
113
|
+
#
|
114
|
+
# <%= upload_status_tag %>
|
115
|
+
#
|
116
|
+
# Status container before form:
|
117
|
+
#
|
118
|
+
# <% my_upload_id = next_upload_id %>
|
119
|
+
#
|
120
|
+
# <%= upload_status_tag %>
|
121
|
+
#
|
122
|
+
# <%= form_tag_with_upload_progress :upload_id => my_upload_id %>
|
123
|
+
# <%= end_form_tag %>
|
124
|
+
#
|
125
|
+
# It is recommended that the helpers manage the +upload_id+ parameter.
|
126
|
+
#
|
127
|
+
# == Options
|
128
|
+
#
|
129
|
+
# +form_tag_with_upload_progress+ uses similar options as +form_tag+
|
130
|
+
# yet accepts another hash for the options used for the +upload_status+ action.
|
131
|
+
#
|
132
|
+
# <tt>url_for_options</tt>:: The same options used by +form_tag+ including:
|
133
|
+
# <tt>:upload_id</tt>:: the upload id used to uniquely identify this upload
|
134
|
+
#
|
135
|
+
# <tt>options</tt>:: similar options to +form_tag+ including:
|
136
|
+
# <tt>:begin</tt>:: Javascript code that executes before the first status update occurs.
|
137
|
+
# <tt>:finish</tt>:: Javascript code that executes after the action that receives the post returns.
|
138
|
+
# <tt>:frequency</tt>:: number of seconds between polls to the upload status action.
|
139
|
+
#
|
140
|
+
# <tt>status_url_for_options</tt>:: options passed to +url_for+ to build the url
|
141
|
+
# for the upload status action.
|
142
|
+
# <tt>:controller</tt>:: defines the controller to be used for a custom update status action
|
143
|
+
# <tt>:action</tt>:: defines the action to be used for a custom update status action
|
144
|
+
#
|
145
|
+
# Parameters passed to the action defined by status_url_for_options
|
146
|
+
#
|
147
|
+
# <tt>:upload_id</tt>:: the upload_id automatically generated by +form_tag_with_upload_progress+ or the user defined id passed to this method.
|
148
|
+
#
|
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
|
231
|
+
|
232
|
+
# This method must be called by the action that receives the form post
|
233
|
+
# with the +upload_progress+. By default this method is rendered when
|
234
|
+
# the controller declares that the action is the receiver of a
|
235
|
+
# +form_tag_with_upload_progress+ posting.
|
236
|
+
#
|
237
|
+
# This template will do a javascript redirect to the URL specified in +redirect_to+
|
238
|
+
# if this method is called anywhere in the controller action. When the action
|
239
|
+
# performs a redirect, the +finish+ handler will not be called.
|
240
|
+
#
|
241
|
+
# If there are errors in the action then you should set the controller
|
242
|
+
# instance variable +@errors+. The +@errors+ object will be
|
243
|
+
# converted to a javascript array from +@errors.full_messages+ and
|
244
|
+
# passed to the +finish+ handler of +form_tag_with_upload_progress+
|
245
|
+
#
|
246
|
+
# If no errors have occured, the parameter to the +finish+ handler will
|
247
|
+
# be +undefined+.
|
248
|
+
#
|
249
|
+
# == Example (in view)
|
250
|
+
#
|
251
|
+
# <script>
|
252
|
+
# function do_finish(errors) {
|
253
|
+
# if (errors) {
|
254
|
+
# alert(errors);
|
255
|
+
# }
|
256
|
+
# }
|
257
|
+
# </script>
|
258
|
+
#
|
259
|
+
# <%= form_tag_with_upload_progress {:action => 'create'}, {finish => 'do_finish(arguments[0])'} %>
|
260
|
+
#
|
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
|
277
|
+
|
278
|
+
# Renders the HTML to contain the upload progress bar above the
|
279
|
+
# default messages
|
280
|
+
#
|
281
|
+
# Use this method to display the upload status after your +form_tag_with_upload_progress+
|
282
|
+
def upload_status_tag(content='', options={})
|
283
|
+
upload_status_progress_bar_tag + upload_status_text_tag(content, options)
|
284
|
+
end
|
285
|
+
|
286
|
+
# Content helper that will create a +div+ with the proper ID and class that
|
287
|
+
# will contain the contents returned by the +upload_status+ action. The container
|
288
|
+
# is defined as
|
289
|
+
#
|
290
|
+
# <div id="#{status_tag_id}" class="uploadStatus"> </div>
|
291
|
+
#
|
292
|
+
# Style this container by selecting the +.uploadStatus+ +CSS+ class.
|
293
|
+
#
|
294
|
+
# The +content+ parameter will be included in the inner most +div+ when
|
295
|
+
# rendered.
|
296
|
+
#
|
297
|
+
# The +options+ will create attributes on the outer most div. To use a different
|
298
|
+
# +CSS+ class, pass a different class option.
|
299
|
+
#
|
300
|
+
# Example +CSS+:
|
301
|
+
# .uploadStatus { font-size: 10px; color: grey; }
|
302
|
+
#
|
303
|
+
def upload_status_text_tag(content=nil, options={})
|
304
|
+
content_tag("div", content, {:id => status_tag_id, :class => 'uploadStatus'}.merge(options))
|
305
|
+
end
|
306
|
+
|
307
|
+
# Content helper that will create the element tree that can be easily styled
|
308
|
+
# with +CSS+ to create a progress bar effect. The containers are defined as:
|
309
|
+
#
|
310
|
+
# <div class="progressBar" id="#{progress_bar_id}">
|
311
|
+
# <div class="border">
|
312
|
+
# <div class="background">
|
313
|
+
# <div class="content"> </div>
|
314
|
+
# </div>
|
315
|
+
# </div>
|
316
|
+
# </div>
|
317
|
+
#
|
318
|
+
# The +content+ parameter will be included in the inner most +div+ when
|
319
|
+
# rendered.
|
320
|
+
#
|
321
|
+
# The +options+ will create attributes on the outer most div. To use a different
|
322
|
+
# +CSS+ class, pass a different class option.
|
323
|
+
#
|
324
|
+
# Example:
|
325
|
+
# <%= upload_status_progress_bar_tag('', {:class => 'progress'}) %>
|
326
|
+
#
|
327
|
+
# Example +CSS+:
|
328
|
+
#
|
329
|
+
# div.progressBar {
|
330
|
+
# margin: 5px;
|
331
|
+
# }
|
332
|
+
#
|
333
|
+
# div.progressBar div.border {
|
334
|
+
# background-color: #fff;
|
335
|
+
# border: 1px solid grey;
|
336
|
+
# width: 100%;
|
337
|
+
# }
|
338
|
+
#
|
339
|
+
# div.progressBar div.background {
|
340
|
+
# background-color: #333;
|
341
|
+
# height: 18px;
|
342
|
+
# width: 0%;
|
343
|
+
# }
|
344
|
+
#
|
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
|
356
|
+
|
357
|
+
# The text and Javascript returned by the default +upload_status+ controller
|
358
|
+
# action which will replace the contents of the div created by +upload_status_text_tag+
|
359
|
+
# and grow the progress bar background to the appropriate width.
|
360
|
+
#
|
361
|
+
# See +upload_progress_text+ and +upload_progress_update_bar_js+
|
362
|
+
def upload_progress_status
|
363
|
+
"#{upload_progress_text}<script>#{upload_progress_update_bar_js}</script>"
|
364
|
+
end
|
365
|
+
|
366
|
+
# Javascript helper that will create a script that will change the width
|
367
|
+
# of the background progress bar container. Include this in the script
|
368
|
+
# portion of your view rendered by your +upload_status+ action to
|
369
|
+
# automatically find and update the progress bar.
|
370
|
+
#
|
371
|
+
# Example (in controller):
|
372
|
+
#
|
373
|
+
# def upload_status
|
374
|
+
# render :inline => "<script><%= update_upload_progress_bar_js %></script>", :layout => false
|
375
|
+
# end
|
376
|
+
#
|
377
|
+
#
|
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
|
389
|
+
|
390
|
+
# Generates a nicely formatted string of current upload progress for
|
391
|
+
# +UploadProgress::Progress+ object +progress+. Addtionally, it
|
392
|
+
# will return "Upload starting..." if progress has not been initialized,
|
393
|
+
# "Receiving data..." if there is no received data yet, and "Upload
|
394
|
+
# finished" when all data has been sent.
|
395
|
+
#
|
396
|
+
# You can overload this method to add you own output to the
|
397
|
+
#
|
398
|
+
# Example return: 223.5 KB of 1.5 MB at 321.2 KB/s; less than 10 seconds
|
399
|
+
# remaining
|
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
|
408
|
+
|
409
|
+
protected
|
410
|
+
# Javascript object used to contain the polling methods and keep track of
|
411
|
+
# the finished state
|
412
|
+
def upload_update_object
|
413
|
+
"document.uploadStatus#{current_upload_id}"
|
414
|
+
end
|
415
|
+
|
416
|
+
# Element ID of the progress bar
|
417
|
+
def progress_bar_id
|
418
|
+
"UploadProgressBar#{current_upload_id}"
|
419
|
+
end
|
420
|
+
|
421
|
+
# Element ID of the progress status container
|
422
|
+
def status_tag_id
|
423
|
+
"UploadStatus#{current_upload_id}"
|
424
|
+
end
|
425
|
+
|
426
|
+
# Element ID of the target <iframe> used as the target of the form
|
427
|
+
def upload_target_id
|
428
|
+
"UploadTarget#{current_upload_id}"
|
429
|
+
end
|
430
|
+
|
431
|
+
end
|
432
|
+
end
|
433
|
+
end
|