HM-31. Добавлен роутинг для страниц (#9)

This commit is contained in:
Nikolay
2020-07-14 20:18:49 +03:00
committed by GitHub
parent 521a80c1e3
commit 4ab8b1e273
15 changed files with 197 additions and 52 deletions

View File

@ -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"
} }
} }

View File

@ -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">

View File

@ -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();
});

View 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;

View File

@ -0,0 +1,3 @@
import ApiPage from './ApiPage';
export default ApiPage;

View File

@ -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();
} }
} }

View 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;

View File

@ -0,0 +1,3 @@
import MainPage from './MainPage';
export default MainPage;

View File

@ -0,0 +1,3 @@
html, body, .PageContainer, .Page {
height: 100%;
}

View File

@ -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;

View File

@ -0,0 +1,5 @@
import RouterPagesContainer from './RouterPagesContainer';
const routerPagesContainer = new RouterPagesContainer();
export default routerPagesContainer;

View File

@ -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');

View 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;

View 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;

View File

@ -18,6 +18,7 @@ module.exports = (env, argv) => ({
devServer: { devServer: {
compress: true, compress: true,
port: 9000, port: 9000,
historyApiFallback: true,
}, },
optimization: { optimization: {
splitChunks: { splitChunks: {