lib/action_view/base.rb in actionpack-1.11.2 vs lib/action_view/base.rb in actionpack-1.12.0

- old
+ new

@@ -3,12 +3,13 @@ module ActionView #:nodoc: class ActionViewError < StandardError #:nodoc: end - # Action View templates can be written in two ways. If the template file has a +.rhtml+ extension then it uses a mixture of ERb - # (included in Ruby) and HTML. If the template file has a +.rxml+ extension then Jim Weirich's Builder::XmlMarkup library is used. + # Action View templates can be written in three ways. If the template file has a +.rhtml+ extension then it uses a mixture of ERb + # (included in Ruby) and HTML. If the template file has a +.rxml+ extension then Jim Weirich's Builder::XmlMarkup library is used. + # If the template file has a +.rjs+ extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator. # # = ERb # # You trigger ERb by using embeddings such as <% %> and <%= %>. The difference is whether you want output or not. Consider the # following loop for names: @@ -71,11 +72,11 @@ # Here are some basic examples: # # xml.em("emphasized") # => <em>emphasized</em> # xml.em { xml.b("emp & bold") } # => <em><b>emph &amp; bold</b></em> # xml.a("A Link", "href"=>"http://onestepback.org") # => <a href="http://onestepback.org">A Link</a> - # xm.target("name"=>"compile", "option"=>"fast") # => <target option="fast" name="compile"\> + # xml.target("name"=>"compile", "option"=>"fast") # => <target option="fast" name="compile"\> # # NOTE: order of attributes is not specified. # # Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following: # # xml.div { @@ -113,10 +114,33 @@ # end # end # end # # More builder documentation can be found at http://builder.rubyforge.org. + # + # == JavaScriptGenerator + # + # JavaScriptGenerator templates end in +.rjs+. Unlike conventional templates which are used to + # render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to + # modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax + # and make updates to the page where the request originated from. + # + # An instance of the JavaScriptGenerator object named +page+ is automatically made available to your template, which is implicitly wrapped in an ActionView::Helpers::PrototypeHelper#update_page block. + # + # When an .rjs action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example: + # + # link_to_remote :url => {:action => 'delete'} + # + # The subsequently rendered +delete.rjs+ might look like: + # + # page.replace_html 'sidebar', :partial => 'sidebar' + # page.remove "person-#{@person.id}" + # page.visual_effect :highlight, 'user-list' + # + # This refreshes the sidebar, removes a person element and highlights the user list. + # + # See the ActionView::Helpers::PrototypeHelper::JavaScriptGenerator documentation for more details. class Base include ERB::Util attr_reader :first_render attr_accessor :base_path, :assigns, :template_extension @@ -136,12 +160,17 @@ # Specify whether local_assigns should be able to use string keys. # Defaults to +true+. String keys are deprecated and will be removed # shortly. @@local_assigns_support_string_keys = true cattr_accessor :local_assigns_support_string_keys + + # Specify whether RJS responses should be wrapped in a try/catch block + # that alert()s the caught exception (and then re-raises it). + @@debug_rjs = false + cattr_accessor :debug_rjs - @@template_handlers = {} + @@template_handlers = HashWithIndifferentAccess.new module CompiledTemplates #:nodoc: # holds compiled template code end include CompiledTemplates @@ -151,11 +180,15 @@ # map method names to their compile time @@compile_time = {} # map method names to the names passed in local assigns so far @@template_args = {} # count the number of inline templates - @@inline_template_count = 0 + @@inline_template_count = 0 + # maps template paths without extension to their file extension returned by pick_template_extension. + # if for a given path, path.ext1 and path.ext2 exist on the file system, the order of extensions + # used by pick_template_extension determines whether ext1 or ext2 will be stored + @@cached_template_extension = {} class ObjectWrapper < Struct.new(:value) #:nodoc: end def self.load_helpers(helper_dir)#:nodoc: @@ -186,16 +219,22 @@ end # Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true, # it's relative to the template_root, otherwise it's absolute. The hash in <tt>local_assigns</tt> # is made available as local variables. - def render_file(template_path, use_full_path = true, local_assigns = {}) - @first_render = template_path if @first_render.nil? + def render_file(template_path, use_full_path = true, local_assigns = {}) #:nodoc: + @first_render ||= template_path if use_full_path - template_extension = pick_template_extension(template_path) - template_file_name = full_template_path(template_path, template_extension) + template_path_without_extension, template_extension = path_and_extension(template_path) + + if template_extension + template_file_name = full_template_path(template_path_without_extension, template_extension) + else + template_extension = pick_template_extension(template_path).to_s + template_file_name = full_template_path(template_path, template_extension) + end else template_file_name = template_path template_extension = template_path.split('.').last end @@ -213,13 +252,15 @@ end end # Renders the template present at <tt>template_path</tt> (relative to the template_root). # The hash in <tt>local_assigns</tt> is made available as local variables. - def render(options = {}, old_local_assigns = {}) + def render(options = {}, old_local_assigns = {}, &block) #:nodoc: if options.is_a?(String) render_file(options, true, old_local_assigns) + elsif options == :update + update_page(&block) elsif options.is_a?(Hash) options[:locals] ||= {} options[:use_full_path] = options[:use_full_path].nil? ? true : options[:use_full_path] if options[:file] @@ -234,11 +275,11 @@ end end # Renders the +template+ which is given as a string as either rhtml or rxml depending on <tt>template_extension</tt>. # The hash in <tt>local_assigns</tt> is made available as local variables. - def render_template(template_extension, template, file_path = nil, local_assigns = {}) + def render_template(template_extension, template, file_path = nil, local_assigns = {}) #:nodoc: if handler = @@template_handlers[template_extension] template ||= read_template_file(file_path, template_extension) # Make sure that a lazyily-read template is loaded. delegate_render(handler, template, local_assigns) else compile_and_render_template(template_extension, template, file_path, local_assigns) @@ -250,11 +291,11 @@ # # Either, but not both, of template and file_path may be nil. If file_path is given, the template # will only be read if it has to be compiled. # - def compile_and_render_template(extension, template = nil, file_path = nil, local_assigns = {}) + def compile_and_render_template(extension, template = nil, file_path = nil, local_assigns = {}) #:nodoc: # compile the given template, if necessary if compile_template?(template, file_path, local_assigns) template ||= read_template_file(file_path, extension) compile_template(extension, template, file_path, local_assigns) end @@ -269,19 +310,19 @@ instance_variable_get "@content_for_#{name.first || 'layout'}" end end def pick_template_extension(template_path)#:nodoc: - if match = delegate_template_exists?(template_path) - match.first - elsif erb_template_exists?(template_path) - 'rhtml' - elsif builder_template_exists?(template_path) - 'rxml' - else - raise ActionViewError, "No rhtml, rxml, or delegate template found for #{template_path}" - end + @@cached_template_extension[template_path] ||= + if match = delegate_template_exists?(template_path) + match.first.to_sym + elsif erb_template_exists?(template_path): :rhtml + elsif builder_template_exists?(template_path): :rxml + elsif javascript_template_exists?(template_path): :rjs + else + raise ActionViewError, "No rhtml, rxml, rjs or delegate template found for #{template_path}" + end end def delegate_template_exists?(template_path)#:nodoc: @@template_handlers.find { |k,| template_exists?(template_path, k) } end @@ -291,13 +332,26 @@ end def builder_template_exists?(template_path)#:nodoc: template_exists?(template_path, :rxml) end + + def javascript_template_exists?(template_path)#:nodoc: + template_exists?(template_path, :rjs) + end def file_exists?(template_path)#:nodoc: - erb_template_exists?(template_path) || builder_template_exists?(template_path) || delegate_template_exists?(template_path) + template_file_name, template_file_extension = path_and_extension(template_path) + + if template_file_extension + template_exists?(template_file_name, template_file_extension) + else + @@cached_template_extension[template_path] || + %w(erb builder javascript delegate).any? do |template_type| + send("#{template_type}_template_exists?", template_path) + end + end end # Returns true is the file may be rendered implicitly. def file_public?(template_path)#:nodoc: template_path.split('/').last[0,1] != '_' @@ -311,10 +365,15 @@ def template_exists?(template_path, extension) file_path = full_template_path(template_path, extension) @@method_names.has_key?(file_path) || FileTest.exists?(file_path) end + def path_and_extension(template_path) + template_path_without_extension = template_path.sub(/\.(\w+)$/, '') + [ template_path_without_extension, $1 ] + end + # This method reads a template file. def read_template_file(template_path, extension) File.read(template_path) end @@ -343,11 +402,11 @@ # Check whether compilation is necessary. # Compile if the inline template or file has not been compiled yet. # Or if local_assigns has a new key, which isn't supported by the compiled code yet. # Or if the file has changed on disk and checking file mods hasn't been disabled. def compile_template?(template, file_name, local_assigns) - method_key = file_name || template + method_key = file_name || template render_symbol = @@method_names[method_key] if @@compile_time[render_symbol] && supports_local_assigns?(render_symbol, local_assigns) if file_name && !@@cache_template_loading @@compile_time[render_symbol] < File.mtime(file_name) @@ -357,14 +416,20 @@ end end # Create source code for given template def create_template_source(extension, template, render_symbol, locals) - if extension && (extension.to_sym == :rxml) - body = "xml = Builder::XmlMarkup.new(:indent => 2)\n" + - "@controller.headers['Content-Type'] ||= 'text/xml'\n" + - template + if template_requires_setup?(extension) + body = case extension.to_sym + when :rxml + "xml = Builder::XmlMarkup.new(:indent => 2)\n" + + "@controller.headers['Content-Type'] ||= 'application/xml'\n" + + template + when :rjs + "@controller.headers['Content-Type'] ||= 'text/javascript'\n" + + "update_page do |page|\n#{template}\nend" + end else body = ERB.new(template, nil, @@erb_trim_mode).src end @@template_args[render_symbol] ||= {} @@ -377,28 +442,31 @@ end "def #{render_symbol}(local_assigns)\n#{locals_code}#{body}\nend" end + def template_requires_setup?(extension) + templates_requiring_setup.include? extension.to_s + end + + def templates_requiring_setup + %w(rxml rjs) + end + def assign_method_name(extension, template, file_name) method_name = '_run_' + method_name << "#{extension}_" if extension - if extension && (extension.to_sym == :rxml) - method_name << 'xml_' - else - method_name << 'html_' - end - if file_name file_path = File.expand_path(file_name) base_path = File.expand_path(@base_path) i = file_path.index(base_path) l = base_path.length method_name_file_part = i ? file_path[i+l+1,file_path.length-l-1] : file_path.clone - method_name_file_part.sub!(/\.r(ht|x)ml$/,'') + method_name_file_part.sub!(/\.r(html|xml|js)$/,'') method_name_file_part.tr!('/:-', '_') method_name_file_part.gsub!(/[^a-zA-Z0-9_]/){|s| s[0].to_s} method_name += method_name_file_part else @@ -414,11 +482,16 @@ render_symbol = @@method_names[method_key] || assign_method_name(extension, template, file_name) render_source = create_template_source(extension, template, render_symbol, local_assigns.keys) line_offset = @@template_args[render_symbol].size - line_offset += 2 if extension && (extension.to_sym == :rxml) - + if extension + case extension.to_sym + when :rxml, :rjs + line_offset += 2 + end + end + begin unless file_name.blank? CompiledTemplates.module_eval(render_source, file_name, -line_offset) else CompiledTemplates.module_eval(render_source, 'compiled-template', -line_offset)