├── Elroid-Example ├── example-component.html ├── example-html-edit.html ├── example-js-component │ ├── App.js │ └── index.html └── example.html ├── Elroid.js ├── Elroid.min.js ├── HTTP-Example ├── 0_GET_noData.html ├── 1_POST_noData.html ├── 2_GET.html ├── 3_POST.html └── 4_HEADER.html ├── LICENSE ├── README.md ├── Router-Example ├── App.js ├── README.md └── index.html └── log.txt /Elroid-Example/example-component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Elroid Example 7 | 8 | 9 | 10 | 11 |
12 | 13 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Elroid-Example/example-html-edit.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Elroid Example 7 | 8 | 9 | 10 | 11 |
12 |

{{title}}

13 | 14 | 15 |
16 | 17 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Elroid-Example/example-js-component/App.js: -------------------------------------------------------------------------------- 1 | const App = new ElComponent({ 2 | template: ` 3 |

{{title}}

4 | 5 | `, 6 | el: "#app", 7 | data: { 8 | title: 'Component', 9 | style: "color: black;", 10 | methods: { 11 | Edit() { 12 | App.update({ title: "Home", style: "color: red;" }); 13 | } 14 | } 15 | } 16 | }); -------------------------------------------------------------------------------- /Elroid-Example/example-js-component/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | App 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Elroid-Example/example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Elroid Example 7 | 8 | 9 | 10 | 11 |
12 |

{{title}}

