resonantjs 1.0.2 → 1.0.4
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/examples/example-taskmanager.html +14 -2
- package/package.json +1 -1
- package/resonant.js +71 -3
- package/resonant.min.js +1 -0
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
<title>Resonant.js Task Manager Demo</title>
|
|
5
5
|
<script src="../resonant.js"></script>
|
|
6
6
|
</head>
|
|
7
|
+
<style>
|
|
8
|
+
.done {
|
|
9
|
+
text-decoration: line-through;
|
|
10
|
+
}
|
|
11
|
+
</style>
|
|
7
12
|
<body>
|
|
8
13
|
<h1>Resonant.js Task Manager Demo</h1>
|
|
9
14
|
|
|
@@ -18,10 +23,10 @@
|
|
|
18
23
|
<div>
|
|
19
24
|
<h2>Task List</h2>
|
|
20
25
|
<ul res="tasks">
|
|
21
|
-
<li>
|
|
26
|
+
<li res-style="tasks.done ? 'done' : ''">
|
|
22
27
|
<input type="checkbox" res-prop="done" />
|
|
23
28
|
<span res-prop="name"></span>
|
|
24
|
-
<button res-onclick
|
|
29
|
+
<button res-onclick="remove">Remove</button>
|
|
25
30
|
</li>
|
|
26
31
|
</ul>
|
|
27
32
|
</div>
|
|
@@ -43,6 +48,13 @@
|
|
|
43
48
|
console.log(`Action taken: ${action} for ${task.name}`);
|
|
44
49
|
});
|
|
45
50
|
|
|
51
|
+
function remove(task) {
|
|
52
|
+
const index = tasks.indexOf(task);
|
|
53
|
+
tasks.splice(index, 1);
|
|
54
|
+
|
|
55
|
+
//You could use as well, still trying to figure out if I want to leave this or not
|
|
56
|
+
}
|
|
57
|
+
|
|
46
58
|
// Add a function to add a new task
|
|
47
59
|
function addTask() {
|
|
48
60
|
const newTask = { name: taskName, done: false };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "resonantjs",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "A lightweight JavaScript framework that enables reactive data-binding for building dynamic and responsive web applications. It simplifies creating interactive UIs by automatically updating the DOM when your data changes.",
|
|
5
5
|
"main": "resonant.js",
|
|
6
6
|
"repository": {
|
package/resonant.js
CHANGED
|
@@ -69,6 +69,7 @@ class Resonant {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
_queueUpdate(variableName, action, item, property, oldValue) {
|
|
72
|
+
|
|
72
73
|
if (!this.pendingUpdates.has(variableName)) {
|
|
73
74
|
this.pendingUpdates.add(variableName);
|
|
74
75
|
setTimeout(() => {
|
|
@@ -76,6 +77,7 @@ class Resonant {
|
|
|
76
77
|
this._triggerCallbacks(variableName, action, item, property, oldValue);
|
|
77
78
|
this.updateElement(variableName);
|
|
78
79
|
this.updateConditionalsFor(variableName);
|
|
80
|
+
this.updateStylesFor(variableName);
|
|
79
81
|
}, 0);
|
|
80
82
|
}
|
|
81
83
|
}
|
|
@@ -93,6 +95,7 @@ class Resonant {
|
|
|
93
95
|
this._assignValueToData(variableName, newValue);
|
|
94
96
|
this.updateElement(variableName);
|
|
95
97
|
this.updateConditionalsFor(variableName);
|
|
98
|
+
this.updateStylesFor(variableName);
|
|
96
99
|
if (!Array.isArray(newValue) && typeof newValue !== 'object') {
|
|
97
100
|
this._queueUpdate(variableName, 'modified', this.data[variableName]);
|
|
98
101
|
}
|
|
@@ -104,12 +107,15 @@ class Resonant {
|
|
|
104
107
|
const elements = document.querySelectorAll(`[res="${variableName}"]`);
|
|
105
108
|
const value = this.data[variableName];
|
|
106
109
|
|
|
110
|
+
|
|
111
|
+
|
|
107
112
|
elements.forEach(element => {
|
|
108
113
|
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
|
|
114
|
+
element.value = value;
|
|
109
115
|
if (!element.hasAttribute('data-resonant-bound')) {
|
|
110
|
-
element.value = value;
|
|
111
116
|
element.oninput = () => {
|
|
112
117
|
this.data[variableName] = element.value;
|
|
118
|
+
this._queueUpdate(variableName, 'modified', this.data[variableName]);
|
|
113
119
|
};
|
|
114
120
|
element.setAttribute('data-resonant-bound', 'true');
|
|
115
121
|
}
|
|
@@ -138,6 +144,16 @@ class Resonant {
|
|
|
138
144
|
subEl.innerHTML = value[key];
|
|
139
145
|
}
|
|
140
146
|
subEl.setAttribute('data-resonant-bound', 'true');
|
|
147
|
+
} else {
|
|
148
|
+
if (subEl.tagName === 'INPUT' || subEl.tagName === 'TEXTAREA') {
|
|
149
|
+
if (subEl.type === 'checkbox') {
|
|
150
|
+
subEl.checked = value[key];
|
|
151
|
+
} else {
|
|
152
|
+
subEl.value = value[key];
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
subEl.innerHTML = value[key];
|
|
156
|
+
}
|
|
141
157
|
}
|
|
142
158
|
}
|
|
143
159
|
});
|
|
@@ -147,6 +163,7 @@ class Resonant {
|
|
|
147
163
|
});
|
|
148
164
|
|
|
149
165
|
this.updateConditionalsFor(variableName);
|
|
166
|
+
this.updateStylesFor(variableName);
|
|
150
167
|
}
|
|
151
168
|
|
|
152
169
|
updateConditionalsFor(variableName) {
|
|
@@ -165,6 +182,49 @@ class Resonant {
|
|
|
165
182
|
});
|
|
166
183
|
}
|
|
167
184
|
|
|
185
|
+
updateStylesFor(variableName) {
|
|
186
|
+
const styleElements = document.querySelectorAll(`[res-style*="${variableName}"]`);
|
|
187
|
+
|
|
188
|
+
styleElements.forEach(styleElement => {
|
|
189
|
+
let styleCondition = styleElement.getAttribute('res-style');
|
|
190
|
+
try {
|
|
191
|
+
let parent = styleElement;
|
|
192
|
+
let index = null;
|
|
193
|
+
while (parent && !index) {
|
|
194
|
+
index = parent.getAttribute('res-index');
|
|
195
|
+
parent = parent.parentElement;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (index !== null) {
|
|
199
|
+
const item = this.data[variableName][index];
|
|
200
|
+
styleCondition = styleCondition.replace(new RegExp(`\\b${variableName}\\b`, 'g'), 'item');
|
|
201
|
+
const styleClass = new Function('item', `return ${styleCondition}`)(item);
|
|
202
|
+
|
|
203
|
+
if (styleClass) {
|
|
204
|
+
styleElement.classList.add(styleClass);
|
|
205
|
+
} else {
|
|
206
|
+
var elementHasStyle = styleElement.classList.contains(styleClass);
|
|
207
|
+
if (elementHasStyle) {
|
|
208
|
+
styleElement.classList.remove(styleClass);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} else {
|
|
212
|
+
const styleClass = eval(styleCondition);
|
|
213
|
+
if (styleClass) {
|
|
214
|
+
styleElement.classList.add(styleClass);
|
|
215
|
+
} else {
|
|
216
|
+
var elementHasStyle = styleElement.classList.contains(styleClass);
|
|
217
|
+
if (elementHasStyle) {
|
|
218
|
+
styleElement.classList.remove(styleClass);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} catch (e) {
|
|
223
|
+
console.error(`Error evaluating style for ${variableName}: ${styleCondition}`, e);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
|
|
168
228
|
_renderArray(variableName, el) {
|
|
169
229
|
let template = el.cloneNode(true);
|
|
170
230
|
el.innerHTML = '';
|
|
@@ -200,18 +260,26 @@ class Resonant {
|
|
|
200
260
|
subEl.innerHTML = instance[key];
|
|
201
261
|
}
|
|
202
262
|
subEl.setAttribute('data-resonant-bound', 'true');
|
|
263
|
+
} else {
|
|
264
|
+
if (subEl.tagName === 'INPUT' || subEl.tagName === 'TEXTAREA') {
|
|
265
|
+
if (subEl.type === 'checkbox') {
|
|
266
|
+
subEl.checked = instance[key];
|
|
267
|
+
} else {
|
|
268
|
+
subEl.value = instance[key];
|
|
269
|
+
}
|
|
270
|
+
} else {
|
|
271
|
+
subEl.innerHTML = instance[key];
|
|
272
|
+
}
|
|
203
273
|
}
|
|
204
274
|
}
|
|
205
275
|
}
|
|
206
276
|
|
|
207
|
-
// Handle res-onclick
|
|
208
277
|
const onclickElements = clonedEl.querySelectorAll('[res-onclick], [res-onclick-remove]');
|
|
209
278
|
onclickElements.forEach(onclickEl => {
|
|
210
279
|
const functionName = onclickEl.getAttribute('res-onclick');
|
|
211
280
|
const removeKey = onclickEl.getAttribute('res-onclick-remove');
|
|
212
281
|
|
|
213
282
|
if (functionName) {
|
|
214
|
-
// Remove any existing event listeners to prevent duplicates
|
|
215
283
|
onclickEl.onclick = null;
|
|
216
284
|
|
|
217
285
|
onclickEl.onclick = () => {
|
package/resonant.min.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class Resonant{constructor(){this.data={},this.callbacks={},this.pendingUpdates=new Set}add(e,t){this._assignValueToData(e,t),this._defineProperty(e),this.updateElement(e)}addAll(e){Object.entries(e).forEach(([e,t])=>{this.add(e,t)})}_assignValueToData(e,t){Array.isArray(t)?this.data[e]=this._createArray(e,t):this.data[e]="object"==typeof t?this._createObject(e,t):t}_createObject(n,e){return e[Symbol("isProxy")]=!0,new Proxy(e,{set:(e,t,a)=>{var s;return e[t]!==a&&(s=e[t],e[t]=a,this._queueUpdate(n,"modified",e,t,s)),!0}})}_createArray(l,e){const i=this;return new Proxy(e,{get(e,t){return"object"!=typeof e[t]||e[t][Symbol("isProxy")]||(e[t]=i._createObject(l+`[${t}]`,e[t])),e[t]},set(e,t,a){var s,n;return e[t]!==a&&(s=e.hasOwnProperty(t)?"modified":"added",n=e[t],e[t]=a,i._queueUpdate(l,s,e[t],t,n)),!0},deleteProperty(e,t){var a=e[t];return e.splice(t,1),i._queueUpdate(l,"removed",a,t),!0}})}_queueUpdate(e,t,a,s,n){this.pendingUpdates.has(e)||(this.pendingUpdates.add(e),setTimeout(()=>{this.pendingUpdates.delete(e),this._triggerCallbacks(e,t,a,s,n),this.updateElement(e),this.updateConditionalsFor(e),this.updateStylesFor(e)},0))}_triggerCallbacks(t,a,s,n,l){this.callbacks[t]&&this.callbacks[t].forEach(e=>e(this.data[t],s,a,n,l))}_defineProperty(t){Object.defineProperty(window,t,{get:()=>this.data[t],set:e=>{this._assignValueToData(t,e),this.updateElement(t),this.updateConditionalsFor(t),this.updateStylesFor(t),Array.isArray(e)||"object"==typeof e||this._queueUpdate(t,"modified",this.data[t])}})}updateElement(a){var e=document.querySelectorAll(`[res="${a}"]`);const s=this.data[a];e.forEach(e=>{"INPUT"===e.tagName||"TEXTAREA"===e.tagName?(e.value=s,e.hasAttribute("data-resonant-bound")||(e.oninput=()=>{this.data[a]=e.value,this._queueUpdate(a,"modified",this.data[a])},e.setAttribute("data-resonant-bound","true"))):Array.isArray(s)?(e.querySelectorAll(`[res="${a}"][res-rendered=true]`).forEach(e=>e.remove()),this._renderArray(a,e)):"object"==typeof s?e.querySelectorAll("[res-prop]").forEach(e=>{const t=e.getAttribute("res-prop");t&&t in s&&(e.hasAttribute("data-resonant-bound")?"INPUT"===e.tagName||"TEXTAREA"===e.tagName?"checkbox"===e.type?e.checked=s[t]:e.value=s[t]:e.innerHTML=s[t]:("INPUT"===e.tagName||"TEXTAREA"===e.tagName?"checkbox"===e.type?(e.checked=s[t],e.onchange=()=>{this.data[a][t]=e.checked}):(e.value=s[t],e.oninput=()=>{this.data[a][t]=e.value}):e.innerHTML=s[t],e.setAttribute("data-resonant-bound","true")))}):e.innerHTML=s}),this.updateConditionalsFor(a),this.updateStylesFor(a)}updateConditionalsFor(variableName){const conditionalElements=document.querySelectorAll(`[res-conditional*="${variableName}"]`);conditionalElements.forEach(conditionalElement=>{const condition=conditionalElement.getAttribute("res-conditional");try{eval(condition)?conditionalElement.style.display="":conditionalElement.style.display="none"}catch(e){}})}updateStylesFor(variableName){const styleElements=document.querySelectorAll(`[res-style*="${variableName}"]`);styleElements.forEach(styleElement=>{let styleCondition=styleElement.getAttribute("res-style");try{let parent=styleElement,index=null;for(;parent&&!index;)index=parent.getAttribute("res-index"),parent=parent.parentElement;if(null!==index){const item=this.data[variableName][index],styleClass=(styleCondition=styleCondition.replace(new RegExp(`\\b${variableName}\\b`,"g"),"item"),new Function("item","return "+styleCondition)(item));var elementHasStyle;styleClass?styleElement.classList.add(styleClass):(elementHasStyle=styleElement.classList.contains(styleClass),elementHasStyle&&styleElement.classList.remove(styleClass))}else{const styleClass=eval(styleCondition);var elementHasStyle;styleClass?styleElement.classList.add(styleClass):(elementHasStyle=styleElement.classList.contains(styleClass),elementHasStyle&&styleElement.classList.remove(styleClass))}}catch(e){}})}_renderArray(n,l){let i=l.cloneNode(!0);l.innerHTML="",window[n+"_template"]?i=window[n+"_template"]:window[n+"_template"]=i,this.data[n].forEach((s,e)=>{var t=i.cloneNode(!0);t.setAttribute("res-index",e);for(let e in s){const a=t.querySelector(`[res-prop="${e}"]`);a&&(a.hasAttribute("data-resonant-bound")?"INPUT"===a.tagName||"TEXTAREA"===a.tagName?"checkbox"===a.type?a.checked=s[e]:a.value=s[e]:a.innerHTML=s[e]:("INPUT"===a.tagName||"TEXTAREA"===a.tagName?"checkbox"===a.type?(a.checked=s[e],a.onchange=()=>{s[e]=a.checked,this._queueUpdate(n,"modified",s,e,s[e])}):(a.value=s[e],a.oninput=()=>{s[e]=a.value,this._queueUpdate(n,"modified",s,e,s[e])}):a.innerHTML=s[e],a.setAttribute("data-resonant-bound","true")))}t.querySelectorAll("[res-onclick], [res-onclick-remove]").forEach(e=>{const t=e.getAttribute("res-onclick"),a=e.getAttribute("res-onclick-remove");t&&(e.onclick=null,e.onclick=()=>{new Function("item",`return ${t}(item)`)(s)}),a&&(e.onclick=null,e.onclick=()=>{var e=this.data[n].findIndex(e=>e[a]===s[a]);-1!==e&&this.data[n].splice(e,1)})}),t.setAttribute("res-rendered",!0),l.appendChild(t)})}addCallback(e,t){this.callbacks[e]||(this.callbacks[e]=[]),this.callbacks[e].push(t)}}
|