| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [View Raw Code] [Original HTTPS Page] |
Когда мы работали с питоном, то у нас был venv, где мы хранили все наши пакеты. На фронтенде для этого используется пакетный менеджер npm. С помощью него мы можем скачивать нужные нам пакеты, все скаченные пакеты храняться в папке node_modules, а вот информация о том, какие пакеты используются в приложении хранится в package.json, подробнее тут. Этот файл хранит в себе не только информацию о скаченных пакетах, но и название приложения, версию, описание, скрипты. package.json можно назвать файлом, в котором хранится сведение о проекте. Он как краткое описание проекта, по нему можно понять из чего состоит проект.
Когда мы пишем код на JS, то нам достаточно часто нужно обращаться к верстке, для этого у нас есть общирное API по работе с DOM деревом. У нас есть все нужны для нас инструменты, чтобы мы могли обратиться к нужному нам элементу и изменить его как мы хотим. Сегодня мы будем использовать getElementById и insertAdjacentHTML, но функций намного больше.
В предыдущей лабораторной работе мы создали наш сервер на Django REST Framework и написали API. Теперь попробуем написать сайт, который будет работать с нашим API. До этого мы использовали Django шаблоны, чтобы показать пользователю интерфейс и отобразить данные, но сейчас так сделать не получится. Когда мы использовали Django шаблоны, то у нас сервер выступал в роли бекенда и фронтенда сразу, то есть в нем была реализовано бизнес логика по работе с данными (бекенд) и шаблонизатор для отдачи html (фронтенд). Сейчас же нас сервер выступает в роли бекенда, который нам просто отдает данные, нам нужно написать приложение, которое будет уметь получать эти данные и выводить пользователю. У нас будет 2 независимых приложения. Бекенд на Django REST и фронтенд на JS. Сегодня в примере будет работать с API по акциям, но немного модифицированном. Приступим к делу.
Для работы на фронтенде я бы посоветовал использовать IDE WebStrom. Это инструмент от команды JetBrains, которые создали PyCharm. Только PyCharm заточен для работы с питоном, а WebStrom для работы на фронтенде.
При инициализации проекта у нас будут спрашивать много вопросов, но их все можно пропустить нажав Enter. Если мы захотим, то это все можно поменять в package.json файле
В конце у нас появится настроенный package.json файл. Командой yes подтвержаем и созраняем его
У нас в директории появился package.json файл, мы создали проект
Во все проекты принято добавлять .gitignore файл, который не будет добавлять лишнее в наш git репозиторий. Подробнее о .gitignore можно почитать тут
Для добавления .gitignore файла просто скачаем его тут и поместим в нашу директорию
У нас есть проект состоящий из package.json и .gitignore файлов. Может приступать к написанию кода
Окей, мы создали проект, теперь давайте начнем его писать. Когда пользователь заходит на сайт, то ему сначала подгружается index.html файл с базовой версткой, а потом уже подгружаются стили и скрипты.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lab7</title>
</head>
<body>
<div>Hello world!</div>
</body>
</html>Если мы откроем html файл, то увидим нашу страницу
Круто, у нас есть базовый html файл и мы можем его открыть, но каждый раз открывать html файл не круто, вот бы можно было написать какой-то сервер, который бы нам отдавал наши файлы и мы могли как в Django зайти на какой-то урл и увидеть наш сайт. Для этого можно использовать простой сервер express который будет отдавать нам файлы
Устанавливаем express через команду npm install express --save-dev
Как мы видим у нас добавился пакет в package.json и добавилась папка node_modules, где хранится наш пакет
'use strict';
const express = require('express');
const path = require('path');
const app = express();
const htmlPath = path.resolve(__dirname, '..', 'src/index.html')
const staticPath = path.resolve(__dirname, '..', 'src')
const port = 3000;
app.use('/', express.static(staticPath));
app.all('*', (req, res) => {
res.sendFile(htmlPath);
});
app.listen(port, () => {
console.log(`Server listening http://localhost:${port}`);
});Для того, чтобы было проще верстать я буду использовать bootstrap. Это библиотека стилей, в которой можно брать верстку и применять у себя на сайте
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lab7</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css">
</head>
<body>
<div>Hello world!</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lab7</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css">
</head>
<body>
<button type="button" class="btn btn-primary">Hello world!</button>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>Как мы видим наша кнопка видна, значит мы все подключили успешно и можно переходить к написанию JS
Теперь попробуем нарисовать нашу кнопку с помощью JS. Для того, чтобы в JS получить доступ к нашему html у нас должен быть какой-то корневой элемент который будет нашим родителем и к которому мы будем добавлять остальные элементы.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lab7</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css">
</head>
<body>
<div id="root"></div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>Теперь у нас есть корневой элемент к которому мы можем обратиться из JS. Создадим js, подключим его и попробуем обратиться к html.
const root = document.getElementById('root');<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Lab7</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css">
</head>
<body>
<div id="root"></div>
<script src="main.js" type="module"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>const root = document.getElementById('root');
root.insertAdjacentHTML('beforeend', '<button type="button" class="btn btn-primary">Hello world 2!</button>')Как мы видим наша кнопка видна, значит мы правильно написали наш JS. Теперь попробуем сделать что-то посложнее.
Мы смогли написать простую страничку на JS, но мы же не будем вечно писать все в одном файле. Нам нужно как-то разбивать наш проект по файлам.
Сейчас мы имеем следующее разбиение по файлам:
├── package-lock.json
├── package.json
├── server
│ └── index.js
└── src
├── index.html
└── main.jsВ фронтенде верстку разделяют на страницы (Pages) и компоненты (Components). Страницы - это отдельная страница, например наша главная страница. Компоненты - маленькие блоки из которых состоит страница.
Добавим папки:
Теперь наша структура выглдит так:
├── package-lock.json
├── package.json
├── server
│ └── index.js
└── src
├── components
├── modules
├── pages
├── index.html
└── main.js
Теперь попробуем переписать нашу страницу под нашу новую архитуктуру
export class MainPage {
}export class MainPage {
constructor(parent) {
this.parent = parent;
}
}export class MainPage {
constructor(parent) {
this.parent = parent;
}
render() {
}
}render() {
this.parent.insertAdjacentHTML('beforeend', '<button type="button" class="btn btn-primary">Hello world 3!</button>')
}import {MainPage} from "./pages/main/MainPage.js";
const root = document.getElementById('root');
const mainPage = new MainPage(root)
mainPage.render()Проверяем, все работает, но мы сказали что у нас страница состоит из мелких компонентов, а сейчас верстка кнопки происходит на странице. Вынесем кнопку в компонент и добавим ее на странице.
export class ButtonComponent {
constructor(parent) {
this.parent = parent;
}
render() {
this.parent.insertAdjacentHTML('beforeend', '<button type="button" class="btn btn-primary">Hello world 4!</button>')
}
}render() {
const button = new ButtonComponent(this.parent)
button.render()
}Отлично, у нас все работает. Теперь сделаем нашу страницу такой, чтобы она была готова с нашим API
Теперь модифицируем нашу страницу так, чтобы она была готова для отображения данных, которые отдает наше API. Для отображения одной акции я буду использовать карточки. Создадим карточку для наших акций
export class StockCardComponent {
constructor(parent) {
this.parent = parent;
}
render() {
}
}getHTML() {
return (
`
<div class="card" style="width: 300px;">
<img class="card-img-top" src="https://i.pinimg.com/originals/c9/ea/65/c9ea654eb3a7398b1f702c758c1c4206.jpg" alt="картинка">
<div class="card-body">
<h5 class="card-title">Акция</h5>
<p class="card-text">Вот тут информация об акции</p>
<button class="btn btn-primary"">Нажми на меня</button>
</div>
</div>
`
)
}
render() {
const html = this.getHTML()
this.parent.insertAdjacentHTML('beforeend', html)
}render() {
const stockCard = new StockCardComponent(this.parent)
stockCard.render()
}Отлично, у нас все работает, но нам бы хотелось прокидывать данные в компонент, а то сейчас у нас данные захардкожены в компоненте.
getHTML(data) {
return (
`
<div class="card" style="width: 300px;">
<img class="card-img-top" src="${data.src}" alt="картинка">
<div class="card-body">
<h5 class="card-title">${data.title}</h5>
<p class="card-text">${data.text}</p>
<button class="btn btn-primary">Нажми на меня</button>
</div>
</div>
`
)
}
render(data) {
const html = this.getHTML(data)
this.parent.insertAdjacentHTML('beforeend', html)
}getData() {
return {
id: 1,
src: "https://i.pinimg.com/originals/c9/ea/65/c9ea654eb3a7398b1f702c758c1c4206.jpg",
title: "Акция",
text: "У меня есть крутая акция"
}
}
render() {
const data = this.getData()
const stockCard = new StockCardComponent(this.parent)
stockCard.render(data)
}Отлично, мы начились прокидывать данные в компонент, теперь нам бы хотелось отрисовать больше чем один компонент. У нас же по API может приходить список акция. Добавим на главной странице отрисовку списка.
get page() {
return document.getElementById('main-page')
}
getHTML() {
return (
`
<div id="main-page" class="d-flex flex-wrap"><div/>
`
)
}
render() {
this.parent.innerHTML = ''
const html = this.getHTML()
this.parent.insertAdjacentHTML('beforeend', html)
const data = this.getData()
const stockCard = new StockCardComponent(this.page)
stockCard.render(data)
}getData() {
return [
{
id: 1,
src: "https://i.pinimg.com/originals/c9/ea/65/c9ea654eb3a7398b1f702c758c1c4206.jpg",
title: "Акция",
text: "Такой акции вы еще не видели 1"
},
{
id: 2,
src: "https://i.pinimg.com/originals/c9/ea/65/c9ea654eb3a7398b1f702c758c1c4206.jpg",
title: "Акция",
text: "Такой акции вы еще не видели 2"
},
{
id: 3,
src: "https://i.pinimg.com/originals/c9/ea/65/c9ea654eb3a7398b1f702c758c1c4206.jpg",
title: "Акция",
text: "Такой акции вы еще не видели 3"
},
]
}
render() {
this.parent.innerHTML = ''
const html = this.getHTML()
this.parent.insertAdjacentHTML('beforeend', html)
const data = this.getData()
data.forEach((item) => {
const stockCard = new StockCardComponent(this.page)
stockCard.render(item)
})
}Отлично, мы смогли отрисовать сразу несколько компонентов, но если мы попробуем нажать на кноку, то ничего не произойдет. Добавим обработку нажатия на кнопку. Для этого нам нужно внутри компонента подписаться на событие клик по кнопке и обработать его. Фнукцию, которая будет срабатывать по клику будем прокидывать в компонент из страницы
<button class="btn btn-primary" id="click-card-${data.id}" data-id="${data.id}">Нажми на меня</button>addListeners(data, listener) {
document
.getElementById(`click-card-${data.id}`)
.addEventListener("click", listener)
}
render(data, listener) {
const html = this.getHTML(data)
this.parent.insertAdjacentHTML('beforeend', html)
this.addListeners(data, listener)
}clickCard(e) {
const cardId = e.target.dataset.id
}
const stockCard = new StockCardComponent(this.page)
stockCard.render(item, this.clickCard.bind(this))Теперь у нас есть все что нам нужно, осталось создать вторую страницу и отрисовать ее
Теперь сверстаем страницу акции под наше API
export class StockPage {
constructor(parent, id) {
this.parent = parent
this.id = id
}
getData() {
return {
id: 1,
src: "https://i.pinimg.com/originals/c9/ea/65/c9ea654eb3a7398b1f702c758c1c4206.jpg",
title: "Акция",
text: "Такой акции вы еще не видели"
}
}
get page() {
return document.getElementById('stock-page')
}
getHTML() {
return (
`
<div id="stock-page">
</div>
`
)
}
render() {
this.parent.innerHTML = ''
const html = this.getHTML()
this.parent.insertAdjacentHTML('beforeend', html)
}
}export class StockComponent {
constructor(parent) {
this.parent = parent
}
getHTML(data) {
return (
`
<div class="card mb-3" style="width: 540px;">
<div class="row g-0">
<div class="col-md-4">
<img src="${data.src}" class="img-fluid" alt="картинка">
</div>
<div class="col-md-8">
<div class="card-body">
<h5 class="card-title">${data.title}</h5>
<p class="card-text">${data.text}</p>
</div>
</div>
</div>
</div>
`
)
}
render(data) {
const html = this.getHTML(data)
this.parent.insertAdjacentHTML('beforeend', html)
}
} render() {
this.parent.innerHTML = ''
const html = this.getHTML()
this.parent.insertAdjacentHTML('beforeend', html)
const data = this.getData()
const stock = new StockComponent(this.page)
stock.render(data)
}Теперь у нас есть страница акции, сделаем так чтобы при нажатии на карточку на главной странице он открывалась страница акции
clickCard(e) {
const cardId = e.target.dataset.id
const stockPage = new StockPage(this.parent, cardId)
stockPage.render()
}Все работает, для удобства добавим кнопку, которая будет возвращать на главную
export class BackButtonComponent {
constructor(parent) {
this.parent = parent;
}
addListeners(listener) {
document
.getElementById("back-button")
.addEventListener("click", listener)
}
getHTML() {
return (
`
<button id="back-button" class="btn btn-primary" type="button">Назад</button>
`
)
}
render(listener) {
const html = this.getHTML()
this.parent.insertAdjacentHTML('beforeend', html)
this.addListeners(listener)
}
}render() {
this.parent.innerHTML = ''
const html = this.getHTML()
this.parent.insertAdjacentHTML('beforeend', html)
const backButton = new BackButtonComponent(this.page)
backButton.render(this.clickBack.bind(this))
const data = this.getData()
const stock = new StockComponent(this.page)
stock.render(data)
}clickBack() {
const mainPage = new MainPage(this.parent)
mainPage.render()
}Отлично, у нас есть кнопка, которая ведет назад на главную страницу. Теперь можно переходить к подключению нашего API
У нас есть приложение, но нам нужно его соединить с нашим бекендом, чтобы оно получало данные. Для этого мы будем делать fetch запросы, что это и как работает можно почитать тут
class Ajax {
async get(url) {
const response = await fetch(url, {
method: "GET"
});
const responseData = await response.json();
return {
status: response.status,
data: responseData
};
}
}
export const ajax = new Ajax();class Urls {
constructor() {
this.url = 'http://localhost:8000/';
}
stocks() {
return `${this.url}stocks/`
}
stock(id) {
return `${this.url}stocks/${id}/`
}
}
export const urls = new Urls()async getData() {
return ajax.get(urls.stocks())
}
async render() {
this.parent.innerHTML = ''
const html = this.getHTML()
this.parent.insertAdjacentHTML('beforeend', html)
const data = await this.getData()
data.data.forEach((item) => {
const stockCard = new StockCardComponent(this.page)
stockCard.render(item, this.clickCard.bind(this))
})
}async getData() {
return ajax.get(urls.stock(this.id))
}
async render() {
this.parent.innerHTML = ''
const html = this.getHTML()
this.parent.insertAdjacentHTML('beforeend', html)
const backButton = new BackButtonComponent(this.page)
backButton.render(this.clickBack.bind(this))
const data = this.getData()
const stock = new StockComponent(this.page)
stock.render(data.data)
}Теперь наше приложение работает с нашим API как мы и хотели, лаба готова. В конечном итоге наше приложение выглядит так:
├── package-lock.json
├── package.json
├── server
│ └── index.js
└── src
├── components
│ ├── back-button
│ │ └── BackButton.js
│ ├── stock
│ │ └── StockComponent.js
│ └── stock-card
│ └── StockCardComponent.js
├── modules
│ ├── ajax.js
│ └── urls.js
├── pages
│ ├── main
│ │ └── MainPage.js
│ └── stock
│ └── StockPage.js
├── index.html
└── main.js| Back | FazBrowse Home | New Git URL |