Groovy Goodness: Using Layouts with MarkupTemplateEngine
The MarkupTemplateEngine
added in Groovy 2.3 is very powerful. We can define layout templates with common markup we want to be used in multiple other templates. In the layout template we define placeholders for variables and content blocks surrounded by shared markup. We define values for these variables and content blocks in the actual template. We even can choose to propagate model attributes from the template to the layout template. Let's first create a layout template with the name main.tpl
:
// File: main.tpl
html {
head {
// Use pageTitle layout property.
title(pageTitle)
}
body {
section(id: 'main') {
// Render mainContents layout property.
mainContents()
}
section(id: 'actions') {
// Render actions layout property.
actions()
}
footer {
// A template is also Groovy code, we can
// define new variables or methods.
// pubDate should be set via original template
// model.
def generatedOn = pubDate ?: new Date()
p("Generated on ${dateFormat(generatedOn)}")
}
}
}
def dateFormat(date) {
date.format('dd-MM-yyyy')
}
Now we can write the template that uses this layout and create a result:
// File: LayoutTemplate.groovy
import groovy.text.*
import groovy.text.markup.*
// Create engine with configuration.
TemplateConfiguration config = new TemplateConfiguration()
MarkupTemplateEngine engine = new MarkupTemplateEngine(config)
// Create template with layout reference
// and values for layout variables.
Template template = engine.createTemplate('''
layout 'main.tpl',
pageTitle: 'Welcome',
mainContents: contents {
h1 'Home'
},
actions: contents {
ul(class: 'actions') {
['Home', 'About'].each { li it }
}
}
''')
// Render output for template.
Writer writer = new StringWriter()
Writable output = template.make(\[:\])
output.writeTo(writer)
String result = writer.toString()
// This is what we would expect as a part of the result.
def expected = $/.*
Home
====
\\
* Home
* About
\\
Generated on 11-08-2014
\\
.*/$
assert result ==~ expected
Notice we can assign directly a value to a layout property (pageTitle) or we can use the contents
method with a closure (mainContents
and actions
). The value of the contents
method is assigned to the layout property when the template is generated. This is useful when the value of a layout property is a closure we don't want to evaluate immediately, but when the template is generated.
We didn't specify a value for the template model property pubDate
. If we want to use this property on our layout template as well, we must specify an extra argument with the layout
method. The second argument must be true
to instruct the layout that model properties are propagated. The default value is false
:
// File: LayoutTemplate.groovy
import groovy.text.*
import groovy.text.markup.*
// Create engine with configuration.
TemplateConfiguration config = new TemplateConfiguration()
MarkupTemplateEngine engine = new MarkupTemplateEngine(config)
// Create template with layout reference
// and values for layout variables.
Template template = engine.createTemplate('''
layout 'main.tpl', true,
pageTitle: 'Welcome',
mainContents: contents {
h1 'Home'
},
actions: contents {
ul(class: 'actions') {
['Home', 'About'].each { li it }
}
}
''')
// Render output for template.
Writer writer = new StringWriter()
Writable output = template.make(pubDate: Date.parse('yyyyMMdd', '20140801'))
//Writable output = template.make([:])
output.writeTo(writer)
String result = writer.toString()
// This is what we would expect as a result.
def expected = $/.*
Home
====
\\
* Home
* About
\\
Generated on 01-08-2014
\\
.*/$
assert result ==~ expected
Code written with Groovy 2.3.6.