vaderjs 1.1.0 → 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.
- package/package.json +1 -1
- package/readme.md +29 -1
- package/vader.js +135 -115
- package/vaderRouter.js +13 -3
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -20,7 +20,7 @@ router.start();
|
|
|
20
20
|
### State Management
|
|
21
21
|
|
|
22
22
|
```javascript
|
|
23
|
-
const [state, setState] = useState(
|
|
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,
|
|
25
|
-
if(dom.body.firstChild.nodeName.toLowerCase() !==
|
|
26
|
-
throw new Error(
|
|
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(
|
|
30
|
-
el.setAttribute(
|
|
31
|
-
el.removeAttribute(
|
|
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
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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 (
|
|
138
|
-
|
|
139
|
-
return state !== oldState;
|
|
140
|
-
});
|
|
141
|
-
if (hasChanged) {
|
|
142
|
-
effectFn();
|
|
143
|
-
}
|
|
147
|
+
if (hasChanged) {
|
|
148
|
+
effectFn();
|
|
144
149
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
150
|
+
}
|
|
151
|
+
} else if (!hasMounted) {
|
|
152
|
+
effectFn();
|
|
153
|
+
hasMounted = true;
|
|
148
154
|
}
|
|
149
155
|
|
|
150
156
|
return () => {
|
|
151
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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 (
|
|
395
|
-
|
|
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,23 +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
|
-
let cache = {}
|
|
447
|
-
export const include = (path) => {
|
|
448
|
-
if(cache[path]){
|
|
449
|
-
return
|
|
465
|
+
let cache = {};
|
|
466
|
+
export const include = (path, options) => {
|
|
467
|
+
if (cache[path]) {
|
|
468
|
+
return new Function(`return \`${cache[path]}\`;`)();
|
|
450
469
|
}
|
|
470
|
+
|
|
451
471
|
return fetch(`./${path}`)
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
};
|
|
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
|
@@ -118,9 +118,16 @@ class VaderRouter {
|
|
|
118
118
|
} else {
|
|
119
119
|
if (this.customerror) {
|
|
120
120
|
this.handleError("404", route);
|
|
121
|
+
console.error("404: Route not found");
|
|
121
122
|
} else {
|
|
122
123
|
console.error("404: Route not found");
|
|
123
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
|
+
}
|
|
124
131
|
}
|
|
125
132
|
}
|
|
126
133
|
/**
|
|
@@ -134,9 +141,9 @@ class VaderRouter {
|
|
|
134
141
|
|
|
135
142
|
handleError(type, data, res) {
|
|
136
143
|
if (this.errorHandlers[type]) {
|
|
137
|
-
this.errorHandlers[type](data
|
|
138
|
-
}
|
|
139
|
-
console.error(
|
|
144
|
+
this.errorHandlers[type](data);
|
|
145
|
+
}else{
|
|
146
|
+
console.error("Error: No error handler found for " + type);
|
|
140
147
|
}
|
|
141
148
|
}
|
|
142
149
|
/**
|
|
@@ -383,6 +390,9 @@ class VaderRouter {
|
|
|
383
390
|
return: function (data) {
|
|
384
391
|
this.hooked = false;
|
|
385
392
|
},
|
|
393
|
+
send: function (selector, data) {
|
|
394
|
+
document.querySelector(selector).innerHTML = data;
|
|
395
|
+
},
|
|
386
396
|
render: function (selector, data) {
|
|
387
397
|
document.querySelector(selector).innerHTML = data;
|
|
388
398
|
},
|