module Merb module RenderMixin # shortcut to a template path based on name. def template_dir(loc) File.expand_path(Merb::Server.config[:merb_root] / "/dist/app/views/#{loc}") end # returns the current method name. Used for # auto discovery of which template to render # based on the action name. def current_method_name(depth=0) caller[depth] =~ /`(.*)'$/; $1 end # given html, js and xml this method returns the template # extension from the :template_ext map froom your app's # configuration. defaults to .herb, .jerb & .xerb def template_extension_for(ext) Merb::Server.config[:template_ext][ext] end # this returns a ViewContext object populated with all # the instance variables in your controller. This is used # as the view context object for the Erubis templates. def create_view_context ViewContext.new(self) end # does a render with no layout. Also sets the # content type header to text/javascript. Use # this when you want to render a template with # .jerb extension. def render_js(template=current_method_name(1)) headers['Content-Type'] = "text/javascript" template = new_eruby_obj(template_dir(self.class.name.snake_case) / "/#{template}.#{template_extension_for(:js)}") template.evaluate(create_view_context) end # renders nothing but sets the status, defaults # to 200 def render_nothing(status=200) @status = status return "\n" end # renders the action without wrapping it in a layout. # call it without arguments if your template matches # the name of the running action. Otherwise you can # explicitely set the template name excluding the file # extension def render_no_layout(template=current_method_name(1)) template = new_eruby_obj(template_dir(self.class.name.snake_case) / "/#{template}.#{template_extension_for(:html)}") template.evaluate(create_view_context) end # This is merb's partial render method. You name your # partials _partialname.herb, and then call it like # partial(:partialname). If there is no '/' character # in the argument passed in it will look for the partial # in the view directory that corresponds to the current # controller name. If you pass a string with a path in it # you can render partials in other view directories. So # if you create a views/shared directory then you can call # partials that live there like partial('shared/foo') def partial(template) if template =~ /\// t = template.split('/') template = t.pop tmpl = new_eruby_obj(template_dir(t.join('/')) / "/_#{template}.#{template_extension_for(:html)}") else tmpl = new_eruby_obj(template_dir(self.class.name.snake_case) / "/_#{template}.#{template_extension_for(:html)}") end tmpl.evaluate(create_view_context) end # This creates and returns a new Erubis object populated # with the template from path. If there is no matching # template then we rescue the Errno::ENOENT exception # and raise a no template found message def new_eruby_obj(path) begin Erubis::MEruby.new(IO.read(path)) rescue Errno::ENOENT raise "No template found at path: #{path}" end end # this is the xml builder render method. This method # builds the Builder::XmlMarkup object for you and adds # the xml headers and encoding. Then it evals your template # in the context of the xml object. So your .xerb templates # will look like this: # xml.foo {|xml| # xml.bar "baz" # } def render_xml(template=current_method_name(1)) xml = Builder::XmlMarkup.new :indent => 2 xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8" eval IO.read( template_dir(self.class.name.snake_case) + "/#{template}.#{template_extension_for(:xml)}" ) @headers['Content-Type'] = 'application/xml' @headers['Encoding'] = 'UTF-8' xml.target! end # This is the main render method that handles layouts. # render will use layout/application.rhtml unless # there is a layout named after the current controller # or if self.layout= has been set to another value in # the current controller. You can over-ride this setting # by passing an options hash with a :layout => 'layoutname'. # if you with to not render a layout then pass :layout => :none # the first argument to render is the template name. if you do # not pass a template name, it will set the template to # views/controller/action automatically. # examples: # class Test < Merb::Controller # # renders views/test/foo.herb # # in layout application.herb # def foo # # code # render # end # # # renders views/test/foo.herb # # in layout application.herb # def bar # # code # render :foo # end # # # renders views/test/baz.herb # # with no layout # def baz # # code # render :layout => :none # end def render(opts={}) template = opts[:action] || params[:action] tmpl_ext = template_extension_for(:html) MERB_LOGGER.info("Rendering template: #{template}.#{tmpl_ext}") name = self.class.name.snake_case template = new_eruby_obj(template_dir(name) / "/#{template}.#{tmpl_ext}") view_context = create_view_context layout_content = template.evaluate(view_context) self.layout = opts[:layout].to_sym if opts.has_key?(:layout) return layout_content if (layout == :none) if layout != :application layout_choice = layout else if File.exist?(template_dir("layout/#{name}.#{tmpl_ext}")) layout_choice = name else layout_choice = layout end end MERB_LOGGER.info("With Layout: #{layout_choice}.#{tmpl_ext}") view_context.instance_eval { throw_content(:layout) { layout_content } } layout_tmpl = new_eruby_obj("#{template_dir('layout')}/#{layout_choice}.#{tmpl_ext}") layout_tmpl.evaluate(view_context) end end end