@aref-shojaei/router 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +332 -205
- package/example/assets/css/styles.css +45 -45
- package/example/assets/js/app.js +7 -7
- package/example/assets/js/router.min.js +1 -1
- package/example/index.html +26 -26
- package/example/package.json +18 -18
- package/example/server.js +17 -17
- package/index.js +6 -4
- package/package.json +48 -42
- package/src/dto/route.js +13 -13
- package/src/exception.js +5 -5
- package/src/page.js +91 -91
- package/src/route.js +103 -103
- package/src/router.js +207 -207
- package/src/utils/element.js +23 -23
- package/src/utils/selector.js +59 -59
- package/src/view.js +24 -24
- package/tests/unit/element.test.js +32 -32
- package/tests/unit/page.test.js +48 -48
- package/tests/unit/route.test.js +82 -82
- package/tests/unit/router.test.js +55 -55
- package/tests/unit/selector.test.js +42 -42
- package/tests/unit/view.test.js +46 -46
- package/webpack.config.js +23 -0
- package/.babelrc +0 -3
package/src/route.js
CHANGED
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
import Router from "./router.js"
|
|
2
|
-
import View from "./view.js";
|
|
3
|
-
import { InvalidArgumentTypeError } from "./exception.js"
|
|
4
|
-
import RouteDTO from "./dto/route.js"
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @abstract
|
|
9
|
-
*/
|
|
10
|
-
export default class Route extends Router {
|
|
11
|
-
constructor() {
|
|
12
|
-
throw new Error(`${new.target.name} class must not be called with \"new\" keyword!`)
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* @param {string} route
|
|
17
|
-
* @param {fucntion} callback
|
|
18
|
-
* @returns {Route}
|
|
19
|
-
*/
|
|
20
|
-
static add(route, callback) {
|
|
21
|
-
if (typeof route !== "string") throw new InvalidArgumentTypeError("'route' must be a string!")
|
|
22
|
-
|
|
23
|
-
if (typeof callback !== "function") throw new InvalidArgumentTypeError("'callback' must be a function!")
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
this._routes[this._routePrefix + route] = new RouteDTO({ template : callback })
|
|
27
|
-
|
|
28
|
-
this._currentRoute = route;
|
|
29
|
-
|
|
30
|
-
return this;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @param {string} prefix
|
|
35
|
-
* @param {function} callback
|
|
36
|
-
* @returns {Route}
|
|
37
|
-
*/
|
|
38
|
-
static group(prefix, callback) {
|
|
39
|
-
if (typeof prefix !== "string" || !prefix.startsWith("/")) throw new InvalidArgumentTypeError("'prefix' must be a string!")
|
|
40
|
-
|
|
41
|
-
if (typeof callback !== "function") throw new InvalidArgumentTypeError("'callback' must be a function!")
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const previousPrefix = this._routePrefix
|
|
45
|
-
|
|
46
|
-
this._routePrefix = prefix;
|
|
47
|
-
|
|
48
|
-
callback();
|
|
49
|
-
|
|
50
|
-
this._routePrefix = previousPrefix
|
|
51
|
-
|
|
52
|
-
return this;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @param {array} middlewares
|
|
57
|
-
* @returns {void}
|
|
58
|
-
*/
|
|
59
|
-
static middleware(middlewares) {
|
|
60
|
-
if (!Array.isArray(middlewares)) throw new InvalidArgumentTypeError("'middlewares' must be an array!")
|
|
61
|
-
|
|
62
|
-
const isDefinedRoutePrefix = this._routePrefix ? true : false
|
|
63
|
-
|
|
64
|
-
// Add middlewares to single route
|
|
65
|
-
if (!isDefinedRoutePrefix) {
|
|
66
|
-
this._routes[this._routePrefix + this._currentRoute]["middlewares"].push(...middlewares);
|
|
67
|
-
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// Add middlewares to the group of routes
|
|
72
|
-
for (const route in this._routes) {
|
|
73
|
-
if (!route.startsWith(this._routePrefix)) continue;
|
|
74
|
-
|
|
75
|
-
this._routes[route]["middlewares"].push(...middlewares);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Set route page title
|
|
81
|
-
* @param {string} value
|
|
82
|
-
* @returns {void}
|
|
83
|
-
*/
|
|
84
|
-
static title(value) {
|
|
85
|
-
if (typeof value !== "string") throw new InvalidArgumentTypeError("'value' must be a string!")
|
|
86
|
-
|
|
87
|
-
this._routes[this._routePrefix + this._currentRoute]["title"] = value
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* @param {string} to - Route pointer
|
|
92
|
-
* @returns {string}
|
|
93
|
-
*/
|
|
94
|
-
static redirect(to) {
|
|
95
|
-
if (typeof to !== "string" || !to.startsWith("/")) throw new InvalidArgumentTypeError("'to' must be a string starting route with \"/\"!")
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
this._setRouteToURL(to)
|
|
99
|
-
|
|
100
|
-
const { template, meta } = this._routes[to] ?? this._defaultRoute;
|
|
101
|
-
|
|
102
|
-
return View.render(template, meta)
|
|
103
|
-
}
|
|
1
|
+
import Router from "./router.js"
|
|
2
|
+
import View from "./view.js";
|
|
3
|
+
import { InvalidArgumentTypeError } from "./exception.js"
|
|
4
|
+
import RouteDTO from "./dto/route.js"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @abstract
|
|
9
|
+
*/
|
|
10
|
+
export default class Route extends Router {
|
|
11
|
+
constructor() {
|
|
12
|
+
throw new Error(`${new.target.name} class must not be called with \"new\" keyword!`)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} route
|
|
17
|
+
* @param {fucntion} callback
|
|
18
|
+
* @returns {Route}
|
|
19
|
+
*/
|
|
20
|
+
static add(route, callback) {
|
|
21
|
+
if (typeof route !== "string") throw new InvalidArgumentTypeError("'route' must be a string!")
|
|
22
|
+
|
|
23
|
+
if (typeof callback !== "function") throw new InvalidArgumentTypeError("'callback' must be a function!")
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
this._routes[this._routePrefix + route] = new RouteDTO({ template : callback })
|
|
27
|
+
|
|
28
|
+
this._currentRoute = route;
|
|
29
|
+
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* @param {string} prefix
|
|
35
|
+
* @param {function} callback
|
|
36
|
+
* @returns {Route}
|
|
37
|
+
*/
|
|
38
|
+
static group(prefix, callback) {
|
|
39
|
+
if (typeof prefix !== "string" || !prefix.startsWith("/")) throw new InvalidArgumentTypeError("'prefix' must be a string!")
|
|
40
|
+
|
|
41
|
+
if (typeof callback !== "function") throw new InvalidArgumentTypeError("'callback' must be a function!")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
const previousPrefix = this._routePrefix
|
|
45
|
+
|
|
46
|
+
this._routePrefix = prefix;
|
|
47
|
+
|
|
48
|
+
callback();
|
|
49
|
+
|
|
50
|
+
this._routePrefix = previousPrefix
|
|
51
|
+
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param {array} middlewares
|
|
57
|
+
* @returns {void}
|
|
58
|
+
*/
|
|
59
|
+
static middleware(middlewares) {
|
|
60
|
+
if (!Array.isArray(middlewares)) throw new InvalidArgumentTypeError("'middlewares' must be an array!")
|
|
61
|
+
|
|
62
|
+
const isDefinedRoutePrefix = this._routePrefix ? true : false
|
|
63
|
+
|
|
64
|
+
// Add middlewares to single route
|
|
65
|
+
if (!isDefinedRoutePrefix) {
|
|
66
|
+
this._routes[this._routePrefix + this._currentRoute]["middlewares"].push(...middlewares);
|
|
67
|
+
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Add middlewares to the group of routes
|
|
72
|
+
for (const route in this._routes) {
|
|
73
|
+
if (!route.startsWith(this._routePrefix)) continue;
|
|
74
|
+
|
|
75
|
+
this._routes[route]["middlewares"].push(...middlewares);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Set route page title
|
|
81
|
+
* @param {string} value
|
|
82
|
+
* @returns {void}
|
|
83
|
+
*/
|
|
84
|
+
static title(value) {
|
|
85
|
+
if (typeof value !== "string") throw new InvalidArgumentTypeError("'value' must be a string!")
|
|
86
|
+
|
|
87
|
+
this._routes[this._routePrefix + this._currentRoute]["title"] = value
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* @param {string} to - Route pointer
|
|
92
|
+
* @returns {string}
|
|
93
|
+
*/
|
|
94
|
+
static redirect(to) {
|
|
95
|
+
if (typeof to !== "string" || !to.startsWith("/")) throw new InvalidArgumentTypeError("'to' must be a string starting route with \"/\"!")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
this._setRouteToURL(to)
|
|
99
|
+
|
|
100
|
+
const { template, meta } = this._routes[to] ?? this._defaultRoute;
|
|
101
|
+
|
|
102
|
+
return View.render(template, meta)
|
|
103
|
+
}
|
|
104
104
|
}
|
package/src/router.js
CHANGED
|
@@ -1,208 +1,208 @@
|
|
|
1
|
-
import Page from "./page.js"
|
|
2
|
-
import View from "./view.js";
|
|
3
|
-
import Selector from "./utils/selector.js";
|
|
4
|
-
import Element from "./utils/element.js";
|
|
5
|
-
import { InvalidArgumentTypeError } from "./exception.js";
|
|
6
|
-
import RouteDTO from "./dto/route.js"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* @abstract
|
|
11
|
-
*/
|
|
12
|
-
export default class Router {
|
|
13
|
-
static _window
|
|
14
|
-
|
|
15
|
-
static _document
|
|
16
|
-
|
|
17
|
-
static _rootElement = "#root"
|
|
18
|
-
|
|
19
|
-
static _routes = {};
|
|
20
|
-
|
|
21
|
-
static _currentRoute = "";
|
|
22
|
-
|
|
23
|
-
static _routePrefix = "";
|
|
24
|
-
|
|
25
|
-
static _defaultRoute = new RouteDTO({
|
|
26
|
-
title : "404",
|
|
27
|
-
template : () => "404 | Page not found!"
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
constructor() {
|
|
34
|
-
throw new Error(`${new.target.name} class must not be called with \"new\" keyword!`)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* @param {object} params
|
|
39
|
-
*/
|
|
40
|
-
static configure({ window, document, selector }) {
|
|
41
|
-
if (typeof window !== "object") throw new InvalidArgumentTypeError("'window' must be a Window object!")
|
|
42
|
-
|
|
43
|
-
if (typeof document !== "object") throw new InvalidArgumentTypeError("'document' must be a Document object!")
|
|
44
|
-
|
|
45
|
-
if (selector && typeof selector !== "string") throw new InvalidArgumentTypeError("'selector' must be a string!")
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (selector) this._rootElement = selector
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
this._window = window
|
|
52
|
-
|
|
53
|
-
this._document = document
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* @param {string} target
|
|
58
|
-
* @returns {object}
|
|
59
|
-
*/
|
|
60
|
-
static #findRoute(target) {
|
|
61
|
-
for (const route in this._routes) {
|
|
62
|
-
const regex = new RegExp(`^${route.replace(/\{(\w+)\}/g, '(?<$1>[^/{}]+)')}$`);
|
|
63
|
-
|
|
64
|
-
if (!regex.test(target)) continue
|
|
65
|
-
|
|
66
|
-
const { groups } = regex.exec(target)
|
|
67
|
-
|
|
68
|
-
this.#setRouteQuery(route)
|
|
69
|
-
|
|
70
|
-
this.#setRouteParams(route, groups)
|
|
71
|
-
|
|
72
|
-
return this._routes[route]
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* @param {string} route
|
|
78
|
-
* @returns {boolean}
|
|
79
|
-
*/
|
|
80
|
-
static #setRouteQuery(route) {
|
|
81
|
-
const query = {}
|
|
82
|
-
|
|
83
|
-
const { search } = location
|
|
84
|
-
|
|
85
|
-
if (!search.length) return false
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
search.slice(1).split("&").forEach(item => {
|
|
89
|
-
const [key, value] = item.split("=")
|
|
90
|
-
|
|
91
|
-
query[key] = value
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
this._routes[route]["meta"]["query"] = {...query}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* @param {string} route
|
|
99
|
-
* @param {object} params
|
|
100
|
-
* @returns {void}
|
|
101
|
-
*/
|
|
102
|
-
static #setRouteParams(route, params) {
|
|
103
|
-
this._routes[route]["meta"]["params"] = {...params}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* @param {string} route
|
|
108
|
-
* @param {Window} window
|
|
109
|
-
* @returns {void}
|
|
110
|
-
*/
|
|
111
|
-
static _setRouteToURL(route) {
|
|
112
|
-
if (typeof route !== "string" || !route.startsWith("/")) throw new InvalidArgumentTypeError("'route' must be a string starting with \"/\"!")
|
|
113
|
-
|
|
114
|
-
this._window.history.pushState({}, "", route)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* @param {string} route
|
|
119
|
-
* @returns {void}
|
|
120
|
-
*/
|
|
121
|
-
static #injectTemplateToDOM(route) {
|
|
122
|
-
try {
|
|
123
|
-
const { title, template, middlewares, meta } = this.#findRoute(route) ?? this._defaultRoute
|
|
124
|
-
|
|
125
|
-
Page.setTitle(title)
|
|
126
|
-
|
|
127
|
-
this.#applyMiddlewares(middlewares)
|
|
128
|
-
|
|
129
|
-
this._document.querySelector(this._rootElement).innerHTML = View.render(template, meta)
|
|
130
|
-
} catch (error) {
|
|
131
|
-
console.error("Error to inject route template:", route, error);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* @returns {void}
|
|
137
|
-
*/
|
|
138
|
-
static #activeHistroyNavigation() {
|
|
139
|
-
this._window.addEventListener("popstate", event => {
|
|
140
|
-
const route = event.target.location.pathname
|
|
141
|
-
|
|
142
|
-
this.#injectTemplateToDOM(route)
|
|
143
|
-
})
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* @returns {void}
|
|
148
|
-
*/
|
|
149
|
-
static #changeRoutebyRequest() {
|
|
150
|
-
const { pathname } = this._window.location
|
|
151
|
-
|
|
152
|
-
this.#injectTemplateToDOM(pathname)
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* @returns {void}
|
|
157
|
-
*/
|
|
158
|
-
static #changeRoutebyLink() {
|
|
159
|
-
Selector.findAll("a", this._document).each(anchor => {
|
|
160
|
-
Element.onClick(anchor, (event) => {
|
|
161
|
-
if (event.target.hasAttribute("data-link")) return false
|
|
162
|
-
|
|
163
|
-
event.preventDefault()
|
|
164
|
-
|
|
165
|
-
const route = event.target.getAttribute("href")
|
|
166
|
-
|
|
167
|
-
this._setRouteToURL(route)
|
|
168
|
-
|
|
169
|
-
this.#injectTemplateToDOM(route)
|
|
170
|
-
})
|
|
171
|
-
})
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Executes middleware functions
|
|
176
|
-
* @param {array} middlewares
|
|
177
|
-
* @returns {void}
|
|
178
|
-
*/
|
|
179
|
-
static #applyMiddlewares(middlewares) {
|
|
180
|
-
try {
|
|
181
|
-
middlewares.length && middlewares.forEach(middleware => middleware())
|
|
182
|
-
} catch (error) {
|
|
183
|
-
console.error("Error executing middleware:", error);
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/**
|
|
188
|
-
* Initializes the router
|
|
189
|
-
* @param {function} callback
|
|
190
|
-
* @returns {void}
|
|
191
|
-
*/
|
|
192
|
-
static run(callback = () => {}) {
|
|
193
|
-
if (typeof callback !== "function") throw new InvalidArgumentTypeError("'callback' must be a function!")
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
Page.setDocument(this._document)
|
|
197
|
-
|
|
198
|
-
Page.setRootTitle(this._document.title)
|
|
199
|
-
|
|
200
|
-
this.#activeHistroyNavigation()
|
|
201
|
-
|
|
202
|
-
this.#changeRoutebyRequest()
|
|
203
|
-
|
|
204
|
-
this.#changeRoutebyLink()
|
|
205
|
-
|
|
206
|
-
callback()
|
|
207
|
-
}
|
|
1
|
+
import Page from "./page.js"
|
|
2
|
+
import View from "./view.js";
|
|
3
|
+
import Selector from "./utils/selector.js";
|
|
4
|
+
import Element from "./utils/element.js";
|
|
5
|
+
import { InvalidArgumentTypeError } from "./exception.js";
|
|
6
|
+
import RouteDTO from "./dto/route.js"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @abstract
|
|
11
|
+
*/
|
|
12
|
+
export default class Router {
|
|
13
|
+
static _window
|
|
14
|
+
|
|
15
|
+
static _document
|
|
16
|
+
|
|
17
|
+
static _rootElement = "#root"
|
|
18
|
+
|
|
19
|
+
static _routes = {};
|
|
20
|
+
|
|
21
|
+
static _currentRoute = "";
|
|
22
|
+
|
|
23
|
+
static _routePrefix = "";
|
|
24
|
+
|
|
25
|
+
static _defaultRoute = new RouteDTO({
|
|
26
|
+
title : "404",
|
|
27
|
+
template : () => "404 | Page not found!"
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
constructor() {
|
|
34
|
+
throw new Error(`${new.target.name} class must not be called with \"new\" keyword!`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @param {object} params
|
|
39
|
+
*/
|
|
40
|
+
static configure({ window, document, selector }) {
|
|
41
|
+
if (typeof window !== "object") throw new InvalidArgumentTypeError("'window' must be a Window object!")
|
|
42
|
+
|
|
43
|
+
if (typeof document !== "object") throw new InvalidArgumentTypeError("'document' must be a Document object!")
|
|
44
|
+
|
|
45
|
+
if (selector && typeof selector !== "string") throw new InvalidArgumentTypeError("'selector' must be a string!")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if (selector) this._rootElement = selector
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
this._window = window
|
|
52
|
+
|
|
53
|
+
this._document = document
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @param {string} target
|
|
58
|
+
* @returns {object}
|
|
59
|
+
*/
|
|
60
|
+
static #findRoute(target) {
|
|
61
|
+
for (const route in this._routes) {
|
|
62
|
+
const regex = new RegExp(`^${route.replace(/\{(\w+)\}/g, '(?<$1>[^/{}]+)')}$`);
|
|
63
|
+
|
|
64
|
+
if (!regex.test(target)) continue
|
|
65
|
+
|
|
66
|
+
const { groups } = regex.exec(target)
|
|
67
|
+
|
|
68
|
+
this.#setRouteQuery(route)
|
|
69
|
+
|
|
70
|
+
this.#setRouteParams(route, groups)
|
|
71
|
+
|
|
72
|
+
return this._routes[route]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {string} route
|
|
78
|
+
* @returns {boolean}
|
|
79
|
+
*/
|
|
80
|
+
static #setRouteQuery(route) {
|
|
81
|
+
const query = {}
|
|
82
|
+
|
|
83
|
+
const { search } = location
|
|
84
|
+
|
|
85
|
+
if (!search.length) return false
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
search.slice(1).split("&").forEach(item => {
|
|
89
|
+
const [key, value] = item.split("=")
|
|
90
|
+
|
|
91
|
+
query[key] = value
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
this._routes[route]["meta"]["query"] = {...query}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {string} route
|
|
99
|
+
* @param {object} params
|
|
100
|
+
* @returns {void}
|
|
101
|
+
*/
|
|
102
|
+
static #setRouteParams(route, params) {
|
|
103
|
+
this._routes[route]["meta"]["params"] = {...params}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* @param {string} route
|
|
108
|
+
* @param {Window} window
|
|
109
|
+
* @returns {void}
|
|
110
|
+
*/
|
|
111
|
+
static _setRouteToURL(route) {
|
|
112
|
+
if (typeof route !== "string" || !route.startsWith("/")) throw new InvalidArgumentTypeError("'route' must be a string starting with \"/\"!")
|
|
113
|
+
|
|
114
|
+
this._window.history.pushState({}, "", route)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* @param {string} route
|
|
119
|
+
* @returns {void}
|
|
120
|
+
*/
|
|
121
|
+
static #injectTemplateToDOM(route) {
|
|
122
|
+
try {
|
|
123
|
+
const { title, template, middlewares, meta } = this.#findRoute(route) ?? this._defaultRoute
|
|
124
|
+
|
|
125
|
+
Page.setTitle(title)
|
|
126
|
+
|
|
127
|
+
this.#applyMiddlewares(middlewares)
|
|
128
|
+
|
|
129
|
+
this._document.querySelector(this._rootElement).innerHTML = View.render(template, meta)
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error("Error to inject route template:", route, error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* @returns {void}
|
|
137
|
+
*/
|
|
138
|
+
static #activeHistroyNavigation() {
|
|
139
|
+
this._window.addEventListener("popstate", event => {
|
|
140
|
+
const route = event.target.location.pathname
|
|
141
|
+
|
|
142
|
+
this.#injectTemplateToDOM(route)
|
|
143
|
+
})
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* @returns {void}
|
|
148
|
+
*/
|
|
149
|
+
static #changeRoutebyRequest() {
|
|
150
|
+
const { pathname } = this._window.location
|
|
151
|
+
|
|
152
|
+
this.#injectTemplateToDOM(pathname)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* @returns {void}
|
|
157
|
+
*/
|
|
158
|
+
static #changeRoutebyLink() {
|
|
159
|
+
Selector.findAll("a", this._document).each(anchor => {
|
|
160
|
+
Element.onClick(anchor, (event) => {
|
|
161
|
+
if (event.target.hasAttribute("data-link")) return false
|
|
162
|
+
|
|
163
|
+
event.preventDefault()
|
|
164
|
+
|
|
165
|
+
const route = event.target.getAttribute("href")
|
|
166
|
+
|
|
167
|
+
this._setRouteToURL(route)
|
|
168
|
+
|
|
169
|
+
this.#injectTemplateToDOM(route)
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Executes middleware functions
|
|
176
|
+
* @param {array} middlewares
|
|
177
|
+
* @returns {void}
|
|
178
|
+
*/
|
|
179
|
+
static #applyMiddlewares(middlewares) {
|
|
180
|
+
try {
|
|
181
|
+
middlewares.length && middlewares.forEach(middleware => middleware())
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error("Error executing middleware:", error);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Initializes the router
|
|
189
|
+
* @param {function} callback
|
|
190
|
+
* @returns {void}
|
|
191
|
+
*/
|
|
192
|
+
static run(callback = () => {}) {
|
|
193
|
+
if (typeof callback !== "function") throw new InvalidArgumentTypeError("'callback' must be a function!")
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
Page.setDocument(this._document)
|
|
197
|
+
|
|
198
|
+
Page.setRootTitle(this._document.title)
|
|
199
|
+
|
|
200
|
+
this.#activeHistroyNavigation()
|
|
201
|
+
|
|
202
|
+
this.#changeRoutebyRequest()
|
|
203
|
+
|
|
204
|
+
this.#changeRoutebyLink()
|
|
205
|
+
|
|
206
|
+
callback()
|
|
207
|
+
}
|
|
208
208
|
}
|
package/src/utils/element.js
CHANGED
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { InvalidArgumentTypeError } from "../exception.js"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @abstract
|
|
5
|
-
*/
|
|
6
|
-
export default class Element {
|
|
7
|
-
constructor() {
|
|
8
|
-
throw new Error(`${new.target.name} class must not be called with \"new\" keyword!`)
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @param {HTMLElement} element
|
|
13
|
-
* @param {function} callback
|
|
14
|
-
* @returns {void}
|
|
15
|
-
*/
|
|
16
|
-
static onClick(element, callback) {
|
|
17
|
-
if (typeof element !== "object") throw new InvalidArgumentTypeError("'element' must be an HTMLElement object!")
|
|
18
|
-
|
|
19
|
-
if (typeof callback !== "function") throw new InvalidArgumentTypeError("'callback' must be a function!")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
element.addEventListener("click", callback)
|
|
23
|
-
}
|
|
1
|
+
import { InvalidArgumentTypeError } from "../exception.js"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @abstract
|
|
5
|
+
*/
|
|
6
|
+
export default class Element {
|
|
7
|
+
constructor() {
|
|
8
|
+
throw new Error(`${new.target.name} class must not be called with \"new\" keyword!`)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {HTMLElement} element
|
|
13
|
+
* @param {function} callback
|
|
14
|
+
* @returns {void}
|
|
15
|
+
*/
|
|
16
|
+
static onClick(element, callback) {
|
|
17
|
+
if (typeof element !== "object") throw new InvalidArgumentTypeError("'element' must be an HTMLElement object!")
|
|
18
|
+
|
|
19
|
+
if (typeof callback !== "function") throw new InvalidArgumentTypeError("'callback' must be a function!")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
element.addEventListener("click", callback)
|
|
23
|
+
}
|
|
24
24
|
}
|