diff options
Diffstat (limited to 'actionpack/lib/action_view')
37 files changed, 679 insertions, 381 deletions
diff --git a/actionpack/lib/action_view/asset_paths.rb b/actionpack/lib/action_view/asset_paths.rb index add8d94b70..4ce41d51f1 100644 --- a/actionpack/lib/action_view/asset_paths.rb +++ b/actionpack/lib/action_view/asset_paths.rb @@ -4,7 +4,7 @@ require 'action_controller/metal/exceptions' module ActionView class AssetPaths #:nodoc: - URI_REGEXP = %r{^[-a-z]+://|^cid:|^//} + URI_REGEXP = %r{^[-a-z]+://|^(?:cid|data):|^//} attr_reader :config, :controller diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 5f81f24a2e..f98648d930 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -1,6 +1,7 @@ require 'active_support/core_ext/module/attr_internal' require 'active_support/core_ext/module/delegation' require 'active_support/core_ext/class/attribute' +require 'active_support/core_ext/class/attribute_accessors' require 'active_support/ordered_options' require 'action_view/log_subscriber' diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb index 083856b2ca..245849d706 100644 --- a/actionpack/lib/action_view/context.rb +++ b/actionpack/lib/action_view/context.rb @@ -5,7 +5,7 @@ module ActionView # = Action View Context # - # Action View contexts are supplied to Action Controller to render template. + # Action View contexts are supplied to Action Controller to render a template. # The default Action View context is ActionView::Base. # # In order to work with ActionController, a Context must just include this module. @@ -25,7 +25,7 @@ module ActionView end # Encapsulates the interaction with the view flow so it - # returns the correct buffer on yield. This is usually + # returns the correct buffer on +yield+. This is usually # overwriten by helpers to add more behavior. # :api: plugin def _layout_for(name=nil) diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index 7985683a72..02c1250c76 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -13,15 +13,16 @@ module ActionView # the assets exist before linking to them: # # image_tag("rails.png") - # # => <img alt="Rails" src="/images/rails.png?1230601161" /> + # # => <img alt="Rails" src="/assets/rails.png" /> # stylesheet_link_tag("application") - # # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" /> + # # => <link href="/assets/application.css?body=1" media="screen" rel="stylesheet" /> + # # # === Using asset hosts # # By default, Rails links to these assets on the current host in the public # folder, but you can direct Rails to link to assets from a dedicated asset - # server by setting ActionController::Base.asset_host in the application + # server by setting <tt>ActionController::Base.asset_host</tt> in the application # configuration, typically in <tt>config/environments/production.rb</tt>. # For example, you'd define <tt>assets.example.com</tt> to be your asset # host this way, inside the <tt>configure</tt> block of your environment-specific @@ -32,9 +33,9 @@ module ActionView # Helpers take that into account: # # image_tag("rails.png") - # # => <img alt="Rails" src="http://assets.example.com/images/rails.png?1230601161" /> + # # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" /> # stylesheet_link_tag("application") - # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" /> + # # => <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 @@ -45,9 +46,9 @@ module ActionView # will open eight simultaneous connections rather than two. # # image_tag("rails.png") - # # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" /> + # # => <img alt="Rails" src="http://assets0.example.com/assets/rails.png" /> # stylesheet_link_tag("application") - # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" /> + # # => <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 @@ -64,29 +65,28 @@ module ActionView # "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com" # } # image_tag("rails.png") - # # => <img alt="Rails" src="http://assets1.example.com/images/rails.png?1230601161" /> + # # => <img alt="Rails" src="http://assets1.example.com/assets/rails.png" /> # stylesheet_link_tag("application") - # # => <link href="http://assets2.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" /> + # # => <link href="http://assets2.example.com/assets/application.css" media="screen" rel="stylesheet" /> # # The example above generates "http://assets1.example.com" and # "http://assets2.example.com". This option is useful for example if # you need fewer/more than four hosts, custom host names, etc. # # As you see the proc takes a +source+ parameter. That's a string with the - # absolute path of the asset with any extensions and timestamps in place, - # for example "/images/rails.png?1230601161". + # absolute path of the asset, for example "/assets/rails.png". # # ActionController::Base.asset_host = Proc.new { |source| - # if source.starts_with?('/images') - # "http://images.example.com" + # if source.ends_with?('.css') + # "http://stylesheets.example.com" # else # "http://assets.example.com" # end # } # image_tag("rails.png") - # # => <img alt="Rails" src="http://images.example.com/images/rails.png?1230601161" /> + # # => <img alt="Rails" src="http://assets.example.com/assets/rails.png" /> # stylesheet_link_tag("application") - # # => <link href="http://assets.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" /> + # # => <link href="http://stylesheets.example.com/assets/application.css" media="screen" rel="stylesheet" /> # # Alternatively you may ask for a second parameter +request+. That one is # particularly useful for serving assets from an SSL-protected page. The @@ -252,7 +252,6 @@ module ActionView # The following call would generate such a tag: # # <%= favicon_link_tag 'mb-icon.png', :rel => 'apple-touch-icon', :type => 'image/png' %> - # def favicon_link_tag(source='favicon.ico', options={}) tag('link', { :rel => 'shortcut icon', @@ -261,13 +260,13 @@ module ActionView }.merge(options.symbolize_keys)) end - # Computes the path to an image asset in the public images directory. + # Computes the path to an image asset. # Full paths from the document root will be passed through. # Used internally by +image_tag+ to build the image path: # - # image_path("edit") # => "/images/edit" - # image_path("edit.png") # => "/images/edit.png" - # image_path("icons/edit.png") # => "/images/icons/edit.png" + # image_path("edit") # => "/assets/edit" + # image_path("edit.png") # => "/assets/edit.png" + # image_path("icons/edit.png") # => "/assets/icons/edit.png" # image_path("/icons/edit.png") # => "/icons/edit.png" # image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png" # @@ -275,11 +274,11 @@ module ActionView # The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and # plugin authors are encouraged to do so. def image_path(source) - asset_paths.compute_public_path(source, 'images') + source.present? ? asset_paths.compute_public_path(source, 'images') : "" end alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route - # Computes the full URL to an image asset in the public images directory. + # Computes the full URL to an image asset. # This will use +image_path+ internally, so most of their behaviors will be the same. def image_url(source) URI.join(current_host, path_to_image(source)).to_s @@ -290,7 +289,6 @@ module ActionView # Full paths from the document root will be passed through. # Used internally by +video_tag+ to build the video path. # - # ==== Examples # video_path("hd") # => /videos/hd # video_path("hd.avi") # => /videos/hd.avi # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi @@ -312,7 +310,6 @@ module ActionView # Full paths from the document root will be passed through. # Used internally by +audio_tag+ to build the audio path. # - # ==== Examples # audio_path("horse") # => /audios/horse # audio_path("horse.wav") # => /audios/horse.wav # audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav @@ -323,20 +320,19 @@ module ActionView end alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route - # Computes the full URL to a audio asset in the public audios directory. + # Computes the full URL to an audio asset in the public audios directory. # This will use +audio_path+ internally, so most of their behaviors will be the same. def audio_url(source) URI.join(current_host, path_to_audio(source)).to_s end alias_method :url_to_audio, :audio_url # aliased to avoid conflicts with an audio_url named route - # Computes the path to a font asset in the public fonts directory. + # Computes the path to a font asset. # Full paths from the document root will be passed through. # - # ==== Examples - # font_path("font") # => /fonts/font - # font_path("font.ttf") # => /fonts/font.ttf - # font_path("dir/font.ttf") # => /fonts/dir/font.ttf + # font_path("font") # => /assets/font + # font_path("font.ttf") # => /assets/font.ttf + # font_path("dir/font.ttf") # => /assets/dir/font.ttf # font_path("/dir/font.ttf") # => /dir/font.ttf # font_path("http://www.example.com/dir/font.ttf") # => http://www.example.com/dir/font.ttf def font_path(source) @@ -344,7 +340,7 @@ module ActionView end alias_method :path_to_font, :font_path # aliased to avoid conflicts with an font_path named route - # Computes the full URL to a font asset in the public fonts directory. + # Computes the full URL to a font asset. # This will use +font_path+ internally, so most of their behaviors will be the same. def font_url(source) URI.join(current_host, path_to_font(source)).to_s @@ -352,7 +348,7 @@ module ActionView alias_method :url_to_font, :font_url # aliased to avoid conflicts with an font_url named route # Returns an html image tag for the +source+. The +source+ can be a full - # path or a file that exists in your public images directory. + # path or a file. # # ==== Options # You can add HTML attributes using the +options+. The +options+ supports @@ -363,33 +359,25 @@ module ActionView # * <tt>:size</tt> - Supplied as "{Width}x{Height}", so "30x45" becomes # width="30" and height="45". <tt>:size</tt> will be ignored if the # value is not in the correct format. - # * <tt>:mouseover</tt> - Set an alternate image to be used when the onmouseover - # event is fired, and sets the original image to be replaced onmouseout. - # This can be used to implement an easy image toggle that fires on onmouseover. # - # ==== Examples # image_tag("icon") # => - # <img src="/images/icon" alt="Icon" /> + # <img src="/assets/icon" alt="Icon" /> # image_tag("icon.png") # => - # <img src="/images/icon.png" alt="Icon" /> + # <img src="/assets/icon.png" alt="Icon" /> # image_tag("icon.png", :size => "16x10", :alt => "Edit Entry") # => - # <img src="/images/icon.png" width="16" height="10" alt="Edit Entry" /> + # <img src="/assets/icon.png" width="16" height="10" alt="Edit Entry" /> # image_tag("/icons/icon.gif", :size => "16x16") # => # <img src="/icons/icon.gif" width="16" height="16" alt="Icon" /> # image_tag("/icons/icon.gif", :height => '32', :width => '32') # => # <img alt="Icon" height="32" src="/icons/icon.gif" width="32" /> # image_tag("/icons/icon.gif", :class => "menu_icon") # => # <img alt="Icon" class="menu_icon" src="/icons/icon.gif" /> - # image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # => - # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" /> - # image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # => - # <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" /> def image_tag(source, options={}) options = options.symbolize_keys src = options[:src] = path_to_image(source) - unless src =~ /^cid:/ + unless src =~ /^(?:cid|data):/ || src.blank? options[:alt] = options.fetch(:alt){ image_alt(src) } end @@ -397,11 +385,6 @@ module ActionView options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$} end - if mouseover = options.delete(:mouseover) - options[:onmouseover] = "this.src='#{path_to_image(mouseover)}'" - options[:onmouseout] = "this.src='#{src}'" - end - tag("img", options) end @@ -425,7 +408,6 @@ module ActionView # width="30" and height="45". <tt>:size</tt> will be ignored if the # value is not in the correct format. # - # ==== Examples # video_tag("trailer") # => # <video src="/videos/trailer" /> # video_tag("trailer.ogg") # => @@ -433,7 +415,7 @@ module ActionView # video_tag("trailer.ogg", :controls => true, :autobuffer => true) # => # <video autobuffer="autobuffer" controls="controls" src="/videos/trailer.ogg" /> # video_tag("trailer.m4v", :size => "16x10", :poster => "screenshot.png") # => - # <video src="/videos/trailer.m4v" width="16" height="10" poster="/images/screenshot.png" /> + # <video src="/videos/trailer.m4v" width="16" height="10" poster="/assets/screenshot.png" /> # video_tag("/trailers/hd.avi", :size => "16x16") # => # <video src="/trailers/hd.avi" width="16" height="16" /> # video_tag("/trailers/hd.avi", :height => '32', :width => '32') # => @@ -458,15 +440,14 @@ module ActionView # The +source+ can be full path or file that exists in # your public audios directory. # - # ==== Examples - # audio_tag("sound") # => - # <audio src="/audios/sound" /> - # audio_tag("sound.wav") # => - # <audio src="/audios/sound.wav" /> - # audio_tag("sound.wav", :autoplay => true, :controls => true) # => - # <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" /> - # audio_tag("sound.wav", "sound.mid") # => - # <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio> + # audio_tag("sound") # => + # <audio src="/audios/sound" /> + # audio_tag("sound.wav") # => + # <audio src="/audios/sound.wav" /> + # audio_tag("sound.wav", :autoplay => true, :controls => true) # => + # <audio autoplay="autoplay" controls="controls" src="/audios/sound.wav" /> + # audio_tag("sound.wav", "sound.mid") # => + # <audio><source src="/audios/sound.wav" /><source src="/audios/sound.mid" /></audio> def audio_tag(*sources) multiple_sources_tag('audio', sources) end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb index e3329c0345..4292d29f60 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb @@ -76,7 +76,6 @@ module ActionView # Full paths from the document root will be passed through. # Used internally by javascript_include_tag to build the script path. # - # ==== Examples # javascript_path "xmlhr" # => /javascripts/xmlhr.js # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js @@ -114,7 +113,6 @@ module ActionView # You can modify the HTML attributes of the script tag by passing a hash as the # last argument. # - # ==== Examples # javascript_include_tag "xmlhr" # # => <script src="/javascripts/xmlhr.js?1284139606"></script> # @@ -136,8 +134,6 @@ module ActionView # # <script src="/javascripts/rails.js?1284139606"></script> # # <script src="/javascripts/application.js?1284139606"></script> # - # * = The application.js file is only referenced if it exists - # # You can also include all JavaScripts in the +javascripts+ directory using <tt>:all</tt> as the source: # # javascript_include_tag :all @@ -162,8 +158,6 @@ module ActionView # <tt>config.perform_caching</tt> is set to true (which is the case by default for the Rails # production environment, but not for the development environment). # - # ==== Examples - # # # assuming config.perform_caching is false # javascript_include_tag :all, :cache => true # # => <script src="/javascripts/jquery.js?1284139606"></script> diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb index 4bcb8b9718..57b0627225 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb @@ -54,7 +54,6 @@ module ActionView # Full paths from the document root will be passed through. # Used internally by +stylesheet_link_tag+ to build the stylesheet path. # - # ==== Examples # stylesheet_path "style" # => /stylesheets/style.css # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css # stylesheet_path "/dir/style.css" # => /dir/style.css @@ -79,7 +78,6 @@ module ActionView # to "screen", so you must explicitely set it to "all" for the stylesheet(s) to # apply to all media types. # - # ==== Examples # stylesheet_link_tag "style" # => # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" /> # @@ -117,7 +115,6 @@ module ActionView # is set to true (which is the case by default for the Rails production environment, but not for the development # environment). Examples: # - # ==== Examples # stylesheet_link_tag :all, :cache => true # when config.perform_caching is false => # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" /> # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" /> diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index 850dd5f448..33799d7d71 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -10,7 +10,6 @@ module ActionView # # See ActionController::Caching::Fragments for usage instructions. # - # ==== Examples # If you want to cache a navigation menu, you can do following: # # <% cache do %> diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index d9d6f90211..397738dd98 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -13,7 +13,6 @@ module ActionView # The capture method allows you to extract part of a template into a # variable. You can then use this variable anywhere in your templates or layout. # - # ==== Examples # The capture method can be used in ERB templates... # # <% @greeting = capture do %> diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index f0a593d2c1..659aacf6d7 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -64,7 +64,6 @@ 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 - # def distance_of_time_in_words(from_time, to_time = 0, include_seconds_or_options = {}, options = {}) if include_seconds_or_options.is_a?(Hash) options = include_seconds_or_options @@ -140,7 +139,6 @@ module ActionView # Like <tt>distance_of_time_in_words</tt>, but where <tt>to_time</tt> is fixed to <tt>Time.now</tt>. # - # ==== Examples # time_ago_in_words(3.minutes.from_now) # => 3 minutes # time_ago_in_words(Time.now - 15.hours) # => about 15 hours # time_ago_in_words(Time.now) # => less than a minute @@ -148,7 +146,6 @@ module ActionView # # from_time = Time.now - 3.days - 14.minutes - 25.seconds # time_ago_in_words(from_time) # => 3 days - # def time_ago_in_words(from_time, include_seconds_or_options = {}) distance_of_time_in_words(from_time, Time.now, include_seconds_or_options) end @@ -197,7 +194,6 @@ module ActionView # # NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed. # - # ==== Examples # # Generates a date select that when POSTed is stored in the article variable, in the written_on attribute. # date_select("article", "written_on") # @@ -253,7 +249,6 @@ module ActionView # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # - # ==== Examples # # Creates a time select tag that, when POSTed, will be stored in the article variable in the sunrise attribute. # time_select("article", "sunrise") # @@ -286,7 +281,6 @@ module ActionView # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # - # ==== Examples # # Generates a datetime select that, when POSTed, will be stored in the article variable in the written_on # # attribute. # datetime_select("article", "written_on") @@ -325,7 +319,6 @@ module ActionView # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # - # ==== Examples # my_date_time = Time.now + 4.days # # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today). @@ -362,7 +355,6 @@ module ActionView # select_datetime(my_date_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'}) # select_datetime(my_date_time, :prompt => {:hour => true}) # generic prompt for hours # select_datetime(my_date_time, :prompt => true) # generic prompts for all - # def select_datetime(datetime = Time.current, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_datetime end @@ -374,7 +366,6 @@ module ActionView # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # - # ==== Examples # my_date = Time.now + 6.days # # # Generates a date select that defaults to the date in my_date (six days after today). @@ -403,7 +394,6 @@ module ActionView # select_date(my_date, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'}) # select_date(my_date, :prompt => {:hour => true}) # generic prompt for hours # select_date(my_date, :prompt => true) # generic prompts for all - # def select_date(date = Date.current, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_date end @@ -414,7 +404,6 @@ module ActionView # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # - # ==== Examples # my_time = Time.now + 5.days + 7.hours + 3.minutes + 14.seconds # # # Generates a time select that defaults to the time in my_time. @@ -442,7 +431,6 @@ module ActionView # select_time(my_time, :prompt => {:day => 'Choose day', :month => 'Choose month', :year => 'Choose year'}) # select_time(my_time, :prompt => {:hour => true}) # generic prompt for hours # select_time(my_time, :prompt => true) # generic prompts for all - # def select_time(datetime = Time.current, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_time end @@ -451,7 +439,6 @@ 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. # - # ==== Examples # my_time = Time.now + 16.minutes # # # Generates a select field for seconds that defaults to the seconds for the time in my_time. @@ -467,7 +454,6 @@ module ActionView # # Generates a select field for seconds with a custom prompt. Use <tt>:prompt => true</tt> for a # # generic prompt. # select_second(14, :prompt => 'Choose seconds') - # def select_second(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_second end @@ -477,7 +463,6 @@ 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. # - # ==== Examples # my_time = Time.now + 6.hours # # # Generates a select field for minutes that defaults to the minutes for the time in my_time. @@ -493,7 +478,6 @@ module ActionView # # Generates a select field for minutes with a custom prompt. Use <tt>:prompt => true</tt> for a # # generic prompt. # select_minute(14, :prompt => 'Choose minutes') - # def select_minute(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_minute end @@ -502,7 +486,6 @@ 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, 'hour' by default. # - # ==== Examples # my_time = Time.now + 6.hours # # # Generates a select field for hours that defaults to the hour for the time in my_time. @@ -521,7 +504,6 @@ module ActionView # # # Generate a select field for hours in the AM/PM format # select_hour(my_time, :ampm => true) - # def select_hour(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_hour end @@ -531,7 +513,6 @@ module ActionView # If you want to display days with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true. # Override the field name using the <tt>:field_name</tt> option, 'day' by default. # - # ==== Examples # my_date = Time.now + 2.days # # # Generates a select field for days that defaults to the day for the date in my_date. @@ -550,7 +531,6 @@ module ActionView # # Generates a select field for days with a custom prompt. Use <tt>:prompt => true</tt> for a # # generic prompt. # select_day(5, :prompt => 'Choose day') - # def select_day(date, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_day end @@ -565,7 +545,6 @@ module ActionView # If you want to display months with a leading zero set the <tt>:use_two_digit_numbers</tt> key in +options+ to true. # Override the field name using the <tt>:field_name</tt> option, 'month' by default. # - # ==== Examples # # Generates a select field for months that defaults to the current month that # # will use keys like "January", "March". # select_month(Date.today) @@ -597,7 +576,6 @@ module ActionView # # Generates a select field for months with a custom prompt. Use <tt>:prompt => true</tt> for a # # generic prompt. # select_month(14, :prompt => 'Choose month') - # def select_month(date, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_month end @@ -608,7 +586,6 @@ module ActionView # greater than <tt>:end_year</tt>. The <tt>date</tt> can also be substituted for a year given as a number. # Override the field name using the <tt>:field_name</tt> option, 'year' by default. # - # ==== Examples # # Generates a select field for years that defaults to the current year that # # has ascending year values. # select_year(Date.today, :start_year => 1992, :end_year => 2007) @@ -628,14 +605,12 @@ module ActionView # # Generates a select field for years with a custom prompt. Use <tt>:prompt => true</tt> for a # # generic prompt. # select_year(14, :prompt => 'Choose year') - # def select_year(date, options = {}, html_options = {}) DateTimeSelector.new(date, options, html_options).select_year end # Returns an html time tag for the given date or time. # - # ==== Examples # time_tag Date.today # => # <time datetime="2010-11-04">November 04, 2010</time> # time_tag Time.now # => @@ -649,7 +624,6 @@ module ActionView # <span>Right now</span> # <% end %> # # => <time datetime="2010-11-04T17:55:45+01:00"><span>Right now</span></time> - # def time_tag(date_or_time, *args, &block) options = args.extract_options! format = options.delete(:format) || :long diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb index c0cc7d347c..878a8734a4 100644 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ b/actionpack/lib/action_view/helpers/debug_helper.rb @@ -8,8 +8,6 @@ module ActionView # If the object cannot be converted to YAML using +to_yaml+, +inspect+ will be called instead. # Useful for inspecting an object at the time of rendering. # - # ==== Example - # # @user = User.new({ :username => 'testing', :password => 'xyz', :age => 42}) %> # debug(@user) # # => @@ -25,7 +23,6 @@ module ActionView # # new_record: true # </pre> - def debug(object) begin Marshal::dump(object) diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 67f2abe509..ad8885b708 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -5,6 +5,7 @@ require 'action_view/helpers/form_tag_helper' require 'action_view/helpers/active_model_helper' require 'action_view/helpers/tags' require 'active_support/core_ext/class/attribute' +require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/hash/slice' require 'active_support/core_ext/object/blank' require 'active_support/core_ext/string/output_safety' @@ -899,7 +900,6 @@ module ActionView # In that case it is preferable to either use +check_box_tag+ or to use # hashes instead of arrays. # - # ==== Examples # # Let's say that @post.validated? is 1: # check_box("post", "validated") # # => <input name="post[validated]" type="hidden" value="0" /> @@ -925,7 +925,6 @@ module ActionView # To force the radio button to be checked pass <tt>:checked => true</tt> in the # +options+ hash. You may pass HTML options there as well. # - # ==== Examples # # Let's say that @post.category returns "rails": # radio_button("post", "category", "rails") # radio_button("post", "category", "java") @@ -940,12 +939,19 @@ module ActionView Tags::RadioButton.new(object_name, method, self, tag_value, options).render end + # Returns a text_field of type "color". + # + # color_field("car", "color") + # # => <input id="car_color" name="car[color]" type="color" value="#000000" /> + # + def color_field(object_name, method, options = {}) + Tags::ColorField.new(object_name, method, self, options).render + end + # Returns an input of type "search" for accessing a specified attribute (identified by +method+) on an object # assigned to the template (identified by +object_name+). Inputs of type "search" may be styled differently by # some browsers. # - # ==== Examples - # # search_field(:user, :name) # # => <input id="user_name" name="user[name]" type="search" /> # search_field(:user, :name, :autosave => false) @@ -961,7 +967,6 @@ module ActionView # # => <input autosave="false" id="user_name" incremental="true" name="user[name]" onsearch="true" type="search" /> # search_field(:user, :name, :autosave => true, :onsearch => true) # # => <input autosave="com.example.www" id="user_name" incremental="true" name="user[name]" onsearch="true" results="10" type="search" /> - # def search_field(object_name, method, options = {}) Tags::SearchField.new(object_name, method, self, options).render end @@ -994,6 +999,91 @@ module ActionView Tags::DateField.new(object_name, method, self, options).render end + # Returns a text_field of type "time". + # + # The default value is generated by trying to call +strftime+ with "%T.%L" + # on the objects's value. It is still possible to override that + # by passing the "value" option. + # + # === Options + # * Accepts same options as time_field_tag + # + # === Example + # time_field("task", "started_at") + # # => <input id="task_started_at" name="task[started_at]" type="time" /> + # + def time_field(object_name, method, options = {}) + Tags::TimeField.new(object_name, method, self, options).render + end + + # Returns a text_field of type "datetime". + # + # datetime_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="datetime" /> + # + # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T.%L%z" + # on the object's value, which makes it behave as expected for instances + # of DateTime and ActiveSupport::TimeWithZone. + # + # @user.born_on = Date.new(1984, 1, 12) + # datetime_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="datetime" value="1984-01-12T00:00:00.000+0000" /> + # + def datetime_field(object_name, method, options = {}) + Tags::DatetimeField.new(object_name, method, self, options).render + end + + # Returns a text_field of type "datetime-local". + # + # datetime_local_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" /> + # + # The default value is generated by trying to call +strftime+ with "%Y-%m-%dT%T" + # on the object's value, which makes it behave as expected for instances + # of DateTime and ActiveSupport::TimeWithZone. + # + # @user.born_on = Date.new(1984, 1, 12) + # datetime_local_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="datetime-local" value="1984-01-12T00:00:00" /> + # + def datetime_local_field(object_name, method, options = {}) + Tags::DatetimeLocalField.new(object_name, method, self, options).render + end + + # Returns a text_field of type "month". + # + # month_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="month" /> + # + # The default value is generated by trying to call +strftime+ with "%Y-%m" + # on the object's value, which makes it behave as expected for instances + # of DateTime and ActiveSupport::TimeWithZone. + # + # @user.born_on = Date.new(1984, 1, 27) + # month_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-01" /> + # + def month_field(object_name, method, options = {}) + Tags::MonthField.new(object_name, method, self, options).render + end + + # Returns a text_field of type "week". + # + # week_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="week" /> + # + # The default value is generated by trying to call +strftime+ with "%Y-W%W" + # on the object's value, which makes it behave as expected for instances + # of DateTime and ActiveSupport::TimeWithZone. + # + # @user.born_on = Date.new(1984, 5, 12) + # week_field("user", "born_on") + # # => <input id="user_born_on" name="user[born_on]" type="date" value="1984-W19" /> + # + def week_field(object_name, method, options = {}) + Tags::WeekField.new(object_name, method, self, options).render + end + # Returns a text_field of type "url". # # url_field("user", "homepage") diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index cafcd93f58..eef426703d 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -264,7 +264,6 @@ module ActionView # Finally, this method supports a <tt>:default</tt> option, which selects # a default ActiveSupport::TimeZone if the object's time zone is +nil+. # - # Examples: # time_zone_select( "user", "time_zone", nil, :include_blank => true) # # time_zone_select( "user", "time_zone", nil, :default => "Pacific Time (US & Canada)" ) @@ -461,8 +460,11 @@ module ActionView # * +selected_key+ - A value equal to the +value+ attribute for one of the <tt><option></tt> tags, # which will have the +selected+ attribute set. Note: It is possible for this value to match multiple options # as you might have the same option in multiple groups. Each will then get <tt>selected="selected"</tt>. - # * +prompt+ - set to true or a prompt string. When the select element doesn't have a value yet, this + # + # Options: + # * <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. + # * <tt>:divider</tt> - the divider for the options groups. # # Sample usage (Array): # grouped_options = [ @@ -475,8 +477,8 @@ module ActionView # # Sample usage (Hash): # grouped_options = { - # 'North America' => [['United States','US'], 'Canada'], - # 'Europe' => ['Denmark','Germany','France'] + # 'North America' => [['United States','US'], 'Canada'], + # 'Europe' => ['Denmark','Germany','France'] # } # grouped_options_for_select(grouped_options) # @@ -491,15 +493,50 @@ module ActionView # <option value="Canada">Canada</option> # </optgroup> # + # Sample usage (divider): + # grouped_options = [ + # [['United States','US'], 'Canada'], + # ['Denmark','Germany','France'] + # ] + # grouped_options_for_select(grouped_options, nil, divider: '---------') + # + # Possible output: + # <optgroup label="---------"> + # <option value="Denmark">Denmark</option> + # <option value="Germany">Germany</option> + # <option value="France">France</option> + # </optgroup> + # <optgroup label="---------"> + # <option value="US">United States</option> + # <option value="Canada">Canada</option> + # </optgroup> + # # <b>Note:</b> Only the <tt><optgroup></tt> and <tt><option></tt> tags are returned, so you still have to # wrap the output in an appropriate <tt><select></tt> tag. - def grouped_options_for_select(grouped_options, selected_key = nil, prompt = nil) + def grouped_options_for_select(grouped_options, selected_key = nil, options = {}) + if options.is_a?(Hash) + prompt = options[:prompt] + divider = options[:divider] + else + prompt = options + options = {} + ActiveSupport::Deprecation.warn "Passing the prompt to grouped_options_for_select as an argument is deprecated. Please use an options hash like `{ prompt: #{prompt.inspect} }`." + end + body = "".html_safe - body.safe_concat content_tag(:option, prompt, :value => "") if prompt + + if prompt + body.safe_concat content_tag(:option, prompt_text(prompt), :value => "") + end grouped_options = grouped_options.sort if grouped_options.is_a?(Hash) - grouped_options.each do |label, container| + grouped_options.each do |container| + if divider + label = divider + else + label, container = container + end body.safe_concat content_tag(:optgroup, options_for_select(container, selected_key), :label => label) end @@ -715,6 +752,10 @@ module ActionView def value_for_collection(item, value) value.respond_to?(:call) ? value.call(item) : item.send(value) end + + def prompt_text(prompt) + prompt = prompt.kind_of?(String) ? prompt : I18n.translate('helpers.select.prompt', :default => 'Please select') + end end class FormBuilder diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 0d824bd574..1a0019a48c 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -386,9 +386,6 @@ module ActionView # drivers will provide a prompt with the question specified. If the user accepts, # the form is processed normally, otherwise no action is taken. # * <tt>:disabled</tt> - If true, the user will not be able to use this input. - # * <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. # * Any other key creates standard HTML options for the tag. # # ==== Examples @@ -401,14 +398,14 @@ module ActionView # submit_tag "Save edits", :disabled => true # # => <input disabled="disabled" name="commit" type="submit" value="Save edits" /> # - # submit_tag "Complete sale", :disable_with => "Please wait..." + # submit_tag "Complete sale", :data => { :disable_with => "Please wait..." } # # => <input name="commit" data-disable-with="Please wait..." type="submit" value="Complete sale" /> # # submit_tag nil, :class => "form_submit" # # => <input class="form_submit" name="commit" type="submit" /> # - # submit_tag "Edit", :disable_with => "Editing...", :class => "edit_button" - # # => <input class="edit_button" data-disable-with="Editing..." name="commit" type="submit" value="Edit" /> + # submit_tag "Edit", :class => "edit_button" + # # => <input class="edit_button" name="commit" type="submit" value="Edit" /> # # submit_tag "Save", :confirm => "Are you sure?" # # => <input name='commit' type='submit' value='Save' data-confirm="Are you sure?" /> @@ -416,10 +413,6 @@ module ActionView def submit_tag(value = "Save changes", options = {}) options = options.stringify_keys - if disable_with = options.delete("disable_with") - options["data-disable-with"] = disable_with - end - if confirm = options.delete("confirm") options["data-confirm"] = confirm end @@ -441,10 +434,6 @@ module ActionView # processed normally, otherwise no action is taken. # * <tt>:disabled</tt> - If true, the user will not be able to # use this input. - # * <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. # * Any other key creates standard HTML options for the tag. # # ==== Examples @@ -458,18 +447,11 @@ module ActionView # # <strong>Ask me!</strong> # # </button> # - # button_tag "Checkout", :disable_with => "Please wait..." - # # => <button data-disable-with="Please wait..." name="button" type="submit">Checkout</button> - # def button_tag(content_or_options = nil, options = nil, &block) options = content_or_options if block_given? && content_or_options.is_a?(Hash) options ||= {} options = options.stringify_keys - if disable_with = options.delete("disable_with") - options["data-disable-with"] = disable_with - end - if confirm = options.delete("confirm") options["data-confirm"] = confirm end @@ -502,6 +484,9 @@ module ActionView # # image_submit_tag("agree.png", :disabled => true, :class => "agree_disagree_button") # # => <input class="agree_disagree_button" disabled="disabled" src="/images/agree.png" type="image" /> + # + # image_submit_tag("save.png", :confirm => "Are you sure?") + # # => <input src="/images/save.png" data-confirm="Are you sure?" type="image" /> def image_submit_tag(source, options = {}) options = options.stringify_keys @@ -539,6 +524,14 @@ module ActionView output.safe_concat("</fieldset>") end + # Creates a text field of type "color". + # + # ==== Options + # * Accepts the same options as text_field_tag. + def color_field_tag(name, value = nil, options = {}) + text_field_tag(name, value, options.stringify_keys.update("type" => "color")) + end + # Creates a text field of type "search". # # ==== Options @@ -564,6 +557,61 @@ module ActionView text_field_tag(name, value, options.stringify_keys.update("type" => "date")) end + # Creates a text field of type "time". + # + # === Options + # * <tt>:min</tt> - The minimum acceptable value. + # * <tt>:max</tt> - The maximum acceptable value. + # * <tt>:step</tt> - The acceptable value granularity. + # * Otherwise accepts the same options as text_field_tag. + def time_field_tag(name, value = nil, options = {}) + text_field_tag(name, value, options.stringify_keys.update("type" => "time")) + end + + # Creates a text field of type "datetime". + # + # === Options + # * <tt>:min</tt> - The minimum acceptable value. + # * <tt>:max</tt> - The maximum acceptable value. + # * <tt>:step</tt> - The acceptable value granularity. + # * Otherwise accepts the same options as text_field_tag. + def datetime_field_tag(name, value = nil, options = {}) + text_field_tag(name, value, options.stringify_keys.update("type" => "datetime")) + end + + # Creates a text field of type "datetime-local". + # + # === Options + # * <tt>:min</tt> - The minimum acceptable value. + # * <tt>:max</tt> - The maximum acceptable value. + # * <tt>:step</tt> - The acceptable value granularity. + # * Otherwise accepts the same options as text_field_tag. + def datetime_local_field_tag(name, value = nil, options = {}) + text_field_tag(name, value, options.stringify_keys.update("type" => "datetime-local")) + end + + # Creates a text field of type "month". + # + # === Options + # * <tt>:min</tt> - The minimum acceptable value. + # * <tt>:max</tt> - The maximum acceptable value. + # * <tt>:step</tt> - The acceptable value granularity. + # * Otherwise accepts the same options as text_field_tag. + def month_field_tag(name, value = nil, options = {}) + text_field_tag(name, value, options.stringify_keys.update("type" => "month")) + end + + # Creates a text field of type "week". + # + # === Options + # * <tt>:min</tt> - The minimum acceptable value. + # * <tt>:max</tt> - The maximum acceptable value. + # * <tt>:step</tt> - The acceptable value granularity. + # * Otherwise accepts the same options as text_field_tag. + def week_field_tag(name, value = nil, options = {}) + text_field_tag(name, value, options.stringify_keys.update("type" => "week")) + end + # Creates a text field of type "url". # # ==== Options diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 2011351bd2..dfc26acfad 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -28,17 +28,20 @@ module ActionView end end - # Formats a +number+ into a US phone number (e.g., (555) 123-9876). You can customize the format - # in the +options+ hash. + # Formats a +number+ into a US phone number (e.g., (555) + # 123-9876). You can customize the format in the +options+ hash. # # ==== Options # - # * <tt>:area_code</tt> - Adds parentheses around the area code. - # * <tt>:delimiter</tt> - Specifies the delimiter to use (defaults to "-"). - # * <tt>:extension</tt> - Specifies an extension to add to the end of the - # generated number. - # * <tt>:country_code</tt> - Sets the country code for the phone number. - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid. + # * <tt>:area_code</tt> - Adds parentheses around the area code. + # * <tt>:delimiter</tt> - Specifies the delimiter to use + # (defaults to "-"). + # * <tt>:extension</tt> - Specifies an extension to add to the + # end of the generated number. + # * <tt>:country_code</tt> - Sets the country code for the phone + # number. + # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when + # the argument is invalid. # # ==== Examples # @@ -81,24 +84,31 @@ module ActionView ERB::Util.html_escape(str) end - # Formats a +number+ into a currency string (e.g., $13.65). You can customize the format - # in the +options+ hash. + # Formats a +number+ into a currency string (e.g., $13.65). You + # can customize the format in the +options+ hash. # # ==== Options # - # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale). - # * <tt>:precision</tt> - Sets the level of precision (defaults to 2). - # * <tt>:unit</tt> - Sets the denomination of the currency (defaults to "$"). - # * <tt>:separator</tt> - Sets the separator between the units (defaults to "."). - # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ","). - # * <tt>:format</tt> - Sets the format for non-negative numbers (defaults to "%u%n"). - # Fields are <tt>%u</tt> for the currency, and <tt>%n</tt> - # for the number. - # * <tt>:negative_format</tt> - Sets the format for negative numbers (defaults to prepending - # an hyphen to the formatted number given by <tt>:format</tt>). - # Accepts the same fields than <tt>:format</tt>, except - # <tt>%n</tt> is here the absolute value of the number. - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid. + # * <tt>:locale</tt> - Sets the locale to be used for formatting + # (defaults to current locale). + # * <tt>:precision</tt> - Sets the level of precision (defaults + # to 2). + # * <tt>:unit</tt> - Sets the denomination of the currency + # (defaults to "$"). + # * <tt>:separator</tt> - Sets the separator between the units + # (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults + # to ","). + # * <tt>:format</tt> - Sets the format for non-negative numbers + # (defaults to "%u%n"). Fields are <tt>%u</tt> for the + # currency, and <tt>%n</tt> for the number. + # * <tt>:negative_format</tt> - Sets the format for negative + # numbers (defaults to prepending an hyphen to the formatted + # number given by <tt>:format</tt>). Accepts the same fields + # than <tt>:format</tt>, except <tt>%n</tt> is here the + # absolute value of the number. + # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when + # the argument is invalid. # # ==== Examples # @@ -148,23 +158,29 @@ module ActionView end end - # Formats a +number+ as a percentage string (e.g., 65%). You can customize the format in the +options+ hash. + # Formats a +number+ as a percentage string (e.g., 65%). You can + # customize the format in the +options+ hash. # # ==== Options # - # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current - # locale). - # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, - # the # of fractional digits (defaults to +false+). - # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults - # to "."). - # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ""). - # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator - # (defaults to +false+). - # * <tt>:format</tt> - Specifies the format of the percentage string - # The number field is <tt>%n</tt> (defaults to "%n%"). - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid. + # * <tt>:locale</tt> - Sets the locale to be used for formatting + # (defaults to current locale). + # * <tt>:precision</tt> - Sets the precision of the number + # (defaults to 3). + # * <tt>:significant</tt> - If +true+, precision will be the # + # of significant_digits. If +false+, the # of fractional + # digits (defaults to +false+). + # * <tt>:separator</tt> - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults + # to ""). + # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). + # * <tt>:format</tt> - Specifies the format of the percentage + # string The number field is <tt>%n</tt> (defaults to "%n%"). + # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when + # the argument is invalid. # # ==== Examples # @@ -200,15 +216,20 @@ module ActionView end end - # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can - # customize the format in the +options+ hash. + # Formats a +number+ with grouped thousands using +delimiter+ + # (e.g., 12,324). You can customize the format in the +options+ + # hash. # # ==== Options # - # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale). - # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ","). - # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to "."). - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid. + # * <tt>:locale</tt> - Sets the locale to be used for formatting + # (defaults to current locale). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults + # to ","). + # * <tt>:separator</tt> - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when + # the argument is invalid. # # ==== Examples # @@ -236,23 +257,32 @@ module ActionView safe_join(parts, options[:separator]) end - # Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision - # of 2 if +:significant+ is +false+, and 5 if +:significant+ is +true+). + # Formats a +number+ with the specified level of + # <tt>:precision</tt> (e.g., 112.32 has a precision of 2 if + # +:significant+ is +false+, and 5 if +:significant+ is +true+). # You can customize the format in the +options+ hash. # # ==== Options - # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale). - # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, - # the # of fractional digits (defaults to +false+). - # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults - # to "."). - # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ""). - # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator - # (defaults to +false+). - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid. + # + # * <tt>:locale</tt> - Sets the locale to be used for formatting + # (defaults to current locale). + # * <tt>:precision</tt> - Sets the precision of the number + # (defaults to 3). + # * <tt>:significant</tt> - If +true+, precision will be the # + # of significant_digits. If +false+, the # of fractional + # digits (defaults to +false+). + # * <tt>:separator</tt> - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults + # to ""). + # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +false+). + # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when + # the argument is invalid. # # ==== Examples + # # number_with_precision(111.2345) # => 111.235 # number_with_precision(111.2345, :precision => 2) # => 111.23 # number_with_precision(13, :precision => 5) # => 13.00000 @@ -305,23 +335,37 @@ module ActionView STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze - # Formats the bytes in +number+ into a more understandable representation - # (e.g., giving it 1500 yields 1.5 KB). This method is useful for - # reporting file sizes to users. You can customize the - # format in the +options+ hash. + # Formats the bytes in +number+ into a more understandable + # representation (e.g., giving it 1500 yields 1.5 KB). This + # method is useful for reporting file sizes to users. You can + # customize the format in the +options+ hash. # - # See <tt>number_to_human</tt> if you want to pretty-print a generic number. + # See <tt>number_to_human</tt> if you want to pretty-print a + # generic number. # # ==== Options - # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale). - # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +true+) - # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to "."). - # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ""). - # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+) - # * <tt>:prefix</tt> - If +:si+ formats the number using the SI prefix (defaults to :binary) - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid. + # + # * <tt>:locale</tt> - Sets the locale to be used for formatting + # (defaults to current locale). + # * <tt>:precision</tt> - Sets the precision of the number + # (defaults to 3). + # * <tt>:significant</tt> - If +true+, precision will be the # + # of significant_digits. If +false+, the # of fractional + # digits (defaults to +true+) + # * <tt>:separator</tt> - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults + # to ""). + # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +true+) + # * <tt>:prefix</tt> - If +:si+ formats the number using the SI + # prefix (defaults to :binary) + # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when + # the argument is invalid. + # # ==== Examples + # # number_to_human_size(123) # => 123 Bytes # number_to_human_size(1234) # => 1.21 KB # number_to_human_size(12345) # => 12.1 KB @@ -332,8 +376,10 @@ module ActionView # number_to_human_size(483989, :precision => 2) # => 470 KB # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,2 MB # - # Non-significant zeros after the fractional separator are stripped out by default (set - # <tt>:strip_insignificant_zeros</tt> to +false+ to change that): + # Non-significant zeros after the fractional separator are + # stripped out by default (set + # <tt>:strip_insignificant_zeros</tt> to +false+ to change + # that): # number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB" # number_to_human_size(524288000, :precision => 5) # => "500 MB" def number_to_human_size(number, options = {}) @@ -371,33 +417,55 @@ module ActionView DECIMAL_UNITS = {0 => :unit, 1 => :ten, 2 => :hundred, 3 => :thousand, 6 => :million, 9 => :billion, 12 => :trillion, 15 => :quadrillion, -1 => :deci, -2 => :centi, -3 => :mili, -6 => :micro, -9 => :nano, -12 => :pico, -15 => :femto}.freeze - # Pretty prints (formats and approximates) a number in a way it is more readable by humans - # (eg.: 1200000000 becomes "1.2 Billion"). This is useful for numbers that - # can get very large (and too hard to read). + # Pretty prints (formats and approximates) a number in a way it + # is more readable by humans (eg.: 1200000000 becomes "1.2 + # Billion"). This is useful for numbers that can get very large + # (and too hard to read). # - # See <tt>number_to_human_size</tt> if you want to print a file size. + # 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 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 (centi, deci, mili, etc). + # You can also define you 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 + # (centi, deci, mili, etc). # # ==== Options - # * <tt>:locale</tt> - Sets the locale to be used for formatting (defaults to current locale). - # * <tt>:precision</tt> - Sets the precision of the number (defaults to 3). - # * <tt>:significant</tt> - If +true+, precision will be the # of significant_digits. If +false+, the # of fractional digits (defaults to +true+) - # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to "."). - # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ""). - # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes insignificant zeros after the decimal separator (defaults to +true+) - # * <tt>:units</tt> - A Hash of unit quantifier names. Or a string containing an i18n scope where to find this hash. It might have the following keys: - # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, <tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, <tt>:billion</tt>, <tt>:trillion</tt>, <tt>:quadrillion</tt> - # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, <tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, <tt>:pico</tt>, <tt>:femto</tt> - # * <tt>:format</tt> - Sets the format of the output string (defaults to "%n %u"). The field types are: - # %u The quantifier (ex.: 'thousand') - # %n The number - # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when the argument is invalid. # + # * <tt>:locale</tt> - Sets the locale to be used for formatting + # (defaults to current locale). + # * <tt>:precision</tt> - Sets the precision of the number + # (defaults to 3). + # * <tt>:significant</tt> - If +true+, precision will be the # + # of significant_digits. If +false+, the # of fractional + # digits (defaults to +true+) + # * <tt>:separator</tt> - Sets the separator between the + # fractional and integer digits (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults + # to ""). + # * <tt>:strip_insignificant_zeros</tt> - If +true+ removes + # insignificant zeros after the decimal separator (defaults to + # +true+) + # * <tt>:units</tt> - A Hash of unit quantifier names. Or a + # string containing an i18n scope where to find this hash. It + # might have the following keys: + # * *integers*: <tt>:unit</tt>, <tt>:ten</tt>, + # *<tt>:hundred</tt>, <tt>:thousand</tt>, <tt>:million</tt>, + # *<tt>:billion</tt>, <tt>:trillion</tt>, + # *<tt>:quadrillion</tt> + # * *fractionals*: <tt>:deci</tt>, <tt>:centi</tt>, + # *<tt>:mili</tt>, <tt>:micro</tt>, <tt>:nano</tt>, + # *<tt>:pico</tt>, <tt>:femto</tt> + # * <tt>:format</tt> - Sets the format of the output string + # (defaults to "%n %u"). The field types are: + # * %u - The quantifier (ex.: 'thousand') + # * %n - The number + # * <tt>:raise</tt> - If true, raises +InvalidNumberError+ when + # the argument is invalid. # # ==== Examples + # # number_to_human(123) # => "123" # number_to_human(1234) # => "1.23 Thousand" # number_to_human(12345) # => "12.3 Thousand" @@ -414,8 +482,9 @@ module ActionView # :separator => ',', # :significant => false) # => "1,2 Million" # - # Unsignificant zeros after the decimal separator are stripped out by default (set - # <tt>:strip_insignificant_zeros</tt> to +false+ to change that): + # Non-significant zeros after the decimal separator are stripped + # out by default (set <tt>:strip_insignificant_zeros</tt> to + # +false+ to change that): # number_to_human(12345012345, :significant_digits => 6) # => "12.345 Billion" # number_to_human(500000000, :precision => 5) # => "500 Million" # diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index 7768c8c151..a727b910e5 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -69,8 +69,6 @@ module ActionView # html-scanner tokenizer and so its HTML parsing ability is limited by # that of html-scanner. # - # ==== Examples - # # strip_tags("Strip <i>these</i> tags!") # # => Strip these tags! # @@ -85,7 +83,6 @@ module ActionView # Strips all link tags from +text+ leaving just the link text. # - # ==== Examples # strip_links('<a href="http://www.rubyonrails.org">Ruby on Rails</a>') # # => Ruby on Rails # diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index f7afa48256..9572f1c192 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -41,7 +41,7 @@ module ActionView # thus accessed as <tt>dataset.userId</tt>. # # Values are encoded to JSON, with the exception of strings and symbols. - # This may come in handy when using jQuery's HTML5-aware <tt>.data()<tt> + # This may come in handy when using jQuery's HTML5-aware <tt>.data()</tt> # from 1.4.3. # # ==== Examples @@ -103,7 +103,6 @@ module ActionView # otherwise be recognized as markup. CDATA sections begin with the string # <tt><![CDATA[</tt> and end with (and may not contain) the string <tt>]]></tt>. # - # ==== Examples # cdata_section("<hello world>") # # => <![CDATA[<hello world>]]> # @@ -119,7 +118,6 @@ module ActionView # Returns an escaped version of +html+ without affecting existing escaped entities. # - # ==== Examples # escape_once("1 < 2 & 3") # # => "1 < 2 & 3" # @@ -156,8 +154,9 @@ module ActionView def data_tag_option(key, value, escape) key = "data-#{key.to_s.dasherize}" - value = value.to_json if !value.is_a?(String) && !value.is_a?(Symbol) - + unless value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(BigDecimal) + value = value.to_json + end tag_option(key, value, escape) end diff --git a/actionpack/lib/action_view/helpers/tags.rb b/actionpack/lib/action_view/helpers/tags.rb index 3cf762877f..a05e16979a 100644 --- a/actionpack/lib/action_view/helpers/tags.rb +++ b/actionpack/lib/action_view/helpers/tags.rb @@ -8,14 +8,18 @@ module ActionView autoload :CollectionCheckBoxes autoload :CollectionRadioButtons autoload :CollectionSelect + autoload :ColorField autoload :DateField autoload :DateSelect + autoload :DatetimeField + autoload :DatetimeLocalField autoload :DatetimeSelect autoload :EmailField autoload :FileField autoload :GroupedCollectionSelect autoload :HiddenField autoload :Label + autoload :MonthField autoload :NumberField autoload :PasswordField autoload :RadioButton @@ -25,9 +29,11 @@ module ActionView autoload :TelField autoload :TextArea autoload :TextField + autoload :TimeField autoload :TimeSelect autoload :TimeZoneSelect autoload :UrlField + autoload :WeekField end end end diff --git a/actionpack/lib/action_view/helpers/tags/base.rb b/actionpack/lib/action_view/helpers/tags/base.rb index e4f431a6d7..e077cd5b3c 100644 --- a/actionpack/lib/action_view/helpers/tags/base.rb +++ b/actionpack/lib/action_view/helpers/tags/base.rb @@ -121,6 +121,7 @@ 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) select = content_tag("select", add_options(option_tags, options, value(object)), html_options) if html_options["multiple"] && options.fetch(:include_hidden, true) @@ -130,13 +131,16 @@ module ActionView end end + def select_not_required?(html_options) + !html_options["required"] || html_options["multiple"] || html_options["size"].to_i > 1 + end + def add_options(option_tags, options, value = nil) if options[:include_blank] option_tags = content_tag('option', options[:include_blank].kind_of?(String) ? options[:include_blank] : nil, :value => '') + "\n" + option_tags end if value.blank? && options[:prompt] - prompt = options[:prompt].kind_of?(String) ? options[:prompt] : I18n.translate('helpers.select.prompt', :default => 'Please select') - option_tags = content_tag('option', prompt, :value => '') + "\n" + option_tags + option_tags = content_tag('option', prompt_text(options[:prompt]), :value => '') + "\n" + option_tags end option_tags end diff --git a/actionpack/lib/action_view/helpers/tags/color_field.rb b/actionpack/lib/action_view/helpers/tags/color_field.rb new file mode 100644 index 0000000000..6f08f8483a --- /dev/null +++ b/actionpack/lib/action_view/helpers/tags/color_field.rb @@ -0,0 +1,25 @@ +module ActionView + module Helpers + module Tags + class ColorField < TextField #:nodoc: + def render + options = @options.stringify_keys + options["value"] = @options.fetch("value") { validate_color_string(value(object)) } + @options = options + super + end + + private + + def validate_color_string(string) + regex = /#[0-9a-fA-F]{6}/ + if regex.match(string) + string.downcase + else + "#000000" + end + end + end + end + end +end diff --git a/actionpack/lib/action_view/helpers/tags/date_field.rb b/actionpack/lib/action_view/helpers/tags/date_field.rb index bb968e9f39..64c29dea3d 100644 --- a/actionpack/lib/action_view/helpers/tags/date_field.rb +++ b/actionpack/lib/action_view/helpers/tags/date_field.rb @@ -1,14 +1,12 @@ module ActionView module Helpers module Tags - class DateField < TextField #:nodoc: - def render - options = @options.stringify_keys - options["value"] = @options.fetch("value") { value(object).try(:to_date) } - options["size"] = nil - @options = options - super - end + class DateField < DatetimeField #:nodoc: + private + + def format_date(value) + value.try(:strftime, "%Y-%m-%d") + end end end end diff --git a/actionpack/lib/action_view/helpers/tags/datetime_field.rb b/actionpack/lib/action_view/helpers/tags/datetime_field.rb new file mode 100644 index 0000000000..e407146e96 --- /dev/null +++ b/actionpack/lib/action_view/helpers/tags/datetime_field.rb @@ -0,0 +1,22 @@ +module ActionView + module Helpers + module Tags + class DatetimeField < TextField #:nodoc: + def render + options = @options.stringify_keys + options["value"] = @options.fetch("value") { format_date(value(object)) } + options["min"] = format_date(options["min"]) + options["max"] = format_date(options["max"]) + @options = options + super + end + + private + + def format_date(value) + value.try(:strftime, "%Y-%m-%dT%T.%L%z") + end + end + end + end +end diff --git a/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb b/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb new file mode 100644 index 0000000000..6668d6d718 --- /dev/null +++ b/actionpack/lib/action_view/helpers/tags/datetime_local_field.rb @@ -0,0 +1,19 @@ +module ActionView + module Helpers + module Tags + class DatetimeLocalField < DatetimeField #:nodoc: + class << self + def field_type + @field_type ||= "datetime-local" + end + end + + private + + def format_date(value) + value.try(:strftime, "%Y-%m-%dT%T") + end + end + end + end +end diff --git a/actionpack/lib/action_view/helpers/tags/file_field.rb b/actionpack/lib/action_view/helpers/tags/file_field.rb index 56442e1c14..59f2ff71b4 100644 --- a/actionpack/lib/action_view/helpers/tags/file_field.rb +++ b/actionpack/lib/action_view/helpers/tags/file_field.rb @@ -2,10 +2,6 @@ module ActionView module Helpers module Tags class FileField < TextField #:nodoc: - def render - @options.update(:size => nil) - super - end end end end diff --git a/actionpack/lib/action_view/helpers/tags/hidden_field.rb b/actionpack/lib/action_view/helpers/tags/hidden_field.rb index ea86596e0b..a8d13dc1b1 100644 --- a/actionpack/lib/action_view/helpers/tags/hidden_field.rb +++ b/actionpack/lib/action_view/helpers/tags/hidden_field.rb @@ -2,10 +2,6 @@ module ActionView module Helpers module Tags class HiddenField < TextField #:nodoc: - def render - @options.update(:size => nil) - super - end end end end diff --git a/actionpack/lib/action_view/helpers/tags/month_field.rb b/actionpack/lib/action_view/helpers/tags/month_field.rb new file mode 100644 index 0000000000..3d3c32d847 --- /dev/null +++ b/actionpack/lib/action_view/helpers/tags/month_field.rb @@ -0,0 +1,13 @@ +module ActionView + module Helpers + module Tags + class MonthField < DatetimeField #:nodoc: + private + + def format_date(value) + value.try(:strftime, "%Y-%m") + end + end + end + end +end diff --git a/actionpack/lib/action_view/helpers/tags/number_field.rb b/actionpack/lib/action_view/helpers/tags/number_field.rb index e89fdbec46..9cd04434f0 100644 --- a/actionpack/lib/action_view/helpers/tags/number_field.rb +++ b/actionpack/lib/action_view/helpers/tags/number_field.rb @@ -4,7 +4,6 @@ module ActionView class NumberField < TextField #:nodoc: def render options = @options.stringify_keys - options['size'] ||= nil if range = options.delete("in") || options.delete("within") options.update("min" => range.min, "max" => range.max) diff --git a/actionpack/lib/action_view/helpers/tags/time_field.rb b/actionpack/lib/action_view/helpers/tags/time_field.rb new file mode 100644 index 0000000000..a3941860c9 --- /dev/null +++ b/actionpack/lib/action_view/helpers/tags/time_field.rb @@ -0,0 +1,13 @@ +module ActionView + module Helpers + module Tags + class TimeField < DatetimeField #:nodoc: + private + + def format_date(value) + value.try(:strftime, "%T.%L") + end + end + end + end +end diff --git a/actionpack/lib/action_view/helpers/tags/week_field.rb b/actionpack/lib/action_view/helpers/tags/week_field.rb new file mode 100644 index 0000000000..1e13939a0a --- /dev/null +++ b/actionpack/lib/action_view/helpers/tags/week_field.rb @@ -0,0 +1,13 @@ +module ActionView + module Helpers + module Tags + class WeekField < DatetimeField #:nodoc: + private + + def format_date(value) + value.try(:strftime, "%Y-W%W") + end + end + end + end +end diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index fffc37ce9e..8cd7cf0052 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -37,7 +37,6 @@ module ActionView # do not operate as expected in an eRuby code block. If you absolutely must # output text within a non-output code block (i.e., <% %>), you can use the concat method. # - # ==== Examples # <% # concat "hello" # # is the equivalent of <%= "hello" %> @@ -67,8 +66,6 @@ module ActionView # used in views, unless wrapped by <tt>raw()</tt>. Care should be taken if +text+ contains HTML tags # or entities, because truncation may produce invalid HTML (such as unbalanced or incomplete tags). # - # ==== Examples - # # truncate("Once upon a time in a world far far away") # # => "Once upon a time in a world..." # @@ -84,16 +81,14 @@ module ActionView # truncate("<p>Once upon a time in a world far far away</p>") # # => "<p>Once upon a time in a wo..." def truncate(text, options = {}) - options.reverse_merge!(:length => 30) - text.truncate(options.delete(:length), options) if text + text.truncate(options.fetch(:length, 30), options) if text end # Highlights one or more +phrases+ everywhere in +text+ by inserting it into # a <tt>:highlighter</tt> string. The highlighter can be specialized by passing <tt>:highlighter</tt> - # as a single-quoted string with \1 where the phrase is to be inserted (defaults to + # as a single-quoted string with <tt>\1</tt> where the phrase is to be inserted (defaults to # '<mark>\1</mark>') # - # ==== Examples # highlight('You searched for: rails', 'rails') # # => You searched for: <mark>rails</mark> # @@ -105,23 +100,15 @@ module ActionView # # highlight('You searched for: rails', 'rails', :highlighter => '<a href="search?q=\1">\1</a>') # # => You searched for: <a href="search?q=rails">rails</a> - # - # You can still use <tt>highlight</tt> with the old API that accepts the - # +highlighter+ as its optional third parameter: - # highlight('You searched for: rails', 'rails', '<a href="search?q=\1">\1</a>') # => You searched for: <a href="search?q=rails">rails</a> - def highlight(text, phrases, *args) - options = args.extract_options! - unless args.empty? - options[:highlighter] = args[0] - end - options[:highlighter] ||= '<mark>\1</mark>' - - text = sanitize(text) unless options[:sanitize] == false + def highlight(text, phrases, options = {}) + highlighter = options.fetch(:highlighter, '<mark>\1</mark>') + + text = sanitize(text) if options.fetch(:sanitize, true) if text.blank? || phrases.blank? text else match = Array(phrases).map { |p| Regexp.escape(p) }.join('|') - text.gsub(/(#{match})(?![^<]*?>)/i, options[:highlighter]) + text.gsub(/(#{match})(?![^<]*?>)/i, highlighter) end.html_safe end @@ -131,7 +118,6 @@ module ActionView # then the <tt>:omission</tt> option (which defaults to "...") will be prepended/appended accordingly. The resulting string # will be stripped in any case. If the +phrase+ isn't found, nil is returned. # - # ==== Examples # excerpt('This is an example', 'an', :radius => 5) # # => ...s is an exam... # @@ -146,22 +132,10 @@ module ActionView # # excerpt('This is also an example', 'an', :radius => 8, :omission => '<chop> ') # # => <chop> is also an example - # - # You can still use <tt>excerpt</tt> with the old API that accepts the - # +radius+ as its optional third and the +ellipsis+ as its - # optional forth parameter: - # excerpt('This is an example', 'an', 5) # => ...s is an exam... - # excerpt('This is also an example', 'an', 8, '<chop> ') # => <chop> is also an example - def excerpt(text, phrase, *args) + def excerpt(text, phrase, options = {}) return unless text && phrase - - options = args.extract_options! - unless args.empty? - options[:radius] = args[0] - options[:omission] = args[1] - end - radius = options[:radius] || 100 - omission = options[:omission] || "..." + radius = options.fetch(:radius, 100) + omission = options.fetch(:omission, "...") phrase = Regexp.escape(phrase) return unless found_pos = text =~ /(#{phrase})/i @@ -177,9 +151,8 @@ module ActionView # Attempts to pluralize the +singular+ word unless +count+ is 1. If # +plural+ is supplied, it will use that when count is > 1, otherwise - # it will use the Inflector to determine the plural form + # it will use the Inflector to determine the plural form. # - # ==== Examples # pluralize(1, 'person') # # => 1 person # @@ -192,36 +165,32 @@ module ActionView # pluralize(0, 'person') # # => 0 people def pluralize(count, singular, plural = nil) - "#{count || 0} " + ((count == 1 || count =~ /^1(\.0+)?$/) ? singular : (plural || singular.pluralize)) + word = if (count == 1 || count =~ /^1(\.0+)?$/) + singular + else + plural || singular.pluralize + end + + "#{count || 0} #{word}" end # Wraps the +text+ into lines no longer than +line_width+ width. This method # breaks on the first whitespace character that does not exceed +line_width+ # (which is 80 by default). # - # ==== Examples - # # word_wrap('Once upon a time') # # => Once upon a time # # word_wrap('Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding a successor to the throne turned out to be more trouble than anyone could have imagined...') - # # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\n a successor to the throne turned out to be more trouble than anyone could have\n imagined... + # # => Once upon a time, in a kingdom called Far Far Away, a king fell ill, and finding\na successor to the throne turned out to be more trouble than anyone could have\nimagined... # # word_wrap('Once upon a time', :line_width => 8) - # # => Once upon\na time + # # => Once\nupon a\ntime # # word_wrap('Once upon a time', :line_width => 1) # # => Once\nupon\na\ntime - # - # You can still use <tt>word_wrap</tt> with the old API that accepts the - # +line_width+ as its optional second parameter: - # word_wrap('Once upon a time', 8) # => Once upon\na time - def word_wrap(text, *args) - options = args.extract_options! - unless args.blank? - options[:line_width] = args[0] - end - line_width = options[:line_width] || 80 + def word_wrap(text, options = {}) + line_width = options.fetch(:line_width, 80) text.split("\n").collect do |line| line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line @@ -239,12 +208,16 @@ module ActionView # # ==== Options # * <tt>:sanitize</tt> - If +false+, does not sanitize +text+. + # * <tt>:wrapper_tag</tt> - String representing the wrapper tag, defaults to <tt>"p"</tt> # # ==== Examples # my_text = "Here is some basic text...\n...with a line break." # # simple_format(my_text) # # => "<p>Here is some basic text...\n<br />...with a line break.</p>" + # + # simple_format(my_text, {}, :wrapper_tag => "div") + # # => "<div>Here is some basic text...\n<br />...with a line break.</div>" # # more_text = "We want to put a paragraph...\n\n...right there." # @@ -256,17 +229,19 @@ module ActionView # # simple_format("<span>I'm allowed!</span> It's true.", {}, :sanitize => false) # # => "<p><span>I'm allowed!</span> It's true.</p>" - def simple_format(text, html_options={}, options={}) - text = '' if text.nil? - text = text.dup - start_tag = tag('p', html_options, true) - text = sanitize(text) unless options[:sanitize] == false - text = text.to_str - text.gsub!(/\r\n?/, "\n") # \r\n and \r -> \n - text.gsub!(/\n\n+/, "</p>\n\n#{start_tag}") # 2+ newline -> paragraph - text.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') # 1 newline -> br - text.insert 0, start_tag - text.html_safe.safe_concat("</p>") + def simple_format(text, html_options = {}, options = {}) + wrapper_tag = options.fetch(:wrapper_tag, :p) + + text = sanitize(text) if options.fetch(:sanitize, true) + paragraphs = split_paragraphs(text) + + if paragraphs.empty? + content_tag(wrapper_tag, nil, html_options) + else + paragraphs.map { |paragraph| + content_tag(wrapper_tag, paragraph, html_options, options[:sanitize]) + }.join("\n\n").html_safe + end end # Creates a Cycle object whose _to_s_ method cycles through elements of an @@ -278,7 +253,6 @@ module ActionView # and passing the name of the cycle. The current cycle string can be obtained # anytime using the current_cycle method. # - # ==== Examples # # Alternate CSS classes for even and odd numbers... # @items = [1,2,3,4] # <table> @@ -309,7 +283,7 @@ module ActionView # <% end %> def cycle(first_value, *values) options = values.extract_options! - name = options.fetch(:name, "default") + name = options.fetch(:name, 'default') values.unshift(first_value) @@ -324,7 +298,6 @@ module ActionView # for complex table highlighting or any other design need which requires # the current cycle string in more than one place. # - # ==== Example # # Alternate background colors # @items = [1,2,3,4] # <% @items.each do |item| %> @@ -340,7 +313,6 @@ module ActionView # Resets a cycle so that it starts from the first element the next time # it is called. Pass in +name+ to reset a named cycle. # - # ==== Example # # Alternate CSS classes for even and odd numbers... # @items = [[1,2,3,4], [5,6,3], [3,4,5,6,7,4]] # <table> @@ -411,6 +383,14 @@ module ActionView @_cycles = Hash.new unless defined?(@_cycles) @_cycles[name] = cycle_object end + + def split_paragraphs(text) + return [] if text.blank? + + text.to_str.gsub(/\r\n?/, "\n").split(/\n\n+/).map! do |t| + t.gsub!(/([^\n]\n)(?=[^\n])/, '\1<br />') || t + end + end end end end diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index fd06bfa2a8..552c9ba660 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -63,6 +63,9 @@ module ActionView alias :t :translate # Delegates to <tt>I18n.localize</tt> with no additional functionality. + # + # See http://rubydoc.info/github/svenfuchs/i18n/master/I18n/Backend/Base:localize + # for more information. def localize(*args) I18n.localize(*args) end @@ -93,7 +96,7 @@ module ActionView new_defaults << lambda { |_, options| translate key, options.merge(:default => defaults) } break else - new_defautls << key + new_defaults << key end end diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 1145f348c2..7e69547dab 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -108,7 +108,7 @@ module ActionView options when nil, Hash options ||= {} - options = options.symbolize_keys.reverse_merge!(:only_path => options[:host].nil?) + options = { :only_path => options[:host].nil? }.merge!(options.symbolize_keys) super when :back controller.request.env["HTTP_REFERER"] || 'javascript:history.back()' @@ -322,11 +322,11 @@ module ActionView # # # <%= button_to('Destroy', 'http://www.example.com', :confirm => 'Are you sure?', - # :method => "delete", :remote => true, :disable_with => 'loading...') %> + # :method => "delete", :remote => true) %> # # => "<form class='button_to' method='post' action='http://www.example.com' data-remote='true'> # # <div> # # <input name='_method' value='delete' type='hidden' /> - # # <input value='Destroy' type='submit' disable_with='loading...' data-confirm='Are you sure?' /> + # # <input value='Destroy' type='submit' data-confirm='Are you sure?' /> # # <input name="authenticity_token" type="hidden" value="10f2163b45388899ad4d5ae948988266befcb6c3d1b2451cf657a0c293d605a6"/> # # </div> # # </form>" @@ -616,11 +616,9 @@ module ActionView html_options = html_options.stringify_keys html_options['data-remote'] = 'true' if link_to_remote_options?(options) || link_to_remote_options?(html_options) - disable_with = html_options.delete("disable_with") confirm = html_options.delete('confirm') method = html_options.delete('method') - html_options["data-disable-with"] = disable_with if disable_with html_options["data-confirm"] = confirm if confirm add_method_to_attributes!(html_options, method) if method @@ -670,11 +668,11 @@ module ActionView end def token_tag(token=nil) - if token == false || !protect_against_forgery? - '' - else + if token != false && protect_against_forgery? token ||= form_authenticity_token tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => token) + else + '' end end diff --git a/actionpack/lib/action_view/renderer/abstract_renderer.rb b/actionpack/lib/action_view/renderer/abstract_renderer.rb index 52473cd222..72616b7463 100644 --- a/actionpack/lib/action_view/renderer/abstract_renderer.rb +++ b/actionpack/lib/action_view/renderer/abstract_renderer.rb @@ -14,12 +14,10 @@ module ActionView protected def extract_details(options) - details = {} - @lookup_context.registered_details.each do |key| + @lookup_context.registered_details.each_with_object({}) do |key, details| next unless value = options[key] details[key] = Array(value) end - details end def instrument(name, options={}) diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 87609fd5ff..9100545718 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -283,7 +283,7 @@ module ActionView return nil if @collection.blank? if @options.key?(:spacer_template) - spacer = find_template(@options[:spacer_template]).render(@view, @locals) + spacer = find_template(@options[:spacer_template], @locals.keys).render(@view, @locals) end result = @template ? collection_with_template : collection_without_template @@ -291,11 +291,11 @@ module ActionView end def render_partial - locals, view, block = @locals, @view, @block + view, locals, block = @view, @locals, @block object, as = @object, @variable if !block && (layout = @options[:layout]) - layout = find_template(layout, @locals.keys + [@variable]) + layout = find_template(layout, @template_keys) end object ||= locals[as] @@ -337,6 +337,7 @@ module ActionView if @path @variable, @variable_counter = retrieve_variable(@path) + @template_keys = retrieve_template_keys else paths.map! { |path| retrieve_variable(path).unshift(path) } end @@ -358,62 +359,55 @@ module ActionView end def collection_from_object - if @object.respond_to?(:to_ary) - @object.to_ary - end + @object.to_ary if @object.respond_to?(:to_ary) end def find_partial if path = @path - locals = @locals.keys - locals << @variable - locals << @variable_counter if @collection - find_template(path, locals) + find_template(path, @template_keys) end end - def find_template(path=@path, locals=@locals.keys) + def find_template(path, locals) prefixes = path.include?(?/) ? [] : @lookup_context.prefixes @lookup_context.find_template(path, prefixes, true, locals, @details) end def collection_with_template - segments, locals, template = [], @locals, @template + view, locals, template = @view, @locals, @template as, counter = @variable, @variable_counter if layout = @options[:layout] - layout = find_template(layout, @locals.keys + [@variable, @variable_counter]) + layout = find_template(layout, @template_keys) end - locals[counter] = -1 - - @collection.each do |object| - locals[counter] += 1 - locals[as] = object + index = -1 + @collection.map do |object| + locals[as] = object + locals[counter] = (index += 1) - content = template.render(@view, locals) - content = layout.render(@view, locals) { content } if layout - segments << content + content = template.render(view, locals) + content = layout.render(view, locals) { content } if layout + content end - - segments end def collection_without_template - segments, locals, collection_data = [], @locals, @collection_data - index, template, cache = -1, nil, {} - keys = @locals.keys + view, locals, collection_data = @view, @locals, @collection_data + cache = {} + keys = @locals.keys - @collection.each_with_index do |object, i| - path, *data = collection_data[i] - template = (cache[path] ||= find_template(path, keys + data)) - locals[data[0]] = object - locals[data[1]] = (index += 1) - segments << template.render(@view, locals) - end + index = -1 + @collection.map do |object| + index += 1 + path, as, counter = collection_data[index] - @template = template - segments + locals[as] = object + locals[counter] = index + + template = (cache[path] ||= find_template(path, keys + [as, counter])) + template.render(view, locals) + end end def partial_path(object = @object) @@ -453,6 +447,13 @@ module ActionView end end + def retrieve_template_keys + keys = @locals.keys + keys << @variable + keys << @variable_counter if @collection + keys + end + def retrieve_variable(path) variable = @options.fetch(:as) { path[%r'_?(\w+)(\.\w+)*$', 1] }.try(:to_sym) variable_counter = :"#{variable}_counter" if @collection diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index edb3d427d5..cd79468502 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -1,6 +1,7 @@ require 'active_support/core_ext/object/blank' require 'active_support/core_ext/object/try' require 'active_support/core_ext/kernel/singleton_class' +require 'thread' module ActionView # = Action View Template @@ -122,6 +123,7 @@ module ActionView @virtual_path = details[:virtual_path] @updated_at = details[:updated_at] || Time.now @formats = Array(format).map { |f| f.is_a?(Mime::Type) ? f.ref : f } + @compile_mutex = Mutex.new end # Returns if the underlying handler supports streaming. If so, @@ -223,18 +225,28 @@ module ActionView def compile!(view) #:nodoc: return if @compiled - if view.is_a?(ActionView::CompiledTemplates) - mod = ActionView::CompiledTemplates - else - mod = view.singleton_class - end + # Templates can be used concurrently in threaded environments + # so compilation and any instance variable modification must + # be synchronized + @compile_mutex.synchronize do + # Any thread holding this lock will be compiling the template needed + # by the threads waiting. So re-check the @compiled flag to avoid + # re-compilation + return if @compiled + + if view.is_a?(ActionView::CompiledTemplates) + mod = ActionView::CompiledTemplates + else + mod = view.singleton_class + end - compile(view, mod) + compile(view, mod) - # Just discard the source if we have a virtual path. This - # means we can get the template back. - @source = nil if @virtual_path - @compiled = true + # Just discard the source if we have a virtual path. This + # means we can get the template back. + @source = nil if @virtual_path + @compiled = true + end end # Among other things, this method is responsible for properly setting diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb index 4e22bec6cc..41b14373a3 100644 --- a/actionpack/lib/action_view/template/handlers.rb +++ b/actionpack/lib/action_view/template/handlers.rb @@ -4,10 +4,12 @@ module ActionView #:nodoc: module Handlers #:nodoc: autoload :ERB, 'action_view/template/handlers/erb' autoload :Builder, 'action_view/template/handlers/builder' + autoload :Raw, 'action_view/template/handlers/raw' def self.extended(base) base.register_default_template_handler :erb, ERB.new base.register_template_handler :builder, Builder.new + base.register_template_handler :raw, Raw.new end @@template_handlers = {} diff --git a/actionpack/lib/action_view/template/handlers/raw.rb b/actionpack/lib/action_view/template/handlers/raw.rb new file mode 100644 index 0000000000..0c0d1fffcb --- /dev/null +++ b/actionpack/lib/action_view/template/handlers/raw.rb @@ -0,0 +1,11 @@ +module ActionView + module Template::Handlers + class Raw + def call(template) + escaped = template.source.gsub(':', '\:') + + '%q:' + escaped + ':;' + end + end + end +end diff --git a/actionpack/lib/action_view/template/resolver.rb b/actionpack/lib/action_view/template/resolver.rb index 8ea2e5bfe4..fa2038f78d 100644 --- a/actionpack/lib/action_view/template/resolver.rb +++ b/actionpack/lib/action_view/template/resolver.rb @@ -1,5 +1,6 @@ require "pathname" require "active_support/core_ext/class" +require "active_support/core_ext/class/attribute_accessors" require "action_view/template" module ActionView @@ -170,7 +171,9 @@ module ActionView def extract_handler_and_format(path, default_formats) pieces = File.basename(path).split(".") pieces.shift - handler = Template.handler_for_extension(pieces.pop) + extension = pieces.pop + ActiveSupport::Deprecation.warn "The file #{path} did not specify a template handler. The default is currently ERB, but will change to RAW in the future." unless extension + handler = Template.handler_for_extension(extension) format = pieces.last && Mime[pieces.last] [handler, format] end |