1
- // Class Elroid
1
+ // Class Elroid: A simple templating engine that compiles a template string with data and binds event listeners based on the provided options.
2
2
class Elroid {
3
3
constructor ( options ) {
4
+ // Cache the provided element selector, data, and template.
4
5
this . el = options . el ;
5
6
this . data = options . data ;
6
7
this . template = document . querySelector ( options . el ) . innerHTML ;
7
8
9
+ // Compile the initial template and bind events.
8
10
this . compile ( ) ;
9
11
this . bindEvents ( ) ;
10
12
}
11
13
14
+ // Compile all elements matching the element selector provided in the options.
12
15
compile ( ) {
13
16
const elements = document . querySelectorAll ( this . el ) ;
14
17
elements . forEach ( ( element ) => {
15
18
this . compileElement ( element ) ;
16
19
} ) ;
17
20
}
18
21
22
+ // Compile a single element's template string.
19
23
compileElement ( element ) {
20
24
const template = element . innerHTML ;
21
25
const compiled = this . compileTemplate ( template ) ;
22
26
element . innerHTML = compiled ;
23
27
}
24
28
29
+ // Compile a template string with data.
25
30
compileTemplate ( template ) {
26
- const regex = / \{ \{ ( .* ?) \} \} / g;
31
+ const regex = / \{ \{ ( .* ?) \} \} / g; // Use a regex to match all instances of {{...}} in the template.
27
32
const compiled = template . replace ( regex , ( match , p1 ) => {
28
- return p1 . split ( '.' ) . reduce ( ( acc , key ) => acc [ key . trim ( ) ] , this . data ) || '' ;
33
+ return p1 . split ( '.' ) . reduce ( ( acc , key ) => acc [ key . trim ( ) ] , this . data ) || '' ; // Replace each matched string with the corresponding data value.
29
34
} ) ;
30
35
return compiled ;
31
36
}
32
37
38
+ // Bind event listeners to elements with the el-click attribute.
33
39
bindEvents ( ) {
34
40
const elements = document . querySelectorAll ( '[el-click]' ) ;
35
41
elements . forEach ( ( element ) => {
36
42
const methodName = element . getAttribute ( 'el-click' ) ;
37
43
const method = this . data . methods [ methodName ] ;
38
44
if ( method && typeof method === 'function' ) {
39
45
element . addEventListener ( 'click' , ( ) => {
40
- method . bind ( this . data ) ( ) ;
41
- this . compile ( ) ;
42
- this . bindEvents ( ) ;
46
+ method . bind ( this . data ) ( ) ; // Bind the method to the data object and invoke it on click.
47
+ const route = this . data . route || '/' ;
48
+ router . navigateTo ( route ) ; // Navigate to the route specified in the data object, or the root route by default.
43
49
} ) ;
44
50
}
45
51
} ) ;
46
52
}
47
53
54
+ // Update the data object and recompile the template.
48
55
update ( data ) {
49
56
Object . assign ( this . data , data ) ;
50
57
const compiledTemplate = this . compileTemplate ( this . template ) ;
@@ -54,43 +61,53 @@ class Elroid {
54
61
}
55
62
}
56
63
57
- // Elroid Component
64
+
65
+ // Class Elroid Component: A subclass of Elroid that represents a single component with its own template and data.
58
66
class ElComponent {
59
67
constructor ( options ) {
68
+ // Cache the provided template, data, route, and element selector.
60
69
this . template = options . template ;
61
70
this . data = options . data ;
71
+ this . route = options . route ;
62
72
this . el = document . querySelector ( options . el ) ;
63
73
74
+ // Compile the initial template and bind events.
64
75
this . compile ( ) ;
65
76
this . bindEvents ( ) ;
66
77
}
67
78
79
+ // Compile the component's template string.
68
80
compile ( ) {
69
81
const compiledTemplate = this . compileTemplate ( this . template ) ;
70
82
this . el . innerHTML = compiledTemplate ;
71
83
}
72
84
85
+ // Compile a template string with data.
73
86
compileTemplate ( template ) {
74
- const regex = / \{ \{ ( .* ?) \} \} / g;
87
+ const regex = / \{ \{ ( .* ?) \} \} / g; // Use a regex to match all instances of {{...}} in the template.
75
88
const compiled = template . replace ( regex , ( match , p1 ) => {
76
- return p1 . split ( '.' ) . reduce ( ( acc , key ) => acc [ key . trim ( ) ] , this . data ) || '' ;
89
+ return p1 . split ( '.' ) . reduce ( ( acc , key ) => acc [ key . trim ( ) ] , this . data ) || '' ; // Replace each matched string with the corresponding data value.
77
90
} ) ;
78
91
return compiled ;
79
92
}
80
93
94
+ // Bind event listeners to elements with the el-click attribute.
81
95
bindEvents ( ) {
82
96
const elements = this . el . querySelectorAll ( '[el-click]' ) ;
83
97
elements . forEach ( ( element ) => {
84
98
const methodName = element . getAttribute ( 'el-click' ) ;
85
99
const method = this . data . methods [ methodName ] ;
86
100
if ( method && typeof method === 'function' ) {
87
101
element . addEventListener ( 'click' , ( ) => {
88
- method . bind ( this . data ) ( ) ;
102
+ method . bind ( this . data ) ( ) ; // Bind the method to the data object and invoke it on click.
103
+ const route = this . data . route || '/' ;
104
+ router . navigateTo ( route ) ; // Navigate to the route specified in the data object, or the root route by default.
89
105
} ) ;
90
106
}
91
107
} ) ;
92
108
}
93
109
110
+ // Update the data object and recompile the template.
94
111
update ( data ) {
95
112
Object . assign ( this . data , data ) ;
96
113
const compiledTemplate = this . compileTemplate ( this . template ) ;
@@ -99,90 +116,179 @@ class ElComponent {
99
116
}
100
117
}
101
118
102
- // Elroid Request
119
+
120
+ // ElRouter: A simple client-side router for single-page applications.
121
+ class ElRouter {
122
+ constructor ( options ) {
123
+ this . routes = options . routes ; // An array of route objects, where each object contains a route and a component.
124
+ this . defaultRoute = options . defaultRoute ; // The default route to navigate to if no matching route is found.
125
+ this . errorRoute = options . errorRoute ; // The error route to navigate to if a matching route is not found.
126
+ this . el = options . el ; // The DOM element to render components into.
127
+ this . visitedRoutes = [ ] ; // An array of visited routes.
128
+
129
+ this . init ( ) ; // Initialize the router.
130
+ }
131
+
132
+ // Initialize the router by setting up event listeners and handling the initial page load.
133
+ init ( ) {
134
+ // Handle initial page load
135
+ this . navigateTo ( window . location . pathname ) ;
136
+
137
+ // Handle back/forward button clicks
138
+ window . addEventListener ( 'popstate' , ( ) => {
139
+ this . goToPreviousRoute ( ) ;
140
+ } ) ;
141
+
142
+ // Handle anchor tag clicks
143
+ document . addEventListener ( 'click' , ( event ) => {
144
+ const anchor = event . target . closest ( 'a' ) ;
145
+ if ( anchor && anchor . getAttribute ( 'href' ) . startsWith ( '/' ) ) {
146
+ event . preventDefault ( ) ;
147
+ this . navigateTo ( anchor . getAttribute ( 'href' ) ) ;
148
+ }
149
+ } ) ;
150
+ }
151
+
152
+ // Navigate to the specified path by finding the corresponding route and rendering the component.
153
+ navigateTo ( path ) {
154
+ const route = this . findRoute ( path ) || this . findRoute ( this . errorRoute ) ; // Find the route object for the specified path or the error route.
155
+ const { component, data } = route ; // Destructure the component and data properties from the route object.
156
+
157
+ // Create a new component instance
158
+ const elComponent = new component ( { el : this . el , data } ) ;
159
+
160
+ // Add the current route to the visited routes array
161
+ this . visitedRoutes . push ( path ) ;
162
+
163
+ // Update the browser history without reloading the page
164
+ history . pushState ( { path } , '' , path ) ;
165
+ }
166
+
167
+ // Navigate to the previous route by retrieving the previous path from the visited routes array and rendering the corresponding component.
168
+ goToPreviousRoute ( ) {
169
+ if ( this . visitedRoutes . length > 1 ) {
170
+ // Remove the current route from the visited routes array
171
+ this . visitedRoutes . pop ( ) ;
172
+ // Retrieve the previous route from the visited routes array
173
+ const previousPath = this . visitedRoutes [ this . visitedRoutes . length - 1 ] ;
174
+ const previousRoute = this . findRoute ( previousPath ) || this . findRoute ( this . errorRoute ) ;
175
+ const { component : previousComponent , data : previousData } = previousRoute ;
176
+
177
+ // Create a new component instance for the previous route
178
+ const previousElComponent = new previousComponent ( { el : this . el , data : previousData } ) ;
179
+
180
+ // Update the browser history without reloading the page
181
+ history . pushState ( { path : previousPath } , '' , previousPath ) ;
182
+ }
183
+ }
184
+
185
+ // Find the route object for the specified path.
186
+ findRoute ( path ) {
187
+ return this . routes . find ( ( route ) => route . route === path ) ;
188
+ }
189
+ }
190
+
191
+
192
+ // ElRequest: A simple XMLHttpRequest wrapper for making HTTP requests.
103
193
class ElRequest {
104
194
constructor ( ) {
105
- this . http = new XMLHttpRequest ( ) ;
106
- this . headers = { } ;
195
+ this . http = new XMLHttpRequest ( ) ; // Create a new instance of XMLHttpRequest.
196
+ this . headers = { } ; // Initialize an empty headers object.
107
197
}
108
198
199
+ // Set a header for the request.
109
200
setHeader ( key , value ) {
110
201
this . headers [ key ] = value ;
111
202
}
112
203
204
+ // Make a GET request.
113
205
get ( url = '' , data = { } , callback = ( ) => { } ) {
206
+ // Convert the data object to a query string.
114
207
const queryString = Object . entries ( data )
115
208
. map ( ( [ key , value ] ) => `${ encodeURIComponent ( key ) } =${ encodeURIComponent ( value ) } ` )
116
209
. join ( '&' ) ;
117
210
118
- this . http . open ( 'GET' , `${ url } ?${ queryString } ` , true ) ;
211
+ this . http . open ( 'GET' , `${ url } ?${ queryString } ` , true ) ; // Open a GET request to the provided URL with the query string.
119
212
213
+ // Set any headers provided.
120
214
for ( const [ key , value ] of Object . entries ( this . headers ) ) {
121
215
this . http . setRequestHeader ( key , value ) ;
122
216
}
123
217
218
+ // Handle the response when it loads.
124
219
this . http . onload = function ( ) {
125
220
if ( this . http . status === 200 ) {
126
- callback ( null , this . http . responseText ) ;
221
+ callback ( null , this . http . responseText ) ; // Invoke the callback with no errors and the response text.
127
222
} else {
128
- callback ( `Error: ${ this . http . status } ` ) ;
223
+ callback ( `Error: ${ this . http . status } ` ) ; // Invoke the callback with an error message.
129
224
}
130
225
} . bind ( this ) ;
131
226
132
- this . http . send ( ) ;
227
+ this . http . send ( ) ; // Send the request.
133
228
}
134
229
230
+ // Make a POST request.
135
231
post ( url = '' , data = { } , callback = ( ) => { } ) {
136
- this . http . open ( 'POST' , url , true ) ;
232
+ this . http . open ( 'POST' , url , true ) ; // Open a POST request to the provided URL.
137
233
234
+ // Set any headers provided.
138
235
for ( const [ key , value ] of Object . entries ( this . headers ) ) {
139
236
this . http . setRequestHeader ( key , value ) ;
140
237
}
141
238
239
+ // Handle the response when it loads.
142
240
this . http . onload = function ( ) {
143
- callback ( null , this . http . responseText ) ;
241
+ callback ( null , this . http . responseText ) ; // Invoke the callback with no errors and the response text.
144
242
} . bind ( this ) ;
145
243
244
+ // Convert the data object to a request body string.
146
245
const requestBody = Object . entries ( data )
147
246
. map ( ( [ key , value ] ) => `${ encodeURIComponent ( key ) } =${ encodeURIComponent ( value ) } ` )
148
247
. join ( '&' ) ;
149
248
150
- this . http . send ( requestBody ) ;
249
+ this . http . send ( requestBody ) ; // Send the request with the request body.
151
250
}
152
251
252
+ // Make a PUT request.
153
253
put ( url = '' , data = { } , callback = ( ) => { } ) {
154
- this . http . open ( 'PUT' , url , true ) ;
254
+ this . http . open ( 'PUT' , url , true ) ; // Open a PUT request to the provided URL.
155
255
256
+ // Set any headers provided.
156
257
for ( const [ key , value ] of Object . entries ( this . headers ) ) {
157
258
this . http . setRequestHeader ( key , value ) ;
158
259
}
159
260
261
+ // Handle the response when it loads.
160
262
this . http . onload = function ( ) {
161
- callback ( null , this . http . responseText ) ;
263
+ callback ( null , this . http . responseText ) ; // Invoke the callback with no errors and the response text.
162
264
} . bind ( this ) ;
163
265
266
+ // Convert the data object to a request body string.
164
267
const requestBody = Object . entries ( data )
165
268
. map ( ( [ key , value ] ) => `${ encodeURIComponent ( key ) } =${ encodeURIComponent ( value ) } ` )
166
269
. join ( '&' ) ;
167
270
168
- this . http . send ( requestBody ) ;
271
+ this . http . send ( requestBody ) ; // Send the request with the request body.
169
272
}
170
273
274
+ // Make a DELETE request.
171
275
delete ( url = '' , callback = ( ) => { } ) {
172
- this . http . open ( 'DELETE' , url , true ) ;
276
+ this . http . open ( 'DELETE' , url , true ) ; // Open a DELETE request to the provided URL.
173
277
278
+ // Set any headers provided.
174
279
for ( const [ key , value ] of Object . entries ( this . headers ) ) {
175
280
this . http . setRequestHeader ( key , value ) ;
176
281
}
177
282
283
+ // Handle the response when it loads.
178
284
this . http . onload = function ( ) {
179
285
if ( this . http . status === 200 ) {
180
- callback ( null , 'Post Deleted!' ) ;
286
+ callback ( null , 'Post Deleted!' ) ; // Invoke the callback with no errors and a success message.
181
287
} else {
182
- callback ( `Error: ${ this . http . status } ` ) ;
288
+ callback ( `Error: ${ this . http . status } ` ) ; // Invoke the callback with an error message.
183
289
}
184
290
} . bind ( this ) ;
185
291
186
- this . http . send ( ) ;
292
+ this . http . send ( ) ; // Send the request.
187
293
}
188
294
}
0 commit comments