·  Doc Home  |  About  |  Installation  |  Configuration  |  Usage  |  Directives  |  Developers  |  Screencasts  ·

MasterView Developer's Guide

The MasterView template engine is designed to be easily configurable for use in your application, by customizing configuration settings for your environment as described in the Configuration Guide. It is also designed to be easily extensible by adding custom directives, as described in this guide.

Before undertaking implementation of your own custom directive, we recommend that you review the Overview of Template Processing and Directives section, which discusses the processing context within which a directive implementation executes. Then read the Directive Implementation Framework for a guide to the facilities provided by MasterView to simplify the development effort required to implement a new directive.

Overview of Template Processing and Directives

A MasterView template is an XHTML document which uses attribute markup on the standard document elements to encode processing directives for the template engine. The XML namespace facility is used to identify attributes containing processing instructions.

Directive Attribute Markup

The builtin set of MasterView directives use the mv: namespace to form qualified attribute names which are unique in your html documents. (Although we do not anticipate namespace collisions, you can if needed override the mv: namespace name using the namespace_prefix configuration setting.)

Note: an XML schema definition of the builtin MasterView directives namespace should be published on the masterview.org site. This would enable a proper declaration of the MasterView attribute namespace on your <html> elements of the form xmlns:mv='http://masterview.org/schema/2006/directives' (specific URI tba when published), which is useful as a documentary aide as well as to enable validation.

In addition to the builtin MasterView directives, custom directives can be used by appending one or more directories containing directive implementation classes the MasterView directive load path (configuration setting directive_paths). Custom directives are loaded at MasterView startup time and may be used in document markup jst like the standard directives. MasterView manages all available directives in a registry which specifies the fully qualified name of the directive attribute (namespace + simple attribute name) and the directive implementation class which is instantiated to process a directive attribute.

TODO: include recommendation on namespace usage for custom directives. Believe we should use mvx: namespace as the default for extension directives, to distinguish from the builtin directives namespace.

Template Document Processing

When MasterView processes a template document, the document is processed as a DOM node hierarchy of elements and attributes. Template processing is essentially a tree transformation process: the elements and their attributes from the template source document are processed and emitted onto the output.

By default, elements with their attributes and content are preserved unchanged in the output. The addition of MasterView directive attributes to an element allows for transformation and substitution to occur between the template source and the generated output.

It can be useful to think of directives as providing several typical flavors of processing:

attribute modifiers
the directive operates on the attributes of the element (adds or removes attributes or changes values of existing attributes)
content modifiers
the directive operates on the content of the element (modifies or replaces the content of the element)
eval-only
the directive is evaluated such that it has some effect on the overall state of the document processing, but does not directly affect the attributes or content of its element in the output

Some directives provide combinations of these processing flavors.

Template Directive Processing

As each element in the source document is processed, any attributes in the element start tag which are MasterView directives are collected and sorted into processing order according to the priority order defined for each directive. The MasterView::DirectivePriorities range from Lowest to Highest, with DirectivePriorities::Medium the default. In most cases, you do not need to be concerned with priority - the default priority order is acceptable for most directives.

For each MasterView directive attribute on a document element, the template engine creates an instance of the directive's implementation class to provide the directive processing for that element. The directive handler is provided with its attribute value from the document element when it is constructed so that it has access to any parameters provided to control its operation.

TODO: add note about "global directives" that can be activated on all elements in the document or on selected elements, e.g., all elements of a specific type (all <img> elements, all <table> elements...). This may be something that we want to control on a per-document basis, so we're considering the need for a masterview directive which would configure doc-specific options

A directive handler can be invoked to perform its processing at several points during the processing of the document element on which it is defined:

(The possible invocation points are actually slightly more detailed, but this simplified list of processing points is sufficient for discussing the behavior of most directives.)

By implementing processing logic at these various points during the overall document processing, a directive can accomplish its objectives by affecting the element attributes that are emitted to the output document, by manipulating the element content that is rendered, or by controlling the entire element (if any) that is rendered onto the output from the source.

Recall also that a document element can contain other elements, in addition to or instead of text content. Directives on nested elements are processed along with the child elements in the source document, so a directive on an ancestor element sees the result of any nested directives at the point where it operates on the resulting element and its content.

