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.
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.
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.)
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.
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:
Some directives provide combinations of these processing flavors.
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.
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.
When you create a custom MasterView template directive, you need to do several things:
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
.
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:
:namespace
- the namespace used for the directive in template markup:attribute_name
- the attribute name used for the directive in template markup:priority
- processing priority level of the directive:description
- a short (1-liner) description of the directiveThe 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:
metadata
declaration in the directive class
:summary
and optionally to modify the default :priority
; used to specify :attribute_name
if necessary
.metadata
specification file in the directory on the load path
default:
metadata values for directive classes contained in this directory. Typically used to specify the :namespace
for a set of directives.metadata settings hash associated with a directive load path directory entry
default:
hash for metadata defaults.
Allows a client application to override the metadata defaults; can be used to modify the namespace for the directives used in template document markup by this application (e.g., to mediate directive namespace collisions if multiple sources of addon directives are used)
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'}, }
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.
attr_arg :argname, :quote => true
- wrap the argument value in (single) quotes
attr_arg :argname, :default => value
- specify default value if the argument is omitted
attr_arg :argname, :varargs => true
- collect remaining args (if any) as an array (like *params in ruby)
attr_arg :argname, :append_element_attrs => [attrName1, attrName1,...]
- merge the specified attributes from the element into a hash value
:common_html
as shorthand for collecting common html attributes (id, class, style, alt, title, width, height, etc)An attr_arg :argname
directive argument definition can also be specified with an block argument which allows for arbitrary processing to determine the value.
attr_arg :argname {...}
- set the argument value to the the result of evaluating the block with no arguments
attr_arg :argname {|value| ... }
- pass arg value into block to allow additional manipulation of the value
attr_arg :argname {|value args| ... }
- pass value and remaining args array to the block
attr_arg :argname {|value args directive| ... }
- pass value, remaining args array, and the directive instance for which the argument value is being obtained to the block
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
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.
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.
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:
Event | Description | Default 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
:render
option
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.