actionview 5.2.3 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of actionview might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +196 -67
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -2
- data/lib/action_view.rb +3 -2
- data/lib/action_view/base.rb +107 -10
- data/lib/action_view/buffers.rb +15 -0
- data/lib/action_view/cache_expiry.rb +54 -0
- data/lib/action_view/context.rb +5 -9
- data/lib/action_view/digestor.rb +12 -20
- data/lib/action_view/gem_version.rb +3 -3
- data/lib/action_view/helpers.rb +0 -2
- data/lib/action_view/helpers/asset_tag_helper.rb +7 -30
- data/lib/action_view/helpers/asset_url_helper.rb +4 -3
- data/lib/action_view/helpers/cache_helper.rb +18 -10
- data/lib/action_view/helpers/capture_helper.rb +4 -0
- data/lib/action_view/helpers/csp_helper.rb +4 -2
- data/lib/action_view/helpers/csrf_helper.rb +1 -1
- data/lib/action_view/helpers/date_helper.rb +69 -25
- data/lib/action_view/helpers/form_helper.rb +240 -8
- data/lib/action_view/helpers/form_options_helper.rb +27 -18
- data/lib/action_view/helpers/form_tag_helper.rb +12 -9
- data/lib/action_view/helpers/javascript_helper.rb +9 -8
- data/lib/action_view/helpers/number_helper.rb +5 -0
- data/lib/action_view/helpers/output_safety_helper.rb +1 -1
- data/lib/action_view/helpers/rendering_helper.rb +6 -4
- data/lib/action_view/helpers/sanitize_helper.rb +12 -18
- data/lib/action_view/helpers/tag_helper.rb +7 -6
- data/lib/action_view/helpers/tags/base.rb +9 -5
- data/lib/action_view/helpers/tags/color_field.rb +1 -1
- data/lib/action_view/helpers/tags/translator.rb +1 -6
- data/lib/action_view/helpers/text_helper.rb +3 -3
- data/lib/action_view/helpers/translation_helper.rb +16 -12
- data/lib/action_view/helpers/url_helper.rb +15 -15
- data/lib/action_view/layouts.rb +5 -5
- data/lib/action_view/log_subscriber.rb +6 -6
- data/lib/action_view/lookup_context.rb +73 -31
- data/lib/action_view/path_set.rb +5 -10
- data/lib/action_view/railtie.rb +24 -1
- data/lib/action_view/record_identifier.rb +2 -2
- data/lib/action_view/renderer/abstract_renderer.rb +56 -3
- data/lib/action_view/renderer/partial_renderer.rb +66 -55
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +62 -16
- data/lib/action_view/renderer/renderer.rb +16 -4
- data/lib/action_view/renderer/streaming_template_renderer.rb +5 -5
- data/lib/action_view/renderer/template_renderer.rb +24 -18
- data/lib/action_view/rendering.rb +51 -31
- data/lib/action_view/routing_url_for.rb +12 -11
- data/lib/action_view/template.rb +102 -70
- data/lib/action_view/template/error.rb +21 -1
- data/lib/action_view/template/handlers.rb +27 -1
- data/lib/action_view/template/handlers/builder.rb +2 -2
- data/lib/action_view/template/handlers/erb.rb +17 -7
- data/lib/action_view/template/handlers/erb/erubi.rb +7 -3
- data/lib/action_view/template/handlers/html.rb +1 -1
- data/lib/action_view/template/handlers/raw.rb +2 -2
- data/lib/action_view/template/html.rb +14 -5
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +28 -0
- data/lib/action_view/template/resolver.rb +136 -133
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/text.rb +5 -3
- data/lib/action_view/test_case.rb +1 -1
- data/lib/action_view/testing/resolvers.rb +33 -20
- data/lib/action_view/unbound_template.rb +32 -0
- data/lib/action_view/view_paths.rb +25 -1
- data/lib/assets/compiled/rails-ujs.js +32 -6
- metadata +22 -17
- data/lib/action_view/helpers/record_tag_helper.rb +0 -23
data/lib/action_view/buffers.rb
CHANGED
@@ -3,6 +3,21 @@
|
|
3
3
|
require "active_support/core_ext/string/output_safety"
|
4
4
|
|
5
5
|
module ActionView
|
6
|
+
# Used as a buffer for views
|
7
|
+
#
|
8
|
+
# The main difference between this and ActiveSupport::SafeBuffer
|
9
|
+
# is for the methods `<<` and `safe_expr_append=` the inputs are
|
10
|
+
# checked for nil before they are assigned and `to_s` is called on
|
11
|
+
# the input. For example:
|
12
|
+
#
|
13
|
+
# obuf = ActionView::OutputBuffer.new "hello"
|
14
|
+
# obuf << 5
|
15
|
+
# puts obuf # => "hello5"
|
16
|
+
#
|
17
|
+
# sbuf = ActiveSupport::SafeBuffer.new "hello"
|
18
|
+
# sbuf << 5
|
19
|
+
# puts sbuf # => "hello\u0005"
|
20
|
+
#
|
6
21
|
class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc:
|
7
22
|
def initialize(*)
|
8
23
|
super
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActionView
|
4
|
+
class CacheExpiry
|
5
|
+
class Executor
|
6
|
+
def initialize(watcher:)
|
7
|
+
@cache_expiry = CacheExpiry.new(watcher: watcher)
|
8
|
+
end
|
9
|
+
|
10
|
+
def before(target)
|
11
|
+
@cache_expiry.clear_cache_if_necessary
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(watcher:)
|
16
|
+
@watched_dirs = nil
|
17
|
+
@watcher_class = watcher
|
18
|
+
@watcher = nil
|
19
|
+
@mutex = Mutex.new
|
20
|
+
end
|
21
|
+
|
22
|
+
def clear_cache_if_necessary
|
23
|
+
@mutex.synchronize do
|
24
|
+
watched_dirs = dirs_to_watch
|
25
|
+
return if watched_dirs.empty?
|
26
|
+
|
27
|
+
if watched_dirs != @watched_dirs
|
28
|
+
@watched_dirs = watched_dirs
|
29
|
+
@watcher = @watcher_class.new([], watched_dirs) do
|
30
|
+
clear_cache
|
31
|
+
end
|
32
|
+
@watcher.execute
|
33
|
+
else
|
34
|
+
@watcher.execute_if_updated
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def clear_cache
|
40
|
+
ActionView::LookupContext::DetailsKey.clear
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def dirs_to_watch
|
46
|
+
fs_paths = all_view_paths.grep(FileSystemResolver)
|
47
|
+
fs_paths.map(&:path).sort.uniq
|
48
|
+
end
|
49
|
+
|
50
|
+
def all_view_paths
|
51
|
+
ActionView::ViewPaths.all_view_paths.flat_map(&:paths)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/action_view/context.rb
CHANGED
@@ -1,21 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActionView
|
4
|
-
module CompiledTemplates #:nodoc:
|
5
|
-
# holds compiled template code
|
6
|
-
end
|
7
|
-
|
8
4
|
# = Action View Context
|
9
5
|
#
|
10
6
|
# Action View contexts are supplied to Action Controller to render a template.
|
11
7
|
# The default Action View context is ActionView::Base.
|
12
8
|
#
|
13
|
-
# In order to work with
|
14
|
-
# The initialization of the variables used by the context
|
15
|
-
# and @virtual_path) is responsibility of the
|
16
|
-
# (although you can call _prepare_context
|
9
|
+
# In order to work with Action Controller, a Context must just include this
|
10
|
+
# module. The initialization of the variables used by the context
|
11
|
+
# (@output_buffer, @view_flow, and @virtual_path) is responsibility of the
|
12
|
+
# object that includes this module (although you can call _prepare_context
|
13
|
+
# defined below).
|
17
14
|
module Context
|
18
|
-
include CompiledTemplates
|
19
15
|
attr_accessor :output_buffer, :view_flow
|
20
16
|
|
21
17
|
# Prepares the context by setting the appropriate instance variables.
|
data/lib/action_view/digestor.rb
CHANGED
@@ -1,28 +1,24 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "concurrent/map"
|
4
3
|
require "action_view/dependency_tracker"
|
5
|
-
require "monitor"
|
6
4
|
|
7
5
|
module ActionView
|
8
6
|
class Digestor
|
9
7
|
@@digest_mutex = Mutex.new
|
10
8
|
|
11
|
-
module PerExecutionDigestCacheExpiry
|
12
|
-
def self.before(target)
|
13
|
-
ActionView::LookupContext::DetailsKey.clear
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
9
|
class << self
|
18
10
|
# Supported options:
|
19
11
|
#
|
20
|
-
# * <tt>name</tt>
|
21
|
-
# * <tt>
|
22
|
-
# * <tt>
|
23
|
-
|
24
|
-
|
25
|
-
|
12
|
+
# * <tt>name</tt> - Template name
|
13
|
+
# * <tt>format</tt> - Template format
|
14
|
+
# * <tt>finder</tt> - An instance of <tt>ActionView::LookupContext</tt>
|
15
|
+
# * <tt>dependencies</tt> - An array of dependent views
|
16
|
+
def digest(name:, format: nil, finder:, dependencies: nil)
|
17
|
+
if dependencies.nil? || dependencies.empty?
|
18
|
+
cache_key = "#{name}.#{format}"
|
19
|
+
else
|
20
|
+
cache_key = [ name, format, dependencies ].flatten.compact.join(".")
|
21
|
+
end
|
26
22
|
|
27
23
|
# this is a correctly done double-checked locking idiom
|
28
24
|
# (Concurrent::Map's lookups have volatile semantics)
|
@@ -32,7 +28,7 @@ module ActionView
|
|
32
28
|
root = tree(name, finder, partial)
|
33
29
|
dependencies.each do |injected_dep|
|
34
30
|
root.children << Injected.new(injected_dep, nil, nil)
|
35
|
-
end
|
31
|
+
end if dependencies
|
36
32
|
finder.digest_cache[cache_key] = root.digest(finder)
|
37
33
|
end
|
38
34
|
end
|
@@ -47,8 +43,6 @@ module ActionView
|
|
47
43
|
logical_name = name.gsub(%r|/_|, "/")
|
48
44
|
|
49
45
|
if template = find_template(finder, logical_name, [], partial, [])
|
50
|
-
finder.rendered_format ||= template.formats.first
|
51
|
-
|
52
46
|
if node = seen[template.identifier] # handle cycles in the tree
|
53
47
|
node
|
54
48
|
else
|
@@ -72,9 +66,7 @@ module ActionView
|
|
72
66
|
private
|
73
67
|
def find_template(finder, name, prefixes, partial, keys)
|
74
68
|
finder.disable_cache do
|
75
|
-
|
76
|
-
result = finder.find_all(name, prefixes, partial, keys, formats: [format]).first if format
|
77
|
-
result || finder.find_all(name, prefixes, partial, keys).first
|
69
|
+
finder.find_all(name, prefixes, partial, keys).first
|
78
70
|
end
|
79
71
|
end
|
80
72
|
end
|
data/lib/action_view/helpers.rb
CHANGED
@@ -23,7 +23,6 @@ module ActionView #:nodoc:
|
|
23
23
|
autoload :JavaScriptHelper, "action_view/helpers/javascript_helper"
|
24
24
|
autoload :NumberHelper
|
25
25
|
autoload :OutputSafetyHelper
|
26
|
-
autoload :RecordTagHelper
|
27
26
|
autoload :RenderingHelper
|
28
27
|
autoload :SanitizeHelper
|
29
28
|
autoload :TagHelper
|
@@ -57,7 +56,6 @@ module ActionView #:nodoc:
|
|
57
56
|
include JavaScriptHelper
|
58
57
|
include NumberHelper
|
59
58
|
include OutputSafetyHelper
|
60
|
-
include RecordTagHelper
|
61
59
|
include RenderingHelper
|
62
60
|
include SanitizeHelper
|
63
61
|
include TagHelper
|
@@ -55,7 +55,7 @@ module ActionView
|
|
55
55
|
# that path.
|
56
56
|
# * <tt>:skip_pipeline</tt> - This option is used to bypass the asset pipeline
|
57
57
|
# when it is set to true.
|
58
|
-
# * <tt>:nonce
|
58
|
+
# * <tt>:nonce</tt> - When set to true, adds an automatic nonce value if
|
59
59
|
# you have Content Security Policy enabled.
|
60
60
|
#
|
61
61
|
# ==== Examples
|
@@ -98,7 +98,7 @@ module ActionView
|
|
98
98
|
if tag_options["nonce"] == true
|
99
99
|
tag_options["nonce"] = content_security_policy_nonce
|
100
100
|
end
|
101
|
-
content_tag("script"
|
101
|
+
content_tag("script", "", tag_options)
|
102
102
|
}.join("\n").html_safe
|
103
103
|
|
104
104
|
request.send_early_hints("Link" => early_hints_links.join("\n")) if respond_to?(:request) && request
|
@@ -329,14 +329,14 @@ module ActionView
|
|
329
329
|
# image_tag("pic.jpg", srcset: [["pic_1024.jpg", "1024w"], ["pic_1980.jpg", "1980w"]], sizes: "100vw")
|
330
330
|
# # => <img src="/assets/pic.jpg" srcset="/assets/pic_1024.jpg 1024w, /assets/pic_1980.jpg 1980w" sizes="100vw">
|
331
331
|
#
|
332
|
-
# Active Storage (images that are uploaded by the users of your app):
|
332
|
+
# Active Storage blobs (images that are uploaded by the users of your app):
|
333
333
|
#
|
334
334
|
# image_tag(user.avatar)
|
335
335
|
# # => <img src="/rails/active_storage/blobs/.../tiger.jpg" />
|
336
|
-
# image_tag(user.avatar.variant(
|
337
|
-
# # => <img src="/rails/active_storage/
|
338
|
-
# image_tag(user.avatar.variant(
|
339
|
-
# # => <img width="100" height="100" src="/rails/active_storage/
|
336
|
+
# image_tag(user.avatar.variant(resize_to_limit: [100, 100]))
|
337
|
+
# # => <img src="/rails/active_storage/representations/.../tiger.jpg" />
|
338
|
+
# image_tag(user.avatar.variant(resize_to_limit: [100, 100]), size: '100')
|
339
|
+
# # => <img width="100" height="100" src="/rails/active_storage/representations/.../tiger.jpg" />
|
340
340
|
def image_tag(source, options = {})
|
341
341
|
options = options.symbolize_keys
|
342
342
|
check_for_image_tag_errors(options)
|
@@ -355,29 +355,6 @@ module ActionView
|
|
355
355
|
tag("img", options)
|
356
356
|
end
|
357
357
|
|
358
|
-
# Returns a string suitable for an HTML image tag alt attribute.
|
359
|
-
# The +src+ argument is meant to be an image file path.
|
360
|
-
# The method removes the basename of the file path and the digest,
|
361
|
-
# if any. It also removes hyphens and underscores from file names and
|
362
|
-
# replaces them with spaces, returning a space-separated, titleized
|
363
|
-
# string.
|
364
|
-
#
|
365
|
-
# ==== Examples
|
366
|
-
#
|
367
|
-
# image_alt('rails.png')
|
368
|
-
# # => Rails
|
369
|
-
#
|
370
|
-
# image_alt('hyphenated-file-name.png')
|
371
|
-
# # => Hyphenated file name
|
372
|
-
#
|
373
|
-
# image_alt('underscored_file_name.png')
|
374
|
-
# # => Underscored file name
|
375
|
-
def image_alt(src)
|
376
|
-
ActiveSupport::Deprecation.warn("image_alt is deprecated and will be removed from Rails 6.0. You must explicitly set alt text on images.")
|
377
|
-
|
378
|
-
File.basename(src, ".*".freeze).sub(/-[[:xdigit:]]{32,64}\z/, "".freeze).tr("-_".freeze, " ".freeze).capitalize
|
379
|
-
end
|
380
|
-
|
381
358
|
# Returns an HTML video tag for the +sources+. If +sources+ is a string,
|
382
359
|
# a single video tag will be returned. If +sources+ is an array, a video
|
383
360
|
# tag with nested source tags for each source will be returned. The
|
@@ -98,8 +98,9 @@ module ActionView
|
|
98
98
|
# have SSL certificates for each of the asset hosts this technique allows you
|
99
99
|
# to avoid warnings in the client about mixed media.
|
100
100
|
# Note that the +request+ parameter might not be supplied, e.g. when the assets
|
101
|
-
# are precompiled
|
102
|
-
# since a +Proc+ allows missing parameters and sets them
|
101
|
+
# are precompiled with the command `rails assets:precompile`. Make sure to use a
|
102
|
+
# +Proc+ instead of a lambda, since a +Proc+ allows missing parameters and sets them
|
103
|
+
# to +nil+.
|
103
104
|
#
|
104
105
|
# config.action_controller.asset_host = Proc.new { |source, request|
|
105
106
|
# if request && request.ssl?
|
@@ -187,7 +188,7 @@ module ActionView
|
|
187
188
|
return "" if source.blank?
|
188
189
|
return source if URI_REGEXP.match?(source)
|
189
190
|
|
190
|
-
tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, ""
|
191
|
+
tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, "")
|
191
192
|
|
192
193
|
if extname = compute_asset_extname(source, options)
|
193
194
|
source = "#{source}#{extname}"
|
@@ -201,34 +201,42 @@ module ActionView
|
|
201
201
|
end
|
202
202
|
|
203
203
|
# This helper returns the name of a cache key for a given fragment cache
|
204
|
-
# call. By supplying
|
204
|
+
# call. By supplying <tt>skip_digest: true</tt> to cache, the digestion of cache
|
205
205
|
# fragments can be manually bypassed. This is useful when cache fragments
|
206
206
|
# cannot be manually expired unless you know the exact key which is the
|
207
207
|
# case when using memcached.
|
208
208
|
#
|
209
209
|
# The digest will be generated using +virtual_path:+ if it is provided.
|
210
210
|
#
|
211
|
-
def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil)
|
211
|
+
def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil, digest_path: nil)
|
212
212
|
if skip_digest
|
213
213
|
name
|
214
214
|
else
|
215
|
-
fragment_name_with_digest(name, virtual_path)
|
215
|
+
fragment_name_with_digest(name, virtual_path, digest_path)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def digest_path_from_template(template) # :nodoc:
|
220
|
+
digest = Digestor.digest(name: template.virtual_path, format: template.format, finder: lookup_context, dependencies: view_cache_dependencies)
|
221
|
+
|
222
|
+
if digest.present?
|
223
|
+
"#{template.virtual_path}:#{digest}"
|
224
|
+
else
|
225
|
+
template.virtual_path
|
216
226
|
end
|
217
227
|
end
|
218
228
|
|
219
229
|
private
|
220
230
|
|
221
|
-
def fragment_name_with_digest(name, virtual_path)
|
231
|
+
def fragment_name_with_digest(name, virtual_path, digest_path)
|
222
232
|
virtual_path ||= @virtual_path
|
223
233
|
|
224
|
-
if virtual_path
|
234
|
+
if virtual_path || digest_path
|
225
235
|
name = controller.url_for(name).split("://").last if name.is_a?(Hash)
|
226
236
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
[ virtual_path, name ]
|
231
|
-
end
|
237
|
+
digest_path ||= digest_path_from_template(@current_template)
|
238
|
+
|
239
|
+
[ digest_path, name ]
|
232
240
|
else
|
233
241
|
name
|
234
242
|
end
|
@@ -36,6 +36,10 @@ module ActionView
|
|
36
36
|
# </body>
|
37
37
|
# </html>
|
38
38
|
#
|
39
|
+
# The return of capture is the string generated by the block. For Example:
|
40
|
+
#
|
41
|
+
# @greeting # => "Welcome to my shiny new web page! The date and time is 2018-09-06 11:09:16 -0500"
|
42
|
+
#
|
39
43
|
def capture(*args)
|
40
44
|
value = nil
|
41
45
|
buffer = with_output_buffer { value = yield(*args) }
|
@@ -14,9 +14,11 @@ module ActionView
|
|
14
14
|
# This is used by the Rails UJS helper to create dynamically
|
15
15
|
# loaded inline <script> elements.
|
16
16
|
#
|
17
|
-
def csp_meta_tag
|
17
|
+
def csp_meta_tag(**options)
|
18
18
|
if content_security_policy?
|
19
|
-
|
19
|
+
options[:name] = "csp-nonce"
|
20
|
+
options[:content] = content_security_policy_nonce
|
21
|
+
tag("meta", options)
|
20
22
|
end
|
21
23
|
end
|
22
24
|
end
|
@@ -20,7 +20,7 @@ module ActionView
|
|
20
20
|
# "X-CSRF-Token" HTTP header. If you are using rails-ujs this happens automatically.
|
21
21
|
#
|
22
22
|
def csrf_meta_tags
|
23
|
-
if protect_against_forgery?
|
23
|
+
if defined?(protect_against_forgery?) && protect_against_forgery?
|
24
24
|
[
|
25
25
|
tag("meta", name: "csrf-param", content: request_forgery_protection_token),
|
26
26
|
tag("meta", name: "csrf-token", content: form_authenticity_token)
|
@@ -205,6 +205,7 @@ module ActionView
|
|
205
205
|
# * <tt>:end_year</tt> - Set the end year for the year select. Default is <tt>Date.today.year + 5</tt> if
|
206
206
|
# you are creating new record. While editing existing record, <tt>:end_year</tt> defaults to
|
207
207
|
# the current selected year plus 5.
|
208
|
+
# * <tt>:year_format</tt> - Set format of years for year select. Lambda should be passed.
|
208
209
|
# * <tt>:discard_day</tt> - Set to true if you don't want to show a day select. This includes the day
|
209
210
|
# as a hidden field instead of showing a select field. Also note that this implicitly sets the day to be the
|
210
211
|
# first of the given month in order to not create invalid dates like 31 February.
|
@@ -275,6 +276,9 @@ module ActionView
|
|
275
276
|
# # Generates a date select with custom prompts.
|
276
277
|
# date_select("article", "written_on", prompt: { day: 'Select day', month: 'Select month', year: 'Select year' })
|
277
278
|
#
|
279
|
+
# # Generates a date select with custom year format.
|
280
|
+
# date_select("article", "written_on", year_format: ->(year) { "Heisei #{year - 1988}" })
|
281
|
+
#
|
278
282
|
# The selects are prepared for multi-parameter assignment to an Active Record object.
|
279
283
|
#
|
280
284
|
# Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that
|
@@ -302,15 +306,15 @@ module ActionView
|
|
302
306
|
# time_select("article", "start_time", include_seconds: true)
|
303
307
|
#
|
304
308
|
# # You can set the <tt>:minute_step</tt> to 15 which will give you: 00, 15, 30, and 45.
|
305
|
-
# time_select 'game', 'game_time', {minute_step: 15}
|
309
|
+
# time_select 'game', 'game_time', { minute_step: 15 }
|
306
310
|
#
|
307
311
|
# # Creates a time select tag with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
|
308
|
-
# time_select("article", "written_on", prompt: {hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds'})
|
309
|
-
# time_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours
|
312
|
+
# time_select("article", "written_on", prompt: { hour: 'Choose hour', minute: 'Choose minute', second: 'Choose seconds' })
|
313
|
+
# time_select("article", "written_on", prompt: { hour: true }) # generic prompt for hours
|
310
314
|
# time_select("article", "written_on", prompt: true) # generic prompts for all
|
311
315
|
#
|
312
316
|
# # You can set :ampm option to true which will show the hours as: 12 PM, 01 AM .. 11 PM.
|
313
|
-
# time_select 'game', 'game_time', {ampm: true}
|
317
|
+
# time_select 'game', 'game_time', { ampm: true }
|
314
318
|
#
|
315
319
|
# The selects are prepared for multi-parameter assignment to an Active Record object.
|
316
320
|
#
|
@@ -346,8 +350,8 @@ module ActionView
|
|
346
350
|
# datetime_select("article", "written_on", discard_type: true)
|
347
351
|
#
|
348
352
|
# # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
|
349
|
-
# datetime_select("article", "written_on", prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
|
350
|
-
# datetime_select("article", "written_on", prompt: {hour: true}) # generic prompt for hours
|
353
|
+
# datetime_select("article", "written_on", prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
|
354
|
+
# datetime_select("article", "written_on", prompt: { hour: true }) # generic prompt for hours
|
351
355
|
# datetime_select("article", "written_on", prompt: true) # generic prompts for all
|
352
356
|
#
|
353
357
|
# The selects are prepared for multi-parameter assignment to an Active Record object.
|
@@ -397,8 +401,8 @@ module ActionView
|
|
397
401
|
# select_datetime(my_date_time, prefix: 'payday')
|
398
402
|
#
|
399
403
|
# # Generates a datetime select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
|
400
|
-
# select_datetime(my_date_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
|
401
|
-
# select_datetime(my_date_time, prompt: {hour: true}) # generic prompt for hours
|
404
|
+
# select_datetime(my_date_time, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
|
405
|
+
# select_datetime(my_date_time, prompt: { hour: true }) # generic prompt for hours
|
402
406
|
# select_datetime(my_date_time, prompt: true) # generic prompts for all
|
403
407
|
def select_datetime(datetime = Time.current, options = {}, html_options = {})
|
404
408
|
DateTimeSelector.new(datetime, options, html_options).select_datetime
|
@@ -436,8 +440,8 @@ module ActionView
|
|
436
440
|
# select_date(my_date, prefix: 'payday')
|
437
441
|
#
|
438
442
|
# # Generates a date select with a custom prompt. Use <tt>prompt: true</tt> for generic prompts.
|
439
|
-
# select_date(my_date, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
|
440
|
-
# select_date(my_date, prompt: {hour: true}) # generic prompt for hours
|
443
|
+
# select_date(my_date, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
|
444
|
+
# select_date(my_date, prompt: { hour: true }) # generic prompt for hours
|
441
445
|
# select_date(my_date, prompt: true) # generic prompts for all
|
442
446
|
def select_date(date = Date.current, options = {}, html_options = {})
|
443
447
|
DateTimeSelector.new(date, options, html_options).select_date
|
@@ -476,8 +480,8 @@ module ActionView
|
|
476
480
|
# select_time(my_time, start_hour: 2, end_hour: 14)
|
477
481
|
#
|
478
482
|
# # Generates a time select with a custom prompt. Use <tt>:prompt</tt> to true for generic prompts.
|
479
|
-
# select_time(my_time, prompt: {day: 'Choose day', month: 'Choose month', year: 'Choose year'})
|
480
|
-
# select_time(my_time, prompt: {hour: true}) # generic prompt for hours
|
483
|
+
# select_time(my_time, prompt: { day: 'Choose day', month: 'Choose month', year: 'Choose year' })
|
484
|
+
# select_time(my_time, prompt: { hour: true }) # generic prompt for hours
|
481
485
|
# select_time(my_time, prompt: true) # generic prompts for all
|
482
486
|
def select_time(datetime = Time.current, options = {}, html_options = {})
|
483
487
|
DateTimeSelector.new(datetime, options, html_options).select_time
|
@@ -668,8 +672,6 @@ module ActionView
|
|
668
672
|
# <time datetime="2010-11-04T17:55:45+01:00">November 04, 2010 17:55</time>
|
669
673
|
# time_tag Date.yesterday, 'Yesterday' # =>
|
670
674
|
# <time datetime="2010-11-03">Yesterday</time>
|
671
|
-
# time_tag Date.today, pubdate: true # =>
|
672
|
-
# <time datetime="2010-11-04" pubdate="pubdate">November 04, 2010</time>
|
673
675
|
# time_tag Date.today, datetime: Date.today.strftime('%G-W%V') # =>
|
674
676
|
# <time datetime="2010-W44">November 04, 2010</time>
|
675
677
|
#
|
@@ -681,9 +683,8 @@ module ActionView
|
|
681
683
|
options = args.extract_options!
|
682
684
|
format = options.delete(:format) || :long
|
683
685
|
content = args.first || I18n.l(date_or_time, format: format)
|
684
|
-
datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601
|
685
686
|
|
686
|
-
content_tag("time"
|
687
|
+
content_tag("time", content, options.reverse_merge(datetime: date_or_time.iso8601), &block)
|
687
688
|
end
|
688
689
|
|
689
690
|
private
|
@@ -702,7 +703,7 @@ module ActionView
|
|
702
703
|
class DateTimeSelector #:nodoc:
|
703
704
|
include ActionView::Helpers::TagHelper
|
704
705
|
|
705
|
-
DEFAULT_PREFIX = "date"
|
706
|
+
DEFAULT_PREFIX = "date"
|
706
707
|
POSITION = {
|
707
708
|
year: 1, month: 2, day: 3, hour: 4, minute: 5, second: 6
|
708
709
|
}.freeze
|
@@ -823,7 +824,7 @@ module ActionView
|
|
823
824
|
1.upto(12) do |month_number|
|
824
825
|
options = { value: month_number }
|
825
826
|
options[:selected] = "selected" if month == month_number
|
826
|
-
month_options << content_tag("option"
|
827
|
+
month_options << content_tag("option", month_name(month_number), options) + "\n"
|
827
828
|
end
|
828
829
|
build_select(:month, month_options.join)
|
829
830
|
end
|
@@ -851,7 +852,7 @@ module ActionView
|
|
851
852
|
raise ArgumentError, "There are too many years options to be built. Are you sure you haven't mistyped something? You can provide the :max_years_allowed parameter."
|
852
853
|
end
|
853
854
|
|
854
|
-
|
855
|
+
build_select(:year, build_year_options(val, options))
|
855
856
|
end
|
856
857
|
end
|
857
858
|
|
@@ -934,6 +935,21 @@ module ActionView
|
|
934
935
|
end
|
935
936
|
end
|
936
937
|
|
938
|
+
# Looks up year names by number.
|
939
|
+
#
|
940
|
+
# year_name(1998) # => 1998
|
941
|
+
#
|
942
|
+
# If the <tt>:year_format</tt> option is passed:
|
943
|
+
#
|
944
|
+
# year_name(1998) # => "Heisei 10"
|
945
|
+
def year_name(number)
|
946
|
+
if year_format_lambda = @options[:year_format]
|
947
|
+
year_format_lambda.call(number)
|
948
|
+
else
|
949
|
+
number
|
950
|
+
end
|
951
|
+
end
|
952
|
+
|
937
953
|
def date_order
|
938
954
|
@date_order ||= @options[:order] || translated_date_order
|
939
955
|
end
|
@@ -990,7 +1006,35 @@ module ActionView
|
|
990
1006
|
tag_options[:selected] = "selected" if selected == i
|
991
1007
|
text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value
|
992
1008
|
text = options[:ampm] ? AMPM_TRANSLATION[i] : text
|
993
|
-
select_options << content_tag("option"
|
1009
|
+
select_options << content_tag("option", text, tag_options)
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
(select_options.join("\n") + "\n").html_safe
|
1013
|
+
end
|
1014
|
+
|
1015
|
+
# Build select option HTML for year.
|
1016
|
+
# If <tt>year_format</tt> option is not passed
|
1017
|
+
# build_year_options(1998, start: 1998, end: 2000)
|
1018
|
+
# => "<option value="1998" selected="selected">1998</option>
|
1019
|
+
# <option value="1999">1999</option>
|
1020
|
+
# <option value="2000">2000</option>"
|
1021
|
+
#
|
1022
|
+
# If <tt>year_format</tt> option is passed
|
1023
|
+
# build_year_options(1998, start: 1998, end: 2000, year_format: ->year { "Heisei #{ year - 1988 }" })
|
1024
|
+
# => "<option value="1998" selected="selected">Heisei 10</option>
|
1025
|
+
# <option value="1999">Heisei 11</option>
|
1026
|
+
# <option value="2000">Heisei 12</option>"
|
1027
|
+
def build_year_options(selected, options = {})
|
1028
|
+
start = options.delete(:start)
|
1029
|
+
stop = options.delete(:end)
|
1030
|
+
step = options.delete(:step)
|
1031
|
+
|
1032
|
+
select_options = []
|
1033
|
+
start.step(stop, step) do |value|
|
1034
|
+
tag_options = { value: value }
|
1035
|
+
tag_options[:selected] = "selected" if selected == value
|
1036
|
+
text = year_name(value)
|
1037
|
+
select_options << content_tag("option", text, tag_options)
|
994
1038
|
end
|
995
1039
|
|
996
1040
|
(select_options.join("\n") + "\n").html_safe
|
@@ -1009,12 +1053,12 @@ module ActionView
|
|
1009
1053
|
select_options[:disabled] = "disabled" if @options[:disabled]
|
1010
1054
|
select_options[:class] = css_class_attribute(type, select_options[:class], @options[:with_css_classes]) if @options[:with_css_classes]
|
1011
1055
|
|
1012
|
-
select_html = "\n"
|
1013
|
-
select_html << content_tag("option"
|
1056
|
+
select_html = +"\n"
|
1057
|
+
select_html << content_tag("option", "", value: "") + "\n" if @options[:include_blank]
|
1014
1058
|
select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt]
|
1015
1059
|
select_html << select_options_as_html
|
1016
1060
|
|
1017
|
-
(content_tag("select"
|
1061
|
+
(content_tag("select", select_html.html_safe, select_options) + "\n").html_safe
|
1018
1062
|
end
|
1019
1063
|
|
1020
1064
|
# Builds the css class value for the select element
|
@@ -1047,7 +1091,7 @@ module ActionView
|
|
1047
1091
|
I18n.translate(:"datetime.prompts.#{type}", locale: @options[:locale])
|
1048
1092
|
end
|
1049
1093
|
|
1050
|
-
prompt ? content_tag("option"
|
1094
|
+
prompt ? content_tag("option", prompt, value: "") : ""
|
1051
1095
|
end
|
1052
1096
|
|
1053
1097
|
# Builds hidden input tag for date part and value.
|
@@ -1091,7 +1135,7 @@ module ActionView
|
|
1091
1135
|
# Given an ordering of datetime components, create the selection HTML
|
1092
1136
|
# and join them with their appropriate separators.
|
1093
1137
|
def build_selects_from_types(order)
|
1094
|
-
select = ""
|
1138
|
+
select = +""
|
1095
1139
|
first_visible = order.find { |type| !@options[:"discard_#{type}"] }
|
1096
1140
|
order.reverse_each do |type|
|
1097
1141
|
separator = separator(type) unless type == first_visible # don't add before first visible field
|