От JavaScript к функциональной веб-разработке (часть 2)
Если вы еще не читали, то можете прочитать первую часть здесь!
Когда мне было 12 или около того, мне очень нравилась браузерная MMO-игра. Я пытался воссоздать свою версию этой игры для себя и друзей. В процессе я действительно хотел показать статистику каждого, поэтому я написал таблицу рекордов. Сегодня мы воссоздадим эту таблицу здесь.
Раньше он работал с базой данных SQL, но теперь мы собираемся сделать так, чтобы он позволял нам добавлять новые записи на тот же веб-сайт.
Сначала мы просто создадим наше одностраничное приложение WebSharper, я назову свой проект «HighScoreTable». Мы будем работать только в Client.fs
и index.html
. Начнем с index.html
, чтобы у нас была база для шаблона на F#. Из исходного проекта нам не нужно ничего менять из тега <head>
. Наш код входит в <body>
.
Для этого нам нужна новая информация о WebSharper. Мы уже видели, как работать со статическими элементами, сгенерированными F#, но на этот раз мы узнаем, как использовать шаблоны. С помощью шаблонов мы можем один раз написать наш код F#, а затем нам нужно только изменить html-файл, и не будет необходимости в пересборке F#.
Что нам нужно? Нам нужно table
для нашей таблицы рекордов, несколько полей input
для нашей новой записи и button
для добавления нашей новой записи. Нам также понадобятся некоторые новые теги, которые мы еще не использовали:
-ws-var
: привязывает реактивную переменную к значениям наших полей ввода'
-ws-children-template
: создает для нас шаблон, но отбрасывает тегированные Элемент DOM
-ws-template
: Создает шаблон, сохраняющий помеченный элемент DOM
-ws-replace
: Заменяет элемент DOM заданным элементом(ами)
-ws-hole
: Заполняет элемент DOM заданным элементом (s)
-ws-onclick
: Назначает функцию действию элемента «onclick».
Но что такое шаблон? С помощью шаблонов мы можем определять html-блоки и использовать (и повторно использовать) их в нашем результирующем html-файле. Например:
<div ws-template="LoginTemplate"> <input ws-var="name" value="Username" /> <input type="password" ws-var="password" /> <button ws-onclick="login">Login</button> </div>
Мы могли бы сослаться на этот шаблон входа и разместить его в любом месте нашего кода из F# (поскольку мы пометили его тегом ws-template
, <div>
является частью шаблона). У него будет реактивная переменная, привязанная к «Имени» и «Паролю», и функция, привязанная к кнопке «Войти». Из нашего кода F# мы можем получить к нему доступ, и мы можем создать документ из этого шаблона, который позже можно будет использовать, например, для заполнения ws-replace
. Сначала это может показаться странным, но к концу этого поста вы все поймете.
Индекс.html
Поскольку мы хотим добиться результатов модификации html без пересборки F#, нам нужно написать немного больше в нашем index.html
. Давайте начнем с нашего основного шаблона, я назову его «Основной»:
<div ws-children-template="Main"> ... </div>
Таблица, которую мы собираемся использовать, проста, содержит статическую thead
и динамическую tbody
. Для создания нашего динамического тела мы будем использовать другой шаблон. Этот шаблон будет служить строкой таблицы для каждой отдельной записи. В этом примере мы назовем этот шаблон «Строка»:
<tr ws-template="Row"> <td ws-hole="Name"></td> <td ws-hole="Level"></td> <td ws-hole="Gold"></td> <td ws-hole="Score"></td> </tr>
У нас должен быть этот шаблон между нашими тегами <tbody></tbody
, так как это шаблон <tr></tr>
. Если у нас есть это вне стола, оно будет выброшено.
От нашего index.html
осталось только одно — новая форма заявки. Поскольку мы используем четыре атрибута для каждой строки, у нас должно быть четыре поля ввода и кнопка:
<div> <input ws-var="Name" /> <input type="number" ws-var="Level" /> <input type="number" ws-var="Gold" /> <input type="number" ws-var="Score" /> <button ws-onclick="Add">Add</button> </div>
С этим добавлением мы почти закончили с файлом html. Все, что нам нужно, это заполнитель для нашего шаблона и красивое название. Например, <div>
с id="main"
хорошо послужит заполнителем:
<h1>My high score table</h1> <div id="main"></div>
Это наш index.html
с добавленной статической частью таблицы:
Клиент.fs
Прежде всего, нам нужно получить доступ к нашему html-шаблону из файла fs. Мы можем сделать это, определив для него новый тип:
type IndexTemplate = Template<"index.html", ClientLoad.FromDocument>
Используя это, мы можем создать наш шаблон, используя IndexTemplate.Main()
и IndexTemplate.Row()
, поскольку мы назвали наши шаблоны «Основной» и «Ряд».
Так же, как мы сделали для html-файла, давайте посмотрим, что нам нужно в файле F#:
— Тип записи для наших записей
— Список с нашими записями
— Четыре реактивные переменные для новая запись
- Функция добавления нашей новой записи
- Заполнение шаблонов
Я назову нашу входную запись «Entry» и задам определенные выше свойства:
type Entry = {Name: string; Level: int; Gold: int; Score: int}
Чтобы сделать код более читаемым, а тип простым в использовании, мы можем определить функцию Create:
static member Create name level gold score = { Name = name Level = level Gold = gold Score = score }
Для списка мы должны использовать ListModel
WebSharper, так как мы можем динамически использовать данные из этого списка, и это именно то, что нам нужно здесь. Мы создадим этот ListModel
из seq
с помощью функции ListModel.FromSeq
и добавим в список элемент по умолчанию:
let Players = ListModel.FromSeq [ Entry.Create "Name" 1 25 200 ]
С этим у нас есть все, что нам нужно, чтобы заполнить наш шаблон строки таблицы. Мы собираемся начать с простой функции для преобразования нашей записи в строку таблицы:
let row entry = IndexTemplate.Row() .Name(entry.Name) .Level(string entry.Level) .Gold(string entry.Gold) .Score(string entry.Score) .Doc()
Теперь нам нужно просмотреть наш список и сопоставить эту функцию строки с каждым элементом, а затем создать из нее документ, чтобы использовать его в наших <tbody>
ws-hole
.
Для этого требуется более одной функции, но я все объясню:
let data = Players.View.Doc(fun lm -> lm |> Seq.sortByDescending (fun t -> t.Score ) |> Seq.map row |> Doc.Concat )
Сначала мы создаем View
из нашего списка. Это означает, что мы всегда будем получать текущие данные из списка. Затем, как того требует tbody
, мы преобразуем наш список в файл Doc. Для этого мы даем функцию, которая получает ListModel
в качестве параметра и возвращает Doc
. Мы называем это ListModel
«lm», затем применяем к нему сортировку (поскольку это таблица рекордов, нам нужна какая-то сортировка). Эта сортировка происходит с помощью функции Seq.sortByDescending
. Этой функции нужна другая функция, чтобы знать, по какому полю сортировать. В этом примере мы установили сортировку по счету игрока. Теперь у нас есть отсортированный seq
с данными игроков. Следующим шагом является преобразование его в таблицу Doc. Нам повезло, и у нас уже есть функция, которая делает то же самое: row
. Все, что нам нужно сделать, это отобразить эту функцию в нашем списке. Мы почти закончили, но теперь у нас есть список в списке, но нам нужен только список на выходе tbody
. Чтобы решить эту проблему, мы просто вызываем Doc.Concat
, чтобы сделать один список из списка списков. На этом мы закончили с нашей таблицей.
Нам все еще нужны четыре реактивные переменные для хранения наших новых входных данных. Давайте создадим их с помощью Var.Create
:
let newEntryName = Var.Create "Name" let newEntryLevel = Var.Create 0 let newEntryGold = Var.Create 0 let newEntryScore = Var.Create 0
На этом мы почти закончили. Последнее, что нам нужно, это новая запись в нашей таблице и построение шаблона. Наша функция для добавления новой записи в список достаточно мала, чтобы оставить ее в объявлении шаблона как лямбда-функцию.
Как я упоминал ранее, мы можем использовать наш шаблон с IndexTemplate.Main()
, и мы можем получить доступ к каждому полю, которое мы пометили ws-*="Something"
как .Something()
. Что нам здесь нужно сделать?
- Заполнить ws-hole
в нашем tbody
- Связать каждое поле ввода с его реактивной переменной
- Написать нашу функцию добавления на кнопку с ws-onlick="Add"
(мы можем добавить новая запись в наши Игроки ListModel
с функцией Add
)
IndexTemplate.Main() .Data(data) .Name(newEntryName) .Level(newEntryLevel) .Gold(newEntryGold) .Score(newEntryScore) .Add(fun _ -> Entry.Create (newEntryName.Value) (newEntryLevel.Value) (newEntryGold.Value) (newEntryScore.Value) |> Players.Add |> ignore )
Мы почти закончили! Единственное, что нам нужно сделать, это доработать наш шаблон, создать из него документ с функцией .Doc()
и, как мы делали это в нашем первом проекте, поместить его в наш <div>
с помощью id="main"
. Мы делаем это, вызывая функцию Doc.RunById "main"
в нашем шаблоне.
Давайте посмотрим на все Client.fs
в его окончательном виде:
Конечно, это не выглядит причудливо или профессионально, но с нашим готовым кодом F# нам нужно только изменить index.html
с помощью шаблона и стилей, и он будет работать без необходимости повторной сборки всего проекта. Просто измените index.html
и нажмите «Обновить» в браузере.
Если вы хотите проверить этот веб-сайт или хотите поиграть с кодом, вы можете сделать это в Try WebSharper. Перейдя по этой ссылке, вы можете увидеть результат написанного нами кода и модифицировать его, чтобы увидеть изменения в вашем браузере!
Обязательно оставьте 💚, это поможет мне узнать, какие истории вы хотите прочитать, и оставьте комментарий, если у вас есть какие-либо вопросы! Надеюсь, вам было интересно читать этот пост, и вы полюбили F# и WebSharper.