I have a struct, containing two slices with same size, which is to be passed to the template.
type V struct {
Process []string
ProcessType []string
}
I need to iterate the slices in same range. In golang(without a framework) we could do this as follows
//I know all of you know this. Added for better understanding of my problem.
{{range $i, $e := .Process}}
{{.}}
{{index $.ProcessType $i}}
{{end}}
We could apply range on a slice in beego like this
{{range $key, $val := .vm.Process}} //this.Data["vm"] = V (Struct V is passed as vm to the template)
{{$val}}
{{end}}
How to include slice ProcessType in this range too?
Related
For example, I want to range Fields except the last one element.
Maybe like:
{{range $Field := $.Fields[:len $Field - 1]}}
Do I have some approaches?
Thx!
The builtin template slice function almost does what you need. The missing piece is computing the last index of the new slice. To do that, add an addition function to the template:
func add(a, b int) int {
return a + b
}
Add the function to template before parsing:
t, err := template.New(name).Funcs(template.FuncMap{"add": add}).Parse(text)
Use the function like this:
{{range slice $ 0 (add (len $) -1)}}
{{.}}
{{end}}
playground example.
How can you calculate something inside a html template of go?
For example:
{{ $length := len . }}
<p>The last index of this map is: {{ $length -1 }} </p>
Were the . is a map.
The code {{ $length -1 }} is not working, is there a way to achieve this?
You can't. Templates are not a scripting language. By design philosophy, complex logic should be outside of templates.
Either pass the calculated result as a parameter (preferred / easiest), or register custom functions which you can call during template execution, pass values to them and which may perform calculations and return any values (e.g. return param - 1).
For examples of registering and using custom functions, see:
Golang templates (and passing funcs to template)
How do I access object field by variable in template?
Iterate Go map get index.
The other answers are correct, you can't do it in the template themselves. However, here's a working example of how to use Funcs:
package main
import (
"fmt"
"html/template"
"os"
)
type MyMap map[string]string
func LastMapIndex(args ...interface{}) string {
if m, ok := args[0].(MyMap); ok && len(args) == 1 {
return fmt.Sprintf("%d", len(m) - 1)
}
return ""
}
func main() {
myMap := MyMap{}
myMap["foo"] = "bar"
t := template.New("template test")
t = t.Funcs(template.FuncMap{"LastMapIndex": LastMapIndex})
t = template.Must(t.Parse("Last map index: {{.|LastMapIndex}}\n"))
t.Execute(os.Stdout, myMap)
}
Playground: https://play.golang.org/p/YNchaHc5Spz
You can use a FuncMap like this. Once you define a function within a funcmap, you can use it in the HTML. In your case you could define a MapLength function or something similar that calculates the length of a given map and returns it for you. You can then call it in the template a bit like this:
<p>The last index of this map is: {{ .MapLength . }} </p>
I'm writing a simple Go program to display an HTML table of deployed service versions per environment. My program contains the following structs:
type versionKey struct {
Environment string
Service string
}
type templateData struct {
Environments []string
Services []string
Versions map[versionKey]string
}
As you can see, the Versions map uses a versionKey as a key for a string value e.g. "1.0.0".
I'm passing the templateData struct to an HTML template and ranging over its Environments and Services slices to build the HTML table. The problem is that I need to construct a versionKey for any given intersection of environment and service so I can use it to look up the version from the Versions map and output that value in the table cell.
Within the template I have $environment and $service variables available from the ranges, but I can't work out the Go template syntax to create the versionKey struct.
Here's the template code with the markup omitted:
{{$environments := .Environments}}
{{$services := .Services}}
{{$versions := .Versions}}
{{range $service := $services}}
...
{{range $environment := $environments}}
...
{{index $versions ...? }} // How to create versionKey struct map key here?
...
{{end}}
...
{{end}}
Using only template code you can't. You need some kind of support from the executing Go code to do that. By design philosophy, templates should not contain complex logic. You may argue whether this is complex, but the template syntax has no support for this.
Simplest solution would be to add a Version() method to the templateData struct, which would simply return the version for a given environment and service:
func (t *templateData) Version(environment, service string) string {
return t.Versions[versionKey{
Environment: environment,
Service: service,
}]
}
Using this from the template:
{{range $service := $services -}}
{{range $environment := $environments}}
{{$environment}} - {{$service}} version: {{$.Version $environment $service}}
{{end}}
{{end}}
Testing it:
t := template.Must(template.New("").Parse(templ))
td := &templateData{
Environments: []string{"EnvA", "EnvB"},
Services: []string{"ServA", "ServB"},
Versions: map[versionKey]string{
{"EnvA", "ServA"}: "1.0.0",
{"EnvA", "ServB"}: "1.0.1",
{"EnvB", "ServA"}: "1.0.2",
},
}
if err := t.Execute(os.Stdout, td); err != nil {
panic(err)
}
Output (try it on the Go Playground):
EnvA - ServA version: 1.0.0
EnvB - ServA version: 1.0.2
EnvA - ServB version: 1.0.1
EnvB - ServB version:
Alternatives
Instead of the templateData.Version() method you could just as easily register a function which could create and return a value of type versionKey from a given environment and service. See Template.Funcs() for details. This would be more complicated though, but more flexible as this could be reused elsewhere. See an example of this here: Golang templates (and passing funcs to template). A slight variation of this would be to pass a function value as any other template data instead of registering it as a named function, which can be called.
Another alternative would be to "transform" your Versions field into a map of maps, e.g.:
Versions map[string]map[string]string
Which first could be indexed by environment, then by service, which in the template you can achieve by 2 {{index}} actions. You would have to check if the first indexing yields any results though.
i give a dataset from mysql-database to go-template. the result have multiple rows but all values is one string!?
type Tasks struct {
tid int
pid int
uid int
del int
finisch int
open int
inprocess int
abnahme int
fertig int
finischdatum string
erstellt string
start string
ende string
name string
beschreibung string
}
type Daten struct {
Tabledata []*Tasks
}
d := Daten{}
rows, err := db.Query("SELECT * FROM tasks WHERE pid=? AND del=0", pid)
checkError(err)
defer rows.Close()
rs := make([]*Tasks, 0)
for rows.Next() {
rst := new(Tasks)
err := rows.Scan(&rst.tid, &rst.pid, &rst.uid, &rst.del, &rst.finisch, &rst.open, &rst.inprocess, &rst.abnahme, &rst.fertig, &rst.finischdatum, &rst.erstellt, &rst.start, &rst.ende, &rst.name, &rst.beschreibung)
if err != nil {
log.Println(err)
}
rs = append(rs, rst)
}
d.Tabledata = rs
template:
{{ range $key, $values := .Tabledata }}
<li><strong>{{ $key }}</strong>: </li>
{{range $values}}
{{.}}
{{end}}
{{ end }}
When I look in the first range and give me the $values as one string and the second range is death.
What's my Problem?
In your code, since you didn't tell, I guess the variable 'd' is passed to the template. 'd' is of type Daten and thus contain a field named TableData. This field is a slice of *Tasks. This is why in your template you can loop on it using range.
The range keyword returns two values, the first one, '$key' in your code, is the index of the slice, and the second one, '$values' in your code is the value pointed by TableData[$key]. Because TableData is a slice of type *Tasks, that means $values is of type *Tasks. So not an array nor a slice.
That means, you can't loop on $values because it's not a slice. But, you can access all the exported fields of Tasks. So depending on what you want to display in your template, you can do the following:
// Inside the firts range
<li><strong>{{ $key }}</strong>: </li>
name: $values.Name
open: $values.Open
Note the uppercase on the field's name, in order to access a field it shall be exported, and thus starts with an upper case in the type declaration.
I have a map of values that looks like this:
vals := map[string]interface{}{"foo": 1, "bar": 2, "baz": 7}
data := map[string]interface{}{"bat": "obj", "values": vals}
What should my template look like to generate the following string (note the correct comma usage)?
SET obj.foo=1, obj.bar=2, obj.baz=7
I started with this as my template:
SET {{range $i, $v := .values}} {{.bat}}.{{$i}}={{$v}},{{end}}
But that just prints out
SET
And even if that did work, the commas would be incorrect. I then tried to use a custom function to format the map, but I couldn't get the template to ever call my function. None of the following seemed to work:
SET {{.MyFunction .values}}
SET {{call .MyFunction .values}}
SET {{call MyFunction .values}}
when MyFunction was defined as:
func MyFunction(data map[string]interface{}) string {
fmt.PrintLn('i was called!')
return "foo"
}
And I'm executing the templates using a helper function that looks like this:
func useTemplate(name string, data interface{}) string {
out := new(bytes.Buffer)
templates[name].Execute(out, data)
return string(out.Bytes())
}
Thanks!
This will get you pretty close:
SET {{range $key, $value := $.values}}{{$.bat}}.{{$key}}={{$value}} {{end}}
rendering as:
SET obj.bar=2 obj.baz=7 obj.foo=1
Unfortunately, I don't think there's any simple way to have the commas added in between the values due to how the range action iterates on maps (there's no numeric index). That said, the template packages were meant to be easily extensible so you can have less logic in your templates and more logic in Go itself, so it's easy enough to code a helper function in Go and make it available to your templates.
If you're happy to go that extra mile, then the template becomes much simpler, and also more efficient. The function can look like this:
func commaJoin(prefix string, m map[string]interface{}) string {
var buf bytes.Buffer
first := true
for k, v := range m {
if !first {
buf.WriteString(", ")
}
first = false
buf.WriteString(prefix)
buf.WriteByte('.')
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(fmt.Sprint(v))
}
return buf.String()
}
and your template would look like:
SET {{$.values | commaJoin $.bat}}
Here is a working example with this logic:
http://play.golang.org/p/5lFUpFCzZm