Подтвердить что ты не робот

Вызов шаблона с несколькими параметрами трубопровода

В шаблоне Go иногда способ передачи правильных данных в правильный шаблон мне кажется неудобным. Вызов шаблона с параметром конвейера выглядит как вызов функции только с одним параметром.

Скажем, у меня есть сайт для Gophers о Gophers. Он имеет основной шаблон главной страницы и шаблон утилиты для печати списка Gophers.

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" по-другому внутри списка, потому что это имя текущего пользователя. Но я не могу передать это имя напрямую, потому что есть только один возможный контур аргумента "точка"! Что я могу сделать?

  • Очевидно, что я могу скопировать-вставить код подтемы в основной шаблон (я не хочу, потому что он полностью утрачивает интерес к тому, чтобы иметь подстроку).
  • Или я могу манипулировать некоторыми глобальными переменными с помощью аксессуаров (я тоже не хочу).
  • Или я могу создать новый конкретный тип структуры для каждого списка параметров шаблона (не очень).
4b9b3361

Ответ 1

Вы можете зарегистрировать функцию "dict" в ваших шаблонах, которые вы можете использовать для передачи нескольких значений вызову шаблона. Сам вызов будет выглядеть следующим образом:

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

Код для маленького помощника "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")

Ответ 2

Вы можете определить функции в своем шаблоне, и эти функции будут закрываться, определенные в ваших данных, например:

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

Хотя я не уверен, как это происходит, когда несколько goroutines пытаются получить доступ к одному и тому же шаблону...

Рабочий пример

Ответ 3

на основе @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")

Ответ 4

Самое лучшее, что я нашел до сих пор (и мне это не очень нравится) - это параметры мультиплексирования и демультиплексирования с помощью "родового" контейнера:

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

Я использую этот трюк для создания своего сайта, и мне интересно, есть ли еще какой-то идиоматический способ достичь того же.

Ответ 5

Ad "... выглядит как вызов функции только с одним параметром.":

В некотором смысле каждая функция принимает один параметр - многозначную запись вызова. С шаблонами это то же самое, что запись "invocation" может быть примитивным значением или многозначным {map, struct, array, slice}. Шаблон может выбрать, какой {ключ, поле, индекс} он будет использовать из "одного" параметра конвейера в любом месте.

IOW, в этом случае достаточно.

Ответ 6

Иногда карты - это быстрое и простое решение таких ситуаций, как это упомянуто в двух других ответах. Поскольку вы много используете Gophers (и поскольку, исходя из вашего другого вопроса, вам все равно, является ли текущий Gopher администратором), я думаю, что он заслуживает своей собственной структуры:

type Gopher struct {
    Name string
    IsCurrent bool
    IsAdmin bool
}

Здесь обновляется код вашей игровой площадки: http://play.golang.org/p/NAyZMn9Pep

Очевидно, что он получает немного громоздкое ручное кодирование примеров structs с дополнительным уровнем глубины, но так как на практике они будут сгенерированы машиной, просто пометить IsCurrent и IsAdmin по мере необходимости.

Ответ 7

Как я подхожу к этому, нужно украсить общий конвейер:

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

Так как HomeData встроен в HomeDataContext, ваш шаблон будет обращаться к нему напрямую (например, вы все еще можете сделать .Popular, а не .HomeData.Popular). Кроме того, теперь у вас есть доступ к полю свободной формы (.I).

Наконец, я делаю функцию Using для HomeDataContext так.

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.

Ответ 8

Я реализовал библиотеку для этой проблемы, которая поддерживает передаваемые по протоколу аргументы и проверку.

Демонстрационный

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