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

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

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

Вы можете зарегистрировать функцию «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") 

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

 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 пытаются получить доступ к одному и тому же шаблону …

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

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

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

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

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

Объявление «… похоже на вызов функции только с одним параметром».:

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

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

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

 type Gopher struct { Name string IsCurrent bool IsAdmin bool } 

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

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

Способ, которым я подхожу к этому, – украсить общий трубопровод:

 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 cI = data return &c } 

Это позволяет мне сохранять состояние ( HomeData ), но передавать произвольное значение в HomeData .

См. http://play.golang.org/p/8tJz2qYHbZ .

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

демонстрация

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

Самый простой способ (хотя и не самый элегантный) – особенно для кого-то, относительно нового, – использовать anon-структуры «на лету». Это было задокументировано / предложено еще в замечательной презентации 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) 

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

  • Зависимая область и вложенные шаблоны
  • Есть ли ошибка компилятора, представленная моей реализацией свойства is_complete?
  • Сведения о создании шаблона компиляторов GCC и MS
  • Получить индекс типа элемента кортежа?
  • «Не объявлена ​​в этой области» ошибка с шаблонами и наследованием
  • Неявное преобразование при перегрузке операторов для classов шаблонов
  • Laravel 5 - глобальная переменная вида Blade, доступная во всех шаблонах
  • Шаблон в шаблоне: почему `` >> должен быть `>> 'в списке вложенных шаблонов шаблонов"
  • Код рефакторинга, чтобы избежать
  • JQuery's $ находится в конфликте с StringTemplate.Net в ASP.Net MVC
  • Как ссылаться на ресурс CSS / JS / image в шаблоне Facelets?
  • Interesting Posts

    Как расширить все столбцы в электронной таблице Excel?

    Почему сетевой байтовый порядок определен как big-endian?

    Как восстановить отсутствующий сертификат IIS Express SSL?

    Есть ли способ доступа к локальной переменной во внешней области в C ++?

    Почему Win7 не может запомнить мой тросик? Новая настройка соединения каждый раз

    CORS с Дартом, как мне заставить его работать?

    Отключить просмотр SSL в Chrome

    Как загрузить все изображения с веб-сайта (а не веб-страницы) с помощью терминала?

    Запуск нескольких экземпляров любого приложения Windows

    Как я могу отсортировать ArrayList строк в Java?

    Перетащите окно без полей с помощью мыши

    Каковы некоторые причины, по которым NetworkStream.Read зависает / блокируется?

    В чем причина жесткого диска, который страдает от щелчка?

    Что такое рабочие, исполнители, ядра в Spark Standalone cluster?

    Как добавить двойные кавычки к строке, находящейся внутри переменной?

    Давайте будем гением компьютера.