root/trunk/actionpack/lib/action_view/base.rb
| Revision 8981, 13.9 kB (checked in by pratik, 9 months ago) |
|---|
| Line | |
|---|---|
| 1 | module ActionView #:nodoc: |
| 2 | class ActionViewError < StandardError #:nodoc: |
| 3 | end |
| 4 | |
| 5 | # Action View templates can be written in three ways. If the template file has a +.erb+ (or +.rhtml+) extension then it uses a mixture of ERb |
| 6 | # (included in Ruby) and HTML. If the template file has a +.builder+ (or +.rxml+) extension then Jim Weirich's Builder::XmlMarkup library is used. |
| 7 | # If the template file has a +.rjs+ extension then it will use ActionView::Helpers::PrototypeHelper::JavaScriptGenerator. |
| 8 | # |
| 9 | # = ERb |
| 10 | # |
| 11 | # You trigger ERb by using embeddings such as <% %>, <% -%>, and <%= %>. The <%= %> tag set is used when you want output. Consider the |
| 12 | # following loop for names: |
| 13 | # |
| 14 | # <b>Names of all the people</b> |
| 15 | # <% for person in @people %> |
| 16 | # Name: <%= person.name %><br/> |
| 17 | # <% end %> |
| 18 | # |
| 19 | # The loop is setup in regular embedding tags <% %> and the name is written using the output embedding tag <%= %>. Note that this |
| 20 | # is not just a usage suggestion. Regular output functions like print or puts won't work with ERb templates. So this would be wrong: |
| 21 | # |
| 22 | # Hi, Mr. <% puts "Frodo" %> |
| 23 | # |
| 24 | # If you absolutely must write from within a function, you can use the TextHelper#concat |
| 25 | # |
| 26 | # <%- and -%> suppress leading and trailing whitespace, including the trailing newline, and can be used interchangeably with <% and %>. |
| 27 | # |
| 28 | # == Using sub templates |
| 29 | # |
| 30 | # Using sub templates allows you to sidestep tedious replication and extract common display structures in shared templates. The |
| 31 | # classic example is the use of a header and footer (even though the Action Pack-way would be to use Layouts): |
| 32 | # |
| 33 | # <%= render "shared/header" %> |
| 34 | # Something really specific and terrific |
| 35 | # <%= render "shared/footer" %> |
| 36 | # |
| 37 | # As you see, we use the output embeddings for the render methods. The render call itself will just return a string holding the |
| 38 | # result of the rendering. The output embedding writes it to the current template. |
| 39 | # |
| 40 | # But you don't have to restrict yourself to static includes. Templates can share variables amongst themselves by using instance |
| 41 | # variables defined using the regular embedding tags. Like this: |
| 42 | # |
| 43 | # <% @page_title = "A Wonderful Hello" %> |
| 44 | # <%= render "shared/header" %> |
| 45 | # |
| 46 | # Now the header can pick up on the @page_title variable and use it for outputting a title tag: |
| 47 | # |
| 48 | # <title><%= @page_title %></title> |
| 49 | # |
| 50 | # == Passing local variables to sub templates |
| 51 | # |
| 52 | # You can pass local variables to sub templates by using a hash with the variable names as keys and the objects as values: |
| 53 | # |
| 54 | # <%= render "shared/header", { :headline => "Welcome", :person => person } %> |
| 55 | # |
| 56 | # These can now be accessed in shared/header with: |
| 57 | # |
| 58 | # Headline: <%= headline %> |
| 59 | # First name: <%= person.first_name %> |
| 60 | # |
| 61 | # If you need to find out whether a certain local variable has been assigned a value in a particular render call, |
| 62 | # you need to use the following pattern: |
| 63 | # |
| 64 | # <% if local_assigns.has_key? :headline %> |
| 65 | # Headline: <%= headline %> |
| 66 | # <% end %> |
| 67 | # |
| 68 | # Testing using <tt>defined? headline</tt> will not work. This is an implementation restriction. |
| 69 | # |
| 70 | # == Template caching |
| 71 | # |
| 72 | # By default, Rails will compile each template to a method in order to render it. When you alter a template, Rails will |
| 73 | # check the file's modification time and recompile it. |
| 74 | # |
| 75 | # == Builder |
| 76 | # |
| 77 | # Builder templates are a more programmatic alternative to ERb. They are especially useful for generating XML content. An +XmlMarkup+ object |
| 78 | # named +xml+ is automatically made available to templates with a +.builder+ extension. |
| 79 | # |
| 80 | # Here are some basic examples: |
| 81 | # |
| 82 | # xml.em("emphasized") # => <em>emphasized</em> |
| 83 | # xml.em { xml.b("emph & bold") } # => <em><b>emph & bold</b></em> |
| 84 | # xml.a("A Link", "href"=>"http://onestepback.org") # => <a href="A">http://onestepback.org">A Link</a> |
| 85 | # xml.target("name"=>"compile", "option"=>"fast") # => <target option="fast" name="compile"\> |
| 86 | # # NOTE: order of attributes is not specified. |
| 87 | # |
| 88 | # Any method with a block will be treated as an XML markup tag with nested markup in the block. For example, the following: |
| 89 | # |
| 90 | # xml.div { |
| 91 | # xml.h1(@person.name) |
| 92 | # xml.p(@person.bio) |
| 93 | # } |
| 94 | # |
| 95 | # would produce something like: |
| 96 | # |
| 97 | # <div> |
| 98 | # <h1>David Heinemeier Hansson</h1> |
| 99 | # <p>A product of Danish Design during the Winter of '79...</p> |
| 100 | # </div> |
| 101 | # |
| 102 | # A full-length RSS example actually used on Basecamp: |
| 103 | # |
| 104 | # xml.rss("version" => "2.0", "xmlns:dc" => "http://purl.org/dc/elements/1.1/") do |
| 105 | # xml.channel do |
| 106 | # xml.title(@feed_title) |
| 107 | # xml.link(@url) |
| 108 | # xml.description "Basecamp: Recent items" |
| 109 | # xml.language "en-us" |
| 110 | # xml.ttl "40" |
| 111 | # |
| 112 | # for item in @recent_items |
| 113 | # xml.item do |
| 114 | # xml.title(item_title(item)) |
| 115 | # xml.description(item_description(item)) if item_description(item) |
| 116 | # xml.pubDate(item_pubDate(item)) |
| 117 | # xml.guid(@person.firm.account.url + @recent_items.url(item)) |
| 118 | # xml.link(@person.firm.account.url + @recent_items.url(item)) |
| 119 | # |
| 120 | # xml.tag!("dc:creator", item.author_name) if item_has_creator?(item) |
| 121 | # end |
| 122 | # end |
| 123 | # end |
| 124 | # end |
| 125 | # |
| 126 | # More builder documentation can be found at http://builder.rubyforge.org. |
| 127 | # |
| 128 | # == JavaScriptGenerator |
| 129 | # |
| 130 | # JavaScriptGenerator templates end in +.rjs+. Unlike conventional templates which are used to |
| 131 | # render the results of an action, these templates generate instructions on how to modify an already rendered page. This makes it easy to |
| 132 | # modify multiple elements on your page in one declarative Ajax response. Actions with these templates are called in the background with Ajax |
| 133 | # and make updates to the page where the request originated from. |
| 134 | # |
| 135 | # 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. |
| 136 | # |
| 137 | # When an .rjs action is called with +link_to_remote+, the generated JavaScript is automatically evaluated. Example: |
| 138 | # |
| 139 | # link_to_remote :url => {:action => 'delete'} |
| 140 | # |
| 141 | # The subsequently rendered +delete.rjs+ might look like: |
| 142 | # |
| 143 | # page.replace_html 'sidebar', :partial => 'sidebar' |
| 144 | # page.remove "person-#{@person.id}" |
| 145 | # page.visual_effect :highlight, 'user-list' |
| 146 | # |
| 147 | # This refreshes the sidebar, removes a person element and highlights the user list. |
| 148 | # |
| 149 | # See the ActionView::Helpers::PrototypeHelper::GeneratorMethods documentation for more details. |
| 150 | class Base |
| 151 | include ERB::Util |
| 152 | |
| 153 | attr_reader :finder |
| 154 | attr_accessor :base_path, :assigns, :template_extension, :first_render |
| 155 | attr_accessor :controller |
| 156 | |
| 157 | attr_reader :logger, :response, :headers |
| 158 | attr_internal :cookies, :flash, :headers, :params, :request, :response, :session |
| 159 | |
| 160 | attr_writer :template_format |
| 161 | attr_accessor :current_render_extension |
| 162 | |
| 163 | # Specify trim mode for the ERB compiler. Defaults to '-'. |
| 164 | # See ERb documentation for suitable values. |
| 165 | @@erb_trim_mode = '-' |
| 166 | cattr_accessor :erb_trim_mode |
| 167 | |
| 168 | # Specify whether file modification times should be checked to see if a template needs recompilation |
| 169 | @@cache_template_loading = false |
| 170 | cattr_accessor :cache_template_loading |
| 171 | |
| 172 | # Specify whether file extension lookup should be cached, and whether template base path lookup should be cached. |
| 173 | # Should be +false+ for development environments. Defaults to +true+. |
| 174 | @@cache_template_extensions = true |
| 175 | cattr_accessor :cache_template_extensions |
| 176 | |
| 177 | # Specify whether RJS responses should be wrapped in a try/catch block |
| 178 | # that alert()s the caught exception (and then re-raises it). |
| 179 | @@debug_rjs = false |
| 180 | cattr_accessor :debug_rjs |
| 181 | |
| 182 | @@erb_variable = '_erbout' |
| 183 | cattr_accessor :erb_variable |
| 184 | |
| 185 | delegate :request_forgery_protection_token, :to => :controller |
| 186 | |
| 187 | module CompiledTemplates #:nodoc: |
| 188 | # holds compiled template code |
| 189 | end |
| 190 | include CompiledTemplates |
| 191 | |
| 192 | # Maps inline templates to their method names |
| 193 | cattr_accessor :method_names |
| 194 | @@method_names = {} |
| 195 | # Map method names to the names passed in local assigns so far |
| 196 | @@template_args = {} |
| 197 | |
| 198 | # Cache public asset paths |
| 199 | cattr_reader :computed_public_paths |
| 200 | @@computed_public_paths = {} |
| 201 | |
| 202 | class ObjectWrapper < Struct.new(:value) #:nodoc: |
| 203 | end |
| 204 | |
| 205 | def self.load_helpers #:nodoc: |
| 206 | Dir.entries("#{File.dirname(__FILE__)}/helpers").sort.each do |file| |
| 207 | next unless file =~ /^([a-z][a-z_]*_helper).rb$/ |
| 208 | require "action_view/helpers/#{$1}" |
| 209 | helper_module_name = $1.camelize |
| 210 | if Helpers.const_defined?(helper_module_name) |
| 211 | include Helpers.const_get(helper_module_name) |
| 212 | end |
| 213 | end |
| 214 | end |
| 215 | |
| 216 | def initialize(view_paths = [], assigns_for_first_render = {}, controller = nil)#:nodoc: |
| 217 | @assigns = assigns_for_first_render |
| 218 | @assigns_added = nil |
| 219 | @controller = controller |
| 220 | @logger = controller && controller.logger |
| 221 | @finder = TemplateFinder.new(self, view_paths) |
| 222 | end |
| 223 | |
| 224 | # Renders the template present at <tt>template_path</tt>. If <tt>use_full_path</tt> is set to true, |
| 225 | # it's relative to the view_paths array, otherwise it's absolute. The hash in <tt>local_assigns</tt> |
| 226 | # is made available as local variables. |
| 227 | def render_file(template_path, use_full_path = true, local_assigns = {}) #:nodoc: |
| 228 | if defined?(ActionMailer) && defined?(ActionMailer::Base) && controller.is_a?(ActionMailer::Base) && !template_path.include?("/") |
| 229 | raise ActionViewError, <<-END_ERROR |
| 230 | Due to changes in ActionMailer, you need to provide the mailer_name along with the template name. |
| 231 | |
| 232 | render "user_mailer/signup" |
| 233 | render :file => "user_mailer/signup" |
| 234 | |
| 235 | If you are rendering a subtemplate, you must now use controller-like partial syntax: |
| 236 | |
| 237 | render :partial => 'signup' # no mailer_name necessary |
| 238 | END_ERROR |
| 239 | end |
| 240 | |
| 241 | template = Template.new(self, template_path, use_full_path, local_assigns) |
| 242 | |
| 243 | begin |
| 244 | render_template(template) |
| 245 | rescue Exception => e |
| 246 | if TemplateError === e |
| 247 | e.sub_template_of(template.filename) |
| 248 | raise e |
| 249 | else |
| 250 | raise TemplateError.new(template, @assigns, e) |
| 251 | end |
| 252 | end |
| 253 | end |
| 254 | |
| 255 | # Renders the template present at <tt>template_path</tt> (relative to the view_paths array). |
| 256 | # The hash in <tt>local_assigns</tt> is made available as local variables. |
| 257 | def render(options = {}, local_assigns = {}, &block) #:nodoc: |
| 258 | if options.is_a?(String) |
| 259 | render_file(options, true, local_assigns) |
| 260 | elsif options == :update |
| 261 | update_page(&block) |
| 262 | elsif options.is_a?(Hash) |
| 263 | options = options.reverse_merge(:locals => {}, :use_full_path => true) |
| 264 | |
| 265 | if partial_layout = options.delete(:layout) |
| 266 | if block_given? |
| 267 | wrap_content_for_layout capture(&block) do |
| 268 | concat(render(options.merge(:partial => partial_layout)), block.binding) |
| 269 | end |
| 270 | else |
| 271 | wrap_content_for_layout render(options) do |
| 272 | render(options.merge(:partial => partial_layout)) |
| 273 | end |
| 274 | end |
| 275 | elsif options[:file] |
| 276 | render_file(options[:file], options[:use_full_path], options[:locals]) |
| 277 | elsif options[:partial] && options[:collection] |
| 278 | render_partial_collection(options[:partial], options[:collection], options[:spacer_template], options[:locals]) |
| 279 | elsif options[:partial] |
| 280 | render_partial(options[:partial], ActionView::Base::ObjectWrapper.new(options[:object]), options[:locals]) |
| 281 | elsif options[:inline] |
| 282 | template = Template.new(self, options[:inline], false, options[:locals], true, options[:type]) |
| 283 | render_template(template) |
| 284 | end |
| 285 | end |
| 286 | end |
| 287 | |
| 288 | def render_template(template) #:nodoc: |
| 289 | template.render |
| 290 | end |
| 291 | |
| 292 | # Returns true is the file may be rendered implicitly. |
| 293 | def file_public?(template_path)#:nodoc: |
| 294 | template_path.split('/').last[0,1] != '_' |
| 295 | end |
| 296 | |
| 297 | # symbolized version of the :format parameter of the request, or :html by default. |
| 298 | # |
| 299 | # EXCEPTION: If the :format parameter is not set, the Accept header will be examined for |
| 300 | # whether it contains the JavaScript mime type as its first priority. If that's the case, |
| 301 | # it will be used. This ensures that Ajax applications can use the same URL to support both |
| 302 | # JavaScript and non-JavaScript users. |
| 303 | def template_format |
| 304 | return @template_format if @template_format |
| 305 | |
| 306 | if controller && controller.respond_to?(:request) |
| 307 | parameter_format = controller.request.parameters[:format] |
| 308 | accept_format = controller.request.accepts.first |
| 309 | |
| 310 | case |
| 311 | when parameter_format.blank? && accept_format != :js |
| 312 | @template_format = :html |
| 313 | when parameter_format.blank? && accept_format == :js |
| 314 | @template_format = :js |
| 315 | else |
| 316 | @template_format = parameter_format.to_sym |
| 317 | end |
| 318 | else |
| 319 | @template_format = :html |
| 320 | end |
| 321 | end |
| 322 | |
| 323 | private |
| 324 | def wrap_content_for_layout(content) |
| 325 | original_content_for_layout = @content_for_layout |
| 326 | @content_for_layout = content |
| 327 | returning(yield) { @content_for_layout = original_content_for_layout } |
| 328 | end |
| 329 | |
| 330 | # Evaluate the local assigns and pushes them to the view. |
| 331 | def evaluate_assigns |
| 332 | unless @assigns_added |
| 333 | assign_variables_from_controller |
| 334 | @assigns_added = true |
| 335 | end |
| 336 | end |
| 337 | |
| 338 | # Assigns instance variables from the controller to the view. |
| 339 | def assign_variables_from_controller |
| 340 | @assigns.each { |key, value| instance_variable_set("@#{key}", value) } |
| 341 | end |
| 342 | |
| 343 | def execute(template) |
| 344 | send(template.method, template.locals) do |*names| |
| 345 | instance_variable_get "@content_for_#{names.first || 'layout'}" |
| 346 | end |
| 347 | end |
| 348 | end |
| 349 | end |
Note: See TracBrowser for help on using the browser.