A templated content-generation plugin for Strelka::Apps. It uses the Inversion templating system.
It adds:
a preloaded/cached template table
a mechanism for fetching templates from the table
a global layout template which is automatically wrapped around responses
To use it, just load the :templating
plugin in your app:
plugins :templating
and declare one or more templates that your application will use:
templates :console => 'views/console.tmpl', :proctable => 'partials/proctable.tmpl'
Then, inside your app, you can fetch a copy of one or more of the templates and return it as the reponse:
def handle_request( req ) super do res = request.response proctable = template :proctable proctable.processes = ProcessList.fetch tmpl = template :console tmpl.message = "Everything's up." tmpl.proctable = proctable res.body = tmpl return res end end
You can also just return the template if you don't need to do anything else to the response.
When returning a template, either in the body of the response or directly, it will automatically set a few attributes for commonly-used objects:
The current Strelka::HTTPRequest
The application object (Strelka::App instance).
Strelka.version_string( true )
Mongrel2.version_string( true )
The RUBY_VERSION of the running interpreter.
If the :routing plugin is loaded, this will be set to the 'routing_info' of the chosen route. See Strelka::Router#add_route for details.
If your app will only be loading and returning a template without doing anything with it, you can return just its name:
def handle_request( req ) super { :console } end
It will be loaded, set as the response body, and the above common objects added to it.
:todo: Explain how returning things other than responses doesn't work well with
:filters and maybe other plugins that run inside :templating.
Very often, you'll want all or most of the views in your app to share a common page layout. To accomplish this, you can declare a layout template:
layout 'layout.tmpl'
Any template that you return will be set as the 'body' attribute of
this layout template (which you'd place into the layout with
<?attr body ?>
) and the layout rendered as the body of
the response.
Note that if you want any of the “common objects” from above with a layout
template, they'll be set on it since it's the top-level template,
but you can still access them using the <?import ?>
directive:
<?import request, strelka_version, route ?>
Inversion looks for templates in a load path much like Ruby does for
libraries that you 'require'. It contains just the current working
directory by default. You can add your own template directories via the
config file (under template_paths
in the
templates
section), or programmatically from your application,
but very often you'll want to distribute templates with the application
gem.
The plugin supports this by looking for a templates/
directory
under your gem's data directory. If it finds such a directory for any
loaded gem that has a Strelka dependency,
it appends it to Inversion's template_paths
. This also
works for plugins, should you write your own, and want to provide some
default templates. See the 'laika-fancyerrors' gem for an example
of this.
The layout template (an Inversion::Template), if one was declared
The map of template names to Inversion::Template instances.
Return an Array of Pathnames to all directories named 'templates' under the data dirctories of loaded gems which have a dependency on Strelka.
# File lib/strelka/app/templating.rb, line 127
def self::discover_template_dirs
directories = Strelka::Discovery.discover_data_dirs.values.flatten
self.log.debug "Discovered data directories: %p" % [ directories ]
return directories.inject( [] ) do |array, dir|
pattern = File.join( dir, 'templates' )
self.log.debug " adding: %s" % [ pattern ]
array += Pathname.glob( pattern )
end
end
Inclusion callback – add the plugin's templates directory right before activation so loading the config doesn't clobber it.
# File lib/strelka/app/templating.rb, line 142
def self::included( mod )
# Add the plugin's template directory to Inversion's template path
dirs = self.discover_template_dirs
self.log.info "Discovered template directories: %p" % [ dirs ]
Inversion::Template.template_paths.concat( dirs )
super
end
Preload any templates registered with the template map.
# File lib/strelka/app/templating.rb, line 192
def initialize( * )
super
@template_map = self.load_template_map
@layout = self.load_layout_template
end
Fetch the template from the response
(if there is one) and
return it. If response
itself is a template.
# File lib/strelka/app/templating.rb, line 267
def extract_template_from_response( response )
# Response is a template name
if response.is_a?( Symbol ) && self.template_map.key?( response )
self.log.debug " response is a template name (Symbol); using the %p template" % [ response ]
return self.template( response )
# Template object
elsif response.respond_to?( :render )
self.log.debug " response is a #renderable %p; returning it as-is" % [ response.class ]
return response
# Template object already in a Response
elsif response.is_a?( Mongrel2::Response ) && response.body.respond_to?( :render )
self.log.debug " response is a %p in the body of a %p" % [ response.body.class, response.class ]
return response.body
# Not templated; returned as-is
else
self.log.debug " response isn't templated; returning nil"
# :TODO: Return the response instead of nil
return nil
end
end
Intercept responses on the way back out and turn them into a Mongrel2::HTTPResponse with a String for its entity body.
# File lib/strelka/app/templating.rb, line 242
def handle_request( request, &block )
response = super
self.log.debug "Templating: examining %p response." % [ response.class ]
template = self.extract_template_from_response( response ) or
return response
# Wrap the template in a layout if there is one
template = self.wrap_in_layout( template, request )
# Set some default stuff on the top-level template
self.set_common_attributes( template, request )
# Now render the response body
self.log.debug " rendering the template into the response body"
response = request.response unless response.is_a?( Mongrel2::Response )
response.body = template.render
response.status ||= HTTP::OK
return response
end
Load an Inversion::Template for the layout template and return it if one
was declared. If none was declared, returns nil
.
# File lib/strelka/app/templating.rb, line 233
def load_layout_template
return nil unless ( lt_path = self.class.layout_template )
enc = Encoding.default_internal || Encoding::UTF_8
return Inversion::Template.load( lt_path, encoding: enc )
end
Load instances for all the template paths specified in the App's class and return them in a hash keyed by name (Symbol).
# File lib/strelka/app/templating.rb, line 222
def load_template_map
return self.class.template_map.inject( {} ) do |map, (name, path)|
enc = Encoding.default_internal || Encoding::UTF_8
map[ name ] = Inversion::Template.load( path, encoding: enc )
map
end
end
Set some default values from the request
in the given
top-level template
.
# File lib/strelka/app/templating.rb, line 308
def set_common_attributes( template, request )
template.request = request
template.app = self
template.strelka_version = Strelka.version_string( true )
template.mongrel2_version = Mongrel2.version_string( true )
template.ruby_version = RUBY_VERSION
template.route = request.notes[:routing][:route]
end
Return the template keyed by the given name
. :TODO: Add
auto-reloading,
# File lib/strelka/app/templating.rb, line 212
def template( name )
template = self.template_map[ name ] or
raise ArgumentError, "no %p template registered!" % [ name ]
template.reload if template.changed?
return template.dup
end
Wrap the specified content
template in the layout template and
return it. If there isn't a layout declared, just return
content
as-is.
# File lib/strelka/app/templating.rb, line 295
def wrap_in_layout( content, request )
return content unless self.layout
self.layout.reload if self.layout.changed?
l_template = self.layout.dup
self.log.debug " wrapping response in layout %p" % [ l_template ]
l_template.body = content
return l_template
end