diff options
Diffstat (limited to 'actionpack/lib')
47 files changed, 1373 insertions, 1130 deletions
diff --git a/actionpack/lib/action_controller/assertions/response_assertions.rb b/actionpack/lib/action_controller/assertions/response_assertions.rb index cb36405700..765225ae24 100644 --- a/actionpack/lib/action_controller/assertions/response_assertions.rb +++ b/actionpack/lib/action_controller/assertions/response_assertions.rb @@ -87,13 +87,13 @@ module ActionController # def assert_template(expected = nil, message=nil) clean_backtrace do - rendered = expected ? @response.rendered_file(!expected.include?('/')) : @response.rendered_file + rendered = @response.rendered_template msg = build_message(message, "expecting <?> but rendering with <?>", expected, rendered) assert_block(msg) do if expected.nil? - !@response.rendered_with_file? + @response.rendered_template.nil? else - rendered.match(expected) + rendered.to_s.match(expected) end end end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index df94f78f18..5f4a38dac0 100755 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -354,6 +354,15 @@ module ActionController #:nodoc: class_inheritable_accessor :allow_forgery_protection self.allow_forgery_protection = true + # If you are deploying to a subdirectory, you will need to set + # <tt>config.action_controller.relative_url_root</tt> + # This defaults to ENV['RAILS_RELATIVE_URL_ROOT'] + cattr_writer :relative_url_root + + def self.relative_url_root + @@relative_url_root || ENV['RAILS_RELATIVE_URL_ROOT'] + end + # Holds the request object that's primarily used to get environment variables through access like # <tt>request.env["REQUEST_URI"]</tt>. attr_internal :request @@ -519,6 +528,8 @@ module ActionController #:nodoc: public # Extracts the action_name from the request parameters and performs that action. def process(request, response, method = :perform_action, *arguments) #:nodoc: + response.request = request + initialize_template_class(response) assign_shortcuts(request, response) initialize_current_url @@ -529,8 +540,6 @@ module ActionController #:nodoc: send(method, *arguments) assign_default_content_type_and_charset - - response.request = request response.prepare! unless component_request? response ensure @@ -968,6 +977,17 @@ module ActionController #:nodoc: render :nothing => true, :status => status end + # Sets the Last-Modified response header. Returns 304 Not Modified if the + # If-Modified-Since request header is <= last modified. + def last_modified!(utc_time) + head(:not_modified) if response.last_modified!(utc_time) + end + + # Sets the ETag response header. Returns 304 Not Modified if the + # If-None-Match request header matches. + def etag!(etag) + head(:not_modified) if response.etag!(etag) + end # Clears the rendered results, allowing for another render to be performed. def erase_render_results #:nodoc: @@ -1155,7 +1175,7 @@ module ActionController #:nodoc: def log_processing if logger && logger.info? - logger.info "\n\nProcessing #{controller_class_name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]" + logger.info "\n\nProcessing #{self.class.name}\##{action_name} (for #{request_origin}) [#{request.method.to_s.upcase}]" logger.info " Session ID: #{@_session.session_id}" if @_session and @_session.respond_to?(:session_id) logger.info " Parameters: #{respond_to?(:filter_parameters) ? filter_parameters(params).inspect : params.inspect}" end @@ -1250,6 +1270,8 @@ module ActionController #:nodoc: def template_exempt_from_layout?(template_name = default_template_name) template_name = @template.pick_template(template_name).to_s if @template @@exempt_from_layout.any? { |ext| template_name =~ ext } + rescue ActionView::MissingTemplate + false end def default_template_name(action_name = self.action_name) diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb index 45946421fc..e9b434dd25 100644 --- a/actionpack/lib/action_controller/caching/fragments.rb +++ b/actionpack/lib/action_controller/caching/fragments.rb @@ -60,10 +60,8 @@ module ActionController #:nodoc: ActiveSupport::Cache.expand_cache_key(key.is_a?(Hash) ? url_for(key).split("://").last : key, :views) end - def fragment_for(block, name = {}, options = nil) #:nodoc: + def fragment_for(buffer, name = {}, options = nil, &block) #:nodoc: if perform_caching - buffer = yield - if cache = read_fragment(name, options) buffer.concat(cache) else diff --git a/actionpack/lib/action_controller/cookies.rb b/actionpack/lib/action_controller/cookies.rb index 51bc4ad23d..0428f2a23d 100644 --- a/actionpack/lib/action_controller/cookies.rb +++ b/actionpack/lib/action_controller/cookies.rb @@ -23,7 +23,7 @@ module ActionController #:nodoc: # cookies.delete :user_name # # Please note that if you specify a :domain when setting a cookie, you must also specify the domain when deleting the cookie: - # + # # cookies[:key] = { # :value => 'a yummy cookie', # :expires => 1.year.from_now, diff --git a/actionpack/lib/action_controller/filters.rb b/actionpack/lib/action_controller/filters.rb index fc63890d13..10dc0cc45b 100644 --- a/actionpack/lib/action_controller/filters.rb +++ b/actionpack/lib/action_controller/filters.rb @@ -569,21 +569,13 @@ module ActionController #:nodoc: # Returns all the before filters for this class and all its ancestors. # This method returns the actual filter that was assigned in the controller to maintain existing functionality. def before_filters #:nodoc: - filters = [] - filter_chain.each do |filter| - filters << filter.method if filter.before? - end - filters + filter_chain.select(&:before?).map(&:method) end # Returns all the after filters for this class and all its ancestors. # This method returns the actual filter that was assigned in the controller to maintain existing functionality. def after_filters #:nodoc: - filters = [] - filter_chain.each do |filter| - filters << filter.method if filter.after? - end - filters + filter_chain.select(&:after?).map(&:method) end end diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb index 3117fe2da5..7e0a6b091e 100644 --- a/actionpack/lib/action_controller/rack_process.rb +++ b/actionpack/lib/action_controller/rack_process.rb @@ -24,7 +24,7 @@ module ActionController #:nodoc: super() end - %w[ AUTH_TYPE CONTENT_TYPE GATEWAY_INTERFACE PATH_INFO + %w[ AUTH_TYPE GATEWAY_INTERFACE PATH_INFO PATH_TRANSLATED QUERY_STRING REMOTE_HOST REMOTE_IDENT REMOTE_USER SCRIPT_NAME SERVER_NAME SERVER_PROTOCOL @@ -98,10 +98,6 @@ module ActionController #:nodoc: @env['REMOTE_ADDR'] end - def request_method - @env['REQUEST_METHOD'].downcase.to_sym - end - def server_port @env['SERVER_PORT'].to_i end @@ -250,7 +246,7 @@ end_msg headers['Content-Language'] = options.delete('language') if options['language'] headers['Expires'] = options.delete('expires') if options['expires'] - @status = options['Status'] || "200 OK" + @status = options.delete('Status') || "200 OK" # Convert 'cookie' header to 'Set-Cookie' headers. # Because Set-Cookie header can appear more the once in the response body, diff --git a/actionpack/lib/action_controller/request.rb b/actionpack/lib/action_controller/request.rb index c42f113d2c..c55788a531 100755 --- a/actionpack/lib/action_controller/request.rb +++ b/actionpack/lib/action_controller/request.rb @@ -3,13 +3,16 @@ require 'stringio' require 'strscan' module ActionController - # HTTP methods which are accepted by default. + # HTTP methods which are accepted by default. ACCEPTED_HTTP_METHODS = Set.new(%w( get head put post delete options )) # CgiRequest and TestRequest provide concrete implementations. class AbstractRequest - cattr_accessor :relative_url_root - remove_method :relative_url_root + def self.relative_url_root=(*args) + ActiveSupport::Deprecation.warn( + "ActionController::AbstractRequest.relative_url_root= has been renamed." + + "You can now set it with config.action_controller.relative_url_root=", caller) + end # The hash of environment variables for this request, # such as { 'RAILS_ENV' => 'production' }. @@ -111,14 +114,14 @@ module ActionController end end end - - + + # Sets the format by string extension, which can be used to force custom formats that are not controlled by the extension. # Example: # # class ApplicationController < ActionController::Base # before_filter :adjust_format_for_iphone - # + # # private # def adjust_format_for_iphone # request.format = :iphone if request.env["HTTP_USER_AGENT"][/iPhone/] @@ -303,26 +306,10 @@ EOM path = (uri = request_uri) ? uri.split('?').first.to_s : '' # Cut off the path to the installation directory if given - path.sub!(%r/^#{relative_url_root}/, '') - path || '' - end - - # Returns the path minus the web server relative installation directory. - # This can be set with the environment variable RAILS_RELATIVE_URL_ROOT. - # It can be automatically extracted for Apache setups. If the server is not - # Apache, this method returns an empty string. - def relative_url_root - @@relative_url_root ||= case - when @env["RAILS_RELATIVE_URL_ROOT"] - @env["RAILS_RELATIVE_URL_ROOT"] - when server_software == 'apache' - @env["SCRIPT_NAME"].to_s.sub(/\/dispatch\.(fcgi|rb|cgi)$/, '') - else - '' - end + path.sub!(%r/^#{ActionController::Base.relative_url_root}/, '') + path || '' end - # Read the request body. This is useful for web services that need to # work with raw requests directly. def raw_post @@ -343,15 +330,15 @@ EOM @symbolized_path_parameters = @parameters = nil end - # The same as <tt>path_parameters</tt> with explicitly symbolized keys - def symbolized_path_parameters + # The same as <tt>path_parameters</tt> with explicitly symbolized keys + def symbolized_path_parameters @symbolized_path_parameters ||= path_parameters.symbolize_keys end # Returns a hash with the parameters used to form the path of the request. # Returned hash keys are strings. See <tt>symbolized_path_parameters</tt> for symbolized keys. # - # Example: + # Example: # # {'action' => 'my_action', 'controller' => 'my_controller'} def path_parameters diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb index 163ed87fbb..482ac7d7a4 100644 --- a/actionpack/lib/action_controller/rescue.rb +++ b/actionpack/lib/action_controller/rescue.rb @@ -112,19 +112,23 @@ module ActionController #:nodoc: protected # Exception handler called when the performance of an action raises an exception. def rescue_action(exception) - log_error(exception) if logger - erase_results if performed? + if handler_for_rescue(exception) + rescue_action_with_handler(exception) + else + log_error(exception) if logger + erase_results if performed? - # Let the exception alter the response if it wants. - # For example, MethodNotAllowed sets the Allow header. - if exception.respond_to?(:handle_response!) - exception.handle_response!(response) - end + # Let the exception alter the response if it wants. + # For example, MethodNotAllowed sets the Allow header. + if exception.respond_to?(:handle_response!) + exception.handle_response!(response) + end - if consider_all_requests_local || local_request? - rescue_action_locally(exception) - else - rescue_action_in_public(exception) + if consider_all_requests_local || local_request? + rescue_action_locally(exception) + else + rescue_action_in_public(exception) + end end end @@ -200,7 +204,7 @@ module ActionController #:nodoc: def perform_action_with_rescue #:nodoc: perform_action_without_rescue rescue Exception => exception - rescue_action_with_handler(exception) || rescue_action(exception) + rescue_action(exception) end def rescues_path(template_name) diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb index b11aa5625b..0614b9a4d9 100644 --- a/actionpack/lib/action_controller/resources.rb +++ b/actionpack/lib/action_controller/resources.rb @@ -307,13 +307,13 @@ module ActionController # map.resources :tags, :path_prefix => '/toys/:toy_id', :name_prefix => 'toy_' # # You may also use <tt>:name_prefix</tt> to override the generic named routes in a nested resource: - # + # # map.resources :articles do |article| # article.resources :comments, :name_prefix => nil - # end - # + # end + # # This will yield named resources like so: - # + # # comments_url(@article) # comment_url(@article, @comment) # @@ -559,6 +559,7 @@ module ActionController def action_options_for(action, resource, method = nil) default_options = { :action => action.to_s } require_id = !resource.kind_of?(SingletonResource) + case default_options[:action] when "index", "new"; default_options.merge(add_conditions_for(resource.conditions, method || :get)).merge(resource.requirements) when "create"; default_options.merge(add_conditions_for(resource.conditions, method || :post)).merge(resource.requirements) diff --git a/actionpack/lib/action_controller/response.rb b/actionpack/lib/action_controller/response.rb index 8f2672425f..de7425230c 100755 --- a/actionpack/lib/action_controller/response.rb +++ b/actionpack/lib/action_controller/response.rb @@ -83,20 +83,48 @@ module ActionController # :nodoc: set_content_length! end + # Sets the Last-Modified response header. Returns whether it's older than + # the If-Modified-Since request header. + def last_modified!(utc_time) + headers['Last-Modified'] ||= utc_time.httpdate + if request && since = request.headers['HTTP_IF_MODIFIED_SINCE'] + utc_time <= Time.rfc2822(since) + end + end + + # Sets the ETag response header. Returns whether it matches the + # If-None-Match request header. + def etag!(tag) + headers['ETag'] ||= %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(tag))}") + if request && request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag'] + true + end + end private def handle_conditional_get! - if body.is_a?(String) && (headers['Status'] ? headers['Status'][0..2] == '200' : true) && !body.empty? - self.headers['ETag'] ||= %("#{Digest::MD5.hexdigest(body)}") - self.headers['Cache-Control'] = 'private, max-age=0, must-revalidate' if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control'] + if nonempty_ok_response? + set_conditional_cache_control! - if request.headers['HTTP_IF_NONE_MATCH'] == headers['ETag'] - self.headers['Status'] = '304 Not Modified' + if etag!(body) + headers['Status'] = '304 Not Modified' self.body = '' end end end + def nonempty_ok_response? + status = headers['Status'] + ok = !status || status[0..2] == '200' + ok && body.is_a?(String) && !body.empty? + end + + def set_conditional_cache_control! + if headers['Cache-Control'] == DEFAULT_HEADERS['Cache-Control'] + headers['Cache-Control'] = 'private, max-age=0, must-revalidate' + end + end + def convert_content_type! if content_type = headers.delete("Content-Type") self.headers["type"] = content_type diff --git a/actionpack/lib/action_controller/routing/builder.rb b/actionpack/lib/action_controller/routing/builder.rb index b8323847fd..912999d845 100644 --- a/actionpack/lib/action_controller/routing/builder.rb +++ b/actionpack/lib/action_controller/routing/builder.rb @@ -76,6 +76,8 @@ module ActionController defaults = (options.delete(:defaults) || {}).dup conditions = (options.delete(:conditions) || {}).dup + validate_route_conditions(conditions) + path_keys = segments.collect { |segment| segment.key if segment.respond_to?(:key) }.compact options.each do |key, value| hash = (path_keys.include?(key) && ! value.is_a?(Regexp)) ? defaults : requirements @@ -198,6 +200,19 @@ module ActionController route end + + private + def validate_route_conditions(conditions) + if method = conditions[:method] + if method == :head + raise ArgumentError, "HTTP method HEAD is invalid in route conditions. Rails processes HEAD requests the same as GETs, returning just the response headers" + end + + unless HTTP_METHODS.include?(method.to_sym) + raise ArgumentError, "Invalid HTTP method specified in route conditions: #{conditions.inspect}" + end + end + end end end end diff --git a/actionpack/lib/action_controller/routing/optimisations.rb b/actionpack/lib/action_controller/routing/optimisations.rb index cd4a423e6b..4b70ea13f2 100644 --- a/actionpack/lib/action_controller/routing/optimisations.rb +++ b/actionpack/lib/action_controller/routing/optimisations.rb @@ -76,7 +76,7 @@ module ActionController elements << '#{request.host_with_port}' end - elements << '#{request.relative_url_root if request.relative_url_root}' + elements << '#{ActionController::Base.relative_url_root if ActionController::Base.relative_url_root}' # The last entry in <tt>route.segments</tt> appears to *always* be a # 'divider segment' for '/' but we have assertions to ensure that diff --git a/actionpack/lib/action_controller/streaming.rb b/actionpack/lib/action_controller/streaming.rb index b944b52b98..333fb61b45 100644 --- a/actionpack/lib/action_controller/streaming.rb +++ b/actionpack/lib/action_controller/streaming.rb @@ -41,7 +41,7 @@ module ActionController #:nodoc: # only available with Lighttpd/Apache2 and specific modules installed and activated. Since this # uses the web server to send the file, this may lower memory consumption on your server and # it will not block your application for further requests. - # See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile and + # See http://blog.lighttpd.net/articles/2006/07/02/x-sendfile and # http://tn123.ath.cx/mod_xsendfile/ for details. Defaults to +false+. # # The default Content-Type and Content-Disposition headers are diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index d50416272a..d0f4f3c71b 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -100,7 +100,7 @@ module ActionController @@controller_class = nil class << self - # Sets the controller class name. Useful if the name can't be inferred from test class. + # Sets the controller class name. Useful if the name can't be inferred from test class. # Expects +controller_class+ as a constant. Example: <tt>tests WidgetController</tt>. def tests(controller_class) self.controller_class = controller_class diff --git a/actionpack/lib/action_controller/test_process.rb b/actionpack/lib/action_controller/test_process.rb index b9cf1e2bb0..66675aaa13 100644 --- a/actionpack/lib/action_controller/test_process.rb +++ b/actionpack/lib/action_controller/test_process.rb @@ -171,7 +171,7 @@ module ActionController #:nodoc: # Was the response successful? def success? - response_code == 200 + (200..299).include?(response_code) end # Was the URL not found? @@ -205,17 +205,10 @@ module ActionController #:nodoc: p.match(redirect_url) != nil end - # Returns the template path of the file which was used to - # render this response (or nil) - def rendered_file(with_controller = false) - if template.first_render - template.first_render.to_s - end - end - - # Was this template rendered by a file? - def rendered_with_file? - !rendered_file.nil? + # Returns the template of the file which was used to + # render this response (or nil) + def rendered_template + template._first_render end # A shortcut to the flash. Returns an empty hash if no session flash exists. diff --git a/actionpack/lib/action_controller/url_rewriter.rb b/actionpack/lib/action_controller/url_rewriter.rb index 457318472c..c2def7a84b 100644 --- a/actionpack/lib/action_controller/url_rewriter.rb +++ b/actionpack/lib/action_controller/url_rewriter.rb @@ -114,7 +114,7 @@ module ActionController # * <tt>:port</tt> - Optionally specify the port to connect to. # * <tt>:anchor</tt> - An anchor name to be appended to the path. # * <tt>:skip_relative_url_root</tt> - If true, the url is not constructed using the - # +relative_url_root+ set in ActionController::AbstractRequest.relative_url_root. + # +relative_url_root+ set in ActionController::Base.relative_url_root. # * <tt>:trailing_slash</tt> - If true, adds a trailing slash, as in "/archive/2009/" # # Any other key (<tt>:controller</tt>, <tt>:action</tt>, etc.) given to @@ -144,7 +144,7 @@ module ActionController [:protocol, :host, :port, :skip_relative_url_root].each { |k| options.delete(k) } end trailing_slash = options.delete(:trailing_slash) if options.key?(:trailing_slash) - url << ActionController::AbstractRequest.relative_url_root.to_s unless options[:skip_relative_url_root] + url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root] anchor = "##{CGI.escape options.delete(:anchor).to_param.to_s}" if options[:anchor] generated = Routing::Routes.generate(options, {}) url << (trailing_slash ? generated.sub(/\?|\z/) { "/" + $& } : generated) @@ -185,7 +185,7 @@ module ActionController end path = rewrite_path(options) - rewritten_url << @request.relative_url_root.to_s unless options[:skip_relative_url_root] + rewritten_url << ActionController::Base.relative_url_root.to_s unless options[:skip_relative_url_root] rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path) rewritten_url << "##{options[:anchor]}" if options[:anchor] diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 9ab615c7a5..dd555b3792 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -34,6 +34,10 @@ require 'action_view/base' require 'action_view/partials' require 'action_view/template_error' +I18n.backend.populate do + require 'action_view/locale/en-US.rb' +end + ActionView::Base.class_eval do include ActionView::Partials diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index fb82443060..bdcb1dc246 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -159,11 +159,11 @@ module ActionView #:nodoc: class Base include ERB::Util - attr_accessor :base_path, :assigns, :template_extension, :first_render + attr_accessor :base_path, :assigns, :template_extension attr_accessor :controller + attr_accessor :_first_render, :_last_render attr_writer :template_format - attr_accessor :current_render_extension attr_accessor :output_buffer @@ -171,13 +171,16 @@ module ActionView #:nodoc: delegate :erb_trim_mode=, :to => 'ActionView::TemplateHandlers::ERB' end - # Specify whether templates should be cached. Otherwise the file we be read everytime it is accessed. - @@cache_template_loading = false - cattr_accessor :cache_template_loading + def self.cache_template_loading=(*args) + ActiveSupport::Deprecation.warn( + "config.action_view.cache_template_loading option has been deprecated" + + "and has no effect. Please remove it from your config files.", caller) + end def self.cache_template_extensions=(*args) - ActiveSupport::Deprecation.warn("config.action_view.cache_template_extensions option has been deprecated and has no effect. " << - "Please remove it from your config files.", caller) + ActiveSupport::Deprecation.warn( + "config.action_view.cache_template_extensions option has been" + + "deprecated and has no effect. Please remove it from your config files.", caller) end # Specify whether RJS responses should be wrapped in a try/catch block @@ -199,10 +202,6 @@ module ActionView #:nodoc: end include CompiledTemplates - # Cache public asset paths - cattr_reader :computed_public_paths - @@computed_public_paths = {} - def self.helper_modules #:nodoc: helpers = [] Dir.entries(File.expand_path("#{File.dirname(__FILE__)}/helpers")).sort.each do |file| @@ -313,7 +312,7 @@ module ActionView #:nodoc: template elsif template = self.view_paths[template_file_name] template - elsif first_render && template = self.view_paths["#{template_file_name}.#{first_render.extension}"] + elsif _first_render && template = self.view_paths["#{template_file_name}.#{_first_render.format_and_extension}"] template elsif template_format == :js && template = self.view_paths["#{template_file_name}.html"] @template_format = :html @@ -324,8 +323,8 @@ module ActionView #:nodoc: if self.class.warn_cache_misses && logger = ActionController::Base.logger logger.debug "[PERFORMANCE] Rendering a template that was " + "not found in view path. Templates outside the view path are " + - "not cached and result in expensive disk operations. Move this " + - "file into #{view_paths.join(':')} or add the folder to your " + + "not cached and result in expensive disk operations. Move this " + + "file into #{view_paths.join(':')} or add the folder to your " + "view path list" end @@ -333,6 +332,9 @@ module ActionView #:nodoc: end end + extend ActiveSupport::Memoizable + memoize :pick_template + private # Renders the template present at <tt>template_path</tt>. The hash in <tt>local_assigns</tt> # is made available as local variables. @@ -382,8 +384,14 @@ module ActionView #:nodoc: @assigns.each { |key, value| instance_variable_set("@#{key}", value) } end - def execute(template, local_assigns = {}) - send(template.method(local_assigns), local_assigns) do |*names| + def set_controller_content_type(content_type) + if controller.respond_to?(:response) + controller.response.content_type ||= content_type + end + end + + def execute(method, local_assigns = {}) + send(method, local_assigns) do |*names| instance_variable_get "@content_for_#{names.first || 'layout'}" end end diff --git a/actionpack/lib/action_view/helpers/active_record_helper.rb b/actionpack/lib/action_view/helpers/active_record_helper.rb index e788ebf359..fce03ff605 100644 --- a/actionpack/lib/action_view/helpers/active_record_helper.rb +++ b/actionpack/lib/action_view/helpers/active_record_helper.rb @@ -25,7 +25,7 @@ module ActionView # Returns an entire form with all needed input tags for a specified Active Record object. For example, if <tt>@post</tt> # has attributes named +title+ of type +VARCHAR+ and +body+ of type +TEXT+ then # - # form("post") + # form("post") # # would yield a form like the following (modulus formatting): # @@ -90,23 +90,41 @@ module ActionView end # Returns a string containing the error message attached to the +method+ on the +object+ if one exists. - # This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a +prepend_text+ and/or +append_text+ - # (to properly explain the error), and a +css_class+ to style it accordingly. +object+ should either be the name of an instance variable or - # the actual object. As an example, let's say you have a model <tt>@post</tt> that has an error message on the +title+ attribute: + # This error message is wrapped in a <tt>DIV</tt> tag, which can be extended to include a <tt>:prepend_text</tt> + # and/or <tt>:append_text</tt> (to properly explain the error), and a <tt>:css_class</tt> to style it + # accordingly. +object+ should either be the name of an instance variable or the actual object. The method can be + # passed in either as a string or a symbol. + # As an example, let's say you have a model <tt>@post</tt> that has an error message on the +title+ attribute: # # <%= error_message_on "post", "title" %> # # => <div class="formError">can't be empty</div> # - # <%= error_message_on @post, "title" %> + # <%= error_message_on @post, :title %> # # => <div class="formError">can't be empty</div> # - # <%= error_message_on "post", "title", "Title simply ", " (or it won't work).", "inputError" %> - # # => <div class="inputError">Title simply can't be empty (or it won't work).</div> - def error_message_on(object, method, prepend_text = "", append_text = "", css_class = "formError") + # <%= error_message_on "post", "title", + # :prepend_text => "Title simply ", + # :append_text => " (or it won't work).", + # :css_class => "inputError" %> + def error_message_on(object, method, *args) + options = args.extract_options! + unless args.empty? + ActiveSupport::Deprecation.warn('error_message_on takes an option hash instead of separate ' + + 'prepend_text, append_text, and css_class arguments', caller) + + options[:prepend_text] = args[0] || '' + options[:append_text] = args[1] || '' + options[:css_class] = args[2] || 'formError' + end + options.reverse_merge!(:prepend_text => '', :append_text => '', :css_class => 'formError') + if (obj = (object.respond_to?(:errors) ? object : instance_variable_get("@#{object}"))) && (errors = obj.errors.on(method)) - content_tag("div", "#{prepend_text}#{errors.is_a?(Array) ? errors.first : errors}#{append_text}", :class => css_class) - else + content_tag("div", + "#{options[:prepend_text]}#{errors.is_a?(Array) ? errors.first : errors}#{options[:append_text]}", + :class => options[:css_class] + ) + else '' end end @@ -133,7 +151,7 @@ module ActionView # # To specify the display for one object, you simply provide its name as a parameter. # For example, for the <tt>@user</tt> model: - # + # # error_messages_for 'user' # # To specify more than one object, you simply list them; optionally, you can add an extra <tt>:object_name</tt> parameter, which @@ -151,12 +169,14 @@ module ActionView # instance yourself and set it up. View the source of this method to see how easy it is. def error_messages_for(*params) options = params.extract_options!.symbolize_keys + if object = options.delete(:object) objects = [object].flatten else objects = params.collect {|object_name| instance_variable_get("@#{object_name}") }.compact end - count = objects.inject(0) {|sum, object| sum + object.errors.count } + + count = objects.inject(0) {|sum, object| sum + object.errors.count } unless count.zero? html = {} [:id, :class].each do |key| @@ -168,16 +188,25 @@ module ActionView end end options[:object_name] ||= params.first - options[:header_message] = "#{pluralize(count, 'error')} prohibited this #{options[:object_name].to_s.gsub('_', ' ')} from being saved" unless options.include?(:header_message) - options[:message] ||= 'There were problems with the following fields:' unless options.include?(:message) - error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join - contents = '' - contents << content_tag(options[:header_tag] || :h2, options[:header_message]) unless options[:header_message].blank? - contents << content_tag(:p, options[:message]) unless options[:message].blank? - contents << content_tag(:ul, error_messages) + I18n.with_options :locale => options[:locale], :scope => [:active_record, :error] do |locale| + header_message = if options.include?(:header_message) + options[:header_message] + else + object_name = options[:object_name].to_s.gsub('_', ' ') + object_name = I18n.t(object_name, :default => object_name) + locale.t :header_message, :count => count, :object_name => object_name + end + message = options.include?(:message) ? options[:message] : locale.t(:message) + error_messages = objects.sum {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } }.join - content_tag(:div, contents, html) + contents = '' + contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank? + contents << content_tag(:p, message) unless message.blank? + contents << content_tag(:ul, error_messages) + + content_tag(:div, contents, html) + end else '' end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index bf13945844..769eada120 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -5,12 +5,12 @@ require 'action_view/helpers/tag_helper' module ActionView module Helpers #:nodoc: # This module provides methods for generating HTML that links views to assets such - # as images, javascripts, stylesheets, and feeds. These methods do not verify - # the assets exist before linking to them. + # as images, javascripts, stylesheets, and feeds. These methods do not verify + # the assets exist before linking to them. # # === 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 assets server by + # folder, but you can direct Rails to link to assets from a dedicated assets server by # setting ActionController::Base.asset_host in your <tt>config/environment.rb</tt>. For example, # let's say your asset host is <tt>assets.example.com</tt>. # @@ -22,16 +22,16 @@ module ActionView # # This is useful since browsers typically open at most two connections to a single host, # which means your assets often wait in single file for their turn to load. You can - # alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com") + # alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com") # to automatically distribute asset requests among four hosts (e.g., "assets0.example.com" through "assets3.example.com") - # so browsers will open eight connections rather than two. + # so browsers will open eight connections rather than two. # # image_tag("rails.png") # => <img src="http://assets0.example.com/images/rails.png" alt="Rails" /> # stylesheet_link_tag("application") # => <link href="http://assets3.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" /> # - # To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME + # To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME # the wildcard to a single asset host. You can read more about setting up your DNS CNAME records from # your ISP. # @@ -86,7 +86,7 @@ module ActionView # asset far into the future, but still be able to instantly invalidate it by simply updating the file (and hence updating the timestamp, # which then updates the URL as the timestamp is part of that, which in turn busts the cache). # - # It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take + # It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take # advantage of this feature. Here's an example for Apache: # # # Asset Expiration @@ -95,16 +95,17 @@ module ActionView # ExpiresDefault "access plus 1 year" # </FilesMatch> # - # Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must + # Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must # have their clocks synchronized. If one of them drift out of sync, you'll see different timestamps at random and the cache won't # work. Which means that the browser will request the same assets over and over again even thought they didn't change. You can use - # something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being + # something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being # requested over and over). module AssetTagHelper ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : "public" JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts" STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets" - + JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'].map(&:to_s).freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES) + # Returns a link tag that browsers and news readers can use to auto-detect # an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or # <tt>:atom</tt>. Control the link options in url_for format using the @@ -154,10 +155,6 @@ module ActionView end alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route - JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES) - @@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup } - @@stylesheet_expansions = {} - # Returns an html script tag for each of the +sources+ provided. You # can pass in the filename (.js extension is optional) of javascript files # that exist in your public/javascripts directory for inclusion into the @@ -193,7 +190,7 @@ module ActionView # # * = The application.js file is only referenced if it exists # - # Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason + # Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason # (e.g., you're going to be using a certain .js file in every action), then take a look at the register_javascript_include_default method. # # You can also include all javascripts in the javascripts directory using <tt>:all</tt> as the source: @@ -218,7 +215,7 @@ module ActionView # 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 # is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development - # environment). + # environment). # # ==== Examples # javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is false => @@ -259,6 +256,8 @@ module ActionView end end + @@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup } + # Register one or more javascript files to be included when <tt>symbol</tt> # is passed to <tt>javascript_include_tag</tt>. This method is typically intended # to be called from plugin initialization to register javascript files @@ -274,6 +273,8 @@ module ActionView @@javascript_expansions.merge!(expansions) end + @@stylesheet_expansions = {} + # Register one or more stylesheet files to be included when <tt>symbol</tt> # is passed to <tt>stylesheet_link_tag</tt>. This method is typically intended # to be called from plugin initialization to register stylesheet files @@ -439,9 +440,9 @@ module ActionView # <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") # => + # 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")) # => + # 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.symbolize_keys! @@ -454,23 +455,15 @@ module ActionView end if mouseover = options.delete(:mouseover) - options[:onmouseover] = "this.src='#{image_path(mouseover)}'" - options[:onmouseout] = "this.src='#{image_path(options[:src])}'" + options[:onmouseover] = "this.src='#{image_path(mouseover)}'" + options[:onmouseout] = "this.src='#{image_path(options[:src])}'" end tag("img", options) end private - def file_exist?(path) - @@file_exist_cache ||= {} - if !(@@file_exist_cache[path] ||= File.exist?(path)) - @@file_exist_cache[path] = true - false - else - true - end - end + COMPUTED_PUBLIC_PATHS = ActiveSupport::Cache::MemoryStore.new.silence!.threadsafe! # 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 @@ -483,14 +476,14 @@ module ActionView if has_request [ @controller.request.protocol, ActionController::Base.asset_host.to_s, - @controller.request.relative_url_root, + ActionController::Base.relative_url_root, dir, source, ext, include_host ].join else [ ActionController::Base.asset_host.to_s, dir, source, ext, include_host ].join end - ActionView::Base.computed_public_paths[cache_key] ||= + source = COMPUTED_PUBLIC_PATHS.fetch(cache_key) do begin source += ".#{ext}" if ext && File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}")) @@ -499,25 +492,27 @@ module ActionView else source = "/#{dir}/#{source}" unless source[0] == ?/ if has_request - unless source =~ %r{^#{@controller.request.relative_url_root}/} - source = "#{@controller.request.relative_url_root}#{source}" + unless source =~ %r{^#{ActionController::Base.relative_url_root}/} + source = "#{ActionController::Base.relative_url_root}#{source}" end end - source = rewrite_asset_path(source) - if include_host - host = compute_asset_host(source) + rewrite_asset_path(source) + end + end + end - if has_request && !host.blank? && host !~ %r{^[-a-z]+://} - host = "#{@controller.request.protocol}#{host}" - end + if include_host && source !~ %r{^[-a-z]+://} + host = compute_asset_host(source) - "#{host}#{source}" - else - source - end - end + if has_request && !host.blank? && host !~ %r{^[-a-z]+://} + host = "#{@controller.request.protocol}#{host}" end + + "#{host}#{source}" + else + source + end end # Pick an asset host for this source. Returns +nil+ if no host is set, @@ -591,7 +586,7 @@ module ActionView expanded_sources = sources.collect do |source| determine_source(source, @@javascript_expansions) end.flatten - expanded_sources << "application" if sources.include?(:defaults) && file_exist?(File.join(JAVASCRIPTS_DIR, "application.js")) + expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, "application.js")) expanded_sources end end diff --git a/actionpack/lib/action_view/helpers/cache_helper.rb b/actionpack/lib/action_view/helpers/cache_helper.rb index 930c397785..64d1ad2715 100644 --- a/actionpack/lib/action_view/helpers/cache_helper.rb +++ b/actionpack/lib/action_view/helpers/cache_helper.rb @@ -32,8 +32,7 @@ module ActionView # <i>Topics listed alphabetically</i> # <% end %> def cache(name = {}, options = nil, &block) - handler = Template.handler_class_for_extension(current_render_extension.to_sym) - handler.new(@controller).cache_fragment(block, name, options) + @controller.fragment_for(output_buffer, name, options, &block) end end end diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 720e2da8cc..e86ca27f31 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -122,14 +122,15 @@ module ActionView nil end - private - def with_output_buffer(buf = '') - self.output_buffer, old_buffer = buf, output_buffer - yield - output_buffer - ensure - self.output_buffer = old_buffer - end + # Use an alternate output buffer for the duration of the block. + # Defaults to a new empty string. + def with_output_buffer(buf = '') #:nodoc: + self.output_buffer, old_buffer = buf, output_buffer + yield + output_buffer + ensure + self.output_buffer = old_buffer + end end end end diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 0735ed07ee..c7a1d40ff2 100755 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -3,14 +3,15 @@ require 'action_view/helpers/tag_helper' module ActionView module Helpers - # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the select-type methods - # share a number of common options that are as follows: + # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the + # select-type methods share a number of common options that are as follows: # - # * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday" would give - # birthday[month] instead of date[month] if passed to the select_month method. + # * <tt>:prefix</tt> - overwrites the default prefix of "date" used for the select names. So specifying "birthday" + # would give birthday[month] instead of date[month] if passed to the select_month method. # * <tt>:include_blank</tt> - set to true if it should be possible to set an empty date. - # * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true, the select_month - # method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead of "date[month]". + # * <tt>:discard_type</tt> - set to true if you want to discard the type part of the select name. If set to true, + # the select_month method would use simply "date" (which can be overwritten using <tt>:prefix</tt>) instead of + # "date[month]". module DateHelper include ActionView::Helpers::TagHelper DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX') @@ -58,33 +59,38 @@ module ActionView # distance_of_time_in_words(to_time, from_time, true) # => over 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 = false) + def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {}) from_time = from_time.to_time if from_time.respond_to?(:to_time) to_time = to_time.to_time if to_time.respond_to?(:to_time) distance_in_minutes = (((to_time - from_time).abs)/60).round distance_in_seconds = ((to_time - from_time).abs).round - case distance_in_minutes - when 0..1 - return (distance_in_minutes == 0) ? 'less than a minute' : '1 minute' unless include_seconds - case distance_in_seconds - when 0..4 then 'less than 5 seconds' - when 5..9 then 'less than 10 seconds' - when 10..19 then 'less than 20 seconds' - when 20..39 then 'half a minute' - when 40..59 then 'less than a minute' - else '1 minute' - end + I18n.with_options :locale => options[:locale], :scope => :'datetime.distance_in_words' do |locale| + case distance_in_minutes + when 0..1 + return distance_in_minutes == 0 ? + locale.t(:less_than_x_minutes, :count => 1) : + locale.t(:x_minutes, :count => distance_in_minutes) unless include_seconds + + case distance_in_seconds + when 0..4 then locale.t :less_than_x_seconds, :count => 5 + when 5..9 then locale.t :less_than_x_seconds, :count => 10 + when 10..19 then locale.t :less_than_x_seconds, :count => 20 + when 20..39 then locale.t :half_a_minute + when 40..59 then locale.t :less_than_x_minutes, :count => 1 + else locale.t :x_minutes, :count => 1 + end - when 2..44 then "#{distance_in_minutes} minutes" - when 45..89 then 'about 1 hour' - when 90..1439 then "about #{(distance_in_minutes.to_f / 60.0).round} hours" - when 1440..2879 then '1 day' - when 2880..43199 then "#{(distance_in_minutes / 1440).round} days" - when 43200..86399 then 'about 1 month' - when 86400..525599 then "#{(distance_in_minutes / 43200).round} months" - when 525600..1051199 then 'about 1 year' - else "over #{(distance_in_minutes / 525600).round} years" + when 2..44 then locale.t :x_minutes, :count => distance_in_minutes + when 45..89 then locale.t :about_x_hours, :count => 1 + when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round + when 1440..2879 then locale.t :x_days, :count => 1 + when 2880..43199 then locale.t :x_days, :count => (distance_in_minutes / 1440).round + when 43200..86399 then locale.t :about_x_months, :count => 1 + when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes / 43200).round + when 525600..1051199 then locale.t :about_x_years, :count => 1 + else locale.t :over_x_years, :count => (distance_in_minutes / 525600).round + end end end @@ -102,15 +108,18 @@ module ActionView alias_method :distance_of_time_in_words_to_now, :time_ago_in_words - # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by - # +method+) on an object assigned to the template (identified by +object+). It's possible to tailor the selects through the +options+ hash, - # which accepts all the keys that each of the individual select builders do (like <tt>:use_month_numbers</tt> for select_month) as well as a range of - # discard options. The discard options are <tt>:discard_year</tt>, <tt>:discard_month</tt> and <tt>:discard_day</tt>. Set to true, they'll - # drop the respective select. Discarding the month select will also automatically discard the day select. It's also possible to explicitly - # set the order of the tags using the <tt>:order</tt> option with an array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in - # the desired order. Symbols may be omitted and the respective select is not included. + # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based + # attribute (identified by +method+) on an object assigned to the template (identified by +object+). It's + # possible to tailor the selects through the +options+ hash, which accepts all the keys that each of the + # individual select builders do (like <tt>:use_month_numbers</tt> for select_month) as well as a range of discard + # options. The discard options are <tt>:discard_year</tt>, <tt>:discard_month</tt> and <tt>:discard_day</tt>. Set + # to true, they'll drop the respective select. Discarding the month select will also automatically discard the + # day select. It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an + # array of symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. Symbols may be omitted + # and the respective select is not included. # - # Pass the <tt>:default</tt> option to set the default date. Use a Time object or a Hash of <tt>:year</tt>, <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt>, and <tt>:second</tt>. + # Pass the <tt>:default</tt> option to set the default date. Use a Time object or a Hash of <tt>:year</tt>, + # <tt>:month</tt>, <tt>:day</tt>, <tt>:hour</tt>, <tt>:minute</tt>, and <tt>:second</tt>. # # Passing <tt>:disabled => true</tt> as part of the +options+ will make elements inaccessible for change. # @@ -128,7 +137,7 @@ module ActionView # # # Generates a date select that when POSTed is stored in the post variable, in the written_on attribute, # # with the year in the year drop down box starting at 1995, numbers used for months instead of words, - # # and without a day select box. + # # and without a day select box. # date_select("post", "written_on", :start_year => 1995, :use_month_numbers => true, # :discard_day => true, :include_blank => true) # @@ -150,8 +159,8 @@ module ActionView # # The selects are prepared for multi-parameter assignment to an Active Record object. # - # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month - # choices are valid. + # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that + # all month choices are valid. def date_select(object_name, method, options = {}, html_options = {}) InstanceTag.new(object_name, method, self, options.delete(:object)).to_date_select_tag(options, html_options) end @@ -175,12 +184,12 @@ module ActionView # # Creates a time select tag that, when POSTed, will be stored in the mail variable in the sent_at attribute # time_select("mail", "sent_at") # - # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the post variables in - # # the sunrise attribute. + # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the post variables in + # # the sunrise attribute. # time_select("post", "start_time", :include_seconds => true) # - # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the entry variables in - # # the submission_time attribute. + # # Creates a time select tag with a seconds field that, when POSTed, will be stored in the entry variables in + # # the submission_time attribute. # time_select("entry", "submission_time", :include_seconds => true) # # # You can set the :minute_step to 15 which will give you: 00, 15, 30 and 45. @@ -188,14 +197,15 @@ module ActionView # # The selects are prepared for multi-parameter assignment to an Active Record object. # - # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that all month - # choices are valid. + # Note: If the day is not included as an option but the month is, the day will be set to the 1st to ensure that + # all month choices are valid. def time_select(object_name, method, options = {}, html_options = {}) InstanceTag.new(object_name, method, self, options.delete(:object)).to_time_select_tag(options, html_options) end - # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based - # attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples: + # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a + # specified datetime-based attribute (identified by +method+) on an object assigned to the template (identified + # by +object+). Examples: # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # @@ -203,16 +213,16 @@ module ActionView # # Generates a datetime select that, when POSTed, will be stored in the post variable in the written_on attribute # datetime_select("post", "written_on") # - # # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the + # # Generates a datetime select with a year select that starts at 1995 that, when POSTed, will be stored in the # # post variable in the written_on attribute. # datetime_select("post", "written_on", :start_year => 1995) # - # # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will be stored in the - # # trip variable in the departing attribute. + # # Generates a datetime select with a default value of 3 days from the current time that, when POSTed, will + # # be stored in the trip variable in the departing attribute. # datetime_select("trip", "departing", :default => 3.days.from_now) # - # # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable as the written_on - # # attribute. + # # Generates a datetime select that discards the type that, when POSTed, will be stored in the post variable + # # as the written_on attribute. # datetime_select("post", "written_on", :discard_type => true) # # The selects are prepared for multi-parameter assignment to an Active Record object. @@ -222,9 +232,10 @@ module ActionView # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+. # It's also possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of - # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, it - # will be appended onto the <tt>:order</tt> passed in. You can also add <tt>:date_separator</tt> and <tt>:time_separator</tt> - # keys to the +options+ to control visual display of the elements. + # symbols <tt>:year</tt>, <tt>:month</tt> and <tt>:day</tt> in the desired order. If you do not supply a Symbol, + # it will be appended onto the <tt>:order</tt> passed in. You can also add <tt>:date_separator</tt>, + # <tt>:datetime_separator</tt> and <tt>:time_separator</tt> keys to the +options+ to control visual display of + # the elements. # # If anything is passed in the html_options hash it will be applied to every select tag in the set. # @@ -245,7 +256,12 @@ module ActionView # # with a '/' between each date field. # select_datetime(my_date_time, :date_separator => '/') # - # # Generates a datetime select that discards the type of the field and defaults to the datetime in + # # Generates a datetime select that defaults to the datetime in my_date_time (four days after today) + # # with a date fields separated by '/', time fields separated by '' and the date and time fields + # # separated by a comma (','). + # select_datetime(my_date_time, :date_separator => '/', :time_separator => '', :datetime_separator => ',') + # + # # Generates a datetime select that discards the type of the field and defaults to the datetime in # # my_date_time (four days after today) # select_datetime(my_date_time, :discard_type => true) # @@ -256,7 +272,7 @@ module ActionView def select_datetime(datetime = Time.current, options = {}, html_options = {}) separator = options[:datetime_separator] || '' select_date(datetime, options, html_options) + separator + select_time(datetime, options, html_options) - end + end # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+. # It's possible to explicitly set the order of the tags using the <tt>:order</tt> option with an array of @@ -278,27 +294,29 @@ module ActionView # # with the fields ordered year, month, day rather than month, day, year. # select_date(my_date, :order => [:year, :month, :day]) # - # # Generates a date select that discards the type of the field and defaults to the date in + # # Generates a date select that discards the type of the field and defaults to the date in # # my_date (six days after today) # select_date(my_date, :discard_type => true) # + # # Generates a date select that defaults to the date in my_date, + # # which has fields separated by '/' + # select_date(my_date, :date_separator => '/') + # # # Generates a date select that defaults to the datetime in my_date (six days after today) # # prefixed with 'payday' rather than 'date' # select_date(my_date, :prefix => 'payday') # def select_date(date = Date.current, options = {}, html_options = {}) - options[:order] ||= [] + options.reverse_merge!(:order => [], :date_separator => '') [:year, :month, :day].each { |o| options[:order].push(o) unless options[:order].include?(o) } - select_date = '' - options[:order].each do |o| - select_date << self.send("select_#{o}", date, options, html_options) - end - select_date + options[:order].inject([]) { |s, o| + s << self.send("select_#{o}", date, options, html_options) + }.join(options[:date_separator]) end # Returns a set of html select-tags (one for hour and minute) - # You can set <tt>:time_separator</tt> key to format the output, and + # You can set <tt>:time_separator</tt> key to format the output, and # the <tt>:include_seconds</tt> option to include an input for seconds. # # If anything is passed in the html_options hash it will be applied to every select tag in the set. @@ -313,7 +331,7 @@ module ActionView # select_time() # # # Generates a time select that defaults to the time in my_time, - # # which has fields separated by ':' + # # which has fields separated by ':' # select_time(my_time, :time_separator => ':') # # # Generates a time select that defaults to the time in my_time, @@ -326,7 +344,8 @@ module ActionView # def select_time(datetime = Time.current, options = {}, html_options = {}) separator = options[:time_separator] || '' - select_hour(datetime, options, html_options) + separator + select_minute(datetime, options, html_options) + (options[:include_seconds] ? separator + select_second(datetime, options, html_options) : '') + select_hour(datetime, options, html_options) + separator + select_minute(datetime, options, html_options) + + (options[:include_seconds] ? separator + select_second(datetime, options, html_options) : '') end # Returns a select tag with options for each of the seconds 0 through 59 with the current second selected. @@ -341,26 +360,16 @@ module ActionView # # # Generates a select field for seconds that defaults to the number given # select_second(33) - # + # # # Generates a select field for seconds that defaults to the seconds for the time in my_time # # that is named 'interval' rather than 'second' # select_second(my_time, :field_name => 'interval') # def select_second(datetime, options = {}, html_options = {}) val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) : '' - if options[:use_hidden] - options[:include_seconds] ? hidden_html(options[:field_name] || 'second', val, options) : '' - else - second_options = [] - 0.upto(59) do |second| - second_options << ((val == second) ? - content_tag(:option, leading_zero_on_single_digits(second), :value => leading_zero_on_single_digits(second), :selected => "selected") : - content_tag(:option, leading_zero_on_single_digits(second), :value => leading_zero_on_single_digits(second)) - ) - second_options << "\n" - end - select_html(options[:field_name] || 'second', second_options.join, options, html_options) - end + options[:use_hidden] ? + (options[:include_seconds] ? _date_hidden_html(options[:field_name] || 'second', val, options) : '') : + _date_select_html(options[:field_name] || 'second', _date_build_options(val), options, html_options) end # Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected. @@ -376,26 +385,17 @@ module ActionView # # # Generates a select field for minutes that defaults to the number given # select_minute(14) - # + # # # Generates a select field for minutes that defaults to the minutes for the time in my_time # # that is named 'stride' rather than 'second' # select_minute(my_time, :field_name => 'stride') # def select_minute(datetime, options = {}, html_options = {}) val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.min) : '' - if options[:use_hidden] - hidden_html(options[:field_name] || 'minute', val, options) - else - minute_options = [] - 0.step(59, options[:minute_step] || 1) do |minute| - minute_options << ((val == minute) ? - content_tag(:option, leading_zero_on_single_digits(minute), :value => leading_zero_on_single_digits(minute), :selected => "selected") : - content_tag(:option, leading_zero_on_single_digits(minute), :value => leading_zero_on_single_digits(minute)) - ) - minute_options << "\n" - end - select_html(options[:field_name] || 'minute', minute_options.join, options, html_options) - end + options[:use_hidden] ? + _date_hidden_html(options[:field_name] || 'minute', val, options) : + _date_select_html(options[:field_name] || 'minute', + _date_build_options(val, :step => options[:minute_step]), options, html_options) end # Returns a select tag with options for each of the hours 0 through 23 with the current hour selected. @@ -410,26 +410,15 @@ module ActionView # # # Generates a select field for minutes that defaults to the number given # select_minute(14) - # + # # # Generates a select field for minutes that defaults to the minutes for the time in my_time # # that is named 'stride' rather than 'second' # select_minute(my_time, :field_name => 'stride') # def select_hour(datetime, options = {}, html_options = {}) val = datetime ? (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) : '' - if options[:use_hidden] - hidden_html(options[:field_name] || 'hour', val, options) - else - hour_options = [] - 0.upto(23) do |hour| - hour_options << ((val == hour) ? - content_tag(:option, leading_zero_on_single_digits(hour), :value => leading_zero_on_single_digits(hour), :selected => "selected") : - content_tag(:option, leading_zero_on_single_digits(hour), :value => leading_zero_on_single_digits(hour)) - ) - hour_options << "\n" - end - select_html(options[:field_name] || 'hour', hour_options.join, options, html_options) - end + options[:use_hidden] ? _date_hidden_html(options[:field_name] || 'hour', val, options) : + _date_select_html(options[:field_name] || 'hour', _date_build_options(val, :end => 23), options, html_options) end # Returns a select tag with options for each of the days 1 through 31 with the current day selected. @@ -444,36 +433,27 @@ module ActionView # # # Generates a select field for days that defaults to the number given # select_day(5) - # + # # # Generates a select field for days that defaults to the day for the date in my_date # # that is named 'due' rather than 'day' # select_day(my_time, :field_name => 'due') # def select_day(date, options = {}, html_options = {}) val = date ? (date.kind_of?(Fixnum) ? date : date.day) : '' - if options[:use_hidden] - hidden_html(options[:field_name] || 'day', val, options) - else - day_options = [] - 1.upto(31) do |day| - day_options << ((val == day) ? - content_tag(:option, day, :value => day, :selected => "selected") : - content_tag(:option, day, :value => day) - ) - day_options << "\n" - end - select_html(options[:field_name] || 'day', day_options.join, options, html_options) - end + options[:use_hidden] ? _date_hidden_html(options[:field_name] || 'day', val, options) : + _date_select_html(options[:field_name] || 'day', + _date_build_options(val, :start => 1, :end => 31, :leading_zeros => false), + options, html_options) end - # Returns a select tag with options for each of the months January through December with the current month selected. - # The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values - # (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names -- - # set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you want both numbers and names, - # set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer to show month names as abbreviations, - # set the <tt>:use_short_month</tt> key in +options+ to true. If you want to use your own month names, set the - # <tt>:use_month_names</tt> key in +options+ to an array of 12 month names. Override the field name using the - # <tt>:field_name</tt> option, 'month' by default. + # Returns a select tag with options for each of the months January through December with the current month + # selected. The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are + # used as values (what's submitted to the server). It's also possible to use month numbers for the presentation + # instead of names -- set the <tt>:use_month_numbers</tt> key in +options+ to true for this to happen. If you + # want both numbers and names, set the <tt>:add_month_numbers</tt> key in +options+ to true. If you would prefer + # to show month names as abbreviations, set the <tt>:use_short_month</tt> key in +options+ to true. If you want + # to use your own month names, set the <tt>:use_month_names</tt> key in +options+ to an array of 12 month names. + # 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 @@ -485,7 +465,7 @@ module ActionView # select_month(Date.today, :field_name => 'start') # # # Generates a select field for months that defaults to the current month that - # # will use keys like "1", "3". + # # will use keys like "1", "3". # select_month(Date.today, :use_month_numbers => true) # # # Generates a select field for months that defaults to the current month that @@ -501,13 +481,19 @@ module ActionView # select_month(Date.today, :use_month_names => %w(Januar Februar Marts ...)) # def select_month(date, options = {}, html_options = {}) + locale = options[:locale] + val = date ? (date.kind_of?(Fixnum) ? date : date.month) : '' if options[:use_hidden] - hidden_html(options[:field_name] || 'month', val, options) + _date_hidden_html(options[:field_name] || 'month', val, options) else month_options = [] - month_names = options[:use_month_names] || (options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES) + month_names = options[:use_month_names] || begin + key = options[:use_short_month] ? :'date.abbr_month_names' : :'date.month_names' + I18n.translate key, :locale => locale + end month_names.unshift(nil) if month_names.size < 13 + 1.upto(12) do |month_number| month_name = if options[:use_month_numbers] month_number @@ -523,14 +509,15 @@ module ActionView ) month_options << "\n" end - select_html(options[:field_name] || 'month', month_options.join, options, html_options) + _date_select_html(options[:field_name] || 'month', month_options.join, options, html_options) end end - # Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius - # can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the +options+. Both ascending and descending year - # lists are supported by making <tt>:start_year</tt> less than or 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. + # Returns a select tag with options for each of the five years on each side of the current, which is selected. + # The five year radius can be changed using the <tt>:start_year</tt> and <tt>:end_year</tt> keys in the + # +options+. Both ascending and descending year lists are supported by making <tt>:start_year</tt> less than or + # 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 @@ -551,38 +538,48 @@ module ActionView # def select_year(date, options = {}, html_options = {}) if !date || date == 0 - value = '' + val = '' middle_year = Date.today.year elsif date.kind_of?(Fixnum) - value = middle_year = date + val = middle_year = date else - value = middle_year = date.year + val = middle_year = date.year end if options[:use_hidden] - hidden_html(options[:field_name] || 'year', value, options) + _date_hidden_html(options[:field_name] || 'year', val, options) else - year_options = '' - start_year = options[:start_year] || middle_year - 5 - end_year = options[:end_year] || middle_year + 5 - step_val = start_year < end_year ? 1 : -1 - - start_year.step(end_year, step_val) do |year| - if value == year - year_options << content_tag(:option, year, :value => year, :selected => "selected") - else - year_options << content_tag(:option, year, :value => year) - end - year_options << "\n" - end - select_html(options[:field_name] || 'year', year_options, options, html_options) + options[:start_year] ||= middle_year - 5 + options[:end_year] ||= middle_year + 5 + step = options[:start_year] < options[:end_year] ? 1 : -1 + + _date_select_html(options[:field_name] || 'year', + _date_build_options(val, + :start => options[:start_year], + :end => options[:end_year], + :step => step, + :leading_zeros => false + ), options, html_options) end end private + def _date_build_options(selected, options={}) + options.reverse_merge!(:start => 0, :end => 59, :step => 1, :leading_zeros => true) + + select_options = [] + (options[:start] || 0).step((options[:end] || 59), options[:step] || 1) do |i| + value = options[:leading_zeros] ? sprintf("%02d", i) : i + tag_options = { :value => value } + tag_options[:selected] = "selected" if selected == i + + select_options << content_tag(:option, value, tag_options) + end + select_options.join("\n") + "\n" + end - def select_html(type, html_options, options, select_tag_options = {}) - name_and_id_from_options(options, type) + def _date_select_html(type, html_options, options, select_tag_options = {}) + _date_name_and_id_from_options(options, type) select_options = {:id => options[:id], :name => options[:name]} select_options.merge!(:disabled => 'disabled') if options[:disabled] select_options.merge!(select_tag_options) unless select_tag_options.empty? @@ -592,19 +589,15 @@ module ActionView content_tag(:select, select_html, select_options) + "\n" end - def hidden_html(type, value, options) - name_and_id_from_options(options, type) + def _date_hidden_html(type, value, options) + _date_name_and_id_from_options(options, type) hidden_html = tag(:input, :type => "hidden", :id => options[:id], :name => options[:name], :value => value) + "\n" end - def name_and_id_from_options(options, type) + def _date_name_and_id_from_options(options, type) options[:name] = (options[:prefix] || DEFAULT_PREFIX) + (options[:discard_type] ? '' : "[#{type}]") options[:id] = options[:name].gsub(/([\[\(])|(\]\[)/, '_').gsub(/[\]\)]/, '') end - - def leading_zero_on_single_digits(number) - number > 9 ? number : "0#{number}" - end end class InstanceTag #:nodoc: @@ -624,6 +617,8 @@ module ActionView private def date_or_time_select(options, html_options = {}) + locale = options[:locale] + defaults = { :discard_type => true } options = defaults.merge(options) datetime = value(object) @@ -631,7 +626,7 @@ module ActionView position = { :year => 1, :month => 2, :day => 3, :hour => 4, :minute => 5, :second => 6 } - order = (options[:order] ||= [:year, :month, :day]) + order = options[:order] ||= I18n.translate(:'date.order', :locale => locale) # Discard explicit and implicit by not being included in the :order discard = {} @@ -660,7 +655,11 @@ module ActionView # This ensures AR can reconstruct valid dates using ParseDate next if discard[param] && (date_or_time_select.empty? || options[:ignore_date]) - date_or_time_select.insert(0, self.send("select_#{param}", datetime, options_with_prefix(position[param], options.merge(:use_hidden => discard[param])), html_options)) + date_or_time_select.insert(0, + self.send("select_#{param}", + datetime, + options_with_prefix(position[param], options.merge(:use_hidden => discard[param])), + html_options)) date_or_time_select.insert(0, case param when :hour then (discard[:year] && discard[:day] ? "" : " — ") @@ -668,7 +667,6 @@ module ActionView when :second then options[:include_seconds] ? " : " : "" else "" end) - end date_or_time_select @@ -696,7 +694,7 @@ module ActionView default[:sec] ||= default[:second] time = Time.current - + [:year, :month, :day, :hour, :min, :sec].each do |key| default[key] ||= time.send(key) end diff --git a/actionpack/lib/action_view/helpers/debug_helper.rb b/actionpack/lib/action_view/helpers/debug_helper.rb index ea70a697de..90863fca08 100644 --- a/actionpack/lib/action_view/helpers/debug_helper.rb +++ b/actionpack/lib/action_view/helpers/debug_helper.rb @@ -11,16 +11,16 @@ module ActionView # @user = User.new({ :username => 'testing', :password => 'xyz', :age => 42}) %> # debug(@user) # # => - # <pre class='debug_dump'>--- !ruby/object:User - # attributes: - # updated_at: + # <pre class='debug_dump'>--- !ruby/object:User + # attributes: + # updated_at: # username: testing - # + # # age: 42 # password: xyz - # created_at: + # created_at: # attributes_cache: {} - # + # # new_record: true # </pre> diff --git a/actionpack/lib/action_view/helpers/form_country_helper.rb b/actionpack/lib/action_view/helpers/form_country_helper.rb new file mode 100644 index 0000000000..84e811f61d --- /dev/null +++ b/actionpack/lib/action_view/helpers/form_country_helper.rb @@ -0,0 +1,92 @@ +require 'action_view/helpers/form_options_helper' + +module ActionView + module Helpers + module FormCountryHelper + + # Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags. + def country_select(object, method, priority_countries = nil, options = {}, html_options = {}) + InstanceTag.new(object, method, self, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options) + end + + # Returns a string of option tags for pretty much any country in the world. Supply a country name as +selected+ to + # have it marked as the selected option tag. You can also supply an array of countries as +priority_countries+, so + # that they will be listed above the rest of the (long) list. + # + # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag. + def country_options_for_select(selected = nil, priority_countries = nil) + country_options = "" + + if priority_countries + country_options += options_for_select(priority_countries, selected) + country_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n" + end + + return country_options + options_for_select(COUNTRIES, selected) + end + + private + + # All the countries included in the country_options output. + COUNTRIES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", + "Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", + "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", + "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil", + "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", + "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China", + "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo", + "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", + "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", + "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)", + "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", + "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea", + "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)", + "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq", + "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", + "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan", + "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya", + "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of", + "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique", + "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of", + "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", + "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger", + "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", + "Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", + "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation", + "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia", + "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino", + "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", + "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", + "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname", + "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic", + "Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste", + "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", + "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", + "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", + "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara", + "Yemen", "Zambia", "Zimbabwe"] unless const_defined?("COUNTRIES") + end + + class InstanceTag #:nodoc: + include FormCountryHelper + + def to_country_select_tag(priority_countries, options, html_options) + html_options = html_options.stringify_keys + add_default_name_and_id(html_options) + value = value(object) + content_tag("select", + add_options( + country_options_for_select(value, priority_countries), + options, value + ), html_options + ) + end + end + + class FormBuilder + def country_select(method, priority_countries = nil, options = {}, html_options = {}) + @template.country_select(@object_name, method, priority_countries, objectify_options(options), @default_options.merge(html_options)) + 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 fa26aa4640..7bb82ba5bb 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -304,10 +304,6 @@ module ActionView when String, Symbol object_name = record_or_name_or_array object = args.first - when Array - object = record_or_name_or_array.last - object_name = ActionController::RecordIdentifier.singular_class_name(object) - apply_form_for_options!(record_or_name_or_array, options) else object = record_or_name_or_array object_name = ActionController::RecordIdentifier.singular_class_name(object) @@ -532,10 +528,10 @@ module ActionView def initialize(object_name, method_name, template_object, object = nil) @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup - @template_object= template_object + @template_object = template_object @object = object - if @object_name.sub!(/\[\]$/,"") - if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param) + if @object_name.sub!(/\[\]$/,"") || @object_name.sub!(/\[\]\]$/,"]") + if (object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}")) && object.respond_to?(:to_param) @auto_index = object.to_param else raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" @@ -712,7 +708,7 @@ module ActionView end def sanitized_object_name - @sanitized_object_name ||= @object_name.gsub(/[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "") + @sanitized_object_name ||= @object_name.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "") end def sanitized_method_name @@ -730,6 +726,13 @@ module ActionView def initialize(object_name, object, template, options, proc) @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc @default_options = @options ? @options.slice(:index) : {} + if @object_name.to_s.match(/\[\]$/) + if object ||= @template.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:to_param) + @auto_index = object.to_param + else + raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to to_param: #{object.inspect}" + end + end end (field_helpers - %w(label check_box radio_button fields_for)).each do |selector| @@ -742,16 +745,25 @@ module ActionView end def fields_for(record_or_name_or_array, *args, &block) + if options.has_key?(:index) + index = "[#{options[:index]}]" + elsif defined?(@auto_index) + self.object_name = @object_name.to_s.sub(/\[\]$/,"") + index = "[#{@auto_index}]" + else + index = "" + end + case record_or_name_or_array when String, Symbol - name = "#{object_name}[#{record_or_name_or_array}]" + name = "#{object_name}#{index}[#{record_or_name_or_array}]" when Array object = record_or_name_or_array.last - name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]" + name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]" args.unshift(object) else object = record_or_name_or_array - name = "#{object_name}[#{ActionController::RecordIdentifier.singular_class_name(object)}]" + name = "#{object_name}#{index}[#{ActionController::RecordIdentifier.singular_class_name(object)}]" args.unshift(object) end @@ -770,8 +782,8 @@ module ActionView @template.radio_button(@object_name, method, tag_value, objectify_options(options)) end - def error_message_on(method, prepend_text = "", append_text = "", css_class = "formError") - @template.error_message_on(@object, method, prepend_text, append_text, css_class) + def error_message_on(method, *args) + @template.error_message_on(@object, method, *args) end def error_messages(options = {}) diff --git a/actionpack/lib/action_view/helpers/form_options_helper.rb b/actionpack/lib/action_view/helpers/form_options_helper.rb index 0bd44c5aca..9aae945408 100644 --- a/actionpack/lib/action_view/helpers/form_options_helper.rb +++ b/actionpack/lib/action_view/helpers/form_options_helper.rb @@ -133,11 +133,6 @@ module ActionView InstanceTag.new(object, method, self, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options) end - # Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags. - def country_select(object, method, priority_countries = nil, options = {}, html_options = {}) - InstanceTag.new(object, method, self, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options) - end - # Return select and option tags for the given object and method, using # #time_zone_options_for_select to generate the list of option tags. # @@ -274,24 +269,6 @@ module ActionView end end - # Returns a string of option tags for most countries in the - # world (as defined in COUNTRIES). Supply a country name as - # +selected+ to have it marked as the selected option tag. You - # can also supply an array of countries as +priority_countries+, - # so that they will be listed above the rest of the (long) list. - # - # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag. - def country_options_for_select(selected = nil, priority_countries = nil) - country_options = "" - - if priority_countries - country_options += options_for_select(priority_countries, selected) - country_options += "<option value=\"\" disabled=\"disabled\">-------------</option>\n" - end - - return country_options + options_for_select(COUNTRIES, selected) - end - # Returns a string of option tags for pretty much any time zone in the # world. Supply a TimeZone name as +selected+ to have it marked as the # selected option tag. You can also supply an array of TimeZone objects @@ -349,43 +326,7 @@ module ActionView end # All the countries included in the country_options output. - COUNTRIES = ["Afghanistan", "Aland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", - "Anguilla", "Antarctica", "Antigua And Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", - "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", - "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegowina", "Botswana", "Bouvet Island", "Brazil", - "British Indian Ocean Territory", "Brunei Darussalam", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", - "Cameroon", "Canada", "Cape Verde", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China", - "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo", - "Congo, the Democratic Republic of the", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", - "Cyprus", "Czech Republic", "Denmark", "Djibouti", "Dominica", "Dominican Republic", "Ecuador", "Egypt", - "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)", - "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", - "French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea", - "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Holy See (Vatican City State)", - "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran, Islamic Republic of", "Iraq", - "Ireland", "Isle of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", - "Kiribati", "Korea, Democratic People's Republic of", "Korea, Republic of", "Kuwait", "Kyrgyzstan", - "Lao People's Democratic Republic", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libyan Arab Jamahiriya", - "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "Macedonia, The Former Yugoslav Republic Of", - "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique", - "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia, Federated States of", "Moldova, Republic of", - "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", - "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger", - "Nigeria", "Niue", "Norfolk Island", "Northern Mariana Islands", "Norway", "Oman", "Pakistan", "Palau", - "Palestinian Territory, Occupied", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", - "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation", - "Rwanda", "Saint Barthelemy", "Saint Helena", "Saint Kitts and Nevis", "Saint Lucia", - "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino", - "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", - "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", - "South Georgia and the South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname", - "Svalbard and Jan Mayen", "Swaziland", "Sweden", "Switzerland", "Syrian Arab Republic", - "Taiwan, Province of China", "Tajikistan", "Tanzania, United Republic of", "Thailand", "Timor-Leste", - "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", - "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", "United Kingdom", - "United States", "United States Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", - "Viet Nam", "Virgin Islands, British", "Virgin Islands, U.S.", "Wallis and Futuna", "Western Sahara", - "Yemen", "Zambia", "Zimbabwe"] unless const_defined?("COUNTRIES") + COUNTRIES = ActiveSupport::Deprecation::DeprecatedConstantProxy.new 'COUNTRIES', 'ActionView::Helpers::FormCountryHelper::COUNTRIES' end class InstanceTag #:nodoc: @@ -408,18 +349,6 @@ module ActionView ) end - def to_country_select_tag(priority_countries, options, html_options) - html_options = html_options.stringify_keys - add_default_name_and_id(html_options) - value = value(object) - content_tag("select", - add_options( - country_options_for_select(value, priority_countries), - options, value - ), html_options - ) - end - def to_time_zone_select_tag(priority_zones, options, html_options) html_options = html_options.stringify_keys add_default_name_and_id(html_options) @@ -447,19 +376,15 @@ module ActionView class FormBuilder def select(method, choices, options = {}, html_options = {}) - @template.select(@object_name, method, choices, options.merge(:object => @object), html_options) + @template.select(@object_name, method, choices, objectify_options(options), @default_options.merge(html_options)) end def collection_select(method, collection, value_method, text_method, options = {}, html_options = {}) - @template.collection_select(@object_name, method, collection, value_method, text_method, options.merge(:object => @object), html_options) - end - - def country_select(method, priority_countries = nil, options = {}, html_options = {}) - @template.country_select(@object_name, method, priority_countries, options.merge(:object => @object), html_options) + @template.collection_select(@object_name, method, collection, value_method, text_method, objectify_options(options), @default_options.merge(html_options)) end def time_zone_select(method, priority_zones = nil, options = {}, html_options = {}) - @template.time_zone_select(@object_name, method, priority_zones, options.merge(:object => @object), html_options) + @template.time_zone_select(@object_name, method, priority_zones, objectify_options(options), @default_options.merge(html_options)) end end end diff --git a/actionpack/lib/action_view/helpers/javascript_helper.rb b/actionpack/lib/action_view/helpers/javascript_helper.rb index 6c2d76c85f..32089442b7 100644 --- a/actionpack/lib/action_view/helpers/javascript_helper.rb +++ b/actionpack/lib/action_view/helpers/javascript_helper.rb @@ -44,7 +44,7 @@ module ActionView include PrototypeHelper - # Returns a link of the given +name+ that will trigger a JavaScript +function+ using the + # 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. @@ -97,8 +97,8 @@ module ActionView content_tag(:a, name, html_options.merge(:href => href, :onclick => onclick)) end - - # Returns a button with the given +name+ text that'll trigger a JavaScript +function+ using the + + # 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. diff --git a/actionpack/lib/action_view/helpers/javascripts/prototype.js b/actionpack/lib/action_view/helpers/javascripts/prototype.js index 546f9fe449..2c70b8a7e8 100644 --- a/actionpack/lib/action_view/helpers/javascripts/prototype.js +++ b/actionpack/lib/action_view/helpers/javascripts/prototype.js @@ -1,5 +1,5 @@ -/* Prototype JavaScript framework, version 1.6.0.1 - * (c) 2005-2007 Sam Stephenson +/* Prototype JavaScript framework, version 1.6.0.2 + * (c) 2005-2008 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://www.prototypejs.org/ @@ -7,7 +7,7 @@ *--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.6.0.1', + Version: '1.6.0.2', Browser: { IE: !!(window.attachEvent && !window.opera), @@ -110,7 +110,7 @@ Object.extend(Object, { try { if (Object.isUndefined(object)) return 'undefined'; if (object === null) return 'null'; - return object.inspect ? object.inspect() : object.toString(); + return object.inspect ? object.inspect() : String(object); } catch (e) { if (e instanceof RangeError) return '...'; throw e; @@ -171,7 +171,8 @@ Object.extend(Object, { }, isArray: function(object) { - return object && object.constructor === Array; + return object != null && typeof object == "object" && + 'splice' in object && 'join' in object; }, isHash: function(object) { @@ -578,7 +579,7 @@ var Template = Class.create({ } return before + String.interpret(ctx); - }.bind(this)); + }); } }); Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; @@ -806,20 +807,20 @@ Object.extend(Enumerable, { function $A(iterable) { if (!iterable) return []; if (iterable.toArray) return iterable.toArray(); - var length = iterable.length, results = new Array(length); + var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; } if (Prototype.Browser.WebKit) { - function $A(iterable) { + $A = function(iterable) { if (!iterable) return []; if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && iterable.toArray) return iterable.toArray(); - var length = iterable.length, results = new Array(length); + var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; - } + }; } Array.from = $A; @@ -1298,7 +1299,7 @@ Ajax.Request = Class.create(Ajax.Base, { var contentType = response.getHeader('Content-type'); if (this.options.evalJS == 'force' - || (this.options.evalJS && contentType + || (this.options.evalJS && this.isSameOrigin() && contentType && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) this.evalResponse(); } @@ -1316,9 +1317,18 @@ Ajax.Request = Class.create(Ajax.Base, { } }, + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + getHeader: function(name) { try { - return this.transport.getResponseHeader(name); + return this.transport.getResponseHeader(name) || null; } catch (e) { return null } }, @@ -1391,7 +1401,8 @@ Ajax.Response = Class.create({ if (!json) return null; json = decodeURIComponent(escape(json)); try { - return json.evalJSON(this.request.options.sanitizeJSON); + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } @@ -1404,7 +1415,8 @@ Ajax.Response = Class.create({ this.responseText.blank()) return null; try { - return this.responseText.evalJSON(options.sanitizeJSON); + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } @@ -1608,24 +1620,28 @@ Element.Methods = { Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) insertions = {bottom:insertions}; - var content, t, range; + var content, insert, tagName, childNodes; - for (position in insertions) { + for (var position in insertions) { content = insertions[position]; position = position.toLowerCase(); - t = Element._insertionTranslations[position]; + insert = Element._insertionTranslations[position]; if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { - t.insert(element, content); + insert(element, content); continue; } content = Object.toHTML(content); - range = element.ownerDocument.createRange(); - t.initializeRange(element, range); - t.insert(element, range.createContextualFragment(content.stripScripts())); + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); content.evalScripts.bind(content).defer(); } @@ -1670,7 +1686,7 @@ Element.Methods = { }, descendants: function(element) { - return $(element).getElementsBySelector("*"); + return $(element).select("*"); }, firstDescendant: function(element) { @@ -1709,32 +1725,31 @@ Element.Methods = { element = $(element); if (arguments.length == 1) return $(element.parentNode); var ancestors = element.ancestors(); - return expression ? Selector.findElement(ancestors, expression, index) : - ancestors[index || 0]; + return Object.isNumber(expression) ? ancestors[expression] : + Selector.findElement(ancestors, expression, index); }, down: function(element, expression, index) { element = $(element); if (arguments.length == 1) return element.firstDescendant(); - var descendants = element.descendants(); - return expression ? Selector.findElement(descendants, expression, index) : - descendants[index || 0]; + return Object.isNumber(expression) ? element.descendants()[expression] : + element.select(expression)[index || 0]; }, previous: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); var previousSiblings = element.previousSiblings(); - return expression ? Selector.findElement(previousSiblings, expression, index) : - previousSiblings[index || 0]; + return Object.isNumber(expression) ? previousSiblings[expression] : + Selector.findElement(previousSiblings, expression, index); }, next: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); var nextSiblings = element.nextSiblings(); - return expression ? Selector.findElement(nextSiblings, expression, index) : - nextSiblings[index || 0]; + return Object.isNumber(expression) ? nextSiblings[expression] : + Selector.findElement(nextSiblings, expression, index); }, select: function() { @@ -1860,7 +1875,8 @@ Element.Methods = { do { ancestor = ancestor.parentNode; } while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); } - if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex); + if (nextAncestor && nextAncestor.sourceIndex) + return (e > a && e < nextAncestor.sourceIndex); } while (element = element.parentNode) @@ -2004,7 +2020,7 @@ Element.Methods = { if (element) { if (element.tagName == 'BODY') break; var p = Element.getStyle(element, 'position'); - if (p == 'relative' || p == 'absolute') break; + if (p !== 'static') break; } } while (element); return Element._returnOffset(valueL, valueT); @@ -2153,46 +2169,6 @@ Element._attributeTranslations = { } }; - -if (!document.createRange || Prototype.Browser.Opera) { - Element.Methods.insert = function(element, insertions) { - element = $(element); - - if (Object.isString(insertions) || Object.isNumber(insertions) || - Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) - insertions = { bottom: insertions }; - - var t = Element._insertionTranslations, content, position, pos, tagName; - - for (position in insertions) { - content = insertions[position]; - position = position.toLowerCase(); - pos = t[position]; - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - pos.insert(element, content); - continue; - } - - content = Object.toHTML(content); - tagName = ((position == 'before' || position == 'after') - ? element.parentNode : element).tagName.toUpperCase(); - - if (t.tags[tagName]) { - var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - if (position == 'top' || position == 'after') fragments.reverse(); - fragments.each(pos.insert.curry(element)); - } - else element.insertAdjacentHTML(pos.adjacency, content.stripScripts()); - - content.evalScripts.bind(content).defer(); - } - - return element; - }; -} - if (Prototype.Browser.Opera) { Element.Methods.getStyle = Element.Methods.getStyle.wrap( function(proceed, element, style) { @@ -2237,12 +2213,31 @@ if (Prototype.Browser.Opera) { } else if (Prototype.Browser.IE) { - $w('positionedOffset getOffsetParent viewportOffset').each(function(method) { + // IE doesn't report offsets correctly for static elements, so we change them + // to "relative" to get the values, then change them back. + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + $w('positionedOffset viewportOffset').each(function(method) { Element.Methods[method] = Element.Methods[method].wrap( function(proceed, element) { element = $(element); var position = element.getStyle('position'); - if (position != 'static') return proceed(element); + if (position !== 'static') return proceed(element); + // Trigger hasLayout on the offset parent so that IE6 reports + // accurate offsetTop and offsetLeft values for position: fixed. + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + offsetParent.setStyle({ zoom: 1 }); element.setStyle({ position: 'relative' }); var value = proceed(element); element.setStyle({ position: position }); @@ -2324,7 +2319,10 @@ else if (Prototype.Browser.IE) { }; Element._attributeTranslations.write = { - names: Object.clone(Element._attributeTranslations.read.names), + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), values: { checked: function(element, value) { element.checked = !!value; @@ -2444,7 +2442,7 @@ if (Prototype.Browser.IE || Prototype.Browser.Opera) { }; } -if (document.createElement('div').outerHTML) { +if ('outerHTML' in document.createElement('div')) { Element.Methods.replace = function(element, content) { element = $(element); @@ -2482,45 +2480,25 @@ Element._returnOffset = function(l, t) { Element._getContentFromAnonymousElement = function(tagName, html) { var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; - div.innerHTML = t[0] + html + t[1]; - t[2].times(function() { div = div.firstChild }); + if (t) { + div.innerHTML = t[0] + html + t[1]; + t[2].times(function() { div = div.firstChild }); + } else div.innerHTML = html; return $A(div.childNodes); }; Element._insertionTranslations = { - before: { - adjacency: 'beforeBegin', - insert: function(element, node) { - element.parentNode.insertBefore(node, element); - }, - initializeRange: function(element, range) { - range.setStartBefore(element); - } + before: function(element, node) { + element.parentNode.insertBefore(node, element); }, - top: { - adjacency: 'afterBegin', - insert: function(element, node) { - element.insertBefore(node, element.firstChild); - }, - initializeRange: function(element, range) { - range.selectNodeContents(element); - range.collapse(true); - } + top: function(element, node) { + element.insertBefore(node, element.firstChild); }, - bottom: { - adjacency: 'beforeEnd', - insert: function(element, node) { - element.appendChild(node); - } + bottom: function(element, node) { + element.appendChild(node); }, - after: { - adjacency: 'afterEnd', - insert: function(element, node) { - element.parentNode.insertBefore(node, element.nextSibling); - }, - initializeRange: function(element, range) { - range.setStartAfter(element); - } + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); }, tags: { TABLE: ['<table>', '</table>', 1], @@ -2532,7 +2510,6 @@ Element._insertionTranslations = { }; (function() { - this.bottom.initializeRange = this.top.initializeRange; Object.extend(this.tags, { THEAD: this.tags.TBODY, TFOOT: this.tags.TBODY, @@ -2716,7 +2693,7 @@ document.viewport = { window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); } }; -/* Portions of the Selector class are derived from Jack Slocum’s DomQuery, +/* Portions of the Selector class are derived from Jack Slocum’s DomQuery, * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style * license. Please see http://www.yui-ext.com/ for more information. */ @@ -2959,13 +2936,13 @@ Object.extend(Selector, { }, criteria: { - tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', - className: 'n = h.className(n, r, "#{1}", c); c = false;', - id: 'n = h.id(n, r, "#{1}", c); c = false;', - attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;', + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', attr: function(m) { m[3] = (m[5] || m[6]); - return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); }, pseudo: function(m) { if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); @@ -2989,7 +2966,8 @@ Object.extend(Selector, { tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, id: /^#([\w\-\*]+)(\b|$)/, className: /^\.([\w\-\*]+)(\b|$)/, - pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/, + pseudo: +/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, attrPresence: /^\[([\w]+)\]/, attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }, @@ -3014,7 +2992,7 @@ Object.extend(Selector, { attr: function(element, matches) { var nodeValue = Element.readAttribute(element, matches[1]); - return Selector.operators[matches[2]](nodeValue, matches[3]); + return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); } }, @@ -3029,14 +3007,15 @@ Object.extend(Selector, { // marks an array of nodes for counting mark: function(nodes) { + var _true = Prototype.emptyFunction; for (var i = 0, node; node = nodes[i]; i++) - node._counted = true; + node._countedByPrototype = _true; return nodes; }, unmark: function(nodes) { for (var i = 0, node; node = nodes[i]; i++) - node._counted = undefined; + node._countedByPrototype = undefined; return nodes; }, @@ -3044,15 +3023,15 @@ Object.extend(Selector, { // "ofType" flag indicates whether we're indexing for nth-of-type // rather than nth-child index: function(parentNode, reverse, ofType) { - parentNode._counted = true; + parentNode._countedByPrototype = Prototype.emptyFunction; if (reverse) { for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { var node = nodes[i]; - if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; } } else { for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) - if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; } }, @@ -3061,8 +3040,8 @@ Object.extend(Selector, { if (nodes.length == 0) return nodes; var results = [], n; for (var i = 0, l = nodes.length; i < l; i++) - if (!(n = nodes[i])._counted) { - n._counted = true; + if (!(n = nodes[i])._countedByPrototype) { + n._countedByPrototype = Prototype.emptyFunction; results.push(Element.extend(n)); } return Selector.handlers.unmark(results); @@ -3114,7 +3093,7 @@ Object.extend(Selector, { // TOKEN FUNCTIONS tagName: function(nodes, root, tagName, combinator) { - tagName = tagName.toUpperCase(); + var uTagName = tagName.toUpperCase(); var results = [], h = Selector.handlers; if (nodes) { if (combinator) { @@ -3127,7 +3106,7 @@ Object.extend(Selector, { if (tagName == "*") return nodes; } for (var i = 0, node; node = nodes[i]; i++) - if (node.tagName.toUpperCase() == tagName) results.push(node); + if (node.tagName.toUpperCase() === uTagName) results.push(node); return results; } else return root.getElementsByTagName(tagName); }, @@ -3174,16 +3153,18 @@ Object.extend(Selector, { return results; }, - attrPresence: function(nodes, root, attr) { + attrPresence: function(nodes, root, attr, combinator) { if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); var results = []; for (var i = 0, node; node = nodes[i]; i++) if (Element.hasAttribute(node, attr)) results.push(node); return results; }, - attr: function(nodes, root, attr, value, operator) { + attr: function(nodes, root, attr, value, operator, combinator) { if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); var handler = Selector.operators[operator], results = []; for (var i = 0, node; node = nodes[i]; i++) { var nodeValue = Element.readAttribute(node, attr); @@ -3262,7 +3243,7 @@ Object.extend(Selector, { var h = Selector.handlers, results = [], indexed = [], m; h.mark(nodes); for (var i = 0, node; node = nodes[i]; i++) { - if (!node.parentNode._counted) { + if (!node.parentNode._countedByPrototype) { h.index(node.parentNode, reverse, ofType); indexed.push(node.parentNode); } @@ -3300,7 +3281,7 @@ Object.extend(Selector, { var exclusions = new Selector(selector).findElements(root); h.mark(exclusions); for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node._counted) results.push(node); + if (!node._countedByPrototype) results.push(node); h.unmark(exclusions); return results; }, @@ -3334,11 +3315,19 @@ Object.extend(Selector, { '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } }, + split: function(expression) { + var expressions = []; + expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + return expressions; + }, + matchElements: function(elements, expression) { - var matches = new Selector(expression).findElements(), h = Selector.handlers; + var matches = $$(expression), h = Selector.handlers; h.mark(matches); for (var i = 0, results = [], element; element = elements[i]; i++) - if (element._counted) results.push(element); + if (element._countedByPrototype) results.push(element); h.unmark(matches); return results; }, @@ -3351,11 +3340,7 @@ Object.extend(Selector, { }, findChildElements: function(element, expressions) { - var exprs = expressions.join(','); - expressions = []; - exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { - expressions.push(m[1].strip()); - }); + expressions = Selector.split(expressions.join(',')); var results = [], h = Selector.handlers; for (var i = 0, l = expressions.length, selector; i < l; i++) { selector = new Selector(expressions[i].strip()); @@ -3366,13 +3351,22 @@ Object.extend(Selector, { }); if (Prototype.Browser.IE) { - // IE returns comment nodes on getElementsByTagName("*"). - // Filter them out. - Selector.handlers.concat = function(a, b) { - for (var i = 0, node; node = b[i]; i++) - if (node.tagName !== "!") a.push(node); - return a; - }; + Object.extend(Selector.handlers, { + // IE returns comment nodes on getElementsByTagName("*"). + // Filter them out. + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + if (node.tagName !== "!") a.push(node); + return a; + }, + + // IE improperly serializes _countedByPrototype in (inner|outer)HTML. + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node.removeAttribute('_countedByPrototype'); + return nodes; + } + }); } function $$() { @@ -3850,9 +3844,9 @@ Object.extend(Event, (function() { var cache = Event.cache; function getEventID(element) { - if (element._eventID) return element._eventID; + if (element._prototypeEventID) return element._prototypeEventID[0]; arguments.callee.id = arguments.callee.id || 1; - return element._eventID = ++arguments.callee.id; + return element._prototypeEventID = [++arguments.callee.id]; } function getDOMEventName(eventName) { @@ -3880,7 +3874,7 @@ Object.extend(Event, (function() { return false; Event.extend(event); - handler.call(element, event) + handler.call(element, event); }; wrapper.handler = handler; @@ -3962,11 +3956,12 @@ Object.extend(Event, (function() { if (element == document && document.createEvent && !element.dispatchEvent) element = document.documentElement; + var event; if (document.createEvent) { - var event = document.createEvent("HTMLEvents"); + event = document.createEvent("HTMLEvents"); event.initEvent("dataavailable", true, true); } else { - var event = document.createEventObject(); + event = document.createEventObject(); event.eventType = "ondataavailable"; } @@ -3995,20 +3990,21 @@ Element.addMethods({ Object.extend(document, { fire: Element.Methods.fire.methodize(), observe: Element.Methods.observe.methodize(), - stopObserving: Element.Methods.stopObserving.methodize() + stopObserving: Element.Methods.stopObserving.methodize(), + loaded: false }); (function() { /* Support for the DOMContentLoaded event is based on work by Dan Webb, Matthias Miller, Dean Edwards and John Resig. */ - var timer, fired = false; + var timer; function fireContentLoadedEvent() { - if (fired) return; + if (document.loaded) return; if (timer) window.clearInterval(timer); document.fire("dom:loaded"); - fired = true; + document.loaded = true; } if (document.addEventListener) { diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 120bb4cc1f..c4ba7ccc8e 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -22,7 +22,7 @@ module ActionView # number_to_phone(1235551234, :country_code => 1) # => +1-123-555-1234 # # number_to_phone(1235551234, :country_code => 1, :extension => 1343, :delimiter => ".") - # => +1.123.555.1234 x 1343 + # => +1.123.555.1234 x 1343 def number_to_phone(number, options = {}) number = number.to_s.strip unless number.nil? options = options.stringify_keys @@ -69,12 +69,15 @@ module ActionView # number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "", :format => "%n %u") # # => 1234567890,50 £ def number_to_currency(number, options = {}) - options = options.stringify_keys - precision = options["precision"] || 2 - unit = options["unit"] || "$" - separator = precision > 0 ? options["separator"] || "." : "" - delimiter = options["delimiter"] || "," - format = options["format"] || "%u%n" + options = options.symbolize_keys + defaults = I18n.translate(:'currency.format', :locale => options[:locale]) || {} + + 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 begin parts = number_with_precision(number, precision).split('.') @@ -115,49 +118,72 @@ module ActionView end end - # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You - # can customize the format using optional <em>delimiter</em> and <em>separator</em> parameters. + # Formats a +number+ with grouped thousands using +delimiter+ (e.g., 12,324). You can + # customize the format in the +options+ hash. # # ==== Options - # * <tt>delimiter</tt> - Sets the thousands delimiter (defaults to ","). - # * <tt>separator</tt> - Sets the separator between the units (defaults to "."). + # * <tt>:delimiter</tt> - Sets the thousands delimiter (defaults to ","). + # * <tt>:separator</tt> - Sets the separator between the units (defaults to "."). # # ==== Examples - # number_with_delimiter(12345678) # => 12,345,678 - # number_with_delimiter(12345678.05) # => 12,345,678.05 - # number_with_delimiter(12345678, ".") # => 12.345.678 - # - # number_with_delimiter(98765432.98, " ", ",") + # number_with_delimiter(12345678) # => 12,345,678 + # number_with_delimiter(12345678.05) # => 12,345,678.05 + # number_with_delimiter(12345678, :delimiter => ".") # => 12.345.678 + # number_with_delimiter(12345678, :seperator => ",") # => 12,345,678 + # number_with_delimiter(98765432.98, :delimiter => " ", :separator => ",") # # => 98 765 432,98 - def number_with_delimiter(number, delimiter=",", separator=".") + # + # 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.05, ".", ",") # => 12.345.678,05 + def number_with_delimiter(number, *args) + options = args.extract_options! + unless args.empty? + options[:delimiter] = args[0] || "," + options[:separator] = args[1] || "." + end + options.reverse_merge!(:delimiter => ",", :separator => ".") + begin parts = number.to_s.split('.') - parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}") - parts.join separator + parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{options[:delimiter]}") + parts.join options[:separator] rescue number end end - # Formats a +number+ with the specified level of +precision+ (e.g., 112.32 has a precision of 2). The default - # level of precision is 3. + # Formats a +number+ with the specified level of <tt>:precision</tt> (e.g., 112.32 has a precision of 2). + # The default level of precision is 3. # # ==== Examples - # number_with_precision(111.2345) # => 111.235 - # number_with_precision(111.2345, 2) # => 111.23 - # number_with_precision(13, 5) # => 13.00000 - # number_with_precision(389.32314, 0) # => 389 - def number_with_precision(number, precision=3) - "%01.#{precision}f" % ((Float(number) * (10 ** precision)).round.to_f / 10 ** precision) + # 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 + # + # 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 + def number_with_precision(number, *args) + options = args.extract_options! + unless args.empty? + options[:precision] = args[0] || 3 + end + options.reverse_merge!(:precision => 3) + "%01.#{options[:precision]}f" % + ((Float(number) * (10 ** options[:precision])).round.to_f / 10 ** options[:precision]) rescue number end # Formats the bytes in +size+ into a more understandable representation - # (e.g., giving it 1500 yields 1.5 KB). This method is useful for + # (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 change the default - # precision of 1 using the precision parameter +precision+. + # precision of 1 using the precision parameter <tt>:precision</tt>. # # ==== Examples # number_to_human_size(123) # => 123 Bytes @@ -166,17 +192,28 @@ module ActionView # 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 + # + # 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) # => 4 MB - def number_to_human_size(size, precision=1) - size = Kernel.Float(size) + # number_to_human_size(483989, 0) # => 473 KB + def number_to_human_size(size, *args) + options = args.extract_options! + unless args.empty? + options[:precision] = args[0] || 1 + end + options.reverse_merge!(:precision => 1) + + size = Float(size) case when size.to_i == 1; "1 Byte" when size < 1.kilobyte; "%d Bytes" % size - when size < 1.megabyte; "%.#{precision}f KB" % (size / 1.0.kilobyte) - when size < 1.gigabyte; "%.#{precision}f MB" % (size / 1.0.megabyte) - when size < 1.terabyte; "%.#{precision}f GB" % (size / 1.0.gigabyte) - else "%.#{precision}f TB" % (size / 1.0.terabyte) + when size < 1.megabyte; "%.#{options[:precision]}f KB" % (size / 1.0.kilobyte) + when size < 1.gigabyte; "%.#{options[:precision]}f MB" % (size / 1.0.megabyte) + when size < 1.terabyte; "%.#{options[:precision]}f GB" % (size / 1.0.gigabyte) + else "%.#{options[:precision]}f TB" % (size / 1.0.terabyte) end.sub(/([0-9]\.\d*?)0+ /, '\1 ' ).sub(/\. /,' ') rescue nil diff --git a/actionpack/lib/action_view/helpers/prototype_helper.rb b/actionpack/lib/action_view/helpers/prototype_helper.rb index 576ca84bcc..cb4b53a9f7 100644 --- a/actionpack/lib/action_view/helpers/prototype_helper.rb +++ b/actionpack/lib/action_view/helpers/prototype_helper.rb @@ -3,25 +3,25 @@ require 'set' module ActionView module Helpers # Prototype[http://www.prototypejs.org/] is a JavaScript library that provides - # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation, + # DOM[http://en.wikipedia.org/wiki/Document_Object_Model] manipulation, # Ajax[http://www.adaptivepath.com/publications/essays/archives/000385.php] - # functionality, and more traditional object-oriented facilities for JavaScript. + # functionality, and more traditional object-oriented facilities for JavaScript. # This module provides a set of helpers to make it more convenient to call - # functions from Prototype using Rails, including functionality to call remote - # Rails methods (that is, making a background request to a Rails action) using Ajax. - # This means that you can call actions in your controllers without - # reloading the page, but still update certain parts of it using + # functions from Prototype using Rails, including functionality to call remote + # Rails methods (that is, making a background request to a Rails action) using Ajax. + # This means that you can call actions in your controllers without + # reloading the page, but still update certain parts of it using # injections into the DOM. A common use case is having a form that adds # a new element to a list without reloading the page or updating a shopping # cart total when a new item is added. # # == Usage - # To be able to use these helpers, you must first include the Prototype - # JavaScript framework in your pages. + # To be able to use these helpers, you must first include the Prototype + # JavaScript framework in your pages. # # javascript_include_tag 'prototype' # - # (See the documentation for + # (See the documentation for # ActionView::Helpers::JavaScriptHelper for more information on including # this and other JavaScript files in your Rails templates.) # @@ -29,7 +29,7 @@ module ActionView # # link_to_remote "Add to cart", # :url => { :action => "add", :id => product.id }, - # :update => { :success => "cart", :failure => "error" } + # :update => { :success => "cart", :failure => "error" } # # ...through a form... # @@ -50,8 +50,8 @@ module ActionView # :update => :hits, # :with => 'query' # %> - # - # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than + # + # As you can see, there are numerous ways to use Prototype's Ajax functions (and actually more than # are listed here); check out the documentation for each method to find out more about its usage and options. # # === Common Options @@ -63,7 +63,7 @@ module ActionView # When building your action handlers (that is, the Rails actions that receive your background requests), it's # important to remember a few things. First, whatever your action would normally return to the browser, it will # return to the Ajax call. As such, you typically don't want to render with a layout. This call will cause - # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up. + # the layout to be transmitted back to your page, and, if you have a full HTML/CSS, will likely mess a lot of things up. # You can turn the layout off on particular actions by doing the following: # # class SiteController < ActionController::Base @@ -74,8 +74,8 @@ module ActionView # # render :layout => false # - # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the - # method that Ajax uses to make background requests) method. + # You can tell the type of request from within your action using the <tt>request.xhr?</tt> (XmlHttpRequest, the + # method that Ajax uses to make background requests) method. # def name # # Is this an XmlHttpRequest request? # if (request.xhr?) @@ -93,7 +93,7 @@ module ActionView # # Dropping this in your ApplicationController turns the layout off for every request that is an "xhr" request. # - # If you are just returning a little data or don't want to build a template for your output, you may opt to simply + # If you are just returning a little data or don't want to build a template for your output, you may opt to simply # render text output, like this: # # render :text => 'Return this from my method!' @@ -103,7 +103,7 @@ module ActionView # # == Updating multiple elements # See JavaScriptGenerator for information on updating multiple elements - # on the page in an Ajax response. + # on the page in an Ajax response. module PrototypeHelper unless const_defined? :CALLBACKS CALLBACKS = Set.new([ :uninitialized, :loading, :loaded, @@ -114,64 +114,64 @@ module ActionView :form, :with, :update, :script ]).merge(CALLBACKS) end - # Returns a link to a remote action defined by <tt>options[:url]</tt> - # (using the url_for format) that's called in the background using + # Returns a link to a remote action defined by <tt>options[:url]</tt> + # (using the url_for format) that's called in the background using # XMLHttpRequest. The result of that request can then be inserted into a - # DOM object whose id can be specified with <tt>options[:update]</tt>. + # DOM object whose id can be specified with <tt>options[:update]</tt>. # Usually, the result would be a partial prepared by the controller with - # render :partial. + # render :partial. # # Examples: - # # Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true}); + # # Generates: <a href="#" onclick="new Ajax.Updater('posts', '/blog/destroy/3', {asynchronous:true, evalScripts:true}); # # return false;">Delete this post</a> - # link_to_remote "Delete this post", :update => "posts", + # link_to_remote "Delete this post", :update => "posts", # :url => { :action => "destroy", :id => post.id } # - # # Generates: <a href="#" onclick="new Ajax.Updater('emails', '/mail/list_emails', {asynchronous:true, evalScripts:true}); + # # Generates: <a href="#" onclick="new Ajax.Updater('emails', '/mail/list_emails', {asynchronous:true, evalScripts:true}); # # return false;"><img alt="Refresh" src="/images/refresh.png?" /></a> - # link_to_remote(image_tag("refresh"), :update => "emails", + # link_to_remote(image_tag("refresh"), :update => "emails", # :url => { :action => "list_emails" }) - # + # # You can override the generated HTML options by specifying a hash in # <tt>options[:html]</tt>. - # + # # link_to_remote "Delete this post", :update => "posts", - # :url => post_url(@post), :method => :delete, - # :html => { :class => "destructive" } + # :url => post_url(@post), :method => :delete, + # :html => { :class => "destructive" } # # You can also specify a hash for <tt>options[:update]</tt> to allow for - # easy redirection of output to an other DOM element if a server-side + # easy redirection of output to an other DOM element if a server-side # error occurs: # # Example: - # # Generates: <a href="#" onclick="new Ajax.Updater({success:'posts',failure:'error'}, '/blog/destroy/5', + # # Generates: <a href="#" onclick="new Ajax.Updater({success:'posts',failure:'error'}, '/blog/destroy/5', # # {asynchronous:true, evalScripts:true}); return false;">Delete this post</a> # link_to_remote "Delete this post", # :url => { :action => "destroy", :id => post.id }, # :update => { :success => "posts", :failure => "error" } # - # Optionally, you can use the <tt>options[:position]</tt> parameter to - # influence how the target DOM element is updated. It must be one of + # Optionally, you can use the <tt>options[:position]</tt> parameter to + # influence how the target DOM element is updated. It must be one of # <tt>:before</tt>, <tt>:top</tt>, <tt>:bottom</tt>, or <tt>:after</tt>. # # The method used is by default POST. You can also specify GET or you # can simulate PUT or DELETE over POST. All specified with <tt>options[:method]</tt> # # Example: - # # Generates: <a href="#" onclick="new Ajax.Request('/person/4', {asynchronous:true, evalScripts:true, method:'delete'}); + # # Generates: <a href="#" onclick="new Ajax.Request('/person/4', {asynchronous:true, evalScripts:true, method:'delete'}); # # return false;">Destroy</a> # link_to_remote "Destroy", :url => person_url(:id => person), :method => :delete # - # By default, these remote requests are processed asynchronous during - # which various JavaScript callbacks can be triggered (for progress - # indicators and the likes). All callbacks get access to the - # <tt>request</tt> object, which holds the underlying XMLHttpRequest. + # By default, these remote requests are processed asynchronous during + # which various JavaScript callbacks can be triggered (for progress + # indicators and the likes). All callbacks get access to the + # <tt>request</tt> object, which holds the underlying XMLHttpRequest. # # To access the server response, use <tt>request.responseText</tt>, to # find out the HTTP status, use <tt>request.status</tt>. # # Example: - # # Generates: <a href="#" onclick="new Ajax.Request('/words/undo?n=33', {asynchronous:true, evalScripts:true, + # # Generates: <a href="#" onclick="new Ajax.Request('/words/undo?n=33', {asynchronous:true, evalScripts:true, # # onComplete:function(request){undoRequestCompleted(request)}}); return false;">hello</a> # word = 'hello' # link_to_remote word, @@ -180,43 +180,43 @@ module ActionView # # The callbacks that may be specified are (in order): # - # <tt>:loading</tt>:: Called when the remote document is being + # <tt>:loading</tt>:: Called when the remote document is being # loaded with data by the browser. # <tt>:loaded</tt>:: Called when the browser has finished loading # the remote document. - # <tt>:interactive</tt>:: Called when the user can interact with the - # remote document, even though it has not + # <tt>:interactive</tt>:: Called when the user can interact with the + # remote document, even though it has not # finished loading. # <tt>:success</tt>:: Called when the XMLHttpRequest is completed, # and the HTTP status code is in the 2XX range. # <tt>:failure</tt>:: Called when the XMLHttpRequest is completed, # and the HTTP status code is not in the 2XX # range. - # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete - # (fires after success/failure if they are + # <tt>:complete</tt>:: Called when the XMLHttpRequest is complete + # (fires after success/failure if they are # present). - # - # You can further refine <tt>:success</tt> and <tt>:failure</tt> by + # + # You can further refine <tt>:success</tt> and <tt>:failure</tt> by # adding additional callbacks for specific status codes. # # Example: - # # Generates: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true, - # # on404:function(request){alert('Not found...? Wrong URL...?')}, + # # Generates: <a href="#" onclick="new Ajax.Request('/testing/action', {asynchronous:true, evalScripts:true, + # # on404:function(request){alert('Not found...? Wrong URL...?')}, # # onFailure:function(request){alert('HTTP Error ' + request.status + '!')}}); return false;">hello</a> # link_to_remote word, # :url => { :action => "action" }, # 404 => "alert('Not found...? Wrong URL...?')", # :failure => "alert('HTTP Error ' + request.status + '!')" # - # A status code callback overrides the success/failure handlers if + # A status code callback overrides the success/failure handlers if # present. # # If you for some reason or another need synchronous processing (that'll - # block the browser while the request is happening), you can specify + # block the browser while the request is happening), you can specify # <tt>options[:type] = :synchronous</tt>. # # You can customize further browser side call logic by passing in - # JavaScript code snippets via some optional parameters. In their order + # JavaScript code snippets via some optional parameters. In their order # of use these are: # # <tt>:confirm</tt>:: Adds confirmation dialog. @@ -228,7 +228,7 @@ module ActionView # <tt>:after</tt>:: Called immediately after request was # initiated and before <tt>:loading</tt>. # <tt>:submit</tt>:: Specifies the DOM element ID that's used - # as the parent of the form elements. By + # as the parent of the form elements. By # default this is the current form, but # it could just as well be the ID of a # table row or any other DOM element. @@ -238,10 +238,10 @@ module ActionView # URL query string. # # Example: - # + # # :with => "'name=' + $('name').value" # - # You can generate a link that uses AJAX in the general case, while + # You can generate a link that uses AJAX in the general case, while # degrading gracefully to plain link behavior in the absence of # JavaScript by setting <tt>html_options[:href]</tt> to an alternate URL. # Note the extra curly braces around the <tt>options</tt> hash separate @@ -251,7 +251,7 @@ module ActionView # link_to_remote "Delete this post", # { :update => "posts", :url => { :action => "destroy", :id => post.id } }, # :href => url_for(:action => "destroy", :id => post.id) - def link_to_remote(name, options = {}, html_options = nil) + def link_to_remote(name, options = {}, html_options = nil) link_to_function(name, remote_function(options), html_options || options.delete(:html)) end @@ -262,15 +262,15 @@ module ActionView # and defining callbacks is the same as link_to_remote. # Examples: # # Call get_averages and put its results in 'avg' every 10 seconds - # # Generates: - # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages', + # # Generates: + # # new PeriodicalExecuter(function() {new Ajax.Updater('avg', '/grades/get_averages', # # {asynchronous:true, evalScripts:true})}, 10) # periodically_call_remote(:url => { :action => 'get_averages' }, :update => 'avg') # # # Call invoice every 10 seconds with the id of the customer # # If it succeeds, update the invoice DIV; if it fails, update the error DIV # # Generates: - # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'}, + # # new PeriodicalExecuter(function() {new Ajax.Updater({success:'invoice',failure:'error'}, # # '/testing/invoice/16', {asynchronous:true, evalScripts:true})}, 10) # periodically_call_remote(:url => { :action => 'invoice', :id => customer.id }, # :update => { :success => "invoice", :failure => "error" } @@ -286,11 +286,11 @@ module ActionView javascript_tag(code) end - # Returns a form tag that will submit using XMLHttpRequest in the - # background instead of the regular reloading POST arrangement. Even + # Returns a form tag that will submit using XMLHttpRequest in the + # background instead of the regular reloading POST arrangement. Even # though it's using JavaScript to serialize the form elements, the form # submission will work just like a regular submission as viewed by the - # receiving side (all elements available in <tt>params</tt>). The options for + # receiving side (all elements available in <tt>params</tt>). The options for # specifying the target with <tt>:url</tt> and defining callbacks is the same as # +link_to_remote+. # @@ -299,21 +299,21 @@ module ActionView # # Example: # # Generates: - # # <form action="/some/place" method="post" onsubmit="new Ajax.Request('', + # # <form action="/some/place" method="post" onsubmit="new Ajax.Request('', # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;"> - # form_remote_tag :html => { :action => + # form_remote_tag :html => { :action => # url_for(:controller => "some", :action => "place") } # # The Hash passed to the <tt>:html</tt> key is equivalent to the options (2nd) # argument in the FormTagHelper.form_tag method. # - # By default the fall-through action is the same as the one specified in + # By default the fall-through action is the same as the one specified in # the <tt>:url</tt> (and the default method is <tt>:post</tt>). # # form_remote_tag also takes a block, like form_tag: # # Generates: - # # <form action="/" method="post" onsubmit="new Ajax.Request('/', - # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); + # # <form action="/" method="post" onsubmit="new Ajax.Request('/', + # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); # # return false;"> <div><input name="commit" type="submit" value="Save" /></div> # # </form> # <% form_remote_tag :url => '/posts' do -%> @@ -323,19 +323,19 @@ module ActionView options[:form] = true options[:html] ||= {} - options[:html][:onsubmit] = - (options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") + + options[:html][:onsubmit] = + (options[:html][:onsubmit] ? options[:html][:onsubmit] + "; " : "") + "#{remote_function(options)}; return false;" form_tag(options[:html].delete(:action) || url_for(options[:url]), options[:html], &block) end - # Creates a form that will submit using XMLHttpRequest in the background - # instead of the regular reloading POST arrangement and a scope around a + # Creates a form that will submit using XMLHttpRequest in the background + # instead of the regular reloading POST arrangement and a scope around a # specific resource that is used as a base for questioning about - # values for the fields. + # values for the fields. # - # === Resource + # === Resource # # Example: # <% remote_form_for(@post) do |f| %> @@ -348,7 +348,7 @@ module ActionView # ... # <% end %> # - # === Nested Resource + # === Nested Resource # # Example: # <% remote_form_for([@post, @comment]) do |f| %> @@ -387,23 +387,23 @@ module ActionView concat('</form>') end alias_method :form_remote_for, :remote_form_for - + # Returns a button input tag with the element name of +name+ and a value (i.e., display text) of +value+ # that will submit form using XMLHttpRequest in the background instead of a regular POST request that - # reloads the page. + # reloads the page. # # # Create a button that submits to the create action - # # - # # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create', - # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); + # # + # # Generates: <input name="create_btn" onclick="new Ajax.Request('/testing/create', + # # {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); # # return false;" type="button" value="Create" /> # <%= button_to_remote 'create_btn', 'Create', :url => { :action => 'create' } %> # # # Submit to the remote action update and update the DIV succeed or fail based # # on the success or failure of the request # # - # # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'}, - # # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); + # # Generates: <input name="update_btn" onclick="new Ajax.Updater({success:'succeed',failure:'fail'}, + # # '/testing/update', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this.form)}); # # return false;" type="button" value="Update" /> # <%= button_to_remote 'update_btn', 'Update', :url => { :action => 'update' }, # :update => { :success => "succeed", :failure => "fail" } @@ -423,7 +423,7 @@ module ActionView tag("input", options[:html], false) end alias_method :submit_to_remote, :button_to_remote - + # Returns '<tt>eval(request.responseText)</tt>' which is the JavaScript function # that +form_remote_tag+ can call in <tt>:complete</tt> to evaluate a multiple # update return document using +update_element_function+ calls. @@ -433,11 +433,11 @@ module ActionView # Returns the JavaScript needed for a remote function. # Takes the same arguments as link_to_remote. - # + # # Example: - # # Generates: <select id="options" onchange="new Ajax.Updater('options', + # # Generates: <select id="options" onchange="new Ajax.Updater('options', # # '/testing/update_options', {asynchronous:true, evalScripts:true})"> - # <select id="options" onchange="<%= remote_function(:update => "options", + # <select id="options" onchange="<%= remote_function(:update => "options", # :url => { :action => :update_options }) %>"> # <option value="0">Hello</option> # <option value="1">World</option> @@ -455,7 +455,7 @@ module ActionView update << "'#{options[:update]}'" end - function = update.empty? ? + function = update.empty? ? "new Ajax.Request(" : "new Ajax.Updater(#{update}, " @@ -476,9 +476,9 @@ module ActionView # callback when its contents have changed. The default callback is an # Ajax call. By default the value of the observed field is sent as a # parameter with the Ajax call. - # + # # Example: - # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest', + # # Generates: new Form.Element.Observer('suggest', 0.25, function(element, value) {new Ajax.Updater('suggest', # # '/testing/find_suggestion', {asynchronous:true, evalScripts:true, parameters:'q=' + value})}) # <%= observe_field :suggest, :url => { :action => :find_suggestion }, # :frequency => 0.25, @@ -500,14 +500,14 @@ module ActionView # new Form.Element.Observer('glass', 1, function(element, value) {alert('Element changed')}) # The element parameter is the DOM element being observed, and the value is its value at the # time the observer is triggered. - # + # # Additional options are: # <tt>:frequency</tt>:: The frequency (in seconds) at which changes to # this field will be detected. Not setting this # option at all or to a value equal to or less than # zero will use event based observation instead of # time based observation. - # <tt>:update</tt>:: Specifies the DOM ID of the element whose + # <tt>:update</tt>:: Specifies the DOM ID of the element whose # innerHTML should be updated with the # XMLHttpRequest response text. # <tt>:with</tt>:: A JavaScript expression specifying the parameters @@ -518,7 +518,7 @@ module ActionView # variable +value+. # # Examples - # + # # :with => "'my_custom_key=' + value" # :with => "'person[name]=' + prompt('New name')" # :with => "Form.Element.serialize('other-field')" @@ -544,7 +544,7 @@ module ActionView # observe_field 'book_title', # :url => 'http://example.com/books/edit/1', # :with => 'title' - # + # # # Sends params: {:book_title => 'Title of the book'} when the focus leaves # # the input field. # observe_field 'book_title', @@ -558,7 +558,7 @@ module ActionView build_observer('Form.Element.EventObserver', field_id, options) end end - + # Observes the form with the DOM ID specified by +form_id+ and calls a # callback when its contents have changed. The default callback is an # Ajax call. By default all fields of the observed field are sent as @@ -574,16 +574,18 @@ module ActionView build_observer('Form.EventObserver', form_id, options) end end - - # All the methods were moved to GeneratorMethods so that + + # All the methods were moved to GeneratorMethods so that # #include_helpers_from_context has nothing to overwrite. class JavaScriptGenerator #:nodoc: def initialize(context, &block) #:nodoc: @context, @lines = context, [] include_helpers_from_context - @context.instance_exec(self, &block) + @context.with_output_buffer(@lines) do + @context.instance_exec(self, &block) + end end - + private def include_helpers_from_context @context.extended_by.each do |mod| @@ -591,17 +593,17 @@ module ActionView end extend GeneratorMethods end - - # JavaScriptGenerator generates blocks of JavaScript code that allow you - # to change the content and presentation of multiple DOM elements. Use + + # JavaScriptGenerator generates blocks of JavaScript code that allow you + # to change the content and presentation of multiple DOM elements. Use # this in your Ajax response bodies, either in a <script> tag or as plain # JavaScript sent with a Content-type of "text/javascript". # - # Create new instances with PrototypeHelper#update_page or with - # ActionController::Base#render, then call +insert_html+, +replace_html+, - # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in - # methods on the yielded generator in any order you like to modify the - # content and appearance of the current page. + # Create new instances with PrototypeHelper#update_page or with + # ActionController::Base#render, then call +insert_html+, +replace_html+, + # +remove+, +show+, +hide+, +visual_effect+, or any other of the built-in + # methods on the yielded generator in any order you like to modify the + # content and appearance of the current page. # # Example: # @@ -614,12 +616,12 @@ module ActionView # page.visual_effect :highlight, 'list' # page.hide 'status-indicator', 'cancel-link' # end - # + # # # Helper methods can be used in conjunction with JavaScriptGenerator. - # When a helper method is called inside an update block on the +page+ + # When a helper method is called inside an update block on the +page+ # object, that method will also have access to a +page+ object. - # + # # Example: # # module ApplicationHelper @@ -655,7 +657,7 @@ module ActionView # end # end # - # You can also use PrototypeHelper#update_page_tag instead of + # You can also use PrototypeHelper#update_page_tag instead of # PrototypeHelper#update_page to wrap the generated JavaScript in a # <script> tag. module GeneratorMethods @@ -668,7 +670,7 @@ module ActionView end end end - + # Returns a element reference by finding it through +id+ in the DOM. This element can then be # used for further method calls. Examples: # @@ -689,31 +691,31 @@ module ActionView JavaScriptElementProxy.new(self, ActionController::RecordIdentifier.dom_id(id)) end end - - # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript + + # Returns an object whose <tt>to_json</tt> evaluates to +code+. Use this to pass a literal JavaScript # expression as an argument to another JavaScriptGenerator method. def literal(code) ActiveSupport::JSON::Variable.new(code.to_s) end - + # Returns a collection reference by finding it through a CSS +pattern+ in the DOM. This collection can then be # used for further method calls. Examples: # # page.select('p') # => $$('p'); # page.select('p.welcome b').first # => $$('p.welcome b').first(); # page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide(); - # + # # You can also use prototype enumerations with the collection. Observe: - # + # # # Generates: $$('#items li').each(function(value) { value.hide(); }); # page.select('#items li').each do |value| # value.hide - # end + # end # - # Though you can call the block param anything you want, they are always rendered in the + # Though you can call the block param anything you want, they are always rendered in the # javascript as 'value, index.' Other enumerations, like collect() return the last statement: # - # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); }); + # # Generates: var hidden = $$('#items li').collect(function(value, index) { return value.hide(); }); # page.select('#items li').collect('hidden') do |item| # item.hide # end @@ -721,13 +723,13 @@ module ActionView def select(pattern) JavaScriptElementCollectionProxy.new(self, pattern) end - + # Inserts HTML at the specified +position+ relative to the DOM element # identified by the given +id+. - # + # # +position+ may be one of: - # - # <tt>:top</tt>:: HTML is inserted inside the element, before the + # + # <tt>:top</tt>:: HTML is inserted inside the element, before the # element's existing content. # <tt>:bottom</tt>:: HTML is inserted inside the element, after the # element's existing content. @@ -750,7 +752,7 @@ module ActionView insertion = position.to_s.camelize call "new Insertion.#{insertion}", id, render(*options_for_render) end - + # Replaces the inner HTML of the DOM element with the given +id+. # # +options_for_render+ may be either a string of HTML to insert, or a hash @@ -764,7 +766,7 @@ module ActionView def replace_html(id, *options_for_render) call 'Element.update', id, render(*options_for_render) end - + # Replaces the "outer HTML" (i.e., the entire element, not just its # contents) of the DOM element with the given +id+. # @@ -786,7 +788,7 @@ module ActionView # </div> # # # Insert a new person - # # + # # # # Generates: new Insertion.Bottom({object: "Matz", partial: "person"}, ""); # page.insert_html :bottom, :partial => 'person', :object => @person # @@ -798,7 +800,7 @@ module ActionView def replace(id, *options_for_render) call 'Element.replace', id, render(*options_for_render) end - + # Removes the DOM elements with the given +ids+ from the page. # # Example: @@ -810,9 +812,9 @@ module ActionView def remove(*ids) loop_on_multiple_args 'Element.remove', ids end - + # Shows hidden DOM elements with the given +ids+. - # + # # Example: # # # Show a few people @@ -822,7 +824,7 @@ module ActionView def show(*ids) loop_on_multiple_args 'Element.show', ids end - + # Hides the visible DOM elements with the given +ids+. # # Example: @@ -832,9 +834,9 @@ module ActionView # page.hide 'person_29', 'person_9', 'person_0' # def hide(*ids) - loop_on_multiple_args 'Element.hide', ids + loop_on_multiple_args 'Element.hide', ids end - + # Toggles the visibility of the DOM elements with the given +ids+. # Example: # @@ -844,9 +846,9 @@ module ActionView # page.toggle 'person_14', 'person_12', 'person_23' # Shows the previously hidden elements # def toggle(*ids) - loop_on_multiple_args 'Element.toggle', ids + loop_on_multiple_args 'Element.toggle', ids end - + # Displays an alert dialog with the given +message+. # # Example: @@ -856,21 +858,21 @@ module ActionView def alert(message) call 'alert', message end - + # Redirects the browser to the given +location+ using JavaScript, in the same form as +url_for+. # # Examples: # # # Generates: window.location.href = "/mycontroller"; # page.redirect_to(:action => 'index') - # + # # # Generates: window.location.href = "/account/signup"; # page.redirect_to(:controller => 'account', :action => 'signup') def redirect_to(location) url = location.is_a?(String) ? location : @context.url_for(location) record "window.location.href = #{url.inspect}" end - + # Reloads the browser's current +location+ using JavaScript # # Examples: @@ -884,17 +886,17 @@ module ActionView # Calls the JavaScript +function+, optionally with the given +arguments+. # # If a block is given, the block will be passed to a new JavaScriptGenerator; - # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt> + # the resulting JavaScript code will then be wrapped inside <tt>function() { ... }</tt> # and passed as the called function's final argument. - # + # # Examples: # # # Generates: Element.replace(my_element, "My content to replace with.") # page.call 'Element.replace', 'my_element', "My content to replace with." - # + # # # Generates: alert('My message!') # page.call 'alert', 'My message!' - # + # # # Generates: # # my_method(function() { # # $("one").show(); @@ -907,7 +909,7 @@ module ActionView def call(function, *arguments, &block) record "#{function}(#{arguments_for_call(arguments, block)})" end - + # Assigns the JavaScript +variable+ the given +value+. # # Examples: @@ -918,13 +920,13 @@ module ActionView # # Generates: record_count = 33; # page.assign 'record_count', 33 # - # # Generates: tabulated_total = 47 + # # Generates: tabulated_total = 47 # page.assign 'tabulated_total', @total_from_cart # def assign(variable, value) record "#{variable} = #{javascript_object_for(value)}" end - + # Writes raw JavaScript to the page. # # Example: @@ -933,10 +935,10 @@ module ActionView def <<(javascript) @lines << javascript end - + # Executes the content of the block after a delay of +seconds+. Example: # - # # Generates: + # # Generates: # # setTimeout(function() { # # ; # # new Effect.Fade("notice",{}); @@ -949,13 +951,13 @@ module ActionView yield record "}, #{(seconds * 1000).to_i})" end - - # Starts a script.aculo.us visual effect. See + + # Starts a script.aculo.us visual effect. See # ActionView::Helpers::ScriptaculousHelper for more information. def visual_effect(name, id = nil, options = {}) record @context.send(:visual_effect, name, id, options) end - + # Creates a script.aculo.us sortable element. Useful # to recreate sortable elements after items get added # or deleted. @@ -963,66 +965,66 @@ module ActionView def sortable(id, options = {}) record @context.send(:sortable_element_js, id, options) end - + # Creates a script.aculo.us draggable element. # See ActionView::Helpers::ScriptaculousHelper for more information. def draggable(id, options = {}) record @context.send(:draggable_element_js, id, options) end - + # Creates a script.aculo.us drop receiving element. # See ActionView::Helpers::ScriptaculousHelper for more information. def drop_receiving(id, options = {}) record @context.send(:drop_receiving_element_js, id, options) end - + private def loop_on_multiple_args(method, ids) - record(ids.size>1 ? - "#{javascript_object_for(ids)}.each(#{method})" : + record(ids.size>1 ? + "#{javascript_object_for(ids)}.each(#{method})" : "#{method}(#{ids.first.to_json})") end - + def page self end - + def record(line) returning line = "#{line.to_s.chomp.gsub(/\;\z/, '')};" do self << line end end - + def render(*options_for_render) old_format = @context && @context.template_format @context.template_format = :html if @context - Hash === options_for_render.first ? - @context.render(*options_for_render) : + Hash === options_for_render.first ? + @context.render(*options_for_render) : options_for_render.first.to_s ensure @context.template_format = old_format if @context end - + def javascript_object_for(object) object.respond_to?(:to_json) ? object.to_json : object.inspect end - + def arguments_for_call(arguments, block = nil) arguments << block_to_function(block) if block arguments.map { |argument| javascript_object_for(argument) }.join ', ' end - + def block_to_function(block) generator = self.class.new(@context, &block) literal("function() { #{generator.to_s} }") - end + end def method_missing(method, *arguments) JavaScriptProxy.new(self, method.to_s.camelize) end end end - + # Yields a JavaScriptGenerator and returns the generated JavaScript code. # Use this to update multiple elements on a page in an Ajax response. # See JavaScriptGenerator for more information. @@ -1035,13 +1037,13 @@ module ActionView def update_page(&block) JavaScriptGenerator.new(@template, &block).to_s end - + # Works like update_page but wraps the generated JavaScript in a <script> # tag. Use this to include generated JavaScript in an ERb template. # See JavaScriptGenerator for more information. # # +html_options+ may be a hash of <script> attributes to be passed - # to ActionView::Helpers::JavaScriptHelper#javascript_tag. + # to ActionView::Helpers::JavaScriptHelper#javascript_tag. def update_page_tag(html_options = {}, &block) javascript_tag update_page(&block), html_options end @@ -1049,7 +1051,7 @@ module ActionView protected def options_for_ajax(options) js_options = build_callbacks(options) - + js_options['asynchronous'] = options[:type] != :synchronous js_options['method'] = method_option_to_s(options[:method]) if options[:method] js_options['insertion'] = "Insertion.#{options[:position].to_s.camelize}" if options[:position] @@ -1062,7 +1064,7 @@ module ActionView elsif options[:with] js_options['parameters'] = options[:with] end - + if protect_against_forgery? && !options[:form] if js_options['parameters'] js_options['parameters'] << " + '&" @@ -1071,14 +1073,14 @@ module ActionView end js_options['parameters'] << "#{request_forgery_protection_token}=' + encodeURIComponent('#{escape_javascript form_authenticity_token}')" end - + options_for_javascript(js_options) end - def method_option_to_s(method) + def method_option_to_s(method) (method.is_a?(String) and !method.index("'").nil?) ? method : "'#{method}'" end - + def build_observer(klass, name, options = {}) if options[:with] && (options[:with] !~ /[\{=(.]/) options[:with] = "'#{options[:with]}=' + encodeURIComponent(value)" @@ -1095,7 +1097,7 @@ module ActionView javascript << ")" javascript_tag(javascript) end - + def build_callbacks(options) callbacks = {} options.each do |callback, code| @@ -1108,7 +1110,7 @@ module ActionView end end - # Converts chained method calls on DOM proxy elements into JavaScript chains + # Converts chained method calls on DOM proxy elements into JavaScript chains class JavaScriptProxy < ActiveSupport::BasicObject #:nodoc: def initialize(generator, root = nil) @@ -1124,7 +1126,7 @@ module ActionView call("#{method.to_s.camelize(:lower)}", *arguments, &block) end end - + def call(function, *arguments, &block) append_to_function_chain!("#{function}(#{@generator.send(:arguments_for_call, arguments, block)})") self @@ -1133,23 +1135,23 @@ module ActionView def assign(variable, value) append_to_function_chain!("#{variable} = #{@generator.send(:javascript_object_for, value)}") end - + def function_chain @function_chain ||= @generator.instance_variable_get(:@lines) end - + def append_to_function_chain!(call) function_chain[-1].chomp!(';') function_chain[-1] += ".#{call};" end end - + class JavaScriptElementProxy < JavaScriptProxy #:nodoc: def initialize(generator, id) @id = id super(generator, "$(#{id.to_json})") end - + # Allows access of element attributes through +attribute+. Examples: # # page['foo']['style'] # => $('foo').style; @@ -1160,11 +1162,11 @@ module ActionView append_to_function_chain!(attribute) self end - + def []=(variable, value) assign(variable, value) end - + def replace_html(*options_for_render) call 'update', @generator.send(:render, *options_for_render) end @@ -1172,11 +1174,11 @@ module ActionView def replace(*options_for_render) call 'replace', @generator.send(:render, *options_for_render) end - + def reload(options_for_replace = {}) replace(options_for_replace.merge({ :partial => @id.to_s })) end - + end class JavaScriptVariableProxy < JavaScriptProxy #:nodoc: @@ -1195,7 +1197,7 @@ module ActionView def to_json(options = nil) @variable end - + private def append_to_function_chain!(call) @generator << @variable if @empty @@ -1213,7 +1215,7 @@ module ActionView def initialize(generator, pattern) super(generator, @pattern = pattern) end - + def each_slice(variable, number, &block) if block enumerate :eachSlice, :variable => variable, :method_args => [number], :yield_args => %w(value index), :return => true, &block @@ -1222,18 +1224,18 @@ module ActionView append_enumerable_function!("eachSlice(#{number.to_json});") end end - + def grep(variable, pattern, &block) enumerate :grep, :variable => variable, :return => true, :method_args => [pattern], :yield_args => %w(value index), &block end - + def in_groups_of(variable, number, fill_with = nil) arguments = [number] arguments << fill_with unless fill_with.nil? add_variable_assignment!(variable) append_enumerable_function!("inGroupsOf(#{arguments_for_call arguments});") - end - + end + def inject(variable, memo, &block) enumerate :inject, :variable => variable, :method_args => [memo], :yield_args => %w(memo value index), :return => true, &block end @@ -1295,13 +1297,13 @@ module ActionView function_chain.push("return #{function_chain.pop.chomp(';')};") end end - + def append_enumerable_function!(call) function_chain[-1].chomp!(';') function_chain[-1] += ".#{call}" end end - + class JavaScriptElementCollectionProxy < JavaScriptCollectionProxy #:nodoc:\ def initialize(generator, pattern) super(generator, "$$(#{pattern.to_json})") diff --git a/actionpack/lib/action_view/helpers/tag_helper.rb b/actionpack/lib/action_view/helpers/tag_helper.rb index 14e4f01a13..de08672d2d 100644 --- a/actionpack/lib/action_view/helpers/tag_helper.rb +++ b/actionpack/lib/action_view/helpers/tag_helper.rb @@ -110,12 +110,18 @@ module ActionView private BLOCK_CALLED_FROM_ERB = 'defined? __in_erb_template' - # 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) + 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 def content_tag_string(name, content, options, escape = true) diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index 3e3452b615..3c9f7230c3 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -34,40 +34,69 @@ module ActionView end if RUBY_VERSION < '1.9' - # If +text+ is longer than +length+, +text+ will be truncated to the length of - # +length+ (defaults to 30) and the last characters will be replaced with the +truncate_string+ - # (defaults to "..."). + # Truncates a given +text+ after a given <tt>:length</tt> if +text+ is longer than <tt>:length</tt> + # (defaults to 30). The last characters will be replaced with the <tt>:omission</tt> (defaults to "..."). # # ==== Examples - # truncate("Once upon a time in a world far far away", 14) - # # => Once upon a... # # truncate("Once upon a time in a world far far away") # # => Once upon a time in a world f... # - # truncate("And they found that many people were sleeping better.", 25, "(clipped)") + # truncate("Once upon a time in a world far far away", :length => 14) + # # => Once upon a... + # + # truncate("And they found that many people were sleeping better.", :length => 25, "(clipped)") # # => And they found that many (clipped) # + # truncate("And they found that many people were sleeping better.", :omission => "... (continued)", :length => 15) + # # => And they found... (continued) + # + # You can still use <tt>truncate</tt> with the old API that accepts the + # +length+ as its optional second and the +ellipsis+ as its + # optional third parameter: + # truncate("Once upon a time in a world far far away", 14) + # # => Once upon a time in a world f... + # # truncate("And they found that many people were sleeping better.", 15, "... (continued)") # # => And they found... (continued) - def truncate(text, length = 30, truncate_string = "...") + def truncate(text, *args) + options = args.extract_options! + unless args.empty? + ActiveSupport::Deprecation.warn('truncate takes an option hash instead of separate ' + + 'length and omission arguments', caller) + + options[:length] = args[0] || 30 + options[:omission] = args[1] || "..." + end + options.reverse_merge!(:length => 30, :omission => "...") + if text - l = length - truncate_string.chars.length + l = options[:length] - options[:omission].chars.length chars = text.chars - (chars.length > length ? chars[0...l] + truncate_string : text).to_s + (chars.length > options[:length] ? chars[0...l] + options[:omission] : text).to_s end end else - def truncate(text, length = 30, truncate_string = "...") #:nodoc: + def truncate(text, *args) #:nodoc: + options = args.extract_options! + unless args.empty? + ActiveSupport::Deprecation.warn('truncate takes an option hash instead of separate ' + + 'length and omission arguments', caller) + + options[:length] = args[0] || 30 + options[:omission] = args[1] || "..." + end + options.reverse_merge!(:length => 30, :omission => "...") + if text - l = length - truncate_string.length - (text.length > length ? text[0...l] + truncate_string : text).to_s + l = options[:length].to_i - options[:omission].length + (text.length > options[:length].to_i ? text[0...l] + options[:omission] : text).to_s end end end # Highlights one or more +phrases+ everywhere in +text+ by inserting it into - # a +highlighter+ string. The highlighter can be specialized by passing +highlighter+ + # 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 # '<strong class="highlight">\1</strong>') # @@ -78,52 +107,75 @@ module ActionView # highlight('You searched for: ruby, rails, dhh', 'actionpack') # # => You searched for: ruby, rails, dhh # - # highlight('You searched for: rails', ['for', 'rails'], '<em>\1</em>') + # highlight('You searched for: rails', ['for', 'rails'], :highlighter => '<em>\1</em>') # # => You searched <em>for</em>: <em>rails</em> # - # 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, highlighter = '<strong class="highlight">\1</strong>') + # 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] || '<strong class="highlight">\1</strong>' + end + options.reverse_merge!(:highlighter => '<strong class="highlight">\1</strong>') + if text.blank? || phrases.blank? text else match = Array(phrases).map { |p| Regexp.escape(p) }.join('|') - text.gsub(/(#{match})/i, highlighter) + text.gsub(/(#{match})/i, options[:highlighter]) end end if RUBY_VERSION < '1.9' # Extracts an excerpt from +text+ that matches the first instance of +phrase+. - # The +radius+ expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters - # defined in +radius+ (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+, - # then the +excerpt_string+ will be prepended/appended accordingly. The resulting string will be stripped in any case. - # If the +phrase+ isn't found, nil is returned. + # The <tt>:radius</tt> option expands the excerpt on each side of the first occurrence of +phrase+ by the number of characters + # defined in <tt>:radius</tt> (which defaults to 100). If the excerpt radius overflows the beginning or end of the +text+, + # 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', 5) - # # => "...s is an exam..." + # excerpt('This is an example', 'an', :radius => 5) + # # => ...s is an exam... # - # excerpt('This is an example', 'is', 5) - # # => "This is a..." + # excerpt('This is an example', 'is', :radius => 5) + # # => This is a... # # excerpt('This is an example', 'is') - # # => "This is an example" + # # => This is an example # - # excerpt('This next thing is an example', 'ex', 2) - # # => "...next..." + # excerpt('This next thing is an example', 'ex', :radius => 2) + # # => ...next... # - # excerpt('This is also an example', 'an', 8, '<chop> ') - # # => "<chop> is also an example" - def excerpt(text, phrase, radius = 100, excerpt_string = "...") + # 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) + options = args.extract_options! + unless args.empty? + options[:radius] = args[0] || 100 + options[:omission] = args[1] || "..." + end + options.reverse_merge!(:radius => 100, :omission => "...") + if text && phrase phrase = Regexp.escape(phrase) if found_pos = text.chars =~ /(#{phrase})/i - start_pos = [ found_pos - radius, 0 ].max - end_pos = [ [ found_pos + phrase.chars.length + radius - 1, 0].max, text.chars.length ].min + start_pos = [ found_pos - options[:radius], 0 ].max + end_pos = [ [ found_pos + phrase.chars.length + options[:radius] - 1, 0].max, text.chars.length ].min - prefix = start_pos > 0 ? excerpt_string : "" - postfix = end_pos < text.chars.length - 1 ? excerpt_string : "" + prefix = start_pos > 0 ? options[:omission] : "" + postfix = end_pos < text.chars.length - 1 ? options[:omission] : "" prefix + text.chars[start_pos..end_pos].strip + postfix else @@ -132,16 +184,23 @@ module ActionView end end else - def excerpt(text, phrase, radius = 100, excerpt_string = "...") #:nodoc: + def excerpt(text, phrase, *args) #:nodoc: + options = args.extract_options! + unless args.empty? + options[:radius] = args[0] || 100 + options[:omission] = args[1] || "..." + end + options.reverse_merge!(:radius => 100, :omission => "...") + if text && phrase phrase = Regexp.escape(phrase) if found_pos = text =~ /(#{phrase})/i - start_pos = [ found_pos - radius, 0 ].max - end_pos = [ [ found_pos + phrase.length + radius - 1, 0].max, text.length ].min + start_pos = [ found_pos - options[:radius], 0 ].max + end_pos = [ [ found_pos + phrase.length + options[:radius] - 1, 0].max, text.length ].min - prefix = start_pos > 0 ? excerpt_string : "" - postfix = end_pos < text.length - 1 ? excerpt_string : "" + prefix = start_pos > 0 ? options[:omission] : "" + postfix = end_pos < text.length - 1 ? options[:omission] : "" prefix + text[start_pos..end_pos].strip + postfix else @@ -176,20 +235,31 @@ module ActionView # (which is 80 by default). # # ==== Examples - # word_wrap('Once upon a time', 4) - # # => Once\nupon\na\ntime - # - # word_wrap('Once upon a time', 8) - # # => Once upon\na time # # word_wrap('Once upon a time') # # => Once upon a time # - # word_wrap('Once upon a time', 1) + # 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... + # + # word_wrap('Once upon a time', :line_width => 8) + # # => Once upon\na time + # + # word_wrap('Once upon a time', :line_width => 1) # # => Once\nupon\na\ntime - def word_wrap(text, line_width = 80) + # + # 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] || 80 + end + options.reverse_merge!(:line_width => 80) + text.split("\n").collect do |line| - line.length > line_width ? line.gsub(/(.{1,#{line_width}})(\s+|$)/, "\\1\n").strip : line + line.length > options[:line_width] ? line.gsub(/(.{1,#{options[:line_width]}})(\s+|$)/, "\\1\n").strip : line end * "\n" end @@ -336,12 +406,32 @@ module ActionView # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.m...</a>. # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>." # - def auto_link(text, link = :all, href_options = {}, &block) + # + # You can still use <tt>auto_link</tt> with the old API that accepts the + # +link+ as its optional second parameter and the +html_options+ hash + # as its optional third parameter: + # post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com." + # auto_link(post_body, :urls) # => Once upon\na time + # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\">http://www.myblog.com</a>. + # Please e-mail me at me@email.com." + # + # auto_link(post_body, :all, :target => "_blank") # => Once upon\na time + # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.myblog.com</a>. + # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>." + def auto_link(text, *args, &block)#link = :all, href_options = {}, &block) return '' if text.blank? - case link - when :all then auto_link_email_addresses(auto_link_urls(text, href_options, &block), &block) - when :email_addresses then auto_link_email_addresses(text, &block) - when :urls then auto_link_urls(text, href_options, &block) + + options = args.size == 2 ? {} : args.extract_options! # this is necessary because the old auto_link API has a Hash as its last parameter + unless args.empty? + options[:link] = args[0] || :all + options[:html] = args[1] || {} + end + options.reverse_merge!(:link => :all, :html => {}) + + case options[:link].to_sym + when :all then auto_link_email_addresses(auto_link_urls(text, options[:html], &block), &block) + when :email_addresses then auto_link_email_addresses(text, &block) + when :urls then auto_link_urls(text, options[:html], &block) end end @@ -477,8 +567,8 @@ module ActionView # Turns all urls into clickable links. If a block is given, each url # is yielded and the result is used as the link text. - def auto_link_urls(text, href_options = {}) - extra_options = tag_options(href_options.stringify_keys) || "" + def auto_link_urls(text, html_options = {}) + extra_options = tag_options(html_options.stringify_keys) || "" text.gsub(AUTO_LINK_RE) do all, a, b, c, d = $&, $1, $2, $3, $4 if a =~ /<a\s/i # don't replace URL's that are already linked @@ -508,4 +598,4 @@ module ActionView end end end -end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb new file mode 100644 index 0000000000..60ac5c8790 --- /dev/null +++ b/actionpack/lib/action_view/helpers/translation_helper.rb @@ -0,0 +1,20 @@ +require 'action_view/helpers/tag_helper' + +module ActionView + module Helpers + module TranslationHelper + def translate(*args) + args << args.extract_options!.merge(:raise => true) + I18n.translate *args + + rescue I18n::MissingTranslationData => e + keys = I18n.send :normalize_translation_keys, e.locale, e.key, e.options[:scope] + content_tag('span', keys.join(', '), :class => 'translation_missing') + end + + def localize(*args) + I18n.localize *args + end + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index 94e1f1d33a..f31502d99d 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -3,8 +3,8 @@ require 'action_view/helpers/javascript_helper' module ActionView module Helpers #:nodoc: # Provides a set of methods for making links and getting URLs that - # depend on the routing subsystem (see ActionController::Routing). - # This allows you to use the same format for links in views + # depend on the routing subsystem (see ActionController::Routing). + # This allows you to use the same format for links in views # and controllers. module UrlHelper include JavaScriptHelper @@ -33,8 +33,8 @@ module ActionView # # If you instead of a hash pass a record (like an Active Record or Active Resource) as the options parameter, # you'll trigger the named route for that record. The lookup will happen on the name of the class. So passing - # a Workshop object will attempt to use the workshop_path route. If you have a nested route, such as - # admin_workshop_path you'll have to call that explicitly (it's impossible for url_for to guess that route). + # a Workshop object will attempt to use the workshop_path route. If you have a nested route, such as + # admin_workshop_path you'll have to call that explicitly (it's impossible for url_for to guess that route). # # ==== Examples # <%= url_for(:action => 'index') %> @@ -62,19 +62,33 @@ module ActionView # <%= url_for(@workshop) %> # # calls @workshop.to_s # # => /workshops/5 + # + # <%= url_for("http://www.example.com") %> + # # => http://www.example.com + # + # <%= url_for(:back) %> + # # if request.env["HTTP_REFERER"] is set to "http://www.example.com" + # # => http://www.example.com + # + # <%= url_for(:back) %> + # # if request.env["HTTP_REFERER"] is not set or is blank + # # => javascript:history.back() def url_for(options = {}) options ||= {} - case options + url = case options + when String + escape = true + options when Hash options = { :only_path => options[:host].nil? }.update(options.symbolize_keys) escape = options.key?(:escape) ? options.delete(:escape) : true - url = @controller.send(:url_for, options) - when String - escape = true - url = options + @controller.send(:url_for, options) + when :back + escape = false + @controller.request.env["HTTP_REFERER"] || 'javascript:history.back()' else escape = false - url = polymorphic_path(options) + polymorphic_path(options) end escape ? escape_once(url) : url @@ -116,8 +130,8 @@ module ActionView # # Note that if the user has JavaScript disabled, the request will fall back # to using GET. If <tt>:href => '#'</tt> is used and the user has JavaScript disabled - # clicking the link will have no effect. If you are relying on the POST - # behavior, your should check for it in your controller's action by using the + # clicking the link will have no effect. If you are relying on the POST + # behavior, your should check for it in your controller's action by using the # request object's methods for <tt>post?</tt>, <tt>delete?</tt> or <tt>put?</tt>. # # You can mix and match the +html_options+ with the exception of @@ -141,8 +155,8 @@ module ActionView # # link_to "Profile", :controller => "profiles", :action => "show", :id => @profile # # => <a href="/profiles/show/1">Profile</a> - # - # Similarly, + # + # Similarly, # # link_to "Profiles", profiles_path # # => <a href="/profiles">Profiles</a> @@ -197,9 +211,9 @@ module ActionView # # => <a href="/images/9" onclick="window.open(this.href,'new_window_name','height=300,width=600');return false;">View Image</a> # # link_to "Delete Image", @image, :confirm => "Are you sure?", :method => :delete - # # => <a href="/images/9" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form'); + # # => <a href="/images/9" onclick="if (confirm('Are you sure?')) { var f = document.createElement('form'); # f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'POST'; f.action = this.href; - # var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); + # var m = document.createElement('input'); m.setAttribute('type', 'hidden'); m.setAttribute('name', '_method'); # m.setAttribute('value', 'delete'); f.appendChild(m);f.submit(); };return false;">Delete Image</a> def link_to(*args, &block) if block_given? @@ -211,14 +225,7 @@ module ActionView options = args.second || {} html_options = args.third - url = case options - when String - options - when :back - @controller.request.env["HTTP_REFERER"] || 'javascript:history.back()' - else - self.url_for(options) - end + url = url_for(options) if html_options html_options = html_options.stringify_keys @@ -228,7 +235,7 @@ module ActionView else tag_options = nil end - + href_attr = "href=\"#{url}\"" unless href "<a #{href_attr}#{tag_options}>#{name || url}</a>" end @@ -260,7 +267,7 @@ module ActionView # * <tt>:confirm</tt> - This will add a JavaScript confirm # prompt with the question specified. If the user accepts, the link is # processed normally, otherwise no action is taken. - # + # # ==== Examples # <%= button_to "New", :action => "new" %> # # => "<form method="post" action="/controller/new" class="button-to"> @@ -286,12 +293,12 @@ module ActionView end form_method = method.to_s == 'get' ? 'get' : 'post' - + request_token_tag = '' if form_method == 'post' && protect_against_forgery? request_token_tag = tag(:input, :type => "hidden", :name => request_forgery_protection_token.to_s, :value => form_authenticity_token) end - + if confirm = html_options.delete("confirm") html_options["onclick"] = "return #{confirm_javascript_function(confirm)};" end @@ -309,7 +316,7 @@ module ActionView # Creates a link tag of the given +name+ using a URL created by the set of # +options+ unless the current request URI is the same as the links, in # which case only the name is returned (or the given block is yielded, if - # one exists). You can give link_to_unless_current a block which will + # one exists). You can give link_to_unless_current a block which will # specialize the default behavior (e.g., show a "Start Here" link rather # than the link's text). # @@ -336,13 +343,13 @@ module ActionView # </ul> # # The implicit block given to link_to_unless_current is evaluated if the current - # action is the action given. So, if we had a comments page and wanted to render a + # action is the action given. So, if we had a comments page and wanted to render a # "Go Back" link instead of a link to the comments page, we could do something like this... - # - # <%= + # + # <%= # link_to_unless_current("Comment", { :controller => 'comments', :action => 'new}) do - # link_to("Go back", { :controller => 'posts', :action => 'index' }) - # end + # link_to("Go back", { :controller => 'posts', :action => 'index' }) + # end # %> def link_to_unless_current(name, options = {}, html_options = {}, &block) link_to_unless current_page?(options), name, options, html_options, &block @@ -359,10 +366,10 @@ module ActionView # # If the user is logged in... # # => <a href="/controller/reply/">Reply</a> # - # <%= + # <%= # link_to_unless(@current_user.nil?, "Reply", { :action => "reply" }) do |name| # link_to(name, { :controller => "accounts", :action => "signup" }) - # end + # end # %> # # If the user is logged in... # # => <a href="/controller/reply/">Reply</a> @@ -391,10 +398,10 @@ module ActionView # # If the user isn't logged in... # # => <a href="/sessions/new/">Login</a> # - # <%= + # <%= # link_to_if(@current_user.nil?, "Login", { :controller => "sessions", :action => "new" }) do # link_to(@current_user.login, { :controller => "accounts", :action => "show", :id => @current_user }) - # end + # end # %> # # If the user isn't logged in... # # => <a href="/sessions/new/">Login</a> @@ -431,20 +438,20 @@ module ActionView # * <tt>:bcc</tt> - Blind Carbon Copy additional recipients on the email. # # ==== Examples - # mail_to "me@domain.com" + # mail_to "me@domain.com" # # => <a href="mailto:me@domain.com">me@domain.com</a> # - # mail_to "me@domain.com", "My email", :encode => "javascript" + # mail_to "me@domain.com", "My email", :encode => "javascript" # # => <script type="text/javascript">eval(unescape('%64%6f%63...%6d%65%6e'))</script> # - # mail_to "me@domain.com", "My email", :encode => "hex" + # mail_to "me@domain.com", "My email", :encode => "hex" # # => <a href="mailto:%6d%65@%64%6f%6d%61%69%6e.%63%6f%6d">My email</a> # - # mail_to "me@domain.com", nil, :replace_at => "_at_", :replace_dot => "_dot_", :class => "email" + # mail_to "me@domain.com", nil, :replace_at => "_at_", :replace_dot => "_dot_", :class => "email" # # => <a href="mailto:me@domain.com" class="email">me_at_domain_dot_com</a> # # mail_to "me@domain.com", "My email", :cc => "ccaddress@domain.com", - # :subject => "This is an example email" + # :subject => "This is an example email" # # => <a href="mailto:me@domain.com?cc=ccaddress@domain.com&subject=This%20is%20an%20example%20email">My email</a> def mail_to(email_address, name = nil, html_options = {}) html_options = html_options.stringify_keys diff --git a/actionpack/lib/action_view/locale/en-US.rb b/actionpack/lib/action_view/locale/en-US.rb new file mode 100644 index 0000000000..3adb199681 --- /dev/null +++ b/actionpack/lib/action_view/locale/en-US.rb @@ -0,0 +1,32 @@ +I18n.backend.store_translations :'en-US', { + :datetime => { + :distance_in_words => { + :half_a_minute => 'half a minute', + :less_than_x_seconds => ['less than 1 second', 'less than {{count}} seconds'], + :x_seconds => ['1 second', '{{count}} seconds'], + :less_than_x_minutes => ['less than a minute', 'less than {{count}} minutes'], + :x_minutes => ['1 minute', '{{count}} minutes'], + :about_x_hours => ['about 1 hour', 'about {{count}} hours'], + :x_days => ['1 day', '{{count}} days'], + :about_x_months => ['about 1 month', 'about {{count}} months'], + :x_months => ['1 month', '{{count}} months'], + :about_x_years => ['about 1 year', 'about {{count}} year'], + :over_x_years => ['over 1 year', 'over {{count}} years'] + } + }, + :currency => { + :format => { + :unit => '$', + :precision => 2, + :separator => '.', + :delimiter => ',', + :format => '%u%n', + } + }, + :active_record => { + :error => { + :header_message => ["1 error prohibited this {{object_name}} from being saved", "{{count}} errors prohibited this {{object_name}} from being saved"], + :message => "There were problems with the following fields:" + } + } +} diff --git a/actionpack/lib/action_view/partials.rb b/actionpack/lib/action_view/partials.rb index 116d61e13b..eb74d4a4c7 100644 --- a/actionpack/lib/action_view/partials.rb +++ b/actionpack/lib/action_view/partials.rb @@ -102,14 +102,15 @@ module ActionView # # As you can see, the <tt>:locals</tt> hash is shared between both the partial and its layout. module Partials + extend ActiveSupport::Memoizable + private def render_partial(partial_path, object_assigns = nil, local_assigns = {}) #:nodoc: local_assigns ||= {} case partial_path when String, Symbol, NilClass - variable_name, path = partial_pieces(partial_path) - pick_template(path).render_partial(self, variable_name, object_assigns, local_assigns) + pick_template(find_partial_path(partial_path)).render_partial(self, object_assigns, local_assigns) when ActionView::Helpers::FormBuilder builder_partial_path = partial_path.class.to_s.demodulize.underscore.sub(/_builder$/, '') render_partial(builder_partial_path, object_assigns, (local_assigns || {}).merge(builder_partial_path.to_sym => partial_path)) @@ -130,43 +131,28 @@ module ActionView local_assigns = local_assigns ? local_assigns.clone : {} spacer = partial_spacer_template ? render(:partial => partial_spacer_template) : '' - _partial_pieces = {} - _templates = {} index = 0 collection.map do |object| _partial_path ||= partial_path || ActionController::RecordIdentifier.partial_path(object, controller.class.controller_path) - variable_name, path = _partial_pieces[_partial_path] ||= partial_pieces(_partial_path) - template = _templates[path] ||= pick_template(path) - - local_assigns["#{variable_name}_counter".to_sym] = index - local_assigns[:object] = local_assigns[variable_name] = object - local_assigns[as] = object if as - - result = template.render_partial(self, variable_name, object, local_assigns) - - local_assigns.delete(as) - local_assigns.delete(variable_name) - local_assigns.delete(:object) + path = find_partial_path(_partial_path) + template = pick_template(path) + local_assigns[template.counter_name] = index + result = template.render_partial(self, object, local_assigns, as) index += 1 - result end.join(spacer) end - def partial_pieces(partial_path) + def find_partial_path(partial_path) if partial_path.include?('/') - variable_name = File.basename(partial_path) - path = "#{File.dirname(partial_path)}/_#{variable_name}" + "#{File.dirname(partial_path)}/_#{File.basename(partial_path)}" elsif respond_to?(:controller) - variable_name = partial_path - path = "#{controller.class.controller_path}/_#{variable_name}" + "#{controller.class.controller_path}/_#{partial_path}" else - variable_name = partial_path - path = "_#{variable_name}" + "_#{partial_path}" end - variable_name = variable_name.sub(/\..*$/, '').to_sym - return variable_name, path end + memoize :find_partial_path end end diff --git a/actionpack/lib/action_view/paths.rb b/actionpack/lib/action_view/paths.rb index b0ab7d0c67..a37706faee 100644 --- a/actionpack/lib/action_view/paths.rb +++ b/actionpack/lib/action_view/paths.rb @@ -1,5 +1,5 @@ module ActionView #:nodoc: - class PathSet < Array #:nodoc: + class PathSet < ActiveSupport::TypedArray #:nodoc: def self.type_cast(obj) if obj.is_a?(String) if Base.warn_cache_misses && defined?(Rails) && Rails.initialized? @@ -16,69 +16,77 @@ module ActionView #:nodoc: end class Path #:nodoc: + def self.eager_load_templates! + @eager_load_templates = true + end + + def self.eager_load_templates? + @eager_load_templates || false + end + attr_reader :path, :paths - delegate :to_s, :to_str, :inspect, :to => :path + delegate :to_s, :to_str, :hash, :inspect, :to => :path - def initialize(path) + def initialize(path, load = true) + raise ArgumentError, "path already is a Path class" if path.is_a?(Path) @path = path.freeze - reload! + reload! if load end def ==(path) to_str == path.to_str end + def eql?(path) + to_str == path.to_str + end + def [](path) @paths[path] end + def loaded? + @loaded ? true : false + end + + def load + reload! unless loaded? + end + # Rebuild load path directory cache def reload! @paths = {} templates_in_path do |template| + # Eager load memoized methods and freeze cached template + template.freeze if self.class.eager_load_templates? + @paths[template.path] = template @paths[template.path_without_extension] ||= template end @paths.freeze + @loaded = true end private def templates_in_path (Dir.glob("#{@path}/**/*/**") | Dir.glob("#{@path}/**")).each do |file| unless File.directory?(file) - template = Template.new(file.split("#{self}/").last, self) - # Eager load memoized methods and freeze cached template - template.freeze if Base.cache_template_loading - yield template + yield Template.new(file.split("#{self}/").last, self) end end end end - def initialize(*args) - super(*args).map! { |obj| self.class.type_cast(obj) } + def load + each { |path| path.load } end def reload! each { |path| path.reload! } end - def <<(obj) - super(self.class.type_cast(obj)) - end - - def push(*objs) - delete_paths!(objs) - super(*objs.map { |obj| self.class.type_cast(obj) }) - end - - def unshift(*objs) - delete_paths!(objs) - super(*objs.map { |obj| self.class.type_cast(obj) }) - end - def [](template_path) each do |path| if template = path[template_path] @@ -87,10 +95,5 @@ module ActionView #:nodoc: end nil end - - private - def delete_paths!(paths) - paths.each { |p1| delete_if { |p2| p1.to_s == p2.to_s } } - end end end diff --git a/actionpack/lib/action_view/renderable.rb b/actionpack/lib/action_view/renderable.rb index 2c4302146f..5fe1ca86f3 100644 --- a/actionpack/lib/action_view/renderable.rb +++ b/actionpack/lib/action_view/renderable.rb @@ -3,26 +3,35 @@ module ActionView # NOTE: The template that this mixin is beening include into is frozen # So you can not set or modify any instance variables + extend ActiveSupport::Memoizable + def self.included(base) @@mutex = Mutex.new end - # NOTE: Exception to earlier notice. Ensure this is called before freeze + def filename + 'compiled-template' + end + def handler - @handler ||= Template.handler_class_for_extension(extension) + Template.handler_class_for_extension(extension) end + memoize :handler - # NOTE: Exception to earlier notice. Ensure this is called before freeze def compiled_source - @compiled_source ||= handler.new(nil).compile(self) if handler.compilable? + handler.call(self) end + memoize :compiled_source def render(view, local_assigns = {}) - view.first_render ||= self + compile(local_assigns) + + view._first_render ||= self + view._last_render = self + view.send(:evaluate_assigns) - view.current_render_extension = extension - compile(local_assigns) if handler.compilable? - handler.new(view).render(self, local_assigns) + view.send(:set_controller_content_type, mime_type) if respond_to?(:mime_type) + view.send(:execute, method(local_assigns), local_assigns) end def method(local_assigns) @@ -33,47 +42,49 @@ module ActionView end private - # Compile and evaluate the template's code + # Compile and evaluate the template's code (if necessary) def compile(local_assigns) render_symbol = method(local_assigns) @@mutex.synchronize do - return false unless recompile?(render_symbol) - - locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join - - source = <<-end_src - def #{render_symbol}(local_assigns) - old_output_buffer = output_buffer;#{locals_code};#{compiled_source} - ensure - self.output_buffer = old_output_buffer - end - end_src - - begin - file_name = respond_to?(:filename) ? filename : 'compiled-template' - ActionView::Base::CompiledTemplates.module_eval(source, file_name, 0) - rescue Exception => e # errors from template code - if logger = ActionController::Base.logger - logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}" - logger.debug "Function body: #{source}" - logger.debug "Backtrace: #{e.backtrace.join("\n")}" - end - - raise ActionView::TemplateError.new(self, {}, e) + if recompile?(render_symbol) + compile!(render_symbol, local_assigns) end end end + def compile!(render_symbol, local_assigns) + locals_code = local_assigns.keys.map { |key| "#{key} = local_assigns[:#{key}];" }.join + + source = <<-end_src + def #{render_symbol}(local_assigns) + old_output_buffer = output_buffer;#{locals_code};#{compiled_source} + ensure + self.output_buffer = old_output_buffer + end + end_src + + begin + logger = ActionController::Base.logger + logger.debug "Compiling template #{render_symbol}" if logger + + ActionView::Base::CompiledTemplates.module_eval(source, filename, 0) + rescue Exception => e # errors from template code + if logger + logger.debug "ERROR: compiling #{render_symbol} RAISED #{e}" + logger.debug "Function body: #{source}" + logger.debug "Backtrace: #{e.backtrace.join("\n")}" + end + + raise ActionView::TemplateError.new(self, {}, e) + end + end + # Method to check whether template compilation is necessary. # The template will be compiled if the file has not been compiled yet, or # if local_assigns has a new key, which isn't supported by the compiled code yet. def recompile?(symbol) - unless Base::CompiledTemplates.instance_methods.include?(symbol) && Base.cache_template_loading - true - else - false - end + !(ActionView::PathSet::Path.eager_load_templates? && Base::CompiledTemplates.method_defined?(symbol)) end end end diff --git a/actionpack/lib/action_view/renderable_partial.rb b/actionpack/lib/action_view/renderable_partial.rb index 6a17b50a14..342850f0f0 100644 --- a/actionpack/lib/action_view/renderable_partial.rb +++ b/actionpack/lib/action_view/renderable_partial.rb @@ -3,16 +3,33 @@ module ActionView # NOTE: The template that this mixin is beening include into is frozen # So you can not set or modify any instance variables + extend ActiveSupport::Memoizable + + def variable_name + name.sub(/\A_/, '').to_sym + end + memoize :variable_name + + def counter_name + "#{variable_name}_counter".to_sym + end + memoize :counter_name + def render(view, local_assigns = {}) ActionController::Base.benchmark("Rendered #{path_without_format_and_extension}", Logger::DEBUG, false) do super end end - def render_partial(view, variable_name, object = nil, local_assigns = {}, as = nil) - object ||= view.controller.instance_variable_get("@#{variable_name}") if view.respond_to?(:controller) - local_assigns[:object] ||= local_assigns[variable_name] ||= object - local_assigns[as] ||= local_assigns[:object] if as + def render_partial(view, object = nil, local_assigns = {}, as = nil) + object ||= local_assigns[:object] || + local_assigns[variable_name] || + view.controller.instance_variable_get("@#{variable_name}") if view.respond_to?(:controller) + + # Ensure correct object is reassigned to other accessors + local_assigns[:object] = local_assigns[variable_name] = object + local_assigns[as] = object if as + render_template(view, local_assigns) end end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 03f9234289..b281ff61f2 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -1,6 +1,7 @@ module ActionView #:nodoc: class Template extend TemplateHandlers + extend ActiveSupport::Memoizable include Renderable attr_accessor :filename, :load_path, :base_path, :name, :format, :extension @@ -16,54 +17,42 @@ module ActionView #:nodoc: extend RenderablePartial if @name =~ /^_/ end - def freeze - # Eager load memoized methods - format_and_extension - path - path_without_extension - path_without_format_and_extension - source - method_segment - - # Eager load memoized methods from Renderable - handler - compiled_source - - instance_variables.each { |ivar| ivar.freeze } - - super + def format_and_extension + (extensions = [format, extension].compact.join(".")).blank? ? nil : extensions end + memoize :format_and_extension - def format_and_extension - @format_and_extension ||= (extensions = [format, extension].compact.join(".")).blank? ? nil : extensions + def mime_type + Mime::Type.lookup_by_extension(format) if format end + memoize :mime_type def path - @path ||= [base_path, [name, format, extension].compact.join('.')].compact.join('/') + [base_path, [name, format, extension].compact.join('.')].compact.join('/') end + memoize :path def path_without_extension - @path_without_extension ||= [base_path, [name, format].compact.join('.')].compact.join('/') + [base_path, [name, format].compact.join('.')].compact.join('/') end + memoize :path_without_extension def path_without_format_and_extension - @path_without_format_and_extension ||= [base_path, name].compact.join('/') + [base_path, name].compact.join('/') end + memoize :path_without_format_and_extension def source - @source ||= File.read(@filename) + File.read(filename) end + memoize :source def method_segment - unless @method_segment - segment = File.expand_path(@filename) - segment.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT) - segment.gsub!(/([^a-zA-Z0-9_])/) { $1.ord } - @method_segment = segment - end - - @method_segment + segment = File.expand_path(filename) + segment.sub!(/^#{Regexp.escape(File.expand_path(RAILS_ROOT))}/, '') if defined?(RAILS_ROOT) + segment.gsub!(/([^a-zA-Z0-9_])/) { $1.ord } end + memoize :method_segment def render_template(view, local_assigns = {}) render(view, local_assigns) @@ -86,7 +75,7 @@ module ActionView #:nodoc: load_paths = Array(load_paths) + [nil] load_paths.each do |load_path| file = [load_path, path].compact.join('/') - return load_path, file if File.exist?(file) + return load_path, file if File.file?(file) end raise MissingTemplate.new(load_paths, path) end diff --git a/actionpack/lib/action_view/template_handler.rb b/actionpack/lib/action_view/template_handler.rb index 1afea21f67..d7e7c9b199 100644 --- a/actionpack/lib/action_view/template_handler.rb +++ b/actionpack/lib/action_view/template_handler.rb @@ -1,25 +1,14 @@ -module ActionView - class TemplateHandler - def self.compilable? - false - end - - def initialize(view) - @view = view - end - - def render(template, local_assigns = {}) - end - - def compile(template) - end +# Legacy TemplateHandler stub - def compilable? - self.class.compilable? +module ActionView + module TemplateHandlers + module Compilable end + end - # Called by CacheHelper#cache - def cache_fragment(block, name = {}, options = nil) + class TemplateHandler + def self.call(template) + new.compile(template) end end end diff --git a/actionpack/lib/action_view/template_handlers.rb b/actionpack/lib/action_view/template_handlers.rb index 1471e99e01..6c8aa4c2a7 100644 --- a/actionpack/lib/action_view/template_handlers.rb +++ b/actionpack/lib/action_view/template_handlers.rb @@ -1,5 +1,4 @@ require 'action_view/template_handler' -require 'action_view/template_handlers/compilable' require 'action_view/template_handlers/builder' require 'action_view/template_handlers/erb' require 'action_view/template_handlers/rjs' diff --git a/actionpack/lib/action_view/template_handlers/builder.rb b/actionpack/lib/action_view/template_handlers/builder.rb index cbe53e11d8..7d24a5c423 100644 --- a/actionpack/lib/action_view/template_handlers/builder.rb +++ b/actionpack/lib/action_view/template_handlers/builder.rb @@ -6,18 +6,12 @@ module ActionView include Compilable def compile(template) - # ActionMailer does not have a response - "controller.respond_to?(:response) && controller.response.content_type ||= Mime::XML;" + + "set_controller_content_type(Mime::XML);" + "xml = ::Builder::XmlMarkup.new(:indent => 2);" + + "self.output_buffer = xml.target!;" + template.source + ";xml.target!;" end - - def cache_fragment(block, name = {}, options = nil) - @view.fragment_for(block, name, options) do - eval('xml.target!', block.binding) - end - end end end end diff --git a/actionpack/lib/action_view/template_handlers/compilable.rb b/actionpack/lib/action_view/template_handlers/compilable.rb deleted file mode 100644 index a0ebaefeef..0000000000 --- a/actionpack/lib/action_view/template_handlers/compilable.rb +++ /dev/null @@ -1,20 +0,0 @@ -module ActionView - module TemplateHandlers - module Compilable - def self.included(base) - base.extend ClassMethod - end - - module ClassMethod - # If a handler is mixin this module, set compilable to true - def compilable? - true - end - end - - def render(template, local_assigns = {}) - @view.send(:execute, template, local_assigns) - end - end - end -end diff --git a/actionpack/lib/action_view/template_handlers/erb.rb b/actionpack/lib/action_view/template_handlers/erb.rb index ac715e30c2..3def949f1e 100644 --- a/actionpack/lib/action_view/template_handlers/erb.rb +++ b/actionpack/lib/action_view/template_handlers/erb.rb @@ -48,14 +48,11 @@ module ActionView self.erb_trim_mode = '-' def compile(template) - src = ::ERB.new(template.source, nil, erb_trim_mode, '@output_buffer').src - "__in_erb_template=true;#{src}" - end + src = ::ERB.new("<% __in_erb_template=true %>#{template.source}", nil, erb_trim_mode, '@output_buffer').src - def cache_fragment(block, name = {}, options = nil) #:nodoc: - @view.fragment_for(block, name, options) do - @view.response.template.output_buffer - end + # Ruby 1.9 prepends an encoding to the source. However this is + # useless because you can only set an encoding on the first line + RUBY_VERSION >= '1.9' ? src.sub(/\A#coding:.*\n/, '') : src end end end diff --git a/actionpack/lib/action_view/template_handlers/rjs.rb b/actionpack/lib/action_view/template_handlers/rjs.rb index 3892bf1d6e..a700655c9a 100644 --- a/actionpack/lib/action_view/template_handlers/rjs.rb +++ b/actionpack/lib/action_view/template_handlers/rjs.rb @@ -7,17 +7,6 @@ module ActionView "controller.response.content_type ||= Mime::JS;" + "update_page do |page|;#{template.source}\nend" end - - def cache_fragment(block, name = {}, options = nil) #:nodoc: - @view.fragment_for(block, name, options) do - begin - debug_mode, ActionView::Base.debug_rjs = ActionView::Base.debug_rjs, false - eval('page.to_s', block.binding) - ensure - ActionView::Base.debug_rjs = debug_mode - end - end - end end end end |