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