Switch or if/elseif/else inside golang HTML templates
Asked Answered
S

3

72

I have this struct :

const (
    paragraph_hypothesis = 1<<iota
    paragraph_attachment = 1<<iota
    paragraph_menu       = 1<<iota
)

type Paragraph struct {
    Type int // paragraph_hypothesis or paragraph_attachment or paragraph_menu
}

I want to display my paragraphs in a Type dependent way.

The only solution I found was based on dedicated functions like isAttachment testing the Type in Go and nested {{if}} :

{{range .Paragraphs}}
    {{if .IsAttachment}}
        -- attachement presentation code  --
    {{else}}{{if .IsMenu}}
        -- menu --
    {{else}}
        -- default code --
    {{end}}{{end}}
{{end}}

In fact I have more types, which makes it even weirder, cluttering both the Go code with IsSomething functions and the template with those {{end}}.

What's the clean solution ? Is there some switch or if/elseif/else solution in go templates ? Or a completely different way to handle these cases ?

Supertax answered 7/6, 2013 at 13:34 Comment(1)
Are you referring to html templates? Your example of the Paragraph type is not too clear. You can generally create custom template behaviour by adding functions to the FuncMap (golang.org/pkg/text/template/#Template.Funcs). Sorry, I'm not sure exactly what you're trying to do.Wycoff
G
54

Templates are logic-less. They're not supposed to have this kind of logic. The maximum logic you can have is a bunch of if.

In such a case, you're supposed to do it like this:

{{if .IsAttachment}}
    -- attachment presentation code --
{{end}}

{{if .IsMenu}}
    -- menu --
{{end}}

{{if .IsDefault}}
    -- default code --
{{end}}
Guzzle answered 7/6, 2013 at 13:55 Comment(9)
And is there no other way to test the Type than to add isSomeValue functions ?Domella
@dystroy well, the point is to move the logic out of the template. So yes, you're supposed to have a helper function out of it. It's the same kind of thing you have to do in other technologies whenever you use logicless templates.Guzzle
In fact, the problem starts with the definition of IsDefault. In the end you start by duplicating all presentation logic which must be present both in the templates and in the Go code and you end with a big verbose code in Go to support the template and the template brings no added value.Domella
This is too rigid no?... what is a condition need to evaluate against a string? @FlorianMargaineAnode
replace: "text/template is not supposed to have this kind of logic." with: "text/template does not support more sophisticated logic."Beginning
There is else and else ifEsotropia
text/template package provides helper functions for sophisticated logic. When {{else if .IsMenu}} would not suffice, you could do something like {{if and .IsMenu (not .IsAttachment)}}.Seleucia
What does "Templates are logic-less" even mean. Of course you can add logic in the template.Reveille
Hey there, so no {{else}} like logic is provided? Because {{if .MyStruct.Name.IsNil}}foo{{end}}{{if .MyStruct.Name.IsNotNil}}bar{{end}} is quite redundant..Menorah
C
68

Yes, you can use {{else if .IsMenu}}

Cyte answered 12/7, 2018 at 14:36 Comment(1)
This works. Maybe this is a new feature for templates. ThanksEntwistle
G
54

Templates are logic-less. They're not supposed to have this kind of logic. The maximum logic you can have is a bunch of if.

In such a case, you're supposed to do it like this:

{{if .IsAttachment}}
    -- attachment presentation code --
{{end}}

{{if .IsMenu}}
    -- menu --
{{end}}

{{if .IsDefault}}
    -- default code --
{{end}}
Guzzle answered 7/6, 2013 at 13:55 Comment(9)
And is there no other way to test the Type than to add isSomeValue functions ?Domella
@dystroy well, the point is to move the logic out of the template. So yes, you're supposed to have a helper function out of it. It's the same kind of thing you have to do in other technologies whenever you use logicless templates.Guzzle
In fact, the problem starts with the definition of IsDefault. In the end you start by duplicating all presentation logic which must be present both in the templates and in the Go code and you end with a big verbose code in Go to support the template and the template brings no added value.Domella
This is too rigid no?... what is a condition need to evaluate against a string? @FlorianMargaineAnode
replace: "text/template is not supposed to have this kind of logic." with: "text/template does not support more sophisticated logic."Beginning
There is else and else ifEsotropia
text/template package provides helper functions for sophisticated logic. When {{else if .IsMenu}} would not suffice, you could do something like {{if and .IsMenu (not .IsAttachment)}}.Seleucia
What does "Templates are logic-less" even mean. Of course you can add logic in the template.Reveille
Hey there, so no {{else}} like logic is provided? Because {{if .MyStruct.Name.IsNil}}foo{{end}}{{if .MyStruct.Name.IsNotNil}}bar{{end}} is quite redundant..Menorah
W
11

You can achieve switch functionality by adding custom functions to the template.FuncMap.

In the example below I've defined a function, printPara (paratype int) string which takes one of your defined paragraph types and changes it's output accordingly.

Please note that, in the actual template, the .Paratype is piped into the printpara function. This is how to pass parameters in templates. Please note that there are restrictions on the number and form of the output parameters for functions added to FuncMaps. This page has some good info, as well as the first link.

package main

import (
    "fmt"
    "os"
    "html/template"
)

func main() {

    const (
        paragraph_hypothesis = 1 << iota
        paragraph_attachment = 1 << iota
        paragraph_menu       = 1 << iota
    )

    const text = "{{.Paratype | printpara}}\n" // A simple test template

    type Paragraph struct {
        Paratype int
    }

    var paralist = []*Paragraph{
        &Paragraph{paragraph_hypothesis},
        &Paragraph{paragraph_attachment},
        &Paragraph{paragraph_menu},
    }

    t := template.New("testparagraphs")

    printPara := func(paratype int) string {
        text := ""
        switch paratype {
        case paragraph_hypothesis:
            text = "This is a hypothesis\n"
        case paragraph_attachment:
            text = "This is an attachment\n"
        case paragraph_menu:
            text = "Menu\n1:\n2:\n3:\n\nPick any option:\n"
        }
        return text
    }

    template.Must(t.Funcs(template.FuncMap{"printpara": printPara}).Parse(text))

    for _, p := range paralist {
        err := t.Execute(os.Stdout, p)
        if err != nil {
            fmt.Println("executing template:", err)
        }
    }
}

Produces:

This is a hypothesis

This is an attachment

Menu
1:
2:
3:

Pick any option:

Playground link

Hope that helps, I'm pretty sure the code could be cleaned up a bit, but I've tried to stay close to the example code you provided.

Wycoff answered 8/6, 2013 at 2:43 Comment(10)
This is interesting but in your solution the rendering of the paragraph in HTML is made in Go (in printPara). Then there doesn't seem to be any point in using a template.Domella
The printing is only as a demonstration. You can feasibly insert any code in the case statements, not just custom output. You could process variables, change struct values etc. This is really just to demonstrate how you could generate switch like functionality in templates.Wycoff
@Wycoff but you're not generating any switch like functionality in templates there. I'd like to have a better example too I guess.Guzzle
Sorry, I'm still not sure exactly what you're trying to achieve, golang.org/pkg/text/template has all of the abilities and features of go native templates. Other than the inbuilt features listed there, adding functions to the FuncMap is the only way I know to modify the output. @FlorianMargaine , The function accepts a single integer (the template could be something like {{4 | printpara}} for a menu) and, internally, changes it's behaviour by doing a switch statement on that integer. That's about as close to the native golang switch as I could emulate in a template.Wycoff
@Wycoff I think both dystroy and I were looking for something like this: pastebin.com/QSz0vAxkGuzzle
That's exactly the kind of things I was looking for.Domella
@FlorianMargaine and @dystroy , Would the data driving the template be coming from structs? Possibly something like type Paragraph struct{Paratype int, Text string}?Wycoff
@Wycoff yes, I had removed the other fields of the struct to make it simpler (and in my obviously wrong opinion of that time, to make it clearer).Domella
@dystroy , ok, I think I can refactor the function to do close to what you want. It's an interesting challenge, I've even been reading the source code to try to determine how the if statements work in templates. if isn't one of the built in template functions, despite not, and and or being so. Hopefully I'll have something soon :-)Wycoff
Ok, after much reading, it seems that I'd need to modify the standard library (in particular golang.org/pkg/text/template/parse/#NodeType , just for starters) to acheive a true switch statement. The next best thing would be a series of {{if switch .Type | case 1}}...{{end}} (defining custom Switch() and Case() functions in the FuncMap), at which point you may as well just go with standard if statements. Ah well, I've learnt a lot about lexers and parsers in the last hour or so! The relevant source, if you're interested, is at golang.org/src/pkg/text/template/parse .Wycoff

© 2022 - 2024 — McMap. All rights reserved.