Directive Implementation Framework

When you create a custom MasterView template directive, you need to do several things:

Creating the Directive Implementation Class

A directive implementation extends the MasterView::DirectiveBase base class, which provides the standard infrastructure for directives.

module MyAppDirectives
  class MyDirective < MasterView::DirectiveBase
    # ... directive metadata declarations ...
    # ... directive attribute value argument definitions ...
    # ... template element processing event handlers ...
  end
end

Note: it is not mandatory to subclass DirectiveBase to implement a directive class, but it'll certainly make your life easier. The key mixin classes which provide the essence of directive-nesss in the DirectiveBase implementation framework are DirectiveMetadata and DirectiveDSL, along with some convenience services from DirectiveHelpers.

Directive Metadata Specification

A directive is described by various standard properties which are associated with the directive implementation class as metadata properties. Some of the metadata properties affect the processing operations of the template engine, while others are descriptive information for documentation purposes.

The standard directive metadata properties recognized by MasterView are:

The default attribute name for a directive is derived from the class name, similar to the name association schemes used by Rails for controllers, helpers, and views, so this metadata attribute typically does not need an explicit declaration. By default, directive class Foo is associated with attribute foo in template markup, while FooBar is associated with attribute foo_bar, etc.

The default directive priority level is Medium, on the spectrum ranging from Highest...VeryHigh...High...Medium...Low...VeryLow...Lowest.

Directive metadata can be specified in several ways, with a precedence hierarchy allowing for application-specific customization:

Directive Argument Definition Examples

module MyAppDirectives
  class MyDirective < MasterView::DirectiveBase

    metadata :priority => 'High',
      :description => 'My really cool directive'

    # ... directive attribute value argument definitions ...
    # ... template element processing event handlers ...
  end
end

A .metadata file in the directory containing directive class .rb code files:

# MasterView directive metadata specifications
# Built-in MasterView directives

# default metadata values for directives loaded from this directory
# template document attribute qnames: <xxx coolops:foo="bar">...</xxx>
default:
  namespace: coolops

An application-specific override of the default namespace for the directives loaded from a directory specified in the application's masterview/settings.rb configuration:

config.add_directive_path '/path/to/custom/directives', { :default: => {:namespace => 'xops'}, }

Directive Attribute Syntax Specification

When you create a directive, you need to specify the syntax that will be used for writing the directive attribute and its value in template document markup. Your directive will generally need to be provided with arguments which control the processing of the document element on which the directive attribute is defined.

In some cases, the entire directive attribute value is used as a single argument. For example, the mv:content directive which replaces the content of the element on which it is defined with the results of evaluating a ruby expression uses its attribute value string to generate an embedded ruby block <%= ...attr value... %> in the output. A directive implementation can access its attribute value using the attr_value accessor.

A directive can also use its attribute value as an argument list, just as arguments are used when invoking a Ruby method. The MasterView directive implementation framework provides a simple declarative notation for defining the argument list signature of an attribute. Each argument that you declare is mapped to an instance variable in your directive class, with parsing of the attribute value string into the variable value handled automatically by the directive framework facilities of MasterView.

As with parameters to a Ruby method, directive arguments obtained from the attribute value string in the template souurce document can be expressed as a sequence of positional parameters, optionally with a default value. Keyword values can be specified using Ruby hash literal notation { key1 => value1, key2 => value2, ...}. Trailing values in a variable-length argument list can be collected in a single array value argument. Finally, an optional block argument can be provided with an argument definition to provide custom processing on the value.

To define the parameters for your directive and the parsing rules for extracting each value from the directive attribute value string, use the attr_arg declaration in your directive implementation class. The first argument to attr_arg is a symbol with the name of the argument, which is used to create an accessor method for an instance variable that will contain the argument value. Argument values extracted from the attribute value are strings.

Additional options can be specified in the form :option_name => value to indicate how the value should be obtained. By default, an omitted argument value is parsed as an empty string.

An attr_arg :argname directive argument definition can also be specified with an block argument which allows for arbitrary processing to determine the value.

Directive Argument Definition Examples

