diff --git a/src/components/component/Component.js b/src/components/component/Component.js index e59fdac..1d07493 100644 --- a/src/components/component/Component.js +++ b/src/components/component/Component.js @@ -12,6 +12,12 @@ class Component extends EmitService { */ _listeners; + /** + * Отображает текущее состояние компонента жив он или мертв + * @type {Boolean} - текущее состояние компонента + */ + _isAlive; + /** * Корневой элемент компонента * @type {Node} - корневой элемент компонента @@ -31,6 +37,7 @@ class Component extends EmitService { } this._listeners = []; this._events = {}; + this._isAlive = true; } /** @@ -51,16 +58,28 @@ class Component extends EmitService { this._listeners.forEach(({element, eventName, listener}) => { element.removeEventListener(eventName, listener); }); + this._listeners = []; + } + + /** + * Позволяет запускать рендер только в том случае, если компонент жив + * @param {Function} renderFunction - функция рендера + */ + render (renderFunction) { + if (this._isAlive) { + renderFunction(); + } } /** * Метод уничтожения компонента. Удаляет элемент из верстки, снимает обработчики и очищает подписки */ destroy = () => { + this._isAlive = false; this.clearListeners(); - this.mainNode.remove(); - this._listeners = []; this.clearSubscribes(); + this.clearEvents(); + this.mainNode.remove(); } } diff --git a/src/components/logs-page/LogsPage.js b/src/components/logs-page/LogsPage.js index dd2db11..d1f4a31 100644 --- a/src/components/logs-page/LogsPage.js +++ b/src/components/logs-page/LogsPage.js @@ -5,7 +5,7 @@ import Pagination from '../pagination'; import LogsFilters from '../logs-filters'; import routeService from '../../services/RouteService'; import {prepareToNumber} from '../../utils/urlUtils'; -import {LOG_TYPE, LOG_COLS} from '../../consts'; +import {LOG_TYPE, LOG_COLS, EVENTS} from '../../consts'; import {createElement, markText} from '../../utils/elementUtils'; const ELEMENTS_ON_PAGE = 15; @@ -36,16 +36,19 @@ class LogsPage extends Component { this.pagination = new Pagination(this.footer); - routeService.onChange(this.renderTable); + this.logList = { + [LOG_TYPE.SERVER]: [], + [LOG_TYPE.CLIENT]: [], + }; + + this.addSubscribe(routeService, EVENTS.ROUTE_SEARCH_CHANGE, this.renderTable); this.initPage(); } initPage = async () => { - this.logList = { - [LOG_TYPE.SERVER]: await storageLogsApi.requestServerLogs(), - [LOG_TYPE.CLIENT]: await storageLogsApi.requestClientLogs(), - }; + this.logList[LOG_TYPE.SERVER] = await storageLogsApi.requestServerLogs(); + this.logList[LOG_TYPE.CLIENT] = await storageLogsApi.requestClientLogs(); this.renderTable(); } @@ -84,14 +87,16 @@ class LogsPage extends Component { } renderTable = () => { - const {query} = routeService.getUrlData(); - const {tableType, ...omitQuery} = query; - this.table?.mainNode?.remove(); - this.table = this.tables[tableType]; - this.body.appendChild(this.table.mainNode); - const filteredRows = this.filterRows(this.logList[tableType], omitQuery); - const rows = this.cutPagginationPage(filteredRows, query.pageNumber); - this.table.render(rows); + this.render(() => { + const {query} = routeService.getUrlData(); + const {tableType, ...omitQuery} = query; + this.table?.mainNode?.remove(); + this.table = this.tables[tableType]; + this.body.appendChild(this.table.mainNode); + const filteredRows = this.filterRows(this.logList[tableType], omitQuery); + const rows = this.cutPagginationPage(filteredRows, query.pageNumber); + this.table.render(rows); + }); } } diff --git a/src/components/pagination/Pagination.js b/src/components/pagination/Pagination.js index 95426d6..d9e8c0b 100644 --- a/src/components/pagination/Pagination.js +++ b/src/components/pagination/Pagination.js @@ -2,12 +2,11 @@ import Component from '../component/index'; import routeService from '../../services/RouteService'; import {createElement} from '../../utils/elementUtils'; import {prepareToNumber} from '../../utils/urlUtils'; +import {EVENTS} from '../../consts'; const LEFT_ICON = '«'; const RIGHT_ICON = '»'; -const CHAGE_PAGE = 'changePage'; - class Pagination extends Component { buttons = []; @@ -19,9 +18,7 @@ class Pagination extends Component { this.container = this.mainNode.querySelector('.pagination'); - routeService.onChange(() => { - this.renderButtons(); - }); + this.addSubscribe(routeService, EVENTS.ROUTE_SEARCH_CHANGE, this.renderButtons); } renderOneButton = (text, isDisabled = false) => { @@ -99,12 +96,6 @@ class Pagination extends Component { } this.renderOneButton(RIGHT_ICON, this.currentPage >= this.countPages); - - this.next(CHAGE_PAGE, this.currentPage); - } - - onPageChange = (listener) => { - this.subscribe(CHAGE_PAGE, listener); } } diff --git a/src/components/router-pages-container/RouterPagesContainer.js b/src/components/router-pages-container/RouterPagesContainer.js index 273d4e7..a46328c 100644 --- a/src/components/router-pages-container/RouterPagesContainer.js +++ b/src/components/router-pages-container/RouterPagesContainer.js @@ -3,6 +3,7 @@ import routeService from '../../services/RouteService'; import NotFoundPage from '../not-found-page'; import './RouterPagesContainer.css'; +import {EVENTS} from '../../consts'; /** * @interface Route @@ -35,7 +36,7 @@ class RouterPagesContainer extends Component { constructor () { super('#page-container', document.body); - routeService.onChange(({url}) => { + this.addSubscribe(routeService, EVENTS.ROUTE_CHANGE, ({url}) => { // Если под указанный url нет pageComponent, то будет испольщована страница NotFound const {pageComponent: PageComponent = NotFoundPage} = this.routes.find((route) => { return route.url === url; @@ -52,7 +53,6 @@ class RouterPagesContainer extends Component { this.currentPage = new PageComponent('#page', this.mainNode); } }); - } /** diff --git a/src/consts.js b/src/consts.js index 4122460..9fb97ed 100644 --- a/src/consts.js +++ b/src/consts.js @@ -29,3 +29,8 @@ export const LOG_COLS = { [LOG_TYPE.SERVER]: SERVER_COLS, [LOG_TYPE.CLIENT]: CLIENT_COLS, }; + +export const EVENTS = { + ROUTE_CHANGE: 'routeChange', + ROUTE_SEARCH_CHANGE: 'routeSearchChange' +}; diff --git a/src/services/EmitService.js b/src/services/EmitService.js index 8032bb2..011a2b6 100644 --- a/src/services/EmitService.js +++ b/src/services/EmitService.js @@ -8,13 +8,19 @@ class EmitService { */ _events; + /** + * Подписки на события других Емитеров + */ + _subscribes; + constructor () { this._events = {}; + this._subscribes = []; } /** - * Метод подписки на события компонента - * @param {string} eventName - событие компонента, на которое будет реагировать обработчик + * Метод подписки на события текущего Емитера + * @param {string} eventName - событие Емитера, на которое будет реагировать обработчик * @param {Function} listener - обработчик события * @example * // Подписка на событие @@ -30,6 +36,16 @@ class EmitService { ]; } + /** + * Метод отписки от события текущего Емитера + * @param {string} eventName - событие Емитера, от которого нужно отписатся + * @param {Function} listener - обработчик события, нужно передать ту же функцию, что и при добавлении + */ + unsubscribe = (eventName, listener) => { + const listeners = this._events[eventName] || []; + this._events[eventName] = listeners.filter((innerListener) => innerListener !== listener); + } + /** * Метод генерирует событие * @param {string} eventName - событие, которое необходимо сгенерировать @@ -47,9 +63,30 @@ class EmitService { } /** - * Очищает все события и слушателей + * Подписка на событие другого Емитера + * @param {EventService} component - экземпляр Емитера на который нужно подписаться + * @param {string} eventName - событие на которое нужно подписаться + * @param {Function} listener - подписчик + */ + addSubscribe = (emiter, eventName, listener) => { + emiter.subscribe(eventName, listener); + this._subscribes.push({emiter, eventName, listener}); + } + + /** + * Очищает все подписки на другие Емитеры */ clearSubscribes = () => { + this._subscribes.forEach(({emiter, eventName, listener}) => { + emiter.unsubscribe(eventName, listener); + }); + this._subscribes = []; + } + + /** + * Очищает все события и слушателей + */ + clearEvents = () => { this._events = {}; } } diff --git a/src/services/RouteService.js b/src/services/RouteService.js index d4824f8..51ce18a 100644 --- a/src/services/RouteService.js +++ b/src/services/RouteService.js @@ -1,6 +1,7 @@ import EmitService from './EmitService'; import {parse} from 'querystring'; import {makeUrlWithQuery} from '../utils/urlUtils'; +import {EVENTS} from '../consts'; /** * @function RouterListener @@ -8,12 +9,6 @@ import {makeUrlWithQuery} from '../utils/urlUtils'; * @param {Object} query - объект ключ-значение из url */ -/** - * Константа для события изменения роута - * @private - */ -const ROUTE_CHANGE = 'routeChange'; - /** * Класс для работы с роутингом. Позволяет переходить по роутам и генерит событие изменения роута. */ @@ -28,7 +23,19 @@ class RouteService extends EmitService { super(); this.history = window.history; - this._events[ROUTE_CHANGE] = []; + this._events[EVENTS.ROUTE_CHANGE] = []; + } + + _nextUrl = (url, query = {}) => { + const prepareQuery = Object.entries(query) + .reduce((memo, [key, value]) => { + if (value) { + memo[key] = value; + } + return memo; + }, {}); + const urlWithQuery = makeUrlWithQuery(url, prepareQuery); + this.history.pushState({}, '', urlWithQuery); } /** @@ -47,7 +54,11 @@ class RouteService extends EmitService { * @private */ generateNext = () => { - this.next(ROUTE_CHANGE, this.getUrlData()); + this.next(EVENTS.ROUTE_CHANGE, this.getUrlData()); + } + + generatePushQuery = () => { + this.next(EVENTS.ROUTE_SEARCH_CHANGE, this.getUrlData()); } /** @@ -73,16 +84,8 @@ class RouteService extends EmitService { * }); * // Это создаст строку в url - site.ru/users?key=testApi&author=Petrov */ - goTo = (url, query = {}) => { - const prepareQuery = Object.entries(query) - .reduce((memo, [key, value]) => { - if (value) { - memo[key] = value; - } - return memo; - }, {}); - const urlWithQuery = makeUrlWithQuery(url, prepareQuery); - this.history.pushState({}, '', urlWithQuery); + goTo = (url, query) => { + this._nextUrl(url, query); this.generateNext(); } @@ -94,23 +97,11 @@ class RouteService extends EmitService { */ pushQuery = (newQuery, isClear = false) => { const {url, query} = this.getUrlData(); - this.goTo(url, { + this._nextUrl(url, { ...(isClear ? {} : query), ...newQuery, }); - } - - /** - * С помощью этого метода подписываемся на событие изменения роута. - * @param {RouterListener} listener - слушатель для события изменения роута - * @example - * // Подписка на изменение url - * _.onChange(({url, query}) => { - * ... - * }); - */ - onChange = (listener) => { - this.subscribe(ROUTE_CHANGE, listener); + this.generatePushQuery(); } }