Создаем собственный блог на Svelte. Часть 3: Добавляем новые посты

Создаем собственный блог на Svelte. Часть 3: Добавляем новые посты

Переходим к третьей части нашей инструкции по созданию собственного блога. Напоминаю, что мы пишем на Svelte с TailwindCSS, а в качестве базы данных используем SQLite. 

Предыдущие «серии»: 

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

Создаем компонент для написания статьи и отправки ее на сайт

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

  • Создаем новый файл в директории routes с названием NewPost.svelte.
  • Создаем внутри блок <script> и записываем в него две переменные: 
let postName 

и 

let postBody
Переменные postName и postBody
  • Затем формируем форму:
Форма с полями для ввода текста

Разбираем построчно.

  1. <form> – это тег, позволяющий создать поле, которое по умолчанию предназначено для передачи данных в какие-то внешние инстанции. Мы добавляем к нему атрибут on:submit (это Svelte-атрибут для отслеживания действия submit). Также мы через прямую строку пишем preventDefault, чтобы заблокировать все действия формы по умолчанию (чтобы не происходила перезагрузка страницы). И указываем функцию, которая будет запускаться вместо действий по умолчанию. В нашем случае она называется addPost (позже мы ее задекларируем).
  2. <label> – это название для нашего поля. Либо название статьи, либо ее текст. 
  3. <input type=»text» name=»name» bind:value={postName} /> – это просто блок с полем для ввода текста. Здесь из интересного только атрибут bind:value. Его идея заключается в том, чтобы «привязать» значение value выбранного поля ввода к уже существующей переменной, чтобы значение переменной postName всегда равнялось значению инпута name. Или значение postBody всегда равнялось значению инпута body.
  4. <input type=»submit» value=»Добавить статью» /> – думаю, тут и без лишних пояснений все понятно. Это кнопка, которая отвечает за добавление статей. В нашем случае эта кнопка будет провоцировать действие on:submit, прописанное в списке атрибутов формы.

Форма готова. Пока что она максимально уродлива, но мы это исправим чуть позже. 

Создаем функцию для передачи данных о статье в новую точку доступа

Теперь нужна как раз та самая функция addPost для добавления новых статей. 

