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.
22class Elroid {
33 constructor ( options ) {
4+ // Cache the provided element selector, data, and template.
45 this . el = options . el ;
56 this . data = options . data ;
67 this . template = document . querySelector ( options . el ) . innerHTML ;
78
9+ // Compile the initial template and bind events.
810 this . compile ( ) ;
911 this . bindEvents ( ) ;
1012 }
1113
14+ // Compile all elements matching the element selector provided in the options.
1215 compile ( ) {
1316 const elements = document . querySelectorAll ( this . el ) ;
1417 elements . forEach ( ( element ) => {
1518 this . compileElement ( element ) ;
1619 } ) ;
1720 }
1821
22+ // Compile a single element's template string.
1923 compileElement ( element ) {
2024 const template = element . innerHTML ;
2125 const compiled = this . compileTemplate ( template ) ;
2226 element . innerHTML = compiled ;
2327 }
2428
29+ // Compile a template string with data.
2530 compileTemplate ( template ) {
26- const regex = / \{ \{ ( .* ?) \} \} / g;
31+ const regex = / \{ \{ ( .* ?) \} \} / g; // Use a regex to match all instances of {{...}} in the template.
2732 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.
2934 } ) ;
3035 return compiled ;
3136 }
3237
38+ // Bind event listeners to elements with the el-click attribute.
3339 bindEvents ( ) {
3440 const elements = document . querySelectorAll ( '[el-click]' ) ;
3541 elements . forEach ( ( element ) => {
3642 const methodName = element . getAttribute ( 'el-click' ) ;
3743 const method = this . data . methods [ methodName ] ;
3844 if ( method && typeof method === 'function' ) {
3945 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.
4349 } ) ;
4450 }
4551 } ) ;
4652 }
4753
54+ // Update the data object and recompile the template.
4855 update ( data ) {
4956 Object . assign ( this . data , data ) ;
5057 const compiledTemplate = this . compileTemplate ( this . template ) ;
@@ -54,43 +61,53 @@ class Elroid {
5461 }
5562}
5663
57- // Elroid Component
64+
65+ // Class Elroid Component: A subclass of Elroid that represents a single component with its own template and data.
5866class ElComponent {
5967 constructor ( options ) {
68+ // Cache the provided template, data, route, and element selector.
6069 this . template = options . template ;
6170 this . data = options . data ;
71+ this . route = options . route ;
6272 this . el = document . querySelector ( options . el ) ;
6373
74+ // Compile the initial template and bind events.
6475 this . compile ( ) ;
6576 this . bindEvents ( ) ;
6677 }
6778
79+ // Compile the component's template string.
6880 compile ( ) {
6981 const compiledTemplate = this . compileTemplate ( this . template ) ;
7082 this . el . innerHTML = compiledTemplate ;
7183 }
7284
85+ // Compile a template string with data.
7386 compileTemplate ( template ) {
74- const regex = / \{ \{ ( .* ?) \} \} / g;
87+ const regex = / \{ \{ ( .* ?) \} \} / g; // Use a regex to match all instances of {{...}} in the template.
7588 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.
7790 } ) ;
7891 return compiled ;
7992 }
8093
94+ // Bind event listeners to elements with the el-click attribute.
8195 bindEvents ( ) {
8296 const elements = this . el . querySelectorAll ( '[el-click]' ) ;
8397 elements . forEach ( ( element ) => {
8498 const methodName = element . getAttribute ( 'el-click' ) ;
8599 const method = this . data . methods [ methodName ] ;
86100 if ( method && typeof method === 'function' ) {
87101 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.
89105 } ) ;
90106 }
91107 } ) ;
92108 }
93109
110+ // Update the data object and recompile the template.
94111 update ( data ) {
95112 Object . assign ( this . data , data ) ;
96113 const compiledTemplate = this . compileTemplate ( this . template ) ;
@@ -99,90 +116,179 @@ class ElComponent {
99116 }
100117}
101118
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.
103193class ElRequest {
104194 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.
107197 }
108198
199+ // Set a header for the request.
109200 setHeader ( key , value ) {
110201 this . headers [ key ] = value ;
111202 }
112203
204+ // Make a GET request.
113205 get ( url = '' , data = { } , callback = ( ) => { } ) {
206+ // Convert the data object to a query string.
114207 const queryString = Object . entries ( data )
115208 . map ( ( [ key , value ] ) => `${ encodeURIComponent ( key ) } =${ encodeURIComponent ( value ) } ` )
116209 . join ( '&' ) ;
117210
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.
119212
213+ // Set any headers provided.
120214 for ( const [ key , value ] of Object . entries ( this . headers ) ) {
121215 this . http . setRequestHeader ( key , value ) ;
122216 }
123217
218+ // Handle the response when it loads.
124219 this . http . onload = function ( ) {
125220 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.
127222 } else {
128- callback ( `Error: ${ this . http . status } ` ) ;
223+ callback ( `Error: ${ this . http . status } ` ) ; // Invoke the callback with an error message.
129224 }
130225 } . bind ( this ) ;
131226
132- this . http . send ( ) ;
227+ this . http . send ( ) ; // Send the request.
133228 }
134229
230+ // Make a POST request.
135231 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.
137233
234+ // Set any headers provided.
138235 for ( const [ key , value ] of Object . entries ( this . headers ) ) {
139236 this . http . setRequestHeader ( key , value ) ;
140237 }
141238
239+ // Handle the response when it loads.
142240 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.
144242 } . bind ( this ) ;
145243
244+ // Convert the data object to a request body string.
146245 const requestBody = Object . entries ( data )
147246 . map ( ( [ key , value ] ) => `${ encodeURIComponent ( key ) } =${ encodeURIComponent ( value ) } ` )
148247 . join ( '&' ) ;
149248
150- this . http . send ( requestBody ) ;
249+ this . http . send ( requestBody ) ; // Send the request with the request body.
151250 }
152251
252+ // Make a PUT request.
153253 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.
155255
256+ // Set any headers provided.
156257 for ( const [ key , value ] of Object . entries ( this . headers ) ) {
157258 this . http . setRequestHeader ( key , value ) ;
158259 }
159260
261+ // Handle the response when it loads.
160262 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.
162264 } . bind ( this ) ;
163265
266+ // Convert the data object to a request body string.
164267 const requestBody = Object . entries ( data )
165268 . map ( ( [ key , value ] ) => `${ encodeURIComponent ( key ) } =${ encodeURIComponent ( value ) } ` )
166269 . join ( '&' ) ;
167270
168- this . http . send ( requestBody ) ;
271+ this . http . send ( requestBody ) ; // Send the request with the request body.
169272 }
170273
274+ // Make a DELETE request.
171275 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.
173277
278+ // Set any headers provided.
174279 for ( const [ key , value ] of Object . entries ( this . headers ) ) {
175280 this . http . setRequestHeader ( key , value ) ;
176281 }
177282
283+ // Handle the response when it loads.
178284 this . http . onload = function ( ) {
179285 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.
181287 } else {
182- callback ( `Error: ${ this . http . status } ` ) ;
288+ callback ( `Error: ${ this . http . status } ` ) ; // Invoke the callback with an error message.
183289 }
184290 } . bind ( this ) ;
185291
186- this . http . send ( ) ;
292+ this . http . send ( ) ; // Send the request.
187293 }
188294}
0 commit comments