├── 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...
--------------------------------------------------------------------------------