require 'facet/string/capitalized' require 'facet/string/camelize' require 'facet/class/method_name' require 'facet/dir/self/recurse' require 'glue/configuration' require 'nitro/template' module Nitro # A programmatically generated element. Elements are a form # of macros to allow for cleaner templates. They are evaluated # at compile time, so there is no performance hit when you use # them (at the expense of slightly reduced functionality). # # Nitro provides an additional method of defining elements. # Instead of creating a lot of small classes, you can put # .xhtml templates in the Element template_root. These templates # are automatically converted into Element classes. # # For extra safety, you are advised to place your classes in the # Nitro::Element namespace. If your classes do not extend # Nitro::Element, the Nitro::ElementMixin is automatically # injected in the class. # # An element can have and access a hierarchy of sub-elements. # use #{content :sub_element_name} to access the render output # of the subelement. Additionaly you can access the whole # subelement object: _children[:sub_element_name] # # === Design # # An underscore is used for the standard attibutes to # avoid name clashes. #-- # TODO: # * separate 'view' template files. #++ module ElementMixin # The parent of this element. attr_accessor :_parent # The children of this element. attr_accessor :_children alias_method :children, :_children # The text of this element. attr_accessor :_text # The view of this element. attr_accessor :_view # The id of this element. attr_accessor :id def initialize(*args) @_children = {} @_text = '' @id = self.class.demodulize.underscore end # Prepend this code to the element content. def open end # If an optional name parameter is passed renders # the content of the named child element. # # eg. #{content :child_element_id} # # === Example # # # .. # # # .. # # # # .. # # # # .. # # # .. # # # # Access children content from within the enclosing element # (Page) like this: # # {content :hello} # {content :world} # {content :sidebar} def content(cname = nil) if cname if c = @_children[cname.to_s] c.content else return nil end else @_text end end # Append this code to the element content. def close end # Override this. def render "#{open}#{content}#{close}" end def render_children str = '' for c in @_children.values str << c.render end return str end def add_child(child) child._parent = self @_children[child.instance_variable_get('@id')] = child end alias_method :children, :_children end # A programmatically generated element. # # === Usage # # = in the code # # class Page < Nitro::Element # def render # %{ #
#{content}
# } # end # end # # = in your template # # hello # # =>
hello
# # the id is automatically fille with the class name using class.method_name # eg. MyModule::MyPage => my_module__my_page # # you can override the id to use the element multiple times on the page # # == Sub Elements # # Elements can be imbricated. To render the the child element in the parent's template, # use #{content :element_id} # # === Design # # An underscore is used for the standard attibutes to # avoid name clashes. #-- # TODO: # * separate 'view' template files. #++ class Element include ElementMixin # The prefix for element tags (in xhtml compatibility mode) setting :prefix, :default => 'x', :doc => 'The prefix for element tags' # Allow auto extension of element classes? setting :auto_extend, :default => true, :doc => 'Allow auto extension of element classes?' # The directory where element templates reside. The default # dir is #{Nitro::Template.root}/element if File.exist?(File.join(Nitro::Template.root, 'element')) default_root = File.join(Nitro::Template.root, 'element') else default_root = 'element' end if File.exist?('src/element') default_root = 'src/element' elsif File.exist?('src/template/element') default_root = 'src/template/element' else default_root = 'element' end setting :template_root, :default => default_root, :doc => 'The directory where element templates reside' class << self # Compile the element templates into element classes. # Typically called at startup. def compile_template_elements if File.exist? Element.template_root Dir.recurse(Element.template_root) do |filename| if filename =~ /\.#{Nitro::Template.extension}$/ name = File.basename(filename).split('.').first.camelize Nitro::Element.module_eval %{ class #{name} < Nitro::Element def render <<-END_OF_TEMPLATE #{File.read(filename)} END_OF_TEMPLATE end end } end end end end end end # Compile the element templates into element classes. Element.compile_template_elements end