Skip to content

Commit

Permalink
Merge pull request #8 from SergeyKazarinov/routing
Browse files Browse the repository at this point in the history
Routing
  • Loading branch information
SergeyKazarinov authored Jun 19, 2024
2 parents 78a6bfa + 6b140e6 commit 9b5710d
Show file tree
Hide file tree
Showing 21 changed files with 383 additions and 50 deletions.
8 changes: 8 additions & 0 deletions docs/developments.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,3 +49,11 @@
- Реализовано добавление стилей для ячеек в соответствии с тулбаром
- Реализовано изменение заголовка таблицы
- Реализован [debounce](/src/helpers/debounce.ts) для оптимизации

#### Routing

- Реализован [роутинг](../structures/routing.dio) приложения:
- Создание новых таблиц с добавлением query-params
- Переход на существующие таблицы
- Выход в главное меню
- Удаление таблицы
52 changes: 52 additions & 0 deletions src/components/dashboard/dashboard.functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import localStorageFn from '@src/helpers/localStorage';
import { IRootState } from '@src/store/store.types';

export const toHTML = (key: string) => {
const model = localStorageFn<IRootState>(key);
const id = key.split(':')[1];

if (!model) {
return '';
}

return /* html */ `
<li class="dashboard__record">
<a href="#excel/${id}" class="dashboard__link"> ${model.title} </a>
<strong class="dashboard__create-date">
${new Date(model.dateTable).toLocaleDateString()} ${new Date(model.dateTable).toLocaleTimeString()}
</strong>
</li>
`;
};

export const getAllKeys = () => {
const keys = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key?.includes('excel')) {
keys.push(key);
}
}
return keys;
};

export const createTable = () => {
const keys = getAllKeys();

if (!keys.length) {
return `
<p>Таблицы отсутствуют</p>
`;
}

return `
<div class="dashboard__list-header">
<span>Название</span>
<span>Дата открытия</span>
</div>
<ul class="dashboard__list">
${keys.map(toHTML).join('')}
</ul>
`;
};
10 changes: 4 additions & 6 deletions src/components/excel/Excel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IExcelComponent } from '@src/core/excelComponent/ExcelComponent';
import Observer from '@src/core/observer/Observer';
import StoreSubscriber from '@src/core/storeSubscriber/StoreSubscriber';
import { TActions } from '@src/store/action.types';
import { updateDate } from '@src/store/actions';
import { IReturnCreateStore, IRootState } from '@src/store/store.types';

