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