@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/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
  }
@@ -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
- static onClick(element, callback) {
16
- if (typeof element !== "object") throw new InvalidArgumentTypeError("'element' must be an HTMLElement object!")
17
-
18
- if (typeof callback !== "function") throw new InvalidArgumentTypeError("'callback' must be a function!")
19
-
20
-
21
- element.addEventListener("click", callback)
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
  }
@@ -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
- static each(callback) {
37
- if (typeof callback !== "function") throw new InvalidArgumentTypeError("'callback' must be a function!")
38
-
39
-
40
- const elements = this._getElements()
41
-
42
- if (this.#elements.length) elements.forEach(callback)
43
- }
44
-
45
- /**
46
- * @param {array} elements
47
- */
48
- static _setElements(elements) {
49
- this.#elements.push(...elements)
50
- }
51
-
52
- /**
53
- * @returns {array}
54
- */
55
- static _getElements() {
56
- return this.#elements
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
  }