actionview 4.1.13 → 6.1.3.1
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 +5 -5
- data/CHANGELOG.md +181 -359
- data/MIT-LICENSE +1 -1
- data/README.rdoc +12 -6
- data/lib/action_view/base.rb +115 -43
- data/lib/action_view/buffers.rb +22 -4
- data/lib/action_view/cache_expiry.rb +52 -0
- data/lib/action_view/context.rb +8 -12
- data/lib/action_view/dependency_tracker.rb +61 -21
- data/lib/action_view/digestor.rb +89 -84
- data/lib/action_view/flows.rb +12 -13
- data/lib/action_view/gem_version.rb +6 -4
- data/lib/action_view/helpers/active_model_helper.rb +16 -11
- data/lib/action_view/helpers/asset_tag_helper.rb +311 -105
- data/lib/action_view/helpers/asset_url_helper.rb +197 -80
- data/lib/action_view/helpers/atom_feed_helper.rb +20 -17
- data/lib/action_view/helpers/cache_helper.rb +109 -45
- data/lib/action_view/helpers/capture_helper.rb +20 -22
- data/lib/action_view/helpers/controller_helper.rb +15 -4
- data/lib/action_view/helpers/csp_helper.rb +26 -0
- data/lib/action_view/helpers/csrf_helper.rb +8 -6
- data/lib/action_view/helpers/date_helper.rb +245 -140
- data/lib/action_view/helpers/debug_helper.rb +14 -17
- data/lib/action_view/helpers/form_helper.rb +875 -148
- data/lib/action_view/helpers/form_options_helper.rb +128 -82
- data/lib/action_view/helpers/form_tag_helper.rb +253 -91
- data/lib/action_view/helpers/javascript_helper.rb +37 -15
- data/lib/action_view/helpers/number_helper.rb +100 -77
- data/lib/action_view/helpers/output_safety_helper.rb +42 -10
- data/lib/action_view/helpers/rendering_helper.rb +26 -15
- data/lib/action_view/helpers/sanitize_helper.rb +79 -164
- data/lib/action_view/helpers/tag_helper.rb +277 -64
- data/lib/action_view/helpers/tags/base.rb +143 -92
- data/lib/action_view/helpers/tags/check_box.rb +20 -19
- data/lib/action_view/helpers/tags/checkable.rb +4 -2
- data/lib/action_view/helpers/tags/collection_check_boxes.rb +12 -30
- data/lib/action_view/helpers/tags/collection_helpers.rb +69 -36
- data/lib/action_view/helpers/tags/collection_radio_buttons.rb +6 -12
- data/lib/action_view/helpers/tags/collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/color_field.rb +4 -3
- data/lib/action_view/helpers/tags/date_field.rb +3 -2
- data/lib/action_view/helpers/tags/date_select.rb +38 -37
- data/lib/action_view/helpers/tags/datetime_field.rb +14 -5
- data/lib/action_view/helpers/tags/datetime_local_field.rb +3 -2
- data/lib/action_view/helpers/tags/datetime_select.rb +2 -0
- data/lib/action_view/helpers/tags/email_field.rb +2 -0
- data/lib/action_view/helpers/tags/file_field.rb +2 -0
- data/lib/action_view/helpers/tags/grouped_collection_select.rb +4 -2
- data/lib/action_view/helpers/tags/hidden_field.rb +2 -0
- data/lib/action_view/helpers/tags/label.rb +41 -22
- data/lib/action_view/helpers/tags/month_field.rb +3 -2
- data/lib/action_view/helpers/tags/number_field.rb +2 -0
- data/lib/action_view/helpers/tags/password_field.rb +3 -1
- data/lib/action_view/helpers/tags/placeholderable.rb +24 -0
- data/lib/action_view/helpers/tags/radio_button.rb +7 -6
- data/lib/action_view/helpers/tags/range_field.rb +2 -0
- data/lib/action_view/helpers/tags/search_field.rb +3 -0
- data/lib/action_view/helpers/tags/select.rb +11 -10
- data/lib/action_view/helpers/tags/tel_field.rb +2 -0
- data/lib/action_view/helpers/tags/text_area.rb +7 -1
- data/lib/action_view/helpers/tags/text_field.rb +11 -7
- data/lib/action_view/helpers/tags/time_field.rb +3 -2
- data/lib/action_view/helpers/tags/time_select.rb +2 -0
- data/lib/action_view/helpers/tags/time_zone_select.rb +3 -1
- data/lib/action_view/helpers/tags/translator.rb +39 -0
- data/lib/action_view/helpers/tags/url_field.rb +2 -0
- data/lib/action_view/helpers/tags/week_field.rb +3 -2
- data/lib/action_view/helpers/tags.rb +4 -1
- data/lib/action_view/helpers/text_helper.rb +80 -45
- data/lib/action_view/helpers/translation_helper.rb +148 -67
- data/lib/action_view/helpers/url_helper.rb +289 -147
- data/lib/action_view/helpers.rb +5 -3
- data/lib/action_view/layouts.rb +68 -63
- data/lib/action_view/log_subscriber.rb +80 -13
- data/lib/action_view/lookup_context.rb +137 -92
- data/lib/action_view/model_naming.rb +4 -2
- data/lib/action_view/path_set.rb +30 -16
- data/lib/action_view/railtie.rb +62 -13
- data/lib/action_view/record_identifier.rb +53 -26
- data/lib/action_view/renderer/abstract_renderer.rb +152 -13
- data/lib/action_view/renderer/collection_renderer.rb +196 -0
- data/lib/action_view/renderer/object_renderer.rb +34 -0
- data/lib/action_view/renderer/partial_renderer/collection_caching.rb +102 -0
- data/lib/action_view/renderer/partial_renderer.rb +61 -261
- data/lib/action_view/renderer/renderer.rb +67 -6
- data/lib/action_view/renderer/streaming_template_renderer.rb +58 -54
- data/lib/action_view/renderer/template_renderer.rb +83 -75
- data/lib/action_view/rendering.rb +73 -46
- data/lib/action_view/routing_url_for.rb +54 -17
- data/lib/action_view/tasks/cache_digests.rake +25 -0
- data/lib/action_view/template/error.rb +44 -29
- data/lib/action_view/template/handlers/builder.rb +12 -13
- data/lib/action_view/template/handlers/erb/erubi.rb +89 -0
- data/lib/action_view/template/handlers/erb.rb +23 -89
- data/lib/action_view/template/handlers/html.rb +11 -0
- data/lib/action_view/template/handlers/raw.rb +4 -4
- data/lib/action_view/template/handlers.rb +22 -9
- data/lib/action_view/template/html.rb +10 -11
- data/lib/action_view/template/inline.rb +22 -0
- data/lib/action_view/template/raw_file.rb +25 -0
- data/lib/action_view/template/renderable.rb +24 -0
- data/lib/action_view/template/resolver.rb +267 -181
- data/lib/action_view/template/sources/file.rb +17 -0
- data/lib/action_view/template/sources.rb +13 -0
- data/lib/action_view/template/text.rb +8 -10
- data/lib/action_view/template/types.rb +18 -18
- data/lib/action_view/template.rb +109 -99
- data/lib/action_view/test_case.rb +73 -53
- data/lib/action_view/testing/resolvers.rb +24 -33
- data/lib/action_view/unbound_template.rb +31 -0
- data/lib/action_view/version.rb +3 -1
- data/lib/action_view/view_paths.rb +74 -44
- data/lib/action_view.rb +14 -9
- data/lib/assets/compiled/rails-ujs.js +746 -0
- metadata +71 -26
- data/lib/action_view/helpers/record_tag_helper.rb +0 -108
- data/lib/action_view/tasks/dependencies.rake +0 -23
- data/lib/action_view/vendor/html-scanner/html/document.rb +0 -68
- data/lib/action_view/vendor/html-scanner/html/node.rb +0 -532
- data/lib/action_view/vendor/html-scanner/html/sanitizer.rb +0 -188
- data/lib/action_view/vendor/html-scanner/html/selector.rb +0 -830
- data/lib/action_view/vendor/html-scanner/html/tokenizer.rb +0 -107
- data/lib/action_view/vendor/html-scanner/html/version.rb +0 -11
- data/lib/action_view/vendor/html-scanner.rb +0 -20
@@ -1,98 +1,140 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_view/helpers/tag_helper"
|
4
|
+
require "active_support/core_ext/symbol/starts_ends_with"
|
3
5
|
|
4
6
|
module ActionView
|
5
7
|
# = Action View Translation Helpers
|
6
|
-
module Helpers
|
8
|
+
module Helpers #:nodoc:
|
7
9
|
module TranslationHelper
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
8
12
|
include TagHelper
|
9
|
-
|
13
|
+
|
14
|
+
included do
|
15
|
+
mattr_accessor :debug_missing_translation, default: true
|
16
|
+
end
|
17
|
+
|
18
|
+
# Delegates to <tt>I18n#translate</tt> but also performs three additional
|
19
|
+
# functions.
|
10
20
|
#
|
11
|
-
# First, it will ensure that any thrown +MissingTranslation+ messages will
|
12
|
-
#
|
21
|
+
# First, it will ensure that any thrown +MissingTranslation+ messages will
|
22
|
+
# be rendered as inline spans that:
|
13
23
|
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
24
|
+
# * Have a <tt>translation-missing</tt> class applied
|
25
|
+
# * Contain the missing key as the value of the +title+ attribute
|
26
|
+
# * Have a titleized version of the last key segment as text
|
17
27
|
#
|
18
|
-
#
|
19
|
-
# <
|
20
|
-
# This way your views will display rather reasonable strings but it will still
|
21
|
-
# be easy to spot missing translations.
|
28
|
+
# For example, the value returned for the missing translation key
|
29
|
+
# <tt>"blog.post.title"</tt> will be:
|
22
30
|
#
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
# <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive
|
27
|
-
# to translate many keys within the same partials and gives you a simple framework
|
28
|
-
# for scoping them consistently. If you don't prepend the key with a period,
|
29
|
-
# nothing is converted.
|
31
|
+
# <span
|
32
|
+
# class="translation_missing"
|
33
|
+
# title="translation missing: en.blog.post.title">Title</span>
|
30
34
|
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
#
|
36
|
-
#
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
35
|
+
# This allows for views to display rather reasonable strings while still
|
36
|
+
# giving developers a way to find missing translations.
|
37
|
+
#
|
38
|
+
# If you would prefer missing translations to raise an error, you can
|
39
|
+
# opt out of span-wrapping behavior globally by setting
|
40
|
+
# <tt>ActionView::Base.raise_on_missing_translations = true</tt> or
|
41
|
+
# individually by passing <tt>raise: true</tt> as an option to
|
42
|
+
# <tt>translate</tt>.
|
43
|
+
#
|
44
|
+
# Second, if the key starts with a period <tt>translate</tt> will scope
|
45
|
+
# the key by the current partial. Calling <tt>translate(".foo")</tt> from
|
46
|
+
# the <tt>people/index.html.erb</tt> template is equivalent to calling
|
47
|
+
# <tt>translate("people.index.foo")</tt>. This makes it less
|
48
|
+
# repetitive to translate many keys within the same partial and provides
|
49
|
+
# a convention to scope keys consistently.
|
50
|
+
#
|
51
|
+
# Third, the translation will be marked as <tt>html_safe</tt> if the key
|
52
|
+
# has the suffix "_html" or the last element of the key is "html". Calling
|
53
|
+
# <tt>translate("footer_html")</tt> or <tt>translate("footer.html")</tt>
|
54
|
+
# will return an HTML safe string that won't be escaped by other HTML
|
55
|
+
# helper methods. This naming convention helps to identify translations
|
56
|
+
# that include HTML tags so that you know what kind of output to expect
|
57
|
+
# when you call translate in a template and translators know which keys
|
58
|
+
# they can provide HTML values for.
|
59
|
+
#
|
60
|
+
# To access the translated text along with the fully resolved
|
61
|
+
# translation key, <tt>translate</tt> accepts a block:
|
62
|
+
#
|
63
|
+
# <%= translate(".relative_key") do |translation, resolved_key| %>
|
64
|
+
# <span title="<%= resolved_key %>"><%= translation %></span>
|
65
|
+
# <% end %>
|
66
|
+
#
|
67
|
+
# This enables annotate translated text to be aware of the scope it was
|
68
|
+
# resolved against.
|
69
|
+
#
|
70
|
+
def translate(key, **options)
|
71
|
+
return key.map { |k| translate(k, **options) } if key.is_a?(Array)
|
72
|
+
key = key&.to_s unless key.is_a?(Symbol)
|
45
73
|
|
46
|
-
|
47
|
-
|
48
|
-
# Note: `raise_error` refers to us re-raising the error in this method. I18n is forced to raise by default.
|
49
|
-
if options[:raise] == false || (options.key?(:rescue_format) && options[:rescue_format].nil?)
|
50
|
-
raise_error = false
|
51
|
-
i18n_raise = false
|
52
|
-
else
|
53
|
-
raise_error = options[:raise] || options[:rescue_format] || ActionView::Base.raise_on_missing_translations
|
54
|
-
i18n_raise = true
|
74
|
+
alternatives = if options.key?(:default)
|
75
|
+
options[:default].is_a?(Array) ? options.delete(:default).compact : [options.delete(:default)]
|
55
76
|
end
|
56
77
|
|
57
|
-
if
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
78
|
+
options[:raise] = true if options[:raise].nil? && ActionView::Base.raise_on_missing_translations
|
79
|
+
default = MISSING_TRANSLATION
|
80
|
+
|
81
|
+
translation = while key || alternatives.present?
|
82
|
+
if alternatives.blank? && !options[:raise].nil?
|
83
|
+
default = NO_DEFAULT # let I18n handle missing translation
|
84
|
+
end
|
85
|
+
|
86
|
+
key = scope_key_by_partial(key)
|
87
|
+
|
88
|
+
if html_safe_translation_key?(key)
|
89
|
+
html_safe_options ||= html_escape_translation_options(options)
|
90
|
+
translated = I18n.translate(key, **html_safe_options, default: default)
|
91
|
+
break html_safe_translation(translated) unless translated.equal?(MISSING_TRANSLATION)
|
92
|
+
else
|
93
|
+
translated = I18n.translate(key, **options, default: default)
|
94
|
+
break translated unless translated.equal?(MISSING_TRANSLATION)
|
63
95
|
end
|
64
|
-
translation = I18n.translate(scope_key_by_partial(key), html_safe_options.merge(raise: i18n_raise))
|
65
96
|
|
66
|
-
|
67
|
-
|
68
|
-
|
97
|
+
break alternatives.first if alternatives.present? && !alternatives.first.is_a?(Symbol)
|
98
|
+
|
99
|
+
first_key ||= key
|
100
|
+
key = alternatives&.shift
|
69
101
|
end
|
70
|
-
|
71
|
-
if
|
72
|
-
|
73
|
-
|
74
|
-
raise e if raise_error
|
75
|
-
|
76
|
-
keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope])
|
77
|
-
content_tag('span', keys.last.to_s.titleize, :class => 'translation_missing', :title => "translation missing: #{keys.join('.')}")
|
102
|
+
|
103
|
+
if key.nil? && !first_key.nil?
|
104
|
+
translation = missing_translation(first_key, options)
|
105
|
+
key = first_key
|
78
106
|
end
|
107
|
+
|
108
|
+
block_given? ? yield(translation, key) : translation
|
79
109
|
end
|
80
110
|
alias :t :translate
|
81
111
|
|
82
112
|
# Delegates to <tt>I18n.localize</tt> with no additional functionality.
|
83
113
|
#
|
84
|
-
# See
|
114
|
+
# See https://www.rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize
|
85
115
|
# for more information.
|
86
|
-
def localize(
|
87
|
-
I18n.localize(
|
116
|
+
def localize(object, **options)
|
117
|
+
I18n.localize(object, **options)
|
88
118
|
end
|
89
119
|
alias :l :localize
|
90
120
|
|
91
121
|
private
|
122
|
+
MISSING_TRANSLATION = Object.new
|
123
|
+
private_constant :MISSING_TRANSLATION
|
124
|
+
|
125
|
+
NO_DEFAULT = [].freeze
|
126
|
+
private_constant :NO_DEFAULT
|
127
|
+
|
128
|
+
def self.i18n_option?(name)
|
129
|
+
(@i18n_option_names ||= I18n::RESERVED_KEYS.to_set).include?(name)
|
130
|
+
end
|
131
|
+
|
92
132
|
def scope_key_by_partial(key)
|
93
|
-
if key
|
133
|
+
if key&.start_with?(".")
|
94
134
|
if @virtual_path
|
95
|
-
@
|
135
|
+
@_scope_key_by_partial_cache ||= {}
|
136
|
+
@_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
|
137
|
+
"#{@_scope_key_by_partial_cache[@virtual_path]}#{key}"
|
96
138
|
else
|
97
139
|
raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
|
98
140
|
end
|
@@ -101,8 +143,47 @@ module ActionView
|
|
101
143
|
end
|
102
144
|
end
|
103
145
|
|
146
|
+
def html_escape_translation_options(options)
|
147
|
+
return options if options.empty?
|
148
|
+
html_safe_options = options.dup
|
149
|
+
|
150
|
+
options.each do |name, value|
|
151
|
+
unless TranslationHelper.i18n_option?(name) || (name == :count && value.is_a?(Numeric))
|
152
|
+
html_safe_options[name] = ERB::Util.html_escape(value.to_s)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
html_safe_options
|
157
|
+
end
|
158
|
+
|
104
159
|
def html_safe_translation_key?(key)
|
105
|
-
|
160
|
+
/(?:_|\b)html\z/.match?(key)
|
161
|
+
end
|
162
|
+
|
163
|
+
def html_safe_translation(translation)
|
164
|
+
if translation.respond_to?(:map)
|
165
|
+
translation.map { |element| element.respond_to?(:html_safe) ? element.html_safe : element }
|
166
|
+
else
|
167
|
+
translation.respond_to?(:html_safe) ? translation.html_safe : translation
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def missing_translation(key, options)
|
172
|
+
keys = I18n.normalize_keys(options[:locale] || I18n.locale, key, options[:scope])
|
173
|
+
|
174
|
+
title = +"translation missing: #{keys.join(".")}"
|
175
|
+
|
176
|
+
options.each do |name, value|
|
177
|
+
unless name == :scope
|
178
|
+
title << ", " << name.to_s << ": " << ERB::Util.html_escape(value)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
if ActionView::Base.debug_missing_translation
|
183
|
+
content_tag("span", keys.last.to_s.titleize, class: "translation_missing", title: title)
|
184
|
+
else
|
185
|
+
title
|
186
|
+
end
|
106
187
|
end
|
107
188
|
end
|
108
189
|
end
|
@@ -1,7 +1,9 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
require
|
4
|
-
require
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "action_view/helpers/javascript_helper"
|
4
|
+
require "active_support/core_ext/array/access"
|
5
|
+
require "active_support/core_ext/hash/keys"
|
6
|
+
require "active_support/core_ext/string/output_safety"
|
5
7
|
|
6
8
|
module ActionView
|
7
9
|
# = Action View URL Helpers
|
@@ -35,21 +37,31 @@ module ActionView
|
|
35
37
|
when :back
|
36
38
|
_back_url
|
37
39
|
else
|
38
|
-
raise ArgumentError, "arguments passed to url_for can't be handled. Please require "
|
40
|
+
raise ArgumentError, "arguments passed to url_for can't be handled. Please require " \
|
39
41
|
"routes or provide your own implementation"
|
40
42
|
end
|
41
43
|
end
|
42
44
|
|
43
45
|
def _back_url # :nodoc:
|
44
|
-
|
45
|
-
|
46
|
+
_filtered_referrer || "javascript:history.back()"
|
47
|
+
end
|
48
|
+
private :_back_url
|
49
|
+
|
50
|
+
def _filtered_referrer # :nodoc:
|
51
|
+
if controller.respond_to?(:request)
|
52
|
+
referrer = controller.request.env["HTTP_REFERER"]
|
53
|
+
if referrer && URI(referrer).scheme != "javascript"
|
54
|
+
referrer
|
55
|
+
end
|
56
|
+
end
|
57
|
+
rescue URI::InvalidURIError
|
46
58
|
end
|
47
|
-
|
59
|
+
private :_filtered_referrer
|
48
60
|
|
49
|
-
# Creates
|
61
|
+
# Creates an anchor element of the given +name+ using a URL created by the set of +options+.
|
50
62
|
# See the valid options in the documentation for +url_for+. It's also possible to
|
51
|
-
# pass a String instead of an options hash, which generates
|
52
|
-
# value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead
|
63
|
+
# pass a \String instead of an options hash, which generates an anchor element that uses the
|
64
|
+
# value of the \String as the href for the link. Using a <tt>:back</tt> \Symbol instead
|
53
65
|
# of an options hash will generate a link to the referrer (a JavaScript back link
|
54
66
|
# will be used in place of a referrer if none exists). If +nil+ is passed as the name
|
55
67
|
# the value of the link itself will become the name.
|
@@ -95,10 +107,9 @@ module ActionView
|
|
95
107
|
# driver to prompt with the question specified (in this case, the
|
96
108
|
# resulting text would be <tt>question?</tt>. If the user accepts, the
|
97
109
|
# link is processed normally, otherwise no action is taken.
|
98
|
-
# * <tt>:disable_with</tt> - Value of this parameter will be
|
99
|
-
#
|
100
|
-
#
|
101
|
-
# by the unobtrusive JavaScript driver.
|
110
|
+
# * <tt>:disable_with</tt> - Value of this parameter will be used as the
|
111
|
+
# name for a disabled version of the link. This feature is provided by
|
112
|
+
# the unobtrusive JavaScript driver.
|
102
113
|
#
|
103
114
|
# ==== Examples
|
104
115
|
# Because it relies on +url_for+, +link_to+ supports both older-style controller/action/id arguments
|
@@ -128,6 +139,11 @@ module ActionView
|
|
128
139
|
# link_to "Profiles", controller: "profiles"
|
129
140
|
# # => <a href="/profiles">Profiles</a>
|
130
141
|
#
|
142
|
+
# When name is +nil+ the href is presented instead
|
143
|
+
#
|
144
|
+
# link_to nil, "http://example.com"
|
145
|
+
# # => <a href="http://www.example.com">http://www.example.com</a>
|
146
|
+
#
|
131
147
|
# You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
|
132
148
|
#
|
133
149
|
# <%= link_to(@profile) do %>
|
@@ -161,7 +177,7 @@ module ActionView
|
|
161
177
|
# # => <a href="/searches?query=ruby+on+rails">Ruby on Rails search</a>
|
162
178
|
#
|
163
179
|
# link_to "Nonsense search", searches_path(foo: "bar", baz: "quux")
|
164
|
-
# # => <a href="/searches?foo=bar&
|
180
|
+
# # => <a href="/searches?foo=bar&baz=quux">Nonsense search</a>
|
165
181
|
#
|
166
182
|
# The only option specific to +link_to+ (<tt>:method</tt>) is used as follows:
|
167
183
|
#
|
@@ -172,6 +188,11 @@ module ActionView
|
|
172
188
|
#
|
173
189
|
# link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" }
|
174
190
|
# # => <a href="http://www.rubyonrails.org/" data-confirm="Are you sure?">Visit Other Site</a>
|
191
|
+
#
|
192
|
+
# Also you can set any link attributes such as <tt>target</tt>, <tt>rel</tt>, <tt>type</tt>:
|
193
|
+
#
|
194
|
+
# link_to "External link", "http://www.rubyonrails.org/", target: "_blank", rel: "nofollow"
|
195
|
+
# # => <a href="http://www.rubyonrails.org/" target="_blank" rel="nofollow">External link</a>
|
175
196
|
def link_to(name = nil, options = nil, html_options = nil, &block)
|
176
197
|
html_options, options, name = options, name, block if block_given?
|
177
198
|
options ||= {}
|
@@ -179,9 +200,9 @@ module ActionView
|
|
179
200
|
html_options = convert_options_to_data_attributes(options, html_options)
|
180
201
|
|
181
202
|
url = url_for(options)
|
182
|
-
html_options[
|
203
|
+
html_options["href"] ||= url
|
183
204
|
|
184
|
-
content_tag(
|
205
|
+
content_tag("a", name || url, html_options, &block)
|
185
206
|
end
|
186
207
|
|
187
208
|
# Generates a form containing a single button that submits to the URL created
|
@@ -205,7 +226,7 @@ module ActionView
|
|
205
226
|
# The +options+ hash accepts the same options as +url_for+.
|
206
227
|
#
|
207
228
|
# There are a few special +html_options+:
|
208
|
-
# * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
|
229
|
+
# * <tt>:method</tt> - \Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
|
209
230
|
# <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>.
|
210
231
|
# * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
|
211
232
|
# * <tt>:data</tt> - This option can be used to add custom data attributes.
|
@@ -214,7 +235,7 @@ module ActionView
|
|
214
235
|
# * <tt>:form</tt> - This hash will be form attributes
|
215
236
|
# * <tt>:form_class</tt> - This controls the class of the form within which the submit button will
|
216
237
|
# be placed
|
217
|
-
# * <tt>:params</tt> - Hash of parameters to be rendered as hidden fields within the form.
|
238
|
+
# * <tt>:params</tt> - \Hash of parameters to be rendered as hidden fields within the form.
|
218
239
|
#
|
219
240
|
# ==== Data attributes
|
220
241
|
#
|
@@ -229,101 +250,97 @@ module ActionView
|
|
229
250
|
# ==== Examples
|
230
251
|
# <%= button_to "New", action: "new" %>
|
231
252
|
# # => "<form method="post" action="/controller/new" class="button_to">
|
232
|
-
# # <
|
253
|
+
# # <input value="New" type="submit" />
|
233
254
|
# # </form>"
|
234
255
|
#
|
235
|
-
# <%= button_to "New",
|
256
|
+
# <%= button_to "New", new_article_path %>
|
236
257
|
# # => "<form method="post" action="/articles/new" class="button_to">
|
237
|
-
# # <
|
258
|
+
# # <input value="New" type="submit" />
|
238
259
|
# # </form>"
|
239
260
|
#
|
240
261
|
# <%= button_to [:make_happy, @user] do %>
|
241
262
|
# Make happy <strong><%= @user.name %></strong>
|
242
263
|
# <% end %>
|
243
264
|
# # => "<form method="post" action="/users/1/make_happy" class="button_to">
|
244
|
-
# # <
|
245
|
-
# # <
|
246
|
-
# #
|
247
|
-
# # </button>
|
248
|
-
# # </div>
|
265
|
+
# # <button type="submit">
|
266
|
+
# # Make happy <strong><%= @user.name %></strong>
|
267
|
+
# # </button>
|
249
268
|
# # </form>"
|
250
269
|
#
|
251
270
|
# <%= button_to "New", { action: "new" }, form_class: "new-thing" %>
|
252
271
|
# # => "<form method="post" action="/controller/new" class="new-thing">
|
253
|
-
# # <
|
272
|
+
# # <input value="New" type="submit" />
|
254
273
|
# # </form>"
|
255
274
|
#
|
256
275
|
#
|
257
276
|
# <%= button_to "Create", { action: "create" }, remote: true, form: { "data-type" => "json" } %>
|
258
277
|
# # => "<form method="post" action="/images/create" class="button_to" data-remote="true" data-type="json">
|
259
|
-
# # <
|
260
|
-
# #
|
261
|
-
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
|
262
|
-
# # </div>
|
278
|
+
# # <input value="Create" type="submit" />
|
279
|
+
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
|
263
280
|
# # </form>"
|
264
281
|
#
|
265
282
|
#
|
266
283
|
# <%= button_to "Delete Image", { action: "delete", id: @image.id },
|
267
284
|
# method: :delete, data: { confirm: "Are you sure?" } %>
|
268
285
|
# # => "<form method="post" action="/images/delete/1" class="button_to">
|
269
|
-
# # <
|
270
|
-
# #
|
271
|
-
# #
|
272
|
-
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
|
273
|
-
# # </div>
|
286
|
+
# # <input type="hidden" name="_method" value="delete" />
|
287
|
+
# # <input data-confirm='Are you sure?' value="Delete Image" type="submit" />
|
288
|
+
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
|
274
289
|
# # </form>"
|
275
290
|
#
|
276
291
|
#
|
277
292
|
# <%= button_to('Destroy', 'http://www.example.com',
|
278
|
-
# method:
|
293
|
+
# method: :delete, remote: true, data: { confirm: 'Are you sure?', disable_with: 'loading...' }) %>
|
279
294
|
# # => "<form class='button_to' method='post' action='http://www.example.com' data-remote='true'>
|
280
|
-
# # <
|
281
|
-
# #
|
282
|
-
# #
|
283
|
-
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
|
284
|
-
# # </div>
|
295
|
+
# # <input name='_method' value='delete' type='hidden' />
|
296
|
+
# # <input value='Destroy' type='submit' data-disable-with='loading...' data-confirm='Are you sure?' />
|
297
|
+
# # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/>
|
285
298
|
# # </form>"
|
286
299
|
# #
|
287
300
|
def button_to(name = nil, options = nil, html_options = nil, &block)
|
288
301
|
html_options, options = options, name if block_given?
|
289
302
|
options ||= {}
|
290
303
|
html_options ||= {}
|
291
|
-
|
292
304
|
html_options = html_options.stringify_keys
|
293
|
-
convert_boolean_attributes!(html_options, %w(disabled))
|
294
305
|
|
295
306
|
url = options.is_a?(String) ? options : url_for(options)
|
296
|
-
remote = html_options.delete(
|
297
|
-
params = html_options.delete(
|
298
|
-
|
299
|
-
method = html_options.delete(
|
300
|
-
method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) :
|
301
|
-
|
302
|
-
form_method = method ==
|
303
|
-
form_options = html_options.delete(
|
304
|
-
form_options[:class] ||= html_options.delete(
|
305
|
-
form_options
|
306
|
-
form_options
|
307
|
-
|
308
|
-
|
307
|
+
remote = html_options.delete("remote")
|
308
|
+
params = html_options.delete("params")
|
309
|
+
|
310
|
+
method = html_options.delete("method").to_s
|
311
|
+
method_tag = BUTTON_TAG_METHOD_VERBS.include?(method) ? method_tag(method) : "".html_safe
|
312
|
+
|
313
|
+
form_method = method == "get" ? "get" : "post"
|
314
|
+
form_options = html_options.delete("form") || {}
|
315
|
+
form_options[:class] ||= html_options.delete("form_class") || "button_to"
|
316
|
+
form_options[:method] = form_method
|
317
|
+
form_options[:action] = url
|
318
|
+
form_options[:'data-remote'] = true if remote
|
319
|
+
|
320
|
+
request_token_tag = if form_method == "post"
|
321
|
+
request_method = method.empty? ? "post" : method
|
322
|
+
token_tag(nil, form_options: { action: url, method: request_method })
|
323
|
+
else
|
324
|
+
""
|
325
|
+
end
|
309
326
|
|
310
327
|
html_options = convert_options_to_data_attributes(options, html_options)
|
311
|
-
html_options[
|
328
|
+
html_options["type"] = "submit"
|
312
329
|
|
313
330
|
button = if block_given?
|
314
|
-
content_tag(
|
331
|
+
content_tag("button", html_options, &block)
|
315
332
|
else
|
316
|
-
html_options[
|
317
|
-
tag(
|
333
|
+
html_options["value"] = name || url
|
334
|
+
tag("input", html_options)
|
318
335
|
end
|
319
336
|
|
320
337
|
inner_tags = method_tag.safe_concat(button).safe_concat(request_token_tag)
|
321
338
|
if params
|
322
|
-
params.each do |
|
323
|
-
inner_tags.safe_concat tag(:input, type: "hidden", name:
|
339
|
+
to_form_params(params).each do |param|
|
340
|
+
inner_tags.safe_concat tag(:input, type: "hidden", name: param[:name], value: param[:value])
|
324
341
|
end
|
325
342
|
end
|
326
|
-
content_tag(
|
343
|
+
content_tag("form", inner_tags, form_options)
|
327
344
|
end
|
328
345
|
|
329
346
|
# Creates a link tag of the given +name+ using a URL created by the set of
|
@@ -389,22 +406,13 @@ module ActionView
|
|
389
406
|
# # If not...
|
390
407
|
# # => <a href="/accounts/signup">Reply</a>
|
391
408
|
def link_to_unless(condition, name, options = {}, html_options = {}, &block)
|
392
|
-
|
393
|
-
if block_given?
|
394
|
-
block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block)
|
395
|
-
else
|
396
|
-
ERB::Util.html_escape(name)
|
397
|
-
end
|
398
|
-
else
|
399
|
-
link_to(name, options, html_options)
|
400
|
-
end
|
409
|
+
link_to_if !condition, name, options, html_options, &block
|
401
410
|
end
|
402
411
|
|
403
412
|
# Creates a link tag of the given +name+ using a URL created by the set of
|
404
413
|
# +options+ if +condition+ is true, otherwise only the name is
|
405
414
|
# returned. To specialize the default behavior, you can pass a block that
|
406
|
-
# accepts the name or the full argument list for +
|
407
|
-
# in +link_to_unless+).
|
415
|
+
# accepts the name or the full argument list for +link_to_if+.
|
408
416
|
#
|
409
417
|
# ==== Examples
|
410
418
|
# <%= link_to_if(@current_user.nil?, "Login", { controller: "sessions", action: "new" }) %>
|
@@ -421,7 +429,15 @@ module ActionView
|
|
421
429
|
# # If they are logged in...
|
422
430
|
# # => <a href="/accounts/show/3">my_username</a>
|
423
431
|
def link_to_if(condition, name, options = {}, html_options = {}, &block)
|
424
|
-
|
432
|
+
if condition
|
433
|
+
link_to(name, options, html_options)
|
434
|
+
else
|
435
|
+
if block_given?
|
436
|
+
block.arity <= 1 ? capture(name, &block) : capture(name, options, html_options, &block)
|
437
|
+
else
|
438
|
+
ERB::Util.html_escape(name)
|
439
|
+
end
|
440
|
+
end
|
425
441
|
end
|
426
442
|
|
427
443
|
# Creates a mailto link tag to the specified +email_address+, which is
|
@@ -436,6 +452,7 @@ module ActionView
|
|
436
452
|
# * <tt>:body</tt> - Preset the body of the email.
|
437
453
|
# * <tt>:cc</tt> - Carbon Copy additional recipients on the email.
|
438
454
|
# * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email.
|
455
|
+
# * <tt>:reply_to</tt> - Preset the Reply-To field of the email.
|
439
456
|
#
|
440
457
|
# ==== Obfuscation
|
441
458
|
# Prior to Rails 4.0, +mail_to+ provided options for encoding the address
|
@@ -465,73 +482,64 @@ module ActionView
|
|
465
482
|
html_options, name = name, nil if block_given?
|
466
483
|
html_options = (html_options || {}).stringify_keys
|
467
484
|
|
468
|
-
extras = %w{ cc bcc body subject }.map! { |item|
|
469
|
-
option = html_options.delete(item) || next
|
470
|
-
"#{item}=#{
|
485
|
+
extras = %w{ cc bcc body subject reply_to }.map! { |item|
|
486
|
+
option = html_options.delete(item).presence || next
|
487
|
+
"#{item.dasherize}=#{ERB::Util.url_encode(option)}"
|
471
488
|
}.compact
|
472
|
-
extras = extras.empty? ?
|
489
|
+
extras = extras.empty? ? "" : "?" + extras.join("&")
|
473
490
|
|
474
491
|
encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@")
|
475
|
-
html_options["href"] = "mailto:#{encoded_email_address}#{extras}"
|
492
|
+
html_options["href"] = "mailto:#{encoded_email_address}#{extras}"
|
476
493
|
|
477
|
-
content_tag(
|
494
|
+
content_tag("a", name || email_address, html_options, &block)
|
478
495
|
end
|
479
496
|
|
480
497
|
# True if the current request URI was generated by the given +options+.
|
481
498
|
#
|
482
499
|
# ==== Examples
|
483
|
-
# Let's say we're in the <tt>http://www.example.com/shop/checkout?order=desc</tt> action.
|
500
|
+
# Let's say we're in the <tt>http://www.example.com/shop/checkout?order=desc&page=1</tt> action.
|
484
501
|
#
|
485
502
|
# current_page?(action: 'process')
|
486
503
|
# # => false
|
487
504
|
#
|
488
|
-
# current_page?(controller: 'shop', action: 'checkout')
|
489
|
-
# # => true
|
490
|
-
#
|
491
|
-
# current_page?(controller: 'shop', action: 'checkout', order: 'asc')
|
492
|
-
# # => false
|
493
|
-
#
|
494
505
|
# current_page?(action: 'checkout')
|
495
506
|
# # => true
|
496
507
|
#
|
497
508
|
# current_page?(controller: 'library', action: 'checkout')
|
498
509
|
# # => false
|
499
510
|
#
|
500
|
-
# current_page?('
|
501
|
-
# # => true
|
502
|
-
#
|
503
|
-
# current_page?('/shop/checkout')
|
511
|
+
# current_page?(controller: 'shop', action: 'checkout')
|
504
512
|
# # => true
|
505
513
|
#
|
506
|
-
#
|
507
|
-
#
|
508
|
-
# current_page?(action: 'process')
|
514
|
+
# current_page?(controller: 'shop', action: 'checkout', order: 'asc')
|
509
515
|
# # => false
|
510
516
|
#
|
511
|
-
# current_page?(controller: 'shop', action: 'checkout')
|
512
|
-
# # => true
|
513
|
-
#
|
514
517
|
# current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '1')
|
515
518
|
# # => true
|
516
519
|
#
|
517
520
|
# current_page?(controller: 'shop', action: 'checkout', order: 'desc', page: '2')
|
518
521
|
# # => false
|
519
522
|
#
|
520
|
-
# current_page?(
|
523
|
+
# current_page?('http://www.example.com/shop/checkout')
|
524
|
+
# # => true
|
525
|
+
#
|
526
|
+
# current_page?('http://www.example.com/shop/checkout', check_parameters: true)
|
521
527
|
# # => false
|
522
528
|
#
|
523
|
-
# current_page?(
|
529
|
+
# current_page?('/shop/checkout')
|
524
530
|
# # => true
|
525
531
|
#
|
526
|
-
# current_page?(
|
527
|
-
# # =>
|
532
|
+
# current_page?('http://www.example.com/shop/checkout?order=desc&page=1')
|
533
|
+
# # => true
|
528
534
|
#
|
529
535
|
# Let's say we're in the <tt>http://www.example.com/products</tt> action with method POST in case of invalid product.
|
530
536
|
#
|
531
537
|
# current_page?(controller: 'product', action: 'index')
|
532
538
|
# # => false
|
533
539
|
#
|
534
|
-
|
540
|
+
# We can also pass in the symbol arguments instead of strings.
|
541
|
+
#
|
542
|
+
def current_page?(options = nil, check_parameters: false, **options_as_kwargs)
|
535
543
|
unless request
|
536
544
|
raise "You cannot use helpers that need to determine the current " \
|
537
545
|
"page unless your view context provides a Request object " \
|
@@ -540,89 +548,223 @@ module ActionView
|
|
540
548
|
|
541
549
|
return false unless request.get? || request.head?
|
542
550
|
|
543
|
-
|
551
|
+
options ||= options_as_kwargs
|
552
|
+
check_parameters ||= options.is_a?(Hash) && options.delete(:check_parameters)
|
553
|
+
url_string = URI::DEFAULT_PARSER.unescape(url_for(options)).force_encoding(Encoding::BINARY)
|
544
554
|
|
545
555
|
# We ignore any extra parameters in the request_uri if the
|
546
|
-
# submitted
|
556
|
+
# submitted URL doesn't have any either. This lets the function
|
547
557
|
# work with things like ?order=asc
|
548
|
-
|
549
|
-
request_uri =
|
558
|
+
# the behaviour can be disabled with check_parameters: true
|
559
|
+
request_uri = url_string.index("?") || check_parameters ? request.fullpath : request.path
|
560
|
+
request_uri = URI::DEFAULT_PARSER.unescape(request_uri).force_encoding(Encoding::BINARY)
|
561
|
+
|
562
|
+
if url_string.start_with?("/") && url_string != "/"
|
563
|
+
url_string.chomp!("/")
|
564
|
+
request_uri.chomp!("/")
|
565
|
+
end
|
550
566
|
|
551
|
-
if url_string
|
567
|
+
if %r{^\w+://}.match?(url_string)
|
552
568
|
url_string == "#{request.protocol}#{request.host_with_port}#{request_uri}"
|
553
569
|
else
|
554
570
|
url_string == request_uri
|
555
571
|
end
|
556
572
|
end
|
557
573
|
|
574
|
+
# Creates an SMS anchor link tag to the specified +phone_number+, which is
|
575
|
+
# also used as the name of the link unless +name+ is specified. Additional
|
576
|
+
# HTML attributes for the link can be passed in +html_options+.
|
577
|
+
#
|
578
|
+
# When clicked, an SMS message is prepopulated with the passed phone number
|
579
|
+
# and optional +body+ value.
|
580
|
+
#
|
581
|
+
# +sms_to+ has a +body+ option for customizing the SMS message itself by
|
582
|
+
# passing special keys to +html_options+.
|
583
|
+
#
|
584
|
+
# ==== Options
|
585
|
+
# * <tt>:body</tt> - Preset the body of the message.
|
586
|
+
#
|
587
|
+
# ==== Examples
|
588
|
+
# sms_to "5155555785"
|
589
|
+
# # => <a href="sms:5155555785;">5155555785</a>
|
590
|
+
#
|
591
|
+
# sms_to "5155555785", "Text me"
|
592
|
+
# # => <a href="sms:5155555785;">Text me</a>
|
593
|
+
#
|
594
|
+
# sms_to "5155555785", "Text me",
|
595
|
+
# body: "Hello Jim I have a question about your product."
|
596
|
+
# # => <a href="sms:5155555785;?body=Hello%20Jim%20I%20have%20a%20question%20about%20your%20product">Text me</a>
|
597
|
+
#
|
598
|
+
# You can use a block as well if your link target is hard to fit into the name parameter. \ERB example:
|
599
|
+
#
|
600
|
+
# <%= sms_to "5155555785" do %>
|
601
|
+
# <strong>Text me:</strong>
|
602
|
+
# <% end %>
|
603
|
+
# # => <a href="sms:5155555785;">
|
604
|
+
# <strong>Text me:</strong>
|
605
|
+
# </a>
|
606
|
+
def sms_to(phone_number, name = nil, html_options = {}, &block)
|
607
|
+
html_options, name = name, nil if block_given?
|
608
|
+
html_options = (html_options || {}).stringify_keys
|
609
|
+
|
610
|
+
extras = %w{ body }.map! { |item|
|
611
|
+
option = html_options.delete(item).presence || next
|
612
|
+
"#{item.dasherize}=#{ERB::Util.url_encode(option)}"
|
613
|
+
}.compact
|
614
|
+
extras = extras.empty? ? "" : "?&" + extras.join("&")
|
615
|
+
|
616
|
+
encoded_phone_number = ERB::Util.url_encode(phone_number)
|
617
|
+
html_options["href"] = "sms:#{encoded_phone_number};#{extras}"
|
618
|
+
|
619
|
+
content_tag("a", name || phone_number, html_options, &block)
|
620
|
+
end
|
621
|
+
|
622
|
+
# Creates a TEL anchor link tag to the specified +phone_number+, which is
|
623
|
+
# also used as the name of the link unless +name+ is specified. Additional
|
624
|
+
# HTML attributes for the link can be passed in +html_options+.
|
625
|
+
#
|
626
|
+
# When clicked, the default app to make calls is opened, and it
|
627
|
+
# is prepopulated with the passed phone number and optional
|
628
|
+
# +country_code+ value.
|
629
|
+
#
|
630
|
+
# +phone_to+ has an optional +country_code+ option which automatically adds the country
|
631
|
+
# code as well as the + sign in the phone numer that gets prepopulated,
|
632
|
+
# for example if +country_code: "01"+ +\+01+ will be prepended to the
|
633
|
+
# phone numer, by passing special keys to +html_options+.
|
634
|
+
#
|
635
|
+
# ==== Options
|
636
|
+
# * <tt>:country_code</tt> - Prepends the country code to the number
|
637
|
+
#
|
638
|
+
# ==== Examples
|
639
|
+
# phone_to "1234567890"
|
640
|
+
# # => <a href="tel:1234567890">1234567890</a>
|
641
|
+
#
|
642
|
+
# phone_to "1234567890", "Phone me"
|
643
|
+
# # => <a href="tel:134567890">Phone me</a>
|
644
|
+
#
|
645
|
+
# phone_to "1234567890", "Phone me", country_code: "01"
|
646
|
+
# # => <a href="tel:+015155555785">Phone me</a>
|
647
|
+
#
|
648
|
+
# You can use a block as well if your link target is hard to fit into the name parameter. \ERB example:
|
649
|
+
#
|
650
|
+
# <%= phone_to "1234567890" do %>
|
651
|
+
# <strong>Phone me:</strong>
|
652
|
+
# <% end %>
|
653
|
+
# # => <a href="tel:1234567890">
|
654
|
+
# <strong>Phone me:</strong>
|
655
|
+
# </a>
|
656
|
+
def phone_to(phone_number, name = nil, html_options = {}, &block)
|
657
|
+
html_options, name = name, nil if block_given?
|
658
|
+
html_options = (html_options || {}).stringify_keys
|
659
|
+
|
660
|
+
country_code = html_options.delete("country_code").presence
|
661
|
+
country_code = country_code.nil? ? "" : "+#{ERB::Util.url_encode(country_code)}"
|
662
|
+
|
663
|
+
encoded_phone_number = ERB::Util.url_encode(phone_number)
|
664
|
+
html_options["href"] = "tel:#{country_code}#{encoded_phone_number}"
|
665
|
+
|
666
|
+
content_tag("a", name || phone_number, html_options, &block)
|
667
|
+
end
|
668
|
+
|
558
669
|
private
|
559
670
|
def convert_options_to_data_attributes(options, html_options)
|
560
671
|
if html_options
|
561
672
|
html_options = html_options.stringify_keys
|
562
|
-
html_options[
|
673
|
+
html_options["data-remote"] = "true" if link_to_remote_options?(options) || link_to_remote_options?(html_options)
|
563
674
|
|
564
|
-
method
|
675
|
+
method = html_options.delete("method")
|
565
676
|
|
566
677
|
add_method_to_attributes!(html_options, method) if method
|
567
678
|
|
568
679
|
html_options
|
569
680
|
else
|
570
|
-
link_to_remote_options?(options) ? {
|
681
|
+
link_to_remote_options?(options) ? { "data-remote" => "true" } : {}
|
571
682
|
end
|
572
683
|
end
|
573
684
|
|
574
685
|
def link_to_remote_options?(options)
|
575
686
|
if options.is_a?(Hash)
|
576
|
-
options.delete(
|
687
|
+
options.delete("remote") || options.delete(:remote)
|
577
688
|
end
|
578
689
|
end
|
579
690
|
|
580
691
|
def add_method_to_attributes!(html_options, method)
|
581
|
-
if method &&
|
582
|
-
|
692
|
+
if method_not_get_method?(method) && !html_options["rel"]&.match?(/nofollow/)
|
693
|
+
if html_options["rel"].blank?
|
694
|
+
html_options["rel"] = "nofollow"
|
695
|
+
else
|
696
|
+
html_options["rel"] = "#{html_options["rel"]} nofollow"
|
697
|
+
end
|
583
698
|
end
|
584
699
|
html_options["data-method"] = method
|
585
700
|
end
|
586
701
|
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
702
|
+
STRINGIFIED_COMMON_METHODS = {
|
703
|
+
get: "get",
|
704
|
+
delete: "delete",
|
705
|
+
patch: "patch",
|
706
|
+
post: "post",
|
707
|
+
put: "put",
|
708
|
+
}.freeze
|
709
|
+
|
710
|
+
def method_not_get_method?(method)
|
711
|
+
return false unless method
|
712
|
+
(STRINGIFIED_COMMON_METHODS[method] || method.to_s.downcase) != "get"
|
713
|
+
end
|
714
|
+
|
715
|
+
def token_tag(token = nil, form_options: {})
|
716
|
+
if token != false && defined?(protect_against_forgery?) && protect_against_forgery?
|
717
|
+
token ||= form_authenticity_token(form_options: form_options)
|
718
|
+
tag(:input, type: "hidden", name: request_forgery_protection_token.to_s, value: token)
|
719
|
+
else
|
720
|
+
""
|
721
|
+
end
|
722
|
+
end
|
723
|
+
|
724
|
+
def method_tag(method)
|
725
|
+
tag("input", type: "hidden", name: "_method", value: method.to_s)
|
726
|
+
end
|
727
|
+
|
728
|
+
# Returns an array of hashes each containing :name and :value keys
|
729
|
+
# suitable for use as the names and values of form input fields:
|
594
730
|
#
|
595
|
-
#
|
731
|
+
# to_form_params(name: 'David', nationality: 'Danish')
|
732
|
+
# # => [{name: 'name', value: 'David'}, {name: 'nationality', value: 'Danish'}]
|
596
733
|
#
|
597
|
-
#
|
598
|
-
#
|
599
|
-
# removed from the +html_options+ hash. (See the XHTML 1.0 spec,
|
600
|
-
# section 4.5 "Attribute Minimization" for more:
|
601
|
-
# http://www.w3.org/TR/xhtml1/#h-4.5)
|
734
|
+
# to_form_params(country: { name: 'Denmark' })
|
735
|
+
# # => [{name: 'country[name]', value: 'Denmark'}]
|
602
736
|
#
|
603
|
-
#
|
604
|
-
#
|
737
|
+
# to_form_params(countries: ['Denmark', 'Sweden']})
|
738
|
+
# # => [{name: 'countries[]', value: 'Denmark'}, {name: 'countries[]', value: 'Sweden'}]
|
605
739
|
#
|
606
|
-
#
|
740
|
+
# An optional namespace can be passed to enclose key names:
|
607
741
|
#
|
608
|
-
#
|
609
|
-
#
|
610
|
-
def
|
611
|
-
|
612
|
-
|
613
|
-
|
742
|
+
# to_form_params({ name: 'Denmark' }, 'country')
|
743
|
+
# # => [{name: 'country[name]', value: 'Denmark'}]
|
744
|
+
def to_form_params(attribute, namespace = nil)
|
745
|
+
attribute = if attribute.respond_to?(:permitted?)
|
746
|
+
attribute.to_h
|
747
|
+
else
|
748
|
+
attribute
|
749
|
+
end
|
614
750
|
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
751
|
+
params = []
|
752
|
+
case attribute
|
753
|
+
when Hash
|
754
|
+
attribute.each do |key, value|
|
755
|
+
prefix = namespace ? "#{namespace}[#{key}]" : key
|
756
|
+
params.push(*to_form_params(value, prefix))
|
757
|
+
end
|
758
|
+
when Array
|
759
|
+
array_prefix = "#{namespace}[]"
|
760
|
+
attribute.each do |value|
|
761
|
+
params.push(*to_form_params(value, array_prefix))
|
762
|
+
end
|
619
763
|
else
|
620
|
-
|
764
|
+
params << { name: namespace.to_s, value: attribute.to_param }
|
621
765
|
end
|
622
|
-
end
|
623
766
|
|
624
|
-
|
625
|
-
tag('input', type: 'hidden', name: '_method', value: method.to_s)
|
767
|
+
params.sort_by { |pair| pair[:name] }
|
626
768
|
end
|
627
769
|
end
|
628
770
|
end
|