diff options
author | Mikel Lindsaar <raasdnil@gmail.com> | 2010-03-28 14:44:34 +1100 |
---|---|---|
committer | Mikel Lindsaar <raasdnil@gmail.com> | 2010-03-28 14:44:34 +1100 |
commit | 2bcc2ebf44b59e46c104c92d621e8051c97bfcf5 (patch) | |
tree | d2a3f04fd3020c1b5d88847af62d52f2d5e5bd61 /actionpack/lib/action_view/helpers | |
parent | f5774e3e3f70a3acfa559b9ff889e9417fb71d4b (diff) | |
parent | 8398f21880a952769ccd6437a4344922fe596dab (diff) | |
download | rails-2bcc2ebf44b59e46c104c92d621e8051c97bfcf5.tar.gz rails-2bcc2ebf44b59e46c104c92d621e8051c97bfcf5.tar.bz2 rails-2bcc2ebf44b59e46c104c92d621e8051c97bfcf5.zip |
Merge branch 'master' of git://github.com/rails/rails
Diffstat (limited to 'actionpack/lib/action_view/helpers')
16 files changed, 553 insertions, 327 deletions
diff --git a/actionpack/lib/action_view/helpers/active_model_helper.rb b/actionpack/lib/action_view/helpers/active_model_helper.rb index 4e12cdab54..80b3d3a664 100644 --- a/actionpack/lib/action_view/helpers/active_model_helper.rb +++ b/actionpack/lib/action_view/helpers/active_model_helper.rb @@ -3,6 +3,7 @@ require 'action_view/helpers/form_helper' require 'active_support/core_ext/class/attribute_accessors' require 'active_support/core_ext/enumerable' require 'active_support/core_ext/kernel/reporting' +require 'active_support/core_ext/object/blank' module ActionView ActionView.base_hook do @@ -127,9 +128,9 @@ module ActionView object = convert_to_model(object) if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) && - (errors = obj.errors[method]) + (errors = obj.errors[method]).presence content_tag("div", - (options[:prepend_text].html_safe << errors.first).safe_concat(options[:append_text]), + "#{options[:prepend_text]}#{ERB::Util.h(errors.first)}#{options[:append_text]}".html_safe, :class => options[:css_class] ) else @@ -295,6 +296,10 @@ module ActionView end end + def error_message + object.errors[@method_name] + end + def column_type object.send(:column_for_attribute, @method_name).type end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index de3d61ebbe..02ad41719b 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -3,6 +3,7 @@ require 'cgi' require 'action_view/helpers/url_helper' require 'action_view/helpers/tag_helper' require 'active_support/core_ext/file' +require 'active_support/core_ext/object/blank' module ActionView module Helpers #:nodoc: @@ -11,7 +12,7 @@ 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="/images/rails.png?1230601161" /> # stylesheet_link_tag("application") # # => <link href="/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" /> # @@ -58,7 +59,7 @@ module ActionView # +asset_host+ to a proc like this: # # ActionController::Base.asset_host = Proc.new { |source| - # "http://assets#{rand(2) + 1}.example.com" + # "http://assets#{source.hash % 2 + 1}.example.com" # } # image_tag("rails.png") # # => <img alt="Rails" src="http://assets0.example.com/images/rails.png?1230601161" /> @@ -66,7 +67,7 @@ module ActionView # # => <link href="http://assets1.example.com/stylesheets/application.css?1232285206" media="screen" rel="stylesheet" type="text/css" /> # # The example above generates "http://assets1.example.com" and - # "http://assets2.example.com" randomly. This option is useful for example if + # "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 @@ -242,12 +243,12 @@ module ActionView # == Caching multiple javascripts into one # # You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be - # compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching + # compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching # is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development # environment). # # ==== Examples - # javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is false => + # javascript_include_tag :all, :cache => true # when config.perform_caching is false => # <script type="text/javascript" src="/javascripts/prototype.js"></script> # <script type="text/javascript" src="/javascripts/effects.js"></script> # ... @@ -255,15 +256,15 @@ module ActionView # <script type="text/javascript" src="/javascripts/shop.js"></script> # <script type="text/javascript" src="/javascripts/checkout.js"></script> # - # javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is true => + # javascript_include_tag :all, :cache => true # when config.perform_caching is true => # <script type="text/javascript" src="/javascripts/all.js"></script> # - # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is false => + # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is false => # <script type="text/javascript" src="/javascripts/prototype.js"></script> # <script type="text/javascript" src="/javascripts/cart.js"></script> # <script type="text/javascript" src="/javascripts/checkout.js"></script> # - # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when ActionController::Base.perform_caching is true => + # javascript_include_tag "prototype", "cart", "checkout", :cache => "shop" # when config.perform_caching is true => # <script type="text/javascript" src="/javascripts/shop.js"></script> # # The <tt>:recursive</tt> option is also available for caching: @@ -275,11 +276,11 @@ module ActionView cache = concat || options.delete("cache") recursive = options.delete("recursive") - if concat || (ActionController::Base.perform_caching && cache) + if concat || (config.perform_caching && cache) joined_javascript_name = (cache == true ? "all" : cache) + ".js" joined_javascript_path = File.join(joined_javascript_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.javascripts_dir, joined_javascript_name) - unless ActionController::Base.perform_caching && File.exists?(joined_javascript_path) + unless config.perform_caching && File.exists?(joined_javascript_path) write_asset_file_contents(joined_javascript_path, compute_javascript_paths(sources, recursive)) end javascript_src_tag(joined_javascript_name, options) @@ -390,25 +391,25 @@ module ActionView # == Caching multiple stylesheets into one # # You can also cache multiple stylesheets into one file, which requires less HTTP connections and can better be - # compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching + # compressed by gzip (leading to faster transfers). Caching will only happen if config.perform_caching # 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 ActionController::Base.perform_caching is false => + # stylesheet_link_tag :all, :cache => true # when config.perform_caching is false => # <link href="/stylesheets/style1.css" media="screen" rel="stylesheet" type="text/css" /> # <link href="/stylesheets/styleB.css" media="screen" rel="stylesheet" type="text/css" /> # <link href="/stylesheets/styleX2.css" media="screen" rel="stylesheet" type="text/css" /> # - # stylesheet_link_tag :all, :cache => true # when ActionController::Base.perform_caching is true => + # stylesheet_link_tag :all, :cache => true # when config.perform_caching is true => # <link href="/stylesheets/all.css" media="screen" rel="stylesheet" type="text/css" /> # - # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is false => + # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is false => # <link href="/stylesheets/shop.css" media="screen" rel="stylesheet" type="text/css" /> # <link href="/stylesheets/cart.css" media="screen" rel="stylesheet" type="text/css" /> # <link href="/stylesheets/checkout.css" media="screen" rel="stylesheet" type="text/css" /> # - # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when ActionController::Base.perform_caching is true => + # stylesheet_link_tag "shop", "cart", "checkout", :cache => "payment" # when config.perform_caching is true => # <link href="/stylesheets/payment.css" media="screen" rel="stylesheet" type="text/css" /> # # The <tt>:recursive</tt> option is also available for caching: @@ -426,11 +427,11 @@ module ActionView cache = concat || options.delete("cache") recursive = options.delete("recursive") - if concat || (ActionController::Base.perform_caching && cache) + if concat || (config.perform_caching && cache) joined_stylesheet_name = (cache == true ? "all" : cache) + ".css" joined_stylesheet_path = File.join(joined_stylesheet_name[/^#{File::SEPARATOR}/] ? config.assets_dir : config.stylesheets_dir, joined_stylesheet_name) - unless ActionController::Base.perform_caching && File.exists?(joined_stylesheet_path) + unless config.perform_caching && File.exists?(joined_stylesheet_path) write_asset_file_contents(joined_stylesheet_path, compute_stylesheet_paths(sources, recursive)) end stylesheet_tag(joined_stylesheet_name, options) @@ -523,7 +524,7 @@ module ActionView options.symbolize_keys! src = options[:src] = path_to_image(source) - options[:alt] ||= File.basename(src, '.*').split('.').first.to_s.capitalize + options[:alt] ||= File.basename(src, '.*').capitalize if size = options.delete(:size) options[:width], options[:height] = size.split("x") if size =~ %r{^\d+x\d+$} @@ -623,41 +624,37 @@ module ActionView @@cache_asset_timestamps = true private + def rewrite_extension?(source, dir, ext) + source_ext = File.extname(source)[1..-1] + ext && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(config.assets_dir, dir, "#{source}.#{ext}")))) + end + + def rewrite_host_and_protocol(source, has_request) + host = compute_asset_host(source) + if has_request && host.present? && !is_uri?(host) + host = "#{controller.request.protocol}#{host}" + end + "#{host}#{source}" + end + # Add the the extension +ext+ if not present. Return full URLs otherwise untouched. # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL # roots. Rewrite the asset path for cache-busting asset ids. Include # asset host, if configured, with the correct request protocol. def compute_public_path(source, dir, ext = nil, include_host = true) - has_request = controller.respond_to?(:request) - - source_ext = File.extname(source)[1..-1] - if ext && !is_uri?(source) && (source_ext.blank? || (ext != source_ext && File.exist?(File.join(config.assets_dir, dir, "#{source}.#{ext}")))) - source += ".#{ext}" - end + return source if is_uri?(source) - unless is_uri?(source) - source = "/#{dir}/#{source}" unless source[0] == ?/ + source += ".#{ext}" if rewrite_extension?(source, dir, ext) + source = "/#{dir}/#{source}" unless source[0] == ?/ + source = rewrite_asset_path(source) - source = rewrite_asset_path(source) - - if has_request && include_host - unless source =~ %r{^#{controller.config.relative_url_root}/} - source = "#{controller.config.relative_url_root}#{source}" - end - end + has_request = controller.respond_to?(:request) + if has_request && include_host && source !~ %r{^#{controller.config.relative_url_root}/} + source = "#{controller.config.relative_url_root}#{source}" end + source = rewrite_host_and_protocol(source, has_request) if include_host - if include_host && !is_uri?(source) - host = compute_asset_host(source) - - if has_request && !host.blank? && !is_uri?(host) - host = "#{controller.request.protocol}#{host}" - end - - "#{host}#{source}" - else - source - end + source end def is_uri?(path) diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index d5cc14b29a..a904af56bb 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -32,7 +32,28 @@ module ActionView # <i>Topics listed alphabetically</i> # <% end %> def cache(name = {}, options = nil, &block) - controller.fragment_for(output_buffer, name, options, &block) + safe_concat fragment_for(name, options, &block) + nil + end + + private + # TODO: Create an object that has caching read/write on it + def fragment_for(name = {}, options = nil, &block) #:nodoc: + if controller.perform_caching + if controller.fragment_exist?(name, options) + controller.read_fragment(name, options) + else + # VIEW TODO: Make #capture usable outside of ERB + # This dance is needed because Builder can't use capture + pos = output_buffer.length + yield + fragment = output_buffer.slice!(pos..-1) + controller.write_fragment(name, fragment, options) + end + else + ret = yield + ActiveSupport::SafeBuffer.new(ret) if ret.is_a?(String) + end end end end diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 75fc2fddeb..f0be814700 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -2,22 +2,22 @@ module ActionView module Helpers # CaptureHelper exposes methods to let you extract generated markup which # can be used in other parts of a template or layout file. - # It provides a method to capture blocks into variables through capture and + # It provides a method to capture blocks into variables through capture and # a way to capture a block of markup for use in a layout through content_for. module CaptureHelper - # 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. - # + # 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 %> # Welcome to my shiny new web page! The date and time is # <%= Time.now %> # <% end %> # # ...and Builder (RXML) templates. - # + # # @timestamp = capture do # "The current timestamp is #{Time.now}." # end @@ -32,16 +32,18 @@ module ActionView # def capture(*args) value = nil - buffer = with_output_buffer { value = yield *args } - buffer.presence || value + buffer = with_output_buffer { value = yield(*args) } + if string = buffer.presence || value and string.is_a?(String) + NonConcattingString.new(string) + end end # Calling content_for stores a block of markup in an identifier for later use. # You can make subsequent calls to the stored content in other templates or the layout # by passing the identifier as an argument to <tt>yield</tt>. - # + # # ==== Examples - # + # # <% content_for :not_authorized do %> # alert('You are not authorized to do that!') # <% end %> @@ -75,7 +77,7 @@ module ActionView # # Then, in another view, you could to do something like this: # - # <%= link_to_remote 'Logout', :action => 'logout' %> + # <%= link_to 'Logout', :action => 'logout', :remote => true %> # # <% content_for :script do %> # <%= javascript_include_tag :defaults %> @@ -92,7 +94,7 @@ module ActionView # <% end %> # # <%# Add some other content, or use a different template: %> - # + # # <% content_for :navigation do %> # <li><%= link_to 'Login', :action => 'login' %></li> # <% end %> @@ -109,13 +111,13 @@ module ActionView # for elements that will be fragment cached. def content_for(name, content = nil, &block) content = capture(&block) if block_given? - return @_content_for[name] << content if content - @_content_for[name] + @_content_for[name] << content if content + @_content_for[name] unless content end # content_for? simply checks whether any content has been captured yet using content_for # Useful to render parts of your layout differently based on what is in your views. - # + # # ==== Examples # # Perhaps you will use different css in you layout if no content_for :right_column @@ -140,7 +142,7 @@ module ActionView def with_output_buffer(buf = nil) #:nodoc: unless buf buf = ActionView::OutputBuffer.new - buf.force_encoding(output_buffer.encoding) if buf.respond_to?(:force_encoding) + buf.force_encoding(output_buffer.encoding) if output_buffer && buf.respond_to?(:force_encoding) end self.output_buffer, old_buffer = buf, output_buffer yield diff --git a/actionpack/lib/action_view/helpers/deprecated_block_helpers.rb b/actionpack/lib/action_view/helpers/deprecated_block_helpers.rb deleted file mode 100644 index 3d0657e873..0000000000 --- a/actionpack/lib/action_view/helpers/deprecated_block_helpers.rb +++ /dev/null @@ -1,52 +0,0 @@ -module ActionView - module Helpers - module DeprecatedBlockHelpers - extend ActiveSupport::Concern - - include ActionView::Helpers::TagHelper - include ActionView::Helpers::TextHelper - include ActionView::Helpers::JavaScriptHelper - include ActionView::Helpers::FormHelper - - def content_tag(*, &block) - block_called_from_erb?(block) ? safe_concat(super) : super - end - - def javascript_tag(*, &block) - block_called_from_erb?(block) ? safe_concat(super) : super - end - - def form_for(*, &block) - block_called_from_erb?(block) ? safe_concat(super) : super - end - - def form_tag(*, &block) - block_called_from_erb?(block) ? safe_concat(super) : super - end - - def fields_for(*, &block) - block_called_from_erb?(block) ? safe_concat(super) : super - end - - def field_set_tag(*, &block) - block_called_from_erb?(block) ? safe_concat(super) : super - end - - BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template' - - if RUBY_VERSION < '1.9.0' - # Check whether we're called from an erb template. - # We'd return a string in any other case, but erb <%= ... %> - # can't take an <% end %> later on, so we have to use <% ... %> - # and implicitly concat. - def block_called_from_erb?(block) - block && eval(BLOCK_CALLED_FROM_ERB, block) - end - else - def block_called_from_erb?(block) - block && eval(BLOCK_CALLED_FROM_ERB, block.binding) - end - end - end - end -end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 48df26efaa..2ba5339b7d 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -28,7 +28,7 @@ module ActionView # # # Note: a @person variable will have been created in the controller. # # For example: @person = Person.new - # <% form_for :person, @person, :url => { :action => "create" } do |f| %> + # <%= form_for :person, @person, :url => { :action => "create" } do |f| %> # <%= f.text_field :first_name %> # <%= f.text_field :last_name %> # <%= submit_tag 'Create' %> @@ -44,7 +44,7 @@ module ActionView # # If you are using a partial for your form fields, you can use this shortcut: # - # <% form_for :person, @person, :url => { :action => "create" } do |f| %> + # <%= form_for :person, @person, :url => { :action => "create" } do |f| %> # <%= render :partial => f %> # <%= submit_tag 'Create' %> # <% end %> @@ -102,7 +102,7 @@ module ActionView # Rails provides succinct resource-oriented form generation with +form_for+ # like this: # - # <% form_for @offer do |f| %> + # <%= form_for @offer do |f| %> # <%= f.label :version, 'Version' %>: # <%= f.text_field :version %><br /> # <%= f.label :author, 'Author' %>: @@ -119,7 +119,7 @@ module ActionView # The generic way to call +form_for+ yields a form builder around a # model: # - # <% form_for :person, :url => { :action => "update" } do |f| %> + # <%= form_for :person, :url => { :action => "update" } do |f| %> # <%= f.error_messages %> # First name: <%= f.text_field :first_name %><br /> # Last name : <%= f.text_field :last_name %><br /> @@ -143,7 +143,7 @@ module ActionView # If the instance variable is not <tt>@person</tt> you can pass the actual # record as the second argument: # - # <% form_for :person, person, :url => { :action => "update" } do |f| %> + # <%= form_for :person, person, :url => { :action => "update" } do |f| %> # ... # <% end %> # @@ -175,7 +175,7 @@ module ActionView # possible to use both the stand-alone FormHelper methods and methods # from FormTagHelper. For example: # - # <% form_for :person, @person, :url => { :action => "update" } do |f| %> + # <%= form_for :person, @person, :url => { :action => "update" } do |f| %> # First name: <%= f.text_field :first_name %> # Last name : <%= f.text_field :last_name %> # Biography : <%= text_area :person, :biography %> @@ -195,37 +195,37 @@ module ActionView # # For example, if <tt>@post</tt> is an existing record you want to edit # - # <% form_for @post do |f| %> + # <%= form_for @post do |f| %> # ... # <% end %> # # is equivalent to something like: # - # <% form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %> + # <%= form_for :post, @post, :url => post_path(@post), :html => { :method => :put, :class => "edit_post", :id => "edit_post_45" } do |f| %> # ... # <% end %> # # And for new records # - # <% form_for(Post.new) do |f| %> + # <%= form_for(Post.new) do |f| %> # ... # <% end %> # # expands to # - # <% form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %> + # <%= form_for :post, Post.new, :url => posts_path, :html => { :class => "new_post", :id => "new_post" } do |f| %> # ... # <% end %> # # You can also overwrite the individual conventions, like this: # - # <% form_for(@post, :url => super_post_path(@post)) do |f| %> + # <%= form_for(@post, :url => super_post_path(@post)) do |f| %> # ... # <% end %> # # And for namespaced routes, like +admin_post_url+: # - # <% form_for([:admin, @post]) do |f| %> + # <%= form_for([:admin, @post]) do |f| %> # ... # <% end %> # @@ -243,7 +243,7 @@ module ActionView # # Example: # - # <% form_for(:post, @post, :remote => true, :html => { :id => 'create-post', :method => :put }) do |f| %> + # <%= form_for(:post, @post, :remote => true, :html => { :id => 'create-post', :method => :put }) do |f| %> # ... # <% end %> # @@ -263,7 +263,7 @@ module ActionView # custom builder. For example, let's say you made a helper to # automatically add labels to form inputs. # - # <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %> + # <%= form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %> # <%= f.text_field :first_name %> # <%= f.text_field :last_name %> # <%= text_area :person, :biography %> @@ -340,11 +340,11 @@ module ActionView # # === Generic Examples # - # <% form_for @person, :url => { :action => "update" } do |person_form| %> + # <%= form_for @person, :url => { :action => "update" } do |person_form| %> # First name: <%= person_form.text_field :first_name %> # Last name : <%= person_form.text_field :last_name %> # - # <% fields_for @person.permission do |permission_fields| %> + # <%= fields_for @person.permission do |permission_fields| %> # Admin? : <%= permission_fields.check_box :admin %> # <% end %> # <% end %> @@ -352,13 +352,13 @@ module ActionView # ...or if you have an object that needs to be represented as a different # parameter, like a Client that acts as a Person: # - # <% fields_for :person, @client do |permission_fields| %> + # <%= fields_for :person, @client do |permission_fields| %> # Admin?: <%= permission_fields.check_box :admin %> # <% end %> # # ...or if you don't have an object, just a name of the parameter: # - # <% fields_for :person do |permission_fields| %> + # <%= fields_for :person do |permission_fields| %> # Admin?: <%= permission_fields.check_box :admin %> # <% end %> # @@ -402,9 +402,9 @@ module ActionView # # This model can now be used with a nested fields_for, like so: # - # <% form_for @person, :url => { :action => "update" } do |person_form| %> + # <%= form_for @person, :url => { :action => "update" } do |person_form| %> # ... - # <% person_form.fields_for :address do |address_fields| %> + # <%= person_form.fields_for :address do |address_fields| %> # Street : <%= address_fields.text_field :street %> # Zip code: <%= address_fields.text_field :zip_code %> # <% end %> @@ -427,15 +427,15 @@ module ActionView # accepts_nested_attributes_for :address, :allow_destroy => true # end # - # Now, when you use a form element with the <tt>_delete</tt> parameter, + # Now, when you use a form element with the <tt>_destroy</tt> parameter, # with a value that evaluates to +true+, you will destroy the associated # model (eg. 1, '1', true, or 'true'): # - # <% form_for @person, :url => { :action => "update" } do |person_form| %> + # <%= form_for @person, :url => { :action => "update" } do |person_form| %> # ... - # <% person_form.fields_for :address do |address_fields| %> + # <%= person_form.fields_for :address do |address_fields| %> # ... - # Delete: <%= address_fields.check_box :_delete %> + # Delete: <%= address_fields.check_box :_destroy %> # <% end %> # <% end %> # @@ -459,9 +459,9 @@ module ActionView # the nested fields_for call will be repeated for each instance in the # collection: # - # <% form_for @person, :url => { :action => "update" } do |person_form| %> + # <%= form_for @person, :url => { :action => "update" } do |person_form| %> # ... - # <% person_form.fields_for :projects do |project_fields| %> + # <%= person_form.fields_for :projects do |project_fields| %> # <% if project_fields.object.active? %> # Name: <%= project_fields.text_field :name %> # <% end %> @@ -470,11 +470,11 @@ module ActionView # # It's also possible to specify the instance to be used: # - # <% form_for @person, :url => { :action => "update" } do |person_form| %> + # <%= form_for @person, :url => { :action => "update" } do |person_form| %> # ... # <% @person.projects.each do |project| %> # <% if project.active? %> - # <% person_form.fields_for :projects, project do |project_fields| %> + # <%= person_form.fields_for :projects, project do |project_fields| %> # Name: <%= project_fields.text_field :name %> # <% end %> # <% end %> @@ -483,9 +483,9 @@ module ActionView # # Or a collection to be used: # - # <% form_for @person, :url => { :action => "update" } do |person_form| %> + # <%= form_for @person, :url => { :action => "update" } do |person_form| %> # ... - # <% person_form.fields_for :projects, @active_projects do |project_fields| %> + # <%= person_form.fields_for :projects, @active_projects do |project_fields| %> # Name: <%= project_fields.text_field :name %> # <% end %> # <% end %> @@ -508,14 +508,14 @@ module ActionView # end # # This will allow you to specify which models to destroy in the - # attributes hash by adding a form element for the <tt>_delete</tt> + # attributes hash by adding a form element for the <tt>_destroy</tt> # parameter with a value that evaluates to +true+ # (eg. 1, '1', true, or 'true'): # - # <% form_for @person, :url => { :action => "update" } do |person_form| %> + # <%= form_for @person, :url => { :action => "update" } do |person_form| %> # ... - # <% person_form.fields_for :projects do |project_fields| %> - # Delete: <%= project_fields.check_box :_delete %> + # <%= person_form.fields_for :projects do |project_fields| %> + # Delete: <%= project_fields.check_box :_destroy %> # <% end %> # <% end %> def fields_for(record_or_name_or_array, *args, &block) @@ -725,7 +725,7 @@ module ActionView # Unfortunately that workaround does not work when the check box goes # within an array-like parameter, as in # - # <% fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %> + # <%= fields_for "project[invoice_attributes][]", invoice, :index => nil do |form| %> # <%= form.check_box :paid %> # ... # <% end %> @@ -1014,7 +1014,7 @@ module ActionView class FormBuilder #:nodoc: # The methods which wrap a form helper call. class_inheritable_accessor :field_helpers - self.field_helpers = (FormHelper.instance_methods - ['form_for']) + self.field_helpers = (FormHelper.instance_method_names - ['form_for']) attr_accessor :object_name, :object, :options @@ -1040,7 +1040,7 @@ module ActionView end (field_helpers - %w(label check_box radio_button fields_for hidden_field)).each do |selector| - src = <<-end_src + src, file, line = <<-end_src, __FILE__, __LINE__ + 1 def #{selector}(method, options = {}) # def text_field(method, options = {}) @template.send( # @template.send( #{selector.inspect}, # "text_field", @@ -1049,7 +1049,7 @@ module ActionView objectify_options(options)) # objectify_options(options)) end # end end_src - class_eval src, __FILE__, __LINE__ + class_eval src, file, line end def fields_for(record_or_name_or_array, *args, &block) @@ -1115,7 +1115,7 @@ module ActionView # Add the submit button for the given form. When no value is given, it checks # if the object is a new resource or not to create the proper label: # - # <% form_for @post do |f| %> + # <%= form_for @post do |f| %> # <%= f.submit %> # <% end %> # diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 7039ecd233..4c523d4b20 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -151,7 +151,7 @@ module ActionView # end # # Sample usage (selecting the associated Author for an instance of Post, <tt>@post</tt>): - # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, {:prompt => true}) + # collection_select(:post, :author_id, Author.all, :id, :name_with_initial, :prompt => true) # # If <tt>@post.author_id</tt> is already <tt>1</tt>, this would return: # <select name="post[author_id]"> diff --git a/actionpack/lib/action_view/helpers/form_tag_helper.rb b/actionpack/lib/action_view/helpers/form_tag_helper.rb index 573733ffea..07694f5ebb 100644 --- a/actionpack/lib/action_view/helpers/form_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/form_tag_helper.rb @@ -37,12 +37,12 @@ module ActionView # form_tag('/upload', :multipart => true) # # => <form action="/upload" method="post" enctype="multipart/form-data"> # - # <% form_tag('/posts')do -%> + # <%= form_tag('/posts')do -%> # <div><%= submit_tag 'Save' %></div> # <% end -%> # # => <form action="/posts" method="post"><div><input type="submit" name="submit" value="Save" /></div></form> # - # <% form_tag('/posts', :remote => true) %> + # <%= form_tag('/posts', :remote => true) %> # # => <form action="/posts" method="post" data-remote="true"> # def form_tag(url_for_options = {}, options = {}, *parameters_for_url, &block) @@ -430,17 +430,17 @@ module ActionView # <tt>options</tt> accept the same values as tag. # # ==== Examples - # <% field_set_tag do %> + # <%= field_set_tag do %> # <p><%= text_field_tag 'name' %></p> # <% end %> # # => <fieldset><p><input id="name" name="name" type="text" /></p></fieldset> # - # <% field_set_tag 'Your details' do %> + # <%= field_set_tag 'Your details' do %> # <p><%= text_field_tag 'name' %></p> # <% end %> # # => <fieldset><legend>Your details</legend><p><input id="name" name="name" type="text" /></p></fieldset> # - # <% field_set_tag nil, :class => 'format' do %> + # <%= field_set_tag nil, :class => 'format' do %> # <p><%= text_field_tag 'name' %></p> # <% end %> # # => <fieldset class="format"><p><input id="name" name="name" type="text" /></p></fieldset> diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index 8dab3094dd..b0a7718f22 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -1,5 +1,4 @@ require 'action_view/helpers/tag_helper' -require 'action_view/helpers/prototype_helper' module ActionView module Helpers @@ -71,7 +70,7 @@ module ActionView # # Instead of passing the content as an argument, you can also use a block # in which case, you pass your +html_options+ as the first parameter. - # <% javascript_tag :defer => 'defer' do -%> + # <%= javascript_tag :defer => 'defer' do -%> # alert('All is good') # <% end -%> def javascript_tag(content_or_options_with_block = nil, html_options = {}, &block) @@ -89,6 +88,93 @@ module ActionView def javascript_cdata_section(content) #:nodoc: "\n//#{cdata_section("\n#{content}\n//")}\n".html_safe end + + # Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the + # onclick handler. + # + # The first argument +name+ is used as the button's value or display text. + # + # The next arguments are optional and may include the javascript function definition and a hash of html_options. + # + # The +function+ argument can be omitted in favor of an +update_page+ + # block, which evaluates to a string when the template is rendered + # (instead of making an Ajax request first). + # + # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button" + # + # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil + # + # Examples: + # button_to_function "Greeting", "alert('Hello world!')" + # button_to_function "Delete", "if (confirm('Really?')) do_delete()" + # button_to_function "Details" do |page| + # page[:details].visual_effect :toggle_slide + # end + # button_to_function "Details", :class => "details_button" do |page| + # page[:details].visual_effect :toggle_slide + # end + def button_to_function(name, *args, &block) + html_options = args.extract_options!.symbolize_keys + + function = block_given? ? update_page(&block) : args[0] || '' + onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};" + + tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick)) + end + + # Returns a link of the given +name+ that will trigger a JavaScript +function+ using the + # onclick handler and return false after the fact. + # + # The first argument +name+ is used as the link text. + # + # The next arguments are optional and may include the javascript function definition and a hash of html_options. + # + # The +function+ argument can be omitted in favor of an +update_page+ + # block, which evaluates to a string when the template is rendered + # (instead of making an Ajax request first). + # + # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button" + # + # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil + # + # + # Examples: + # link_to_function "Greeting", "alert('Hello world!')" + # Produces: + # <a onclick="alert('Hello world!'); return false;" href="#">Greeting</a> + # + # link_to_function(image_tag("delete"), "if (confirm('Really?')) do_delete()") + # Produces: + # <a onclick="if (confirm('Really?')) do_delete(); return false;" href="#"> + # <img src="/images/delete.png?" alt="Delete"/> + # </a> + # + # link_to_function("Show me more", nil, :id => "more_link") do |page| + # page[:details].visual_effect :toggle_blind + # page[:more_link].replace_html "Show me less" + # end + # Produces: + # <a href="#" id="more_link" onclick="try { + # $("details").visualEffect("toggle_blind"); + # $("more_link").update("Show me less"); + # } + # catch (e) { + # alert('RJS error:\n\n' + e.toString()); + # alert('$(\"details\").visualEffect(\"toggle_blind\"); + # \n$(\"more_link\").update(\"Show me less\");'); + # throw e + # }; + # return false;">Show me more</a> + # + def link_to_function(name, *args, &block) + html_options = args.extract_options!.symbolize_keys + + function = block_given? ? update_page(&block) : args[0] || '' + onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function}; return false;" + href = html_options[:href] || '#' + + content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick)) + end end end end diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 46e41bc406..719b64b940 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -3,10 +3,24 @@ require 'active_support/core_ext/float/rounding' module ActionView module Helpers #:nodoc: + # Provides methods for converting numbers into formatted strings. # Methods are provided for phone numbers, currency, percentage, - # precision, positional notation, and file size. + # precision, positional notation, file size and pretty printing. + # + # Most methods expect a +number+ argument, and will return it + # unchanged if can't be converted into a valid number. module NumberHelper + + # Raised when argument +number+ param given to the helpers is invalid and + # the option :raise is set to +true+. + class InvalidNumberError < StandardError + attr_accessor :number + def initialize(number) + @number = number + end + end + # Formats a +number+ into a US phone number (e.g., (555) 123-9876). You can customize the format # in the +options+ hash. # @@ -30,6 +44,17 @@ module ActionView def number_to_phone(number, options = {}) return nil if number.nil? + begin + Float(number) + is_number_html_safe = true + rescue ArgumentError, TypeError + if options[:raise] + raise InvalidNumberError, number + else + is_number_html_safe = number.to_s.html_safe? + end + end + number = number.to_s.strip options = options.symbolize_keys area_code = options[:area_code] || nil @@ -46,7 +71,7 @@ module ActionView number.starts_with?('-') ? number.slice!(1..-1) : number end str << " x #{extension}" unless extension.blank? - str + is_number_html_safe ? str.html_safe : str end # Formats a +number+ into a currency string (e.g., $13.65). You can customize the format @@ -72,38 +97,42 @@ module ActionView # number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u") # # => 1234567890,50 £ def number_to_currency(number, options = {}) + return nil if number.nil? + options.symbolize_keys! - defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {} - currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :raise => true) rescue {} + defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {}) + currency = I18n.translate(:'number.currency.format', :locale => options[:locale], :default => {}) defaults = defaults.merge(currency) - precision = options[:precision] || defaults[:precision] - unit = options[:unit] || defaults[:unit] - separator = options[:separator] || defaults[:separator] - delimiter = options[:delimiter] || defaults[:delimiter] - format = options[:format] || defaults[:format] - separator = '' if precision == 0 + options = options.reverse_merge(defaults) - value = number_with_precision(number, - :precision => precision, - :delimiter => delimiter, - :separator => separator) + unit = options.delete(:unit) + format = options.delete(:format) - if value + begin + value = number_with_precision(number, options.merge(:raise => true)) format.gsub(/%n/, value).gsub(/%u/, unit).html_safe - else - number + rescue InvalidNumberError => e + if options[:raise] + raise + else + formatted_number = format.gsub(/%n/, e.number).gsub(/%u/, unit) + e.number.to_s.html_safe? ? formatted_number.html_safe : formatted_number + end end + end # Formats a +number+ as a percentage string (e.g., 65%). You can customize the # format in the +options+ hash. # # ==== Options - # * <tt>:precision</tt> - Sets the level of precision (defaults to 3). - # * <tt>:separator</tt> - Sets the separator between the units (defaults to "."). + # * <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+) # # ==== Examples # number_to_percentage(100) # => 100.000% @@ -111,21 +140,25 @@ module ActionView # number_to_percentage(1000, :delimiter => '.', :separator => ',') # => 1.000,000% # number_to_percentage(302.24398923423, :precision => 5) # => 302.24399% def number_to_percentage(number, options = {}) + return nil if number.nil? + options.symbolize_keys! - defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {} - percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :raise => true) rescue {} + defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {}) + percentage = I18n.translate(:'number.percentage.format', :locale => options[:locale], :default => {}) defaults = defaults.merge(percentage) - precision = options[:precision] || defaults[:precision] - separator = options[:separator] || defaults[:separator] - delimiter = options[:delimiter] || defaults[:delimiter] + options = options.reverse_merge(defaults) - value = number_with_precision(number, - :precision => precision, - :separator => separator, - :delimiter => delimiter) - value ? value + "%" : number + begin + "#{number_with_precision(number, options.merge(:raise => true))}%".html_safe + rescue InvalidNumberError => e + if options[:raise] + raise + else + e.number.to_s.html_safe? ? "#{e.number}%".html_safe : "#{e.number}%" + end + end end # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can @@ -133,7 +166,7 @@ module ActionView # # ==== Options # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ","). - # * <tt>:separator</tt> - Sets the separator between the units (defaults to "."). + # * <tt>:separator</tt> - Sets the separator between the fractional and integer digits (defaults to "."). # # ==== Examples # number_with_delimiter(12345678) # => 12,345,678 @@ -146,148 +179,186 @@ module ActionView # You can still use <tt>number_with_delimiter</tt> with the old API that accepts the # +delimiter+ as its optional second and the +separator+ as its # optional third parameter: - # number_with_delimiter(12345678, " ") # => 12 345.678 + # number_with_delimiter(12345678, " ") # => 12 345 678 # number_with_delimiter(12345678.05, ".", ",") # => 12.345.678,05 def number_with_delimiter(number, *args) options = args.extract_options! options.symbolize_keys! - defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {} + begin + Float(number) + rescue ArgumentError, TypeError + if options[:raise] + raise InvalidNumberError, number + else + return number + end + end + + defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {}) unless args.empty? ActiveSupport::Deprecation.warn('number_with_delimiter takes an option hash ' + 'instead of separate delimiter and precision arguments.', caller) - delimiter = args[0] || defaults[:delimiter] - separator = args[1] || defaults[:separator] + options[:delimiter] ||= args[0] if args[0] + options[:separator] ||= args[1] if args[1] end - delimiter ||= (options[:delimiter] || defaults[:delimiter]) - separator ||= (options[:separator] || defaults[:separator]) + options = options.reverse_merge(defaults) parts = number.to_s.split('.') - if parts[0] - parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}") - parts.join(separator) - else - number - end + parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}") + parts.join(options[:separator]).html_safe + end - # Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision of 2). + # 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>:precision</tt> - Sets the level of precision (defaults to 3). - # * <tt>:separator</tt> - Sets the separator between the units (defaults to "."). + # * <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+) # # ==== 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 - # number_with_precision(389.32314, :precision => 0) # => 389 + # number_with_precision(111.2345) # => 111.235 + # number_with_precision(111.2345, :precision => 2) # => 111.23 + # number_with_precision(13, :precision => 5) # => 13.00000 + # number_with_precision(389.32314, :precision => 0) # => 389 + # number_with_precision(111.2345, :significant => true) # => 111 + # number_with_precision(111.2345, :precision => 1, :significant => true) # => 100 + # number_with_precision(13, :precision => 5, :significant => true) # => 13.000 + # number_with_precision(13, :precision => 5, :significant => true, strip_insignificant_zeros => true) + # # => 13 + # number_with_precision(389.32314, :precision => 4, :significant => true) # => 389.3 # number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.') # # => 1.111,23 # # You can still use <tt>number_with_precision</tt> with the old API that accepts the # +precision+ as its optional second parameter: - # number_with_precision(number_with_precision(111.2345, 2) # => 111.23 + # number_with_precision(111.2345, 2) # => 111.23 def number_with_precision(number, *args) + options = args.extract_options! options.symbolize_keys! - defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {} - precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale], - :raise => true) rescue {} + number = begin + Float(number) + rescue ArgumentError, TypeError + if options[:raise] + raise InvalidNumberError, number + else + return number + end + end + + defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {}) + precision_defaults = I18n.translate(:'number.precision.format', :locale => options[:locale], :default => {}) defaults = defaults.merge(precision_defaults) + #Backwards compatibility unless args.empty? ActiveSupport::Deprecation.warn('number_with_precision takes an option hash ' + 'instead of a separate precision argument.', caller) - precision = args[0] || defaults[:precision] + options[:precision] ||= args[0] if args[0] end - precision ||= (options[:precision] || defaults[:precision]) - separator ||= (options[:separator] || defaults[:separator]) - delimiter ||= (options[:delimiter] || defaults[:delimiter]) + options = options.reverse_merge(defaults) # Allow the user to unset default values: Eg.: :significant => false + precision = options.delete :precision + significant = options.delete :significant + strip_insignificant_zeros = options.delete :strip_insignificant_zeros - begin - value = Float(number) - rescue ArgumentError, TypeError - value = nil + if significant and precision > 0 + digits = (Math.log10(number) + 1).floor + rounded_number = BigDecimal.new((number / 10 ** (digits - precision)).to_s).round.to_f * 10 ** (digits - precision) + precision = precision - digits + precision = precision > 0 ? precision : 0 #don't let it be negative + else + rounded_number = BigDecimal.new((number * (10 ** precision)).to_s).round.to_f / 10 ** precision end - - if value - rounded_number = BigDecimal.new((Float(number) * (10 ** precision)).to_s).round.to_f / 10 ** precision - number_with_delimiter("%01.#{precision}f" % rounded_number, - :separator => separator, - :delimiter => delimiter) + formatted_number = number_with_delimiter("%01.#{precision}f" % rounded_number, options) + if strip_insignificant_zeros + escaped_separator = Regexp.escape(options[:separator]) + formatted_number.sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '').html_safe else - number + formatted_number end + end STORAGE_UNITS = [:byte, :kb, :mb, :gb, :tb].freeze - # Formats the bytes in +size+ into a more understandable representation + # 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. This method returns nil if - # +size+ cannot be converted into a number. You can customize the + # 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. + # # ==== Options - # * <tt>:precision</tt> - Sets the level of precision (defaults to 1). - # * <tt>:separator</tt> - Sets the separator between the units (defaults to "."). + # * <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+) # ==== Examples # number_to_human_size(123) # => 123 Bytes - # number_to_human_size(1234) # => 1.2 KB + # number_to_human_size(1234) # => 1.21 KB # number_to_human_size(12345) # => 12.1 KB - # number_to_human_size(1234567) # => 1.2 MB - # number_to_human_size(1234567890) # => 1.1 GB - # number_to_human_size(1234567890123) # => 1.1 TB - # number_to_human_size(1234567, :precision => 2) # => 1.18 MB - # number_to_human_size(483989, :precision => 0) # => 473 KB - # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,18 MB + # number_to_human_size(1234567) # => 1.18 MB + # number_to_human_size(1234567890) # => 1.15 GB + # number_to_human_size(1234567890123) # => 1.12 TB + # number_to_human_size(1234567, :precision => 2) # => 1.2 MB + # number_to_human_size(483989, :precision => 2) # => 470 KB + # number_to_human_size(1234567, :precision => 2, :separator => ',') # => 1,2 MB # - # Zeros after the decimal point are always stripped out, regardless of the - # specified precision: - # helper.number_to_human_size(1234567890123, :precision => 5) # => "1.12283 TB" - # helper.number_to_human_size(524288000, :precision=>5) # => "500 MB" + # Unsignificant 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" # # You can still use <tt>number_to_human_size</tt> with the old API that accepts the # +precision+ as its optional second parameter: - # number_to_human_size(1234567, 2) # => 1.18 MB - # number_to_human_size(483989, 0) # => 473 KB + # number_to_human_size(1234567, 1) # => 1 MB + # number_to_human_size(483989, 2) # => 470 KB def number_to_human_size(number, *args) - return nil if number.nil? - options = args.extract_options! options.symbolize_keys! - defaults = I18n.translate(:'number.format', :locale => options[:locale], :raise => true) rescue {} - human = I18n.translate(:'number.human.format', :locale => options[:locale], :raise => true) rescue {} + number = begin + Float(number) + rescue ArgumentError, TypeError + if options[:raise] + raise InvalidNumberError, number + else + return number + end + end + + defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {}) + human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {}) defaults = defaults.merge(human) unless args.empty? ActiveSupport::Deprecation.warn('number_to_human_size takes an option hash ' + 'instead of a separate precision argument.', caller) - precision = args[0] || defaults[:precision] + options[:precision] ||= args[0] if args[0] end - precision ||= (options[:precision] || defaults[:precision]) - separator ||= (options[:separator] || defaults[:separator]) - delimiter ||= (options[:delimiter] || defaults[:delimiter]) + options = options.reverse_merge(defaults) + #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files + options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros) storage_units_format = I18n.translate(:'number.human.storage_units.format', :locale => options[:locale], :raise => true) if number.to_i < 1024 unit = I18n.translate(:'number.human.storage_units.units.byte', :locale => options[:locale], :count => number.to_i, :raise => true) - storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit) + storage_units_format.gsub(/%n/, number.to_i.to_s).gsub(/%u/, unit).html_safe else max_exp = STORAGE_UNITS.size - 1 - number = Float(number) exponent = (Math.log(number) / Math.log(1024)).to_i # Convert to base 1024 exponent = max_exp if exponent > max_exp # we need this to avoid overflow for the highest unit number /= 1024 ** exponent @@ -295,15 +366,138 @@ module ActionView unit_key = STORAGE_UNITS[exponent] unit = I18n.translate(:"number.human.storage_units.units.#{unit_key}", :locale => options[:locale], :count => number, :raise => true) - escaped_separator = Regexp.escape(separator) - formatted_number = number_with_precision(number, - :precision => precision, - :separator => separator, - :delimiter => delimiter - ).sub(/(#{escaped_separator})(\d*[1-9])?0+\z/, '\1\2').sub(/#{escaped_separator}\z/, '') - storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit) + formatted_number = number_with_precision(number, options) + storage_units_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).html_safe + end + end + + 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). + # + # 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 mililiters", etc). You may define + # a wide range of unit quantifiers, even fractional ones (centi, deci, mili, etc). + # + # ==== Options + # * <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 + # + # ==== Examples + # number_to_human(123) # => "123" + # number_to_human(1234) # => "1.23 Thousand" + # number_to_human(12345) # => "12.3 Thousand" + # number_to_human(1234567) # => "1.23 Million" + # number_to_human(1234567890) # => "1.23 Billion" + # number_to_human(1234567890123) # => "1.23 Trillion" + # number_to_human(1234567890123456) # => "1.23 Quadrillion" + # number_to_human(1234567890123456789) # => "1230 Quadrillion" + # number_to_human(489939, :precision => 2) # => "490 Thousand" + # number_to_human(489939, :precision => 4) # => "489.9 Thousand" + # number_to_human(1234567, :precision => 4, + # :significant => false) # => "1.2346 Million" + # number_to_human(1234567, :precision => 1, + # :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): + # number_to_human(12345012345, :significant_digits => 6) # => "12.345 Billion" + # number_to_human(500000000, :precision=>5) # => "500 Million" + # + # ==== Custom Unit Quantifiers + # + # You can also use your own custom unit quantifiers: + # number_to_human(500000, :units => {:unit => "ml", :thousand => "lt"}) # => "500 lt" + # + # If in your I18n locale you have: + # distance: + # centi: + # one: "centimeter" + # other: "centimeters" + # unit: + # one: "meter" + # other: "meters" + # thousand: + # one: "kilometer" + # other: "kilometers" + # billion: "gazilion-distance" + # + # Then you could do: + # + # number_to_human(543934, :units => :distance) # => "544 kilometers" + # number_to_human(54393498, :units => :distance) # => "54400 kilometers" + # number_to_human(54393498000, :units => :distance) # => "54.4 gazilion-distance" + # number_to_human(343, :units => :distance, :precision => 1) # => "300 meters" + # number_to_human(1, :units => :distance) # => "1 meter" + # number_to_human(0.34, :units => :distance) # => "34 centimeters" + # + def number_to_human(number, options = {}) + options.symbolize_keys! + + number = begin + Float(number) + rescue ArgumentError, TypeError + if options[:raise] + raise InvalidNumberError, number + else + return number + end end + + defaults = I18n.translate(:'number.format', :locale => options[:locale], :default => {}) + human = I18n.translate(:'number.human.format', :locale => options[:locale], :default => {}) + defaults = defaults.merge(human) + + options = options.reverse_merge(defaults) + #for backwards compatibility with those that didn't add strip_insignificant_zeros to their locale files + options[:strip_insignificant_zeros] = true if not options.key?(:strip_insignificant_zeros) + + units = options.delete :units + unit_exponents = case units + when Hash + units + when String, Symbol + I18n.translate(:"#{units}", :locale => options[:locale], :raise => true) + when nil + I18n.translate(:"number.human.decimal_units.units", :locale => options[:locale], :raise => true) + else + raise ArgumentError, ":units must be a Hash or String translation scope." + end.keys.map{|e_name| DECIMAL_UNITS.invert[e_name] }.sort_by{|e| -e} + + number_exponent = Math.log10(number).floor + display_exponent = unit_exponents.find{|e| number_exponent >= e } + number /= 10 ** display_exponent + + unit = case units + when Hash + units[DECIMAL_UNITS[display_exponent]] + when String, Symbol + I18n.translate(:"#{units}.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i) + else + I18n.translate(:"number.human.decimal_units.units.#{DECIMAL_UNITS[display_exponent]}", :locale => options[:locale], :count => number.to_i) + end + + decimal_format = options[:format] || I18n.translate(:'number.human.decimal_units.format', :locale => options[:locale], :default => "%n %u") + formatted_number = number_with_precision(number, options) + decimal_format.gsub(/%n/, formatted_number).gsub(/%u/, unit).strip.html_safe end + end end end diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index be49b5cc28..ccdc8181db 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -35,7 +35,7 @@ module ActionView # # ...through a form... # - # <% form_remote_tag :url => '/shipping' do -%> + # <%= form_remote_tag :url => '/shipping' do -%> # <div><%= submit_tag 'Recalculate Shipping' %></div> # <% end -%> # @@ -102,39 +102,6 @@ module ActionView :form, :with, :update, :script, :type ]).merge(CALLBACKS) end - # Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the - # onclick handler. - # - # The first argument +name+ is used as the button's value or display text. - # - # The next arguments are optional and may include the javascript function definition and a hash of html_options. - # - # The +function+ argument can be omitted in favor of an +update_page+ - # block, which evaluates to a string when the template is rendered - # (instead of making an Ajax request first). - # - # The +html_options+ will accept a hash of html attributes for the link tag. Some examples are :class => "nav_button", :id => "articles_nav_button" - # - # Note: if you choose to specify the javascript function in a block, but would like to pass html_options, set the +function+ parameter to nil - # - # Examples: - # button_to_function "Greeting", "alert('Hello world!')" - # button_to_function "Delete", "if (confirm('Really?')) do_delete()" - # button_to_function "Details" do |page| - # page[:details].visual_effect :toggle_slide - # end - # button_to_function "Details", :class => "details_button" do |page| - # page[:details].visual_effect :toggle_slide - # end - def button_to_function(name, *args, &block) - html_options = args.extract_options!.symbolize_keys - - function = block_given? ? update_page(&block) : args[0] || '' - onclick = "#{"#{html_options[:onclick]}; " if html_options[:onclick]}#{function};" - - tag(:input, html_options.merge(:type => 'button', :value => name, :onclick => onclick)) - end - # Returns the JavaScript needed for a remote function. # Takes the same arguments as link_to_remote. # @@ -180,13 +147,10 @@ module ActionView # #include_helpers_from_context has nothing to overwrite. class JavaScriptGenerator #:nodoc: def initialize(context, &block) #:nodoc: - context._evaluate_assigns_and_ivars @context, @lines = context, [] - @context.update_details(:formats => [:js, :html]) do - include_helpers_from_context - @context.with_output_buffer(@lines) do - @context.instance_exec(self, &block) - end + include_helpers_from_context + @context.with_output_buffer(@lines) do + @context.instance_exec(self, &block) end end @@ -616,7 +580,7 @@ module ActionView # page.hide 'spinner' # end def update_page(&block) - JavaScriptGenerator.new(@template, &block).to_s.html_safe + JavaScriptGenerator.new(view_context, &block).to_s.html_safe end # Works like update_page but wraps the generated JavaScript in a <script> @@ -690,6 +654,10 @@ module ActionView @generator << root if root end + def is_a?(klass) + klass == JavaScriptProxy + end + private def method_missing(method, *arguments, &block) if method.to_s =~ /(.*)=$/ @@ -883,5 +851,3 @@ module ActionView end end end - -require 'action_view/helpers/javascript_helper' diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index 31411dc08a..a9cf15f418 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -4,7 +4,7 @@ module ActionView # Produces a wrapper DIV element with id and class parameters that # relate to the specified Active Record object. Usage example: # - # <% div_for(@person, :class => "foo") do %> + # <%= div_for(@person, :class => "foo") do %> # <%=h @person.name %> # <% end %> # @@ -19,7 +19,7 @@ module ActionView # content_tag_for creates an HTML element with id and class parameters # that relate to the specified Active Record object. For example: # - # <% content_tag_for(:tr, @person) do %> + # <%= content_tag_for(:tr, @person) do %> # <td><%=h @person.first_name %></td> # <td><%=h @person.last_name %></td> # <% end %> @@ -31,7 +31,7 @@ module ActionView # # If you require the HTML id attribute to have a prefix, you can specify it: # - # <% content_tag_for(:tr, @person, :foo) do %> ... + # <%= content_tag_for(:tr, @person, :foo) do %> ... # # produces: # @@ -41,7 +41,7 @@ module ActionView # additional HTML attributes. If you specify a <tt>:class</tt> value, it will be combined # with the default class name for your object. For example: # - # <% content_tag_for(:li, @person, :class => "bar") %>... + # <%= content_tag_for(:li, @person, :class => "bar") %>... # # produces: # diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index d9d2588a2a..9b4cacd4d7 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -65,7 +65,7 @@ module ActionView # content_tag("select", options, :multiple => true) # # => <select multiple="multiple">...options...</select> # - # <% content_tag :div, :class => "strong" do -%> + # <%= content_tag :div, :class => "strong" do -%> # Hello world! # <% end -%> # # => <div class="strong">Hello world!</div> @@ -109,7 +109,7 @@ module ActionView def content_tag_string(name, content, options, escape = true) tag_options = tag_options(options, escape) if options - ("<#{name}#{tag_options}>".html_safe << content.to_s).safe_concat("</#{name}>") + "<#{name}#{tag_options}>#{ERB::Util.h(content)}</#{name}>".html_safe end def tag_options(options, escape = true) diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index b19a9754f4..27be1690dd 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -17,7 +17,7 @@ module ActionView # concat "hello" # # is the equivalent of <%= "hello" %> # - # if (logged_in == true): + # if logged_in # concat "Logged in!" # else # concat link_to('login', :action => login) @@ -29,7 +29,7 @@ module ActionView end def safe_concat(string) - output_buffer.safe_concat(string) + output_buffer.respond_to?(:safe_concat) ? output_buffer.safe_concat(string) : concat(string) end # Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt> @@ -415,7 +415,7 @@ module ActionView # {:first => 'Emily', :middle => 'Shannon', :maiden => 'Pike', :last => 'Hicks'}, # {:first => 'June', :middle => 'Dae', :last => 'Jones'}] # <% @items.each do |item| %> - # <tr class="<%= cycle("even", "odd", :name => "row_class") -%>"> + # <tr class="<%= cycle("odd", "even", :name => "row_class") -%>"> # <td> # <% item.values.each do |value| %> # <%# Create a named cycle "colors" %> @@ -576,7 +576,7 @@ module ActionView # each email is yielded and the result is used as the link text. def auto_link_email_addresses(text, html_options = {}) body = text.dup - text.gsub(/([\w\.!#\$%\-+.]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do + text.gsub(/([\w\.!#\$%\-+]+@[A-Za-z0-9\-]+(\.[A-Za-z0-9\-]+)+)/) do text = $1 if body.match(/<a\b[^>]*>(.*)(#{Regexp.escape(text)})(.*)<\/a>/) diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb index 8a89ee58a0..457944dbb6 100644 --- a/actionpack/lib/action_view/helpers/translation_helper.rb +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -13,7 +13,7 @@ module ActionView def translate(key, options = {}) options[:raise] = true translation = I18n.translate(scope_key_by_partial(key), options) - translation.is_a?(Array) ? translation.map { |entry| entry.html_safe } : translation.html_safe + (translation.respond_to?(:join) ? translation.join : translation).html_safe rescue I18n::MissingTranslationData => e keys = I18n.normalize_keys(e.locale, e.key, e.options[:scope]) content_tag('span', keys.join(', '), :class => 'translation_missing') @@ -29,9 +29,10 @@ module ActionView private def scope_key_by_partial(key) - if (key.respond_to?(:join) ? key.join : key.to_s).first == "." + strkey = key.respond_to?(:join) ? key.join : key.to_s + if strkey.first == "." if @_virtual_path - @_virtual_path.gsub(%r{/_?}, ".") + key.to_s + @_virtual_path.gsub(%r{/_?}, ".") + strkey else raise "Cannot use t(#{key.inspect}) shortcut because path is not available" end diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 148f2868e9..b23d5fcb68 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -1,6 +1,7 @@ require 'action_view/helpers/javascript_helper' require 'active_support/core_ext/array/access' require 'active_support/core_ext/hash/keys' +require 'action_dispatch' module ActionView module Helpers #:nodoc: @@ -9,6 +10,9 @@ module ActionView # This allows you to use the same format for links in views # and controllers. module UrlHelper + extend ActiveSupport::Concern + + include ActionDispatch::Routing::UrlFor include JavaScriptHelper # Need to map default url options to controller one. @@ -16,6 +20,10 @@ module ActionView controller.send(:default_url_options, *args) end + def url_options + controller.url_options + end + # Returns the URL for the set of +options+ provided. This takes the # same options as +url_for+ in Action Controller (see the # documentation for <tt>ActionController::Base#url_for</tt>). Note that by default @@ -162,7 +170,7 @@ module ActionView # # You can use a block as well if your link target is hard to fit into the name parameter. ERb example: # - # <% link_to(@profile) do %> + # <%= link_to(@profile) do %> # <strong><%= @profile.name %></strong> -- <span>Check it out!</span> # <% end %> # # => <a href="/profiles/1"> @@ -206,7 +214,7 @@ module ActionView if block_given? options = args.first || {} html_options = args.second - safe_concat(link_to(capture(&block), options, html_options)) + link_to(capture(&block), options, html_options) else name = args[0] options = args[1] || {} @@ -224,7 +232,7 @@ module ActionView end href_attr = "href=\"#{url}\"" unless href - ("<a #{href_attr}#{tag_options}>".html_safe << (name || url)).safe_concat("</a>") + "<a #{href_attr}#{tag_options}>#{ERB::Util.h(name || url)}</a>".html_safe end end @@ -578,8 +586,6 @@ module ActionView add_confirm_to_attributes!(html_options, confirm) if confirm add_method_to_attributes!(html_options, method) if method - html_options["data-url"] = options[:url] if options.is_a?(Hash) && options[:url] - html_options end |