The next code works perfect for output one slice inside the HomeTemplate.
main.go
type Item struct {
Id int
Name string
Type string
}
var tmpl = template.Must(template.ParseGlob("tmpl/*"))
func Index(w http.ResponseWriter, r *http.Request) {
db := database.DbConn()
selDB, err := product.ByID()
if err != nil {
panic(err.Error())
}
i := Item{}
resItems := []Item{}
for selDB.Next() {
var id int
var product_name, product_type string
err = selDB.Scan(&id, &product_name, &product_type)
if err != nil {
panic(err.Error())
}
i.Id = id
i.Name = product_name
i.Type = product_type
resItems = append(resItems, i)
}
tmpl.ExecuteTemplate(w, "HomeTemplate", resItems)
// Close database connection
defer db.Close()
}
In the template forks fine the next code:
{{ range . }}
{{ .Name }}<br />
{{ end }}
Why something like this does not work?
{{ range .resItems }}
{{ .Name }}<br />
{{ end }}
What if I want output two or more slices, what I need to do or change?
Thank you
first question, why rang .resItems doesn't work.
In template, . means current item. like this in java.
if . in some direction like range, it means the item from range operation.
if not, it means the item you passed form ExecuteTemplate() method. like in ExecuteTemplate(w, "HomeTemplate", resItems), . means resItems. so you can not use .resItems because it means resItems have a value called resItems.
second, if you have more slices to pass to template, you can add all of them to a map, like this:
t := template.New("test")
t, _ = t.Parse(`
test range
{{range .first}} {{.}} {{end}}
{{range .second}} {{.}} {{end}}
`)
var res = make(map[string]interface{})
aa := []string{"first", "second"}
bb := []string{"123", "456"}
res["first"] = aa
res["second"] = bb
t.Execute(os.Stdout, res)
// output
test range
first second
123 456
I have two array aa and bb, and add them to a map then pass it to the template. In template, . means the map, and .first got the array aa. and so as bb.
Hope this can help you...
Related
I'm hoping to create a common/generic Go html template which will produce a standard html table based on its inputs. I had hoped to look up the struct members by name, but I haven't been able to make this work.
I've had a look around and I can't find a solution, so I'm either missing something obvious, or the approach is wrong. In this respect I would accept a solution which showed an alternate or better approach which avoids trying this lookup.
Example Template:
{{ $fields := .FieldMap }}
<table>
<thead>
<tr>
{{ range $key, $value := $fields }}
<th>{{ $key }}</th>
{{ end }}
</tr>
</thead>
<tbody>
{{ range $i, $v := .Model }}
<tr>
{{ $rowData := . }}
{{/* FAILS: error calling index: can't index item of type main.Person <td> {{ index . "FirstName"}}</td>*/}}
{{ range $key, $value := $fields }}
{{/* FAILS: error calling index: can't index item of type main.Person <td> {{ index $rowData $value }}</td>*/}}
{{/* FAILS: bad character U+0024 '$' <td> {{ $rowData.$value }}</td>*/}}
{{ end }}
</tr>
{{ end }}
</tbody>
</table>
Example Go:
package main
import (
"html/template"
"os"
)
type Person struct {
FirstName string
LastName string
}
type Animal struct {
Species string
}
type TemplateData struct {
Model interface{}
FieldMap map[string]string
}
func main() {
t, err := template.ParseFiles("table.gohtml")
if err != nil {
panic(err)
}
// Here we use Person, but I may want to pass other types of struct to the template, for example "Animal"
dataPerson := TemplateData{
Model: []Person{
{
FirstName: "Test",
LastName: "Template",
},
},
FieldMap: map[string]string{"First": "FirstName", "Last": "LastName"},
}
err = t.Execute(os.Stdout, dataPerson)
if err != nil {
panic(err)
}
}
I hope its clear what I am trying to do - have a single template which I can reuse across various types of struct.
Create a template function that returns the struct field names and values as a map:
// fields returns map of field names and values for struct s.
func fields(s interface{}) (map[string]interface{}, error) {
v := reflect.Indirect(reflect.ValueOf(s))
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("%T is not a struct", s)
}
m := make(map[string]interface{})
t := v.Type()
for i := 0; i < t.NumField(); i++ {
sv := t.Field(i)
m[sv.Name] = v.Field(i).Interface()
}
return m, nil
}
Specify the function when parsing the file:
t, err := template.New("").Funcs(template.FuncMap{"fields": fields}).ParseFiles("table.gohtml")
if err != nil {
panic(err)
}
Use it like this:
{{range $i, $v := .Model}}
<tr>
{{$m := fields $v}}
{{range $key, $value := $fields}}
<td>{{index $m $value}}</td>
{{end}}
</tr>
{{end}}
Run it on the playground.
Another approach is to write a function that looks up a field by name:
func field(s interface{}, k string) (interface{}, error) {
v := reflect.Indirect(reflect.ValueOf(s))
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("%T is not a struct", s)
}
v = v.FieldByName(k)
if !v.IsValid() {
return nil, fmt.Errorf("no field in %T with name %s", s, k)
}
return v.Interface(), nil
}
Parse with the function:
t, err := template.New("").Funcs(template.FuncMap{"field": field}).ParseFiles("table.gohtml")
Use it like this:
{{range $i, $v := .Model}}
<tr>
{{range $key, $value := $fields}}
<td>{{field $v $value}}</td>
{{end}}
</tr>
{{end}}
Run it on the playground.
I am using go-lang templates to output some HTML. There is a block of HTML that I will like to repeat many times. So I am using a variable to store this block of HTML. Here is a dummy version of my code:
package main
import (
"html/template"
"log"
"os"
)
var tmplString = ` // content of index.html
{{define "index"}}
{{ $DUMMY := "{{.var1}} is equal to {{.var2}}" }}
{{ $DUMMY }}
{{ $DUMMY }}
{{end}}
`
func main() {
tmpl, err := template.New("test").Parse(tmplString)
if err != nil {
log.Fatal(err)
}
varmap := map[string]interface{}{
"var1": "value",
"var2": 100,
}
tmpl.ExecuteTemplate(os.Stdout, "index", varmap)
}
The problem is that this piece of code will
{{.var1}} is equal to {{.var2}}
{{.var1}} is equal to {{.var2}}
But I need it to produce
value is equal to 100
value is equal to 100
How can I define a variable so that the string gets built from my structure?
You can concatenate strings in templates with print:
{{ $DUMMY := (print .var1 " is equal to " .var2 )}}
I am trying to simplify the template which I use to make it use a flatter data structure:
from
data := []App{App{"test data", []string{"app1", "app2", "app3"}}}
To:
data := App{App{"test data", []string{"app1", "app2", "app3"}}}
i.e. Remove the array of App, but when I try it I get an error.
Here is the working version: https://play.golang.org/p/2THGtDvlu01
I tried to change the template to
{{ range . -}}
{range $i,$a := .Command}{{if gt $i 0 }} && {{end}}{{.}}{{end}}
{{end}}
But I got an error of type mismatched, any idea how to fix it?
package main
import (
"log"
"os"
"text/template"
)
func main() {
// Define a template.
const tmpl = `
echo &1
{{range $i,$a := .Command}}{{if gt $i 0 }} && {{end}}{{.}}{{end}}
echo 2
`
// Prepare some data
type App struct {
Data string
Command []string
}
data := App{"test data", []string{"app1", "app2", "app3"}}
// Create a new template and parse into it.
t := template.Must(template.New("tmpl").Parse(tmpl))
// Execute the template with data
err := t.Execute(os.Stdout, data)
if err != nil {
log.Println("executing template:", err)
}
}
Playground example
Gives the output
echo &1
app1 && app2 && app3
echo 2
Program exited.
If you remove the []App from your code, you also need to remove the range used in the template.
In my app, I'm trying to split a string into an array based on a regex pattern. I'd like to be able to load my volt templates and run them through our custom rendering engine - just to learn a bit more on how rendering engines work.
I wrote the regex below to do just that:
"(?s)(\\{\\{.*?\\}\\}|\\{%.*?%\\}|\\{#.*?#\\})"
And this is an example of such a template:
# {{ title }}
{{created_at}} {{created_location}}
============
Paragraphs are separated by a blank line.
2nd paragraph. *Italic*, **bold**, and `monospace`.
Itemized lists look like:
{% for (item in items) %}
* {{ item }}
{% endfor %}
Now, ideally, I'd like this to be converted to an array looking like this:
[
"# ",
"{{ title }}",
"\n",
"{{created_at}}",
" ",
"{{created_location}}",
"\n============\nParagraphs are separated by a blank line\n2nd paragraph. *Italic*, **bold**, and `monospace`.\n\nItemized lists look like:"
"{% for (item in items) %}",
"\n* {{ item }}\n",
"{% endfor %}"
]
However, when I run the regex above, I get:
[
"Paragraphs are separated by a blank line.\n2nd paragraph. *Italic*, **bold**, and `monospace`.\n\nItemized lists look like:",
"{% for (item in items) %}\n* {{ item }}",
"{% endfor %}\n"
]
As you can see the title part completely disappears. Furthermore, there seem to be some issues with the newline characters. Any ideas how I could solve this?
The problem wasn't in the regex, but in the code that I was using to split on the regex. I modified the code below to also return the regex itself.
extension NSRegularExpression {
func split(_ str: String) -> [String] {
let range = NSRange(location: 0, length: str.characters.count)
//get locations of matches
var matchingRanges: [NSRange] = []
let matches: [NSTextCheckingResult] = self.matches(in: str, options: [], range: range)
for match: NSTextCheckingResult in matches {
matchingRanges.append(match.range)
}
//invert ranges - get ranges of non-matched pieces
var pieceRanges: [NSRange] = []
//add first range
pieceRanges.append(NSRange(location: 0, length: (matchingRanges.count == 0 ? str.characters.count : matchingRanges[0].location)))
var endLoc: Int = 0
var startLoc: Int = 0
//add between splits ranges and last range
for i in 0..<matchingRanges.count {
let isLast = i + 1 == matchingRanges.count
let location = matchingRanges[i].location
let length = matchingRanges[i].length
startLoc = location + length
endLoc = isLast ? str.characters.count : matchingRanges[i + 1].location
pieceRanges.append(NSRange(location: startLoc, length: endLoc - startLoc))
}
var pieces: [String] = []
var previous: NSRange = NSRange(location: 0, length: 0)
for range: NSRange in pieceRanges {
let item = (str as NSString).substring(with: NSRange(location:previous.location+previous.length, length:range.location-(previous.location+previous.length)))
pieces.append(item)
let piece = (str as NSString).substring(with: range)
pieces.append(piece)
previous = range
}
return pieces
}
}
I'm trying to print the values of a map, whose keys have a dot (.) on it.
Example map:
type TemplateData struct {
Data map[string] int
}
tpldata := TemplateData{map[string]int {"core.value": 1}}
I've tried:
{{ $key := "core.value" }}
{{ .Data.key }}
but got:
2014/06/17 16:46:17 http: panic serving [::1]:41395: template: template.html:13: bad character U+0024 '$'
and
{{ .Data."core.value" }}
but got:
2014/06/17 16:45:07 http: panic serving [::1]:41393: template: template.html:12: bad character U+0022 '"'
Note that I'm able to successfully print the value of keys without dots.
As #martin-ghallager said, one needs to use an external function to access those elements.
Helpfully, the standard library already provides the index function (which does exactly what Martin's dotNotation function does).
To use it just write:
{{ index .Data "core.value" }}
The index function will return a default value in case the key is not present. This works if your dictionary has homogeneous data, however it will return the wrong value when it is heterogeneous. In such a case you can explicitly set the default with:
{{ 0 | or (index .Data "core.value") }}
As fabrizioM has stated, it's against the specs of the package, however there's nothing stopping you creating your own accessor to use dot notation using a function map:
package main
import (
"fmt"
"html/template"
"os"
)
type TemplateData struct {
Data map[string]int
}
var funcMap = template.FuncMap{
"dotNotation": dotNotation,
}
func main() {
data := TemplateData{map[string]int{"core.value": 1, "test": 100}}
t, err := template.New("foo").Funcs(funcMap).Parse(`{{dotNotation .Data "core.value"}}`)
if err != nil {
fmt.Println(err)
}
err = t.Execute(os.Stdout, data)
if err != nil {
fmt.Println(err)
}
}
func dotNotation(m map[string]int, key string) int {
// Obviously you'll need to validate existence / nil map
return m[key]
}
http://play.golang.org/p/-rlKFx3Ayt
No you can't.
According to the specs in http://golang.org/pkg/text/template/#Arguments, the key must be alphanumeric
- The name of a key of the data, which must be a map, preceded
by a period, such as
.Key
The result is the map element value indexed by the key.
Key invocations may be chained and combined with fields to any
depth:
.Field1.Key1.Field2.Key2
Although the key must be an alphanumeric identifier, unlike with
field names they do not need to start with an upper case letter.
Keys can also be evaluated on variables, including chaining:
$x.key1.key2
You can still print it by iterating over the Map
package main
import (
"fmt"
"html/template"
"os"
)
type TemplateData struct {
Data map[string]int
}
func main() {
data := TemplateData{map[string]int{"core.value": 1, "test": 100}}
t, err := template.New("foo").Parse(`{{range $key, $value := .Data}}
{{$key}}: {{$value}}
{{end}}`)
if err != nil {
fmt.Println(err)
}
err = t.Execute(os.Stdout, data)
if err != nil {
fmt.Println(err)
}
}
http://play.golang.org/p/6xB_7WQ-59
I had a similar issue where my key names in secret vault had - and . in it for example
test-key or test.key
If solved it like this
{{ with secret "secret/path/test"}}
{{ range $k, $v := .Data }}
{{ $k }}:{{ $v }}
{{ end }}
{{ end }}
Hope this will help someone...
Similarly for vault templates when exporting secrets as envvars this worked for me:
{{- with secret "secret/path/mysecret" -}}
export MYVAR="{{ index .Data.data "mykey"}}"
{{- end }}