Appel d'un modèle avec plusieurs paramètres de pipeline

dans un modèle Go, parfois la façon de transmettre les bonnes données au bon modèle me semble embarrassante. Appeler un modèle avec un paramètre pipeline ressemble à appeler une fonction avec un seul paramètre.

disons que j'ai un site pour les Gophers à propos des Gophers. Il a un modèle principal de page d'accueil, et un modèle d'utilité pour imprimer une liste de gaufres.

http://play.golang.org/p/Jivy_WPh16

Sortie :

*The great GopherBook*    (logged in as Dewey)

    [Most popular]  
        >> Huey
        >> Dewey
        >> Louie

    [Most active]   
        >> Huey
        >> Louie

    [Most recent]   
        >> Louie

maintenant je veux pour ajouter un peu de contexte dans le sous-répertoire : formater le nom "Dewey" différemment à l'intérieur de la liste parce que c'est le nom de l'utilisateur actuellement connecté. Mais je ne peux pas passer le nom directement parce qu'il y a un seul possibilité d'un pipeline d'arguments "dot"! Que puis-je faire?

  • évidemment je peux copier-coller le code de sous-template dans le modèle principal (Je ne veux pas parce qu'il supprime tout l'intérêt d'avoir un sous-template).
  • Ou je peux jongler avec certains genre de variables globales avec accesseurs (Je ne veux pas non plus).
  • ou je peux créer un nouveau type de structure spécifique pour chaque liste de paramètres de template (pas grand).
24
demandé sur ROMANIA_engineer 2013-08-16 18:51:56
la source

9 ответов

vous pouvez enregistrer une fonction" dict " dans vos templates que vous pouvez utiliser pour passer plusieurs valeurs à un appel de template. L'appel lui-même pourrait alors ressembler à ceci:

{{template "userlist" dict "Users" .MostPopular "Current" .CurrentUser}}

le code pour le petit helper "dict", y compris l'enregistrer comme un modèle func est ici:

var tmpl = template.Must(template.New("").Funcs(template.FuncMap{
    "dict": func(values ...interface{}) (map[string]interface{}, error) {
        if len(values)%2 != 0 {
            return nil, errors.New("invalid dict call")
        }
        dict := make(map[string]interface{}, len(values)/2)
        for i := 0; i < len(values); i+=2 {
            key, ok := values[i].(string)
            if !ok {
                return nil, errors.New("dict keys must be strings")
            }
            dict[key] = values[i+1]
        }
        return dict, nil
    },
}).ParseGlob("templates/*.html")
45
répondu tux21b 2013-08-16 19:42:51
la source

vous pouvez définir des fonctions dans votre modèle, et avoir ces fonctions étant des fermetures définies sur vos données comme ceci:

template.FuncMap{"isUser": func(g Gopher) bool { return string(g) == string(data.User);},}

Ensuite, vous pouvez simplement appeler cette fonction dans votre template:

{{define "sub"}}

    {{range $y := .}}>> {{if isUser $y}}!!{{$y}}!!{{else}}{{$y}}{{end}}
    {{end}}
{{end}}

Cette version mise à jour sur le terrain de jeu sorties jolie !! autour de l'utilisateur actuel:

*The great GopherBook*    (logged in as Dewey)

[Most popular]  

>> Huey
>> !!Dewey!!
>> Louie



[Most active]   

>> Huey
>> Louie



[Most recent]   

>> Louie

EDIT

puisque vous pouvez outrepasser les fonctions en appelant Funcs, vous pouvez pré-remplir les template fonctionne lors de la compilation de votre template, et mettez-les à jour avec votre fermeture actuelle comme ceci:

var defaultfuncs = map[string]interface{} {
    "isUser": func(g Gopher) bool { return false;},
}

func init() {
    // Default value returns `false` (only need the correct type)
    t = template.New("home").Funcs(defaultfuncs)
    t, _ = t.Parse(subtmpl)
    t, _ = t.Parse(hometmpl)
}

func main() {
    // When actually serving, we update the closure:
    data := &HomeData{
        User:    "Dewey",
        Popular: []Gopher{"Huey", "Dewey", "Louie"},
        Active:  []Gopher{"Huey", "Louie"},
        Recent:  []Gopher{"Louie"},
    }
    t.Funcs(template.FuncMap{"isUser": func(g Gopher) bool { return string(g) == string(data.User); },})
    t.ExecuteTemplate(os.Stdout, "home", data)
}

bien que je ne sois pas sûr de savoir comment cela se joue lorsque plusieurs goroutines tentent d'accéder au même modèle...

exemple

4
répondu val 2013-08-16 21:42:38
la source

basé sur @tux21b

j'ai amélioré la fonction pour qu'elle puisse être utilisée même sans spécifier les index ( juste pour garder le chemin va attache des variables au modèle)

Alors maintenant, vous pouvez le faire comme ceci:

{{template "userlist" dict "Users" .MostPopular "Current" .CurrentUser}}

ou

{{template "userlist" dict .MostPopular .CurrentUser}}

ou

{{template "userlist" dict .MostPopular "Current" .CurrentUser}}

mais si le paramètre (.CurrentUser.nom) n'est pas un tableau que vous devez absolument mettre un index afin de passer cette valeur à la template

{{template "userlist" dict .MostPopular "Name" .CurrentUser.name}}

voir mon code:

var tmpl = template.Must(template.New("").Funcs(template.FuncMap{
    "dict": func(values ...interface{}) (map[string]interface{}, error) {
        if len(values) == 0 {
            return nil, errors.New("invalid dict call")
        }

        dict := make(map[string]interface{})

        for i := 0; i < len(values); i ++ {
            key, isset := values[i].(string)
            if !isset {
                if reflect.TypeOf(values[i]).Kind() == reflect.Map {
                    m := values[i].(map[string]interface{})
                    for i, v := range m {
                        dict[i] = v
                    }
                }else{
                    return nil, errors.New("dict values must be maps")
               }
            }else{
                i++
                if i == len(values) {
                    return nil, errors.New("specify the key for non array values")
                }
                dict[key] = values[i]
            }

        }
        return dict, nil
    },
}).ParseGlob("templates/*.html")
2
répondu Igli Hoxha 2017-09-13 18:49:21
la source

le mieux que j'ai trouvé jusqu'à présent (et je ne l'aime pas vraiment) est de Muxer et démuxer les paramètres avec une paire de conteneurs" génériques":

http://play.golang.org/p/ri3wMAubPX

type PipelineDecorator struct {
    // The actual pipeline
    Data interface{}
    // Some helper data passed as "second pipeline"
    Deco interface{}
}

func decorate(data interface{}, deco interface{}) *PipelineDecorator {
    return &PipelineDecorator{
        Data: data,
        Deco: deco,
    }
}

j'utilise ce truc beaucoup pour construire mon site web, et je me demande s'il existe un moyen plus idiomatique d'atteindre la même chose.

0
répondu Deleplace 2013-08-16 18:55:28
la source

Annonce "... ressemble à l'appel d'une fonction avec un seul paramètre.":

Dans un sens, chaque fonction prend paramater-un dossier d'invocation multivalorisé. Avec les gabarits, c'est la même chose, que l'enregistrement "invocation" peut être une valeur primitive,ou une multivalue {map,struct,array, slice}. Le modèle peut sélectionner le {key,field, index} qu'il utilisera à partir du paramètre "single" pipeline à n'importe quel endroit.

IOW, est suffisant dans ce cas.

0
répondu zzzz 2013-08-17 15:31:30
la source

parfois les cartes sont une solution rapide et facile à des situations comme celle-ci, comme mentionné dans quelques autres réponses. Puisque vous utilisez beaucoup Gophers (et puisque, basé sur votre autre question, vous vous souciez si le Gopher actuel est un administrateur), je pense qu'il mérite sa propre structure:

type Gopher struct {
    Name string
    IsCurrent bool
    IsAdmin bool
}

Voici une mise à jour de votre code de terrain de jeu:http://play.golang.org/p/NAyZMn9Pep

évidemment il y a un peu de lourdeur à coder les structures d'exemple avec un niveau de profondeur supplémentaire, mais puisque dans la pratique ils seront générés par la machine, il est simple de marquer IsCurrent et IsAdmin en tant que de besoin.

0
répondu Darshan Rivka Whittle 2013-08-19 11:22:24
la source

la façon dont j'aborde ceci est de décorer le pipeline général:

type HomeData struct {
    User    Gopher
    Popular []Gopher
    Active  []Gopher
    Recent  []Gopher
}

en créant un contexte spécifique pipeline:

type HomeDataContext struct {
    *HomeData
    I interface{}
}

attribuer le pipeline spécifique au contexte est très bon marché. Vous aurez accès à potentiellement importants HomeData en copiant le pointeur:

t.ExecuteTemplate(os.Stdout, "home", &HomeDataContext{
    HomeData: data,
})

Depuis HomeData est incorporé dans HomeDataContext, votre template y accèdera directement (par ex. vous pouvez toujours faire .Popular et non .HomeData.Popular). Plus vous avez maintenant accès à un libre-champ de formulaire (.I).

Enfin, je fais un Using fonction pour HomeDataContext comme si.

func (ctx *HomeDataContext) Using(data interface{}) *HomeDataContext {
    c := *ctx // make a copy, so we don't actually alter the original
    c.I = data
    return &c
}

cela me permet de garder un État (HomeData) mais passer une valeur arbitraire au sous-modèle.

voir http://play.golang.org/p/8tJz2qYHbZ.

0
répondu chowey 2014-10-02 09:19:18
la source

j'ai implémenté une bibliothèque pour ce numéro qui supporte les arguments de type pipe passing&check.

Demo

{{define "foo"}}
    {{if $args := . | require "arg1" | require "arg2" "int" | args }}
        {{with .Origin }} // Original dot
            {{.Bar}}
            {{$args.arg1}}
        {{ end }}
    {{ end }}
{{ end }}

{{ template "foo" . | arg "arg1" "Arg1" | arg "arg2" 42 }}
{{ template "foo" . | arg "arg1" "Arg1" | arg "arg2" "42" }} // will raise an error

dépôt Github

0
répondu lz96 2016-08-16 10:19:57
la source

la méthode la plus simple (bien que pas la plus élégante) - en particulier pour quelqu'un relativement nouveau à aller - est d'utiliser anon structs "à la volée". Cela a été documenté/suggéré aussi loin que L'excellente présentation d'Andrew Gerrand en 2012 "10 choses que vous ne savez probablement pas sur go"

https://talks.golang.org/2012/10things.slide#1

exemple Trivial ci-dessous:

// define the template

const someTemplate = `insert into {{.Schema}}.{{.Table}} (field1, field2)
values
   {{ range .Rows }}
       ({{.Field1}}, {{.Field2}}),
   {{end}};`

// wrap your values and execute the template

    data := struct {
        Schema string
        Table string
        Rows   []MyCustomType
    }{
        schema,
        table,
        someListOfMyCustomType,
    }

    t, err := template.New("new_tmpl").Parse(someTemplate)
    if err != nil {
        panic(err)
    }

    // working buffer
    buf := &bytes.Buffer{}

    err = t.Execute(buf, data)

notez que ce ne sera pas techniquement exécuté tel quel, puisque le template a besoin d'un nettoyage mineur (à savoir se débarrasser de la virgule sur la dernière ligne de la boucle de gamme), mais c'est assez trivial. Envelopper les params de votre modèle dans une structure anonyme peut sembler fastidieux et verbeux, mais il a l'avantage supplémentaire d'indiquer explicitement ce qui sera utilisé une fois que le modèle sera exécuté. Certainement moins fastidieux que d'avoir à définir une structure nommée pour chaque nouveau template que vous écrivez.

0
répondu Yuri 2018-07-30 02:16:56
la source

Autres questions sur