native-document 1.0.166 → 1.0.168
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/.vitepress/config.js +166 -0
- package/CHANGELOG.md +153 -0
- package/components.js +2 -1
- package/dist/native-document.components.min.js +495 -228
- package/dist/native-document.dev.js +7 -0
- package/dist/native-document.dev.js.map +1 -1
- package/dist/native-document.min.js +1 -1
- package/docs/advanced-components.md +213 -608
- package/docs/anchor.md +173 -312
- package/docs/cache.md +95 -803
- package/docs/cli.md +179 -0
- package/docs/components/accordion.md +172 -0
- package/docs/components/alert.md +99 -0
- package/docs/components/avatar.md +160 -0
- package/docs/components/badge.md +102 -0
- package/docs/components/breadcrumb.md +89 -0
- package/docs/components/button.md +183 -0
- package/docs/components/card.md +69 -0
- package/docs/components/context-menu.md +118 -0
- package/docs/components/data-table.md +345 -0
- package/docs/components/dropdown.md +214 -0
- package/docs/components/form/autocomplete-field.md +81 -0
- package/docs/components/form/checkbox-field.md +41 -0
- package/docs/components/form/checkbox-group-field.md +54 -0
- package/docs/components/form/color-field.md +64 -0
- package/docs/components/form/date-field.md +92 -0
- package/docs/components/form/field-collection.md +63 -0
- package/docs/components/form/file-field.md +203 -0
- package/docs/components/form/form-control.md +87 -0
- package/docs/components/form/image-field.md +90 -0
- package/docs/components/form/index.md +115 -0
- package/docs/components/form/number-field.md +65 -0
- package/docs/components/form/radio-field.md +51 -0
- package/docs/components/form/select-field.md +123 -0
- package/docs/components/form/slider.md +136 -0
- package/docs/components/form/string-field.md +134 -0
- package/docs/components/form/textarea-field.md +65 -0
- package/docs/components/form-fields.md +372 -0
- package/docs/components/getting-started.md +264 -0
- package/docs/components/index.md +337 -0
- package/docs/components/layout.md +279 -0
- package/docs/components/list.md +73 -0
- package/docs/components/menu.md +215 -0
- package/docs/components/modal.md +156 -0
- package/docs/components/pagination.md +95 -0
- package/docs/components/popover.md +131 -0
- package/docs/components/progress.md +111 -0
- package/docs/components/shortcut-manager.md +221 -0
- package/docs/components/simple-table.md +107 -0
- package/docs/components/skeleton.md +155 -0
- package/docs/components/spinner.md +100 -0
- package/docs/components/splitter.md +133 -0
- package/docs/components/stepper.md +163 -0
- package/docs/components/switch.md +113 -0
- package/docs/components/tabs.md +153 -0
- package/docs/components/toast.md +119 -0
- package/docs/components/tooltip.md +151 -0
- package/docs/components/traits.md +261 -0
- package/docs/conditional-rendering.md +170 -588
- package/docs/contributing.md +300 -25
- package/docs/core-concepts.md +205 -374
- package/docs/elements.md +251 -367
- package/docs/extending-native-document-element.md +192 -207
- package/docs/filters.md +153 -1122
- package/docs/getting-started.md +193 -267
- package/docs/i18n.md +241 -0
- package/docs/index.md +76 -0
- package/docs/lifecycle-events.md +143 -75
- package/docs/list-rendering.md +227 -852
- package/docs/memory-management.md +134 -47
- package/docs/native-document-element.md +337 -186
- package/docs/native-fetch.md +99 -630
- package/docs/observable-resource.md +364 -0
- package/docs/observables.md +592 -526
- package/docs/routing.md +244 -653
- package/docs/state-management.md +134 -241
- package/docs/svg-elements.md +231 -0
- package/docs/theming.md +409 -0
- package/docs/tutorials/.gitkeep +0 -0
- package/docs/validation.md +95 -97
- package/docs/vitepress-conventions.md +219 -0
- package/package.json +34 -13
- package/readme.md +269 -89
- package/src/components/card/Card.js +93 -39
- package/src/components/card/index.js +1 -1
- package/src/components/list/HasListItem.js +171 -0
- package/src/components/list/List.js +41 -107
- package/src/components/list/ListDivider.js +39 -0
- package/src/components/list/ListGroup.js +76 -59
- package/src/components/list/ListItem.js +117 -69
- package/src/components/list/index.js +3 -1
- package/src/components/list/types/ListItem.d.ts +45 -34
- package/src/components/spacer/Spacer.js +1 -1
- package/src/core/data/ObservableResource.js +5 -0
- package/src/core/data/observable-helpers/observable.prototypes.js +2 -0
- package/src/ui/components/card/CardRender.js +133 -0
- package/src/ui/components/card/card.css +169 -0
- package/src/ui/components/contextmenu/ContextmenuRender.js +1 -1
- package/src/ui/components/list/ListRender.js +18 -0
- package/src/ui/components/list/divider/ListDividerRender.js +10 -0
- package/src/ui/components/list/divider/list-divider.css +12 -0
- package/src/ui/components/list/group/ListGroupRender.js +61 -0
- package/src/ui/components/list/group/list-group.css +62 -0
- package/src/ui/components/list/item/ListItemRender.js +238 -0
- package/src/ui/components/list/item/list-item.css +191 -0
- package/src/ui/components/list/list.css +24 -0
- package/src/ui/components/spacer/SpacerRender.js +10 -0
- package/src/ui/index.js +8 -0
|
@@ -1,35 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Advanced Components
|
|
3
|
+
description: Optimize rendering performance with useCache and useSingleton - template cloning and data binding for high-performance lists and layouts
|
|
4
|
+
---
|
|
5
|
+
|
|
1
6
|
# Advanced Components
|
|
2
7
|
|
|
3
|
-
NativeDocument provides advanced component patterns for optimizing rendering performance through template cloning and data binding. The `useCache()` utility creates reusable component templates with a
|
|
8
|
+
NativeDocument provides advanced component patterns for optimizing rendering performance through template cloning and data binding. The `useCache()` utility creates reusable component templates with a binding system that efficiently updates only the dynamic parts.
|
|
4
9
|
|
|
5
10
|
## Overview
|
|
6
11
|
|
|
7
|
-
Advanced component utilities include:
|
|
8
12
|
- **`useCache(fn)`** - Create cached components with data binding
|
|
9
|
-
- **`useSingleton(fn)`** - Create singleton views with
|
|
10
|
-
- **Binder API** -
|
|
13
|
+
- **`useSingleton(fn)`** - Create singleton views with updatable sections
|
|
14
|
+
- **Binder API** - Data binding system for templates
|
|
11
15
|
|
|
12
16
|
## Import
|
|
17
|
+
|
|
13
18
|
```javascript
|
|
14
19
|
import { useCache, useSingleton } from 'native-document';
|
|
20
|
+
import { ForEachArray } from 'native-document/elements';
|
|
15
21
|
```
|
|
16
22
|
|
|
17
|
-
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## `useCache()` - Cached Components with Binding
|
|
18
26
|
|
|
19
27
|
`useCache()` creates a component template that is built once and then cloned efficiently. The component function receives a **binder** object (`$binder`) that creates bindings for dynamic data.
|
|
20
28
|
|
|
21
29
|
### Basic Concept
|
|
30
|
+
|
|
22
31
|
```javascript
|
|
23
32
|
import { useCache } from 'native-document';
|
|
24
|
-
import { Div, Span } from 'native-document/
|
|
33
|
+
import { Div, Span } from 'native-document/elements';
|
|
25
34
|
|
|
26
|
-
// Component function receives $binder
|
|
27
35
|
const UserCard = useCache(($binder) => {
|
|
28
|
-
// Create bindings for dynamic data
|
|
29
36
|
const name = $binder.value('name');
|
|
30
|
-
const age
|
|
37
|
+
const age = $binder.value('age');
|
|
31
38
|
|
|
32
|
-
// Build template with bindings
|
|
33
39
|
return Div({ class: 'user-card' }, [
|
|
34
40
|
Span('Name: '),
|
|
35
41
|
Span(name),
|
|
@@ -38,777 +44,376 @@ const UserCard = useCache(($binder) => {
|
|
|
38
44
|
]);
|
|
39
45
|
});
|
|
40
46
|
|
|
41
|
-
// Usage - pass data object
|
|
42
47
|
const card1 = UserCard({ name: 'Alice', age: 25 });
|
|
43
|
-
const card2 = UserCard({ name: 'Bob',
|
|
44
|
-
// card2 is cloned from template
|
|
48
|
+
const card2 = UserCard({ name: 'Bob', age: 30 });
|
|
49
|
+
// card2 is cloned from the template - bindings update automatically
|
|
45
50
|
```
|
|
46
51
|
|
|
47
|
-
|
|
52
|
+
---
|
|
48
53
|
|
|
49
|
-
Binder
|
|
50
|
-
```javascript
|
|
51
|
-
import { useCache } from 'native-document';
|
|
52
|
-
import { Div } from 'native-document/src/elements';
|
|
54
|
+
## Binder Methods
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
// Function receives arguments exactly as passed
|
|
56
|
-
const value = $binder.value((data) => {
|
|
57
|
-
// data = { name: 'Alice', age: 25 }
|
|
58
|
-
return data.name;
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
return Div(value);
|
|
62
|
-
});
|
|
56
|
+
### `$binder.name` - Property shorthand (Proxy)
|
|
63
57
|
|
|
64
|
-
|
|
65
|
-
|
|
58
|
+
`$binder` is a Proxy. Any property access that is not a known method is automatically treated as `$binder.value('propertyName')`:
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
const name = $binder.name; // same as $binder.value('name')
|
|
62
|
+
const price = $binder.price; // same as $binder.value('price')
|
|
63
|
+
const stock = $binder.stock; // same as $binder.value('stock')
|
|
66
64
|
```
|
|
67
65
|
|
|
68
|
-
|
|
66
|
+
> The shorthand only works for property names. For transform functions you still need `$binder.value(fn)`.
|
|
69
67
|
|
|
70
|
-
###
|
|
68
|
+
### `$binder.value(key | fn)` / `$binder.property(key | fn)` / `$binder.text(key | fn)`
|
|
71
69
|
|
|
72
|
-
Bind to a property name or use a transform function
|
|
73
|
-
```javascript
|
|
74
|
-
import { useCache } from 'native-document';
|
|
75
|
-
import { Div, Span } from 'native-document/src/elements';
|
|
70
|
+
Bind to a property by name, or use a transform function. `property()` and `text()` are aliases for `value()`:
|
|
76
71
|
|
|
72
|
+
```javascript
|
|
77
73
|
const ProductCard = useCache(($binder) => {
|
|
78
|
-
//
|
|
79
|
-
const name
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const status = $binder.value((product) => {
|
|
89
|
-
return product.stock > 0 ? 'In Stock' : 'Out of Stock';
|
|
90
|
-
});
|
|
91
|
-
|
|
74
|
+
// Shorthand via Proxy
|
|
75
|
+
const name = $binder.name;
|
|
76
|
+
const stock = $binder.stock;
|
|
77
|
+
|
|
78
|
+
// Transform function - receives the full data object
|
|
79
|
+
const formattedPrice = $binder.value(product => `$${product.price.toFixed(2)}`);
|
|
80
|
+
const status = $binder.value(product => product.stock > 0 ? 'In Stock' : 'Out of Stock');
|
|
81
|
+
|
|
92
82
|
return Div({ class: 'product-card' }, [
|
|
93
|
-
Span({ class: 'name' },
|
|
94
|
-
Span({ class: 'price' },
|
|
83
|
+
Span({ class: 'name' }, name),
|
|
84
|
+
Span({ class: 'price' }, formattedPrice),
|
|
95
85
|
Span({ class: 'status' }, status)
|
|
96
86
|
]);
|
|
97
87
|
});
|
|
98
88
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
// Displays: Phone, $599.00, In Stock
|
|
89
|
+
ProductCard({ name: 'Phone', price: 599, stock: 10 });
|
|
90
|
+
// -> Phone, $599.00, In Stock
|
|
102
91
|
```
|
|
103
92
|
|
|
104
|
-
###
|
|
105
|
-
```javascript
|
|
106
|
-
import { useCache } from 'native-document';
|
|
107
|
-
|
|
108
|
-
const Component = useCache(($binder) => {
|
|
109
|
-
// property() is an alias for value()
|
|
110
|
-
const name = $binder.property('name');
|
|
111
|
-
const title = $binder.value('title');
|
|
112
|
-
|
|
113
|
-
// Both work the same way
|
|
114
|
-
return Div([Span(name), Span(title)]);
|
|
115
|
-
});
|
|
116
|
-
```
|
|
93
|
+
### `$binder.class(fn)`
|
|
117
94
|
|
|
118
|
-
|
|
95
|
+
Bind CSS classes dynamically. The function returns a boolean:
|
|
119
96
|
|
|
120
|
-
Bind CSS classes dynamically based on data:
|
|
121
97
|
```javascript
|
|
122
|
-
import { useCache } from 'native-document';
|
|
123
|
-
import { Div, Span } from 'native-document/src/elements';
|
|
124
|
-
|
|
125
98
|
const TaskItem = useCache(($binder) => {
|
|
126
|
-
const text
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
return Div({
|
|
138
|
-
class: {
|
|
139
|
-
'task': true,
|
|
140
|
-
'completed': completedClass,
|
|
141
|
-
'high-priority': priorityClass
|
|
142
|
-
}
|
|
143
|
-
}, [
|
|
144
|
-
Span(text)
|
|
145
|
-
]);
|
|
99
|
+
const text = $binder.value('text');
|
|
100
|
+
const completedClass = $binder.class(task => task.completed);
|
|
101
|
+
const priorityClass = $binder.class(task => task.priority === 'high');
|
|
102
|
+
|
|
103
|
+
return Div({
|
|
104
|
+
class: {
|
|
105
|
+
'task': true,
|
|
106
|
+
'completed': completedClass,
|
|
107
|
+
'high-priority': priorityClass
|
|
108
|
+
}
|
|
109
|
+
}, [Span(text)]);
|
|
146
110
|
});
|
|
147
111
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// <div class="task completed high-priority">...</div>
|
|
112
|
+
TaskItem({ text: 'Fix bug', completed: true, priority: 'high' });
|
|
113
|
+
// -> <div class="task completed high-priority">...</div>
|
|
151
114
|
```
|
|
152
115
|
|
|
153
|
-
|
|
116
|
+
Combine with `Observable.when()` for reactive class binding:
|
|
117
|
+
|
|
154
118
|
```javascript
|
|
155
|
-
import { useCache } from 'native-document';
|
|
156
|
-
import { Tr, Td } from 'native-document/src/elements';
|
|
157
119
|
import { Observable } from 'native-document';
|
|
120
|
+
import { Tr, Td } from 'native-document/elements';
|
|
158
121
|
|
|
159
122
|
const selectedId = Observable(null);
|
|
160
123
|
|
|
161
124
|
const TableRow = useCache(($binder) => {
|
|
162
|
-
const id
|
|
125
|
+
const id = $binder.value('id');
|
|
163
126
|
const name = $binder.value('name');
|
|
164
|
-
|
|
165
|
-
//
|
|
166
|
-
const isSelected = $binder.class(
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return Tr({
|
|
171
|
-
class: { 'selected': isSelected }
|
|
172
|
-
}, [
|
|
127
|
+
|
|
128
|
+
// Updates automatically when selectedId changes
|
|
129
|
+
const isSelected = $binder.class(item => selectedId.when(item.id));
|
|
130
|
+
|
|
131
|
+
return Tr({ class: { 'selected': isSelected } }, [
|
|
173
132
|
Td(id),
|
|
174
133
|
Td(name)
|
|
175
134
|
]);
|
|
176
135
|
});
|
|
177
136
|
|
|
178
|
-
//
|
|
179
|
-
|
|
180
|
-
const row2 = TableRow({ id: 2, name: 'Item 2' });
|
|
181
|
-
|
|
182
|
-
// Change selection - classes update automatically
|
|
183
|
-
selectedId.set(1); // row1 gets 'selected' class
|
|
184
|
-
selectedId.set(2); // row2 gets 'selected' class, row1 loses it
|
|
137
|
+
selectedId.set(1); // row with id 1 gets 'selected' class
|
|
138
|
+
selectedId.set(2); // row 2 selected, row 1 loses the class
|
|
185
139
|
```
|
|
186
140
|
|
|
187
|
-
###
|
|
141
|
+
### `$binder.style(fn)`
|
|
188
142
|
|
|
189
|
-
Bind inline
|
|
190
|
-
```javascript
|
|
191
|
-
import { useCache } from 'native-document';
|
|
192
|
-
import { Div } from 'native-document/src/elements';
|
|
143
|
+
Bind inline style values:
|
|
193
144
|
|
|
145
|
+
```javascript
|
|
194
146
|
const ProgressBar = useCache(($binder) => {
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const widthStyle = $binder.style((progress) => {
|
|
199
|
-
return progress.percentage + '%';
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
const colorStyle = $binder.style((progress) => {
|
|
203
|
-
return progress.percentage >= 100 ? 'green' : 'blue';
|
|
204
|
-
});
|
|
205
|
-
|
|
147
|
+
const widthStyle = $binder.style(p => p.percentage + '%');
|
|
148
|
+
const colorStyle = $binder.style(p => p.percentage >= 100 ? 'green' : 'blue');
|
|
149
|
+
|
|
206
150
|
return Div({ class: 'progress-bar' }, [
|
|
207
|
-
Div({
|
|
151
|
+
Div({
|
|
208
152
|
class: 'progress-fill',
|
|
209
153
|
style: {
|
|
210
|
-
width:
|
|
154
|
+
width: widthStyle,
|
|
211
155
|
backgroundColor: colorStyle
|
|
212
156
|
}
|
|
213
157
|
})
|
|
214
158
|
]);
|
|
215
159
|
});
|
|
216
160
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// <div style="width: 75%; background-color: blue"></div>
|
|
161
|
+
ProgressBar({ percentage: 75 });
|
|
162
|
+
// -> <div style="width: 75%; background-color: blue"></div>
|
|
220
163
|
```
|
|
221
164
|
|
|
222
|
-
###
|
|
165
|
+
### `$binder.attr(fn)`
|
|
166
|
+
|
|
167
|
+
Bind element attributes. Takes only a function:
|
|
223
168
|
|
|
224
|
-
Bind element attributes dynamically. Takes only a function that returns the attribute value:
|
|
225
169
|
```javascript
|
|
226
|
-
import {
|
|
227
|
-
import { Div, Img, A } from 'native-document/src/elements';
|
|
170
|
+
import { Img, Link } from 'native-document/elements';
|
|
228
171
|
|
|
229
172
|
const ProductCard = useCache(($binder) => {
|
|
230
|
-
const name
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
const imageSrc = $binder.attr((product) => {
|
|
235
|
-
return product.imageUrl;
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
const detailsHref = $binder.attr((product) => {
|
|
239
|
-
return `/products/${product.id}`;
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
// Use as object property with attribute name
|
|
173
|
+
const name = $binder.value('name');
|
|
174
|
+
const imageSrc = $binder.attr(product => product.imageUrl);
|
|
175
|
+
const detailHref = $binder.attr(product => `/products/${product.id}`);
|
|
176
|
+
|
|
243
177
|
return Div({ class: 'product' }, [
|
|
244
178
|
Img({ src: imageSrc }),
|
|
245
|
-
|
|
179
|
+
Link({ href: detailHref }, name)
|
|
246
180
|
]);
|
|
247
181
|
});
|
|
248
182
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
name: 'Phone',
|
|
253
|
-
imageUrl: '/images/phone.jpg'
|
|
254
|
-
});
|
|
255
|
-
// <img src="/images/phone.jpg">
|
|
256
|
-
// <a href="/products/123">Phone</a>
|
|
183
|
+
ProductCard({ id: 123, name: 'Phone', imageUrl: '/images/phone.jpg' });
|
|
184
|
+
// -> <img src="/images/phone.jpg">
|
|
185
|
+
// -> <a href="/products/123">Phone</a>
|
|
257
186
|
```
|
|
258
187
|
|
|
259
|
-
###
|
|
188
|
+
### `$binder.attach(fn)` + `.nd.attach()`
|
|
189
|
+
|
|
190
|
+
Bind event handlers. The handler receives `(...userArguments, event)` - user data comes first, the DOM event is last. Use `.nd.attach()` on the element to connect it:
|
|
260
191
|
|
|
261
|
-
Attach event handlers that receive the event followed by user arguments:
|
|
262
192
|
```javascript
|
|
263
|
-
import {
|
|
264
|
-
import { Div, Button, Span } from 'native-document/src/elements';
|
|
193
|
+
import { Div, Button, Span } from 'native-document/elements';
|
|
265
194
|
|
|
266
195
|
const TodoItem = useCache(($binder) => {
|
|
267
196
|
const text = $binder.value('text');
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const handleToggle = $binder.attach((event, todo) => {
|
|
271
|
-
console.log('Toggle clicked:', event);
|
|
272
|
-
console.log('Todo data:', todo);
|
|
197
|
+
|
|
198
|
+
const handleToggle = $binder.attach((todo, event) => {
|
|
273
199
|
updateTodo(todo.id, { completed: !todo.completed });
|
|
274
200
|
});
|
|
275
|
-
|
|
276
|
-
const handleDelete = $binder.attach((
|
|
277
|
-
console.log('Delete clicked:', event);
|
|
201
|
+
|
|
202
|
+
const handleDelete = $binder.attach((todo, event) => {
|
|
278
203
|
deleteTodo(todo.id);
|
|
279
204
|
});
|
|
280
|
-
|
|
205
|
+
|
|
281
206
|
return Div({ class: 'todo' }, [
|
|
282
|
-
Span(text)
|
|
283
|
-
|
|
207
|
+
Span(text)
|
|
208
|
+
.nd.attach('onClick', handleToggle),
|
|
209
|
+
Button('Delete')
|
|
210
|
+
.nd.attach('onClick', handleDelete)
|
|
284
211
|
]);
|
|
285
212
|
});
|
|
286
213
|
|
|
287
|
-
|
|
288
|
-
const todo = TodoItem({ id: 1, text: 'Buy milk', completed: false });
|
|
289
|
-
// Clicking triggers handlers with the todo object
|
|
214
|
+
TodoItem({ id: 1, text: 'Buy milk', completed: false });
|
|
290
215
|
```
|
|
291
216
|
|
|
292
|
-
###
|
|
217
|
+
### Multiple Arguments
|
|
218
|
+
|
|
219
|
+
When calling a cached component with multiple arguments, binder functions receive all of them:
|
|
293
220
|
|
|
294
|
-
The `.nd.attach(eventName, handler)` method connects binder handlers to elements:
|
|
295
221
|
```javascript
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
console.log('Action:', data.action);
|
|
305
|
-
console.log('Event:', event);
|
|
306
|
-
performAction(data.action);
|
|
222
|
+
const ArticleCard = useCache(($binder) => {
|
|
223
|
+
const title = $binder.value((article, options) => {
|
|
224
|
+
return options.uppercase ? article.title.toUpperCase() : article.title;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const excerpt = $binder.value((article, options) => {
|
|
228
|
+
const length = options.excerptLength || 100;
|
|
229
|
+
return article.content.substring(0, length) + '...';
|
|
307
230
|
});
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
return Button(label).nd.attach('onClick', clickHandler);
|
|
231
|
+
|
|
232
|
+
return Div({ class: 'article' }, [H3(title), P(excerpt)]);
|
|
311
233
|
});
|
|
312
234
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
235
|
+
ArticleCard(
|
|
236
|
+
{ title: 'My Article', content: 'Long content...' },
|
|
237
|
+
{ uppercase: true, excerptLength: 150 }
|
|
238
|
+
);
|
|
316
239
|
```
|
|
317
240
|
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Binder API Reference
|
|
244
|
+
|
|
245
|
+
| Method | Parameters | Description |
|
|
246
|
+
|---|---|---|
|
|
247
|
+
| `$binder.name` | property name | Proxy shorthand for `$binder.value('name')` |
|
|
248
|
+
| `$binder.value(key)` | `key: string` | Bind to property by name |
|
|
249
|
+
| `$binder.value(fn)` | `fn: (...args) => any` | Bind with transform function |
|
|
250
|
+
| `$binder.property(key \| fn)` | same as `value` | Alias for `value()` |
|
|
251
|
+
| `$binder.text(key \| fn)` | same as `value` | Alias for `value()` |
|
|
252
|
+
| `$binder.class(fn)` | `fn: (...args) => boolean` | Bind CSS class toggle |
|
|
253
|
+
| `$binder.style(fn)` | `fn: (...args) => string` | Bind inline style value |
|
|
254
|
+
| `$binder.attr(fn)` | `fn: (...args) => string` | Bind attribute value |
|
|
255
|
+
| `$binder.attach(fn)` | `fn: (event, ...args) => void` | Bind event handler |
|
|
256
|
+
| `$binder.callback(fn)` | same as `attach` | Alias for `attach()` |
|
|
257
|
+
|
|
258
|
+
> All binder methods receive arguments exactly as passed by the caller. `attach` pre-binds user arguments first - the handler signature is `(...userArguments, event)`, so data comes before the DOM event.
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
318
262
|
## Complete Example
|
|
319
263
|
|
|
320
|
-
|
|
264
|
+
A cached table row using `ForEachArray`:
|
|
265
|
+
|
|
321
266
|
```javascript
|
|
322
267
|
import { useCache } from 'native-document';
|
|
323
|
-
import { Tr, Td, Link, Button } from 'native-document/
|
|
324
|
-
import { Observable
|
|
268
|
+
import { ForEachArray, Tr, Td, Link, Button, TBody } from 'native-document/elements';
|
|
269
|
+
import { Observable } from 'native-document';
|
|
325
270
|
|
|
326
|
-
// App state
|
|
327
271
|
const AppService = {
|
|
328
|
-
data:
|
|
272
|
+
data: Observable.array([
|
|
329
273
|
{ id: 1, label: 'Item 1' },
|
|
330
274
|
{ id: 2, label: 'Item 2' },
|
|
331
275
|
{ id: 3, label: 'Item 3' }
|
|
332
276
|
]),
|
|
333
277
|
selected: Observable(null),
|
|
334
|
-
|
|
335
|
-
select(id)
|
|
336
|
-
|
|
337
|
-
},
|
|
338
|
-
|
|
339
|
-
remove(id) {
|
|
278
|
+
|
|
279
|
+
select(id) { this.selected.set(id); },
|
|
280
|
+
remove(id) {
|
|
340
281
|
const index = this.data.val().findIndex(item => item.id === id);
|
|
341
|
-
if (index > -1)
|
|
342
|
-
this.data.remove(index);
|
|
343
|
-
}
|
|
282
|
+
if (index > -1) this.data.remove(index);
|
|
344
283
|
}
|
|
345
284
|
};
|
|
346
285
|
|
|
347
|
-
// Cached table row with bindings
|
|
348
286
|
const TableRowBuilder = useCache(($binder) => {
|
|
349
|
-
|
|
350
|
-
const
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
const id = $binder.value('id');
|
|
356
|
-
const label = $binder.value('label');
|
|
357
|
-
|
|
358
|
-
// Event handler bindings - receive (event, item)
|
|
359
|
-
const rowClick = $binder.attach((event, item) => {
|
|
360
|
-
AppService.select(item.id);
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
const removeClick = $binder.attach((event, item) => {
|
|
364
|
-
AppService.remove(item.id);
|
|
365
|
-
});
|
|
366
|
-
|
|
287
|
+
const isSelected = $binder.class(item => AppService.selected.when(item.id));
|
|
288
|
+
const id = $binder.id;
|
|
289
|
+
const label = $binder.label;
|
|
290
|
+
const rowClick = $binder.attach((item, event) => AppService.select(item.id));
|
|
291
|
+
const removeClick = $binder.attach((item, event) => AppService.remove(item.id));
|
|
292
|
+
|
|
367
293
|
return Tr({ class: { 'selected': isSelected } }, [
|
|
368
294
|
Td({ class: 'col-md-1' }, id),
|
|
369
|
-
Td({ class: 'col-md-4' },
|
|
295
|
+
Td({ class: 'col-md-4' },
|
|
370
296
|
Link(label).nd.attach('onClick', rowClick)
|
|
371
297
|
),
|
|
372
298
|
Td({ class: 'col-md-1' },
|
|
373
|
-
Button('
|
|
299
|
+
Button('x').nd.attach('onClick', removeClick)
|
|
374
300
|
),
|
|
375
301
|
Td({ class: 'col-md-6' })
|
|
376
302
|
]);
|
|
377
303
|
});
|
|
378
304
|
|
|
379
|
-
|
|
380
|
-
const TableBody = TBody(
|
|
381
|
-
ForEachArray(AppService.data, TableRowBuilder)
|
|
382
|
-
);
|
|
305
|
+
const TableBody = TBody(ForEachArray(AppService.data, TableRowBuilder));
|
|
383
306
|
```
|
|
384
307
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
### Multiple Bindings
|
|
388
|
-
```javascript
|
|
389
|
-
import { useCache } from 'native-document';
|
|
390
|
-
import { Div, H3, Span, Img } from 'native-document/src/elements';
|
|
391
|
-
|
|
392
|
-
const UserProfile = useCache(($binder) => {
|
|
393
|
-
// Simple value bindings
|
|
394
|
-
const email = $binder.value('email');
|
|
395
|
-
|
|
396
|
-
// Computed value bindings - function receives user object directly
|
|
397
|
-
const fullName = $binder.value((user) => {
|
|
398
|
-
return `${user.firstName} ${user.lastName}`;
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
const memberSince = $binder.value((user) => {
|
|
402
|
-
const date = new Date(user.joinedAt);
|
|
403
|
-
return date.toLocaleDateString();
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
// Attribute binding - function receives user object directly
|
|
407
|
-
const avatarSrc = $binder.attr((user) => {
|
|
408
|
-
return user.avatarUrl;
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
// Class binding - function receives user object directly
|
|
412
|
-
const isPremium = $binder.class((user) => {
|
|
413
|
-
return user.plan === 'premium';
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
return Div({
|
|
417
|
-
class: {
|
|
418
|
-
'user-profile': true,
|
|
419
|
-
'premium': isPremium
|
|
420
|
-
}
|
|
421
|
-
}, [
|
|
422
|
-
Img({ src: avatarSrc, class: 'avatar' }),
|
|
423
|
-
H3(fullName),
|
|
424
|
-
Span({ class: 'email' }, email),
|
|
425
|
-
Span({ class: 'member-since' }, ['Member since: ', memberSince])
|
|
426
|
-
]);
|
|
427
|
-
});
|
|
308
|
+
---
|
|
428
309
|
|
|
429
|
-
|
|
430
|
-
const profile = UserProfile({
|
|
431
|
-
firstName: 'Alice',
|
|
432
|
-
lastName: 'Johnson',
|
|
433
|
-
email: 'alice@example.com',
|
|
434
|
-
avatarUrl: '/avatars/alice.jpg',
|
|
435
|
-
plan: 'premium',
|
|
436
|
-
joinedAt: '2023-01-15'
|
|
437
|
-
});
|
|
438
|
-
```
|
|
310
|
+
## `useSingleton()` - Singleton Views
|
|
439
311
|
|
|
440
|
-
|
|
441
|
-
```javascript
|
|
442
|
-
import { useCache } from 'native-document';
|
|
443
|
-
import { Div } from 'native-document/src/elements';
|
|
444
|
-
|
|
445
|
-
const ColoredBox = useCache(($binder) => {
|
|
446
|
-
const text = $binder.value('text');
|
|
447
|
-
|
|
448
|
-
// Multiple style bindings - each receives box object
|
|
449
|
-
const bgColor = $binder.style((box) => {
|
|
450
|
-
return box.color;
|
|
451
|
-
});
|
|
452
|
-
|
|
453
|
-
const boxWidth = $binder.style((box) => {
|
|
454
|
-
return box.width + 'px';
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
const boxHeight = $binder.style((box) => {
|
|
458
|
-
return box.height + 'px';
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
return Div({
|
|
462
|
-
style: {
|
|
463
|
-
backgroundColor: bgColor,
|
|
464
|
-
width: boxWidth,
|
|
465
|
-
height: boxHeight,
|
|
466
|
-
display: 'flex',
|
|
467
|
-
alignItems: 'center',
|
|
468
|
-
justifyContent: 'center'
|
|
469
|
-
}
|
|
470
|
-
}, text);
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
// Usage
|
|
474
|
-
const box = ColoredBox({
|
|
475
|
-
text: 'Hello',
|
|
476
|
-
color: '#3498db',
|
|
477
|
-
width: 200,
|
|
478
|
-
height: 100
|
|
479
|
-
});
|
|
480
|
-
```
|
|
481
|
-
|
|
482
|
-
### Conditional Content
|
|
483
|
-
```javascript
|
|
484
|
-
import { useCache } from 'native-document';
|
|
485
|
-
import { Div, Span, Badge } from 'native-document/src/elements';
|
|
486
|
-
|
|
487
|
-
const ProductCard = useCache(($binder) => {
|
|
488
|
-
const name = $binder.value('name');
|
|
489
|
-
|
|
490
|
-
// Computed price - receives product object
|
|
491
|
-
const displayPrice = $binder.value((product) => {
|
|
492
|
-
return product.salePrice
|
|
493
|
-
? `$${product.salePrice} (was $${product.price})`
|
|
494
|
-
: `$${product.price}`;
|
|
495
|
-
});
|
|
496
|
-
|
|
497
|
-
// Conditional classes - receive product object
|
|
498
|
-
const isOnSale = $binder.class((product) => {
|
|
499
|
-
return product.salePrice !== null;
|
|
500
|
-
});
|
|
501
|
-
|
|
502
|
-
const isOutOfStock = $binder.class((product) => {
|
|
503
|
-
return product.stock === 0;
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
// Stock badge text - receives product object
|
|
507
|
-
const stockBadge = $binder.value((product) => {
|
|
508
|
-
if (product.stock === 0) return 'Out of Stock';
|
|
509
|
-
if (product.stock < 5) return 'Low Stock';
|
|
510
|
-
return 'In Stock';
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
return Div({
|
|
514
|
-
class: {
|
|
515
|
-
'product': true,
|
|
516
|
-
'on-sale': isOnSale,
|
|
517
|
-
'out-of-stock': isOutOfStock
|
|
518
|
-
}
|
|
519
|
-
}, [
|
|
520
|
-
Span({ class: 'name' }, name),
|
|
521
|
-
Span({ class: 'price' }, displayPrice),
|
|
522
|
-
Badge(stockBadge)
|
|
523
|
-
]);
|
|
524
|
-
});
|
|
525
|
-
|
|
526
|
-
// Usage
|
|
527
|
-
const product = ProductCard({
|
|
528
|
-
name: 'Phone',
|
|
529
|
-
price: 599,
|
|
530
|
-
salePrice: 499,
|
|
531
|
-
stock: 3
|
|
532
|
-
});
|
|
533
|
-
```
|
|
534
|
-
|
|
535
|
-
### Multiple Arguments
|
|
536
|
-
```javascript
|
|
537
|
-
import { useCache } from 'native-document';
|
|
538
|
-
import { Div, H3, P } from 'native-document/src/elements';
|
|
539
|
-
|
|
540
|
-
// Component that accepts multiple arguments
|
|
541
|
-
const ArticleCard = useCache(($binder) => {
|
|
542
|
-
// When using multiple args, functions receive them all
|
|
543
|
-
const title = $binder.value((article, options) => {
|
|
544
|
-
return options.uppercase ? article.title.toUpperCase() : article.title;
|
|
545
|
-
});
|
|
546
|
-
|
|
547
|
-
const excerpt = $binder.value((article, options) => {
|
|
548
|
-
const length = options.excerptLength || 100;
|
|
549
|
-
return article.content.substring(0, length) + '...';
|
|
550
|
-
});
|
|
551
|
-
|
|
552
|
-
return Div({ class: 'article' }, [
|
|
553
|
-
H3(title),
|
|
554
|
-
P(excerpt)
|
|
555
|
-
]);
|
|
556
|
-
});
|
|
557
|
-
|
|
558
|
-
// Usage with multiple arguments
|
|
559
|
-
const card = ArticleCard(
|
|
560
|
-
{ title: 'My Article', content: 'Long article content...' },
|
|
561
|
-
{ uppercase: true, excerptLength: 150 }
|
|
562
|
-
);
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
## useSingleton() - Singleton Views
|
|
566
|
-
|
|
567
|
-
`useSingleton()` creates a view that is rendered only once, then updated through named sections.
|
|
312
|
+
`useSingleton()` returns a **function**. The first call (no args) renders and returns the cached DOM node. Subsequent calls accept an object with named section keys to update only the sections you want:
|
|
568
313
|
|
|
569
314
|
### Basic Usage
|
|
315
|
+
|
|
570
316
|
```javascript
|
|
571
317
|
import { useSingleton } from 'native-document';
|
|
572
|
-
import { Div, H1 } from 'native-document/
|
|
318
|
+
import { Div, H1 } from 'native-document/elements';
|
|
573
319
|
|
|
574
|
-
const
|
|
320
|
+
const AppLayout = useSingleton((view) => {
|
|
575
321
|
return Div({ class: 'dashboard' }, [
|
|
576
322
|
H1('Dashboard'),
|
|
577
|
-
|
|
578
|
-
// Named section - can be updated later
|
|
579
323
|
view.createSection('content'),
|
|
580
|
-
|
|
581
324
|
Div({ class: 'footer' }, 'Footer')
|
|
582
325
|
]);
|
|
583
326
|
});
|
|
584
327
|
|
|
585
|
-
// First
|
|
586
|
-
const
|
|
587
|
-
document.body.appendChild(
|
|
328
|
+
// First call - renders the cached DOM node
|
|
329
|
+
const layout = AppLayout();
|
|
330
|
+
document.body.appendChild(layout);
|
|
588
331
|
|
|
589
|
-
// Update specific section
|
|
590
|
-
|
|
591
|
-
Div(['Updated content at ', new Date().toLocaleTimeString()])
|
|
592
|
-
]);
|
|
332
|
+
// Update a specific section by name
|
|
333
|
+
AppLayout({ content: Div(['Updated at ', new Date().toLocaleTimeString()]) });
|
|
593
334
|
```
|
|
594
335
|
|
|
595
|
-
### createSection(name, transformFn?)
|
|
336
|
+
### `view.createSection(name, transformFn?)`
|
|
337
|
+
|
|
338
|
+
Creates a named section. An optional transform function wraps the content before insertion:
|
|
596
339
|
|
|
597
|
-
Creates a named section that can be updated. Optional transform function wraps the content:
|
|
598
340
|
```javascript
|
|
599
341
|
import { useSingleton } from 'native-document';
|
|
600
|
-
import { Div,
|
|
342
|
+
import { Div, Header, Main, Section } from 'native-document/elements';
|
|
601
343
|
|
|
602
|
-
const
|
|
344
|
+
const AppLayout = useSingleton((view) => {
|
|
603
345
|
return Div([
|
|
604
346
|
Header([
|
|
605
|
-
// Section without wrapper
|
|
606
347
|
view.createSection('header')
|
|
607
348
|
]),
|
|
608
349
|
Main([
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
})
|
|
350
|
+
view.createSection('main', content =>
|
|
351
|
+
Section({ class: 'main-section' }, content)
|
|
352
|
+
)
|
|
613
353
|
])
|
|
614
354
|
]);
|
|
615
355
|
});
|
|
616
356
|
|
|
617
|
-
|
|
618
|
-
const layout = Layout.render();
|
|
357
|
+
const layout = AppLayout();
|
|
619
358
|
document.body.appendChild(layout);
|
|
620
359
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
// Update main - content wrapped in Section
|
|
625
|
-
Layout.render([P('Main content')]);
|
|
626
|
-
// Result: <section class="main-section"><p>Main content</p></section>
|
|
360
|
+
AppLayout({ header: H1('My App') }); // updates header only
|
|
361
|
+
AppLayout({ main: P('Main content') }); // updates main only - wrapped in Section
|
|
627
362
|
```
|
|
628
363
|
|
|
629
364
|
### Multiple Sections
|
|
365
|
+
|
|
630
366
|
```javascript
|
|
631
367
|
import { useSingleton } from 'native-document';
|
|
632
|
-
import { Div, Header, Main, Aside, H1, P, Ul, Li } from 'native-document/
|
|
368
|
+
import { Div, Header, Main, Aside, H1, P, Ul, Li } from 'native-document/elements';
|
|
633
369
|
|
|
634
370
|
const AppLayout = useSingleton((view) => {
|
|
635
371
|
return Div({ class: 'app' }, [
|
|
636
|
-
Header([
|
|
637
|
-
view.createSection('header')
|
|
638
|
-
]),
|
|
372
|
+
Header([view.createSection('header')]),
|
|
639
373
|
Div({ class: 'container' }, [
|
|
640
|
-
Main([
|
|
641
|
-
|
|
642
|
-
]),
|
|
643
|
-
Aside([
|
|
644
|
-
view.createSection('sidebar')
|
|
645
|
-
])
|
|
374
|
+
Main([view.createSection('main')]),
|
|
375
|
+
Aside([view.createSection('sidebar')])
|
|
646
376
|
])
|
|
647
377
|
]);
|
|
648
378
|
});
|
|
649
379
|
|
|
650
|
-
|
|
651
|
-
const app = AppLayout.render();
|
|
380
|
+
const app = AppLayout();
|
|
652
381
|
document.body.appendChild(app);
|
|
653
382
|
|
|
654
|
-
// Update sections
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
```
|
|
659
|
-
|
|
660
|
-
## Best Practices
|
|
661
|
-
|
|
662
|
-
### 1. Use Descriptive Binding Names
|
|
663
|
-
```javascript
|
|
664
|
-
import { useCache } from 'native-document';
|
|
665
|
-
|
|
666
|
-
// ✅ Good: Clear variable names
|
|
667
|
-
const UserCard = useCache(($binder) => {
|
|
668
|
-
const userName = $binder.value('name');
|
|
669
|
-
const userEmail = $binder.value('email');
|
|
670
|
-
const isPremiumUser = $binder.class((user) => user.premium);
|
|
671
|
-
|
|
672
|
-
return Div({ class: { 'premium': isPremiumUser } }, [
|
|
673
|
-
Span(userName),
|
|
674
|
-
Span(userEmail)
|
|
675
|
-
]);
|
|
676
|
-
});
|
|
677
|
-
|
|
678
|
-
// ❌ Bad: Generic names
|
|
679
|
-
const UserCard = useCache(($binder) => {
|
|
680
|
-
const v1 = $binder.value('name');
|
|
681
|
-
const v2 = $binder.value('email');
|
|
682
|
-
const c1 = $binder.class((user) => user.premium);
|
|
683
|
-
});
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
### 2. Remember Arguments Are Passed Directly
|
|
687
|
-
```javascript
|
|
688
|
-
import { useCache } from 'native-document';
|
|
689
|
-
|
|
690
|
-
// ✅ Good: Direct access to data
|
|
691
|
-
const Card = useCache(($binder) => {
|
|
692
|
-
const value = $binder.value((item) => {
|
|
693
|
-
return item.property; // item is the object passed
|
|
694
|
-
});
|
|
695
|
-
});
|
|
696
|
-
|
|
697
|
-
// Component call
|
|
698
|
-
Card({ property: 'value' });
|
|
699
|
-
```
|
|
700
|
-
|
|
701
|
-
### 3. Use .nd.attach() for Event Handlers
|
|
702
|
-
```javascript
|
|
703
|
-
import { useCache } from 'native-document';
|
|
704
|
-
import { Button } from 'native-document/src/elements';
|
|
705
|
-
|
|
706
|
-
// ✅ Good: Use binder.attach with .nd.attach()
|
|
707
|
-
const ActionButton = useCache(($binder) => {
|
|
708
|
-
const label = $binder.value('label');
|
|
709
|
-
|
|
710
|
-
// Handler receives (event, data)
|
|
711
|
-
const handleClick = $binder.attach((event, data) => {
|
|
712
|
-
console.log('Clicked:', data);
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
return Button(label).nd.attach('onClick', handleClick);
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
// ❌ Bad: Direct event handler (won't receive data)
|
|
719
|
-
const ActionButton = useCache(($binder) => {
|
|
720
|
-
const label = $binder.value('label');
|
|
721
|
-
|
|
722
|
-
return Button(label).nd.onClick((event) => {
|
|
723
|
-
// No access to data here
|
|
724
|
-
});
|
|
725
|
-
});
|
|
726
|
-
```
|
|
727
|
-
|
|
728
|
-
### 4. Combine with Observable.when()
|
|
729
|
-
```javascript
|
|
730
|
-
import { useCache } from 'native-document';
|
|
731
|
-
import { Observable } from 'native-document';
|
|
732
|
-
import { Li } from 'native-document/src/elements';
|
|
733
|
-
|
|
734
|
-
const activeId = Observable(null);
|
|
735
|
-
|
|
736
|
-
// ✅ Good: Use Observable.when() for reactive class binding
|
|
737
|
-
const ListItem = useCache(($binder) => {
|
|
738
|
-
const label = $binder.value('label');
|
|
739
|
-
|
|
740
|
-
// Class updates automatically when activeId changes
|
|
741
|
-
const isActive = $binder.class((item) => {
|
|
742
|
-
return activeId.when(item.id);
|
|
743
|
-
});
|
|
744
|
-
|
|
745
|
-
return Li({ class: { 'active': isActive } }, label);
|
|
746
|
-
});
|
|
747
|
-
```
|
|
748
|
-
|
|
749
|
-
### 5. Keep Transform Functions Simple
|
|
750
|
-
```javascript
|
|
751
|
-
import { useCache } from 'native-document';
|
|
383
|
+
// Update sections individually
|
|
384
|
+
AppLayout({ header: H1('My App') });
|
|
385
|
+
AppLayout({ main: P('Main content') });
|
|
386
|
+
AppLayout({ sidebar: Ul([Li('Item 1')]) });
|
|
752
387
|
|
|
753
|
-
//
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
const inStock = $binder.class((product) => {
|
|
760
|
-
return product.stock > 0;
|
|
761
|
-
});
|
|
762
|
-
|
|
763
|
-
return Div({ class: { 'in-stock': inStock } }, price);
|
|
764
|
-
});
|
|
765
|
-
|
|
766
|
-
// ❌ Bad: Complex logic
|
|
767
|
-
const Card = useCache(($binder) => {
|
|
768
|
-
const value = $binder.value((product) => {
|
|
769
|
-
// Multiple API calls
|
|
770
|
-
// Complex calculations
|
|
771
|
-
// Side effects
|
|
772
|
-
return result;
|
|
773
|
-
});
|
|
388
|
+
// Or update multiple sections at once
|
|
389
|
+
AppLayout({
|
|
390
|
+
header: H1('New Title'),
|
|
391
|
+
main: P('New content')
|
|
774
392
|
});
|
|
775
393
|
```
|
|
776
394
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
| Method | Parameters | Returns | Description |
|
|
780
|
-
|--------|------------|---------|-------------|
|
|
781
|
-
| `$binder.value(key)` | `key: string` | `TemplateBinding` | Bind to property by name |
|
|
782
|
-
| `$binder.value(fn)` | `fn: (...args) => any` | `TemplateBinding` | Bind with transform function |
|
|
783
|
-
| `$binder.property(key)` | `key: string` | `TemplateBinding` | Alias for value() |
|
|
784
|
-
| `$binder.class(fn)` | `fn: (...args) => boolean` | `TemplateBinding` | Bind CSS class |
|
|
785
|
-
| `$binder.style(fn)` | `fn: (...args) => string` | `TemplateBinding` | Bind inline style value |
|
|
786
|
-
| `$binder.attr(fn)` | `fn: (...args) => string` | `TemplateBinding` | Bind attribute value |
|
|
787
|
-
| `$binder.attach(fn)` | `fn: (event, ...args) => void` | `TemplateBinding` | Bind event handler |
|
|
395
|
+
---
|
|
788
396
|
|
|
789
|
-
|
|
397
|
+
## Best Practices
|
|
790
398
|
|
|
791
|
-
|
|
399
|
+
1. Use `$binder.attach()` with `.nd.attach()` for event handlers - direct `.nd.onClick()` won't receive the data object
|
|
400
|
+
2. Use `Observable.when()` inside `$binder.class()` for reactive class binding that responds to external state
|
|
401
|
+
3. Keep transform functions simple and free of side effects
|
|
402
|
+
4. Use `ForEachArray` (not `ForEach`) with `useCache` - it's designed for cached component cloning
|
|
403
|
+
5. Use `useSingleton` for layouts and shells that are rendered once but have dynamic inner regions
|
|
792
404
|
|
|
793
|
-
|
|
405
|
+
---
|
|
794
406
|
|
|
795
407
|
## Next Steps
|
|
796
408
|
|
|
797
|
-
- **[
|
|
798
|
-
- **[
|
|
799
|
-
- **[
|
|
800
|
-
- **[
|
|
801
|
-
- **[
|
|
802
|
-
- **[List Rendering](list-rendering.md)** - (ForEach | ForEachArray) and dynamic lists
|
|
803
|
-
- **[Routing](routing.md)** - Navigation and URL management
|
|
804
|
-
- **[State Management](state-management.md)** - Global state patterns
|
|
805
|
-
- **[NDElement](native-document-element.md)** - Native Document Element
|
|
806
|
-
- **[Extending NDElement](extending-native-document-element.md)** - Custom Methods Guide
|
|
807
|
-
- **[Args Validation](validation.md)** - Function Argument Validation
|
|
808
|
-
- **[Memory Management](memory-management.md)** - Memory management
|
|
409
|
+
- **[Elements](./elements.md)** - Creating and composing UI
|
|
410
|
+
- **[List Rendering](./list-rendering.md)** - ForEach and ForEachArray
|
|
411
|
+
- **[NDElement](./native-document-element.md)** - `.nd.attach()` reference
|
|
412
|
+
- **[Observables](./observables.md)** - Reactive state management
|
|
413
|
+
- **[State Management](./state-management.md)** - Global state patterns
|
|
809
414
|
|
|
810
415
|
## Utilities
|
|
811
416
|
|
|
812
|
-
- **[Cache](
|
|
813
|
-
- **[NativeFetch](
|
|
814
|
-
- **[Filters](
|
|
417
|
+
- **[Cache](./cache.md)** - Lazy initialization and singleton patterns
|
|
418
|
+
- **[NativeFetch](./native-fetch.md)** - HTTP client with interceptors
|
|
419
|
+
- **[Filters](./filters.md)** - Data filtering helpers
|