HM-31. Добавлен роутинг для страниц (#9)
This commit is contained in:
@ -54,6 +54,7 @@
|
|||||||
"axios": "^0.19.2",
|
"axios": "^0.19.2",
|
||||||
"bootstrap": "^5.0.0-alpha1",
|
"bootstrap": "^5.0.0-alpha1",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
|
"query-string": "^6.13.1",
|
||||||
"uuid": "^8.2.0"
|
"uuid": "^8.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,15 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<!-- Шаблон контейнера страниц-->
|
||||||
|
<template id="page-container">
|
||||||
|
<div class="PageContainer"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Шаблон страниц -->
|
||||||
|
<template id="page">
|
||||||
|
<div class="Page"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- Шаблон Модального окна -->
|
<!-- Шаблон Модального окна -->
|
||||||
<template id="test-modal">
|
<template id="test-modal">
|
||||||
|
|||||||
28
src/app.js
28
src/app.js
@ -2,22 +2,16 @@ import './app.css';
|
|||||||
import 'bootstrap/dist/css/bootstrap.min.css';
|
import 'bootstrap/dist/css/bootstrap.min.css';
|
||||||
import 'bootstrap';
|
import 'bootstrap';
|
||||||
|
|
||||||
// ! TODO: 5-14 строчки удалить, после теста компонента
|
import routeService from './services/RouteService';
|
||||||
import TestModal from './components/test-modal';
|
import routerPagesContainer from './components/router-pages-container';
|
||||||
import TestButton from './components/test-button';
|
import ApiPage from './components/api-page';
|
||||||
import TableComponent from './components/table-component';
|
import MainPage from './components/main-page';
|
||||||
import storageApi from './api/StorageServiceAPI';
|
|
||||||
|
|
||||||
const testModal = new TestModal();
|
// Новые страницы обязательно добавляем тут
|
||||||
const testButton = new TestButton();
|
routerPagesContainer.addRoutes([
|
||||||
const initStorageListTable = async function () {
|
{url: '/', pageComponent: MainPage},
|
||||||
const list = await storageApi.request();
|
{url: '/api', pageComponent: ApiPage},
|
||||||
const storageListTable = new TableComponent();
|
]);
|
||||||
return storageListTable.render(list);
|
|
||||||
};
|
|
||||||
|
|
||||||
initStorageListTable();
|
// Этот метод генерит событие Route, чтобы все компоненты получили его после инициализации
|
||||||
|
routeService.init();
|
||||||
testButton.subscribe('click', () => {
|
|
||||||
testModal.show();
|
|
||||||
});
|
|
||||||
|
|||||||
19
src/components/api-page/ApiPage.js
Normal file
19
src/components/api-page/ApiPage.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import Component from '../component/index';
|
||||||
|
import TableComponent from '../table-component';
|
||||||
|
import storageApi from '../../api/StorageServiceAPI';
|
||||||
|
|
||||||
|
class ApiPage extends Component {
|
||||||
|
constructor (mainNodeSelector, parentNode) {
|
||||||
|
super(mainNodeSelector, parentNode);
|
||||||
|
|
||||||
|
const initStorageListTable = async () => {
|
||||||
|
const list = await storageApi.request();
|
||||||
|
const storageListTable = new TableComponent(this.mainNode);
|
||||||
|
return storageListTable.render(list);
|
||||||
|
};
|
||||||
|
|
||||||
|
initStorageListTable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ApiPage;
|
||||||
3
src/components/api-page/index.js
Normal file
3
src/components/api-page/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import ApiPage from './ApiPage';
|
||||||
|
|
||||||
|
export default ApiPage;
|
||||||
@ -1,21 +1,17 @@
|
|||||||
|
import EmitService from '../../services/EmitService';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Класс для создания компонентов приложения. Необходим для наследования.
|
* Класс для создания компонентов приложения. Необходим для наследования.
|
||||||
* @param {string} mainNodeSelector - селектор, с помощью которого извлекается шаблон компонента
|
* @param {string} mainNodeSelector - селектор, с помощью которого извлекается шаблон компонента
|
||||||
* @param {Node} parentNode - родительский Node, в который следует положить созданный элемент
|
* @param {Node} parentNode - родительский Node, в который следует положить созданный элемент
|
||||||
*/
|
*/
|
||||||
class Component {
|
class Component extends EmitService {
|
||||||
/**
|
/**
|
||||||
* Список слушателей компонента
|
* Список слушателей компонента
|
||||||
* @type {{element: Node, eventName: string, listener: Function}[]}
|
* @type {{element: Node, eventName: string, listener: Function}[]}
|
||||||
*/
|
*/
|
||||||
_listeners;
|
_listeners;
|
||||||
|
|
||||||
/**
|
|
||||||
* События компонента
|
|
||||||
* @type {Object<string, Function[]>}
|
|
||||||
*/
|
|
||||||
_events;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Корневой элемент компонента
|
* Корневой элемент компонента
|
||||||
* @type {Node} - корневой элемент компонента
|
* @type {Node} - корневой элемент компонента
|
||||||
@ -23,6 +19,7 @@ class Component {
|
|||||||
mainNode;
|
mainNode;
|
||||||
|
|
||||||
constructor (mainNodeSelector, parentNode) {
|
constructor (mainNodeSelector, parentNode) {
|
||||||
|
super();
|
||||||
const content = document.querySelector(mainNodeSelector).content;
|
const content = document.querySelector(mainNodeSelector).content;
|
||||||
if (content.children.length > 1) {
|
if (content.children.length > 1) {
|
||||||
const message = '<template> должен содержать только один элемент children';
|
const message = '<template> должен содержать только один элемент children';
|
||||||
@ -45,31 +42,6 @@ class Component {
|
|||||||
this._listeners.push({element, eventName, listener});
|
this._listeners.push({element, eventName, listener});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Метод подписки на события компонента
|
|
||||||
* @param {string} eventName - событие компонента, на которое будет реагировать обработчик
|
|
||||||
* @param {Function} listener - обработчик события
|
|
||||||
*/
|
|
||||||
subscribe = (eventName, listener) => {
|
|
||||||
const listeners = this._events[eventName] || [];
|
|
||||||
this._events[eventName] = [
|
|
||||||
...listeners,
|
|
||||||
listener,
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Метод генерирует событие
|
|
||||||
* @param {string} eventName - событие, которое необходимо сгенерировать
|
|
||||||
* @param {unknown[]} args - аругемнты, который необходимо передать обработчикам события
|
|
||||||
*/
|
|
||||||
next = (eventName, ...args) => {
|
|
||||||
const listeners = this._events[eventName];
|
|
||||||
listeners.forEach((listener) => {
|
|
||||||
listener(...args);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Метод уничтожения компонента. Удаляет элемент из верстки, снимает обработчики и очищает подписки
|
* Метод уничтожения компонента. Удаляет элемент из верстки, снимает обработчики и очищает подписки
|
||||||
*/
|
*/
|
||||||
@ -79,7 +51,7 @@ class Component {
|
|||||||
});
|
});
|
||||||
this.mainNode.remove();
|
this.mainNode.remove();
|
||||||
this._listeners = [];
|
this._listeners = [];
|
||||||
this._events = {};
|
this.clearSubscribes();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
src/components/main-page/MainPage.js
Normal file
11
src/components/main-page/MainPage.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import Component from '../component/index';
|
||||||
|
|
||||||
|
class MainPage extends Component {
|
||||||
|
constructor (mainNodeSelector, parentNode) {
|
||||||
|
super(mainNodeSelector, parentNode);
|
||||||
|
|
||||||
|
this.mainNode.textContent = 'Главная страница';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MainPage;
|
||||||
3
src/components/main-page/index.js
Normal file
3
src/components/main-page/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import MainPage from './MainPage';
|
||||||
|
|
||||||
|
export default MainPage;
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
html, body, .PageContainer, .Page {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
@ -0,0 +1,35 @@
|
|||||||
|
import Component from '../component/index';
|
||||||
|
import routeService from '../../services/RouteService';
|
||||||
|
|
||||||
|
import './RouterPagesContainer.css';
|
||||||
|
|
||||||
|
class RouterPagesContainer extends Component {
|
||||||
|
routes = [];
|
||||||
|
|
||||||
|
currentPage;
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
super('#page-container', document.body);
|
||||||
|
|
||||||
|
routeService.onChange(({url}) => {
|
||||||
|
const {pageComponent: PageComponent} = this.routes.find((route) => {
|
||||||
|
return route.url === url;
|
||||||
|
}) || {};
|
||||||
|
|
||||||
|
if (this.currentPage) {
|
||||||
|
this.currentPage.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PageComponent) {
|
||||||
|
this.currentPage = new PageComponent('#page', this.mainNode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
addRoutes = (routes) => {
|
||||||
|
this.routes = this.routes.concat(routes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RouterPagesContainer;
|
||||||
5
src/components/router-pages-container/index.js
Normal file
5
src/components/router-pages-container/index.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import RouterPagesContainer from './RouterPagesContainer';
|
||||||
|
|
||||||
|
const routerPagesContainer = new RouterPagesContainer();
|
||||||
|
|
||||||
|
export default routerPagesContainer;
|
||||||
@ -5,8 +5,8 @@ import TableColumnComponent from '../table-column-component/index';
|
|||||||
import TableRowComponent from '../table-row-component/TableRowComponent';
|
import TableRowComponent from '../table-row-component/TableRowComponent';
|
||||||
|
|
||||||
class TableComponent extends Component {
|
class TableComponent extends Component {
|
||||||
constructor () {
|
constructor (parentNode) {
|
||||||
super('#main-table', document.body);
|
super('#main-table', parentNode);
|
||||||
|
|
||||||
this.tableHead = this.mainNode.querySelector('.Table__head');
|
this.tableHead = this.mainNode.querySelector('.Table__head');
|
||||||
this.tableBody = this.mainNode.querySelector('.Table__body');
|
this.tableBody = this.mainNode.querySelector('.Table__body');
|
||||||
|
|||||||
48
src/services/EmitService.js
Normal file
48
src/services/EmitService.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Класс для создвания объектов с генерацией событий
|
||||||
|
*/
|
||||||
|
class EmitService {
|
||||||
|
/**
|
||||||
|
* События компонента
|
||||||
|
* @type {Object<string, Function[]>}
|
||||||
|
*/
|
||||||
|
_events;
|
||||||
|
|
||||||
|
constructor () {
|
||||||
|
this._events = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Метод подписки на события компонента
|
||||||
|
* @param {string} eventName - событие компонента, на которое будет реагировать обработчик
|
||||||
|
* @param {Function} listener - обработчик события
|
||||||
|
*/
|
||||||
|
subscribe = (eventName, listener) => {
|
||||||
|
const listeners = this._events[eventName] || [];
|
||||||
|
this._events[eventName] = [
|
||||||
|
...listeners,
|
||||||
|
listener,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Метод генерирует событие
|
||||||
|
* @param {string} eventName - событие, которое необходимо сгенерировать
|
||||||
|
* @param {unknown[]} args - аругемнты, который необходимо передать обработчикам события
|
||||||
|
*/
|
||||||
|
next = (eventName, ...args) => {
|
||||||
|
const listeners = this._events[eventName];
|
||||||
|
listeners.forEach((listener) => {
|
||||||
|
listener(...args);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Очищает все события и слушателей
|
||||||
|
*/
|
||||||
|
clearSubscribes = () => {
|
||||||
|
this._events = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EmitService;
|
||||||
41
src/services/RouteService.js
Normal file
41
src/services/RouteService.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import EmitService from './EmitService';
|
||||||
|
import {parse} from 'querystring';
|
||||||
|
|
||||||
|
const ROUTE_CHANGE = 'routeChange';
|
||||||
|
|
||||||
|
class RouteService extends EmitService {
|
||||||
|
constructor () {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.history = window.history;
|
||||||
|
this._events[ROUTE_CHANGE] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getUrlData = () => {
|
||||||
|
return {
|
||||||
|
url: location.pathname,
|
||||||
|
query: parse(location.search.slice(1)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
generateNext = () => {
|
||||||
|
this.next(ROUTE_CHANGE, this.getUrlData());
|
||||||
|
}
|
||||||
|
|
||||||
|
init = () => {
|
||||||
|
this.generateNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
goTo = (url) => {
|
||||||
|
this.history.pushState({}, '', url);
|
||||||
|
this.generateNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange = (listener) => {
|
||||||
|
this.subscribe(ROUTE_CHANGE, listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const routeService = new RouteService();
|
||||||
|
|
||||||
|
export default routeService;
|
||||||
@ -18,6 +18,7 @@ module.exports = (env, argv) => ({
|
|||||||
devServer: {
|
devServer: {
|
||||||
compress: true,
|
compress: true,
|
||||||
port: 9000,
|
port: 9000,
|
||||||
|
historyApiFallback: true,
|
||||||
},
|
},
|
||||||
optimization: {
|
optimization: {
|
||||||
splitChunks: {
|
splitChunks: {
|
||||||
|
|||||||
Reference in New Issue
Block a user