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 & 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)