web-dev-qa-db-ja.com

複数のパイプラインパラメーターを使用してテンプレートを呼び出す

Goテンプレートでは、適切なデータを適切なテンプレートに渡す方法が不便な場合があります。パイプラインパラメータを使用してテンプレートを呼び出すと、パラメータを1つだけ使用して関数を呼び出すように見えます。

Gopherに関するGopherのサイトがあるとします。ホームページのメインテンプレートと、Gopherのリストを印刷するユーティリティテンプレートがあります。

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

出力:

*The great GopherBook*    (logged in as Dewey)

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

    [Most active]   
        >> Huey
        >> Louie

    [Most recent]   
        >> Louie

次に、サブテンプレートに少しコンテキストを追加します。「Dewey」という名前は、現在ログインしているユーザーの名前なので、リスト内で別の形式に設定します。しかし、 1つだけ 可能な「ドット」引数パイプラインがあるため、名前を直接渡すことはできません!私に何ができる?

  • 明らかに、サブテンプレートコードをメインテンプレートにコピーして貼り付けることができます(サブテンプレートを使用することによるすべての関心が失われるため、そうしたくありません)。
  • または、アクセサを使用してある種のグローバル変数を調整することもできます(どちらもしたくない)。
  • または、テンプレートパラメータリストごとに新しい特定の構造体タイプを作成することもできます(すばらしいものではありません)。
29
Deleplace

複数の値をテンプレート呼び出しに渡すために使用できる「dict」関数をテンプレートに登録できます。呼び出し自体は次のようになります。

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

テンプレートfuncとしての登録を含む、小さな「dict」ヘルパーのコードは次のとおりです。

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")
50
tux21b

テンプレートで関数を定義し、これらの関数を次のようにデータで定義されたクロージャにすることができます。

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

次に、テンプレートでこの関数を呼び出すだけです。

{{define "sub"}}

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

この更新されたバージョン プレイグラウンドの出力はかなり!!現在のユーザーの周り:

*The great GopherBook*    (logged in as Dewey)

[Most popular]  

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



[Most active]   

>> Huey
>> Louie



[Most recent]   

>> Louie

[〜#〜]編集[〜#〜]

Funcsを呼び出すときに関数をオーバーライドできるため、テンプレートをコンパイルするときにテンプレート関数を実際に事前入力し、次のように実際のクロージャーで更新できます。

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)
}

いくつかのgoroutineが同じテンプレートにアクセスしようとしたときにそれがどのように機能するかはわかりませんが...

実際の例

5
val

最も単純な方法(最もエレガントではありませんが)-特に比較的新しい人が行く場合-は、「オンザフライ」でanon構造体を使用します。これは、Andrew Gerrandの優れた2012年のプレゼンテーション「行くことについておそらく知らない10のこと」まで遡って文書化/提案されました。

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

以下の簡単な例:

// 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)

テンプレートは多少のクリーンアップを必要とするため(つまり、範囲ループの最後の行にあるコンマを削除するため)、これは技術的にはそのままでは実行されないことに注意してください。テンプレートのパラメーターを匿名の構造体でラップするのは面倒で冗長に見えるかもしれませんが、テンプレートの実行時に何が使用されるかを正確に明示するという追加の利点があります。記述する新しいテンプレートごとに名前付き構造体を定義する必要があるよりも、退屈な作業はまったくありません。

2
Yuri

@ Tux21bに基づく

インデックスを指定しなくても使用できるように関数を改善しました(変数をテンプレートにアタッチする方法を維持するためだけに)

だから今あなたはこのようにそれを行うことができます:

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

または

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

または

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

ただし、パラメーター(.CurrentUser.name)が配列でない場合は、この値をテンプレートに渡すためにインデックスを配置する必要があります

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

私のコードを参照してください:

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")
1
Igli Hoxha

他のいくつかの回答で述べたように、マップはこのような状況に対する迅速で簡単な解決策になる場合があります。 Gopherを頻繁に使用しているため(そして、他の質問に基づいて、現在のGopherが管理者であるかどうかを気にしているため)、独自の構造体に値すると思います。

type Gopher struct {
    Name string
    IsCurrent bool
    IsAdmin bool
}

Playgroundコードの更新を以下に示します。 http://play.golang.org/p/NAyZMn9Pep

明らかに、追加のレベルの深さで例の構造体を少し面倒な手作業でコーディングしますが、実際にはそれらは機械で生成されるため、IsCurrentIsAdminを必要に応じてマークするのは簡単です。

私がこれまでに見つけた最高の(そして私は本当にそれが好きではない)は、「汎用」ペアコンテナーを使用してパラメーターを多重化および逆多重化することです。

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,
    }
}

私は自分のウェブサイトを構築するためにこのトリックを頻繁に使用しており、同じことを達成するためのいくつかの慣用的な方法があるかどうか疑問に思っています。

0
Deleplace

私がこれに取り組む方法は、一般的なパイプラインを装飾することです:

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

コンテキスト固有のパイプラインを作成する:

type HomeDataContext struct {
    *HomeData
    I interface{}
}

コンテキスト固有のパイプラインの割り当ては非常に安価です。ポインタをコピーすることにより、潜在的に大きいHomeDataにアクセスできます。

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

HomeDataHomeDataContextに埋め込まれているため、テンプレートから直接アクセスできます(例:.Popularではなく.HomeData.Popular)。さらに、自由形式のフィールド(.I)。

最後に、UsingHomeDataContext関数をこのように作成します。

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
}

これにより、状態(HomeData)を維持しながら、任意の値をサブテンプレートに渡すことができます。

http://play.golang.org/p/8tJz2qYHbZ を参照してください。

0
chowey

広告 "...パラメータを1つだけ使用して関数を呼び出すように見えます。":

ある意味で、すべての関数はoneパラメータをとります-複数値の呼び出しレコード。テンプレートの場合も同じで、その「呼び出し」レコードはプリミティブ値または複数値の{map、struct、array、slice}にすることができます。テンプレートは、どの{key、field、index}を使用するかを、任意の場所の「単一」パイプラインパラメータから選択できます。

IOW、この場合はoneで十分です。

0
zzzz

この問題のために、パイプのような引数の受け渡しをサポートするライブラリを実装しました。

デモ

{{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リポジトリ

0
lz96