cdom 0.0.8 → 0.0.10
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/README.md +178 -446
- package/examples/dashboard.html +78 -80
- package/examples/repro.html +49 -0
- package/examples/scratch.html +23 -16
- package/examples/spreadsheet.cdom +147 -21
- package/helpers/assign.js +3 -1
- package/helpers/clear.js +3 -1
- package/helpers/decrement.js +3 -1
- package/helpers/increment.js +3 -1
- package/helpers/pop.js +3 -1
- package/helpers/push.js +3 -1
- package/helpers/set.js +3 -1
- package/helpers/toggle.js +3 -1
- package/index.js +204 -226
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
# cDOM - Computational DOM
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **⚠️ EXPERIMENTAL**: cDOM is currently in version v0.0.10. The syntax and API are rapidly evolving and may change without notice. Use with caution.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**A reactive UI library with hypermedia capabilities with modified JPRX support (reactive JSON Pointers and XPath) plus JSON Schema validation and state persistence.**
|
|
6
|
+
|
|
7
|
+
cDOM is a reactive framework that lets you build dynamic UIs using declarative object notation. Uniquely, cDOM uses **structural reactivity**, where logic is defined via JSON structure rather than string parsing.
|
|
6
8
|
|
|
7
9
|
## Features
|
|
8
10
|
|
|
9
11
|
- 🎯 **Reactive State Management** - Signals and state with automatic dependency tracking
|
|
10
12
|
- 🔄 **Declarative UI** - Build interfaces using simple JavaScript objects
|
|
13
|
+
- 🧱 **Structural Reactivity** - Define complex logic using nested objects `{ "=": ... }`
|
|
11
14
|
- 🛡️ **Schema Validation** - Built-in JSON Schema validation for robust state
|
|
12
15
|
- 💾 **Persistence** - Automatic sync to `localStorage` or `sessionStorage`
|
|
13
16
|
- 🌐 **Hypermedia Support** - `src` and `href` attributes for dynamic content loading
|
|
14
|
-
- 📊 **XPath & CSS Queries** - Navigate and query the DOM with reactive
|
|
15
|
-
- 🧮 **
|
|
17
|
+
- 📊 **XPath & CSS Queries** - Navigate and query the DOM with reactive `{ "$": ... }` expressions
|
|
18
|
+
- 🧮 **Direct Operator Support** - Use operators like `*`, `+`, `>=` directly in your structure
|
|
16
19
|
- 🪶 **Lightweight** - ~15KB minified, ZERO dependencies
|
|
17
20
|
- 🔌 **Standalone** - Works independently of Lightview
|
|
18
21
|
|
|
@@ -43,175 +46,169 @@ Download `cdom.js` and include it in your project:
|
|
|
43
46
|
|
|
44
47
|
<body>
|
|
45
48
|
<script>
|
|
46
|
-
//
|
|
47
|
-
cDOM({
|
|
48
|
-
div: {
|
|
49
|
-
oncreate: function() {
|
|
50
|
-
cDOM.state({ count: 0 }, { name: 'counter', scope: this });
|
|
51
|
-
},
|
|
52
|
-
children: [
|
|
53
|
-
{ h2: "Counter Example" },
|
|
54
|
-
{ p: ["Count: ", "_(/counter/count)"] },
|
|
55
|
-
{ button: {
|
|
56
|
-
onclick: "_(++/counter/count)",
|
|
57
|
-
children: ["Increment"]
|
|
58
|
-
}}
|
|
59
|
-
]
|
|
60
|
-
}
|
|
61
|
-
}, { target: document.body, location: 'beforeend' });
|
|
62
|
-
</script>
|
|
63
|
-
</body>
|
|
64
|
-
</html>
|
|
65
|
-
```
|
|
49
|
+
// Register helper
|
|
50
|
+
cDOM.helper('increment', (s) => { if(s && typeof s.count === 'number') s.count++; return s.count; });
|
|
66
51
|
|
|
67
|
-
### Inline Usage
|
|
68
|
-
|
|
69
|
-
cDOM will replace the script is runs in if an emty options object is provided, just put the script where you want the HTML.
|
|
70
|
-
|
|
71
|
-
```html
|
|
72
|
-
<html>
|
|
73
|
-
<script src="https://cdn.jsdelivr.net/npm/cdom/index.min.js"></script>
|
|
74
|
-
|
|
75
|
-
<body>
|
|
76
|
-
<script>
|
|
77
52
|
// Create a simple counter
|
|
78
53
|
cDOM({
|
|
79
54
|
div: {
|
|
80
|
-
oncreate:
|
|
81
|
-
|
|
55
|
+
oncreate: {
|
|
56
|
+
"=state": [{ count: 0 }, { name: 'counter', scope: "$this" }]
|
|
82
57
|
},
|
|
83
58
|
children: [
|
|
84
59
|
{ h2: "Counter Example" },
|
|
85
|
-
{ p: ["Count: ", "
|
|
60
|
+
{ p: ["Count: ", { "=": "/counter/count" }] },
|
|
86
61
|
{ button: {
|
|
87
|
-
onclick: "
|
|
62
|
+
onclick: { "=increment": ["/counter"] },
|
|
88
63
|
children: ["Increment"]
|
|
89
64
|
}}
|
|
90
65
|
]
|
|
91
66
|
}
|
|
92
|
-
}, { });
|
|
67
|
+
}, { target: document.body, location: 'beforeend' });
|
|
93
68
|
</script>
|
|
94
69
|
</body>
|
|
95
70
|
</html>
|
|
96
71
|
```
|
|
97
72
|
|
|
98
|
-
**Resulting DOM (as seen in DevTools):**
|
|
99
|
-
|
|
100
|
-
```html
|
|
101
|
-
<body>
|
|
102
|
-
<div>
|
|
103
|
-
<h2>Counter Example</h2>
|
|
104
|
-
<p>Count: 0</p>
|
|
105
|
-
<button onclick="_(++/counter/count)">Increment</button>
|
|
106
|
-
</div>
|
|
107
|
-
</body>
|
|
108
|
-
```
|
|
109
|
-
|
|
110
|
-
|
|
111
73
|
### Component Functions
|
|
112
74
|
|
|
113
|
-
Create reusable components as functions.
|
|
114
|
-
|
|
115
|
-
**Option A: Returning a rendered element**
|
|
75
|
+
Create reusable components as functions.
|
|
116
76
|
|
|
117
77
|
```javascript
|
|
118
|
-
const { state } = cDOM;
|
|
119
78
|
function Counter(initialValue = 0) {
|
|
120
|
-
|
|
121
|
-
return cDOM({
|
|
79
|
+
return {
|
|
122
80
|
div: {
|
|
123
81
|
class: "counter-widget",
|
|
124
|
-
|
|
125
|
-
|
|
82
|
+
// Use structural object for initialization
|
|
83
|
+
oncreate: {
|
|
84
|
+
"=state": [{ count: initialValue }, { name: 'local', scope: "$this" }]
|
|
126
85
|
},
|
|
127
86
|
children: [
|
|
128
87
|
{ h3: "Counter" },
|
|
129
|
-
{ p: ["Current: ", "
|
|
130
|
-
|
|
88
|
+
{ p: ["Current: ", { "=": "/local/count" }] },
|
|
89
|
+
// Structural event handler
|
|
90
|
+
{ button: {
|
|
91
|
+
onclick: { "=increment": ["/local"] },
|
|
92
|
+
children: ["+"]
|
|
93
|
+
}}
|
|
131
94
|
]
|
|
132
95
|
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
**Option B: Deferring to a wrapper (Returning oDOM)**
|
|
138
|
-
|
|
139
|
-
Alternatively, skip the `cDOM()` call to return a plain object. This is often cleaner for nesting components within larger structures.
|
|
140
|
-
|
|
141
|
-
```javascript
|
|
142
|
-
function Header(title) {
|
|
143
|
-
return { h1: title }; // Return raw oDOM
|
|
96
|
+
};
|
|
144
97
|
}
|
|
145
98
|
|
|
146
99
|
cDOM({
|
|
147
100
|
div: [
|
|
148
|
-
|
|
149
|
-
Counter(0)
|
|
101
|
+
{ h1: "My App" },
|
|
102
|
+
Counter(0)
|
|
150
103
|
]
|
|
151
104
|
}, {});
|
|
152
105
|
```
|
|
153
106
|
|
|
154
107
|
## Core Concepts
|
|
155
108
|
|
|
156
|
-
### 1.
|
|
109
|
+
### 1. Structural Reactivity (The `=` Key)
|
|
110
|
+
|
|
111
|
+
cDOM v0.0.10 moves away from complex string parsing (`_()`) in favor of **structural reactivity**. You express logic using JSON keys starting with `=`.
|
|
112
|
+
|
|
113
|
+
**State Lookup:**
|
|
114
|
+
```javascript
|
|
115
|
+
{ "=": "/user/name" } // Resolves to state value
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Math Expressions:**
|
|
119
|
+
For simple math, you can still use string expressions inside the value:
|
|
120
|
+
```javascript
|
|
121
|
+
{ "=": "/price * /quantity" }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Helper Calls:**
|
|
125
|
+
Complex logic uses the key as the helper name:
|
|
126
|
+
```javascript
|
|
127
|
+
{ "=increment": ["/counter"] } // Calls 'increment' helper with resolving args
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Direct Operators:**
|
|
131
|
+
You can use mathematical and logical operators directly as keys:
|
|
132
|
+
```javascript
|
|
133
|
+
{ "*": ["/price", "/qty"] }
|
|
134
|
+
{ ">=": ["/age", 18] }
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 3. Sequential Actions (Array Handlers)
|
|
157
138
|
|
|
158
|
-
|
|
139
|
+
Event handlers (`onclick`, `onmount`, `onchange`, etc.) can accept an **Array** of structural expressions. Each expression in the array will be executed sequentially. This is useful for performing multiple side-effects in a single interaction.
|
|
159
140
|
|
|
160
141
|
```javascript
|
|
161
142
|
{
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
{
|
|
166
|
-
{
|
|
167
|
-
|
|
143
|
+
button: {
|
|
144
|
+
onclick: [
|
|
145
|
+
{ "set": ["/ui/loading", true] },
|
|
146
|
+
{ "=analytics.track": ["save_clicked"] }, // Resolves via global path (window.analytics.track)
|
|
147
|
+
{ "=saveData": "/form" },
|
|
148
|
+
{ "set": ["/ui/status", "Saved!"] },
|
|
149
|
+
{ "set": ["/ui/loading", false] }
|
|
150
|
+
],
|
|
151
|
+
children: ["Save Now"]
|
|
168
152
|
}
|
|
169
153
|
}
|
|
170
154
|
```
|
|
171
155
|
|
|
172
|
-
###
|
|
156
|
+
### 4. DOM Queries (The `$` Key)
|
|
173
157
|
|
|
174
|
-
|
|
158
|
+
Query the DOM using XPath or CSS selectors via the `$` key.
|
|
159
|
+
|
|
160
|
+
**Structural Usage (Reactive):**
|
|
161
|
+
When used as a key in a cDOM object, queries are reactive to DOM changes using a `MutationObserver`. They will automatically update when nodes are added, removed, or attributes change.
|
|
175
162
|
|
|
176
163
|
```javascript
|
|
177
|
-
//
|
|
178
|
-
|
|
164
|
+
// XPath - Count buttons (updates automatically on DOM change)
|
|
165
|
+
{ "$": "count(//button)" }
|
|
179
166
|
|
|
180
|
-
//
|
|
181
|
-
|
|
167
|
+
// CSS - Get value
|
|
168
|
+
{ "$": "#myInput" }
|
|
182
169
|
```
|
|
183
170
|
|
|
184
|
-
|
|
171
|
+
**Functional Usage (Non-Reactive to DOM):**
|
|
172
|
+
When used inside an expression string or directly in JavaScript, the query is a one-time evaluation. It will not re-run when the DOM changes, unless the surrounding expression is triggered by a state change.
|
|
185
173
|
|
|
186
174
|
```javascript
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// Scoped state (component-local)
|
|
191
|
-
cDOM.state({ count: 0 }, { name: 'counter', scope: this });
|
|
175
|
+
// This counts buttons once, or when a state dependency triggers a re-eval
|
|
176
|
+
{ "=": "count($('//button')) + 1" }
|
|
192
177
|
```
|
|
193
178
|
|
|
194
|
-
|
|
179
|
+
> **Design Note: Why aren't all queries live?**
|
|
180
|
+
> While making every `$(...)` call live would be convenient, it carries significant performance overhead. Structural reactivity (`{ "$": ... }`) allows the engine to explicitly track which elements are watching the DOM, preventing "mutation storms" and infinite loops while ensuring efficient memory cleanup.
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
### 5. Signals and State
|
|
184
|
+
|
|
185
|
+
#### Initialization
|
|
186
|
+
|
|
187
|
+
Use the `state` helper in `oncreate`:
|
|
195
188
|
|
|
196
189
|
```javascript
|
|
197
|
-
|
|
198
|
-
"
|
|
190
|
+
oncreate: {
|
|
191
|
+
"=state": [
|
|
192
|
+
{ user: 'Alice' },
|
|
193
|
+
{ name: 'currentUser' }
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
```
|
|
199
197
|
|
|
200
|
-
|
|
201
|
-
"_(counter/count)"
|
|
202
|
-
// OR
|
|
203
|
-
"_(./counter/count)"
|
|
198
|
+
#### Scoped State
|
|
204
199
|
|
|
205
|
-
|
|
206
|
-
"_(/price * /quantity + /shipping)"
|
|
200
|
+
Scope state to a specific component using the `$this` keyword in the options:
|
|
207
201
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
202
|
+
```javascript
|
|
203
|
+
oncreate: {
|
|
204
|
+
"=state": [
|
|
205
|
+
{ count: 0 },
|
|
206
|
+
{ name: 'counter', scope: "$this" }
|
|
207
|
+
]
|
|
208
|
+
}
|
|
212
209
|
```
|
|
213
210
|
|
|
214
|
-
###
|
|
211
|
+
### 6. Schema Validation
|
|
215
212
|
|
|
216
213
|
cDOM supports JSON Schema validation to ensure your state remains consistent.
|
|
217
214
|
|
|
@@ -227,367 +224,133 @@ cDOM.schema('User', {
|
|
|
227
224
|
});
|
|
228
225
|
|
|
229
226
|
// Apply to state
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
|
|
227
|
+
oncreate: {
|
|
228
|
+
"=state": [
|
|
229
|
+
{ name: 'Bob', age: 25 },
|
|
230
|
+
{ name: 'user', schema: 'User' }
|
|
231
|
+
]
|
|
232
|
+
}
|
|
236
233
|
```
|
|
237
234
|
|
|
238
|
-
###
|
|
235
|
+
### 7. Persistence
|
|
239
236
|
|
|
240
237
|
Sync your signals and state automatically to `localStorage` or `sessionStorage`.
|
|
241
238
|
|
|
242
239
|
```javascript
|
|
243
240
|
// Retrieve on every access, update on every change
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const settings = cDOM.state({ zoom: 1 }, {
|
|
251
|
-
name: 'app-settings',
|
|
252
|
-
storage: sessionStorage
|
|
253
|
-
});
|
|
241
|
+
oncreate: {
|
|
242
|
+
"=signal": [
|
|
243
|
+
'light',
|
|
244
|
+
{ name: 'site-theme', storage: localStorage }
|
|
245
|
+
]
|
|
246
|
+
}
|
|
254
247
|
```
|
|
255
248
|
|
|
256
|
-
###
|
|
249
|
+
### 8. Persistence & Transformations
|
|
257
250
|
|
|
258
|
-
Automatically cast incoming values
|
|
251
|
+
Automatically cast incoming values or sync with storage.
|
|
259
252
|
|
|
260
253
|
```javascript
|
|
261
|
-
|
|
254
|
+
cDOM.signal(0, {
|
|
262
255
|
name: 'count',
|
|
263
256
|
transform: 'Integer' // Built-in: Integer, Number, String, Boolean
|
|
264
257
|
});
|
|
265
|
-
|
|
266
|
-
count.value = "10"; // Automatically becomes 10 (Number)
|
|
267
258
|
```
|
|
268
259
|
|
|
269
|
-
|
|
260
|
+
## Supported Operators and Helpers
|
|
270
261
|
|
|
271
|
-
###
|
|
262
|
+
### Operators (Structural Keys)
|
|
272
263
|
|
|
273
|
-
|
|
264
|
+
These can be used directly as keys in your cDOM structure (e.g., `{ "+": [1, 2] }`).
|
|
274
265
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
266
|
+
* **Math:** `+`, `-`, `*`, `/`, `%`
|
|
267
|
+
* **Comparison:** `==`, `!=`, `>`, `<`, `>=`, `<=`
|
|
268
|
+
* **Logic:** `&&`, `||`, `!` (unary)
|
|
269
|
+
* **Mutation:** `++`, `--`
|
|
270
|
+
* **Ternary:** `?`, `:`
|
|
278
271
|
|
|
279
|
-
|
|
280
|
-
_('/user/name') // Returns the expression string for lazy binding
|
|
281
|
-
```
|
|
272
|
+
### Built-in Helpers
|
|
282
273
|
|
|
283
|
-
|
|
274
|
+
Helpers are dynamically loaded if not already registered. You can use them structurally (e.g., `{ "=sum": [1, 2] }`) or within expression strings.
|
|
284
275
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
```
|
|
276
|
+
#### Math & Statistics
|
|
277
|
+
`abs`, `add`, `average`, `avg`, `ceil`, `ceiling`, `floor`, `int`, `max`, `median`, `min`, `mod`, `multiply`, `percent`, `pow`, `power`, `rand`, `random`, `round`, `sign`, `sqrt`, `stddev`, `stdev`, `subtract`, `sum`, `trunc`, `var`, `variance`
|
|
288
278
|
|
|
289
|
-
|
|
279
|
+
#### Logic & Flow
|
|
280
|
+
`and`, `or`, `not`, `if`, `ifs`, `switch`, `choose`, `coalesce`, `iferror`
|
|
290
281
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
```
|
|
282
|
+
#### String Manipulation
|
|
283
|
+
`concat`, `join`, `split`, `trim`, `upper`, `lower`, `proper`, `titlecase`, `tocamelcase`, `toslugcase`, `left`, `right`, `mid`, `len`, `length`, `slice`, `substring`, `replace`, `substitute`, `padend`, `padstart`, `startswith`, `endswith`, `includes`, `charat`, `text`, `textjoin`, `fixed`
|
|
294
284
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
The built-in secure parser handles math and logic operations natively.
|
|
285
|
+
#### Array & Object
|
|
286
|
+
`count`, `map`, `filter`, `reduce`, `every`, `some`, `find`, `findindex`, `sort`, `reverse`, `push`, `pop`, `first`, `last`, `unique`, `flat`, `keys`, `object`, `isarray`, `xlookup`
|
|
298
287
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
_('./local/price * 1.1') // Scoped path (requires ./ prefix)
|
|
302
|
-
```
|
|
288
|
+
#### Type Checking
|
|
289
|
+
`isnumber`, `isstring`, `istext`, `isblank`, `isempty`, `isarray`
|
|
303
290
|
|
|
304
|
-
|
|
291
|
+
#### Date & Time
|
|
292
|
+
`now`, `today`, `day`, `month`, `year`, `weekday`, `datedif`
|
|
305
293
|
|
|
306
|
-
|
|
294
|
+
#### Formatting
|
|
295
|
+
`currency`, `tofixed`, `tolocalestring`
|
|
307
296
|
|
|
308
|
-
|
|
297
|
+
#### State Mutation
|
|
298
|
+
`set`, `assign`, `increment`, `decrement`, `clear`, `toggle`
|
|
309
299
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
* ✅ `"_( 'Hello ' + /name )"` (Using string concatenation in math expression)
|
|
313
|
-
* ✅ `"_(concat('Hello ', /name))"` (Using concat helper)
|
|
300
|
+
#### Network
|
|
301
|
+
`fetch`, `webservice`
|
|
314
302
|
|
|
315
|
-
|
|
316
|
-
* ✅ `class="btn _(/isActive ? 'active' : '') text-lg"`
|
|
317
|
-
* ✅ `href="/users/_(/id)/profile"`
|
|
303
|
+
### Defining Custom Helpers
|
|
318
304
|
|
|
319
|
-
|
|
305
|
+
You can register custom helpers using `cDOM.helper(name, fn)`.
|
|
320
306
|
|
|
321
|
-
|
|
307
|
+
#### Mutation Helpers
|
|
308
|
+
If a helper is designed to mutate state data (rather than just calculating a value), you **must** set the `.mutates = true` property on the function. This informs the cDOM parser to pass the underlying state reference (wrapper) rather than the unwrapped value, allowing the helper to perform the update.
|
|
322
309
|
|
|
310
|
+
**Example: Custom Increment**
|
|
323
311
|
```javascript
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
"$(.active)" // Query by class
|
|
330
|
-
```
|
|
331
|
-
|
|
332
|
-
DOM queries are reactive, if the DOM changes, the query will be re-evaluated.
|
|
333
|
-
|
|
334
|
-
### 5. Lifecycle Hooks
|
|
335
|
-
|
|
336
|
-
- **`oncreate`**: Called when element is created (before DOM insertion)
|
|
337
|
-
- **`onmount`**: Called when element is added to the DOM
|
|
338
|
-
|
|
339
|
-
```javascript
|
|
340
|
-
{
|
|
341
|
-
div: {
|
|
342
|
-
oncreate() {
|
|
343
|
-
// Initialize state here
|
|
344
|
-
cDOM.state({ data: [] }, { name: 'myData', scope: this });
|
|
345
|
-
},
|
|
346
|
-
onmount() {
|
|
347
|
-
// DOM is ready, can access this element
|
|
348
|
-
console.log('Mounted:', this);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
312
|
+
const increment = function (target, by = 1) {
|
|
313
|
+
// target here is a state wrapper with a .value property
|
|
314
|
+
const current = (target && typeof target === 'object' && 'value' in target) ? target.value : 0;
|
|
315
|
+
target.value = Number(current) + Number(by);
|
|
316
|
+
return target.value;
|
|
351
317
|
}
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
## Hypermedia Features
|
|
355
|
-
|
|
356
|
-
### The `src` Attribute
|
|
357
|
-
|
|
358
|
-
Load content dynamically into any element:
|
|
359
318
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
<div src="#template"></div>
|
|
363
|
-
|
|
364
|
-
<!-- Load HTML file -->
|
|
365
|
-
<div src="/components/header.html"></div>
|
|
366
|
-
|
|
367
|
-
<!-- Load cDOM JSON -->
|
|
368
|
-
<div src="/data/widget.cdom"></div>
|
|
369
|
-
|
|
370
|
-
<!-- Load any text (displays in <pre>) -->
|
|
371
|
-
<div src="/data/config.json"></div>
|
|
372
|
-
```
|
|
373
|
-
|
|
374
|
-
**Important:** For non-standard elements (anything other than `<img>`, `<script>`, etc.), src paths **MUST** start with `./`, `../`, or `/`. This ensures they are correctly identified as URLs and not CSS selectors.
|
|
375
|
-
|
|
376
|
-
**Supported content types:**
|
|
377
|
-
- `.cdom` / `application/cdom` → Parsed as cDOM JSON
|
|
378
|
-
- `.html` / `text/html` → Inserted as HTML
|
|
379
|
-
- Everything else → Displayed in `<pre>` tag
|
|
380
|
-
|
|
381
|
-
### The `href` Attribute (Non-`<a>` Elements)
|
|
382
|
-
|
|
383
|
-
Make any element clickable with navigation:
|
|
384
|
-
|
|
385
|
-
```html
|
|
386
|
-
<!-- Load content on click -->
|
|
387
|
-
<button href="/api/data" target="#results">Load Data</button>
|
|
319
|
+
// CRITICAL: Must flag as mutation for the parser to pass the state reference
|
|
320
|
+
increment.mutates = true;
|
|
388
321
|
|
|
389
|
-
|
|
390
|
-
<button href="/docs.html#section-2">Jump to Section</button>
|
|
322
|
+
cDOM.helper('myIncrement', increment);
|
|
391
323
|
```
|
|
392
324
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
## Helpers
|
|
397
|
-
|
|
398
|
-
Register custom helper functions:
|
|
399
|
-
|
|
325
|
+
Usage in cDOM:
|
|
400
326
|
```javascript
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
"_(double(/count))"
|
|
406
|
-
"_(greet(/user/name))"
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
// use directly in functions
|
|
410
|
-
double(_('/user/name'))
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
When `_()` is used a string in a cDOM it establishes reatcive context and calls to wrapped helpers do not need to wrap state paths in nested `_()`. When a helper is called directly it does not establish a reactive context, but it know how to handle reactive arguments.
|
|
414
|
-
|
|
415
|
-
## Complete Example: Todo List
|
|
416
|
-
|
|
417
|
-
```html
|
|
418
|
-
<html>
|
|
419
|
-
<script src="https://cdn.jsdelivr.net/npm/cdom/index.min.js"></script>
|
|
420
|
-
|
|
421
|
-
<body>
|
|
422
|
-
<script>
|
|
423
|
-
const { state } = cDOM;
|
|
424
|
-
|
|
425
|
-
// Create global state
|
|
426
|
-
const appState = state({
|
|
427
|
-
todos: [],
|
|
428
|
-
input: ''
|
|
429
|
-
}, { name: 'app' });
|
|
430
|
-
|
|
431
|
-
// Helper to add todo
|
|
432
|
-
const addTodo = () => {
|
|
433
|
-
if (!appState.input.trim()) return;
|
|
434
|
-
appState.todos.push({
|
|
435
|
-
text: appState.input,
|
|
436
|
-
done: false
|
|
437
|
-
});
|
|
438
|
-
appState.input = '';
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
// Helper to toggle todo
|
|
442
|
-
cDOM.helper('toggleTodo', (index) => {
|
|
443
|
-
appState.todos[index].done = !appState.todos[index].done;
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
// Helper to remove todo
|
|
447
|
-
cDOM.helper('removeTodo', (index) => {
|
|
448
|
-
appState.todos.splice(index, 1);
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
cDOM({
|
|
452
|
-
div: {
|
|
453
|
-
class: "todo-app",
|
|
454
|
-
children: [
|
|
455
|
-
{ h1: "Todo List" },
|
|
456
|
-
{ div: {
|
|
457
|
-
children: [
|
|
458
|
-
{ input: {
|
|
459
|
-
type: "text",
|
|
460
|
-
placeholder: "New todo...",
|
|
461
|
-
value: _('/app/input'),
|
|
462
|
-
oninput: "_(set(/app/input, $event.target.value))"
|
|
463
|
-
}},
|
|
464
|
-
{ button: {
|
|
465
|
-
onclick() { addTodo() },
|
|
466
|
-
children: ["Add"]
|
|
467
|
-
}}
|
|
468
|
-
]
|
|
469
|
-
}},
|
|
470
|
-
{ ul: {
|
|
471
|
-
id: "todo-list",
|
|
472
|
-
children: _('/app/todos') // Reactive list rendering
|
|
473
|
-
}}
|
|
474
|
-
]
|
|
475
|
-
}
|
|
476
|
-
}, {});
|
|
477
|
-
</script>
|
|
478
|
-
</body>
|
|
479
|
-
</html>
|
|
327
|
+
{ button: {
|
|
328
|
+
onclick: { "=myIncrement": ["/counter/count", 5] },
|
|
329
|
+
children: ["+5"]
|
|
330
|
+
}}
|
|
480
331
|
```
|
|
481
332
|
|
|
482
333
|
## API Reference
|
|
483
334
|
|
|
484
335
|
### `cDOM(object, options?)`
|
|
485
336
|
|
|
486
|
-
Converts cDOM object to DOM
|
|
487
|
-
|
|
488
|
-
**Parameters:**
|
|
489
|
-
- `object`: cDOM object or JSON string
|
|
490
|
-
- `options`: Optional configuration
|
|
491
|
-
- `target`: Element or CSS selector string (default: `document.currentScript`). If a selector matches multiple elements, the component will be instantiated for each.
|
|
492
|
-
- `location`: Where to insert - `'innerHTML'`, `'outerHTML'`, `'beforeend'`, etc.
|
|
493
|
-
- `unsafe`: Allow unsafe eval (default: `false`)
|
|
494
|
-
|
|
495
|
-
**Returns:** The (first) created DOM element
|
|
496
|
-
|
|
497
|
-
If an options object is provided, the default location will be outerHTML on the current script, i.e. replace the script. If you do not want to replace anything and just want the reactive element, DO NOT pass in an options object.
|
|
498
|
-
|
|
499
|
-
```javascript
|
|
500
|
-
// Target a specific element by ID
|
|
501
|
-
cDOM({ h1: "Hello" }, { target: "#header" });
|
|
502
|
-
|
|
503
|
-
// Target all elements with a class
|
|
504
|
-
cDOM({ button: "Click Me" }, { target: ".action-buttons", location: "beforeend" });
|
|
505
|
-
```
|
|
506
|
-
|
|
507
|
-
### `cDOM.state(value, options)` / `cDOM.signal(value, options)`
|
|
508
|
-
|
|
509
|
-
Create reactive state or signals.
|
|
337
|
+
Converts cDOM object to DOM.
|
|
510
338
|
|
|
511
339
|
**Options:**
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
- `schema`: String (named schema) or Object (inline schema).
|
|
516
|
-
- `transform`: String (helper name) or Function.
|
|
517
|
-
|
|
518
|
-
### `cDOM.helper(name, function)`
|
|
519
|
-
|
|
520
|
-
Register a helper function for use in expressions or transformations.
|
|
521
|
-
|
|
522
|
-
### `cDOM.schema(name, definition)`
|
|
523
|
-
|
|
524
|
-
Register a JSON Schema for state validation.
|
|
525
|
-
|
|
526
|
-
### `_(expression, contextNode?)`
|
|
527
|
-
|
|
528
|
-
Evaluate state expression.
|
|
340
|
+
* `target`: Element or CSS selector.
|
|
341
|
+
* `location`: Insertion position (`innerHTML`, `beforeend`, etc.).
|
|
342
|
+
* `unsafe`: Allow unsafe eval (default: `false`).
|
|
529
343
|
|
|
530
|
-
|
|
531
|
-
- `expression`: State path or expression
|
|
532
|
-
- `contextNode`: Optional context element (omit for lazy binding)
|
|
344
|
+
### `cDOM.operator(symbol, helperName)`
|
|
533
345
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
Evaluate DOM query (XPath or CSS).
|
|
537
|
-
|
|
538
|
-
**Parameters:**
|
|
539
|
-
- `expression`: XPath or CSS selector
|
|
540
|
-
- `contextNode`: Optional context element (omit for lazy binding)
|
|
541
|
-
|
|
542
|
-
## Expression Syntax
|
|
543
|
-
|
|
544
|
-
### State Expressions `_()`
|
|
346
|
+
Map a custom symbol to a helper function.
|
|
545
347
|
|
|
546
348
|
```javascript
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
"_(/price * 1.1)" // With literal
|
|
550
|
-
"_(helper(/arg1, /arg2))" // Helper function
|
|
551
|
-
```
|
|
552
|
-
|
|
553
|
-
### Supported Expressions
|
|
349
|
+
// Map '^' to 'pow' helper
|
|
350
|
+
cDOM.operator('^', 'pow');
|
|
554
351
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
**Supported:**
|
|
558
|
-
* **Math:** `+`, `-`, `*`, `/`, `%`
|
|
559
|
-
* **comparison:** `==`, `!=`, `<`, `>`, `<=`, `>=`
|
|
560
|
-
* **Logic:** `&&`, `||`, `!`
|
|
561
|
-
* **Ternary:** `condition ? trueVal : falseVal`
|
|
562
|
-
* **Grouping:** `( )`
|
|
563
|
-
* **Data Types:** Strings (`'hello'`, `"world"`), Numbers (`123`, `4.5`), Booleans (`true`, `false`), `null`
|
|
564
|
-
* **Objects & Arrays:** `{ key: val }`, `[1, 2, 3]`
|
|
565
|
-
* **Member Access:** `obj.prop`, `obj['key']`, `arr[0]`
|
|
566
|
-
* **Functions:** Calls to registered helpers (e.g., `sum(1, 2)`)
|
|
567
|
-
|
|
568
|
-
**Unsupported (Use Helpers instead):**
|
|
569
|
-
* **Assignment/Mutation:** `=`, `+=`, `-=`, `++`, `--` (Expressions should be side-effect free)
|
|
570
|
-
* *Workaround:* Use `onclick` handlers calling helpers to mutate state.
|
|
571
|
-
* **Bitwise Operators:** `&`, `|`, `^`, `~`, `<<`, `>>`
|
|
572
|
-
* **`Math` Object:** Direct access to `Math.max`, `Math.random` etc. is not exposed globally.
|
|
573
|
-
* *Workaround:* Register needed Math functions as helpers: `cDOM.helper('max', Math.max)`.
|
|
574
|
-
* **New Object Creation:** `new Date()`, `new RegExp()`
|
|
575
|
-
* *Workaround:* Create helpers like `now()` or `timestamp()`.
|
|
576
|
-
* **Arrow Functions / Lambdas:** `x => x * 2`
|
|
577
|
-
* *Workaround:* Define complex logic in a helper.
|
|
578
|
-
* **Try/Catch/Throw**: No control flow statements.
|
|
579
|
-
|
|
580
|
-
### DOM Queries `$()`
|
|
581
|
-
|
|
582
|
-
```javascript
|
|
583
|
-
// XPath
|
|
584
|
-
"$(//button)" // All buttons
|
|
585
|
-
"$(count(//div))" // Count divs
|
|
586
|
-
"$(../../@id)" // Parent's parent id
|
|
587
|
-
|
|
588
|
-
// CSS
|
|
589
|
-
"$(.active)" // By class
|
|
590
|
-
"$(#main)" // By id
|
|
352
|
+
// Use in HTML structure
|
|
353
|
+
{ "^": [2, 3] } // Returns 8
|
|
591
354
|
```
|
|
592
355
|
|
|
593
356
|
## Browser Support
|
|
@@ -596,37 +359,6 @@ The built-in expression engine supports a safe subset of JavaScript. This ensure
|
|
|
596
359
|
- Requires `MutationObserver`, `Proxy`, and `XPath` APIs
|
|
597
360
|
- IE11 not supported
|
|
598
361
|
|
|
599
|
-
## Performance
|
|
600
|
-
|
|
601
|
-
- Reactive updates are batched using `queueMicrotask`
|
|
602
|
-
- Expressions are cached after first parse
|
|
603
|
-
- DOM queries are evaluated on-demand
|
|
604
|
-
- Minimal overhead for static content
|
|
605
|
-
|
|
606
|
-
## Limitations
|
|
607
|
-
|
|
608
|
-
- No virtual DOM diffing (direct DOM manipulation)
|
|
609
|
-
- No component lifecycle beyond `oncreate`/`onmount`
|
|
610
|
-
- No built-in routing
|
|
611
|
-
- No server-side rendering
|
|
612
|
-
|
|
613
|
-
## cDOM is great for simple reactive UIs, but consider [Lightview](https://github.com/anywhichway/lightview) if you need:
|
|
614
|
-
|
|
615
|
-
- **Advanced routing** with middleware chains
|
|
616
|
-
- **Component system** with 40+ pre-built components
|
|
617
|
-
- **Template literals** in attributes (cDOM uses `${}` style in Lightview-X mode)
|
|
618
|
-
- **Deep integration** with CSS-in-JS and shadow DOM defaults
|
|
619
|
-
|
|
620
|
-
cDOM was extracted from Lightview to provide a lightweight alternative for developers who want reactivity without the full framework.
|
|
621
|
-
|
|
622
362
|
## License
|
|
623
363
|
|
|
624
364
|
MIT
|
|
625
|
-
|
|
626
|
-
## Contributing
|
|
627
|
-
|
|
628
|
-
Issues and pull requests welcome at the [Lightview repository](https://github.com/anywhichway/lightview).
|
|
629
|
-
|
|
630
|
-
---
|
|
631
|
-
|
|
632
|
-
**Made with ❤️ by the Lightview team**
|