В прошлый раз мы рассмотрели, что требуется для запуска сервера, а также узнали, как выводить текстовую информацию на страницу сайта и как возвращать данные в формате JSON. Однако для создания полноценного сайта нам необходимо овладеть основными возможностями Golang в плане работы с HTML. Этим мы и займемся в данной статье.
Статические страницы
Если мы хотим, чтобы при при заходе на стартовую страницу пользователю вместо текстового сообщения открывалась страничка с HTML контентом , то нам достаточно сделать следующее:
В папке «public» создаем новую папку «html». Затем создаем и размещаем там готовую страницу «startStaticPage.html» .
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Приветствую тебя на стартовой странице этого сайта!</h1>
<ul>
<li>Первый элемент маркированного списка</li>
<li>Второй элемент маркированного списка</li>
<li>Третий элемент маркированного списка</li>
</ul>
</body>
</html>
Переписываем хендлер главной страницы:
package controller
import (
"github.com/julienschmidt/httprouter"
"html/template"
"net/http"
"path/filepath"
)
func StartPage(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
//указываем путь к нужному файлу
path := filepath.Join("public", "html", "startStaticPage.html")
//создаем html-шаблон
tmpl, err := template.ParseFiles(path)
if err != nil {
http.Error(rw, err.Error(), 400)
return
}
//выводим шаблон клиенту в браузер
err = tmpl.Execute(rw, nil)
if err != nil {
http.Error(rw, err.Error(), 400)
return
}
}
Проверяем результат:

При этом необязательно использовать HTML файлы (хотя предпочтительнее и удобнее). Мы можем «сверстать» наш контент прямо в коде в виде обычной строки, а затем превратить его в HTML и отправить клиенту:
func StartPage(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
//верстаем контент страницы в виде обычной строки
content := `<html lang="ru">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Приветствую тебя на стартовой странице этого сайта!</h1>
<ul>
<li>Первый элемент маркированного списка</li>
<li>Второй элемент маркированного списка</li>
<li>Третий элемент маркированного списка</li>
<li>Четвертый элемент маркированного списка</li>
</ul>
</body>
</html>`
//создаем html-шаблон
tmpl, err := template.New("example").Parse(content)
if err != nil {
http.Error(rw, err.Error(), 400)
return
}
tmpl.Execute(rw, content)
}
Результат:

Динамические страницы
Если данные подтягиваются из базы и могут меняться, то мы можем генерировать содержание страниц на лету перед выводом. Для этого в Go предусмотрен отдельный пакет, который может соединять шаблоны по именам, а также перебирать в цикле переданные данные, превращая их в элементы интефейса: строки таблиц, блоки, контроллы и т.д.
Создадим страницу «usersDynamicPage.html» в папке «html»:
{{define "users"}}
<!doctype html>
<html lang="ru" class="h-100">
<head>
<title>Title</title>
</head>
<body class="d-flex flex-column h-100">
<table class="table">
<th>id</th>
<th>Имя</th>
<th>Фамилия</th>
{{range .}}
<tr>
<td>{{.Id}}</td>
<td>{{.Name}}</td>
<td>{{.Surname}}</td>
</tr>
{{end}}
</table>
</body>
</html>
{{end}}
Обратите внимание, что здесь используются две специальные конструкции:
- {{define «users»}}{{end}} — объявляется начало и конец именованного шаблона «users»;
- {{range .}} {{end}} — объявляется начало и конец цикла, при помощи которого будут генерироваться строки таблицы на основании массива переданных данных. Переданные данные у нас не имеют ключа, поэтому представлены в виде точки. Однако внутри каждого из элементов массива есть элементы структуры «Id», «Name» и «Surname», обращение к которым возможно только по ключу.
Теперь перепишем наш хендлер для вывода списка пользователей:
package controller
import (
"github.com/julienschmidt/httprouter"
"goSiteProject/app/model"
"html/template"
"net/http"
"path/filepath"
)
func GetUsers(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
//получаем список всех пользователей
users, err := model.GetAllUsers()
if err != nil {
http.Error(rw, err.Error(), 400)
return
}
//указываем путь к файлу с шаблоном
main := filepath.Join("public", "html", "usersDynamicPage.html")
//создаем html-шаблон
tmpl, err := template.ParseFiles(main)
if err != nil {
http.Error(rw, err.Error(), 400)
return
}
//исполняем именованный шаблон "users", передавая туда массив со списком пользователей
err = tmpl.ExecuteTemplate(rw, "users", users)
if err != nil {
http.Error(rw, err.Error(), 400)
return
}
}
Проверяем результат:

К слову, можно использовать условия в виде конструкций {{if}}{{else}}{{end}}, которые можно применять при генерации страниц. С ними вы при желании сможете ознакомиться отдельно, но для примера мы попробуем выводить пользователей, начиная с пятого.
{{range .}}
<tr>
{{if gt .Id 4}}
<td>{{.Id}}</td>
<td>{{.Name}}</td>
<td>{{.Surname}}</td>
{{end}}
</tr>
{{end}}
Результат:

Теперь попробуем собрать одну страницу из нескольких частей.
В папке «public/html» создаем новый файл «common.html» и размещаем там следующий код:
{{define "meta"}}
<meta charset="utf-8">
{{end}}
{{define "styles"}}
<style>
table th {color: red};
</style>
{{end}}
{{define "scripts"}}
<script>
document.addEventListener('DOMContentLoaded', function(){
setTimeout(sayHello, 1000);
});
function sayHello() {
alert("Hello from scripts!");
}
</script>
{{end}}
Далее модифицируем файл «usersDynamicPage.html». Внутри тега head размещаем ссылки на именованные шаблоны со стилями и метаданными:
<head>
{{template "meta"}}
{{template "styles"}}
<title>Title</title>
</head>
Перед закрывающим тегом body размещаем ссылку на именованный шаблон со скриптами:
{{template "scripts"}}
</body>
Теперь доработаем немного хендлер вывода страницы со списком сотрудников, так, чтобы при генерации мы могли использовать именованные шаблоны и из файла «common.html».
//указываем пути к файлам с шаблонами
main := filepath.Join("public", "html", "usersDynamicPage.html")
common := filepath.Join("public", "html", "common.html")
//создаем html-шаблон
tmpl, err := template.ParseFiles(main, common)
if err != nil {
http.Error(rw, err.Error(), 400)
return
}
Проверяем результат:

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