resonantjs 1.0.0 → 1.0.2
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 +110 -76
- package/examples/example-basic.html +88 -0
- package/examples/example-taskmanager.html +54 -0
- package/package.json +1 -1
- package/resonant.js +150 -35
- package/example.html +0 -83
package/Demo.gif
ADDED
|
Binary file
|
package/README.md
CHANGED
|
@@ -7,98 +7,62 @@ Resonant.js is an open-source lightweight JavaScript framework that enables reac
|
|
|
7
7
|
- **Reactive Data Binding**: Automatically synchronize your data with the UI.
|
|
8
8
|
- **Dynamic List Rendering**: Easily render lists that react to data changes.
|
|
9
9
|
- **Bidirectional Input Binding**: Bind HTML input fields directly to your data model.
|
|
10
|
+
- **Efficient Conditional Updates**: Only evaluate conditional expressions tied to specific variable changes.
|
|
10
11
|
- **Lightweight and Easy to Integrate**: Minimal setup required to get started.
|
|
11
12
|
- **Compatible with Modern Browsers**: Works seamlessly across all modern web browsers.
|
|
13
|
+
## Installation
|
|
14
|
+
## NPM
|
|
15
|
+
To install via NPM, use the following command:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm i resonantjs
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## CDN
|
|
22
|
+
To use via CDN, include the following URLs in your HTML file:
|
|
23
|
+
|
|
24
|
+
```html
|
|
25
|
+
<script src="https://unpkg.com/resonantjs@latest/resonant.js"></script>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Demo
|
|
29
|
+

|
|
12
30
|
|
|
13
31
|
## Usage
|
|
14
32
|
Include resonant.js in your HTML file, and use the following example to understand how to integrate it into your web application.
|
|
15
33
|
|
|
16
|
-
```
|
|
34
|
+
```html
|
|
17
35
|
<!DOCTYPE html>
|
|
18
36
|
<html lang="en">
|
|
19
37
|
<head>
|
|
20
|
-
|
|
21
|
-
|
|
38
|
+
<title>Resonant.js Basic Example</title>
|
|
39
|
+
<script src="https://unpkg.com/resonantjs@latest/resonant.js"></script>
|
|
22
40
|
</head>
|
|
23
41
|
<body>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<h2>Person Information</h2>
|
|
36
|
-
<div res="person">
|
|
37
|
-
<span res-prop="firstname"></span>
|
|
38
|
-
<span res-prop="lastname"></span>
|
|
39
|
-
<br/><br/>
|
|
40
|
-
First Name: <input type="text" res-prop="firstname">
|
|
41
|
-
Last Name: <input type="text" res-prop="lastname">
|
|
42
|
-
</div>
|
|
43
|
-
</div>
|
|
44
|
-
|
|
45
|
-
<!-- Demonstrate dynamic list rendering -->
|
|
46
|
-
<div>
|
|
47
|
-
<h2>Team Members</h2>
|
|
48
|
-
<ul res="team">
|
|
49
|
-
<li>
|
|
50
|
-
<span res-prop="name"></span> - <span res-prop="role"></span>
|
|
51
|
-
</li>
|
|
52
|
-
</ul>
|
|
53
|
-
<button onclick="addTeamMember()">Add Team Member</button>
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
<script>
|
|
57
|
-
const resonantJs = new Resonant();
|
|
58
|
-
|
|
59
|
-
// Initialize a counter
|
|
60
|
-
resonantJs.add("counter", 0);
|
|
61
|
-
|
|
62
|
-
// Initialize a single person object
|
|
63
|
-
resonantJs.add("person", {
|
|
64
|
-
firstname: "Andy",
|
|
65
|
-
lastname: "Murgola"
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
// Example of a callback
|
|
69
|
-
resonantJs.addCallback("person", exampleCallbackOutput);
|
|
70
|
-
|
|
71
|
-
// Initialize a list of people with dynamic properties
|
|
72
|
-
resonantJs.add("team", [
|
|
73
|
-
{ name: "Alice", role: "Developer" },
|
|
74
|
-
{ name: "Bob", role: "Designer" }
|
|
75
|
-
]);
|
|
76
|
-
|
|
77
|
-
function incrementCounter() {
|
|
78
|
-
counter++;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function addTeamMember() {
|
|
82
|
-
const newMember = { name: "Charlie", role: "Product Manager" };
|
|
83
|
-
team.push(newMember);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function exampleCallbackOutput(result) {
|
|
87
|
-
console.log(result.firstname + " " + result.lastname);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
</script>
|
|
42
|
+
<h1>Resonant.js Basic Example</h1>
|
|
43
|
+
<div>
|
|
44
|
+
<h2>Counter</h2>
|
|
45
|
+
<p>Current count: <span res="counter"></span></p>
|
|
46
|
+
<button onclick="counter++">Increment Counter</button>
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
<script>
|
|
50
|
+
const resonantJs = new Resonant();
|
|
51
|
+
resonantJs.add("counter", 0);
|
|
52
|
+
</script>
|
|
91
53
|
</body>
|
|
92
54
|
</html>
|
|
93
|
-
|
|
94
55
|
```
|
|
56
|
+
|
|
95
57
|
## Features Overview
|
|
96
58
|
|
|
97
59
|
### Core Concepts
|
|
98
60
|
- **`res` and `res-prop` Attributes**: Bind HTML elements to your data model seamlessly.
|
|
99
61
|
- `res` is used to identify an overarching data model.
|
|
100
62
|
- `res-prop` links individual properties within that model to corresponding UI elements.
|
|
101
|
-
|
|
63
|
+
- **`res-conditional` Attribute**: Conditionally display elements based on the data model's properties.
|
|
64
|
+
- **`res-onclick` Attribute**: Triggers a function when an element is clicked, allowing for custom event handling.
|
|
65
|
+
- **`res-onclick-remove` Attribute**: Removes an item from an array when the associated element is clicked.
|
|
102
66
|
- **Automatic UI Updates**: Changes to your JavaScript objects instantly reflect in the associated UI components, reducing manual DOM manipulation.
|
|
103
67
|
|
|
104
68
|
### Advanced Features
|
|
@@ -106,10 +70,80 @@ Include resonant.js in your HTML file, and use the following example to understa
|
|
|
106
70
|
- **Event Callbacks**: Register custom functions to execute whenever your data model changes.
|
|
107
71
|
- **Bidirectional Input Binding**: Bind form input fields directly to your data, making two-way synchronization simple.
|
|
108
72
|
|
|
109
|
-
###
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
-
|
|
73
|
+
### New Features in Version 1.0.2
|
|
74
|
+
|
|
75
|
+
#### Pending Updates Mechanism
|
|
76
|
+
- Introduced to prevent redundant updates and ensure callbacks are only triggered once per update cycle, improving performance and user experience.
|
|
77
|
+
|
|
78
|
+
#### Callback Parameter Enhancement
|
|
79
|
+
- Callbacks now receive detailed parameters including the specific action taken (`added`, `modified`, `removed`), the item affected, and the previous value. This provides better context for handling updates.
|
|
80
|
+
|
|
81
|
+
#### Batched Updates for Object Properties
|
|
82
|
+
- Improved handling of object property updates to ensure changes are batched together, preventing multiple redundant callback triggers.
|
|
83
|
+
|
|
84
|
+
#### Refined Data Binding
|
|
85
|
+
- Enhanced data binding between model and view to ensure consistent synchronization without unnecessary updates.
|
|
86
|
+
|
|
87
|
+
### Task Manager Example
|
|
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
|
+
```
|
|
113
147
|
|
|
114
148
|
## Future Enhancements
|
|
115
149
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<title>Resonant.js Practical Demo</title>
|
|
5
|
+
<script src="../resonant.js"></script>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<h1>Resonant.js Practical Demo</h1>
|
|
9
|
+
|
|
10
|
+
<!-- Display and update a single item -->
|
|
11
|
+
<div>
|
|
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>
|
|
24
|
+
|
|
25
|
+
<!-- Demonstrate object property binding -->
|
|
26
|
+
<div>
|
|
27
|
+
<h2>User Information</h2>
|
|
28
|
+
<div res="user">
|
|
29
|
+
<span res-prop="firstname"></span>
|
|
30
|
+
<span res-prop="lastname"></span>
|
|
31
|
+
<br/>
|
|
32
|
+
<div res-conditional="user.firstname == 'John' && user.lastname == 'Doe'">
|
|
33
|
+
Only shows when firstname is John and lastname is Doe
|
|
34
|
+
</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
|
+
|
|
41
|
+
<!-- Demonstrate dynamic list rendering -->
|
|
42
|
+
<div>
|
|
43
|
+
<h2>Project Members</h2>
|
|
44
|
+
<ul res="projectTeam">
|
|
45
|
+
<li>
|
|
46
|
+
<span res-prop="name"></span> - <span res-prop="role"></span>
|
|
47
|
+
</li>
|
|
48
|
+
</ul>
|
|
49
|
+
<button onclick="addProjectMember()">Add Project Member</button>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<script>
|
|
53
|
+
const resonantJs = new Resonant();
|
|
54
|
+
|
|
55
|
+
// Initialize a single object with add method
|
|
56
|
+
resonantJs.add("counter", 0);
|
|
57
|
+
|
|
58
|
+
// Initialize variables using a configuration object
|
|
59
|
+
resonantJs.addAll({
|
|
60
|
+
user: {
|
|
61
|
+
firstname: "John",
|
|
62
|
+
lastname: "Doe",
|
|
63
|
+
email: ""
|
|
64
|
+
},
|
|
65
|
+
projectTeam: [
|
|
66
|
+
{ name: "Alice", role: "Developer" },
|
|
67
|
+
{ name: "Bob", role: "Designer" }
|
|
68
|
+
]
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Chain together callbacks
|
|
72
|
+
resonantJs.addCallback("user", (user) => {
|
|
73
|
+
console.log(`User updated: ${user.firstname} ${user.lastname}`);
|
|
74
|
+
// You can nest updates within callbacks
|
|
75
|
+
counter++;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
resonantJs.addCallback("counter", (count) => {
|
|
79
|
+
console.log(`Counter updated: ${count}`);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
function addProjectMember() {
|
|
83
|
+
const newMember = { name: "Charlie", role: "Product Manager" };
|
|
84
|
+
projectTeam.push(newMember);
|
|
85
|
+
}
|
|
86
|
+
</script>
|
|
87
|
+
</body>
|
|
88
|
+
</html>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<title>Resonant.js Task Manager Demo</title>
|
|
5
|
+
<script src="../resonant.js"></script>
|
|
6
|
+
</head>
|
|
7
|
+
<body>
|
|
8
|
+
<h1>Resonant.js Task Manager Demo</h1>
|
|
9
|
+
|
|
10
|
+
<!-- Task Input -->
|
|
11
|
+
<div>
|
|
12
|
+
<h2>Add New Task</h2>
|
|
13
|
+
<input type="text" placeholder="Task Name" res="taskName" />
|
|
14
|
+
<button onclick="addTask()">Add Task</button>
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<!-- Task List -->
|
|
18
|
+
<div>
|
|
19
|
+
<h2>Task List</h2>
|
|
20
|
+
<ul res="tasks">
|
|
21
|
+
<li>
|
|
22
|
+
<input type="checkbox" res-prop="done" />
|
|
23
|
+
<span res-prop="name"></span>
|
|
24
|
+
<button res-onclick-remove="name">Remove</button>
|
|
25
|
+
</li>
|
|
26
|
+
</ul>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<script>
|
|
30
|
+
const resonantJs = new Resonant();
|
|
31
|
+
|
|
32
|
+
// Initialize variables using a configuration object
|
|
33
|
+
resonantJs.addAll({
|
|
34
|
+
tasks: [
|
|
35
|
+
{ name: "Task 1", done: false },
|
|
36
|
+
{ name: "Task 2", done: true }
|
|
37
|
+
],
|
|
38
|
+
taskName: ""
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Add a callback to log actions taken on tasks
|
|
42
|
+
resonantJs.addCallback("tasks", (tasks, task, action) => {
|
|
43
|
+
console.log(`Action taken: ${action} for ${task.name}`);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Add a function to add a new task
|
|
47
|
+
function addTask() {
|
|
48
|
+
const newTask = { name: taskName, done: false };
|
|
49
|
+
tasks.push(newTask);
|
|
50
|
+
taskName = '';
|
|
51
|
+
}
|
|
52
|
+
</script>
|
|
53
|
+
</body>
|
|
54
|
+
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "resonantjs",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
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
|
@@ -2,21 +2,21 @@ class Resonant {
|
|
|
2
2
|
constructor() {
|
|
3
3
|
this.data = {};
|
|
4
4
|
this.callbacks = {};
|
|
5
|
+
this.pendingUpdates = new Set();
|
|
5
6
|
}
|
|
6
7
|
|
|
7
|
-
add(variableName,
|
|
8
|
-
let value;
|
|
9
|
-
if (values.length > 1) {
|
|
10
|
-
value = values;
|
|
11
|
-
} else {
|
|
12
|
-
value = values[0];
|
|
13
|
-
}
|
|
14
|
-
|
|
8
|
+
add(variableName, value) {
|
|
15
9
|
this._assignValueToData(variableName, value);
|
|
16
10
|
this._defineProperty(variableName);
|
|
17
11
|
this.updateElement(variableName);
|
|
18
12
|
}
|
|
19
13
|
|
|
14
|
+
addAll(config) {
|
|
15
|
+
Object.entries(config).forEach(([variableName, value]) => {
|
|
16
|
+
this.add(variableName, value);
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
20
|
_assignValueToData(variableName, value) {
|
|
21
21
|
if (Array.isArray(value)) {
|
|
22
22
|
this.data[variableName] = this._createArray(variableName, value);
|
|
@@ -27,39 +27,75 @@ class Resonant {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
_createObject(
|
|
30
|
+
_createObject(variableName, obj) {
|
|
31
31
|
obj[Symbol('isProxy')] = true;
|
|
32
32
|
return new Proxy(obj, {
|
|
33
33
|
set: (target, property, value) => {
|
|
34
|
-
target[property]
|
|
35
|
-
|
|
34
|
+
if (target[property] !== value) {
|
|
35
|
+
const oldValue = target[property];
|
|
36
|
+
target[property] = value;
|
|
37
|
+
this._queueUpdate(variableName, 'modified', target, property, oldValue);
|
|
38
|
+
}
|
|
36
39
|
return true;
|
|
37
40
|
}
|
|
38
41
|
});
|
|
39
42
|
}
|
|
40
43
|
|
|
41
44
|
_createArray(variableName, arr) {
|
|
45
|
+
const self = this;
|
|
42
46
|
return new Proxy(arr, {
|
|
43
|
-
get
|
|
44
|
-
if (typeof target[index] === 'object') {
|
|
45
|
-
target[index] =
|
|
47
|
+
get(target, index) {
|
|
48
|
+
if (typeof target[index] === 'object' && !target[index][Symbol('isProxy')]) {
|
|
49
|
+
target[index] = self._createObject(`${variableName}[${index}]`, target[index]);
|
|
46
50
|
}
|
|
47
51
|
return target[index];
|
|
48
52
|
},
|
|
49
|
-
set
|
|
50
|
-
target[index]
|
|
51
|
-
|
|
53
|
+
set(target, index, value) {
|
|
54
|
+
if (target[index] !== value) {
|
|
55
|
+
const action = target.hasOwnProperty(index) ? 'modified' : 'added';
|
|
56
|
+
const oldValue = target[index];
|
|
57
|
+
target[index] = value;
|
|
58
|
+
self._queueUpdate(variableName, action, target[index], index, oldValue);
|
|
59
|
+
}
|
|
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);
|
|
52
66
|
return true;
|
|
53
67
|
}
|
|
54
68
|
});
|
|
55
69
|
}
|
|
56
70
|
|
|
71
|
+
_queueUpdate(variableName, action, item, property, oldValue) {
|
|
72
|
+
if (!this.pendingUpdates.has(variableName)) {
|
|
73
|
+
this.pendingUpdates.add(variableName);
|
|
74
|
+
setTimeout(() => {
|
|
75
|
+
this.pendingUpdates.delete(variableName);
|
|
76
|
+
this._triggerCallbacks(variableName, action, item, property, oldValue);
|
|
77
|
+
this.updateElement(variableName);
|
|
78
|
+
this.updateConditionalsFor(variableName);
|
|
79
|
+
}, 0);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
_triggerCallbacks(variableName, action, item, property, oldValue) {
|
|
84
|
+
if (this.callbacks[variableName]) {
|
|
85
|
+
this.callbacks[variableName].forEach(callback => callback(this.data[variableName], item, action, property, oldValue));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
57
89
|
_defineProperty(variableName) {
|
|
58
90
|
Object.defineProperty(window, variableName, {
|
|
59
91
|
get: () => this.data[variableName],
|
|
60
92
|
set: (newValue) => {
|
|
61
93
|
this._assignValueToData(variableName, newValue);
|
|
62
94
|
this.updateElement(variableName);
|
|
95
|
+
this.updateConditionalsFor(variableName);
|
|
96
|
+
if (!Array.isArray(newValue) && typeof newValue !== 'object') {
|
|
97
|
+
this._queueUpdate(variableName, 'modified', this.data[variableName]);
|
|
98
|
+
}
|
|
63
99
|
}
|
|
64
100
|
});
|
|
65
101
|
}
|
|
@@ -69,20 +105,39 @@ class Resonant {
|
|
|
69
105
|
const value = this.data[variableName];
|
|
70
106
|
|
|
71
107
|
elements.forEach(element => {
|
|
72
|
-
if (
|
|
73
|
-
element.
|
|
108
|
+
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
|
|
109
|
+
if (!element.hasAttribute('data-resonant-bound')) {
|
|
110
|
+
element.value = value;
|
|
111
|
+
element.oninput = () => {
|
|
112
|
+
this.data[variableName] = element.value;
|
|
113
|
+
};
|
|
114
|
+
element.setAttribute('data-resonant-bound', 'true');
|
|
115
|
+
}
|
|
116
|
+
} else if (Array.isArray(value)) {
|
|
117
|
+
element.querySelectorAll(`[res="${variableName}"][res-rendered=true]`).forEach(el => el.remove());
|
|
74
118
|
this._renderArray(variableName, element);
|
|
75
119
|
} else if (typeof value === 'object') {
|
|
76
120
|
const subElements = element.querySelectorAll(`[res-prop]`);
|
|
77
|
-
|
|
78
121
|
subElements.forEach(subEl => {
|
|
79
122
|
const key = subEl.getAttribute('res-prop');
|
|
80
123
|
if (key && key in value) {
|
|
81
|
-
if (subEl.
|
|
82
|
-
subEl.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
124
|
+
if (!subEl.hasAttribute('data-resonant-bound')) {
|
|
125
|
+
if (subEl.tagName === 'INPUT' || subEl.tagName === 'TEXTAREA') {
|
|
126
|
+
if (subEl.type === 'checkbox') {
|
|
127
|
+
subEl.checked = value[key];
|
|
128
|
+
subEl.onchange = () => {
|
|
129
|
+
this.data[variableName][key] = subEl.checked;
|
|
130
|
+
};
|
|
131
|
+
} else {
|
|
132
|
+
subEl.value = value[key];
|
|
133
|
+
subEl.oninput = () => {
|
|
134
|
+
this.data[variableName][key] = subEl.value;
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
subEl.innerHTML = value[key];
|
|
139
|
+
}
|
|
140
|
+
subEl.setAttribute('data-resonant-bound', 'true');
|
|
86
141
|
}
|
|
87
142
|
}
|
|
88
143
|
});
|
|
@@ -91,11 +146,24 @@ class Resonant {
|
|
|
91
146
|
}
|
|
92
147
|
});
|
|
93
148
|
|
|
94
|
-
|
|
95
|
-
this.callbacks[variableName](value);
|
|
96
|
-
}
|
|
149
|
+
this.updateConditionalsFor(variableName);
|
|
97
150
|
}
|
|
98
151
|
|
|
152
|
+
updateConditionalsFor(variableName) {
|
|
153
|
+
const conditionalElements = document.querySelectorAll(`[res-conditional*="${variableName}"]`);
|
|
154
|
+
conditionalElements.forEach(conditionalElement => {
|
|
155
|
+
const condition = conditionalElement.getAttribute('res-conditional');
|
|
156
|
+
try {
|
|
157
|
+
if (eval(condition)) {
|
|
158
|
+
conditionalElement.style.display = '';
|
|
159
|
+
} else {
|
|
160
|
+
conditionalElement.style.display = 'none';
|
|
161
|
+
}
|
|
162
|
+
} catch (e) {
|
|
163
|
+
console.error(`Error evaluating condition for ${variableName}: ${condition}`, e);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
99
167
|
|
|
100
168
|
_renderArray(variableName, el) {
|
|
101
169
|
let template = el.cloneNode(true);
|
|
@@ -107,25 +175,72 @@ class Resonant {
|
|
|
107
175
|
template = window[variableName + "_template"];
|
|
108
176
|
}
|
|
109
177
|
|
|
110
|
-
this.data[variableName].forEach((instance) => {
|
|
178
|
+
this.data[variableName].forEach((instance, index) => {
|
|
111
179
|
const clonedEl = template.cloneNode(true);
|
|
180
|
+
clonedEl.setAttribute("res-index", index);
|
|
112
181
|
for (let key in instance) {
|
|
113
182
|
const subEl = clonedEl.querySelector(`[res-prop="${key}"]`);
|
|
114
183
|
if (subEl) {
|
|
115
|
-
if (subEl.
|
|
116
|
-
subEl.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
184
|
+
if (!subEl.hasAttribute('data-resonant-bound')) {
|
|
185
|
+
if (subEl.tagName === 'INPUT' || subEl.tagName === 'TEXTAREA') {
|
|
186
|
+
if (subEl.type === 'checkbox') {
|
|
187
|
+
subEl.checked = instance[key];
|
|
188
|
+
subEl.onchange = () => {
|
|
189
|
+
instance[key] = subEl.checked;
|
|
190
|
+
this._queueUpdate(variableName, 'modified', instance, key, instance[key]);
|
|
191
|
+
};
|
|
192
|
+
} else {
|
|
193
|
+
subEl.value = instance[key];
|
|
194
|
+
subEl.oninput = () => {
|
|
195
|
+
instance[key] = subEl.value;
|
|
196
|
+
this._queueUpdate(variableName, 'modified', instance, key, instance[key]);
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
} else {
|
|
200
|
+
subEl.innerHTML = instance[key];
|
|
201
|
+
}
|
|
202
|
+
subEl.setAttribute('data-resonant-bound', 'true');
|
|
120
203
|
}
|
|
121
204
|
}
|
|
122
205
|
}
|
|
206
|
+
|
|
207
|
+
// Handle res-onclick
|
|
208
|
+
const onclickElements = clonedEl.querySelectorAll('[res-onclick], [res-onclick-remove]');
|
|
209
|
+
onclickElements.forEach(onclickEl => {
|
|
210
|
+
const functionName = onclickEl.getAttribute('res-onclick');
|
|
211
|
+
const removeKey = onclickEl.getAttribute('res-onclick-remove');
|
|
212
|
+
|
|
213
|
+
if (functionName) {
|
|
214
|
+
// Remove any existing event listeners to prevent duplicates
|
|
215
|
+
onclickEl.onclick = null;
|
|
216
|
+
|
|
217
|
+
onclickEl.onclick = () => {
|
|
218
|
+
const func = new Function('item', `return ${functionName}(item)`);
|
|
219
|
+
func(instance);
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (removeKey) {
|
|
224
|
+
onclickEl.onclick = null;
|
|
225
|
+
|
|
226
|
+
onclickEl.onclick = () => {
|
|
227
|
+
const index = this.data[variableName].findIndex(t => t[removeKey] === instance[removeKey]);
|
|
228
|
+
if (index !== -1) {
|
|
229
|
+
this.data[variableName].splice(index, 1);
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
123
235
|
clonedEl.setAttribute("res-rendered", true);
|
|
124
236
|
el.appendChild(clonedEl);
|
|
125
237
|
});
|
|
126
238
|
}
|
|
127
239
|
|
|
128
240
|
addCallback(variableName, method) {
|
|
129
|
-
this.callbacks[variableName]
|
|
241
|
+
if (!this.callbacks[variableName]) {
|
|
242
|
+
this.callbacks[variableName] = [];
|
|
243
|
+
}
|
|
244
|
+
this.callbacks[variableName].push(method);
|
|
130
245
|
}
|
|
131
246
|
}
|
package/example.html
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<title>Resonant.js Quick Demo</title>
|
|
5
|
-
<script src="resonant.js"></script>
|
|
6
|
-
</head>
|
|
7
|
-
<body>
|
|
8
|
-
<h1>Resonant.js Quick Demo</h1>
|
|
9
|
-
|
|
10
|
-
<!-- Display and update a single item -->
|
|
11
|
-
<div>
|
|
12
|
-
<h2>Counter</h2>
|
|
13
|
-
<p>
|
|
14
|
-
Current count: <span res="counter"></span>
|
|
15
|
-
</p>
|
|
16
|
-
<button onclick="incrementCounter()">Increment Counter</button>
|
|
17
|
-
</div>
|
|
18
|
-
|
|
19
|
-
<!-- Demonstrate object property binding -->
|
|
20
|
-
<div>
|
|
21
|
-
<h2>Person Information</h2>
|
|
22
|
-
<div res="person">
|
|
23
|
-
<span res-prop="firstname"></span>
|
|
24
|
-
<span res-prop="lastname"></span>
|
|
25
|
-
<br/>
|
|
26
|
-
<br/>
|
|
27
|
-
|
|
28
|
-
First Name: <input type="text" res-prop="firstname" />
|
|
29
|
-
Last Name: <input type="text" res-prop="lastname" />
|
|
30
|
-
</div>
|
|
31
|
-
</div>
|
|
32
|
-
|
|
33
|
-
<!-- Demonstrate dynamic list rendering -->
|
|
34
|
-
<div>
|
|
35
|
-
<h2>Team Members</h2>
|
|
36
|
-
<ul res="team">
|
|
37
|
-
<li>
|
|
38
|
-
<span res-prop="name"></span> - <span res-prop="role"></span>
|
|
39
|
-
</li>
|
|
40
|
-
</ul>
|
|
41
|
-
<button onclick="addTeamMember()">Add Team Member</button>
|
|
42
|
-
</div>
|
|
43
|
-
|
|
44
|
-
<script>
|
|
45
|
-
const resonantJs = new Resonant();
|
|
46
|
-
|
|
47
|
-
// Initialize a counter
|
|
48
|
-
resonantJs.add("counter", 0);
|
|
49
|
-
|
|
50
|
-
function exampleCallbackOutput(result) {
|
|
51
|
-
console.log(result.firstname + " " + result.lastname);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Initialize a single person object
|
|
55
|
-
resonantJs.add("person", {
|
|
56
|
-
firstname: "Andy",
|
|
57
|
-
lastname: "Murgola"
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// Initialize a list of people with dynamic properties
|
|
61
|
-
resonantJs.add("team", [
|
|
62
|
-
{ name: "Alice", role: "Developer" },
|
|
63
|
-
{ name: "Bob", role: "Designer" }
|
|
64
|
-
]);
|
|
65
|
-
|
|
66
|
-
// Example of a callback
|
|
67
|
-
resonantJs.addCallback("person", exampleCallbackOutput);
|
|
68
|
-
|
|
69
|
-
function incrementCounter() {
|
|
70
|
-
counter++;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function updatePersonInfo(prop, value) {
|
|
74
|
-
person[prop] = value;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function addTeamMember() {
|
|
78
|
-
const newMember = { name: "Charlie", role: "Product Manager" };
|
|
79
|
-
team.push(newMember);
|
|
80
|
-
}
|
|
81
|
-
</script>
|
|
82
|
-
</body>
|
|
83
|
-
</html>
|