web-dev-qa-db-fra.com

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

Dans un modèle Go, parfois, la manière de transmettre les bonnes données au bon modèle me semble gênante. L'appel d'un modèle avec un paramètre de pipeline ressemble à l'appel d'une fonction avec un seul paramètre.

Disons que j'ai un site pour Gophers à propos de Gophers. Il comporte un modèle principal de page d'accueil et un modèle d'utilitaire pour imprimer une liste de Gophers.

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 ajouter un peu de contexte dans le sous-modèle: formatez le nom "Dewey" différemment dans la liste car c'est le nom de l'utilisateur actuellement connecté. Mais je ne peux pas passer le nom directement car il y a un seul pipeline d'argument "dot" possible! Que puis-je faire? 

  • Évidemment, je peux copier-coller le code du sous-modèle dans le modèle principal (je ne le souhaite pas, car il perd tout intérêt à avoir un sous-modèle).
  • Ou je peux jongler avec une sorte de variable globale avec des 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 modèle (pas génial).
25
Deleplace

Vous pouvez enregistrer une fonction "dict" dans vos modèles que vous pouvez utiliser pour transmettre plusieurs valeurs à un appel de modèle. L'appel lui-même ressemblerait alors à ça:

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

Le code du petit assistant "dict", incluant son enregistrement en tant que modèle, 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")
46
tux21b

Vous pouvez définir des fonctions dans votre modèle et les définir comme fermetures sur vos données, comme suit:

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

Ensuite, vous pouvez simplement appeler cette fonction dans votre modèle:

{{define "sub"}}

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

Cette version mise à jour sur le terrain de jeu affiche un !! intéressant autour de l'utilisateur actuel:

*The great GopherBook*    (logged in as Dewey)

[Most popular]  

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



[Most active]   

>> Huey
>> Louie



[Most recent]   

>> Louie

MODIFIER

Comme vous pouvez remplacer des fonctions lorsque vous appelez Funcs, vous pouvez en fait pré-renseigner les fonctions de modèle lors de la compilation de votre modèle et les mettre à jour avec votre fermeture réelle comme suit:

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 passe lorsque plusieurs goroutines tentent d’accéder au même modèle ... 

L'exemple de travail

4
val

basé sur @ Tux21b

J'ai amélioré la fonction afin qu'elle puisse être utilisée même sans spécifier les index (juste pour continuer, 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.name) n'est pas un tableau, vous devez absolument mettre un index pour pouvoir transmettre cette valeur au modèle.

{{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
Igli Hoxha

La méthode la plus simple (bien que pas la plus élégante) - en particulier pour les personnes relativement nouvelles - consiste à utiliser une structure "à la volée". Cela a été documenté/suggéré dès l'excellent exposé d'Andrew Gerrand en 2012, "10 choses que vous ne savez probablement pas à propos"

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 cela ne fonctionnera pas techniquement tel quel, car le modèle nécessite un nettoyage mineur (notamment l'élimination de la virgule sur la dernière ligne de la boucle), mais c'est assez simple. Envelopper les paramètres de votre modèle dans une structure anonyme peut sembler fastidieux et prolixe, mais cela présente l'avantage supplémentaire d'indiquer explicitement ce qui sera utilisé une fois que le modèle sera exécuté. Définitivement moins fastidieux que de devoir définir une structure nommée pour chaque nouveau modèle que vous écrivez.

1
Yuri

Parfois, les cartes constituent une solution rapide et facile à des situations de ce type, comme indiqué dans quelques autres réponses. Puisque vous utilisez beaucoup Gophers (et puisque, sur la base de votre autre question, vous vous souciez de savoir 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 Playground: http://play.golang.org/p/NAyZMn9Pep

Évidemment, il est un peu fastidieux de coder manuellement les exemples de structure avec un niveau supplémentaire de profondeur, mais comme ils seront en pratique générés par machine, il est facile de marquer IsCurrent et IsAdmin si nécessaire.

0

Le meilleur que j'ai trouvé jusqu'à présent (et je ne l'aime pas vraiment) est le multiplexage et le démultiplexage avec un conteneur de paires "générique":

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 beaucoup cette astuce pour créer mon site Web et je me demande s'il existe un moyen plus idiomatique de réaliser la même chose.

0
Deleplace

La façon dont je l'aborde est de décorer le pipeline général:

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

en créant un pipeline spécifique au contexte:

type HomeDataContext struct {
    *HomeData
    I interface{}
}

L'allocation du pipeline spécifique au contexte est très économique. Vous pouvez accéder à la variable HomeData potentiellement importante en y copiant le pointeur:

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

Étant donné que HomeData est incorporé à HomeDataContext, votre modèle y accédera directement (par exemple, vous pouvez toujours utiliser .Popular et non .HomeData.Popular). De plus, vous avez maintenant accès à un champ de forme libre (.I).

Enfin, je crée une fonction Using pour HomeDataContext comme suit.

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 conserver un état (HomeData) mais de transmettre une valeur arbitraire au sous-modèle.

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

0
chowey

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

En un sens, chaque fonction prend un paramater - un enregistrement d’invocation à valeurs multiples. Avec les templates, c'est pareil, cet enregistrement "invocation" peut être une valeur primitive ou une {map, struct, array, slice} à plusieurs valeurs. Le modèle peut sélectionner quelle {clé, champ, index} il utilisera à partir du paramètre "unique" de pipeline, quel que soit le lieu.

IOW, one est suffisant dans ce cas.

0
zzzz

J'ai mis en place une bibliothèque pour ce problème qui prend en charge les arguments de type pipe comme ceux de type pass & check.

Démo

{{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

Github repo

0
lz96