2013-08-16 2 views
19

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

Предположим, у меня есть сайт для Gophers about 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 

Теперь я хочу добавить немного контекста в subtemplate: форматировать название «Дьюи» по-разному в списке потому, что это имя текущего пользователя , Но я не могу передать имя напрямую, потому что есть only one возможный контур аргумента «точка»! Что я могу сделать?

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

ответ

42

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

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

код для маленького «Словаря» помощника, в том числе регистрации его в качестве шаблона FUNC здесь:

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") 
+0

Это приятно, спасибо. Таким образом, это мультиплексор множественных ключей/значений, например PipelineDecorator (см. Другой ответ), но с более возможными значениями в одном вызове. – Deleplace

+0

Прежде чем я начал писать этот код, я подумал, что это станет действительно уродливым, но теперь мне это нравится, и я, вероятно, собираюсь использовать его в некоторых своих проектах. – tux21b

+0

Я отмечаю его как принятый, так как это «современное состояние» для конвейерной обработки произвольных данных (но все же обходное решение в отношении выбора дизайна шаблонов). Вот мой весь пример, переопределенный 'dict()': http: //play.golang.org/p/oWdPlyWfvG – Deleplace

1

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

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

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

3

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

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

This updated version на площадке выходов довольно !! вокруг текущего пользователя:

*The great GopherBook* (logged in as Dewey) 

[Most popular] 

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



[Most active] 

>> Huey 
>> Louie 



[Most recent] 

>> Louie 

EDIT

Поскольку вы можете переопределить функции при вызове 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 пытаются получить доступ к одному шаблону ...

The working example

+0

Хорошо, поэтому в рамках текущего исполнения шаблона мы определяем некоторые «глобальные аксессоры» для интересных данных. Главный недостаток imho заключается в том, что я не могу повторно использовать скомпилированный шаблон, я вынужден создать FuncMap с закрытием, затем скомпилировать, а затем выполнить шаблон для каждого запроса. – Deleplace

+0

Да, я думаю, в вашем случае решение tux21b может быть более гибким. В конце концов, вам просто нужно все свои значения упаковать так или иначе ... – val

+0

Woh хорошо знать, что на самом деле можно связать новые закрытия после компиляции шаблона, я не знал об этом. Для потенциального состояния гонки я попробовал это и использовал 'runtime.GoSched()' для принудительного чередования goroutine и обнаружил ошибку с помощью 'panic()': http://play.golang.org/p/b5WVlUlGjS – Deleplace

0

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

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

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

+1

Точно: моя озабоченность не связана с осуществимостью, это скорее чистый дизайн. Посмотрите на другие языки (C, Java), которые не имеют нескольких возвращаемых значений: akward определяет эвклидовое деление, дающее фактор и остаток. Представьте себе язык, где функции имеют только один параметр, и вы должны использовать 4 или 5 строк для объявления новой структуры данных перед каждым списком параметров: не было бы хорошо скомпоновать код. – Deleplace

0

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

type Gopher struct { 
    Name string 
    IsCurrent bool 
    IsAdmin bool 
} 

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

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

0

Путь Подхожу это украсить общий трубопровод:

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.

+0

Украшение и вложение - это многообещающие идеи, но я хочу, чтобы контекст был доступен для подтемов. Не могли бы вы устранить эту проблему, отредактировав исходный пример? Благодаря! – Deleplace

+0

К сожалению, ваш первый комментарий абсолютно точный: «Вызов шаблона с параметром конвейера выглядит как вызов функции только с одним параметром». У меня был лучший успех с сохранением общего объекта «Контекст» и изменением одного поля на нем. – chowey

+0

ОК, так что это похоже на PipelineDecorator (см. Другой ответ). И это хорошая идея иметь 'Использование' как метод, а не функцию. – Deleplace

0

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

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 

Github repo

2

на основе @ 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") 
Смежные вопросы