@aref-shojaei/router 1.0.1 → 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/build/router.min.js +1 -1
- 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/server.js +17 -17
- package/index.js +6 -4
- package/package.json +10 -4
- package/src/dto/route.js +13 -17
- 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 -22
- package/src/utils/selector.js +59 -57
- 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/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,23 +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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
+
}
|
|
23
24
|
}
|
package/src/utils/selector.js
CHANGED
|
@@ -1,58 +1,60 @@
|
|
|
1
|
-
import { InvalidArgumentTypeError } from "../exception.js"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @abstract
|
|
5
|
-
*/
|
|
6
|
-
export default class Selector {
|
|
7
|
-
static #elements = []
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
constructor() {
|
|
11
|
-
throw new Error(`${new.target.name} class must not be called with \"new\" keyword!`)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* @param {HTMLAnchorElement} element
|
|
16
|
-
* @param {Document} document
|
|
17
|
-
* @returns {Selector}
|
|
18
|
-
*/
|
|
19
|
-
static findAll(element, document) {
|
|
20
|
-
if (typeof element !== "string") throw new InvalidArgumentTypeError("'element' must be an HTMLElement object!")
|
|
21
|
-
|
|
22
|
-
if (typeof document !== "object") throw new InvalidArgumentTypeError("'document' must be a Document object!")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const elements = document.querySelectorAll(element)
|
|
26
|
-
|
|
27
|
-
this._setElements(elements)
|
|
28
|
-
|
|
29
|
-
return this
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @param {function} callback
|
|
34
|
-
* @returns {void}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
1
|
+
import { InvalidArgumentTypeError } from "../exception.js"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @abstract
|
|
5
|
+
*/
|
|
6
|
+
export default class Selector {
|
|
7
|
+
static #elements = []
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
throw new Error(`${new.target.name} class must not be called with \"new\" keyword!`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {HTMLAnchorElement} element
|
|
16
|
+
* @param {Document} document
|
|
17
|
+
* @returns {Selector}
|
|
18
|
+
*/
|
|
19
|
+
static findAll(element, document) {
|
|
20
|
+
if (typeof element !== "string") throw new InvalidArgumentTypeError("'element' must be an HTMLElement object!")
|
|
21
|
+
|
|
22
|
+
if (typeof document !== "object") throw new InvalidArgumentTypeError("'document' must be a Document object!")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
const elements = document.querySelectorAll(element)
|
|
26
|
+
|
|
27
|
+
this._setElements(elements)
|
|
28
|
+
|
|
29
|
+
return this
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @param {function} callback
|
|
34
|
+
* @returns {void}
|
|
35
|
+
* @returns {void}
|
|
36
|
+
*/
|
|
37
|
+
static each(callback) {
|
|
38
|
+
if (typeof callback !== "function") throw new InvalidArgumentTypeError("'callback' must be a function!")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
const elements = this._getElements()
|
|
42
|
+
|
|
43
|
+
if (this.#elements.length) elements.forEach(callback)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {array} elements
|
|
48
|
+
* @returns {void}
|
|
49
|
+
*/
|
|
50
|
+
static _setElements(elements) {
|
|
51
|
+
this.#elements.push(...elements)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @returns {array}
|
|
56
|
+
*/
|
|
57
|
+
static _getElements() {
|
|
58
|
+
return this.#elements
|
|
59
|
+
}
|
|
58
60
|
}
|
package/src/view.js
CHANGED
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import { InvalidArgumentTypeError } from "./exception.js"
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @abstract
|
|
5
|
-
*/
|
|
6
|
-
export default class View {
|
|
7
|
-
constructor() {
|
|
8
|
-
throw new Error(`${new.target.name} class must not be called with \"new\" keyword!`)
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* @param {function} template
|
|
13
|
-
* @param {object} data
|
|
14
|
-
* @returns {string}
|
|
15
|
-
*/
|
|
16
|
-
static render(template, data = {}) {
|
|
17
|
-
if (typeof template !== "function") throw new InvalidArgumentTypeError("'template' must be a function!")
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
return template(data)
|
|
21
|
-
} catch (error) {
|
|
22
|
-
console.error("Error during template rendering: ", error);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
1
|
+
import { InvalidArgumentTypeError } from "./exception.js"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @abstract
|
|
5
|
+
*/
|
|
6
|
+
export default class View {
|
|
7
|
+
constructor() {
|
|
8
|
+
throw new Error(`${new.target.name} class must not be called with \"new\" keyword!`)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {function} template
|
|
13
|
+
* @param {object} data
|
|
14
|
+
* @returns {string}
|
|
15
|
+
*/
|
|
16
|
+
static render(template, data = {}) {
|
|
17
|
+
if (typeof template !== "function") throw new InvalidArgumentTypeError("'template' must be a function!")
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
return template(data)
|
|
21
|
+
} catch (error) {
|
|
22
|
+
console.error("Error during template rendering: ", error);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
25
|
}
|