13 | 14 |
15 | 16 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Elroid.js: -------------------------------------------------------------------------------- 1 | // Class Elroid: A simple templating engine that compiles a template string with data and binds event listeners based on the provided options. 2 | class Elroid { 3 | constructor(options) { 4 | // Cache the provided element selector, data, and template. 5 | this.el = options.el; 6 | this.data = options.data; 7 | this.template = document.querySelector(options.el).innerHTML; 8 | 9 | // Compile the initial template and bind events. 10 | this.compile(); 11 | this.bindEvents(); 12 | } 13 | 14 | // Compile all elements matching the element selector provided in the options. 15 | compile() { 16 | const elements = document.querySelectorAll(this.el); 17 | elements.forEach((element) => { 18 | this.compileElement(element); 19 | }); 20 | } 21 | 22 | // Compile a single element's template string. 23 | compileElement(element) { 24 | const template = element.innerHTML; 25 | const compiled = this.compileTemplate(template); 26 | element.innerHTML = compiled; 27 | } 28 | 29 | // Compile a template string with data. 30 | compileTemplate(template) { 31 | const regex = /\{\{(.*?)\}\}/g; // Use a regex to match all instances of {{...}} in the template. 32 | const compiled = template.replace(regex, (match, p1) => { 33 | return p1.split('.').reduce((acc, key) => acc[key.trim()], this.data) || ''; // Replace each matched string with the corresponding data value. 34 | }); 35 | return compiled; 36 | } 37 | 38 | // Bind event listeners to elements with the el-click attribute. 39 | bindEvents() { 40 | const elements = document.querySelectorAll('[el-click]'); 41 | elements.forEach((element) => { 42 | const methodName = element.getAttribute('el-click'); 43 | const method = this.data.methods[methodName]; 44 | if (method && typeof method === 'function') { 45 | element.addEventListener('click', () => { 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. 49 | }); 50 | } 51 | }); 52 | } 53 | 54 | // Update the data object and recompile the template. 55 | update(data) { 56 | Object.assign(this.data, data); 57 | const compiledTemplate = this.compileTemplate(this.template); 58 | const el = document.querySelector(this.el); 59 | el.innerHTML = compiledTemplate; 60 | this.bindEvents(); 61 | } 62 | } 63 | 64 | 65 | // Class Elroid Component: A subclass of Elroid that represents a single component with its own template and data. 66 | class ElComponent { 67 | constructor(options) { 68 | // Cache the provided template, data, route, and element selector. 69 | this.template = options.template; 70 | this.data = options.data; 71 | this.route = options.route; 72 | this.el = document.querySelector(options.el); 73 | 74 | // Compile the initial template and bind events. 75 | this.compile(); 76 | this.bindEvents(); 77 | } 78 | 79 | // Compile the component's template string. 80 | compile() { 81 | const compiledTemplate = this.compileTemplate(this.template); 82 | this.el.innerHTML = compiledTemplate; 83 | } 84 | 85 | // Compile a template string with data. 86 | compileTemplate(template) { 87 | const regex = /\{\{(.*?)\}\}/g; // Use a regex to match all instances of {{...}} in the template. 88 | const compiled = template.replace(regex, (match, p1) => { 89 | return p1.split('.').reduce((acc, key) => acc[key.trim()], this.data) || ''; // Replace each matched string with the corresponding data value. 90 | }); 91 | return compiled; 92 | } 93 | 94 | // Bind event listeners to elements with the el-click attribute. 95 | bindEvents() { 96 | const elements = this.el.querySelectorAll('[el-click]'); 97 | elements.forEach((element) => { 98 | const methodName = element.getAttribute('el-click'); 99 | const method = this.data.methods[methodName]; 100 | if (method && typeof method === 'function') { 101 | element.addEventListener('click', () => { 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. 105 | }); 106 | } 107 | }); 108 | } 109 | 110 | // Update the data object and recompile the template. 111 | update(data) { 112 | Object.assign(this.data, data); 113 | const compiledTemplate = this.compileTemplate(this.template); 114 | this.el.innerHTML = compiledTemplate; 115 | this.bindEvents(); 116 | } 117 | } 118 | 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. 193 | class ElRequest { 194 | constructor() { 195 | this.http = new XMLHttpRequest(); // Create a new instance of XMLHttpRequest. 196 | this.headers = {}; // Initialize an empty headers object. 197 | } 198 | 199 | // Set a header for the request. 200 | setHeader(key, value) { 201 | this.headers[key] = value; 202 | } 203 | 204 | // Make a GET request. 205 | get(url = '', data = {}, callback = () => { }) { 206 | // Convert the data object to a query string. 207 | const queryString = Object.entries(data) 208 | .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) 209 | .join('&'); 210 | 211 | this.http.open('GET', `${url}?${queryString}`, true); // Open a GET request to the provided URL with the query string. 212 | 213 | // Set any headers provided. 214 | for (const [key, value] of Object.entries(this.headers)) { 215 | this.http.setRequestHeader(key, value); 216 | } 217 | 218 | // Handle the response when it loads. 219 | this.http.onload = function () { 220 | if (this.http.status === 200) { 221 | callback(null, this.http.responseText); // Invoke the callback with no errors and the response text. 222 | } else { 223 | callback(`Error: ${this.http.status}`); // Invoke the callback with an error message. 224 | } 225 | }.bind(this); 226 | 227 | this.http.send(); // Send the request. 228 | } 229 | 230 | // Make a POST request. 231 | post(url = '', data = {}, callback = () => { }) { 232 | this.http.open('POST', url, true); // Open a POST request to the provided URL. 233 | 234 | // Set any headers provided. 235 | for (const [key, value] of Object.entries(this.headers)) { 236 | this.http.setRequestHeader(key, value); 237 | } 238 | 239 | // Handle the response when it loads. 240 | this.http.onload = function () { 241 | callback(null, this.http.responseText); // Invoke the callback with no errors and the response text. 242 | }.bind(this); 243 | 244 | // Convert the data object to a request body string. 245 | const requestBody = Object.entries(data) 246 | .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) 247 | .join('&'); 248 | 249 | this.http.send(requestBody); // Send the request with the request body. 250 | } 251 | 252 | // Make a PUT request. 253 | put(url = '', data = {}, callback = () => { }) { 254 | this.http.open('PUT', url, true); // Open a PUT request to the provided URL. 255 | 256 | // Set any headers provided. 257 | for (const [key, value] of Object.entries(this.headers)) { 258 | this.http.setRequestHeader(key, value); 259 | } 260 | 261 | // Handle the response when it loads. 262 | this.http.onload = function () { 263 | callback(null, this.http.responseText); // Invoke the callback with no errors and the response text. 264 | }.bind(this); 265 | 266 | // Convert the data object to a request body string. 267 | const requestBody = Object.entries(data) 268 | .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`) 269 | .join('&'); 270 | 271 | this.http.send(requestBody); // Send the request with the request body. 272 | } 273 | 274 | // Make a DELETE request. 275 | delete(url = '', callback = () => { }) { 276 | this.http.open('DELETE', url, true); // Open a DELETE request to the provided URL. 277 | 278 | // Set any headers provided. 279 | for (const [key, value] of Object.entries(this.headers)) { 280 | this.http.setRequestHeader(key, value); 281 | } 282 | 283 | // Handle the response when it loads. 284 | this.http.onload = function () { 285 | if (this.http.status === 200) { 286 | callback(null, 'Post Deleted!'); // Invoke the callback with no errors and a success message. 287 | } else { 288 | callback(`Error: ${this.http.status}`); // Invoke the callback with an error message. 289 | } 290 | }.bind(this); 291 | 292 | this.http.send(); // Send the request. 293 | } 294 | } -------------------------------------------------------------------------------- /Elroid.min.js: -------------------------------------------------------------------------------- 1 | class Elroid{constructor(t){this.el=t.el,this.data=t.data,this.template=document.querySelector(t.el).innerHTML,this.compile(),this.bindEvents()}compile(){let t=document.querySelectorAll(this.el);t.forEach(t=>{this.compileElement(t)})}compileElement(t){let e=t.innerHTML,i=this.compileTemplate(e);t.innerHTML=i}compileTemplate(t){let e=t.replace(/\{\{(.*?)\}\}/g,(t,e)=>e.split(".").reduce((t,e)=>t[e.trim()],this.data)||"");return e}bindEvents(){let t=document.querySelectorAll("[el-click]");t.forEach(t=>{let e=t.getAttribute("el-click"),i=this.data.methods[e];i&&"function"==typeof i&&t.addEventListener("click",()=>{i.bind(this.data)();let t=this.data.route||"/";router.navigateTo(t)})})}update(t){Object.assign(this.data,t);let e=this.compileTemplate(this.template),i=document.querySelector(this.el);i.innerHTML=e,this.bindEvents()}}class ElComponent{constructor(t){this.template=t.template,this.data=t.data,this.route=t.route,this.el=document.querySelector(t.el),this.compile(),this.bindEvents()}compile(){let t=this.compileTemplate(this.template);this.el.innerHTML=t}compileTemplate(t){let e=t.replace(/\{\{(.*?)\}\}/g,(t,e)=>e.split(".").reduce((t,e)=>t[e.trim()],this.data)||"");return e}bindEvents(){let t=this.el.querySelectorAll("[el-click]");t.forEach(t=>{let e=t.getAttribute("el-click"),i=this.data.methods[e];i&&"function"==typeof i&&t.addEventListener("click",()=>{i.bind(this.data)();let t=this.data.route||"/";router.navigateTo(t)})})}update(t){Object.assign(this.data,t);let e=this.compileTemplate(this.template);this.el.innerHTML=e,this.bindEvents()}}class ElRouter{constructor(t){this.routes=t.routes,this.defaultRoute=t.defaultRoute,this.errorRoute=t.errorRoute,this.el=t.el,this.visitedRoutes=[],this.init()}init(){this.navigateTo(window.location.pathname),window.addEventListener("popstate",()=>{this.goToPreviousRoute()}),document.addEventListener("click",t=>{let e=t.target.closest("a");e&&e.getAttribute("href").startsWith("/")&&(t.preventDefault(),this.navigateTo(e.getAttribute("href")))})}navigateTo(t){let e=this.findRoute(t)||this.findRoute(this.errorRoute),{component:i,data:s}=e;new i({el:this.el,data:s}),this.visitedRoutes.push(t),history.pushState({path:t},"",t)}goToPreviousRoute(){if(this.visitedRoutes.length>1){this.visitedRoutes.pop();let t=this.visitedRoutes[this.visitedRoutes.length-1],e=this.findRoute(t)||this.findRoute(this.errorRoute),{component:i,data:s}=e;new i({el:this.el,data:s}),history.pushState({path:t},"",t)}}findRoute(t){return this.routes.find(e=>e.route===t)}}class ElRequest{constructor(){this.http=new XMLHttpRequest,this.headers={}}setHeader(t,e){this.headers[t]=e}get(t="",e={},i=()=>{}){let s=Object.entries(e).map(([t,e])=>`${encodeURIComponent(t)}=${encodeURIComponent(e)}`).join("&");for(let[h,o]of(this.http.open("GET",`${t}?${s}`,!0),Object.entries(this.headers)))this.http.setRequestHeader(h,o);this.http.onload=(function(){200===this.http.status?i(null,this.http.responseText):i(`Error: ${this.http.status}`)}).bind(this),this.http.send()}post(t="",e={},i=()=>{}){for(let[s,h]of(this.http.open("POST",t,!0),Object.entries(this.headers)))this.http.setRequestHeader(s,h);this.http.onload=(function(){i(null,this.http.responseText)}).bind(this);let o=Object.entries(e).map(([t,e])=>`${encodeURIComponent(t)}=${encodeURIComponent(e)}`).join("&");this.http.send(o)}put(t="",e={},i=()=>{}){for(let[s,h]of(this.http.open("PUT",t,!0),Object.entries(this.headers)))this.http.setRequestHeader(s,h);this.http.onload=(function(){i(null,this.http.responseText)}).bind(this);let o=Object.entries(e).map(([t,e])=>`${encodeURIComponent(t)}=${encodeURIComponent(e)}`).join("&");this.http.send(o)}delete(t="",e=()=>{}){for(let[i,s]of(this.http.open("DELETE",t,!0),Object.entries(this.headers)))this.http.setRequestHeader(i,s);this.http.onload=(function(){200===this.http.status?e(null,"Post Deleted!"):e(`Error: ${this.http.status}`)}).bind(this),this.http.send()}} -------------------------------------------------------------------------------- /HTTP-Example/0_GET_noData.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Elroid 9 | 10 | 11 | 12 | 13 | 14 | 15 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /HTTP-Example/1_POST_noData.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Elroid 9 | 10 | 11 | 12 | 13 | 14 | 15 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /HTTP-Example/2_GET.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Elroid 9 | 10 | 11 | 12 | 13 | 14 | 15 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /HTTP-Example/3_POST.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Elroid 9 | 10 | 11 | 12 | 13 | 14 | 15 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /HTTP-Example/4_HEADER.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Elroid 9 | 10 | 11 | 12 | 13 | 14 | 15 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 ReactMVC 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elroid 2 | A powerful front-end development library 3 | ## Changes 4 | Open the [log.txt](log.txt) file to see the changes 5 | ## Documentation 6 | [Elroid Wiki](https://github.com/ReactMVC/Elroid/wiki) 7 | ## Contact the developer 8 | [Telegram](https://t.me/h3dev) 9 |
10 | 11 | [Email](mailto:h3dev.pira@gmail.com) 12 | -------------------------------------------------------------------------------- /Router-Example/App.js: -------------------------------------------------------------------------------- 1 | class HomeComponent extends ElComponent { 2 | constructor() { 3 | super({ 4 | el: '#app', 5 | template: ` 6 |
7 |

Welcome to the Home Page

8 |

Click the links in the navigation bar to go to other pages.

9 |
10 | `, 11 | data: {} 12 | }); 13 | } 14 | } 15 | 16 | class AboutComponent extends ElComponent { 17 | constructor() { 18 | super({ 19 | el: '#app', 20 | template: ` 21 |
22 |

About Us

23 |

We are a company that specializes in creating web applications.

24 |
25 | `, 26 | data: {} 27 | }); 28 | } 29 | } 30 | 31 | class NotFoundComponent extends ElComponent { 32 | constructor() { 33 | super({ 34 | el: '#app', 35 | template: ` 36 |
37 |

404 - Page Not Found

38 |

The page you are looking for could not be found.

39 |
40 | `, 41 | data: {} 42 | }); 43 | } 44 | } 45 | 46 | const router = new ElRouter({ 47 | routes: [ 48 | { route: '/', component: HomeComponent }, 49 | { route: '/about', component: AboutComponent }, 50 | { route: '/404', component: NotFoundComponent }, 51 | ], 52 | defaultRoute: '/', 53 | errorRoute: '/404', 54 | }); 55 | -------------------------------------------------------------------------------- /Router-Example/README.md: -------------------------------------------------------------------------------- 1 | # Important note. 2 | If you build a router with Elroid, add these so that when https://copy.reactmvc.ir/about is refreshed, you will not get a 404 error. 3 |
4 | 5 | For Apache 6 | ``` 7 | RewriteEngine On 8 | RewriteCond %{REQUEST_FILENAME} !-f 9 | RewriteRule ^(.*)$ index.html [L,QSA] 10 | ``` 11 | For Nginx 12 | ``` 13 | location / { 14 | try_files $uri $uri/ /index.html; 15 | } 16 | ``` 17 | Apache in the .htaccess file and Nginx in the nginx.conf file 18 | -------------------------------------------------------------------------------- /Router-Example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | My App 6 | 7 | 8 | 9 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /log.txt: -------------------------------------------------------------------------------- 1 | Added editing functionality 2 | Add clickability and functions for the component 3 | Added HTTP request feature 4 | Another bug fix... --------------------------------------------------------------------------------