vaderjs 1.0.9 → 1.1.1

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.
Files changed (4) hide show
  1. package/package.json +1 -1
  2. package/readme.md +29 -1
  3. package/vader.js +136 -111
  4. package/vaderRouter.js +51 -33
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vaderjs",
3
- "version": "1.0.9",
3
+ "version": "1.1.1",
4
4
  "description": "A Reactive Framework for Single-Page Applications (SPA)",
5
5
  "main": "vader.js",
6
6
  "scripts": {
package/readme.md CHANGED
@@ -20,7 +20,7 @@ router.start();
20
20
  ### State Management
21
21
 
22
22
  ```javascript
23
- const [state, setState] = useState('stateName', initialState);
23
+ const [state, setState] = useState("count", initialState);
24
24
  function increment(){
25
25
  setState(state + 1)
26
26
  }
@@ -111,6 +111,34 @@ const app = component('app', {
111
111
  })
112
112
  ```
113
113
 
114
+ ## Include views
115
+
116
+ As of v1.1.0 - Vader allows you to include html files as templates
117
+
118
+ ```html
119
+ // views/app.html
120
+
121
+ <div>
122
+ ${
123
+ window.location.hash === "#/home" ? "Home page" : "Not on the Home Page"
124
+ }
125
+ </div>
126
+ ```
127
+
128
+ ```js
129
+ // home.js
130
+ import { vhtml, component, rf, include } from 'vader.js'
131
+ const Home = component('Home', {
132
+ render: async () =>{
133
+ let html = await include('views/app.html')
134
+ return vhtml(html) || vhtml`${html}` // if using more than one view component
135
+ },
136
+ componentDidMount: () =>{
137
+ console.log('home mounted')
138
+ }
139
+ })
140
+ ```
141
+
114
142
  ## Get Started
115
143
 
116
144
  1. Install VaderJS:
package/vader.js CHANGED
@@ -3,7 +3,8 @@
3
3
  * @property {Object} props
4
4
  * @description Allows you to store props for component
5
5
  */
6
- window.props = {}
6
+ window.props = {};
7
+ let events = {};
7
8
  /**
8
9
  * @function vhtml
9
10
  * @param {String} strings
@@ -13,7 +14,7 @@ window.props = {}
13
14
  */
14
15
  export function vhtml(strings, ...args) {
15
16
  let result = "";
16
-
17
+
17
18
  for (let i = 0; i < strings.length; i++) {
18
19
  result += strings[i];
19
20
  if (i < args.length) {
@@ -21,16 +22,18 @@ export function vhtml(strings, ...args) {
21
22
  }
22
23
  }
23
24
 
24
- let dom = new DOMParser().parseFromString(result, 'text/html')
25
- if(dom.body.firstChild.nodeName.toLowerCase() !== 'div'){
26
- throw new Error(`Ensure that you have a parent div for all component elements`)
25
+ let dom = new DOMParser().parseFromString(result, "text/html");
26
+ if (dom.body.firstChild.nodeName.toLowerCase() !== "div") {
27
+ throw new Error(
28
+ `Ensure that you have a parent div for all component elements`
29
+ );
27
30
  }
28
31
 
29
- dom.body.querySelectorAll('[className]').forEach((el)=>{
30
- el.setAttribute('class', el.getAttribute('classname'))
31
- el.removeAttribute('classname')
32
- })
33
- return dom.body.innerHTML
32
+ dom.body.querySelectorAll("[className]").forEach((el) => {
33
+ el.setAttribute("class", el.getAttribute("classname"));
34
+ el.removeAttribute("classname");
35
+ });
36
+ return dom.body.innerHTML;
34
37
  }
35
38
  /**
36
39
  * @function component
@@ -73,7 +76,7 @@ export function vhtml(strings, ...args) {
73
76
  ***/
74
77
 
75
78
  export function component(name, options) {
76
- let states = {}
79
+ let states = {};
77
80
  const effects = {};
78
81
  const executedEffects = {};
79
82
  let storedProps = {};
@@ -92,6 +95,12 @@ export function component(name, options) {
92
95
  updateComponent();
93
96
  };
94
97
 
98
+ const signal = (key, value) => {
99
+ states[key] = value;
100
+ window.props[key] = value;
101
+ updateComponent();
102
+ };
103
+
95
104
  /**
96
105
  * @function useState
97
106
  * @param {*} key
@@ -99,23 +108,20 @@ export function component(name, options) {
99
108
  * @returns {Array} [state, setState]
100
109
  * @description Allows you to bind state to component
101
110
  */
102
-
103
-
104
111
 
105
-
106
- const useState = (key, initialValue) => {
107
- if (!states[key]) {
108
- states[key] = initialValue;
109
- }
110
- return [
111
- states[key],
112
- (value) => {
113
- states[key] = value;
114
- window.props[key] = value;
115
- updateComponent();
116
- },
117
- ];
118
- };
112
+ const useState = (key, initialValue) => {
113
+ if (!states[key]) {
114
+ states[key] = initialValue;
115
+ }
116
+ return [
117
+ states[key],
118
+ (value) => {
119
+ states[key] = value;
120
+ window.props[key] = value;
121
+ updateComponent();
122
+ },
123
+ ];
124
+ };
119
125
  /**
120
126
  * @function useEffect
121
127
  * @param {*} effectFn
@@ -128,50 +134,60 @@ export function component(name, options) {
128
134
  effects[name] = [];
129
135
  }
130
136
  effects[name].push(effectFn);
131
-
137
+
132
138
  if (dependencies.length > 0) {
133
- const oldState = states[name];
134
- const newState = dependencies.map((dependency) => {
135
- return states[dependency];
139
+ const oldState = states[name];
140
+ const newState = dependencies.map((dependency) => {
141
+ return states[dependency];
142
+ });
143
+ if (oldState) {
144
+ const hasChanged = newState.some((state) => {
145
+ return state !== oldState;
136
146
  });
137
- if (oldState) {
138
- const hasChanged = newState.some((state) => {
139
- return state !== oldState;
140
- });
141
- if (hasChanged) {
142
- effectFn();
143
- }
147
+ if (hasChanged) {
148
+ effectFn();
144
149
  }
145
- }else if (!hasMounted) {
146
- effectFn();
147
- hasMounted = true;
150
+ }
151
+ } else if (!hasMounted) {
152
+ effectFn();
153
+ hasMounted = true;
148
154
  }
149
155
 
150
156
  return () => {
151
- effects[name] = effects[name].filter((fn) => fn !== effectFn);
157
+ effects[name] = effects[name].filter((fn) => fn !== effectFn);
152
158
  };
153
159
  };
154
160
 
161
+ /**
162
+ *
163
+ * @param {String} key
164
+ * @param {Function} reducer
165
+ * @param {Object} initialState
166
+ * @returns {Array} [state, dispatch]
167
+ * @description Allows you to bind state to component
168
+ */
169
+
155
170
  const useReducer = (key, reducer, initialState) => {
156
171
  const [state, setState] = useState(key, initialState);
157
172
 
158
173
  const dispatch = (action) => {
159
- const newState = reducer(state, action);
160
- setState(newState);
161
- }
174
+ const newState = reducer(state, action);
175
+ setState(newState);
176
+ };
162
177
 
163
178
  return [state, dispatch];
164
179
  };
165
180
  /**
166
181
  * @function useSyncStore
167
- * @param {*} storeName
168
- * @param {*} initialState
182
+ * @param {*} storeName
183
+ * @param {*} initialState
169
184
  * @returns {Object} {getField, setField, subscribe, clear}
170
185
  * @description Allows you to manage state in local storage
171
186
  */
172
187
  const useSyncStore = (storeName, initialState) => {
173
188
  // Load state from local storage or use initial state
174
- const storedState = JSON.parse(localStorage.getItem(storeName)) || initialState;
189
+ const storedState =
190
+ JSON.parse(localStorage.getItem(storeName)) || initialState;
175
191
 
176
192
  // Create a store object
177
193
  const store = createStore(storedState);
@@ -183,7 +199,7 @@ export function component(name, options) {
183
199
  * @returns {*} The value of the specified field.
184
200
  */
185
201
  const getField = (fieldName) => {
186
- return store.state[fieldName];
202
+ return store.state[fieldName];
187
203
  };
188
204
 
189
205
  /**
@@ -193,11 +209,11 @@ export function component(name, options) {
193
209
  * @param {*} value - The new value to set for the field.
194
210
  */
195
211
  const setField = (fieldName, value) => {
196
- // Create a new state object with the updated field
197
- const newState = { ...store.state, [fieldName]: value };
198
- // Update the store's state and save it to local storage
199
- store.setState(newState);
200
- saveStateToLocalStorage(storeName, newState);
212
+ // Create a new state object with the updated field
213
+ const newState = { ...store.state, [fieldName]: value };
214
+ // Update the store's state and save it to local storage
215
+ store.setState(newState);
216
+ saveStateToLocalStorage(storeName, newState);
201
217
  };
202
218
 
203
219
  /**
@@ -207,14 +223,14 @@ export function component(name, options) {
207
223
  * @returns {Function} A function to unsubscribe the subscriber.
208
224
  */
209
225
  const subscribe = (subscriber) => {
210
- return store.subscribe(subscriber);
226
+ return store.subscribe(subscriber);
211
227
  };
212
228
 
213
229
  /**
214
230
  * Clear the stored state from local storage.
215
231
  */
216
232
  const clear = () => {
217
- localStorage.removeItem(storeName);
233
+ localStorage.removeItem(storeName);
218
234
  };
219
235
 
220
236
  /**
@@ -224,29 +240,29 @@ export function component(name, options) {
224
240
  * @param {*} state - The state to be stored.
225
241
  */
226
242
  const saveStateToLocalStorage = (key, state) => {
227
- try {
228
- localStorage.setItem(key, JSON.stringify(state));
229
- } catch (error) {
230
- // Handle errors when saving to local storage
231
- console.error('Error saving state to local storage:', error);
232
- }
243
+ try {
244
+ localStorage.setItem(key, JSON.stringify(state));
245
+ } catch (error) {
246
+ // Handle errors when saving to local storage
247
+ console.error("Error saving state to local storage:", error);
248
+ }
233
249
  };
234
250
 
235
251
  return {
236
- getField,
237
- setField,
238
- subscribe,
239
- clear,
252
+ getField,
253
+ setField,
254
+ subscribe,
255
+ clear,
240
256
  };
241
- };
257
+ };
242
258
 
243
259
  /**
244
260
  * @function useAuth
245
261
  * @param {*} rulesets
246
- * @param {*} options
262
+ * @param {*} options
247
263
  * @returns {Object} {canAccess, grantAccess, revokeAccess}
248
264
  * @description Allows you to manage access to resources through rulesets
249
- * @returns
265
+ * @returns
250
266
  */
251
267
 
252
268
  function useAuth(options) {
@@ -366,63 +382,66 @@ export function component(name, options) {
366
382
  window.useAuth = useAuth;
367
383
  window.useSyncStore = useSyncStore;
368
384
  window.useReducer = useReducer;
369
-
385
+
370
386
  const updateComponent = async () => {
371
-
372
-
373
387
  const componentContainer = document.querySelector(
374
388
  `[data-component="${name}"]`
375
- );
389
+ );
376
390
  const newHtml = await options.render(states, storedProps);
377
391
  if (componentContainer && newHtml !== componentContainer.innerHTML) {
378
-
379
- // only update the chunk of DOM that has changed
380
- let newDom = new DOMParser().parseFromString(newHtml, 'text/html')
381
- let oldDom = new DOMParser().parseFromString(componentContainer.innerHTML, 'text/html')
382
- let html = newDom.body.firstChild
383
- let oldHtml = oldDom.body.firstChild
384
- if(!html.isEqualNode(oldHtml)){
385
- // only update the chunk of DOM that has changed
386
- componentContainer.innerHTML = newHtml
387
-
388
- }
392
+ // only update the chunk of DOM that has changed
393
+ let newDom = new DOMParser().parseFromString(newHtml, "text/html");
394
+ let oldDom = new DOMParser().parseFromString(
395
+ componentContainer.innerHTML,
396
+ "text/html"
397
+ );
398
+ let html = newDom.body.firstChild;
399
+ let oldHtml = oldDom.body.firstChild;
400
+ if (!html.isEqualNode(oldHtml)) {
401
+ // only update the chunk of DOM that has changed
402
+ componentContainer.innerHTML = newHtml;
403
+ }
389
404
  if (!componentMounted) {
390
405
  componentMounted = true;
391
-
392
406
 
393
407
  // Execute the "component did mount" code here
394
- if (options.componentDidMount && typeof options.componentDidMount === 'function') {
395
- options.componentUpdate ? options.componentDidMount() : null
408
+ if (
409
+ options.componentDidMount &&
410
+ typeof options.componentDidMount === "function"
411
+ ) {
412
+ options.componentUpdate ? options.componentDidMount() : null;
396
413
  }
397
414
  }
398
415
  }
399
- };
416
+ };
400
417
 
401
418
  /**
402
419
  * @function render
403
420
  * @param {*} states
404
- * @param {*} props
421
+ * @param {*} props
405
422
  * @description Allows you to render component to DOM
406
423
  * @returns {HTMLcContent}
407
- * @returns
424
+ * @returns
408
425
  */
409
426
 
410
427
  const render = async (props) => {
411
-
412
-
413
- options.componentDidMount ? options.componentDidMount() : null
414
- return vhtml`
428
+ options.componentDidMount ? options.componentDidMount() : null;
429
+ return vhtml`
415
430
  <div data-component="${name}">
416
- ${await options.render(
417
- states,
418
- props
419
- )}
431
+ ${await options.render(states, props)}
420
432
  </div>
421
433
  `;
422
- }
434
+ };
423
435
 
424
436
  return {
425
437
  render,
438
+ setState,
439
+ useState,
440
+ useEffect,
441
+ useAuth,
442
+ useSyncStore,
443
+ useReducer,
444
+ runEffects,
426
445
  };
427
446
  }
428
447
 
@@ -440,18 +459,24 @@ export const rf = (name, fn) => {
440
459
  * @function include
441
460
  * @description Allows you to include html file
442
461
  * @returns - modified string with html content
443
- * @param {string} path
462
+ * @param {string} path
444
463
  */
445
464
 
446
- export const include = (path) => {
465
+ let cache = {};
466
+ export const include = (path, options) => {
467
+ if (cache[path]) {
468
+ return new Function(`return \`${cache[path]}\`;`)();
469
+ }
470
+
447
471
  return fetch(`./${path}`)
448
- .then((res) => {
449
- if(res.status === 404){
450
- throw new Error(`No file found at ${path}`)
451
- }
452
- return res.text()
453
- })
454
- .then((data) => {
455
- return new Function(`return \`${data}\`;`)()
456
- })
457
- };
472
+ .then((res) => {
473
+ if (res.status === 404) {
474
+ throw new Error(`No file found at ${path}`);
475
+ }
476
+ return res.text();
477
+ })
478
+ .then((data) => {
479
+ cache[path] = data;
480
+ return new Function(`return \`${data}\`;`)();
481
+ });
482
+ };
package/vaderRouter.js CHANGED
@@ -64,39 +64,11 @@ class VaderRouter {
64
64
  */
65
65
  start() {
66
66
 
67
- if (!this.routes[window.location.hash.substring(1)]) {
67
+ if (window.location.hash === "") {
68
68
  window.location.hash = this.starturl;
69
69
  }
70
70
  window.addEventListener("hashchange", () => {
71
- let hash = window.location.hash.substring(1).split("/")
72
- ? window.location.hash.substring(1).split("/")
73
- : window.location.hash.substring(1);
74
- // remove '' from array
75
- hash = hash.filter((item) => item !== "");
76
- let basePath = "";
77
- if (hash.length > 1) {
78
- basePath = hash[0] + "/" + hash[1];
79
- } else {
80
- basePath = hash[0];
81
- }
82
-
83
- if (!this.routes[basePath] && !this.customerror) {
84
- window.location.hash = this.starturl;
85
- } else if (!this.routes[basePath] && this.customerror) {
86
- const errBody = {
87
- status: 404,
88
- message: "Page not found",
89
- };
90
- const res = {
91
- return: function (data) {
92
- this.hooked = false;
93
- },
94
- render: function (selector, data) {
95
- document.querySelector(selector).innerHTML = data;
96
- }
97
- }
98
- this.handleError("404", errBody, res);
99
- }
71
+ this.handleRoute();
100
72
  });
101
73
  }
102
74
 
@@ -116,6 +88,48 @@ class VaderRouter {
116
88
  this.errorHandlers[type] = callback;
117
89
  this.customerror = true;
118
90
  }
91
+ handleRoute() {
92
+ if (this.hooked) {
93
+ return;
94
+ }
95
+ this.hooked = true;
96
+ const route = window.location.hash.substring(1);
97
+ this.currentUrl = route;
98
+ window.$CURRENT_URL = route;
99
+ window.$URL_PARAMS = {};
100
+ window.$URL_QUERY = {};
101
+ if (this.routes[route]) {
102
+ this.storedroutes.push(route);
103
+ const req = {
104
+ params: {},
105
+ query: {},
106
+ url: route,
107
+ method: "GET",
108
+ };
109
+ const res = {
110
+ return: function (data) {
111
+ this.hooked = false;
112
+ },
113
+ render: function (selector, data) {
114
+ document.querySelector(selector).innerHTML = data;
115
+ },
116
+ };
117
+ this.routes[route] === Function ? this.routes[route](req, res) : null;
118
+ } else {
119
+ if (this.customerror) {
120
+ this.handleError("404", route);
121
+ console.error("404: Route not found");
122
+ } else {
123
+ console.error("404: Route not found");
124
+ }
125
+
126
+ // if route has no content to send return 500
127
+ if (this.routes[route] === undefined || this.hooked === false) {
128
+ this.handleError("500", route);
129
+ console.error("500: Route has no content");
130
+ }
131
+ }
132
+ }
119
133
  /**
120
134
  *
121
135
  * @param {*} type
@@ -127,9 +141,9 @@ class VaderRouter {
127
141
 
128
142
  handleError(type, data, res) {
129
143
  if (this.errorHandlers[type]) {
130
- this.errorHandlers[type](data, res);
131
- } else {
132
- console.error(`No error handler found for type: ${type}`);
144
+ this.errorHandlers[type](data);
145
+ }else{
146
+ console.error("Error: No error handler found for " + type);
133
147
  }
134
148
  }
135
149
  /**
@@ -215,6 +229,7 @@ class VaderRouter {
215
229
 
216
230
  window.$URL_PARAMS = params;
217
231
  window.$URL_QUERY = query;
232
+ window.$CURRENT_URL = window.location.hash.substring(1);
218
233
 
219
234
  const res = {
220
235
  return: function (data) {
@@ -375,6 +390,9 @@ class VaderRouter {
375
390
  return: function (data) {
376
391
  this.hooked = false;
377
392
  },
393
+ send: function (selector, data) {
394
+ document.querySelector(selector).innerHTML = data;
395
+ },
378
396
  render: function (selector, data) {
379
397
  document.querySelector(selector).innerHTML = data;
380
398
  },