Translate

Поиск по этому блогу

суббота, 6 октября 2018 г.

Блог на NodeJS Express MongoDB ( III ).

Добавление постов - админ панель

Начать следует с добавления постов в блог. Для этого нам потребуется некая форма, которая будет принимать информацию от пользователя, обрабатывать ее и передавать на хранение в БД.



Все материалы по 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()}

Метод приводит даты к локальному формату.

В итоге мы получили мини блог с адаптивным дизайном, возможностью сохранения в БД, удалением и созданием постов.





                                                                                                                                                             

Комментариев нет:

Отправить комментарий



Хотите освоить самые современные методы написания React приложений? Надоели простые проекты? Нужны курсы, книги, руководства, индивидуальные занятия по React и не только? Хотите стать разработчиком полного цикла, освоить стек MERN, или вы только начинаете свой путь в программировании, и не знаете с чего начать, то пишите через форму связи, подписывайтесь на мой канал в Телеге, вступайте в группу на Facebook.Пишите мне - kolesnikovy70 почта gmail.com