While developing several full stack apps over years, I realised some apps don’t
need a full fledged frontend application running. Most of these apps serve
static content with dynamic values fitted here and there. For example, consider
your social media feed, each post of a certain type looks same but it gets
populated with data specific to that user. They use some sort of templating to
achieve it.
What are templates?
Templates are essentially text files that are used to create dynamic content.
For instance the following javascript function takes “name” as argument and
produce different strings.
We can also use same principal to generate web pages in Go. Web templates allow
us to serve personalised results to users. Generating web templates using string
concatenation is a tedious tasks. It can also lead to injection attacks.
Templates in go
There two packages in go to work with templates:
- text/templates (Used for generating textual output)
- html/templates (Used for generating HTML output safe against code injection)
Both of them basically have same interface with subtle web specific differences
in latter like encoding script tags to prevent them from running, parsing maps
as json in view.
Our First Template
- The extension of template file could be anything. We are using
.gohtml
so that it is supported development tools across IDEs. - “Actions” — data evaluations or control structures — is delimited by
{{
and}}
. The data evaluated inside it is called Pipeline. Anything outside them is send to output unchanged.
You can parse multiple files on line:10 as comma separated strings (path to
file) or parse all files inside a directory. For example:
tpl, err := template.ParseFiles(“index1.gohtml”, “index2.gohtml”)
tpl, err := template.ParseGlob("views/templates/*")
- It returns a template container (here called tpl) and error. We can use our data to execute tpl *by calling method Execute. *In case of multiple templates, The name of template will be passed as 2nd argument while data being 3rd.
- We define our data structure (here type struct). It could be anything in go slice, map, struct, slice-of-structs, structs-of-slice of structs. We will see each of them shortly.
- The data is fetched using *dot (aka cursor). *We use it to access variables of
data inside templates. Remember the identifiers in provided data have to start
with Capital Case. We can use
them to initialise a variable inside Actions like
$myCoupon := .Coupon
. - We can output the execution result to web page or standard output because template Execute method takes any value which implements type Writer interface.
It renders as plain text in output console, but If we use it to send as response
to web request it will be rendered as a web page enabling us to use HTML tags.
It is preferred to do the parsing job inside init function and execution at
required places.
func init() {
// Must is a helper that wraps a function returning
// (*Template, error) and panics if the error is non-nil.
tpl = template.Must(template.ParseGlob(“templates/*”))
}
Using different data structures in web templates
Slice (or Array)
Let’s consider a
slice of
strings:
This can be used in template by ranging over the Slice (also array) inside
Actions. If the value of the pipeline has length zero, nothing will be
executed
otherwise, dot(aka cursor) is set to the successive elements of
the Slice (also array) and template is executed.
Map
Let’s consider a Map data
structure:
The value of the pipeline inside Action is a map. If there are no key values
pairs in map, nothing is output, otherwise, dot (aka cursor) is set to the
successive elements of the map and template is executed with $key
taking each
key, $val
taking the respective value. If keys are of basic type with a
defined order ("comparable"), the elements will be visited in sorted key order.
Struct
Let’s consider a struct with collection of
fields as declared
inline:
The name of a field of the data struct, preceded by a period, such as .Name
is
the argument in template. The result is the value of the field. Field
invocations may be chained: .Field1.Field2
. Fields can also be evaluated on
variables, including chaining: $x.Field1.Field2
. The following template shows
the fields store in variables and then evaluated in Actions.
Slice of structs
Let’s consider a slice of structs :
We use range to iterate over slice. The value of the pipeline in Action must be
an array (or slice). If the value of the length of slice is zero, nothing is
output; otherwise, dot (aka cursor) is set to the successive elements of the
array, slice and Template is executed.
Like this different data structures in go can be combined to make useful
templates.
A note on Conditionals in templates:
It is also possible to write conditionals templates
{{if pipeline}} T1 {{else if pipeline}} T0 {{end}}
like this. This gives us amazing abilities to generate dynamic content. If value of pipeline is empty (that is, false, 0, any nil pointer or interface value, and any array, slice, map, or string of length zero), T0 is executed else T1 is executed.
Function In templates
To send a function in go template we can use, by default, no functions are
defined in the template but the Funcs
method on template can be used to add
function by creating mappings like this:
template.Must(template.New(“”).Funcs(fm).ParseFiles(“index.gohtml”))
To create a mapping from names to functions we need to use type FuncMap
. Each
function must have either a single return value, or two return values of which
the second has type error.
type FuncMap map[]interface{}
On line:7, we have defined a mapping of function monthDayYear to *name
fdateMDY. Now this function can be used inside template. Remember dot (aka
*cursor) holds the data provided to template.
These are various ways we can use templates in Go. I will publish how to use
what we learn’t to build our own working web pages using nested templates. You
can drop me hello with questions and feedback on
twitter. Please consider leaving a feedback and share with anyone who may benefit from it. Thanks!
Top comments (0)