Добавление постов - админ панель
Начать следует с добавления постов в блог. Для этого нам потребуется некая форма, которая будет принимать информацию от пользователя, обрабатывать ее и передавать на хранение в БД.
Все материалы по ES6
Добавление формы поста
Кнопка
На сайте Materialize возьмем кнопку.Копируем код кнопки и добавляем в самый конец файла index.html.
Все что внутри тега
ul удалим вместе с тегом. Это нам не нужно. Нам достаточно одной кнопки.
По ней сложно кликнуть, ничего не происходит. Поэтому надо поставит модальное окно.
Идем на сайт Materialize и там нам нужно посмотреть как работают модальные окна.
Берем
modal-trigger и добавляем к ссылке нашей кнопки и далее нам нужно указать название модального окна, которое нам нужно открыть. Мы это будем делать через data-target="название"
Модальное окно
Так же берем с сайта Materialize готовый код окна и вставляем после кнопки.Исправляем id - оно должно совпадать с значением
data-target="createForm".
Название поставим - "Создать новый пост", а вместо контента поставим поля ввода.
Файл index.html полностью
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
 <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
 <!-- Compiled and minified CSS -->
 <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"> 
 <!--Import Google Icon Font-->
 <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
 <title>FS-Blog</title>
   <!--[if IE]>
   <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
 <![endif]-->
</head>
<body>
 <div class="container center" style="padding-top: 50px;">
  <div class="row">
   <div class="col s12 m8 offset-m2 lg6 offset-3" id="posts">
    <div class="preloader-wrapper small active">
     <div class="spinner-layer spinner-green-only">
      <div class="circle-clipper left">
       <div class="circle"></div>
      </div><div class="gap-patch">
       <div class="circle"></div>
      </div><div class="circle-clipper right">
       <div class="circle"></div>
      </div>
     </div>
    </div>
   </div>
  </div>
 </div>
 <!-- button -->
 <div class="fixed-action-btn">
  <a class="btn-floating btn-large red modal-trigger" data-target="createForm">
   <i class="large material-icons">add</i>
  </a>
 </div>
   <!-- Modal Structure -->
  <div id="createForm" class="modal">
   <div class="modal-content">
    <h4>Создать новый пост</h4>
    <div class="input-field">
     <input id="title" type="text" class="validate" required>
     <label for="title">Название.</label>
    </div>
    <div class="input-field">
     <textarea id="text" class="materialize-textarea"></textarea>
     <label for="text">Название.</label>
    </div>
   </div>
    <div class="modal-footer">
      <a class="waves-effect waves-light btn" id="createPost">Создать пост.</a>
    </div>
  </div>
 <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
 <script src="index.js"></script>
</body>
</html>
Теперь нужно проинициализировать модальное окно
На нужно передать в глобальный объект Материалайза элемент, который нам нужно проинициализировать и после этого все должно работать.
M.Modal.init(elems, options);
Поэтому идем в скрипт - index.js
Уберем за одно и таймаут. Он нам уже не нужен.
Инициализируем модальное окно.
M.Modal.init(document.querySelector('.modal'))
внутри события загрузки всего документа.
Создаем глобальную переменную modal вверху файла и занесем в нее инстанс модального окна внизу, чтобы в последствии им можно было управлять.
Файл index.js
const card = post => {
 return `
    <div class="card z-depth-4">
         <div class="card-content">
           <span class="card-title">${post.title}</span>
           <p>${post.text}</p>
           <small>${post.date}</small>
         </div>
         <div class="card-action">
      <button class="btn btn-small red">
       <i class="material-icons">Delete</i>
      </button> 
         </div>
       </div>
 `
}
const BASE_URL = '/api/post'
let posts =[]
let modal;
class PostApi {
 static fetch(){
  return fetch(BASE_URL, {method: 'get'}).then(res=>res.json())
 }
}
document.addEventListener('DOMContentLoaded', () => {
 PostApi.fetch().then(backendPosts => {
  posts = backendPosts.concat()
  renderPosts(posts)  
 })
 modal = M.Modal.init(document.querySelector('.modal'))
})
function renderPosts(_posts=[]) {
 const $posts = document.querySelector('#posts')
 if(_posts.length > 0) {
  $posts.innerHTML = _posts.map(post=> card(post)).join()  
 } else {
  $posts.innerHTML = `<div class="center">Постов пока нет.</div>`
 }
}
Проверяем.
Придаем функциональность
Первое, что нам нужно сделать, это поставить прослушку события на кнопку в модальном окне. Когда мы кликаем по кнопке "Создать пост" мы будем вызывать функцию.Внутри основного события загрузки документа в файле index.js возьмем кнопку -
document.querySelector('#createPost').addEventListener('click', onCreatePost)
Функцию мы вызываем без круглых скобок - просто передаем название.
Создаем функцию
onCreatePost
В ней мы должны получит контент, который написан в инпутах. Посему, создадим переменные
$title = document.querySelector('#title')
 $text = document.querySelector('#text')
