resonantjs 1.0.0

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.
@@ -0,0 +1,32 @@
1
+ # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
2
+ # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
3
+
4
+ name: Node.js Package
5
+
6
+ on:
7
+ release:
8
+ types: [created]
9
+
10
+ jobs:
11
+ build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v3
15
+ - uses: actions/setup-node@v3
16
+ with:
17
+ node-version: 16
18
+ - run: npm ci
19
+
20
+ publish-npm:
21
+ needs: build
22
+ runs-on: ubuntu-latest
23
+ steps:
24
+ - uses: actions/checkout@v3
25
+ - uses: actions/setup-node@v3
26
+ with:
27
+ node-version: 16
28
+ registry-url: https://registry.npmjs.org/
29
+ - run: npm ci
30
+ - run: npm publish
31
+ env:
32
+ NODE_AUTH_TOKEN: ${{secrets.npm_token}}
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Andrew Murgola
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # Resonant.js
2
+
3
+ Resonant.js is an open-source 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.
4
+
5
+ ## Features
6
+
7
+ - **Reactive Data Binding**: Automatically synchronize your data with the UI.
8
+ - **Dynamic List Rendering**: Easily render lists that react to data changes.
9
+ - **Bidirectional Input Binding**: Bind HTML input fields directly to your data model.
10
+ - **Lightweight and Easy to Integrate**: Minimal setup required to get started.
11
+ - **Compatible with Modern Browsers**: Works seamlessly across all modern web browsers.
12
+
13
+ ## Usage
14
+ Include resonant.js in your HTML file, and use the following example to understand how to integrate it into your web application.
15
+
16
+ ```javascript
17
+ <!DOCTYPE html>
18
+ <html lang="en">
19
+ <head>
20
+ <title>Resonant.js Quick Demo</title>
21
+ <script src="./resonant.js"></script>
22
+ </head>
23
+ <body>
24
+ <h1>Resonant.js Quick Demo</h1>
25
+
26
+ <!-- Display and update a single item -->
27
+ <div>
28
+ <h2>Counter</h2>
29
+ <p>Current count: <span res="counter"></span></p>
30
+ <button onclick="incrementCounter()">Increment Counter</button>
31
+ </div>
32
+
33
+ <!-- Demonstrate object property binding -->
34
+ <div>
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>
91
+ </body>
92
+ </html>
93
+
94
+ ```
95
+ ## Features Overview
96
+
97
+ ### Core Concepts
98
+ - **`res` and `res-prop` Attributes**: Bind HTML elements to your data model seamlessly.
99
+ - `res` is used to identify an overarching data model.
100
+ - `res-prop` links individual properties within that model to corresponding UI elements.
101
+
102
+ - **Automatic UI Updates**: Changes to your JavaScript objects instantly reflect in the associated UI components, reducing manual DOM manipulation.
103
+
104
+ ### Advanced Features
105
+ - **Dynamic Arrays and Objects**: Easily handle collections and nested objects to dynamically add or remove elements based on your data structures.
106
+ - **Event Callbacks**: Register custom functions to execute whenever your data model changes.
107
+ - **Bidirectional Input Binding**: Bind form input fields directly to your data, making two-way synchronization simple.
108
+
109
+ ### Example Applications
110
+ - **Single-Page Applications**: Build dynamic and responsive single-page applications quickly.
111
+ - **Admin Dashboards**: Create data-rich dashboards that reflect real-time changes in your database.
112
+ - **Form-Based Applications**: Automate forms, surveys, and user profiles for seamless data entry.
113
+
114
+ ## Future Enhancements
115
+
116
+ - [ ] Support for nested data structures with deep linking.
117
+ - [ ] Advanced filtering and sorting methods for arrays.
118
+ - [ ] Compatibility with popular JavaScript frameworks.
119
+
120
+ ## License
121
+
122
+ Resonant.js is released under the MIT License. You can find a copy of the MIT License in the LICENSE file included with the package.
package/example.html ADDED
@@ -0,0 +1,83 @@
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>
package/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "resonantjs",
3
+ "version": "1.0.0",
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
+ "main": "resonant.js",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/amurgola/ResonantJs.git"
9
+ },
10
+ "author": "Andrew Paul Murgola (andy@murgo.la)",
11
+ "license": "MIT",
12
+ "bugs": {
13
+ "url": "https://github.com/amurgola/ResonantJs/issues"
14
+ },
15
+ "homepage": "https://github.com/amurgola/ResonantJs#readme",
16
+ "scripts": {
17
+ "test": "echo \"Error: no test specified\" && exit 1"
18
+ }
19
+ }
package/resonant.js ADDED
@@ -0,0 +1,131 @@
1
+ class Resonant {
2
+ constructor() {
3
+ this.data = {};
4
+ this.callbacks = {};
5
+ }
6
+
7
+ add(variableName, ...values) {
8
+ let value;
9
+ if (values.length > 1) {
10
+ value = values;
11
+ } else {
12
+ value = values[0];
13
+ }
14
+
15
+ this._assignValueToData(variableName, value);
16
+ this._defineProperty(variableName);
17
+ this.updateElement(variableName);
18
+ }
19
+
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
+ _createObject(parentName, obj) {
31
+ obj[Symbol('isProxy')] = true;
32
+ return new Proxy(obj, {
33
+ set: (target, property, value) => {
34
+ target[property] = value;
35
+ this.updateElement(parentName);
36
+ return true;
37
+ }
38
+ });
39
+ }
40
+
41
+ _createArray(variableName, arr) {
42
+ return new Proxy(arr, {
43
+ get: (target, index) => {
44
+ if (typeof target[index] === 'object') {
45
+ target[index] = this._createObject(`${variableName}[${index}]`, target[index]);
46
+ }
47
+ return target[index];
48
+ },
49
+ set: (target, index, value) => {
50
+ target[index] = value;
51
+ this.updateElement(variableName);
52
+ return true;
53
+ }
54
+ });
55
+ }
56
+
57
+ _defineProperty(variableName) {
58
+ Object.defineProperty(window, variableName, {
59
+ get: () => this.data[variableName],
60
+ set: (newValue) => {
61
+ this._assignValueToData(variableName, newValue);
62
+ this.updateElement(variableName);
63
+ }
64
+ });
65
+ }
66
+
67
+ updateElement(variableName) {
68
+ const elements = document.querySelectorAll(`[res="${variableName}"]`);
69
+ const value = this.data[variableName];
70
+
71
+ elements.forEach(element => {
72
+ if (Array.isArray(value)) {
73
+ element.querySelectorAll(`[res="${variableName}"]` && "[res-rendered=true]").forEach(el => el.remove());
74
+ this._renderArray(variableName, element);
75
+ } else if (typeof value === 'object') {
76
+ const subElements = element.querySelectorAll(`[res-prop]`);
77
+
78
+ subElements.forEach(subEl => {
79
+ const key = subEl.getAttribute('res-prop');
80
+ if (key && key in value) {
81
+ if (subEl.tagName === 'INPUT' || subEl.tagName === 'TEXTAREA') {
82
+ subEl.value = value[key];
83
+ subEl.oninput = () => this.data[variableName][key] = subEl.value;
84
+ } else {
85
+ subEl.innerHTML = value[key];
86
+ }
87
+ }
88
+ });
89
+ } else {
90
+ element.innerHTML = value;
91
+ }
92
+ });
93
+
94
+ if (this.callbacks[variableName]) {
95
+ this.callbacks[variableName](value);
96
+ }
97
+ }
98
+
99
+
100
+ _renderArray(variableName, el) {
101
+ let template = el.cloneNode(true);
102
+ el.innerHTML = '';
103
+
104
+ if (!window[variableName + "_template"]) {
105
+ window[variableName + "_template"] = template;
106
+ } else {
107
+ template = window[variableName + "_template"];
108
+ }
109
+
110
+ this.data[variableName].forEach((instance) => {
111
+ const clonedEl = template.cloneNode(true);
112
+ for (let key in instance) {
113
+ const subEl = clonedEl.querySelector(`[res-prop="${key}"]`);
114
+ if (subEl) {
115
+ if (subEl.tagName === 'INPUT' || subEl.tagName === 'TEXTAREA') {
116
+ subEl.value = instance[key];
117
+ subEl.oninput = () => instance[key] = subEl.value;
118
+ } else {
119
+ subEl.innerHTML = instance[key];
120
+ }
121
+ }
122
+ }
123
+ clonedEl.setAttribute("res-rendered", true);
124
+ el.appendChild(clonedEl);
125
+ });
126
+ }
127
+
128
+ addCallback(variableName, method) {
129
+ this.callbacks[variableName] = method;
130
+ }
131
+ }