Creating a Template from an Object

An object or range of objects can be used with a template to create a textual output.

Pretty much its as simple as a template recognizing the object being passed in and drawing attributes from the object to create wanted text. It is very useful for reports or configurations.

Let’s say we have the following structs:

type Conf struct {

TimeGenerated string

[]Zoo

} type Zoo struct {

Name string

Climate string

Animals []Animal

} type Animal struct {

Name string

Climates []string

} Alligator = Animal{Name: "Alligator", Climates: []string{"Tropical", "SubTropical"}} Puffin = Animal{Name: "Puffin", Climates: []string{"Arctic", "SubArctic"}} miamiZoo = Zoo{Name: "MiamiZoo", Climate: "SubTropical", Animals: []Animal{Alligator, Puffin}} conf := Conf{TimeGenerated: time.Now().UTC().String(), Zoos: []Zoo{miamiZoo}}

and the following template:

{{ $cfg := . }}

{{ $zoos := .Zoos }} Official Zoo's Report

---------------------

Report Generation Time: {{ $cfg.TimeGenerated }}

ZooSoftware: ZooKeeper 0.1.1 {{ range $index, $zoo := $zoos }}

{{ $zoo.Name }}

---------------

{{ range $animalIndex, $animal := $zoo.Animals }}

Animal {{ $animalIndex }}: {{ $animal.Name }}{{end}}

{{ end }}

we can loop through each of the Animals and generate a Report containing those animals. When executing the template, we will get the following output:

Official Zoo's Report

---------------------

Report Generation Time: 2018-12-12 23:16:21.340949125 +0000 UTC

ZooSoftware: ZooKeeper 0.1.1 MiamiZoo

---------------

Animal 0: Alligator

Animal 1: Puffin

For a detailed example you can run, checkout example 1.

Creating a Template Function

Template functions are very useful for processing Objects passed within the template. With these functions we can run some logic before passing objects to the template.

Photo by Matthew Cabret on Unsplash

Suppose the zoo only wanted to allow animals which are suitable for the climate in the area of the zoo’s location. We can create a function to sort which animals belong in the zoo, so we can generate a template of the acceptable animals.

The template function would look as follows:

// Here we pass in an Animal type and a Climate(string)

// If the animal's climate is the same climate as the one that

// we passed, then the animal is acceptable func getAcceptableAnimals(a interface{}, b string) []Animal { animals, ok := a.([]Animal)

if !ok {

err := errors.New(fmt.Sprintf("expected an '[]*Animal' type but %T was returned", animals))

panic(err)

} acceptable := []Animal{}

for _, animal := range animals {

for _, climate := range animal.Climates {

if b == climate {

acceptable = append(acceptable, animal)

break

}

}

}

return acceptable

}

and it can be used in a template as follows:

{{ $cfg := . }}

{{ $zoos := .Zoos }} Official Zoo's Report

---------------------

Report Generation Time: {{ $cfg.TimeGenerated }}

ZooSoftware: ZooKeeper 0.1.2 {{ range $index, $zoo := $zoos }}

{{ $zoo.Name }}

--------------- {{ $acceptableAnimals := getAcceptableAnimals $zoo.Animals $zoo.Climate }}

{{ range $animalIndex, $animal := $acceptableAnimals }}

Animal {{ $animalIndex }}: {{ $animal.Name }}{{end}}

{{ end }}

We can see that before ranging through the animals to print, we run the getAcceptableAnimals function, passing the all the animals within a zoo, and the zoo’s climate. The we pass the result of that function(an array of animals) to the loop instead of zoo.Animals.

this will give us which animals are acceptable in the zoo:

Official Zoo's Report

---------------------

Report Generation Time: 2018-12-12 23:17:35.330908345 +0000 UTC

ZooSoftware: ZooKeeper 0.1.1 MiamiZoo

---------------

Animal 0: Alligator

For a detailed example you can run, checkout example 2.

Creating a Sub-Template

A sub-template would be a template that is loaded within another template.

This is useful because, we can move all common code to another template and reduce duplication of code if the sub-template is used several times in the main template.

In the following template, you can see how the the “ANIMALS” sub-template is defined, and used twice in the main template with the template syntax. This allows use to avoid duplicating the traversal of animals.

{{ $cfg := . }}

{{ $zoos := .Zoos }}

Official Zoo's Report

---------------------

Report Generation Time: {{ $cfg.TimeGenerated }}

ZooSoftware: ZooKeeper 0.1.3

{{ range $index, $zoo := $zoos }} {{ $zoo.Name }}

%%%%%%%%%%%%%%%%

{{ $acceptableAnimals := getAcceptableAnimals $zoo.Animals $zoo.Climate }}

{{ $unacceptableAnimals := getUnacceptableAnimals $zoo.Animals $zoo.Climate }} Acceptable Animals

------------------

{{ template "ANIMALS" $acceptableAnimals }}

UnAcceptable Animals

--------------------

{{ template "ANIMALS" $unacceptableAnimals }} {{ end }} {{ define "ANIMALS" }}

{{ $animals := . }}

{{ range $animalIndex, $animal := $animals }}

Animal {{ $animalIndex }}: {{ $animal.Name }}{{end}}

{{ end }}

Also, note that Sub-Templates can be in other .tmpl files. You will just need to pass those files into the ParseFiles() function seen in the example.

For a detailed example you can run, checkout example 3. Note, I have added different template functions to process which animals should and should not be in the zoo.