resonantjs 1.0.4 → 1.0.6
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/Demo.gif +0 -0
- package/README.md +4 -67
- package/examples/example-basic.html +73 -40
- package/examples/example-taskmanager.html +2 -1
- package/package.json +1 -1
- package/resonant.js +122 -65
- package/resonant.min.js +1 -1
package/Demo.gif
CHANGED
|
Binary file
|
package/README.md
CHANGED
|
@@ -60,9 +60,10 @@ Include resonant.js in your HTML file, and use the following example to understa
|
|
|
60
60
|
- **`res` and `res-prop` Attributes**: Bind HTML elements to your data model seamlessly.
|
|
61
61
|
- `res` is used to identify an overarching data model.
|
|
62
62
|
- `res-prop` links individual properties within that model to corresponding UI elements.
|
|
63
|
-
- **`res-
|
|
63
|
+
- **`res-display` Attribute**: Conditionally display elements based on the data model's properties.
|
|
64
64
|
- **`res-onclick` Attribute**: Triggers a function when an element is clicked, allowing for custom event handling.
|
|
65
65
|
- **`res-onclick-remove` Attribute**: Removes an item from an array when the associated element is clicked.
|
|
66
|
+
- **`res-style` Attribute**: Dynamically update CSS styles based on data model properties.
|
|
66
67
|
- **Automatic UI Updates**: Changes to your JavaScript objects instantly reflect in the associated UI components, reducing manual DOM manipulation.
|
|
67
68
|
|
|
68
69
|
### Advanced Features
|
|
@@ -84,72 +85,8 @@ Include resonant.js in your HTML file, and use the following example to understa
|
|
|
84
85
|
#### Refined Data Binding
|
|
85
86
|
- Enhanced data binding between model and view to ensure consistent synchronization without unnecessary updates.
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
Here is an example of a task manager application using Resonant.js:
|
|
90
|
-
|
|
91
|
-
```html
|
|
92
|
-
<!DOCTYPE html>
|
|
93
|
-
<html lang="en">
|
|
94
|
-
<head>
|
|
95
|
-
<title>Resonant.js Task Manager Demo</title>
|
|
96
|
-
<script src="https://cdn.jsdelivr.net/npm/resonantjs@latest/dist/resonant.min.js"></script>
|
|
97
|
-
</head>
|
|
98
|
-
<body>
|
|
99
|
-
<h1>Resonant.js Task Manager Demo</h1>
|
|
100
|
-
|
|
101
|
-
<!-- Task Input -->
|
|
102
|
-
<div>
|
|
103
|
-
<h2>Add New Task</h2>
|
|
104
|
-
<input type="text" placeholder="Task Name" res="taskName" />
|
|
105
|
-
<button onclick="addTask()">Add Task</button>
|
|
106
|
-
</div>
|
|
107
|
-
|
|
108
|
-
<!-- Task List -->
|
|
109
|
-
<div>
|
|
110
|
-
<h2>Task List</h2>
|
|
111
|
-
<ul res="tasks">
|
|
112
|
-
<li>
|
|
113
|
-
<input type="checkbox" res-prop="done" />
|
|
114
|
-
<span res-prop="name"></span>
|
|
115
|
-
<button res-onclick-remove="name">Remove</button>
|
|
116
|
-
</li>
|
|
117
|
-
</ul>
|
|
118
|
-
</div>
|
|
119
|
-
|
|
120
|
-
<script>
|
|
121
|
-
const resonantJs = new Resonant();
|
|
122
|
-
|
|
123
|
-
// Initialize variables using a configuration object
|
|
124
|
-
resonantJs.addAll({
|
|
125
|
-
tasks: [
|
|
126
|
-
{ name: "Task 1", done: false },
|
|
127
|
-
{ name: "Task 2", done: true }
|
|
128
|
-
],
|
|
129
|
-
taskName: ""
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// Add a callback to log actions taken on tasks
|
|
133
|
-
resonantJs.addCallback("tasks", (tasks, task, action) => {
|
|
134
|
-
console.log(`Action taken: ${action} for ${task.name}`);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
// Add a function to add a new task
|
|
138
|
-
function addTask() {
|
|
139
|
-
const newTask = { name: taskName, done: false };
|
|
140
|
-
tasks.push(newTask);
|
|
141
|
-
taskName = '';
|
|
142
|
-
}
|
|
143
|
-
</script>
|
|
144
|
-
</body>
|
|
145
|
-
</html>
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## Future Enhancements
|
|
149
|
-
|
|
150
|
-
- [ ] Support for nested data structures with deep linking.
|
|
151
|
-
- [ ] Advanced filtering and sorting methods for arrays.
|
|
152
|
-
- [ ] Compatibility with popular JavaScript frameworks.
|
|
88
|
+
## Other Information
|
|
89
|
+
- The demo HTML file uses the Pico CSS framework for styling. You can find more information about Pico CSS [here](https://picocss.com/).
|
|
153
90
|
|
|
154
91
|
## License
|
|
155
92
|
|
|
@@ -2,52 +2,85 @@
|
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
4
|
<title>Resonant.js Practical Demo</title>
|
|
5
|
-
<
|
|
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="https://unpkg.com/resonantjs@latest/resonant.min.js"></script>
|
|
10
|
+
<style>
|
|
11
|
+
section.grid {
|
|
12
|
+
padding: 1rem;
|
|
13
|
+
margin-bottom: 2rem;
|
|
14
|
+
border: 1px solid #e0e0e0;
|
|
15
|
+
border-radius: 0.5rem;
|
|
16
|
+
}
|
|
17
|
+
.conditional {
|
|
18
|
+
font-style: italic;
|
|
19
|
+
color: #888;
|
|
20
|
+
}
|
|
21
|
+
</style>
|
|
6
22
|
</head>
|
|
7
23
|
<body>
|
|
8
|
-
<h1>Resonant.js Practical Demo</h1>
|
|
9
24
|
|
|
10
|
-
|
|
11
|
-
<
|
|
12
|
-
<h2>Counter</h2>
|
|
13
|
-
<p>
|
|
14
|
-
Current count: <span res="counter"></span>
|
|
15
|
-
</p>
|
|
16
|
-
<div res-conditional="counter < 10">
|
|
17
|
-
Only shows when counter is less than 10
|
|
18
|
-
</div>
|
|
19
|
-
<div res-conditional="counter >= 10">
|
|
20
|
-
Only shows when counter is greater than or equal to 10
|
|
21
|
-
</div>
|
|
22
|
-
<button onclick="counter++">Increment Counter</button>
|
|
23
|
-
</div>
|
|
25
|
+
<main class="container">
|
|
26
|
+
<h1>Resonant.js Basic Demo</h1>
|
|
24
27
|
|
|
25
|
-
<!--
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
<!-- Display and update a single item -->
|
|
29
|
+
<section class="grid">
|
|
30
|
+
<div>
|
|
31
|
+
<h2>Counter: <span res="counter"></span></h2>
|
|
32
|
+
<div res-display="counter < 10" class="conditional">
|
|
33
|
+
Only shows when counter is less than 10
|
|
34
|
+
</div>
|
|
35
|
+
<div res-display="counter >= 10" class="conditional">
|
|
36
|
+
Only shows when counter is greater than or equal to 10
|
|
37
|
+
</div>
|
|
34
38
|
</div>
|
|
35
|
-
<br/>
|
|
36
|
-
First Name: <input type="text" res-prop="firstname" />
|
|
37
|
-
Last Name: <input type="text" res-prop="lastname" />
|
|
38
|
-
</div>
|
|
39
|
-
</div>
|
|
40
39
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
</div>
|
|
40
|
+
<button onclick="counter++">Increment Counter</button>
|
|
41
|
+
</section>
|
|
42
|
+
|
|
43
|
+
<!-- Demonstrate object property binding -->
|
|
44
|
+
<section class="grid" res="user">
|
|
45
|
+
<div>
|
|
46
|
+
<h2>User Information</h2>
|
|
47
|
+
<div res-display="user.firstname == 'John' && user.lastname == 'Doe'" class="conditional">
|
|
48
|
+
Only shows when firstname is John and lastname is Doe
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<p>
|
|
52
|
+
<span res-prop="firstname"></span>
|
|
53
|
+
<span res-prop="lastname"></span>
|
|
54
|
+
</p>
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
<div>
|
|
58
|
+
|
|
59
|
+
<div class="form-group">
|
|
60
|
+
<label>First Name:
|
|
61
|
+
<input type="text" res-prop="firstname" />
|
|
62
|
+
</label>
|
|
63
|
+
<label>Last Name:
|
|
64
|
+
<input type="text" res-prop="lastname" />
|
|
65
|
+
</label>
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
</section>
|
|
69
|
+
|
|
70
|
+
<!-- Demonstrate dynamic list rendering -->
|
|
71
|
+
<section class="grid">
|
|
72
|
+
<div>
|
|
73
|
+
<h2>Project Members</h2>
|
|
74
|
+
<ul res="projectTeam">
|
|
75
|
+
<li>
|
|
76
|
+
<span res-prop="name"></span> - <span res-prop="role"></span>
|
|
77
|
+
</li>
|
|
78
|
+
</ul>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<button onclick="addProjectMember()">Add Project Member</button>
|
|
82
|
+
</section>
|
|
83
|
+
</main>
|
|
51
84
|
|
|
52
85
|
<script>
|
|
53
86
|
const resonantJs = new Resonant();
|
|
@@ -49,7 +49,8 @@
|
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
function remove(task) {
|
|
52
|
-
|
|
52
|
+
//get by name
|
|
53
|
+
const index = tasks.findIndex(t => t.name === task.name);
|
|
53
54
|
tasks.splice(index, 1);
|
|
54
55
|
|
|
55
56
|
//You could use as well, still trying to figure out if I want to leave this or not
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "resonantjs",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
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
|
@@ -1,12 +1,94 @@
|
|
|
1
|
+
class ObservableArray extends Array {
|
|
2
|
+
constructor(variableName, resonantInstance, ...args) {
|
|
3
|
+
super(...args);
|
|
4
|
+
this.variableName = variableName;
|
|
5
|
+
this.resonantInstance = resonantInstance;
|
|
6
|
+
this.isDeleting = false;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
push(...args) {
|
|
10
|
+
const result = super.push(...args);
|
|
11
|
+
this.resonantInstance.arrayDataChangeDetection[this.variableName] = this.slice();
|
|
12
|
+
args.forEach((item, index) => {
|
|
13
|
+
this.resonantInstance._queueUpdate(this.variableName, 'added', item, this.length - 1 - index);
|
|
14
|
+
});
|
|
15
|
+
return result;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
splice(start, deleteCount, ...items) {
|
|
19
|
+
const removedItems = super.splice(start, deleteCount, ...items);
|
|
20
|
+
this.resonantInstance.arrayDataChangeDetection[this.variableName] = this.slice();
|
|
21
|
+
|
|
22
|
+
if (deleteCount > 0) {
|
|
23
|
+
removedItems.forEach((item, index) => {
|
|
24
|
+
this.resonantInstance._queueUpdate(this.variableName, 'removed', item, start + index);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (items.length > 0) {
|
|
29
|
+
items.forEach((item, index) => {
|
|
30
|
+
this.resonantInstance._queueUpdate(this.variableName, 'added', item, start + index);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return removedItems;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
set(index, value) {
|
|
38
|
+
if (this[index] !== value) {
|
|
39
|
+
if (this.isDeleting) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const originalBeforeChange = this.resonantInstance.arrayDataChangeDetection[this.variableName];
|
|
44
|
+
let action = 'modified';
|
|
45
|
+
|
|
46
|
+
if (index >= originalBeforeChange.length) {
|
|
47
|
+
action = 'added';
|
|
48
|
+
} else if (originalBeforeChange[index] === undefined) {
|
|
49
|
+
action = 'added';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.resonantInstance.arrayDataChangeDetection[this.variableName] = this.slice();
|
|
53
|
+
|
|
54
|
+
const oldValue = this[index];
|
|
55
|
+
this[index] = value;
|
|
56
|
+
this.resonantInstance._queueUpdate(this.variableName, action, this[index], index, oldValue);
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
delete(index) {
|
|
62
|
+
const oldValue = this[index];
|
|
63
|
+
this.isDeleting = true;
|
|
64
|
+
this.splice(index, 1);
|
|
65
|
+
|
|
66
|
+
this.resonantInstance.arrayDataChangeDetection[this.variableName] = this.slice();
|
|
67
|
+
|
|
68
|
+
this.resonantInstance._queueUpdate(this.variableName, 'removed', null, index, oldValue);
|
|
69
|
+
this.isDeleting = false;
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
1
74
|
class Resonant {
|
|
2
75
|
constructor() {
|
|
3
76
|
this.data = {};
|
|
4
77
|
this.callbacks = {};
|
|
5
|
-
this.pendingUpdates = new
|
|
78
|
+
this.pendingUpdates = new Map();
|
|
79
|
+
this.arrayDataChangeDetection = {}; // Added to keep track of array state
|
|
6
80
|
}
|
|
7
81
|
|
|
8
82
|
add(variableName, value) {
|
|
9
|
-
|
|
83
|
+
if (Array.isArray(value)) {
|
|
84
|
+
this.data[variableName] = new ObservableArray(variableName, this, ...value);
|
|
85
|
+
this.arrayDataChangeDetection[variableName] = this.data[variableName].slice();
|
|
86
|
+
} else if (typeof value === 'object') {
|
|
87
|
+
this.data[variableName] = this._createObject(variableName, value);
|
|
88
|
+
} else {
|
|
89
|
+
this.data[variableName] = value;
|
|
90
|
+
}
|
|
91
|
+
|
|
10
92
|
this._defineProperty(variableName);
|
|
11
93
|
this.updateElement(variableName);
|
|
12
94
|
}
|
|
@@ -17,16 +99,6 @@ class Resonant {
|
|
|
17
99
|
});
|
|
18
100
|
}
|
|
19
101
|
|
|
20
|
-
_assignValueToData(variableName, value) {
|
|
21
|
-
if (Array.isArray(value)) {
|
|
22
|
-
this.data[variableName] = this._createArray(variableName, value);
|
|
23
|
-
} else if (typeof value === 'object') {
|
|
24
|
-
this.data[variableName] = this._createObject(variableName, value);
|
|
25
|
-
} else {
|
|
26
|
-
this.data[variableName] = value;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
102
|
_createObject(variableName, obj) {
|
|
31
103
|
obj[Symbol('isProxy')] = true;
|
|
32
104
|
return new Proxy(obj, {
|
|
@@ -41,77 +113,66 @@ class Resonant {
|
|
|
41
113
|
});
|
|
42
114
|
}
|
|
43
115
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (
|
|
49
|
-
|
|
116
|
+
_defineProperty(variableName) {
|
|
117
|
+
Object.defineProperty(window, variableName, {
|
|
118
|
+
get: () => this.data[variableName],
|
|
119
|
+
set: (newValue) => {
|
|
120
|
+
if (Array.isArray(newValue)) {
|
|
121
|
+
this.data[variableName] = new ObservableArray(variableName, this, ...newValue);
|
|
122
|
+
this.arrayDataChangeDetection[variableName] = this.data[variableName].slice(); // Create a copy for change detection
|
|
123
|
+
|
|
124
|
+
} else if (typeof newValue === 'object') {
|
|
125
|
+
this.data[variableName] = this._createObject(variableName, newValue);
|
|
126
|
+
} else {
|
|
127
|
+
this.data[variableName] = newValue;
|
|
50
128
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (
|
|
55
|
-
|
|
56
|
-
const oldValue = target[index];
|
|
57
|
-
target[index] = value;
|
|
58
|
-
self._queueUpdate(variableName, action, target[index], index, oldValue);
|
|
129
|
+
this.updateElement(variableName);
|
|
130
|
+
this.updateDisplayConditionalsFor(variableName);
|
|
131
|
+
this.updateStylesFor(variableName);
|
|
132
|
+
if (!Array.isArray(newValue) && typeof newValue !== 'object') {
|
|
133
|
+
this._queueUpdate(variableName, 'modified', this.data[variableName]);
|
|
59
134
|
}
|
|
60
|
-
return true;
|
|
61
|
-
},
|
|
62
|
-
deleteProperty(target, index) {
|
|
63
|
-
const oldValue = target[index];
|
|
64
|
-
target.splice(index, 1);
|
|
65
|
-
self._queueUpdate(variableName, 'removed', oldValue, index);
|
|
66
|
-
return true;
|
|
67
135
|
}
|
|
68
136
|
});
|
|
69
137
|
}
|
|
70
138
|
|
|
71
139
|
_queueUpdate(variableName, action, item, property, oldValue) {
|
|
72
|
-
|
|
73
140
|
if (!this.pendingUpdates.has(variableName)) {
|
|
74
|
-
this.pendingUpdates.
|
|
141
|
+
this.pendingUpdates.set(variableName, []);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
this.pendingUpdates.get(variableName).push({ action, item, property, oldValue });
|
|
145
|
+
|
|
146
|
+
if (this.pendingUpdates.get(variableName).length === 1) {
|
|
75
147
|
setTimeout(() => {
|
|
148
|
+
const updates = this.pendingUpdates.get(variableName);
|
|
76
149
|
this.pendingUpdates.delete(variableName);
|
|
77
|
-
|
|
150
|
+
updates.forEach(update => {
|
|
151
|
+
this._triggerCallbacks(variableName, update);
|
|
152
|
+
});
|
|
78
153
|
this.updateElement(variableName);
|
|
79
|
-
this.
|
|
154
|
+
this.updateDisplayConditionalsFor(variableName);
|
|
80
155
|
this.updateStylesFor(variableName);
|
|
81
156
|
}, 0);
|
|
82
157
|
}
|
|
83
158
|
}
|
|
84
159
|
|
|
85
|
-
_triggerCallbacks(variableName,
|
|
160
|
+
_triggerCallbacks(variableName, callbackData) {
|
|
86
161
|
if (this.callbacks[variableName]) {
|
|
87
|
-
this.callbacks[variableName].forEach(callback =>
|
|
162
|
+
this.callbacks[variableName].forEach(callback => {
|
|
163
|
+
const item = callbackData.item || callbackData.oldValue;
|
|
164
|
+
callback(this.data[variableName], item, callbackData.action);
|
|
165
|
+
});
|
|
88
166
|
}
|
|
89
167
|
}
|
|
90
168
|
|
|
91
|
-
_defineProperty(variableName) {
|
|
92
|
-
Object.defineProperty(window, variableName, {
|
|
93
|
-
get: () => this.data[variableName],
|
|
94
|
-
set: (newValue) => {
|
|
95
|
-
this._assignValueToData(variableName, newValue);
|
|
96
|
-
this.updateElement(variableName);
|
|
97
|
-
this.updateConditionalsFor(variableName);
|
|
98
|
-
this.updateStylesFor(variableName);
|
|
99
|
-
if (!Array.isArray(newValue) && typeof newValue !== 'object') {
|
|
100
|
-
this._queueUpdate(variableName, 'modified', this.data[variableName]);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
169
|
updateElement(variableName) {
|
|
107
170
|
const elements = document.querySelectorAll(`[res="${variableName}"]`);
|
|
108
171
|
const value = this.data[variableName];
|
|
109
172
|
|
|
110
|
-
|
|
111
|
-
|
|
112
173
|
elements.forEach(element => {
|
|
174
|
+
element.value = value;
|
|
113
175
|
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
|
|
114
|
-
element.value = value;
|
|
115
176
|
if (!element.hasAttribute('data-resonant-bound')) {
|
|
116
177
|
element.oninput = () => {
|
|
117
178
|
this.data[variableName] = element.value;
|
|
@@ -162,14 +223,14 @@ class Resonant {
|
|
|
162
223
|
}
|
|
163
224
|
});
|
|
164
225
|
|
|
165
|
-
this.
|
|
226
|
+
this.updateDisplayConditionalsFor(variableName);
|
|
166
227
|
this.updateStylesFor(variableName);
|
|
167
228
|
}
|
|
168
229
|
|
|
169
|
-
|
|
170
|
-
const conditionalElements = document.querySelectorAll(`[res-
|
|
230
|
+
updateDisplayConditionalsFor(variableName) {
|
|
231
|
+
const conditionalElements = document.querySelectorAll(`[res-display*="${variableName}"]`);
|
|
171
232
|
conditionalElements.forEach(conditionalElement => {
|
|
172
|
-
const condition = conditionalElement.getAttribute('res-
|
|
233
|
+
const condition = conditionalElement.getAttribute('res-display');
|
|
173
234
|
try {
|
|
174
235
|
if (eval(condition)) {
|
|
175
236
|
conditionalElement.style.display = '';
|
|
@@ -280,8 +341,6 @@ class Resonant {
|
|
|
280
341
|
const removeKey = onclickEl.getAttribute('res-onclick-remove');
|
|
281
342
|
|
|
282
343
|
if (functionName) {
|
|
283
|
-
onclickEl.onclick = null;
|
|
284
|
-
|
|
285
344
|
onclickEl.onclick = () => {
|
|
286
345
|
const func = new Function('item', `return ${functionName}(item)`);
|
|
287
346
|
func(instance);
|
|
@@ -289,8 +348,6 @@ class Resonant {
|
|
|
289
348
|
}
|
|
290
349
|
|
|
291
350
|
if (removeKey) {
|
|
292
|
-
onclickEl.onclick = null;
|
|
293
|
-
|
|
294
351
|
onclickEl.onclick = () => {
|
|
295
352
|
const index = this.data[variableName].findIndex(t => t[removeKey] === instance[removeKey]);
|
|
296
353
|
if (index !== -1) {
|
package/resonant.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
class
|
|
1
|
+
class ObservableArray extends Array{constructor(e,t,...a){super(...a),this.variableName=e,this.resonantInstance=t,this.isDeleting=!1}push(...e){var 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(a,e,...t){var s=super.splice(a,e,...t);return this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice(),0<e&&s.forEach((e,t)=>{this.resonantInstance._queueUpdate(this.variableName,"removed",e,a+t)}),0<t.length&&t.forEach((e,t)=>{this.resonantInstance._queueUpdate(this.variableName,"added",e,a+t)}),s}set(t,a){if(this[t]!==a){if(this.isDeleting)return!0;var s=this.resonantInstance.arrayDataChangeDetection[this.variableName];let e="modified";(t>=s.length||void 0===s[t])&&(e="added"),this.resonantInstance.arrayDataChangeDetection[this.variableName]=this.slice();s=this[t];this[t]=a,this.resonantInstance._queueUpdate(this.variableName,e,this[t],t,s)}return!0}delete(e){var 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)}}class Resonant{constructor(){this.data={},this.callbacks={},this.pendingUpdates=new Map,this.arrayDataChangeDetection={}}add(e,t){Array.isArray(t)?(this.data[e]=new ObservableArray(e,this,...t),this.arrayDataChangeDetection[e]=this.data[e].slice()):this.data[e]="object"==typeof t?this._createObject(e,t):t,this._defineProperty(e),this.updateElement(e)}addAll(e){Object.entries(e).forEach(([e,t])=>{this.add(e,t)})}_createObject(i,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(i,"modified",e,t,s)),!0}})}_defineProperty(t){Object.defineProperty(window,t,{get:()=>this.data[t],set:e=>{Array.isArray(e)?(this.data[t]=new ObservableArray(t,this,...e),this.arrayDataChangeDetection[t]=this.data[t].slice()):this.data[t]="object"==typeof e?this._createObject(t,e):e,this.updateElement(t),this.updateDisplayConditionalsFor(t),this.updateStylesFor(t),Array.isArray(e)||"object"==typeof e||this._queueUpdate(t,"modified",this.data[t])}})}_queueUpdate(t,e,a,s,i){this.pendingUpdates.has(t)||this.pendingUpdates.set(t,[]),this.pendingUpdates.get(t).push({action:e,item:a,property:s,oldValue:i}),1===this.pendingUpdates.get(t).length&&setTimeout(()=>{var e=this.pendingUpdates.get(t);this.pendingUpdates.delete(t),e.forEach(e=>{this._triggerCallbacks(t,e)}),this.updateElement(t),this.updateDisplayConditionalsFor(t),this.updateStylesFor(t)},0)}_triggerCallbacks(a,s){this.callbacks[a]&&this.callbacks[a].forEach(e=>{var t=s.item||s.oldValue;e(this.data[a],t,s.action)})}updateElement(a){var e=document.querySelectorAll(`[res="${a}"]`);const s=this.data[a];e.forEach(e=>{e.value=s,"INPUT"===e.tagName||"TEXTAREA"===e.tagName?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.updateDisplayConditionalsFor(a),this.updateStylesFor(a)}updateDisplayConditionalsFor(variableName){const conditionalElements=document.querySelectorAll(`[res-display*="${variableName}"]`);conditionalElements.forEach(conditionalElement=>{const condition=conditionalElement.getAttribute("res-display");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(i,n){let r=n.cloneNode(!0);n.innerHTML="",window[i+"_template"]?r=window[i+"_template"]:window[i+"_template"]=r,this.data[i].forEach((s,e)=>{var t=r.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(i,"modified",s,e,s[e])}):(a.value=s[e],a.oninput=()=>{s[e]=a.value,this._queueUpdate(i,"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=()=>{new Function("item",`return ${t}(item)`)(s)}),a&&(e.onclick=()=>{var e=this.data[i].findIndex(e=>e[a]===s[a]);-1!==e&&this.data[i].splice(e,1)})}),t.setAttribute("res-rendered",!0),n.appendChild(t)})}addCallback(e,t){this.callbacks[e]||(this.callbacks[e]=[]),this.callbacks[e].push(t)}}
|