interface IExcelOptions<T> {
Expand All @@ -11,8 +12,6 @@ interface IExcelOptions<T> {
}

class Excel<T extends IExcelComponent> {
public $el;

public components: (new (...arg: any[]) => T)[];

public objectComponents: T[];
Expand All @@ -23,8 +22,7 @@ class Excel<T extends IExcelComponent> {

public subscriber: StoreSubscriber;

constructor(selector: string, options: IExcelOptions<T>) {
this.$el = $(selector);
constructor(options: IExcelOptions<T>) {
this.components = options.components || [];
this.objectComponents = [];
this.observer = new Observer();
Expand All @@ -51,8 +49,8 @@ class Excel<T extends IExcelComponent> {
return $root;
}

render() {
this.$el?.append(this.getRoot());
init() {
this.store.dispatch(updateDate());
this.subscriber.subscribeComponents(this.objectComponents);
this.objectComponents.forEach((component) => {
component.init();
Expand Down
1 change: 0 additions & 1 deletion src/components/formula/Formula.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ class Formula extends ExcelComponent implements IFormula {
this.$subscribe('table:select', ($cell: Dom) => {
const id = $cell.getId<false>();
const dataValue = $cell.attr('data-value');
console.log('dataValue', dataValue);
if (id) {
this.$dispatch(actions.changeTextActionCreator({ text: $cell.text(), id }));
}
Expand Down
35 changes: 27 additions & 8 deletions src/components/header/Header.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { defaultTitle } from '@src/consts/consts';
import { DEFAULT_TITLE } from '@src/consts/consts';
import $, { Dom } from '@src/core/dom/dom';
import ExcelComponent from '@src/core/excelComponent/ExcelComponent';
import ActiveRoute from '@src/core/routes/ActiveRoute';
import debounce from '@src/helpers/debounce';
import { changeTitle } from '@src/store/actions';
import { IComponentOptions } from '@src/types/components';
import { IInputEvent } from '@src/types/general';
import { IButtonEvent, IInputEvent } from '@src/types/general';

interface IHeader {}

Expand All @@ -14,7 +15,7 @@ class Header extends ExcelComponent implements IHeader {
constructor($root: Dom, options: IComponentOptions) {
super($root, {
name: 'Header',
listeners: ['input'],
listeners: ['input', 'click'],
...options,
});
}
Expand All @@ -24,17 +25,17 @@ class Header extends ExcelComponent implements IHeader {
}

toHTML(): string {
const { title = defaultTitle } = this.store.getState();
const { title = DEFAULT_TITLE } = this.store.getState();
return `
<header class="excel__header header">
<input type="text" class="header__input" value="${title}" spellcheck/>
<div>
<button class="header__button">
<span class="material-icons"> delete </span>
<button class="header__button" data-button="remove" >
<span class="material-icons" data-button="remove"> delete </span>
</button>
<button class="header__button">
<span class="material-icons"> logout </span>
<button class="header__button" data-button="exit">
<span class="material-icons" data-button="exit"> logout </span>
</button>
</div>
</header>
Expand All @@ -45,6 +46,24 @@ class Header extends ExcelComponent implements IHeader {
const $target = $(event.target);
this.$dispatch(changeTitle($target.text()));
}

onClick(event: IButtonEvent) {
const $target = $(event.target);

if ($target.data?.button === 'remove') {
// eslint-disable-next-line
const decision = confirm('Вы действительно хотите удалить таблицу?');

if (decision) {
localStorage.removeItem(`excel:${ActiveRoute.param}`);
ActiveRoute.navigate('');
}
}

if ($target.data?.button === 'exit') {
ActiveRoute.navigate('');
}
}
}

export default Header;
2 changes: 1 addition & 1 deletion src/consts/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ export const initialToolbarState: IToolbarState = {
fontStyle: 'normal',
};

export const defaultTitle = 'Новая таблица';
export const DEFAULT_TITLE = 'Новая таблица';
4 changes: 1 addition & 3 deletions src/core/dom/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,9 +239,7 @@ export class Dom implements IDom {
attr(name: string, value: string): this;
attr(name: string): string | undefined | null;
attr(name: string, value?: string): string | this | undefined | null {
console.log('value', value);
if (value) {
console.log('name', name);
if (typeof value === 'string') {
this.$el?.setAttribute(name, value);
return this;
}
Expand Down
29 changes: 29 additions & 0 deletions src/core/routes/ActiveRoute.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
class ActiveRoute {
/**
* Геттер получения текущего адреса
*
* @static
* @readonly
* @type {string}
*/
static get path() {
return window.location.hash.slice(1);
}

/**
* Геттер получения параметра таблицы
*
* @static
* @readonly
* @type {string}
*/
static get param() {
return ActiveRoute.path.split('/')[1];
}

static navigate(path: string) {
window.location.hash = path;
}
}

export default ActiveRoute;
30 changes: 30 additions & 0 deletions src/core/routes/Page.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Dom } from '../dom/dom';

interface IPage {
getRoot(): Dom;
afterRender(): void;
destroy(): void;
}

class Page implements IPage {
protected params: string | undefined;

constructor(params?: string) {
this.params = params;
}

/**
* Метод выбрасывает ошибку, если не реализован в классах наследников
*
* @returns {Element}
*/
getRoot(): Dom {
throw new Error('error page');
}

afterRender(): void {}

destroy(): void {}
}

export default Page;
72 changes: 72 additions & 0 deletions src/core/routes/Router.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import DashboardPage from '@src/pages/DashboardPage';
import ExcelPage from '@src/pages/ExcelPage';
import $, { Dom } from '../dom/dom';
import ActiveRoute from './ActiveRoute';
import Page from './Page';

interface IRoutesParams {
dashboard: new (...arg: any[]) => DashboardPage;
excel: new (...arg: any[]) => ExcelPage;
}

interface IRouter {
/**
* Инициализация роутинга
* Добавление слушателя события на изменения hash
*/
init(): void;

/**
* Метод рендеринга страницы (компонента)
*/
changePageHandler(): void;
/**
* Метод при размонтировании
* Удаляет слушатели событий
*/
destroy(): void;
}

class Router implements IRouter {
private $placeholder: Dom;

private routes: IRoutesParams;

private page: null | Page;

constructor(selector: string, routes: IRoutesParams) {
if (!selector) {
throw new Error('Selector is not provided in Router');
}

this.$placeholder = $(selector);
this.routes = routes;
this.page = null;

this.changePageHandler = this.changePageHandler.bind(this);

this.init();
}

init() {
window.addEventListener('hashchange', this.changePageHandler);
this.changePageHandler();
}

changePageHandler() {
this.$placeholder.clear();
if (this.page) {
this.page.destroy();
}
const PageClass = ActiveRoute.path.includes('excel') ? this.routes.excel : this.routes.dashboard;
this.page = new PageClass(ActiveRoute.param);
this.$placeholder.append(this.page.getRoot());
this.page.afterRender();
}

destroy() {
window.removeEventListener('hashchange', this.changePageHandler);
}
}

export default Router;
2 changes: 1 addition & 1 deletion src/helpers/localStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
const localStorageFn = <R>(key: string, data?: unknown) => {
if (!data) {
const localStorageData = localStorage.getItem(key);
return localStorageData && (JSON.parse(localStorageData) as R);
return localStorageData ? (JSON.parse(localStorageData) as R) : undefined;
}

localStorage.setItem(key, JSON.stringify(data));
Expand Down
32 changes: 7 additions & 25 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,10 @@
import Excel from './components/excel/Excel';
import Formula from './components/formula/Formula';
import Header from './components/header/Header';
import Table from './components/table/Table';
import Toolbar from './components/toolbar/Toolbar';
import { EXCEL_STATE } from './consts/localStorage';
import debounce from './helpers/debounce';
import localStorageFn from './helpers/localStorage';
import Router from './core/routes/Router';
import DashboardPage from './pages/DashboardPage';
import ExcelPage from './pages/ExcelPage';
import './scss/index.scss';
import createStore from './store/createStore';
import { initialState } from './store/initialState';
import rootReducer from './store/rootReducer';

const store = createStore(rootReducer, initialState);

const stateListener = debounce(<S>(state: S) => {
console.info(state);
localStorageFn(EXCEL_STATE, state);
}, 500);

store.subscribe(stateListener);

const excel = new Excel<Header | Toolbar | Formula | Table>('#app', {
components: [Header, Toolbar, Formula, Table],
store,
// eslint-disable-next-line
new Router('#app', {
dashboard: DashboardPage,
excel: ExcelPage,
});

excel.render();
Loading

0 comments on commit 9b5710d

Please sign in to comment.