module MyAppDirectives
  class MyDirective < MasterView::DirectiveBase

    # wrap the value parsed from the attribute value in string quotes
    attr_arg :string_lit_arg, :quote => true

    # a value that we want to normalize
    attr_arg :some_option { |value| value.downcase }

    # a value with a default if not specified
    attr_arg :other_option, :default => 'normal'

    # collect attributes from the element
    attr_arg :html_options, :append_element_attrs => [:common_html, :size]

    # ... template element processing event handlers ...

  end
end

Directive Attribute Value Processing

When a directive attribute is encountered while processing a document element node, an instance of the implementation class registered for that directive is constructed for processing the element. The value of the directive attribute from the document element is passed to the directive's initializer as a String value and is available to the directive handler using the attr_value accessor.

The directive argument definitions from the attr_arg specifications in the directive implementation are applied in declaration order to initialize the instance variable values of the directive handler from the attribute value.

Document Element Processing

A directive implementation specifies its effect on processing the document element to which it is attached by declaring one or more event handlers using the event declaration. An event handler definition specifies a processing event at which it is to be invoked and the action to be taken.

Document Processing Event Registration

The general form of an event handler declaration is event :eventname action.

A directive can specify an action to be performed at any of the following events which are notified during the processing of a document element:

EventDescriptionDefault Action
:before_stag tbs  
:stag element start tag and attributes are available Append the start tag to the rendering output for this element
:after_stag tbs  
:content the element content (text and/or child elements) is available Append the source element content to the rendering output for this element
:before_etag tbs  
:etag the element end tag has been reached Append the element end tag to the rendering output for this element
:after_etag tbs  
:element the complete document element (start/end tags + content) is available Append the accumulated element rendering to the output document

As noted in the overview, most directive processing is associated with the :stag event, when the directive can manipulate the attributes of the element, or with the :content or :element events when performing transformations which involve the element content or the entire (tags+content) element output. Processing done on the :etag event is used by conditional directives that control the presence or absence of the element in the output document. An :etag handler is also used in combination with :stag when providing directive processing which wraps the element in some fashion (e.g., the mv:block directive which wraps a document element with ruby erb inserts to form a block evaluation context).

The before_ and after_ events can be used to insert additional content or elements into the document, as well as being useful for initializing or updating the state of the directive handler itself. However, when used to append additional output these processing events should be used with care to ensure that the resulting output is a properly-defined document structure.

The action for an event handler can be defined by specifying an option which indicates a standard processing action or by providing a block containing the processing logic.

module MyAppDirectives
  class MyDirective < MasterView::DirectiveBase
    # ... directive attribute value argument definitions ...

   event :stag {
       # ... add/change/transform element attributes...
   }
   event :content do
      #... modify the element content before it is emitted onto the output
   end

  end
end
TODO: add an example of a handler which specifies its action using a standard :render option

Document Processing Event Actions

An event handler block in a directive implementation can reference any of its attribute arguments using the accessor named in an attr_arg :argname declaration or its complete atribute value string using the attr_value accessor. It can also access information about its document element in the source template using element_attrs to access a hash of the element attributes and element_tag to obtain the name of the element to which it is attached.

At each event during the course of processing a template source document element, the output content being accumulated for that element can be affected. The principal service used by a directive handler to specify its contribution to the accumulated rendering of the element is the render service. As with Rails controllers and views, a directive event handler can actively specify the render output it wishes to produce. If no rendering operation is performed by an event handler, the default rendering is provided automatically.

The argument for a render operation is the symbol :nothing to specify that no output should be produced for this event. This option can be used by conditional processing directives to suppress content from the output.

More commonly, a directive handler will invoke render to cause either text content, possibly including html tag markup as well as element content, or erb markup to be appended to the element output. The default rendering for the element is available from the render_result accessor. This default rendering can be modified or used with erb markup. The helper function erb_eval generates output wrapped in erb evaluation markup <% ...ruby expression(s)... %> which causes the content to be evaluated as ruby code without affecting the content of the view. The companion helper function erb_content generates output wrapped in erb evaluation markup <%= ...ruby expression(s)... %> which causes the content to be evaluated as ruby code and the resulting value inserted in the final view content.