Проверим, что эти поля не пустые:
if ($title.value && $text.value)
Если они не пустые, то нам нужно отправить POST-запрос, который мы реализовывали на сервере.
Для этого создадим
date, которую мы хотим отправить на сервер. На сервере мы ожидаем увидеть два поля -  title и text с запросом.
Поэтому мы здесь создадим новую переменную -
 newPost
  const newPost = {
   title: $title.value 
   text: $text.value
  }
И теперь когда у нас есть данные которые мы принимаем на сервере (объект) нам нужно отправить запрос.
Для этого у нас есть вспомогательный класс
PostApi и давайте здесь же реализуем метод, который нам позволит создавать посты.
 static create(post) {
 }
Будет принимать объект поста.
Далее мы будем возвращать результат работы метода
fetch(BASE_URL) по BASE_URL и далее нам необходимо передать объект конфигурации {}
Во первых мы ожидаем, что будем отправлять метод POST, поэтому
 static create(post) {
  return fetch(BASE_URL, {
   method: 'post',
  })
 }
Далее нам необходимо указать
body (посмотрите на сервер - title: req.body.title, text: req.body.text, (14 и 15 строка файла routes/post.js)
Поэтому в методе fetch() мы так и пишем
body и далее нам необходимо сделать строкой - сериализовать объект  post
Сделаем так:
JSON.stringify(post)
Так же нам нужно здесь реализовать хедеры, чтобы сервер понимал, что работа идет с JSON-ом.
   hraders: {
    'Accept': 'application/json',
    'Content-Type':'application/json'
   }
Конфигурация запроса на этом закончена и после того, как пройдет метод
fetch мф хотим получить некоторый body. Для этого в методу .then(res => res.json()) делаем то, что обычно с запросом. Отдаем некоторый json.
Метод
create готов.
class PostApi {
 static fetch() {
  return fetch(BASE_URL, {method: 'get'}).then(res=>res.json())
 }
 static create(post) {
  return fetch(BASE_URL, {
   method: 'post',
   body: JSON.stringify(post),
   hraders: {
    'Accept': 'application/json',
    'Content-Type':'application/json'
   }
  }).then(res => res.json())
 }
}
Теперь у нас метод
create готов  и мы можем продолжить работу.
В функции
onCreatePost мы сформировали новый пост и далее можем обратиться к классу PostAPI
 и с помощью метода create() передать ему данные - newPost и далее, когда обработается запрос и сервер нам ответит, в метод  .then() мы  получим новый объект post.
Нам нужно отобразить его в списке всех постов. Для этого у нас есть локальная переменная
posts, гд хранятся все посты. Мы просто добавим его туда методом push()
И далее. чтобы отобразить новые посты, мы вызываем метод
renderPosts(posts) c массивом  posts.
Помимо того. что мы создали пост на сервере нам нужно сразу закрыть модальное окно -
modal.close()
И очистим значения инпутов -
$title.value = ''
  $text.value = ''
И обновим текстовые инпуты - Обратиться к библиотеке материолайз и вызвать метод
M.updateTextFields() - по новому отобразит текстовые инпуты.
Сейчас при попытке создания посты мы получим следующее сообщение:
Для правильной работы нашего сервера нам нужно добавить плагин, который будет за нас парсить входящие данные. Мы с клиента их сериализовали - превратили в строку и теперь нужно сделать так, чтобы сервер их принимал как обычный JS-объект.
Для этого нам потребуется пакет body-parser
Остановим выполнение сервера.
Установим пакет, который автоматически будет парсить за нас входящие данные.
npm install body-parser
Подключаем его в папке app.js где-нибудь вверху.
const bodyParser = require('body-parser')
Теперь нужно применить пакет к нашему серверу.
Например. пред тем, как мы определяем роуты мы можем написать такую конструкцию:
app.use(bodyParser.json())
Будет преобразовывать входящие параметры в json-объект.
app.js
const express = require('express')
const mongoose = require('mongoose')
const bodyParser = require('body-parser')
const path = require('path')
const postRouter = require('./routes/post')
const keys = require('./keys')
const port = process.env.PORT || 5000
const clientPath = path.join(__dirname, 'client')
mongoose.connect(keys.mongoURI)
   .then(() => console.log('MongoDB connected'))
   .catch(err => console.error(err)) 
const app = express()
app.use(bodyParser.json())
app.use('/api/post', postRouter)
app.use(express.static(clientPath))
app.listen(port,()=>{
 console.log(`Server run on ${port} port`)
})
Запускаем сервер
Добавляем пост:
Если обновить страницу, то мы все равно увиим данный пост, потому что он берется уже из БД. В консоли можно посмотреть данные поста
Если посмотреть в mLab в коллекциях то мы увидим данные нашего поста.
Теперь можно добавлять посты на страницу.
Мы реализовали функционал по фетчингу и добавлению постов. осталось добавить удаление поста и правильный вывод даты, а так же сохранить форматирование строк поста.
Реализация удаления поста.
Нас интересует клиентский скрипт - index.jsЗдесь есть функция, которая вызывается когда полностью загрузится контент DOM. В ней у нас уже есть прослушка событий на кнопке "Добавить пост". Создадим еще одну, которая позволит нам удалять определенные посты. В данном случае мы будем делегировать события и прослушку повесим на весь cписок постов (id="posts"), потому что на кнопку удаления повесить ее мы не можем, так как они все время меняются. Поэтому лучше делегировать события.
document.querySelector('#posts').addEventListener('click', onDeletePost)
и создаем данную функцию внизу файла.
В нее принимаем некоторый
event
Теперь нам нужно определить, что нам нужно что-то удалить. Так как сейчас событие
event будет срабатывать при клике по любому месту поста.
Нам нужно понять, что клик был именно по кнопке удаления поста.
Для этого мы можем в функции, которая генерирует нам html-карточки добавить на кнопку удаления поста специальный класс, напрмер
.js-remove
Теперь нужно в функции определить, есть ли у элемента по которому мы кликнули этот класс.
В конструкцию
if запишем: если у event.target в объекте classList есть класс .js-remove, то мы что-то делаем.
if (event.target.classList.contains('js-remove'))
далее спросим у пользователя через функцию
confirm желает ли он удалить этот пост.
const decision = confirm('Вы уверены, что хотите удалить пост?')
А далее если пользователь нажал (согласился), то нужно понять какой именно пост нужно удалить.
Нам для этого нужно получить id поста. id хранится в объекте
post._id. И зная что мы будем кликать именно по кнопке удаления, мы можем на кнопку добавить атрибут - data-id="${post._id}" и будем забирать именно этот атрибут.
const id = event.target.getAttribute('data-id')
Далее, зная нужный id мы можем отправить его на сервер.
Для этого нам нужно реализовать функцию удаления в классе
PostApi.
Там мы создадим новую функцию
static remove(id) в нее мы будем получать  id  и все что мы будем делать - возвращать функцию  fetch() по BASE_URL но к нему надо еще добавить id, поэтому В параметрах метод delete, потому что мы будем слушать именно его. и после этого получим результат. 
 static remove(id) {
   return fetch(`${BASE_URL}/${id}`, {
    method: 'delete'
   }).then(res => res.json())
 }
Теперь мы можем обратиться к классе PostApi взывать у него метод remove передать ему нужный id и как только сервер ответи успехом, мы в функции .then() удалим пост из списков (визуально)
function onDeletePost(event) {
 if (event.target.classList.contains('js-remove')) {
  const decision = confirm('Вы уверены, что хотите удалить пост?')
  if (decision) {
   const id = event.target.getAttribute('data-id')
   PostApi.remove(id).then(() => {
    const postIndex = post.findIndex(post => post._id === id)
    posts.splice(postIndex, 1)
    renderPosts(posts)
   })
  }
 }
}
Правильно указать дату поста и мультимтрочный текст
Текст.
Параграфу просто добавить инлайн стиль white-space:pre-line
Для нормального отображения даты нужно поле
${post.date} обернуть в конструктор ${new Date(post.date).toLocaleDateString()}
Метод приводит даты к локальному формату.
В итоге мы получили мини блог с адаптивным дизайном, возможностью сохранения в БД, удалением и созданием постов.
 







































