module MasterView
# Namespace module for built-in directive implementations
# that are standard with the MasterView template engine.
#
module Directives
end
# Base class for directive implementations.
#
# The standard technique for implementing a directive
# is to subclass DirectiveBase. The builtin
# MasterView directives are implemented in
# module namespace MasterView::Directives.
#
# If you create a directive implementation class
# elsewhere in the class hierarchy, it is recommended
# that you include the DirectiveHelpers mixin.
# If you do not include the PluginLoadTracking mixin,
# you will need to manually register your directive
# class using the MasterView.register_directive service.
# either the PluginLoadTracking mixin or
#
#
#--
#TODO: add docs here on responsibilities and techniques for
# implementing directives. Subclass DirectiveBase, define
# within MasterView::Directives module namespace.
#
# mumble - class methods:
# attr_name is the directive's attribute markup name - def. is class name
# full_attr_name(ns) is the qualified name (name-space prefixed)
#
# Directive can optionally implement class method :on_load to initialize
# itself prior to processed for the directive registry
#
#TODO: establish protocol convention for additional namespaces
#
# mumble: :global_directive? predicate indicates... what?? (inline erb)
#
# Directives implement priority
to control the processing
# order when multiple directive attributes are used on a template
# document element.
#
#TODO: document the priority hierarchy and convention for level usage
#
# Discuss operational context: describe how/when MasterView parser
# invokes a directive handler in the course of parsing the elements
# and attributes of a template document node hierarchy.
# Notion of directive call stack; what state is the world in and
# information is available to a directive when invoked;
# what services are available in order to produce some effect
# on the results of the template parse.
#
# Two general flavors on content directives: those which operate on attribute
# values of the containing element and those which operate on the
# entire containing element, sometimes by supplying or modifying its
# content, in other cases by replacing the template element with something
# else.
#
# Third flavor: eval-only directive. Expression which is evaluate for its
# effect on the directive processing context or the overall state of processing
# the template document element tree.
#
# Directive implementation typically wants to implement either or both of
# the methods stag(dcs)
and etag(dcs)
to hook
# up its processing on the start/end tags of the element on which the
# attribute is defined.
#
# When a directive attribute is used on a template document element,
# the directive class is instantiated with the attribute_value provided
# to its constructure. All directives used on an element are sorted
# into processing order according to their priority
# (default is Medium
. The directive processor is invoked
# when the element start tag is encountered and when the element end
# tag is completed, allowing the implementation to control when and
# how its processing is hooked up to effect the template output.
#++
#
class DirectiveBase
include PluginLoadTracking
include DirectiveHelpers
# Register a class manually without regard to whether it inherits from DirectiveBase.
# Classes which derive from DirectiveBase will automatically be registered as they are
# loaded.
def self.register_directive(directive_class)
#ISSUE: do we really need both PluginLoadTracking.register_class
#and DirectiveBase.register_directive, in addition to MasterView.register_directive???
#[DJL 04-Jul-2006]
MasterView.register_directive(directive_class)
end
# Returns the fully qualified attribute name of the directive,
# consisting of the directive namespace and the directive attribute name.
#
# The default MasterView namespace_prefix is used if the directive does not
# specify a separate namespace.
#
#--
#TODO: clarify the mechanism by which alternate namespaces are defined.
# Is this done by a code value or configured as part of the directory
# path specifications, or some combination thereof to allow ovverides
# in the event of namespace collisions?
#++
#
def self.full_attr_name( namespace_prefix )
#TODO: fix this so that directives can override to define their own namespace separate from mv:
namespace_prefix + self.attr_name
end
# Returns the attribute name of the directive.
#
# Use full_attr_name to obtain the fully-qualified name
# of the directive attribute with the qualifying namespace prefix.
#
# The default attribute name of a directive is formed
# from the simple class name (without any module prefix qualifier),
# with the first character downcased.
#
def self.attr_name()
self.default_attr_name()
end
def self.default_attr_name() #:nodoc:
self.name.split(':').last.downcase_first_letter
end
# Construct a directive processor for the attribute_value
# of a directive attribute on a document template element.
#
def initialize(attribute_value)
@attribute_value = attribute_value
end
#if this method exists, it will be called by renderer to save directive_call_stack before each method call
def save_directive_call_stack(directive_call_stack)
@directive_call_stack = directive_call_stack
end
# Returns the directive's attribute value string being processed.
def attr_value
@attribute_value
end
# Set the directive's attribute value string to be processed.
def attr_value=(attribute_value)
@attribute_value = attribute_value
end
#get attribute hash from tag
def attrs
@directive_call_stack.context[:tag].attributes
end
#set attribute hash for tag
def attrs=(attributes)
@directive_call_stack.context[:tag].attributes = attributes
end
#get attribute hash with lowercased keys and values, and cache it
def attrs_lckv
@attrs_lckv ||= lowercase_attribute_keys_and_values(attrs)
end
#get attribute hash with lowercased keys (and original values) and cache it
def attrs_lck
@attrs_lck ||= lowercase_attribute_keys(attrs)
end
#returns true if the value for lckey of the attribute hash with lowercased keys and values
#matches (lowercase) lcmatch string
def attr_lckv_matches(lckey, lcmatch)
(attrs_lckv[lckey] && attrs_lckv[lckey] == lcmatch.downcase) ? true : false
end
#DEPRECATED - going away
#output '<% '+str+' %>'
def erb(str) #:nodoc:
#ISSUE: convert clients to erb_eval and drop. Ya oughta have a point of view. [DJL 04-Jul-2006]
ERB_START + str + ERB_END
end
# Compose an Erb expression which produces content in the containing document.
# The expression may also have effects on the processing context of
# the template document
#
# output '<%= '+str+' %>
#
def erb_content(str)
ERB_CONTENT_START + str + ERB_CONTENT_END
end
# Compose an Erb expression which is evaluated for its effect on the processing context
# but does not produce content in the containing document.
#
# output '<% '+str+' %>'
#
def erb_eval(str)
ERB_EVAL_START + str + ERB_EVAL_END
end
#get tag_name
def tag_name
@directive_call_stack.context[:tag].tag_name
end
#set tag_name
def tag_name=(tag_name)
@directive_call_stack.context[:tag].tag_name = tag_name
end
#inside characters, cdata, or comment you can call this to get the characters passed
def data
@directive_call_stack.context[:content_part]
end
#set the data that will be passed to characters, cdata, or comment directives
def data=(data)
@directive_call_stack.context[:content_part]=data
end
# rolled up content from all children of the tag, note this will not be complete until hitting the end tag method :etag
def content
@directive_call_stack.context[:tag].content
end
#return rolled up content from all children as string, note this will not be complete until hitting the end tag method :etag
def content_str
content = @directive_call_stack.context[:tag].content
content = content.join if content.respond_to? :join
content
end
# replace the content from all children with a new value
def content=(content)
@directive_call_stack.context[:tag].content = content
end
#add single quotes around string
def quote(str)
'\''+str+'\''
end
# adds single quotes around string if it is a simple
# word [a-zA-Z0-9_]* otherwise return existing string
# also quote if empty string
def quote_if(str)
(str =~ /^\w*$/) ? quote(str) : str
end
def remove_strings_from_attr_value!
self.attr_value = remove_prepended_strings(attr_value)
end
#prepend string to attribute value adding a comma if attribute value was not empty
def prepend_to_attr_value!(str)
return attr_value if str.nil? || str.strip.empty?
av = str
av << ', ' << attr_value unless attr_value.strip.empty?
self.attr_value = av
end
#append string to attribute value adding a comma if attribute value was not empty
def append_to_attr_value!(str)
return attr_value if str.nil? || str.strip.empty?
av = attr_value
av << ', ' unless av.strip.empty?
av << str
self.attr_value = av
end
#merge merge_hash into hashes stored in attribute_value string
#hash_index is the zero based index of the hash you want to add to
def merge_hash_attr_value!(hash_index, merge_hash)
self.attr_value = merge_into_embedded_hash(attr_value, hash_index, merge_hash)
end
#calls non-evaling parse to split into string arguments
def parse_attr_value
parse(attr_value)
end
# check for common html options and return the hash
def common_html_options(attrs_lck)
options = {}
options[:id] = attrs_lck['id'] if attrs_lck['id']
options[:class] = attrs_lck['class'] if attrs_lck['class']
options[:style] = attrs_lck['style'] if attrs_lck['style']
options[:tabindex] = attrs_lck['tabindex'] if attrs_lck['tabindex']
options[:accesskey] = attrs_lck['accesskey'] if attrs_lck['accesskey']
options[:disabled] = true if attrs_lck['disabled']
options[:readonly] = true if attrs_lck['readonly']
options
end
end
end