Давайте вдохнем в наш проект немного жизни и создадим красивую домашнюю страницу для нашего веб-приложения. В следующих нескольких уроках мы будем заниматься созданием страницы, которая выглядит следующим образом:
Рекомендуем вам супер TELEGRAM канал по Golang где собраны все материалы для качественного изучения языка. Удивите всех своими знаниями на собеседовании! 😎
Мы публикуем в паблике ВК и Telegram качественные обучающие материалы для быстрого изучения Go. Подпишитесь на нас в ВК и в Telegram. Поддержите сообщество Go программистов.
Для этого создадим файл шаблона в папке ui/html
1 2 |
$ cd $HOME/code/snippetbox $ touch ui/html/home.page.tmpl |
Добавим в него следующую HTML разметку для домашней страницы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
<!doctype html> <html lang='en'> <head> <meta charset='utf-8'> <title>Домашняя страница - Snippetbox</title> </head> <body> <header> <h1><a href='/'>Snippetbox</a></h1> </header> <nav> <a href="/">Домашняя страница</a> </nav> <main> <h2>Последние заметки</h2> <p>Здесь пока ничего нет!</p> </main> </body> </html> |
На заметку: В следующих уроках для файлов шаблонов мы будем использовать именование следующего формата
<название>.<роль>.tmpl
, где<роль>
будет одно из трехpage
,partial
илиlayout
. Возможность определить роль шаблона из самого названия файла поможет нам, когда придет время создания кеша шаблонов.
Итак, теперь, когда мы создали файл шаблона с HTML разметкой для домашней страницы, возникает вопрос: как заставить обработчик главной страницы home()
показать его?
Для этого потребуется импортировать пакет Go html/template, который предоставляет множество функций для безопасного чтения и рендеринга HTML шаблонов. Можно использовать функции из данного пакета для парсинга файла шаблона, а затем выполнить сам шаблон.
Продемонстрируем это в деле. Откройте файл cmd/web/handlers.go
и добавьте следующий код:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
package main import ( "fmt" "html/template" // новый импорт "log" // новый импорт "net/http" "strconv" ) // Обработчик главной страницы. func home(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } // Используем функцию template.ParseFiles() для чтения файла шаблона. // Если возникла ошибка, мы запишем детальное сообщение ошибки и // используя функцию http.Error() мы отправим пользователю // ответ: 500 Internal Server Error (Внутренняя ошибка на сервере) ts, err := template.ParseFiles("./ui/html/home.page.tmpl") if err != nil { log.Println(err.Error()) http.Error(w, "Internal Server Error", 500) return } // Затем мы используем метод Execute() для записи содержимого // шаблона в тело HTTP ответа. Последний параметр в Execute() предоставляет // возможность отправки динамических данных в шаблон. err = ts.Execute(w, nil) if err != nil { log.Println(err.Error()) http.Error(w, "Internal Server Error", 500) } } ... |
Важно отметить, что путь к файлу, который вы передаете функции template.ParseFiles()
, должен быть либо относительно вашей текущей рабочей папки, либо абсолютным путем. В приведенном выше коде мы указали путь относительно корня папки проекта.
Итак, с учетом сказанного убедитесь, что вы находитесь в корневой папке вашего проекта, и перезапустите веб-приложение:
1 2 3 |
$ cd $HOME/code/snippetbox $ go run ./cmd/web 2021/01/19 15:56:10 Запуск сервера на http://127.0.0.1:4000 |
Откройте страницу http://127.0.0.1:4000 в браузере. Вы заметите, что домашняя страница начала приобретать новый вид:
Основы использования шаблонизатора в Go
При добавлении дополнительных страниц в веб-приложение будет использоваться некоторая общая стандартная HTML разметка, некий «каркас«. Данный каркас будет использован на каждой странице, в него будет входить например, заголовок, меню навигации и метаданные внутри HTML элемента <head>
. По сути, у нас на таких страницах как «Контакты» и «О проекте» будет одна и та же HTML разметка кроме тех данных, что будут находится в теге <body>
.
Чтобы избавить нас от лишнего набора текста и предотвратить дублирование одного и того же HTML в нескольких файлах, рекомендуется создать макет или главный шаблон, содержащий общую структуру, который затем можно дополнить данными из других файлов шаблона.
Создайте новый файл ui/html/base.layout.tmpl
1 |
$ touch ui/html/base.layout.tmpl |
Добавим следующую основную HTML разметку (которую мы хотим отображать на каждой странице):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
{{define "base"}} <!doctype html> <html lang='en'> <head> <meta charset='utf-8'> <title>{{template "title" .}} - Snippetbox</title> </head> <body> <header> <h1><a href='/'>Snippetbox</a></h1> </header> <nav> <a href='/'>Домашняя страница</a> </nav> <main> {{template "main" .}} </main> </body> </html> {{end}} |
Если вы раньше использовали шаблонизаторы в других языках программирования, то это покажется вам знакомым. По сути, это обычный HTML с некоторыми дополнительными выражениями в двойных фигурных скобках.
Здесь мы используем выражение {{define "base"}}...{{end}}
для определения отдельного именованного шаблона под названием base
, который содержит основной HTML каркас для отображения на каждой странице.
Внутри мы используем выражение {{template "title" .}}
и {{template "main" .}}
, чтобы обозначить, что мы хотим вызывать другие именованные шаблоны (под названием title
и main
) в определенной точке нашего HTML шаблона.
На заметку: Если вам интересно, точка в конце выражения
{{template "title" .}}
означает любые динамические данные, которые требуется передать для вызываемого шаблона. В будущих уроках мы поговорим об этом подробнее.
Теперь вернемся к файлу ui/html/home.page.tmpl
и обновим этот файл для определения именованных блоков с контентом title
и main
, содержащих определенный контент для домашней страницы:
1 2 3 4 5 6 7 8 |
{{template "base" .}} {{define "title"}}Домашняя страницы{{end}} {{define "main"}} <h2>Последние заметки</h2> <p>Пока здесь ничего нет!</p> {{end}} |
В верхней правой части этого файла находится, пожалуй, самый важный элемент — выражение {{template "base" .}}
. Оно сообщает Go шаблонизатору, что при выполнении файла home.page.tmpl
требуется вызвать указанный базовый-шаблон base
который содержит основную HTML разметку.
Шаблон base
содержит инструкции по вызову именованных блоков с контентом title
и main
. Поначалу это может показаться замкнутым кругом, но не волнуйтесь — на практике этот шаблон работает очень хорошо.
Когда все будет закончено, следующим шагом будет обновление кода в обработчике home()
, чтобы тот загружал оба файла шаблонов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
package main ... // Обработчик главной странице. func home(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } // Инициализируем срез содержащий пути к двум файлам. Обратите внимание, что // файл home.page.tmpl должен быть *первым* файлом в срезе. files := []string{ "./ui/html/home.page.tmpl", "./ui/html/base.layout.tmpl", } // Используем функцию template.ParseFiles() для чтения файлов шаблона. // Если возникла ошибка, мы запишем детальное сообщение ошибки и // используя функцию http.Error() мы отправим пользователю // ответ: 500 Internal Server Error (Внутренняя ошибка на сервере) ts, err := template.ParseFiles(files...) if err != nil { log.Println(err.Error()) http.Error(w, "Internal Server Error", 500) return } // Затем мы используем метод Execute() для записи содержимого // шаблона в тело HTTP ответа. Последний параметр в Execute() предоставляет // возможность отправки динамических данных в шаблон. err = ts.Execute(w, nil) if err != nil { log.Println(err.Error()) http.Error(w, "Internal Server Error", 500) } } ... |
Теперь, вместо того, чтобы шаблон home.page.tmpl
содержал всю HTML разметку, ему достаточно указать несколько именованных блоков с контентом (base
, title
и main
) и указать в начале на основной файл шаблона, в данном случае это base
.
Не бойтесь перезапустить веб-сервер и попробовать код в работе. Вы увидите, что он отображает тот же результат, что и раньше.
Большим преимуществом использования такого шаблонизатора для работы с шаблонами является то, что с ним можно четко определять содержание конкретной страницы в отдельных файлах, и в этих файлах контролировать, какой именно каркас будет использоваться на определенной странице. Это очень полезно для больших приложений, где на разных страницах может потребоваться использование разных макетов.
Разбиваем шаблон на части
Для некоторых приложений может потребоваться разбить определенные фрагменты HTML на части, которые можно повторно использовать на разных страницах или макетах. Для примера давайте создадим файл partial
, содержащий HTML разметку подвала для веб-приложения.
Создайте новый файл ui/html/footer.partial.tmpl
и добавьте именованный блок с контентом под названием footer
следующим образом:
1 |
$ touch ui/html/footer.partial.tmpl |
1 2 3 |
{{define "footer"}} <footer>Сайт создан на <strong>Golang</strong></footer> {{end}} |
Затем обновите файл шаблона base
, чтобы он вызывал шаблон подвала, используя выражение {{template "footer" .}}
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
{{define "base"}} <!doctype html> <html lang='en'> <head> <meta charset='utf-8'> <title>{{template "title" .}} - Snippetbox</title> </head> <body> <header> <h1><a href='/'>Snippetbox</a></h1> </header> <nav> <a href='/'>Домашняя страница</a> </nav> <main> {{template "main" .}} </main> <!-- Вызываем шаблон подвала --> {{template "footer" .}} </body> </html> {{end}} |
Под конец нам нужно обновить обработчик главной странице home()
, чтобы он включал новый файл ui/html/footer.partial.tmpl
при загрузки файлов шаблона:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package main ... // Обработчик главной странице. func home(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { http.NotFound(w, r) return } // Инициализируем срез содержащий пути к двум файлам. Обратите внимание, что // файл home.page.tmpl должен быть *первым* файлом в срезе. files := []string{ "./ui/html/home.page.tmpl", "./ui/html/base.layout.tmpl", "./ui/html/footer.partial.tmpl", } ts, err := template.ParseFiles(files...) if err != nil { log.Println(err.Error()) http.Error(w, "Internal Server Error", 500) return } err = ts.Execute(w, nil) if err != nil { log.Println(err.Error()) http.Error(w, "Internal Server Error", 500) } } ... |
После перезапуска веб-сервера, шаблон base
должен вызвать шаблон footer
и вставить его содержимое в себя вместо выражения {{template "footer" .}}
. Домашняя страница должна выглядеть следующим образом:
В коде выше мы использовали выражение {{template}}
для вызова определенного шаблона из другого шаблона. Шаблонизатор в Go, также предоставляет выражение {{block}} ... {{end}}
, которое можно использовать вместо {{template}}
.
Оно работает как {{template}}
, за исключением того, что позволяет указать некоторое содержимое по умолчанию, если вызываемого шаблона нет в текущем наборе шаблонов.
Говоря о веб-приложении, это полезно, когда требуется предоставить некоторый контент по умолчанию например, боковое меню сайта (sidebar), которую отдельные страницы могут переписать по разному в зависимости от обстоятельств.
Синтаксически {{block}}
выглядит следующим образом:
1 2 3 4 5 6 |
{{define "base"}} <h1>Пример шаблона</h1> {{block "sidebar" .}} <p>Моя боковая панель!</p> {{end}} {{end}} |
Но, если хотите, вы можете не включать какой-либо контент по умолчанию между выражениями {{block}}
и {{end}}
. В этом случае вызванный шаблон действует как «необязательный». Если рассматриваемый шаблон есть в наборе шаблонов, в данном случае это sidebar
, он будет отображен. Но если его нет, то ничего не будет отображаться.
Скачать исходный код веб-приложения
В конце каждой статьи мы готовим для вас все файлы которые были использованы в этой статье. У вас всегда будет актуальная версия веб-приложения после прочтения статьи.
Скачать: snippetbox-7.zip
Администрирую данный сайт с целью распространения как можно большего объема обучающего материала для языка программирования Go. В IT с 2008 года, с тех пор изучаю и применяю интересующие меня технологии. Проявляю огромный интерес к машинному обучению и анализу данных.
E-mail: vasile.buldumac@ati.utm.md
Образование
Технический Университет Молдовы (utm.md), Факультет Вычислительной Техники, Информатики и Микроэлектроники
- 2014 — 2018 Universitatea Tehnică a Moldovei, ИТ-Инженер. Тема дипломной работы «Автоматизация покупки и продажи криптовалюты используя технический анализ»
- 2018 — 2020 Universitatea Tehnică a Moldovei, Магистр, Магистерская диссертация «Идентификация человека в киберпространстве по фотографии лица»