resonantjs 1.1.2 → 1.1.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-basic.html +4 -2
- package/examples/tests.html +452 -0
- package/package.json +1 -1
- package/resonant.js +197 -60
- package/resonant.min.js +1 -1
|
@@ -102,8 +102,8 @@
|
|
|
102
102
|
<section class="grid">
|
|
103
103
|
<div>
|
|
104
104
|
<h2>Project Members</h2>
|
|
105
|
-
<ul
|
|
106
|
-
<li>
|
|
105
|
+
<ul>
|
|
106
|
+
<li res="projectTeam">
|
|
107
107
|
<span res-prop="name"></span> - <span res-prop="role"></span>
|
|
108
108
|
</li>
|
|
109
109
|
</ul>
|
|
@@ -207,6 +207,8 @@
|
|
|
207
207
|
]
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
+
|
|
211
|
+
|
|
210
212
|
// Chain together callbacks
|
|
211
213
|
resonantJs.addCallback("user", (user) => {
|
|
212
214
|
console.log(`User updated: ${user.firstname} ${user.lastname}`);
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<title>Resonant.js Practical Demo</title>
|
|
5
|
+
<link
|
|
6
|
+
rel="stylesheet"
|
|
7
|
+
href="https://cdn.jsdelivr.net/npm/@picocss/pico@2.0.6/css/pico.min.css"
|
|
8
|
+
/>
|
|
9
|
+
<script src="../resonant.js"></script>
|
|
10
|
+
<style>
|
|
11
|
+
.success {
|
|
12
|
+
background-color: #0172ad;
|
|
13
|
+
}
|
|
14
|
+
.fail {
|
|
15
|
+
background-color: #883935;
|
|
16
|
+
}
|
|
17
|
+
section.grid {
|
|
18
|
+
padding: 1rem;
|
|
19
|
+
margin-bottom: 2rem;
|
|
20
|
+
border: 1px solid #e0e0e0;
|
|
21
|
+
border-radius: 0.5rem;
|
|
22
|
+
}
|
|
23
|
+
</style>
|
|
24
|
+
</head>
|
|
25
|
+
<body>
|
|
26
|
+
|
|
27
|
+
<main class="container">
|
|
28
|
+
<h1>Variable/Object/Array update tests</h1>
|
|
29
|
+
<section class="grid">
|
|
30
|
+
<div>
|
|
31
|
+
<h2>Basic Variable: <span res="basicVariable"></span></h2>
|
|
32
|
+
<div res-display="basicVariable == 'Initial'">
|
|
33
|
+
Has not been updated yet
|
|
34
|
+
</div>
|
|
35
|
+
<div res-display="basicVariable != 'Initial'">
|
|
36
|
+
Has been updated
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
<div>
|
|
40
|
+
<button class="runTest" data-type="basicVariable">Run Test</button>
|
|
41
|
+
</div>
|
|
42
|
+
</section>
|
|
43
|
+
<section class="grid">
|
|
44
|
+
<div res="simpleObject">
|
|
45
|
+
<h2>Simple Object: </h2>
|
|
46
|
+
First Name: <span res-prop="firstName"></span>
|
|
47
|
+
Last Name: <span res-prop="lastName"></span>
|
|
48
|
+
|
|
49
|
+
<div res-display="firstName == 'John'">
|
|
50
|
+
Has not been updated yet
|
|
51
|
+
</div>
|
|
52
|
+
<div res-display="firstName != 'John'">
|
|
53
|
+
Has been updated
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div>
|
|
57
|
+
<button class="runTest" data-type="simpleObject">Run Test</button>
|
|
58
|
+
</div>
|
|
59
|
+
</section>
|
|
60
|
+
<section class="grid">
|
|
61
|
+
<div>
|
|
62
|
+
<h2>Simple Array Update: </h2>
|
|
63
|
+
<ul>
|
|
64
|
+
<li res="simpleArrayUpdate">
|
|
65
|
+
<span res-prop="firstName"></span> <span res-prop="lastName"></span>
|
|
66
|
+
</li>
|
|
67
|
+
</ul>
|
|
68
|
+
|
|
69
|
+
<div res-display="simpleArrayUpdate[0].firstName == 'John'">
|
|
70
|
+
Has not been updated yet
|
|
71
|
+
</div>
|
|
72
|
+
<div res-display="simpleArrayUpdate[0].firstName != 'John'">
|
|
73
|
+
Has been updated
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
<div>
|
|
77
|
+
<button class="runTest" data-type="simpleArrayUpdate">Run Test</button>
|
|
78
|
+
</div>
|
|
79
|
+
</section>
|
|
80
|
+
<section class="grid">
|
|
81
|
+
<div>
|
|
82
|
+
<h2>Simple Array Add: </h2>
|
|
83
|
+
<ul>
|
|
84
|
+
<li res="simpleArrayAdd">
|
|
85
|
+
<span res-prop="firstName"></span> <span res-prop="lastName"></span>
|
|
86
|
+
</li>
|
|
87
|
+
</ul>
|
|
88
|
+
|
|
89
|
+
<div res-display="simpleArrayAdd[2] === undefined">
|
|
90
|
+
Has not been updated yet
|
|
91
|
+
</div>
|
|
92
|
+
<div res-display="simpleArrayAdd[2] !== undefined">
|
|
93
|
+
Has been updated
|
|
94
|
+
</div>
|
|
95
|
+
</div>
|
|
96
|
+
<div>
|
|
97
|
+
<button class="runTest" data-type="simpleArrayAdd">Run Test</button>
|
|
98
|
+
</div>
|
|
99
|
+
</section>
|
|
100
|
+
<section class="grid">
|
|
101
|
+
<div>
|
|
102
|
+
<h2>Simple Array Remove: </h2>
|
|
103
|
+
<ul>
|
|
104
|
+
<li res="simpleArrayRemove">
|
|
105
|
+
<span res-prop="firstName"></span> <span res-prop="lastName"></span>
|
|
106
|
+
</li>
|
|
107
|
+
</ul>
|
|
108
|
+
|
|
109
|
+
<div res-display="simpleArrayRemove[0].firstName == 'John'">
|
|
110
|
+
Has not been updated yet
|
|
111
|
+
</div>
|
|
112
|
+
<div res-display="simpleArrayRemove[0].firstName != 'John'">
|
|
113
|
+
Has been updated
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
<div>
|
|
117
|
+
<button class="runTest" data-type="simpleArrayRemove">Run Test</button>
|
|
118
|
+
</div>
|
|
119
|
+
</section>
|
|
120
|
+
<section class="grid">
|
|
121
|
+
<div>
|
|
122
|
+
<h2>Complex Array With Children Update: </h2>
|
|
123
|
+
<hr>
|
|
124
|
+
<div>
|
|
125
|
+
<div res="complexArrayWithChildrenUpdate">
|
|
126
|
+
<h3><span res-prop="firstName"></span> <span res-prop="lastName"></span></h3>
|
|
127
|
+
|
|
128
|
+
Phone Numbers:
|
|
129
|
+
<ul>
|
|
130
|
+
<li res-prop="phoneNumbers">
|
|
131
|
+
</li>
|
|
132
|
+
</ul>
|
|
133
|
+
Addresses
|
|
134
|
+
<ul>
|
|
135
|
+
<li res-prop="addresses">
|
|
136
|
+
<span res-prop="city"></span>,
|
|
137
|
+
<span res-prop="state"></span>
|
|
138
|
+
</li>
|
|
139
|
+
</ul>
|
|
140
|
+
<hr>
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
<div res-display="complexArrayWithChildrenUpdate[0].firstName == 'John'">
|
|
146
|
+
Has not been updated yet
|
|
147
|
+
</div>
|
|
148
|
+
<div res-display="complexArrayWithChildrenUpdate[0].firstName != 'John'">
|
|
149
|
+
Has been updated
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
<div>
|
|
153
|
+
<button class="runTest" data-type="complexArrayWithChildrenUpdate">Run Test</button>
|
|
154
|
+
</div>
|
|
155
|
+
</section>
|
|
156
|
+
<section class="grid">
|
|
157
|
+
<div>
|
|
158
|
+
<h2>Complex Array With Children Display Update: </h2>
|
|
159
|
+
<hr>
|
|
160
|
+
<div>
|
|
161
|
+
<div res="complexArrayWithChildrenDisplayUpdate">
|
|
162
|
+
<h3><span res-prop="firstName"></span> <span res-prop="lastName"></span></h3>
|
|
163
|
+
|
|
164
|
+
<div res-display="attributes.showOnPage">
|
|
165
|
+
Show on page
|
|
166
|
+
</div>
|
|
167
|
+
<hr>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</div>
|
|
171
|
+
<div>
|
|
172
|
+
<button class="runTest" data-type="complexArrayWithChildrenDisplayUpdate">Run Test</button>
|
|
173
|
+
</div>
|
|
174
|
+
</section>
|
|
175
|
+
<section class="grid">
|
|
176
|
+
<div>
|
|
177
|
+
<h2>Complex Array With Arrays Display Test: </h2>
|
|
178
|
+
<hr>
|
|
179
|
+
<div>
|
|
180
|
+
<div res="complexArrayWithArrayDisplayUpdate">
|
|
181
|
+
<h3>
|
|
182
|
+
<span res-prop="firstName"></span>
|
|
183
|
+
<span res-prop="lastName"></span>
|
|
184
|
+
</h3>
|
|
185
|
+
|
|
186
|
+
<div res-prop="attributes">
|
|
187
|
+
<div res-prop="name">
|
|
188
|
+
</div>
|
|
189
|
+
<div res-display="showOnPage">
|
|
190
|
+
Show on page
|
|
191
|
+
</div>
|
|
192
|
+
</div>
|
|
193
|
+
<hr>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
</div>
|
|
197
|
+
<div>
|
|
198
|
+
<button class="runTest" data-type="complexArrayWithArrayDisplayUpdate">Run Test</button>
|
|
199
|
+
</div>
|
|
200
|
+
</section>
|
|
201
|
+
<button onclick="runAllTests()">Run All Tests</button>
|
|
202
|
+
</main>
|
|
203
|
+
|
|
204
|
+
<script>
|
|
205
|
+
const resonantJs = new Resonant();
|
|
206
|
+
resonantJs.add("basicVariable", "Initial");
|
|
207
|
+
resonantJs.add("simpleObject", {
|
|
208
|
+
firstName: "John",
|
|
209
|
+
lastName: "Doe"
|
|
210
|
+
});
|
|
211
|
+
resonantJs.add("simpleArrayUpdate", [
|
|
212
|
+
{
|
|
213
|
+
firstName: "John",
|
|
214
|
+
lastName: "Doe"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
firstName: "Jane",
|
|
218
|
+
lastName: "Doe"
|
|
219
|
+
}
|
|
220
|
+
]);
|
|
221
|
+
resonantJs.add("simpleArrayAdd", [
|
|
222
|
+
{
|
|
223
|
+
firstName: "John",
|
|
224
|
+
lastName: "Doe"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
firstName: "Jane",
|
|
228
|
+
lastName: "Doe"
|
|
229
|
+
}
|
|
230
|
+
]);
|
|
231
|
+
resonantJs.add("simpleArrayRemove", [
|
|
232
|
+
{
|
|
233
|
+
firstName: "John",
|
|
234
|
+
lastName: "Doe"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
firstName: "Jane",
|
|
238
|
+
lastName: "Doe"
|
|
239
|
+
}
|
|
240
|
+
]);
|
|
241
|
+
resonantJs.add("complexArrayWithChildrenUpdate", [
|
|
242
|
+
{
|
|
243
|
+
firstName: "Michael",
|
|
244
|
+
lastName: "Smith",
|
|
245
|
+
phoneNumbers: [
|
|
246
|
+
"555-123-4567",
|
|
247
|
+
"555-987-6543"
|
|
248
|
+
],
|
|
249
|
+
addresses: [
|
|
250
|
+
{
|
|
251
|
+
city: "Chicago",
|
|
252
|
+
state: "IL"
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
city: "Seattle",
|
|
256
|
+
state: "WA"
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
city: "New York",
|
|
260
|
+
state: "NY"
|
|
261
|
+
}
|
|
262
|
+
]
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
firstName: "Sarah",
|
|
266
|
+
lastName: "Johnson",
|
|
267
|
+
phoneNumbers: [
|
|
268
|
+
"555-234-5678",
|
|
269
|
+
"555-876-5432",
|
|
270
|
+
"555-345-6789"
|
|
271
|
+
],
|
|
272
|
+
addresses: [
|
|
273
|
+
{
|
|
274
|
+
city: "Boston",
|
|
275
|
+
state: "MA"
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
city: "Austin",
|
|
279
|
+
state: "TX"
|
|
280
|
+
}
|
|
281
|
+
]
|
|
282
|
+
}
|
|
283
|
+
]);
|
|
284
|
+
resonantJs.add("complexArrayWithChildrenDisplayUpdate", [
|
|
285
|
+
{
|
|
286
|
+
firstName: "Michael",
|
|
287
|
+
lastName: "Smith",
|
|
288
|
+
attributes:
|
|
289
|
+
{
|
|
290
|
+
showOnPage: true
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
firstName: "Sarah",
|
|
295
|
+
lastName: "Johnson",
|
|
296
|
+
attributes:
|
|
297
|
+
{
|
|
298
|
+
showOnPage: true
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
]);
|
|
302
|
+
resonantJs.add("complexArrayWithArrayDisplayUpdate", [
|
|
303
|
+
{
|
|
304
|
+
firstName: "Michael",
|
|
305
|
+
lastName: "Smith",
|
|
306
|
+
attributes: [
|
|
307
|
+
{
|
|
308
|
+
name: "Personal",
|
|
309
|
+
showOnPage: true
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
name: "Not Personal",
|
|
313
|
+
showOnPage: true
|
|
314
|
+
}]
|
|
315
|
+
}
|
|
316
|
+
]);
|
|
317
|
+
|
|
318
|
+
function runAllTests() {
|
|
319
|
+
let tests = document.querySelectorAll(".runTest");
|
|
320
|
+
tests.forEach(test => {
|
|
321
|
+
test.click();
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function runTest() {
|
|
326
|
+
|
|
327
|
+
//hide button
|
|
328
|
+
this.style.display = "none";
|
|
329
|
+
|
|
330
|
+
let parentElement = this.parentElement.parentElement;
|
|
331
|
+
let dataAttribute = this.getAttribute("data-type");
|
|
332
|
+
let expectedText = "";
|
|
333
|
+
|
|
334
|
+
console.log("Parent element inner text: ", parentElement.innerText);
|
|
335
|
+
|
|
336
|
+
if(dataAttribute === "basicVariable") {
|
|
337
|
+
basicVariable = "Updated";
|
|
338
|
+
|
|
339
|
+
expectedText = `Basic Variable: Updated
|
|
340
|
+
Has been updated`;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if(dataAttribute === "simpleObject") {
|
|
344
|
+
simpleObject.firstName = "Jane";
|
|
345
|
+
simpleObject.lastName = "Doe";
|
|
346
|
+
|
|
347
|
+
expectedText = `Simple Object:
|
|
348
|
+
First Name: Jane
|
|
349
|
+
Last Name: Doe
|
|
350
|
+
Has been updated`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if(dataAttribute === "simpleArrayUpdate") {
|
|
354
|
+
simpleArrayUpdate[0].firstName = "Josh";
|
|
355
|
+
simpleArrayUpdate[0].lastName = "Forger";
|
|
356
|
+
simpleArrayUpdate[1].firstName = "Matilda";
|
|
357
|
+
simpleArrayUpdate[1].lastName = "Swinson";
|
|
358
|
+
|
|
359
|
+
expectedText = `Simple Array Update:
|
|
360
|
+
Josh Forger
|
|
361
|
+
Matilda Swinson
|
|
362
|
+
Has been updated`;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if(dataAttribute === "simpleArrayAdd") {
|
|
366
|
+
simpleArrayAdd.push({
|
|
367
|
+
firstName: "Josh",
|
|
368
|
+
lastName: "Forger"
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
expectedText = `Simple Array Add:
|
|
372
|
+
John Doe
|
|
373
|
+
Jane Doe
|
|
374
|
+
Josh Forger
|
|
375
|
+
Has been updated`;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if(dataAttribute === "simpleArrayRemove") {
|
|
379
|
+
simpleArrayRemove.splice(0, 1);
|
|
380
|
+
|
|
381
|
+
expectedText = `Simple Array Remove:
|
|
382
|
+
Jane Doe
|
|
383
|
+
Has been updated`;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if(dataAttribute === "complexArrayWithChildrenUpdate") {
|
|
387
|
+
complexArrayWithChildrenUpdate[0].firstName = "Josh";
|
|
388
|
+
complexArrayWithChildrenUpdate[0].lastName = "Forger";
|
|
389
|
+
complexArrayWithChildrenUpdate[0].phoneNumbers[0] = "123-123-1234";
|
|
390
|
+
complexArrayWithChildrenUpdate[0].addresses[1].city = "Hudson";
|
|
391
|
+
complexArrayWithChildrenUpdate[0].addresses[1].state = "OH";
|
|
392
|
+
|
|
393
|
+
expectedText = `Complex Array With Children Update:
|
|
394
|
+
Josh Forger
|
|
395
|
+
Phone Numbers:
|
|
396
|
+
123-123-1234
|
|
397
|
+
555-987-6543
|
|
398
|
+
Addresses
|
|
399
|
+
Chicago, IL
|
|
400
|
+
Hudson, OH
|
|
401
|
+
New York, NY
|
|
402
|
+
Sarah Johnson
|
|
403
|
+
Phone Numbers:
|
|
404
|
+
555-234-5678
|
|
405
|
+
555-876-5432
|
|
406
|
+
555-345-6789
|
|
407
|
+
Addresses
|
|
408
|
+
Boston, MA
|
|
409
|
+
Austin, TX
|
|
410
|
+
Has been updated
|
|
411
|
+
`;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if(dataAttribute === "complexArrayWithChildrenDisplayUpdate") {
|
|
415
|
+
complexArrayWithChildrenDisplayUpdate[0].attributes.showOnPage = false;
|
|
416
|
+
// requires a force update unfortunately
|
|
417
|
+
complexArrayWithChildrenDisplayUpdate.forceUpdate();
|
|
418
|
+
|
|
419
|
+
expectedText = `Complex Array With Children Display Update:
|
|
420
|
+
Michael Smith
|
|
421
|
+
Sarah Johnson
|
|
422
|
+
Show on page
|
|
423
|
+
`;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
setTimeout(() => {
|
|
427
|
+
visibleTextCompare(parentElement,expectedText);
|
|
428
|
+
}, 1000)
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
document.querySelectorAll(".runTest").forEach(test => {
|
|
432
|
+
test.addEventListener("click", runTest);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
function visibleTextCompare(element, expectedText) {
|
|
436
|
+
let visibleText = element.innerText;
|
|
437
|
+
|
|
438
|
+
visibleText = visibleText.replace(/\s/g, "").replace("RunTest", "");
|
|
439
|
+
expectedText = expectedText.replace(/\s/g, "").replace("RunTest", "");
|
|
440
|
+
|
|
441
|
+
let result = visibleText === expectedText;
|
|
442
|
+
|
|
443
|
+
if(!result) {
|
|
444
|
+
console.log("Visible text: ", visibleText);
|
|
445
|
+
console.log("Expected text: ", expectedText);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
element.classList.add(result ? "success" : "fail");
|
|
449
|
+
}
|
|
450
|
+
</script>
|
|
451
|
+
</body>
|
|
452
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "resonantjs",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.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
|
@@ -13,6 +13,7 @@ class ObservableArray extends Array {
|
|
|
13
13
|
|
|
14
14
|
this.forEach((item, index) => {
|
|
15
15
|
if (typeof item === 'object') {
|
|
16
|
+
this._setKeyForObjectAndChildObjects(item, index);
|
|
16
17
|
this[index] = this._createProxy(item, index);
|
|
17
18
|
}
|
|
18
19
|
});
|
|
@@ -22,12 +23,53 @@ class ObservableArray extends Array {
|
|
|
22
23
|
}
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
_setKeyForObjectAndChildObjects(obj, index) {
|
|
27
|
+
if (typeof obj === 'object') {
|
|
28
|
+
obj.key = index + '-' + this._generateKeyByContentCheckSum(obj);
|
|
29
|
+
Object.keys(obj).forEach(key => {
|
|
30
|
+
this._setKeyForObjectAndChildObjects(obj[key], index);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_generateKeyByContentCheckSum(obj) {
|
|
36
|
+
const removeKeysRecursively = (obj) => {
|
|
37
|
+
if (!obj || typeof obj !== 'object') return obj;
|
|
38
|
+
|
|
39
|
+
if (Array.isArray(obj)) {
|
|
40
|
+
return obj.map(item => removeKeysRecursively(item));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const newObj = {...obj};
|
|
44
|
+
delete newObj.key;
|
|
45
|
+
|
|
46
|
+
Object.keys(newObj).forEach(key => {
|
|
47
|
+
if (typeof newObj[key] === 'object' && newObj[key] !== null) {
|
|
48
|
+
newObj[key] = removeKeysRecursively(newObj[key]);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return newObj;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const objForHash = removeKeysRecursively(obj);
|
|
56
|
+
const str = JSON.stringify(objForHash);
|
|
57
|
+
let hash = 0;
|
|
58
|
+
for (let i = 0; i < str.length; i++) {
|
|
59
|
+
const char = str.charCodeAt(i);
|
|
60
|
+
hash = ((hash << 5) - hash) + char;
|
|
61
|
+
hash = hash & hash;
|
|
62
|
+
}
|
|
63
|
+
return Math.abs(hash).toString(36);
|
|
64
|
+
}
|
|
65
|
+
|
|
25
66
|
_createProxy(item, index) {
|
|
26
67
|
return new Proxy(item, {
|
|
27
68
|
set: (target, property, value) => {
|
|
28
69
|
if (target[property] !== value) {
|
|
29
70
|
const oldValue = target[property];
|
|
30
71
|
target[property] = value;
|
|
72
|
+
this._setKeyForObjectAndChildObjects(item, index);
|
|
31
73
|
this.resonantInstance._queueUpdate(this.variableName, 'modified', target, property, oldValue, index);
|
|
32
74
|
}
|
|
33
75
|
return true;
|
|
@@ -36,6 +78,9 @@ class ObservableArray extends Array {
|
|
|
36
78
|
}
|
|
37
79
|
|
|
38
80
|
forceUpdate() {
|
|
81
|
+
this.forEach((item, index) => {
|
|
82
|
+
this._setKeyForObjectAndChildObjects(item, index);
|
|
83
|
+
});
|
|
39
84
|
this.resonantInstance.arrayDataChangeDetection[this.variableName] = this.slice();
|
|
40
85
|
this.resonantInstance._queueUpdate(this.variableName, 'modified', this.slice());
|
|
41
86
|
}
|
|
@@ -48,6 +93,7 @@ class ObservableArray extends Array {
|
|
|
48
93
|
push(...args) {
|
|
49
94
|
args = args.map((item, index) => {
|
|
50
95
|
if (typeof item === 'object') {
|
|
96
|
+
this._setKeyForObjectAndChildObjects(item, this.length + index);
|
|
51
97
|
return this._createProxy(item, this.length + index);
|
|
52
98
|
}
|
|
53
99
|
return item;
|
|
@@ -212,10 +258,60 @@ class Resonant {
|
|
|
212
258
|
});
|
|
213
259
|
}
|
|
214
260
|
|
|
215
|
-
_evaluateDisplayCondition(element,
|
|
261
|
+
_evaluateDisplayCondition(element, instance, condition) {
|
|
216
262
|
try {
|
|
217
|
-
|
|
218
|
-
|
|
263
|
+
let parent = element.parentElement;
|
|
264
|
+
let parentResName = null;
|
|
265
|
+
let arrayIndex = null;
|
|
266
|
+
|
|
267
|
+
while (parent && !parentResName) {
|
|
268
|
+
parentResName = parent.getAttribute('res');
|
|
269
|
+
if (!arrayIndex) {
|
|
270
|
+
arrayIndex = parent.getAttribute('res-index');
|
|
271
|
+
}
|
|
272
|
+
parent = parent.parentElement;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// For array items, use the specific array element as instance
|
|
276
|
+
if (parentResName && arrayIndex !== null && Array.isArray(this.data[parentResName])) {
|
|
277
|
+
instance = this.data[parentResName][arrayIndex];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// For object properties without parent reference
|
|
281
|
+
if (parentResName && !condition.includes(parentResName + '.')) {
|
|
282
|
+
const propNames = condition.match(/\b[a-zA-Z_][a-zA-Z0-9_]*\b/g) || [];
|
|
283
|
+
propNames.forEach(prop => {
|
|
284
|
+
if (prop === '==' || prop === '!=' || !(prop in instance)) return;
|
|
285
|
+
|
|
286
|
+
const regex = new RegExp(`\\b${prop}\\b`, 'g');
|
|
287
|
+
const propValue = instance[prop];
|
|
288
|
+
|
|
289
|
+
if (propValue === undefined) {
|
|
290
|
+
// Keep as is - property doesn't exist
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Determine correct property access path
|
|
295
|
+
if (instance['item.' + prop] !== undefined) {
|
|
296
|
+
condition = condition.replace(regex, `item.${prop}`);
|
|
297
|
+
} else if (typeof propValue === 'object') {
|
|
298
|
+
condition = condition.replace(regex, `${parentResName}.${prop}`);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let show = false;
|
|
304
|
+
try{
|
|
305
|
+
show = new Function('item', `return ${condition}`)(instance);
|
|
306
|
+
} catch(e) {
|
|
307
|
+
|
|
308
|
+
const firstPeriodIndex = condition.indexOf('.');
|
|
309
|
+
const conditionAfterFirstPeriod = condition.substring(firstPeriodIndex + 1);
|
|
310
|
+
show = new Function('item', `return item.${conditionAfterFirstPeriod}`)(instance);
|
|
311
|
+
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
element.style.display = show ? 'inherit' : 'none';
|
|
219
315
|
} catch (e) {
|
|
220
316
|
console.error(`Error evaluating display condition: ${condition}`, e);
|
|
221
317
|
}
|
|
@@ -314,9 +410,9 @@ class Resonant {
|
|
|
314
410
|
}
|
|
315
411
|
|
|
316
412
|
updateElement(variableName) {
|
|
413
|
+
|
|
317
414
|
const elements = document.querySelectorAll(`[res="${variableName}"]`);
|
|
318
415
|
const value = this.data[variableName];
|
|
319
|
-
|
|
320
416
|
elements.forEach(element => {
|
|
321
417
|
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
|
|
322
418
|
this._handleInputElement(element, value, (newValue) => {
|
|
@@ -355,7 +451,8 @@ class Resonant {
|
|
|
355
451
|
});
|
|
356
452
|
}
|
|
357
453
|
else if (Array.isArray(propValue)) {
|
|
358
|
-
this.
|
|
454
|
+
let parentKey = this.data[parentVarName].key;
|
|
455
|
+
this._renderNestedArray(subEl, propValue, parentKey);
|
|
359
456
|
}
|
|
360
457
|
else if (typeof propValue === 'object' && propValue !== null) {
|
|
361
458
|
const nestedElements = subEl.querySelectorAll('[res-prop]');
|
|
@@ -372,68 +469,93 @@ class Resonant {
|
|
|
372
469
|
}
|
|
373
470
|
|
|
374
471
|
_renderArray(variableName, el) {
|
|
375
|
-
|
|
376
|
-
el.innerHTML = '';
|
|
472
|
+
const container = el.hasAttribute('res') && el.parentElement ? el.parentElement : el;
|
|
377
473
|
|
|
474
|
+
let template;
|
|
378
475
|
if (!window[variableName + "_template"]) {
|
|
476
|
+
template = el.cloneNode(true);
|
|
379
477
|
window[variableName + "_template"] = template;
|
|
478
|
+
el.style.display = 'none';
|
|
479
|
+
el.setAttribute('res-template', 'true');
|
|
380
480
|
} else {
|
|
381
481
|
template = window[variableName + "_template"];
|
|
382
482
|
}
|
|
383
483
|
|
|
484
|
+
const existingElements = new Map();
|
|
485
|
+
container.querySelectorAll(`[res="${variableName}"][res-rendered="true"]`).forEach(element => {
|
|
486
|
+
const key = element.getAttribute('res-key');
|
|
487
|
+
if (key) {
|
|
488
|
+
existingElements.set(key, element);
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
container.querySelectorAll(`[res="${variableName}"][res-rendered="true"]`).forEach(el => el.remove());
|
|
493
|
+
|
|
384
494
|
this.data[variableName].forEach((instance, index) => {
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
this._queueUpdate(variableName, 'modified', instance, key, value);
|
|
407
|
-
}
|
|
408
|
-
);
|
|
409
|
-
}
|
|
410
|
-
else if (Array.isArray(value)) {
|
|
411
|
-
this._renderNestedArray(subEl, value);
|
|
495
|
+
const elementKey = instance.key;
|
|
496
|
+
let elementToUse;
|
|
497
|
+
|
|
498
|
+
if (existingElements.has(elementKey)) {
|
|
499
|
+
elementToUse = existingElements.get(elementKey);
|
|
500
|
+
existingElements.delete(elementKey);
|
|
501
|
+
elementToUse.setAttribute("res-index", index);
|
|
502
|
+
} else {
|
|
503
|
+
elementToUse = template.cloneNode(true);
|
|
504
|
+
elementToUse.removeAttribute('res-template');
|
|
505
|
+
elementToUse.style.display = '';
|
|
506
|
+
elementToUse.setAttribute("res-rendered", "true");
|
|
507
|
+
elementToUse.setAttribute("res-key", elementKey);
|
|
508
|
+
elementToUse.setAttribute("res-index", index);
|
|
509
|
+
|
|
510
|
+
for (let key in instance) {
|
|
511
|
+
let overrideInstanceValue = null;
|
|
512
|
+
let subEl = elementToUse.querySelector(`[res-prop="${key}"]`);
|
|
513
|
+
if (!subEl) {
|
|
514
|
+
subEl = elementToUse.querySelector('[res-prop=""]');
|
|
515
|
+
overrideInstanceValue = instance;
|
|
412
516
|
}
|
|
413
|
-
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
517
|
+
if (subEl) {
|
|
518
|
+
const value = this._resolveValue(instance, key, overrideInstanceValue);
|
|
519
|
+
|
|
520
|
+
if ((subEl.tagName === 'INPUT' || subEl.tagName === 'TEXTAREA') &&
|
|
521
|
+
!Array.isArray(value) &&
|
|
522
|
+
typeof value !== 'object') {
|
|
523
|
+
this._handleInputElement(
|
|
524
|
+
subEl,
|
|
525
|
+
value,
|
|
526
|
+
(newValue) => {
|
|
527
|
+
instance[key] = newValue;
|
|
528
|
+
this._queueUpdate(variableName, 'modified', instance, key, value);
|
|
529
|
+
}
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
else if (Array.isArray(value)) {
|
|
533
|
+
let parentKey = this.data[variableName][index].key;
|
|
534
|
+
this._renderNestedArray(subEl, value, parentKey);
|
|
535
|
+
}
|
|
536
|
+
else if (typeof value === 'object' && value !== null) {
|
|
537
|
+
const nestedElements = subEl.querySelectorAll('[res-prop]');
|
|
538
|
+
nestedElements.forEach(nestedEl => {
|
|
539
|
+
const nestedKey = nestedEl.getAttribute('res-prop');
|
|
540
|
+
if (nestedKey && nestedKey in value) {
|
|
541
|
+
this._renderObjectProperty(nestedEl, value[nestedKey], variableName, nestedKey);
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
subEl.innerHTML = value ?? '';
|
|
547
|
+
}
|
|
424
548
|
}
|
|
425
549
|
}
|
|
426
550
|
}
|
|
427
551
|
|
|
428
|
-
this._handleDisplayElements(
|
|
429
|
-
this._bindClickEvents(
|
|
552
|
+
this._handleDisplayElements(elementToUse, instance);
|
|
553
|
+
this._bindClickEvents(elementToUse, instance, this.data[variableName]);
|
|
430
554
|
|
|
431
|
-
|
|
432
|
-
el.appendChild(clonedEl);
|
|
555
|
+
container.appendChild(elementToUse);
|
|
433
556
|
});
|
|
434
557
|
}
|
|
435
|
-
|
|
436
|
-
_renderNestedArray(subEl, arrayValue) {
|
|
558
|
+
_renderNestedArray(subEl, arrayValue, parentKey) {
|
|
437
559
|
const template = subEl.cloneNode(true);
|
|
438
560
|
subEl.innerHTML = '';
|
|
439
561
|
|
|
@@ -443,17 +565,26 @@ class Resonant {
|
|
|
443
565
|
const cloned = template.cloneNode(true);
|
|
444
566
|
cloned.setAttribute('res-rendered', 'true');
|
|
445
567
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
568
|
+
if (item !== null && typeof item !== 'object') {
|
|
569
|
+
cloned.innerHTML = item;
|
|
570
|
+
} else {
|
|
571
|
+
const nestedEls = cloned.querySelectorAll('[res-prop]');
|
|
572
|
+
nestedEls.forEach(nestedEl => {
|
|
573
|
+
const nestedKey = nestedEl.getAttribute('res-prop');
|
|
574
|
+
if (nestedKey && nestedKey in item) {
|
|
575
|
+
nestedEl.setAttribute('res-child', item.key + '-' + nestedKey + '--' + parentKey);
|
|
576
|
+
this._renderObjectProperty(nestedEl, item[nestedKey], null, nestedKey);
|
|
577
|
+
}
|
|
578
|
+
});
|
|
453
579
|
|
|
454
|
-
|
|
455
|
-
|
|
580
|
+
this._handleDisplayElements(cloned, item);
|
|
581
|
+
this._bindClickEvents(cloned, item, arrayValue);
|
|
456
582
|
|
|
583
|
+
const displayCondition = cloned.getAttribute('res-display');
|
|
584
|
+
if (displayCondition) {
|
|
585
|
+
this._evaluateDisplayCondition(cloned, item, displayCondition);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
457
588
|
subEl.appendChild(cloned);
|
|
458
589
|
});
|
|
459
590
|
}
|
|
@@ -464,6 +595,12 @@ class Resonant {
|
|
|
464
595
|
const condition = conditionalElement.getAttribute('res-display');
|
|
465
596
|
this._evaluateDisplayCondition(conditionalElement, this.data[variableName], condition);
|
|
466
597
|
});
|
|
598
|
+
|
|
599
|
+
const contextElements = document.querySelectorAll(`[res="${variableName}"] [res-display]`);
|
|
600
|
+
contextElements.forEach(conditionalElement => {
|
|
601
|
+
const condition = conditionalElement.getAttribute('res-display');
|
|
602
|
+
this._evaluateDisplayCondition(conditionalElement, this.data[variableName], condition);
|
|
603
|
+
});
|
|
467
604
|
}
|
|
468
605
|
|
|
469
606
|
updateStylesFor(variableName) {
|
package/resonant.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
class ObservableArray extends Array{constructor(e,t,...a){if(void 0===t)return super(...a);super(...a);var s=void 0===t.data[e];this.variableName=e,this.resonantInstance=t,this.isDeleting=!1,this.forEach((e,t)=>{"object"==typeof e&&(this[t]=this._createProxy(e,t))}),s||this.forceUpdate()}_createProxy(e,t){return new Proxy(e,{set:(e,a,s)=>{if(e[a]!==s){let r=e[a];e[a]=s,this.resonantInstance._queueUpdate(this.variableName,"modified",e,a,r,t)}return!0}})}forceUpdate(){this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),this.resonantInstance._queueUpdate(this.variableName,"modified",this.slice())}update(e){window[this.variableName]=e,this.resonantInstance._queueUpdate(this.variableName,"updated",e)}push(...e){e=e.map((e,t)=>"object"==typeof e?this._createProxy(e,this.length+t):e);let t=super.push(...e);return this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),e.forEach((e,t)=>{this.resonantInstance._queueUpdate(this.variableName,"added",e,this.length-1-t)}),t}splice(e,t,...a){a=a.map((t,a)=>"object"==typeof t?this._createProxy(t,e+a):t);let s=super.splice(e,t,...a);return this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),t>0&&s.forEach((t,a)=>{this.resonantInstance._queueUpdate(this.variableName,"removed",t,e+a)}),a.length>0&&a.forEach((t,a)=>{this.resonantInstance._queueUpdate(this.variableName,"added",t,e+a)}),s}set(e,t){if(this[e]!==t){if(this.isDeleting)return!0;let a=this.resonantInstance.arrayDataChangeDetection[this.variableName],s="modified";e>=a.length?s="added":void 0===a[e]&&(s="added"),this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice();let r=this[e];this[e]=t,this.resonantInstance._queueUpdate(this.variableName,s,this[e],e,r)}return!0}delete(e){let t=this[e];return this.isDeleting=!0,this.splice(e,1),this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),this.resonantInstance._queueUpdate(this.variableName,"removed",null,e,t),this.isDeleting=!1,!0}filter(e,t=!0){if(void 0===this.resonantInstance||!1===t)return super.filter(e);let a=super.filter(e);return this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),this.resonantInstance._queueUpdate(this.variableName,"filtered"),a}}class Resonant{constructor(){this.data={},this.callbacks={},this.pendingUpdates=new Map,this.arrayDataChangeDetection={}}_handleInputElement(e,t,a){"checkbox"===e.type?(e.checked=t,e.hasAttribute("data-resonant-bound")||(e.onchange=()=>a(e.checked),e.setAttribute("data-resonant-bound","true"))):(e.value=t,e.hasAttribute("data-resonant-bound")||(e.oninput=()=>a(e.value),e.setAttribute("data-resonant-bound","true")))}add(e,t,a){Array.isArray(t=this.persist(e,t,a))?(this.data[e]=new ObservableArray(e,this,...t),this.arrayDataChangeDetection[e]=this.data[e].slice()):"object"==typeof t&&null!==t?this.data[e]=this._createObject(e,t):this.data[e]=t,this._defineProperty(e),this.updateElement(e)}persist(e,t,a){if(void 0===a||!a)return t;var s=localStorage.getItem("res_"+e);return null!=s?JSON.parse(s):(localStorage.setItem("res_"+e,JSON.stringify(t)),t)}updatePersistantData(e){localStorage.getItem("res_"+e)&&localStorage.setItem("res_"+e,JSON.stringify(this.data[e]))}addAll(e){Object.entries(e).forEach(([e,t])=>{this.add(e,t)})}_resolveValue(e,t,a=null){return a??e[t]}_createObject(e,t){return t[Symbol("isProxy")]=!0,new Proxy(t,{set:(t,a,s)=>{if(t[a]!==s){let r=t[a];t[a]=s,this._queueUpdate(e,"modified",t,a,r)}return!0}})}_evaluateDisplayCondition(e,t,a){try{let s=Function("item",`return ${a}`)(t);e.style.display=s?"":"none"}catch(r){console.error(`Error evaluating display condition: ${a}`,r)}}_handleDisplayElements(e,t){let a=e.querySelectorAll("[res-display]");a.forEach(e=>{let a=e.getAttribute("res-display")||"";this._evaluateDisplayCondition(e,t,a)})}_bindClickEvents(e,t,a){let s=e.querySelectorAll("[res-onclick], [res-onclick-remove]");s.forEach(e=>{let s=e.getAttribute("res-onclick"),r=e.getAttribute("res-onclick-remove");s&&(e.onclick=()=>{let e=Function("item",`return ${s}(item)`);e(t)}),r&&(e.onclick=()=>{if(a){let e=a.findIndex(e=>e[r]===t[r]);-1!==e&&a.splice(e,1)}})})}_defineProperty(e){Object.defineProperty(window,e,{get:()=>this.data[e],set:t=>{Array.isArray(t)?(this.data[e]=new ObservableArray(e,this,...t),this.arrayDataChangeDetection[e]=this.data[e].slice()):"object"==typeof t&&null!==t?this.data[e]=this._createObject(e,t):this.data[e]=t,this.updateElement(e),this.updateDisplayConditionalsFor(e),this.updateStylesFor(e),Array.isArray(t)||"object"==typeof t&&null!==t||this._queueUpdate(e,"modified",this.data[e])}})}_queueUpdate(e,t,a,s,r){this.pendingUpdates.has(e)||this.pendingUpdates.set(e,[]),this.pendingUpdates.get(e).push({action:t,item:a,property:s,oldValue:r}),1===this.pendingUpdates.get(e).length&&setTimeout(()=>{let t=this.pendingUpdates.get(e);this.updatePersistantData(e),this.pendingUpdates.delete(e),(t=t.filter((e,t,a)=>a.findIndex(t=>t.property===e.property&&t.action===e.action)===t)).forEach(t=>{this._triggerCallbacks(e,t)}),this.updateElement(e),this.updateDisplayConditionalsFor(e),this.updateStylesFor(e)},0)}_triggerCallbacks(e,t){this.callbacks[e]&&this.callbacks[e].forEach(a=>{let s=t.item||t.oldValue;a(this.data[e],s,t.action)})}updateElement(e){let t=document.querySelectorAll(`[res="${e}"]`),a=this.data[e];t.forEach(t=>{if("INPUT"===t.tagName||"TEXTAREA"===t.tagName)this._handleInputElement(t,a,t=>{this.data[e]=t,this._queueUpdate(e,"modified",this.data[e])});else if(Array.isArray(a))t.querySelectorAll(`[res="${e}"][res-rendered="true"]`).forEach(e=>e.remove()),this._renderArray(e,t);else if("object"==typeof a&&null!==a){let s=t.querySelectorAll("[res-prop]");s.forEach(t=>{let s=t.getAttribute("res-prop");s&&s in a&&this._renderObjectProperty(t,a[s],e,s)})}else t.innerHTML=a??""}),this.updateDisplayConditionalsFor(e),this.updateStylesFor(e)}_renderObjectProperty(e,t,a,s){if("INPUT"!==e.tagName&&"TEXTAREA"!==e.tagName||Array.isArray(t)||"object"==typeof t){if(Array.isArray(t))this._renderNestedArray(e,t);else if("object"==typeof t&&null!==t){let r=e.querySelectorAll("[res-prop]");r.forEach(e=>{let s=e.getAttribute("res-prop");s&&s in t&&this._renderObjectProperty(e,t[s],a,s)})}else e.innerHTML=t??""}else this._handleInputElement(e,t,e=>{window[a][s]=e})}_renderArray(e,t){let a=t.cloneNode(!0);t.innerHTML="",window[e+"_template"]?a=window[e+"_template"]:window[e+"_template"]=a,this.data[e].forEach((s,r)=>{let i=a.cloneNode(!0);for(let n in i.setAttribute("res-index",r),s){let l=null,o=i.querySelector(`[res-prop="${n}"]`);if(o||(o=i.querySelector('[res-prop=""]'),l=s),o){let h=this._resolveValue(s,n,l);if("INPUT"!==o.tagName&&"TEXTAREA"!==o.tagName||Array.isArray(h)||"object"==typeof h){if(Array.isArray(h))this._renderNestedArray(o,h);else if("object"==typeof h&&null!==h){let d=o.querySelectorAll("[res-prop]");d.forEach(t=>{let a=t.getAttribute("res-prop");a&&a in h&&this._renderObjectProperty(t,h[a],e,a)})}else o.innerHTML=h??""}else this._handleInputElement(o,h,t=>{s[n]=t,this._queueUpdate(e,"modified",s,n,h)})}}this._handleDisplayElements(i,s),this._bindClickEvents(i,s,this.data[e]),i.setAttribute("res-rendered","true"),t.appendChild(i)})}_renderNestedArray(e,t){let a=e.cloneNode(!0);e.innerHTML="",e.removeAttribute("res-prop"),t.forEach((s,r)=>{let i=a.cloneNode(!0);i.setAttribute("res-rendered","true");let n=i.querySelectorAll("[res-prop]");n.forEach(e=>{let t=e.getAttribute("res-prop");t&&t in s&&this._renderObjectProperty(e,s[t],null,t)}),this._handleDisplayElements(i,s),this._bindClickEvents(i,s,t),e.appendChild(i)})}updateDisplayConditionalsFor(e){let t=document.querySelectorAll(`[res-display*="${e}"]`);t.forEach(t=>{let a=t.getAttribute("res-display");this._evaluateDisplayCondition(t,this.data[e],a)})}updateStylesFor(variableName){let 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;let resStyles=styleElement.getAttribute("res-styles");if(resStyles&&resStyles.split(" ").forEach(e=>{styleElement.classList.remove(e)}),null!==index){let item=this.data[variableName][index];styleCondition=styleCondition.replace(RegExp(`\\b${variableName}\\b`,"g"),"item");let styleClass=Function("item",`return ${styleCondition}`)(item);if(styleClass)styleElement.classList.add(styleClass);else{let elementHasStyle=styleElement.classList.contains(styleClass);elementHasStyle&&styleElement.classList.remove(styleClass)}}else{let styleClass=eval(styleCondition);if(styleClass)styleElement.classList.add(styleClass),styleElement.setAttribute("res-styles",styleClass);else{let elementHasStyle=styleElement.classList.contains(styleClass);elementHasStyle&&styleElement.classList.remove(styleClass)}}}catch(e){console.error(`Error evaluating style for ${variableName}: ${styleCondition}`,e)}})}addCallback(e,t){this.callbacks[e]||(this.callbacks[e]=[]),this.callbacks[e].push(t)}}
|
|
1
|
+
class ObservableArray extends Array{constructor(e,t,...r){if(void 0===t)return super(...r);super(...r);var s=void 0===t.data[e];this.variableName=e,this.resonantInstance=t,this.isDeleting=!1,this.forEach((e,t)=>{"object"==typeof e&&(this._setKeyForObjectAndChildObjects(e,t),this[t]=this._createProxy(e,t))}),s||this.forceUpdate()}_setKeyForObjectAndChildObjects(e,t){"object"==typeof e&&(e.key=t+"-"+this._generateKeyByContentCheckSum(e),Object.keys(e).forEach(r=>{this._setKeyForObjectAndChildObjects(e[r],t)}))}_generateKeyByContentCheckSum(e){let t=e=>{if(!e||"object"!=typeof e)return e;if(Array.isArray(e))return e.map(e=>t(e));let r={...e};return delete r.key,Object.keys(r).forEach(e=>{"object"==typeof r[e]&&null!==r[e]&&(r[e]=t(r[e]))}),r},r=t(e),s=JSON.stringify(r),a=0;for(let i=0;i<s.length;i++){let l=s.charCodeAt(i);a=(a<<5)-a+l,a&=a}return Math.abs(a).toString(36)}_createProxy(e,t){return new Proxy(e,{set:(r,s,a)=>{if(r[s]!==a){let i=r[s];r[s]=a,this._setKeyForObjectAndChildObjects(e,t),this.resonantInstance._queueUpdate(this.variableName,"modified",r,s,i,t)}return!0}})}forceUpdate(){this.forEach((e,t)=>{this._setKeyForObjectAndChildObjects(e,t)}),this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),this.resonantInstance._queueUpdate(this.variableName,"modified",this.slice())}update(e){window[this.variableName]=e,this.resonantInstance._queueUpdate(this.variableName,"updated",e)}push(...e){e=e.map((e,t)=>"object"==typeof e?(this._setKeyForObjectAndChildObjects(e,this.length+t),this._createProxy(e,this.length+t)):e);let t=super.push(...e);return this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),e.forEach((e,t)=>{this.resonantInstance._queueUpdate(this.variableName,"added",e,this.length-1-t)}),t}splice(e,t,...r){r=r.map((t,r)=>"object"==typeof t?this._createProxy(t,e+r):t);let s=super.splice(e,t,...r);return this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),t>0&&s.forEach((t,r)=>{this.resonantInstance._queueUpdate(this.variableName,"removed",t,e+r)}),r.length>0&&r.forEach((t,r)=>{this.resonantInstance._queueUpdate(this.variableName,"added",t,e+r)}),s}set(e,t){if(this[e]!==t){if(this.isDeleting)return!0;let r=this.resonantInstance.arrayDataChangeDetection[this.variableName],s="modified";e>=r.length?s="added":void 0===r[e]&&(s="added"),this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice();let a=this[e];this[e]=t,this.resonantInstance._queueUpdate(this.variableName,s,this[e],e,a)}return!0}delete(e){let t=this[e];return this.isDeleting=!0,this.splice(e,1),this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),this.resonantInstance._queueUpdate(this.variableName,"removed",null,e,t),this.isDeleting=!1,!0}filter(e,t=!0){if(void 0===this.resonantInstance||!1===t)return super.filter(e);let r=super.filter(e);return this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),this.resonantInstance._queueUpdate(this.variableName,"filtered"),r}}class Resonant{constructor(){this.data={},this.callbacks={},this.pendingUpdates=new Map,this.arrayDataChangeDetection={}}_handleInputElement(e,t,r){"checkbox"===e.type?(e.checked=t,e.hasAttribute("data-resonant-bound")||(e.onchange=()=>r(e.checked),e.setAttribute("data-resonant-bound","true"))):(e.value=t,e.hasAttribute("data-resonant-bound")||(e.oninput=()=>r(e.value),e.setAttribute("data-resonant-bound","true")))}add(e,t,r){Array.isArray(t=this.persist(e,t,r))?(this.data[e]=new ObservableArray(e,this,...t),this.arrayDataChangeDetection[e]=this.data[e].slice()):"object"==typeof t&&null!==t?this.data[e]=this._createObject(e,t):this.data[e]=t,this._defineProperty(e),this.updateElement(e)}persist(e,t,r){if(void 0===r||!r)return t;var s=localStorage.getItem("res_"+e);return null!=s?JSON.parse(s):(localStorage.setItem("res_"+e,JSON.stringify(t)),t)}updatePersistantData(e){localStorage.getItem("res_"+e)&&localStorage.setItem("res_"+e,JSON.stringify(this.data[e]))}addAll(e){Object.entries(e).forEach(([e,t])=>{this.add(e,t)})}_resolveValue(e,t,r=null){return r??e[t]}_createObject(e,t){return t[Symbol("isProxy")]=!0,new Proxy(t,{set:(t,r,s)=>{if(t[r]!==s){let a=t[r];t[r]=s,this._queueUpdate(e,"modified",t,r,a)}return!0}})}_evaluateDisplayCondition(e,t,r){try{let s=e.parentElement,a=null,i=null;for(;s&&!a;)a=s.getAttribute("res"),i||(i=s.getAttribute("res-index")),s=s.parentElement;if(a&&null!==i&&Array.isArray(this.data[a])&&(t=this.data[a][i]),a&&!r.includes(a+".")){let l=r.match(/\b[a-zA-Z_][a-zA-Z0-9_]*\b/g)||[];l.forEach(e=>{if("=="===e||"!="===e||!(e in t))return;let s=RegExp(`\\b${e}\\b`,"g"),i=t[e];void 0!==i&&(void 0!==t["item."+e]?r=r.replace(s,`item.${e}`):"object"==typeof i&&(r=r.replace(s,`${a}.${e}`)))})}let n=!1;try{n=Function("item",`return ${r}`)(t)}catch(o){let h=r.indexOf("."),d=r.substring(h+1);n=Function("item",`return item.${d}`)(t)}e.style.display=n?"inherit":"none"}catch(c){console.error(`Error evaluating display condition: ${r}`,c)}}_handleDisplayElements(e,t){let r=e.querySelectorAll("[res-display]");r.forEach(e=>{let r=e.getAttribute("res-display")||"";this._evaluateDisplayCondition(e,t,r)})}_bindClickEvents(e,t,r){let s=e.querySelectorAll("[res-onclick], [res-onclick-remove]");s.forEach(e=>{let s=e.getAttribute("res-onclick"),a=e.getAttribute("res-onclick-remove");s&&(e.onclick=()=>{let e=Function("item",`return ${s}(item)`);e(t)}),a&&(e.onclick=()=>{if(r){let e=r.findIndex(e=>e[a]===t[a]);-1!==e&&r.splice(e,1)}})})}_defineProperty(e){Object.defineProperty(window,e,{get:()=>this.data[e],set:t=>{Array.isArray(t)?(this.data[e]=new ObservableArray(e,this,...t),this.arrayDataChangeDetection[e]=this.data[e].slice()):"object"==typeof t&&null!==t?this.data[e]=this._createObject(e,t):this.data[e]=t,this.updateElement(e),this.updateDisplayConditionalsFor(e),this.updateStylesFor(e),Array.isArray(t)||"object"==typeof t&&null!==t||this._queueUpdate(e,"modified",this.data[e])}})}_queueUpdate(e,t,r,s,a){this.pendingUpdates.has(e)||this.pendingUpdates.set(e,[]),this.pendingUpdates.get(e).push({action:t,item:r,property:s,oldValue:a}),1===this.pendingUpdates.get(e).length&&setTimeout(()=>{let t=this.pendingUpdates.get(e);this.updatePersistantData(e),this.pendingUpdates.delete(e),(t=t.filter((e,t,r)=>r.findIndex(t=>t.property===e.property&&t.action===e.action)===t)).forEach(t=>{this._triggerCallbacks(e,t)}),this.updateElement(e),this.updateDisplayConditionalsFor(e),this.updateStylesFor(e)},0)}_triggerCallbacks(e,t){this.callbacks[e]&&this.callbacks[e].forEach(r=>{let s=t.item||t.oldValue;r(this.data[e],s,t.action)})}updateElement(e){let t=document.querySelectorAll(`[res="${e}"]`),r=this.data[e];t.forEach(t=>{if("INPUT"===t.tagName||"TEXTAREA"===t.tagName)this._handleInputElement(t,r,t=>{this.data[e]=t,this._queueUpdate(e,"modified",this.data[e])});else if(Array.isArray(r))t.querySelectorAll(`[res="${e}"][res-rendered="true"]`).forEach(e=>e.remove()),this._renderArray(e,t);else if("object"==typeof r&&null!==r){let s=t.querySelectorAll("[res-prop]");s.forEach(t=>{let s=t.getAttribute("res-prop");s&&s in r&&this._renderObjectProperty(t,r[s],e,s)})}else t.innerHTML=r??""}),this.updateDisplayConditionalsFor(e),this.updateStylesFor(e)}_renderObjectProperty(e,t,r,s){if("INPUT"!==e.tagName&&"TEXTAREA"!==e.tagName||Array.isArray(t)||"object"==typeof t){if(Array.isArray(t)){let a=this.data[r].key;this._renderNestedArray(e,t,a)}else if("object"==typeof t&&null!==t){let i=e.querySelectorAll("[res-prop]");i.forEach(e=>{let s=e.getAttribute("res-prop");s&&s in t&&this._renderObjectProperty(e,t[s],r,s)})}else e.innerHTML=t??""}else this._handleInputElement(e,t,e=>{window[r][s]=e})}_renderArray(e,t){let r=t.hasAttribute("res")&&t.parentElement?t.parentElement:t,s;window[e+"_template"]?s=window[e+"_template"]:(s=t.cloneNode(!0),window[e+"_template"]=s,t.style.display="none",t.setAttribute("res-template","true"));let a=new Map;r.querySelectorAll(`[res="${e}"][res-rendered="true"]`).forEach(e=>{let t=e.getAttribute("res-key");t&&a.set(t,e)}),r.querySelectorAll(`[res="${e}"][res-rendered="true"]`).forEach(e=>e.remove()),this.data[e].forEach((t,i)=>{let l=t.key,n;if(a.has(l))n=a.get(l),a.delete(l),n.setAttribute("res-index",i);else for(let o in(n=s.cloneNode(!0)).removeAttribute("res-template"),n.style.display="",n.setAttribute("res-rendered","true"),n.setAttribute("res-key",l),n.setAttribute("res-index",i),t){let h=null,d=n.querySelector(`[res-prop="${o}"]`);if(d||(d=n.querySelector('[res-prop=""]'),h=t),d){let c=this._resolveValue(t,o,h);if("INPUT"!==d.tagName&&"TEXTAREA"!==d.tagName||Array.isArray(c)||"object"==typeof c){if(Array.isArray(c)){let u=this.data[e][i].key;this._renderNestedArray(d,c,u)}else if("object"==typeof c&&null!==c){let p=d.querySelectorAll("[res-prop]");p.forEach(t=>{let r=t.getAttribute("res-prop");r&&r in c&&this._renderObjectProperty(t,c[r],e,r)})}else d.innerHTML=c??""}else this._handleInputElement(d,c,r=>{t[o]=r,this._queueUpdate(e,"modified",t,o,c)})}}this._handleDisplayElements(n,t),this._bindClickEvents(n,t,this.data[e]),r.appendChild(n)})}_renderNestedArray(e,t,r){let s=e.cloneNode(!0);e.innerHTML="",e.removeAttribute("res-prop"),t.forEach((a,i)=>{let l=s.cloneNode(!0);if(l.setAttribute("res-rendered","true"),null!==a&&"object"!=typeof a)l.innerHTML=a;else{let n=l.querySelectorAll("[res-prop]");n.forEach(e=>{let t=e.getAttribute("res-prop");t&&t in a&&(e.setAttribute("res-child",a.key+"-"+t+"--"+r),this._renderObjectProperty(e,a[t],null,t))}),this._handleDisplayElements(l,a),this._bindClickEvents(l,a,t);let o=l.getAttribute("res-display");o&&this._evaluateDisplayCondition(l,a,o)}e.appendChild(l)})}updateDisplayConditionalsFor(e){let t=document.querySelectorAll(`[res-display*="${e}"]`);t.forEach(t=>{let r=t.getAttribute("res-display");this._evaluateDisplayCondition(t,this.data[e],r)});let r=document.querySelectorAll(`[res="${e}"] [res-display]`);r.forEach(t=>{let r=t.getAttribute("res-display");this._evaluateDisplayCondition(t,this.data[e],r)})}updateStylesFor(variableName){let 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;let resStyles=styleElement.getAttribute("res-styles");if(resStyles&&resStyles.split(" ").forEach(e=>{styleElement.classList.remove(e)}),null!==index){let item=this.data[variableName][index];styleCondition=styleCondition.replace(RegExp(`\\b${variableName}\\b`,"g"),"item");let styleClass=Function("item",`return ${styleCondition}`)(item);if(styleClass)styleElement.classList.add(styleClass);else{let elementHasStyle=styleElement.classList.contains(styleClass);elementHasStyle&&styleElement.classList.remove(styleClass)}}else{let styleClass=eval(styleCondition);if(styleClass)styleElement.classList.add(styleClass),styleElement.setAttribute("res-styles",styleClass);else{let elementHasStyle=styleElement.classList.contains(styleClass);elementHasStyle&&styleElement.classList.remove(styleClass)}}}catch(e){console.error(`Error evaluating style for ${variableName}: ${styleCondition}`,e)}})}addCallback(e,t){this.callbacks[e]||(this.callbacks[e]=[]),this.callbacks[e].push(t)}}
|