Функция добавления статей
  1. Сначала создаем функцию addPost. Она должна быть асинхронной. 
  2. В ее теле создаем константу с названием dataToAdd. Это информация о запросе, который мы будем передавать в точку доступа. А fetch(‘/api/addPost’ – это как раз название точки доступа. Заметьте, что перед fetch написано await, а это значит, что действие выполняется асинхронно, дожидаясь ответа от точки доступа и базы данных.
  3. Сразу после пути к точке доступа через запятую мы ставим фигурные скобки, чтобы создать новый объект с информацией о fetch-запросе. Во-первых, указываем тип fetch. Нам нужен POST-метод, поэтому пишем method: ‘POST’. Затем ставим запятую и вторым элементом объекта указываем body: JSON.stringify({postName, postBody}). Функция JSON.stringify нужна, чтобы JSON-объект превратить в строку, а остальное – это переменные с информацией из инпутов. 

Готово. Теперь у нас есть функция, которая делает запрос данных. Почти.

Создаем функцию для передачи информации о новой статье в базу данных

Перед тем как продолжить, создадим новую точку доступа с названием addPost.js и добавим ее в директорию api, где лежит наш предыдущий endpoint, отвечающий за загрузку статей на сайт. 

Внутри него указываем следующее:

Точка доступа к базе данных
  • Сначала импортируем базу данных из $lib, как уже делали это ранее. 
  • Создаем функцию post для передачи данных и в качестве ее аргумента передаем request(это как раз те данные, что мы цепляем из компонента NewPost.svelte).
  • Далее создаем константу data и записываем туда наши данные, предварительно превратив их из строки в объект:
JSON.parse(request.body)
  • Потом создаем еще две константы с содержимым полученного объекта. Нам нужны наши postName и postBody. 
  • После этого подготавливаем базу данных для работы с новыми данными. Мы последовательно указываем, что нам нужно вставить (INSERT INTO)-данные (title и body) в таблицу Posts, а их новые значения должны быть переданы в качестве аргументов, поэтому пока вместо них указаны вопросительные знаки. Учтите, что здесь важную роль теперь играет непосредственно порядок передачи аргументов. 
  • Создаем константу sendDbData, через которую будет осуществлен запуск функции runповерх уже подготовленной ранее информации c подменой аргументов: 
dbData.run(postName, postBody)
  • postName займет место первого вопросительного знака, а postBody – второго. Также нужно сделать пустой возврат, чтобы наша точка доступа функционировала: 
return {body: {}}

Готово. Теперь у нас есть все функции и элементы интерфейса, необходимые для добавления на сайт новых статей. При заполнении полей и нажатии на «Добавить статью» в базу данных автоматически попадет информация с заполненных полей. Все просто. 

Стилизуем форму

Сейчас форма выглядит неудобной и нелогичной. Непонятно, какой элемент где заканчивается и на что из этого можно нажать. Так происходит отчасти из-за TailwindCSS, потому что по умолчанию этот фреймворк стирает все существующие стили (чтобы вы могли выбрать свой дизайн, предварительно не занимаясь удалением уродских иконок и рамок). 

Tailwind-стили на форме

Сразу оговорюсь по поводу дизайна. Это вещь сугубо индивидуальная и не каждому понравится тот стиль, что выбрал я. Поэкспериментируйте на сайте Tailwind Play и подберите собственный уникальный стиль. Это несложно. 

А теперь немного про то, что натворил я. 

  • <form> теперь отображается как flex-box с flex-direction: column. То есть это вертикально расположенные блоки. Также добавил небольшой margin, чтобы «оторвать» форму от краев экрана. 
  • Инпутам я добавил рамку, чтобы их легче было идентифицировать. А также сделал так, чтобы они растягивались на всю длину блока. За такое поведение отвечает свойство w-full. А еще я заменил второй инпут на textarea, чтобы можно было делать отступы в тексте. 
  • Ну и кнопка окрасилась в синий цвет, параллельно добавив другой дизайн курсора, чтобы было понятно, что это кнопка и на нее можно нажать. 

Результат: 

Пример интерфейса для добавления статьи

Решаем возникшие проблемы

По ходу создания предыдущего компонента и добавления новой функциональности у нас возникло две сложности:

  1. Чтобы информация о новой статье появилась на странице, надо ее перезагрузить.
  2. Поля находятся постоянно на виду и занимают слишком много полезного пространства. 

Обе эти проблемы можно быстро решить парой строчек кода и сделать страницу намного симпатичнее, а ее поведение – логичнее.

Обновляем данные на странице

Поведение нашей страницы в текущий момент очевидно. Подгрузка данных из БД происходит лишь при загрузке страницы, поэтому даже после обновления наш сайт остается прежним до тех пор, пока мы не нажмем Ctrl+R или не зайдем на страницу повторно. Но с точки зрения пользователя такое поведение нелогично и нам нужно его исправить. Есть много способов, но мы выберем простую хитрость, которая вдобавок ко всему покажет, как в Svelte работает функция dispatch

Импорт диспетчера Svelte
  • Сначала нужно добавить в наш блок скрипт импорт пакета createEventDispatcher прямо из Svelte: 
import { createEventDispatcher } from 'svelte
  • А потом создать константу dispatch, которая будет непосредственно отвечать на запуск функции createEventDispatcher
const dispatch = createEventDispatcher()
  • Теперь у нас есть доступ к этой функции, но воспользуемся мы ею чуть позже. Для начала нужно скорректировать содержимое addPost.js:
Отредактированная функция добавления данных в БД

Мы изменили тут две вещи: переименовали переменные postName и postBody в title и bodyсоответственно, а также изменили названия переменных в функции dbData.run().

Также в return добавили переименованные переменные. Это необходимо для импорта новых объектов с обновленным данными в компонент Post, который используется для рендеринга постов на странице.

Теперь сделаем свой диспетчер. Сама функция необходима, чтобы передавать данные из одного компонента в другой, т.е. из компонента-наследника компоненту-родителю. Наша задача сейчас передать родителю (index.svelte) информацию о том, что мы добавили новую статью и объект с этой статьей. А родительский компонент отреагирует на эти изменения и отрисует новый пост на главной странице нашего блога.

Дополненная функция добавления постов
  • Мы добавили в нашу функцию addPost переменную reposponseOfDataToAdd, чтобы хранить ответ от промиса (асинхронного запроса) в addPost.js:
const responseOfDataToAdd = await dataToAdd.json()
  • Этот ответ мы и будем передавать родителю. Создаем новый диспетчер, помечаем в нем событие как newPost и через запятую прописываем передаваемый контент: 
dispatch('newPost', {reponse: responseOfDataToAdd})
  • А вот сейчас можно вернуться в родительский компонент и там обработать диспетчинг. 
Настройка реакции компонента на диспетчинг
  • Добавляем реакцию на изменения в NewPost (то есть появление диспетчера), добавив наше кастомное событие newPost, и привяжем к этому событию запуск функции reloadPage:
on:newPost={reloadPage}
Функция обновления данных на странице
  • А функция reload.page выглядит вот так просто:
const reloadPage = (event) => {}
  • Мы передаем в качестве аргумента событие, которое произошло в компоненте-наследнике. А событие, в свою очередь, несет в себе все данные, что были прописаны в функции dispatch:
data.posts.push(event.detail.response)
  • Добавляем в массив с постами объекта новый пост, переданный диспетчером. Заметьте, мы обращаемся к response, указанный нами же в функции dispatch.
data.posts = data.posts

Присвоение переменной с постами самой себя запускает ререндеринг страницы. Срабатывает реактивная сущность Svelte и мы сразу видим изменения на экране. 

Прячем инпуты под кнопку

Здесь придется потратить чуть больше времени. Мы будем создавать отдельные классы для двух видов формы, расширять конфигурационный файл TailwindCSS и осваивать директиву class в Svelte.

Трудно выбрать, с чего именно начать, потому что все задачи тесно связаны. Попробуем со стилей.

  • Открываем файл app.css (он отвечает за выгрузку Tailwind-классов в наше приложение). 
  • И добавляем в него два новых класса, как вы добавляли бы классы в CSS (если знаете о них).
  • В классе .form пишем @apply, а потом просто копируем мои стили:
@apply absolute flex flex-col w-[90%] h-[360px] m-5 p-5 bg-white shadow-md rounded-lg border animate-expand overflow-hidden
  • В классе .form-hidden пишем: 
@apply hidden
Классы с новыми стилями для формы

Кто-то мог заметить свойство animate-expand в классе .form. В TailwindCSS такого не существует, мы его создадим сами. 

Конфигурационный файл tailwind
  • Открываем файл tailwind.config.cjs, который лежит в корне проекта. 
  • В тело объекта extend вписываем объект animation, а в него свойства анимации. В нашем случае это длительность анимации и ее тип: 
expand: 'expand 0.2s linear',
  • Потом добавляем туда же keyframes, а внутрь keyframes добавляем объект expand (по названию анимации), а внутрь прописываем свойства анимации: 
expand: {'0%': { width: '0%', height: '0px', transform: 'translateY(-20px)', filter: 'blur(2px)' }, '50%': { width: '92%', height: '362px' }, '100%': { width: '90%', height: '360px' },},

Анимация готова. Но чтобы она сработала, надо привязать появление анимации к какому-то событию. В коде нашего приложения и так уже указано, что анимация срабатывает на объектах с классом form при их появлении в поле видимости. 

Осталось настроить смену тех самых классов. Это делается через директиву class в Svelte. С помощью нее можно менять классы в зависимости от значения переменных, указанных в блоке <script>. В нашем коде директива class для <form> настроена так:

{inputShown ? 'form' : form-hidden'} 

Если значение переменной inputShown равно true, то применяется класс form, а если inputShown равен false, то будет применен класс form-hidden.

Директива class в Svelte

Если вы не понимаете, с чего вдруг простая запись inputShown равняется true или false, я поясню. Любое положительное значение в JavaScript считается true. А отрицательные (undefined, null) автоматически признаются false.

Теперь сделаем кнопку, которая будет менять значение inputShown, а вместе с ней и отображение формы с полями для добавления статей. Привяжем к ней действие: 

on:click={toggleInputVisibility} 

И зададим дизайн на свой вкус.

Кнопка со стилями Tailwind

А вот так выглядит содержимое функции toggleInputVisibility:

Функция отображения интерфейса для добавления постов
  • Сначала объявляем саму функцию и передаем в нее событие, которое ее вызвало: 
const toggleInputVisibility = (event) => {}
  • Проводим проверку, существует ли event (передало ли событие команду или какой-то другой сценарий на странице), и если результат будет true, то создаем переменную для кнопки, активировавшей функцию:
if (event) { button = event.target }
  • А теперь создаем switch-структуру, которая будет проверять значение переменной inputShown и менять его при необходимости. Если true, то ставим false  если false, то ставим true.

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

Единственная проблема, оставшаяся в нашем приложении – форма не исчезает даже при добавлении постов. То есть мы нажимаем на кнопку, а форма остается. Это непредсказуемое поведение, которое нужно исправить. 

Дополненная функция добавления статей в блог

Поэтому в функцию addPost мы добавим еще и инициализацию функции: 

toggleInputVisibility()

Также мы обнулим значения переменных postName и postBody, чтобы при повторной попытке добавить статью они были пустыми, а не хранили тот текст, что мы набирали в предыдущий раз. Для этого достаточно переменные переназначить на пустые строки. 

Вместо заключения

На этом все. Вот такая у нас получилась форма для добавления статей. Посты сразу попадают в базу данных и вовремя отображаются на странице. Фактически это уже похоже на блог. 

Форма для добавления записей в блог

В следующей статье мы будем разбирать удаление статей и деплой сего чуда в Netlify и на серверы Timeweb. А после этого снова продолжим модифицировать наше приложение. Добавим туда что-то в духе тегов или категорий, научимся подписывать авторов, создадим отдельные страницы для статей и подключим сторонний markdown-редактор с пагинатором. В общем, впереди еще куча работы.

P.S. Не забудьте сделать коммит!

Related Posts
AllEscortAllEscort