diff options
Diffstat (limited to 'actionview/lib/action_view/helpers')
15 files changed, 229 insertions, 96 deletions
diff --git a/actionview/lib/action_view/helpers/asset_tag_helper.rb b/actionview/lib/action_view/helpers/asset_tag_helper.rb index e32f8e219e..e506c782d6 100644 --- a/actionview/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionview/lib/action_view/helpers/asset_tag_helper.rb @@ -60,7 +60,7 @@ module ActionView tag_options = { "src" => path_to_javascript(source, path_options) }.merge!(options) - content_tag(:script, "", tag_options) + content_tag("script".freeze, "", tag_options) }.join("\n").html_safe end @@ -237,7 +237,7 @@ module ActionView # image_alt('underscored_file_name.png') # # => Underscored file name def image_alt(src) - File.basename(src, '.*').sub(/-[[:xdigit:]]{32}\z/, '').tr('-_', ' ').capitalize + File.basename(src, '.*'.freeze).sub(/-[[:xdigit:]]{32}\z/, ''.freeze).tr('-_'.freeze, ' '.freeze).capitalize end # Returns an HTML video tag for the +sources+. If +sources+ is a string, diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb index ef4a6c98c0..717b326740 100644 --- a/actionview/lib/action_view/helpers/asset_url_helper.rb +++ b/actionview/lib/action_view/helpers/asset_url_helper.rb @@ -31,26 +31,33 @@ module ActionView # stylesheet_link_tag("application") # # => <link href="http://assets.example.com/assets/application.css" media="screen" rel="stylesheet" /> # - # Browsers typically open at most two simultaneous connections to a single - # host, which means your assets often have to wait for other assets to finish - # downloading. You can alleviate this by using a <tt>%d</tt> wildcard in the - # +asset_host+. For example, "assets%d.example.com". If that wildcard is - # present Rails distributes asset requests among the corresponding four hosts - # "assets0.example.com", ..., "assets3.example.com". With this trick browsers - # will open eight simultaneous connections rather than two. + # Browsers open a limited number of simultaneous connections to a single + # host. The exact number varies by browser and version. This limit may cause + # some asset downloads to wait for previous assets to finish before they can + # begin. You can use the <tt>%d</tt> wildcard in the +asset_host+ to + # distribute the requests over four hosts. For example, + # <tt>assets%d.example.com<tt> will spread the asset requests over + # "assets0.example.com", ..., "assets3.example.com". # # image_tag("rails.png") # # => <img alt="Rails" src="http://assets0.example.com/assets/rails.png" /> # stylesheet_link_tag("application") # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" /> # - # To do this, you can either setup four actual hosts, or you can use wildcard - # DNS to CNAME the wildcard to a single asset host. You can read more about - # setting up your DNS CNAME records from your ISP. + # This may improve the asset loading performance of your application. + # It is also possible the combination of additional connection overhead + # (DNS, SSL) and the overall browser connection limits may result in this + # solution being slower. You should be sure to measure your actual + # performance across targeted browsers both before and after this change. + # + # To implement the corresponding hosts you can either setup four actual + # hosts or use wildcard DNS to CNAME the wildcard to a single asset host. + # You can read more about setting up your DNS CNAME records from your ISP. # # Note: This is purely a browser performance optimization and is not meant # for server load balancing. See http://www.die.net/musings/page_load_time/ - # for background. + # for background and http://www.browserscope.org/?category=network for + # connection limit data. # # Alternatively, you can exert more control over the asset host by setting # +asset_host+ to a proc like this: @@ -121,11 +128,13 @@ module ActionView # asset_path "application", type: :stylesheet # => /assets/application.css # asset_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js def asset_path(source, options = {}) + raise ArgumentError, "nil is not a valid asset source" if source.nil? + source = source.to_s return "" unless source.present? return source if source =~ URI_REGEXP - tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, '') + tail, source = source[/([\?#].+)$/], source.sub(/([\?#].+)$/, ''.freeze) if extname = compute_asset_extname(source, options) source = "#{source}#{extname}" diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb index 251764a8de..636d7d4cc3 100644 --- a/actionview/lib/action_view/helpers/cache_helper.rb +++ b/actionview/lib/action_view/helpers/cache_helper.rb @@ -39,7 +39,7 @@ module ActionView # This will include both records as part of the cache key and updating either of them will # expire the cache. # - # ==== Template digest + # ==== \Template digest # # The template digest that's added to the cache key is computed by taking an md5 of the # contents of the entire template file. This ensures that your caches will automatically @@ -75,7 +75,7 @@ module ActionView # render(topics) => render("topics/topic") # render(message.topics) => render("topics/topic") # - # It's not possible to derive all render calls like that, though. + # It's not possible to derive all render calls like that, though. # Here are a few examples of things that can't be derived: # # render group_of_attachments @@ -98,14 +98,26 @@ module ActionView # <%# Template Dependency: todolists/todolist %> # <%= render_sortable_todolists @project.todolists %> # - # The pattern used to match these is /# Template Dependency: ([^ ]+)/, + # In some cases, like a single table inheritance setup, you might have + # a bunch of explicit dependencies. Instead of writing every template out, + # you can use a wildcard to match any template in a directory: + # + # <%# Template Dependency: events/* %> + # <%= render_categorizable_events @person.events %> + # + # This marks every template in the directory as a dependency. To find those + # templates, the wildcard path must be absolutely defined from app/views or paths + # otherwise added with +prepend_view_path+ or +append_view_path+. + # This way the wildcard for `app/views/recordings/events` would be `recordings/events/*` etc. + # + # The pattern used to match explicit dependencies is <tt>/# Template Dependency: (\S+)/</tt>, # so it's important that you type it out just so. # You can only declare one template dependency per line. # # === External dependencies # - # If you use a helper method, for example, inside a cached block and - # you then update that helper, you'll have to bump the cache as well. + # If you use a helper method, for example, inside a cached block and + # you then update that helper, you'll have to bump the cache as well. # It doesn't really matter how you do it, but the md5 of the template file # must change. One recommendation is to simply be explicit in a comment, like: # @@ -130,14 +142,29 @@ module ActionView # The collection can then automatically use any cached renders for that # template by reading them at once instead of one by one. # - # See ActionView::Template::Handlers::ERB.resource_cache_call_pattern for - # more information on what cache calls make a template eligible for this + # See ActionView::Template::Handlers::ERB.resource_cache_call_pattern for + # more information on what cache calls make a template eligible for this # collection caching. # # The automatic cache multi read can be turned off like so: # # <%= render @notifications, cache: false %> - def cache(name = {}, options = nil, &block) + # + # === Explicit Collection Caching + # + # If the partial template doesn't start with a clean cache call as + # mentioned above, you can still benefit from collection caching by + # adding a special comment format anywhere in the template, like: + # + # <%# Template Collection: notification %> + # <% my_helper_that_calls_cache(some_arg, notification) do %> + # <%= notification.name %> + # <% end %> + # + # The pattern used to match these is <tt>/# Template Collection: (\S+)/</tt>, + # so it's important that you type it out just so. + # You can only declare one collection in a partial template file. + def cache(name = {}, options = {}, &block) if controller.respond_to?(:perform_caching) && controller.perform_caching safe_concat(fragment_for(cache_fragment_name(name, options), options, &block)) else @@ -153,7 +180,7 @@ module ActionView # <b>All the topics on this project</b> # <%= render project.topics %> # <% end %> - def cache_if(condition, name = {}, options = nil, &block) + def cache_if(condition, name = {}, options = {}, &block) if condition cache(name, options, &block) else @@ -169,22 +196,23 @@ module ActionView # <b>All the topics on this project</b> # <%= render project.topics %> # <% end %> - def cache_unless(condition, name = {}, options = nil, &block) + def cache_unless(condition, name = {}, options = {}, &block) cache_if !condition, name, options, &block end # This helper returns the name of a cache key for a given fragment cache - # call. By supplying skip_digest: true to cache, the digestion of cache + # call. By supplying +skip_digest:+ true to cache, the digestion of cache # fragments can be manually bypassed. This is useful when cache fragments # cannot be manually expired unless you know the exact key which is the # case when using memcached. - def cache_fragment_name(name = {}, options = nil) - skip_digest = options && options[:skip_digest] - + # + # The digest will be generated using +virtual_path:+ if it is provided. + # + def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil) if skip_digest name else - fragment_name_with_digest(name) + fragment_name_with_digest(name, virtual_path) end end @@ -198,10 +226,11 @@ module ActionView private - def fragment_name_with_digest(name) #:nodoc: - if @virtual_path + def fragment_name_with_digest(name, virtual_path) #:nodoc: + virtual_path ||= @virtual_path + if virtual_path names = Array(name.is_a?(Hash) ? controller.url_for(name).split("://").last : name) - digest = Digestor.digest name: @virtual_path, finder: lookup_context, dependencies: view_cache_dependencies + digest = Digestor.digest name: virtual_path, finder: lookup_context, dependencies: view_cache_dependencies [ *names, digest ] else diff --git a/actionview/lib/action_view/helpers/capture_helper.rb b/actionview/lib/action_view/helpers/capture_helper.rb index a67ba580f1..93c7cba395 100644 --- a/actionview/lib/action_view/helpers/capture_helper.rb +++ b/actionview/lib/action_view/helpers/capture_helper.rb @@ -115,7 +115,7 @@ module ActionView # <li><%= link_to 'Home', action: 'index' %></li> # <% end %> # - # And in other place: + # And in another place: # # <% content_for :navigation do %> # <li><%= link_to 'Login', action: 'login' %></li> diff --git a/actionview/lib/action_view/helpers/date_helper.rb b/actionview/lib/action_view/helpers/date_helper.rb index 46ce7cf0be..312e41ee48 100644 --- a/actionview/lib/action_view/helpers/date_helper.rb +++ b/actionview/lib/action_view/helpers/date_helper.rb @@ -68,6 +68,27 @@ module ActionView # distance_of_time_in_words(from_time, to_time, include_seconds: true) # => about 6 years # distance_of_time_in_words(to_time, from_time, include_seconds: true) # => about 6 years # distance_of_time_in_words(Time.now, Time.now) # => less than a minute + # + # With the <tt>scope</tt> option, you can define a custom scope for Rails + # to look up the translation. + # + # For example you can define the following in your locale (e.g. en.yml). + # + # datetime: + # distance_in_words: + # short: + # about_x_hours: + # one: 'an hour' + # other: '%{count} hours' + # + # See https://github.com/svenfuchs/rails-i18n/blob/master/rails/locale/en.yml + # for more examples. + # + # Which will then result in the following: + # + # from_time = Time.now + # distance_of_time_in_words(from_time, from_time + 50.minutes, scope: 'datetime.distance_in_words.short') # => "an hour" + # distance_of_time_in_words(from_time, from_time + 3.hours, scope: 'datetime.distance_in_words.short') # => "3 hours" def distance_of_time_in_words(from_time, to_time = 0, options = {}) options = { scope: :'datetime.distance_in_words' @@ -207,6 +228,7 @@ module ActionView # or the given prompt string. # * <tt>:with_css_classes</tt> - Set to true if you want assign different styles for 'select' tags. This option # automatically set classes 'year', 'month', 'day', 'hour', 'minute' and 'second' for your 'select' tags. + # * <tt>:use_hidden</tt> - Set to true if you only want to generate hidden input tags. # # If anything is passed in the +html_options+ hash it will be applied to every select tag in the set. # @@ -464,7 +486,7 @@ module ActionView # The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer. # Override the field name using the <tt>:field_name</tt> option, 'second' by default. # - # my_time = Time.now + 16.minutes + # my_time = Time.now + 16.seconds # # # Generates a select field for seconds that defaults to the seconds for the time in my_time. # select_second(my_time) @@ -488,7 +510,7 @@ module ActionView # selected. The <tt>datetime</tt> can be either a +Time+ or +DateTime+ object or an integer. # Override the field name using the <tt>:field_name</tt> option, 'minute' by default. # - # my_time = Time.now + 6.hours + # my_time = Time.now + 10.minutes # # # Generates a select field for minutes that defaults to the minutes for the time in my_time. # select_minute(my_time) @@ -660,7 +682,7 @@ module ActionView content = args.first || I18n.l(date_or_time, :format => format) datetime = date_or_time.acts_like?(:time) ? date_or_time.xmlschema : date_or_time.iso8601 - content_tag(:time, content, options.reverse_merge(:datetime => datetime), &block) + content_tag("time".freeze, content, options.reverse_merge(:datetime => datetime), &block) end end @@ -788,7 +810,7 @@ module ActionView 1.upto(12) do |month_number| options = { :value => month_number } options[:selected] = "selected" if month == month_number - month_options << content_tag(:option, month_name(month_number), options) + "\n" + month_options << content_tag("option".freeze, month_name(month_number), options) + "\n" end build_select(:month, month_options.join) end @@ -950,7 +972,7 @@ module ActionView tag_options[:selected] = "selected" if selected == i text = options[:use_two_digit_numbers] ? sprintf("%02d", i) : value text = options[:ampm] ? AMPM_TRANSLATION[i] : text - select_options << content_tag(:option, text, tag_options) + select_options << content_tag("option".freeze, text, tag_options) end (select_options.join("\n") + "\n").html_safe @@ -970,11 +992,11 @@ module ActionView select_options[:class] = [select_options[:class], type].compact.join(' ') if @options[:with_css_classes] select_html = "\n" - select_html << content_tag(:option, '', :value => '') + "\n" if @options[:include_blank] + select_html << content_tag("option".freeze, '', :value => '') + "\n" if @options[:include_blank] select_html << prompt_option_tag(type, @options[:prompt]) + "\n" if @options[:prompt] select_html << select_options_as_html - (content_tag(:select, select_html.html_safe, select_options) + "\n").html_safe + (content_tag("select".freeze, select_html.html_safe, select_options) + "\n").html_safe end # Builds a prompt option tag with supplied options or from default options. @@ -991,7 +1013,7 @@ module ActionView I18n.translate(:"datetime.prompts.#{type}", :locale => @options[:locale]) end - prompt ? content_tag(:option, prompt, :value => '') : '' + prompt ? content_tag("option".freeze, prompt, :value => '') : '' end # Builds hidden input tag for date part and value. diff --git a/actionview/lib/action_view/helpers/form_options_helper.rb b/actionview/lib/action_view/helpers/form_options_helper.rb index d3deee0df3..728e633bbf 100644 --- a/actionview/lib/action_view/helpers/form_options_helper.rb +++ b/actionview/lib/action_view/helpers/form_options_helper.rb @@ -35,8 +35,8 @@ module ActionView # <select name="post[person_id]" id="post_person_id"> # <option value="">None</option> # <option value="1">David</option> - # <option value="2" selected="selected">Sam</option> - # <option value="3">Tobias</option> + # <option value="2" selected="selected">Eileen</option> + # <option value="3">Rafael</option> # </select> # # * <tt>:prompt</tt> - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string. @@ -48,8 +48,8 @@ module ActionView # <select name="post[person_id]" id="post_person_id"> # <option value="">Select Person</option> # <option value="1">David</option> - # <option value="2">Sam</option> - # <option value="3">Tobias</option> + # <option value="2">Eileen</option> + # <option value="3">Rafael</option> # </select> # # * <tt>:index</tt> - like the other form helpers, +select+ can accept an <tt>:index</tt> option to manually set the ID used in the resulting output. Unlike other helpers, +select+ expects this @@ -112,8 +112,8 @@ module ActionView # <select name="post[person_id]" id="post_person_id"> # <option value=""></option> # <option value="1" selected="selected">David</option> - # <option value="2">Sam</option> - # <option value="3">Tobias</option> + # <option value="2">Eileen</option> + # <option value="3">Rafael</option> # </select> # # assuming the associated person has ID 1. @@ -456,7 +456,7 @@ module ActionView option_tags = options_from_collection_for_select( group.send(group_method), option_key_method, option_value_method, selected_key) - content_tag(:optgroup, option_tags, label: group.send(group_label_method)) + content_tag("optgroup".freeze, option_tags, label: group.send(group_label_method)) end.join.html_safe end @@ -528,7 +528,7 @@ module ActionView body = "".html_safe if prompt - body.safe_concat content_tag(:option, prompt_text(prompt), value: "") + body.safe_concat content_tag("option".freeze, prompt_text(prompt), value: "") end grouped_options.each do |container| @@ -541,7 +541,7 @@ module ActionView end html_attributes = { label: label }.merge!(html_attributes) - body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), html_attributes) + body.safe_concat content_tag("optgroup".freeze, options_for_select(container, selected_key), html_attributes) end body @@ -577,7 +577,7 @@ module ActionView end zone_options.safe_concat options_for_select(convert_zones[priority_zones], selected) - zone_options.safe_concat content_tag(:option, '-------------', value: '', disabled: true) + zone_options.safe_concat content_tag("option".freeze, '-------------', value: '', disabled: true) zone_options.safe_concat "\n" zones = zones - priority_zones @@ -707,6 +707,27 @@ module ActionView # collection_check_boxes(:post, :author_ids, Author.all, :id, :name_with_initial) do |b| # b.label(:"data-value" => b.value) { b.check_box + b.text } # end + # + # ==== Gotcha + # + # When no selection is made for a collection of checkboxes most + # web browsers will not send any value. + # + # For example, if we have a +User+ model with +category_ids+ field and we + # have the following code in our update action: + # + # @user.update(params[:user]) + # + # If no +category_ids+ are selected then we can safely assume this field + # will not be updated. + # + # This is possible thanks to a hidden field generated by the helper method + # for every collection of checkboxes. + # This hidden field is given the same field name as the checkboxes with a + # blank value. + # + # In the rare case you don't want this hidden field, you can pass the + # <tt>include_hidden: false</tt> option to the helper method. def collection_check_boxes(object, method, collection, value_method, text_method, options = {}, html_options = {}, &block) Tags::CollectionCheckBoxes.new(object, method, self, collection, value_method, text_method, options, html_options).render(&block) end diff --git a/actionview/lib/action_view/helpers/form_tag_helper.rb b/actionview/lib/action_view/helpers/form_tag_helper.rb index 1f76f40138..af684e05c6 100644 --- a/actionview/lib/action_view/helpers/form_tag_helper.rb +++ b/actionview/lib/action_view/helpers/form_tag_helper.rb @@ -140,15 +140,15 @@ module ActionView end if include_blank - option_tags = content_tag(:option, include_blank, value: '').safe_concat(option_tags) + option_tags = content_tag("option".freeze, include_blank, value: '').safe_concat(option_tags) end end if prompt = options.delete(:prompt) - option_tags = content_tag(:option, prompt, value: '').safe_concat(option_tags) + option_tags = content_tag("option".freeze, prompt, value: '').safe_concat(option_tags) end - content_tag :select, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys) + content_tag "select".freeze, option_tags, { "name" => html_name, "id" => sanitize_to_id(name) }.update(options.stringify_keys) end # Creates a standard text field; use these text fields to input smaller chunks of text like a username @@ -414,42 +414,57 @@ module ActionView # the form is processed normally, otherwise no action is taken. # * <tt>:disable_with</tt> - Value of this parameter will be used as the value for a # disabled version of the submit button when the form is submitted. This feature is - # provided by the unobtrusive JavaScript driver. + # provided by the unobtrusive JavaScript driver. To disable this feature for a single submit tag + # pass <tt>:data => { disable_with: false }</tt> Defaults to value attribute. # # ==== Examples # submit_tag - # # => <input name="commit" type="submit" value="Save changes" /> + # # => <input name="commit" data-disable-with="Save changes" type="submit" value="Save changes" /> # # submit_tag "Edit this article" - # # => <input name="commit" type="submit" value="Edit this article" /> + # # => <input name="commit" data-disable-with="Edit this article" type="submit" value="Edit this article" /> # # submit_tag "Save edits", disabled: true - # # => <input disabled="disabled" name="commit" type="submit" value="Save edits" /> + # # => <input disabled="disabled" name="commit" data-disable-with="Save edits" type="submit" value="Save edits" /> # - # submit_tag "Complete sale", data: { disable_with: "Please wait..." } - # # => <input name="commit" data-disable-with="Please wait..." type="submit" value="Complete sale" /> + # submit_tag "Complete sale", data: { disable_with: "Submitting..." } + # # => <input name="commit" data-disable-with="Submitting..." type="submit" value="Complete sale" /> # # submit_tag nil, class: "form_submit" # # => <input class="form_submit" name="commit" type="submit" /> # # submit_tag "Edit", class: "edit_button" - # # => <input class="edit_button" name="commit" type="submit" value="Edit" /> + # # => <input class="edit_button" data-disable-with="Edit" name="commit" type="submit" value="Edit" /> # # submit_tag "Save", data: { confirm: "Are you sure?" } - # # => <input name='commit' type='submit' value='Save' data-confirm="Are you sure?" /> + # # => <input name='commit' type='submit' value='Save' data-disable-with="Save" data-confirm="Are you sure?" /> # def submit_tag(value = "Save changes", options = {}) options = options.stringify_keys + tag_options = { "type" => "submit", "name" => "commit", "value" => value }.update(options) + + if ActionView::Base.automatically_disable_submit_tag + unless tag_options["data-disable-with"] == false || (tag_options["data"] && tag_options["data"][:disable_with] == false) + disable_with_text = tag_options["data-disable-with"] + disable_with_text ||= tag_options["data"][:disable_with] if tag_options["data"] + disable_with_text ||= value.clone + tag_options.deep_merge!("data" => { "disable_with" => disable_with_text }) + else + tag_options.delete("data-disable-with") + tag_options["data"].delete(:disable_with) if tag_options["data"] + end + end - tag :input, { "type" => "submit", "name" => "commit", "value" => value }.update(options) + tag :input, tag_options end # Creates a button element that defines a <tt>submit</tt> button, # <tt>reset</tt>button or a generic button which can be used in # JavaScript, for example. You can use the button tag as a regular # submit tag but it isn't supported in legacy browsers. However, - # the button tag allows richer labels such as images and emphasis, - # so this helper will also accept a block. + # the button tag does allow for richer labels such as images and emphasis, + # so this helper will also accept a block. By default, it will create + # a button tag with type `submit`, if type is not given. # # ==== Options # * <tt>:data</tt> - This option can be used to add custom data attributes. @@ -472,6 +487,15 @@ module ActionView # button_tag # # => <button name="button" type="submit">Button</button> # + # button_tag 'Reset', type: 'reset' + # # => <button name="button" type="reset">Reset</button> + # + # button_tag 'Button', type: 'button' + # # => <button name="button" type="button">Button</button> + # + # button_tag 'Reset', type: 'reset', disabled: true + # # => <button name="button" type="reset" disabled="disabled">Reset</button> + # # button_tag(type: 'button') do # content_tag(:strong, 'Ask me!') # end @@ -479,6 +503,9 @@ module ActionView # # <strong>Ask me!</strong> # # </button> # + # button_tag "Save", data: { confirm: "Are you sure?" } + # # => <button name="button" type="submit" data-confirm="Are you sure?">Save</button> + # # button_tag "Checkout", data: { disable_with: "Please wait..." } # # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button> # @@ -555,7 +582,7 @@ module ActionView # # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset> def field_set_tag(legend = nil, options = nil, &block) output = tag(:fieldset, options, true) - output.safe_concat(content_tag(:legend, legend)) unless legend.blank? + output.safe_concat(content_tag("legend".freeze, legend)) unless legend.blank? output.concat(capture(&block)) if block_given? output.safe_concat("</fieldset>") end diff --git a/actionview/lib/action_view/helpers/javascript_helper.rb b/actionview/lib/action_view/helpers/javascript_helper.rb index e237a32cb7..ed7e882c94 100644 --- a/actionview/lib/action_view/helpers/javascript_helper.rb +++ b/actionview/lib/action_view/helpers/javascript_helper.rb @@ -47,8 +47,8 @@ module ActionView # tag. # # javascript_tag "alert('All is good')", defer: 'defer' - # - # Returns: + # + # Returns: # <script defer="defer"> # //<![CDATA[ # alert('All is good') @@ -70,7 +70,7 @@ module ActionView content_or_options_with_block end - content_tag(:script, javascript_cdata_section(content), html_options) + content_tag("script".freeze, javascript_cdata_section(content), html_options) end def javascript_cdata_section(content) #:nodoc: diff --git a/actionview/lib/action_view/helpers/number_helper.rb b/actionview/lib/action_view/helpers/number_helper.rb index ca8d30e4ef..13effa592d 100644 --- a/actionview/lib/action_view/helpers/number_helper.rb +++ b/actionview/lib/action_view/helpers/number_helper.rb @@ -279,7 +279,7 @@ module ActionView # See <tt>number_to_human_size</tt> if you want to print a file # size. # - # You can also define you own unit-quantifier names if you want + # You can also define your own unit-quantifier names if you want # to use other decimal units (eg.: 1500 becomes "1.5 # kilometers", 0.150 becomes "150 milliliters", etc). You may # define a wide range of unit quantifiers, even fractional ones diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb index 827932d8e2..c98f2d74a8 100644 --- a/actionview/lib/action_view/helpers/rendering_helper.rb +++ b/actionview/lib/action_view/helpers/rendering_helper.rb @@ -18,7 +18,7 @@ module ActionView # performs HTML escape on the string first. Setting the content type as # <tt>text/html</tt>. # * <tt>:body</tt> - Renders the text passed in, and inherits the content - # type of <tt>text/html</tt> from <tt>ActionDispatch::Response</tt> + # type of <tt>text/plain</tt> from <tt>ActionDispatch::Response</tt> # object. # # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter diff --git a/actionview/lib/action_view/helpers/sanitize_helper.rb b/actionview/lib/action_view/helpers/sanitize_helper.rb index a2e9f37453..191a881de0 100644 --- a/actionview/lib/action_view/helpers/sanitize_helper.rb +++ b/actionview/lib/action_view/helpers/sanitize_helper.rb @@ -120,7 +120,7 @@ module ActionView attr_writer :full_sanitizer, :link_sanitizer, :white_list_sanitizer # Vendors the full, link and white list sanitizers. - # Provided strictly for compabitility and can be removed in Rails 5. + # Provided strictly for compatibility and can be removed in Rails 5. def sanitizer_vendor Rails::Html::Sanitizer end diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb index a87c223a71..2562504896 100644 --- a/actionview/lib/action_view/helpers/tag_helper.rb +++ b/actionview/lib/action_view/helpers/tag_helper.rb @@ -22,9 +22,10 @@ module ActionView TAG_PREFIXES = ['aria', 'data', :aria, :data].to_set - PRE_CONTENT_STRINGS = { - :textarea => "\n" - } + PRE_CONTENT_STRINGS = Hash.new { "".freeze } + PRE_CONTENT_STRINGS[:textarea] = "\n" + PRE_CONTENT_STRINGS["textarea"] = "\n" + # Returns an empty HTML tag of type +name+ which by default is XHTML # compliant. Set +open+ to true to create an open tag compatible @@ -143,24 +144,30 @@ module ActionView def content_tag_string(name, content, options, escape = true) tag_options = tag_options(options, escape) if options content = ERB::Util.unwrapped_html_escape(content) if escape - "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name.to_sym]}#{content}</#{name}>".html_safe + "<#{name}#{tag_options}>#{PRE_CONTENT_STRINGS[name]}#{content}</#{name}>".html_safe end def tag_options(options, escape = true) return if options.blank? - attrs = [] + output = "" + sep = " ".freeze options.each_pair do |key, value| if TAG_PREFIXES.include?(key) && value.is_a?(Hash) value.each_pair do |k, v| - attrs << prefix_tag_option(key, k, v, escape) + output << sep + output << prefix_tag_option(key, k, v, escape) end elsif BOOLEAN_ATTRIBUTES.include?(key) - attrs << boolean_tag_option(key) if value + if value + output << sep + output << boolean_tag_option(key) + end elsif !value.nil? - attrs << tag_option(key, value, escape) + output << sep + output << tag_option(key, value, escape) end end - " #{attrs * ' '}" unless attrs.empty? + output unless output.empty? end def prefix_tag_option(prefix, key, value, escape) @@ -177,7 +184,7 @@ module ActionView def tag_option(key, value, escape) if value.is_a?(Array) - value = escape ? safe_join(value, " ") : value.join(" ") + value = escape ? safe_join(value, " ".freeze) : value.join(" ".freeze) else value = escape ? ERB::Util.unwrapped_html_escape(value) : value end diff --git a/actionview/lib/action_view/helpers/tags/base.rb b/actionview/lib/action_view/helpers/tags/base.rb index acc6443a96..d57f26ba4f 100644 --- a/actionview/lib/action_view/helpers/tags/base.rb +++ b/actionview/lib/action_view/helpers/tags/base.rb @@ -120,7 +120,12 @@ module ActionView def select_content_tag(option_tags, options, html_options) html_options = html_options.stringify_keys add_default_name_and_id(html_options) - options[:include_blank] ||= true unless options[:prompt] || select_not_required?(html_options) + + if placeholder_required?(html_options) + raise ArgumentError, "include_blank cannot be false for a required field." if options[:include_blank] == false + options[:include_blank] ||= true unless options[:prompt] + end + value = options.fetch(:selected) { value(object) } select = content_tag("select", add_options(option_tags, options, value), html_options) @@ -131,8 +136,9 @@ module ActionView end end - def select_not_required?(html_options) - !html_options["required"] || html_options["multiple"] || html_options["size"].to_i > 1 + def placeholder_required?(html_options) + # See https://html.spec.whatwg.org/multipage/forms.html#attr-select-required + html_options["required"] && !html_options["multiple"] && html_options.fetch("size", 1).to_i == 1 end def add_options(option_tags, options, value = nil) diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb index c216d4401f..432693bc23 100644 --- a/actionview/lib/action_view/helpers/text_helper.rb +++ b/actionview/lib/action_view/helpers/text_helper.rb @@ -206,6 +206,11 @@ module ActionView # +plural+ is supplied, it will use that when count is > 1, otherwise # it will use the Inflector to determine the plural form. # + # If passed an optional +locale:+ parameter, the word will be pluralized + # using rules defined for that language (you must define your own + # inflection rules for languages other than English). See + # ActiveSupport::Inflector.pluralize + # # pluralize(1, 'person') # # => 1 person # @@ -217,11 +222,14 @@ module ActionView # # pluralize(0, 'person') # # => 0 people - def pluralize(count, singular, plural = nil) + # + # pluralize(2, 'Person', locale: :de) + # # => 2 Personen + def pluralize(count, singular, plural = nil, locale: nil) word = if (count == 1 || count =~ /^1(\.0+)?$/) singular else - plural || singular.pluralize + plural || singular.pluralize(locale) end "#{count || 0} #{word}" @@ -242,12 +250,15 @@ module ActionView # # word_wrap('Once upon a time', line_width: 1) # # => Once\nupon\na\ntime - def word_wrap(text, options = {}) - line_width = options.fetch(:line_width, 80) - + # + # You can also specify a custom +break_sequence+ ("\n" by default) + # + # word_wrap('Once upon a time', line_width: 1, break_sequence: "\r\n") + # # => Once\r\nupon\r\na\r\ntime + def word_wrap(text, line_width: 80, break_sequence: "\n") text.split("\n").collect! do |line| - line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line - end * "\n" + line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1#{break_sequence}").strip : line + end * break_sequence end # Returns +text+ transformed into HTML using simple formatting rules. diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb index afb1265ad9..d676a0a931 100644 --- a/actionview/lib/action_view/helpers/url_helper.rb +++ b/actionview/lib/action_view/helpers/url_helper.rb @@ -184,9 +184,9 @@ module ActionView html_options = convert_options_to_data_attributes(options, html_options) url = url_for(options) - html_options['href'] ||= url + html_options["href".freeze] ||= url - content_tag(:a, name || url, html_options, &block) + content_tag("a".freeze, name || url, html_options, &block) end # Generates a form containing a single button that submits to the URL created @@ -468,9 +468,10 @@ module ActionView }.compact extras = extras.empty? ? '' : '?' + extras.join('&') - html_options["href"] = "mailto:#{email_address}#{extras}" + encoded_email_address = ERB::Util.url_encode(email_address).gsub("%40", "@") + html_options["href"] = "mailto:#{encoded_email_address}#{extras}" - content_tag(:a, name || email_address, html_options, &block) + content_tag("a".freeze, name || email_address, html_options, &block) end # True if the current request URI was generated by the given +options+. |