Skip to content

Commit

Permalink
fix: handle popstate event
Browse files Browse the repository at this point in the history
  • Loading branch information
Kleostro committed May 13, 2024
1 parent 81af873 commit 9ca0d26
Show file tree
Hide file tree
Showing 14 changed files with 75 additions and 67 deletions.
2 changes: 1 addition & 1 deletion src/app/App/model/AppModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class AppModel {
},
[PAGE_ID.BLOG]: async (): Promise<Page> => {
const { default: PostListModel } = await import('@/pages/Blog/PostList/model/PostListModel.ts');
return new PostListModel(this.appView.getHTML(), this.router);
return new PostListModel(this.appView.getHTML());
},
[PAGE_ID.CART_PAGE]: async (): Promise<Page> => {
const { default: CartPageModel } = await import('@/pages/CartPage/model/CartPageModel.ts');
Expand Down
47 changes: 29 additions & 18 deletions src/app/Router/model/RouterModel.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,59 @@
import type { Page } from '@/shared/types/common.ts';

import { PAGE_ID } from '@/shared/constants/pages.ts';
import { isValidPath, isValidState } from '@/shared/types/validation/paths.ts';

const DEFAULT_SEGMENT = import.meta.env.VITE_APP_DEFAULT_SEGMENT;
const NEXT_SEGMENT = import.meta.env.VITE_APP_NEXT_SEGMENT;
const PATH_SEGMENTS_TO_KEEP = import.meta.env.VITE_APP_PATH_SEGMENTS_TO_KEEP;
const PROJECT_TITLE = import.meta.env.VITE_APP_PROJECT_TITLE;

class RouterModel {
private routes: Map<string, () => Promise<Page>> = new Map();

constructor() {
document.addEventListener('DOMContentLoaded', async () => {
const currentPath = window.location.pathname
.split(DEFAULT_SEGMENT)
.slice(PATH_SEGMENTS_TO_KEEP + NEXT_SEGMENT)
.join(DEFAULT_SEGMENT);
const currentPath = window.location.pathname.slice(1) || PAGE_ID.DEFAULT_PAGE;
await this.navigateTo(currentPath);
});

window.addEventListener('popstate', async (event) => {
const currentPath: unknown = event.state;

if (!isValidState(currentPath) || !isValidPath(currentPath.path)) {
window.location.pathname = PAGE_ID.DEFAULT_PAGE;
return;
}

await this.handleRequest(currentPath.path);
});
}

private changeAppTitle(path: string, hasRoute: boolean): void {
const title = `${PROJECT_TITLE} | ${hasRoute ? path : PAGE_ID.NOT_FOUND_PAGE}`;
document.title = title;
}

public async navigateTo(path: string): Promise<void> {
const pathnameApp = window.location.pathname
.split(DEFAULT_SEGMENT)
.slice(NEXT_SEGMENT, PATH_SEGMENTS_TO_KEEP + NEXT_SEGMENT)
.join(DEFAULT_SEGMENT);
const url = `${pathnameApp}/${path}`;
private async handleRequest(path: string): Promise<void> {
const hasRoute = this.routes.has(path);
this.changeAppTitle(path === PAGE_ID.DEFAULT_PAGE ? PAGE_ID.MAIN_PAGE : path, hasRoute);

const pathParts = url.split(DEFAULT_SEGMENT);
const hasRoute = this.routes.has(pathParts[1]);
this.changeAppTitle(pathParts[1], hasRoute);
if (!hasRoute) {
await this.routes.get(PAGE_ID.NOT_FOUND_PAGE)?.();
return;
}

await this.routes.get(path)?.();
}

public async navigateTo(path: string): Promise<void> {
const hasRoute = this.routes.has(path);
this.changeAppTitle(path === PAGE_ID.DEFAULT_PAGE ? PAGE_ID.MAIN_PAGE : path, hasRoute);

if (!hasRoute) {
await this.routes.get(PAGE_ID.NOT_FOUND_PAGE)?.();
return;
}

await this.routes.get(pathParts[1])?.();
history.pushState(path, '', url);
await this.routes.get(path)?.();
history.pushState({ path }, '', path);
}

public setRoutes(routes: Map<string, () => Promise<Page>>): Map<string, () => Promise<Page>> {
Expand Down
16 changes: 8 additions & 8 deletions src/app/styles/fonts.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
@font-face {
src:
local('cerapro-black'),
url('/public/fonts/cerapro-black-webfont.woff2') format('woff2'),
url('/public/fonts/cerapro-black-webfont.woff') format('woff');
url('/fonts/cerapro-black-webfont.woff2') format('woff2'),
url('/fonts/cerapro-black-webfont.woff') format('woff');
font-family: 'Cerapro';
font-weight: 900;
font-style: normal;
Expand All @@ -12,8 +12,8 @@
@font-face {
src:
local('cerapro-bold'),
url('/public/fonts/cerapro-bold-webfont.woff2') format('woff2'),
url('/public/fonts/cerapro-bold-webfont.woff') format('woff');
url('/fonts/cerapro-bold-webfont.woff2') format('woff2'),
url('/fonts/cerapro-bold-webfont.woff') format('woff');
font-family: 'Cerapro';
font-weight: 700;
font-style: normal;
Expand All @@ -23,8 +23,8 @@
@font-face {
src:
local('cerapro-medium'),
url('/public//fonts/cerapro-medium-webfont.woff2') format('woff2'),
url('/public/fonts/cerapro-medium-webfont.woff') format('woff');
url('/fonts/cerapro-medium-webfont.woff2') format('woff2'),
url('/fonts/cerapro-medium-webfont.woff') format('woff');
font-family: 'Cerapro';
font-weight: 500;
font-style: normal;
Expand All @@ -34,8 +34,8 @@
@font-face {
src:
local('cerapro-regular'),
url('/public/fonts/cerapro-regular-webfont.woff2') format('woff2'),
url('/public/fonts/cerapro-regular-webfont.woff') format('woff');
url('/fonts/cerapro-regular-webfont.woff2') format('woff2'),
url('/fonts/cerapro-regular-webfont.woff') format('woff');
font-family: 'Cerapro';
font-weight: 400;
font-style: normal;
Expand Down
2 changes: 1 addition & 1 deletion src/entities/Navigation/model/NavigationModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ class NavigationModel {

private switchLinksState(): boolean {
const { currentPage } = getStore().getState();
const currentPath = currentPage === '' ? PAGE_ID.MAIN_PAGE : currentPage;
const currentPath = currentPage === PAGE_ID.DEFAULT_PAGE ? PAGE_ID.MAIN_PAGE : currentPage;
const navigationLinks = this.view.getNavigationLinks();
const currentLink = navigationLinks.get(String(currentPath));
navigationLinks.forEach((link) => link.setEnabled());
Expand Down
13 changes: 3 additions & 10 deletions src/pages/Blog/Post/view/PostView.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type RouterModel from '@/app/Router/model/RouterModel';
import type { Post } from '@/shared/constants/blog';

import getStore from '@/shared/Store/Store.ts';
import observeStore, { selectCurrentLanguage } from '@/shared/Store/observer.ts';
import { PAGE_ID } from '@/shared/constants/pages.ts';
import createBaseElement from '@/shared/utils/createBaseElement.ts';

import styles from './post.module.scss';
Expand All @@ -17,16 +15,13 @@ export default class PostView {

private post: Post;

private router: RouterModel;

constructor(post: Post, postClickCallback: (post: PostView) => void, router: RouterModel) {
constructor(post: Post, postClickCallback: (post: PostView) => void) {
this.post = post;
this.router = router;
this.callback = postClickCallback;
this.card = this.createCardHTML();
this.blogPost = this.createPostHtml();

this.card.addEventListener('click', (event) => this.onPostClick(event));
this.card.addEventListener('click', () => this.onPostClick());
observeStore(selectCurrentLanguage, () => this.updateLanguage());
}

Expand Down Expand Up @@ -61,9 +56,7 @@ export default class PostView {
return content;
}

private async onPostClick(event: Event): Promise<void> {
event.preventDefault();
await this.router.navigateTo(`${PAGE_ID.BLOG}/${this.post.id}`);
private onPostClick(): void {
this.callback(this);
}

Expand Down
9 changes: 2 additions & 7 deletions src/pages/Blog/PostList/model/PostListModel.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type RouterModel from '@/app/Router/model/RouterModel.ts';
import type { Post } from '@/shared/constants/blog.ts';
import type { Page } from '@/shared/types/common.ts';

Expand All @@ -20,15 +19,12 @@ export default class PostListModel implements Page {

private posts: PostView[];

private router: RouterModel;

private view: BlogPageView;

constructor(parent: HTMLDivElement, router: RouterModel) {
constructor(parent: HTMLDivElement) {
const newPost: Post[] = postsData;
this.parent = parent;
this.router = router;
this.posts = newPost.map((post) => new PostView(post, this.postClickHandler, this.router));
this.posts = newPost.map((post) => new PostView(post, this.postClickHandler));
this.view = new BlogPageView(parent, this.posts);
this.render();
this.init();
Expand All @@ -37,7 +33,6 @@ export default class PostListModel implements Page {
private init(): boolean {
getStore().dispatch(setCurrentPage(PAGE_ID.BLOG));
this.observeStoreLanguage();
window.addEventListener('popstate', () => this.render());
return true;
}

Expand Down
9 changes: 2 additions & 7 deletions src/pages/Blog/PostWidget/model/PostWidgetModel.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import type RouterModel from '@/app/Router/model/RouterModel.ts';

import PostView from '@/pages/Blog/Post/view/PostView.ts';
import observeStore, { selectCurrentLanguage } from '@/shared/Store/observer.ts';

Expand All @@ -16,15 +14,12 @@ export default class PostWidgetModel {

private posts: PostView[];

private router: RouterModel;

private view: PostWidgetView;

constructor(parent: HTMLDivElement, router: RouterModel) {
constructor(parent: HTMLDivElement) {
const shuffledPosts = [...postsData].sort(() => HALF_RANDOM - Math.random());
const newPost = shuffledPosts.slice(0, CART_COUNT);
this.router = router;
this.posts = newPost.map((post) => new PostView(post, this.postClickHandler, this.router));
this.posts = newPost.map((post) => new PostView(post, this.postClickHandler));
this.view = new PostWidgetView(parent, this.posts);
this.init();
}
Expand Down
11 changes: 1 addition & 10 deletions src/pages/MainPage/model/MainPageModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,13 @@ class MainPageModel implements Page {
this.parent = parent;
this.view = new MainPageView(this.parent);
this.navigation = new NavigationModel(this.router);
this.blogWidget = new PostWidgetModel(this.view.getHTML(), router);
this.blogWidget = new PostWidgetModel(this.view.getHTML());
this.init();
}

private init(): void {
this.getHTML().append(this.navigation.getHTML(), this.blogWidget.getHTML());
getStore().dispatch(setCurrentPage(PAGE_ID.MAIN_PAGE));
window.addEventListener('popstate', () => this.onHistoryChange());
}

private async onHistoryChange(): Promise<void> {
const path = window.location.pathname;

if (path === '/main') {
await this.router.navigateTo(PAGE_ID.MAIN_PAGE);
}
}

public getHTML(): HTMLDivElement {
Expand Down
1 change: 1 addition & 0 deletions src/pages/NotFoundPage/view/NotFoundPageView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class NotFoundPageView {

constructor(parent: HTMLDivElement) {
this.parent = parent;
this.parent.innerHTML = '';
this.logo = this.createPageLogo();
this.title = this.createPageTitle();
this.description = this.createPageDescription();
Expand Down
4 changes: 2 additions & 2 deletions src/shared/Store/test.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ vi.mock('./Store.ts', async (importOriginal) => {
billingCountry: '',
categories: [],
currentLanguage: 'en',
currentPage: '',
currentPage: '/',
currentUser: null,
isAppThemeLight: true,
isUserLoggedIn: false,
Expand Down Expand Up @@ -169,7 +169,7 @@ describe('rootReducer', () => {
billingCountry: '',
categories: [],
currentLanguage: 'en',
currentPage: '',
currentPage: '/',
currentUser: null,
isAppThemeLight: true,
isUserLoggedIn: false,
Expand Down
4 changes: 3 additions & 1 deletion src/shared/constants/initialState.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { State } from '../Store/reducer';

import { PAGE_ID } from './pages.ts';

const initialState: State = {
billingCountry: '',
categories: [],
currentLanguage: 'en',
currentPage: '',
currentPage: PAGE_ID.DEFAULT_PAGE,
currentUser: null,
isAppThemeLight: true,
isUserLoggedIn: false,
Expand Down
2 changes: 1 addition & 1 deletion src/shared/constants/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export const PAGE_ID = {
BLOG: 'blog',
CART_PAGE: 'cart',
CATALOG_PAGE: 'catalog',
DEFAULT_PAGE: '',
DEFAULT_PAGE: '/',
ITEM_PAGE: 'item',
LOGIN_PAGE: 'login',
MAIN_PAGE: 'main',
Expand Down
20 changes: 20 additions & 0 deletions src/shared/types/validation/paths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { PageIdType } from '@/shared/constants/pages';

import { PAGE_ID } from '@/shared/constants/pages.ts';

interface PopStateEventState {
path: string;
}

export const isValidPath = (value: unknown): value is PageIdType =>
Object.values(PAGE_ID).findIndex((route) => route === value) !== -1;

export const isValidState = (state: unknown): state is PopStateEventState => {
if (!state) {
return false;
}
if (typeof state === 'object' && 'path' in state && typeof state.path === 'string') {
return true;
}
return false;
};
2 changes: 1 addition & 1 deletion src/widgets/Catalog/model/CatalogModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class CatalogModel {
filter.push(addFilter(FilterFields.SIZE, size));
}

return { filter, limit: 100, sort: { direction: 'desc', field: 'name', locale: 'en' } };
return { filter, limit: 100, sort: { direction: 'asc', field: 'price' } };
}

private init(): void {
Expand Down

0 comments on commit 9ca0d26

Please sign in to comment.