@base-framework/base 3.5.7 → 3.5.9
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/copilot.md +355 -28
- package/dist/base.js +1 -1
- package/dist/base.js.map +2 -2
- package/dist/types/modules/data/types/basic-data.d.ts +4 -2
- package/package.json +1 -1
package/copilot.md
CHANGED
|
@@ -3,57 +3,114 @@
|
|
|
3
3
|
Purpose: concise, project-specific guidance so AI agents are instantly productive in this codebase.
|
|
4
4
|
|
|
5
5
|
## Architecture (what to know first)
|
|
6
|
-
- Render UI from plain JS objects ("layouts")—no templates. Parser turns objects into DOM (browser) or HTML strings (server).
|
|
7
|
-
- Public API re-exported by `src/base.js
|
|
8
|
-
- Runtime renderer switch
|
|
9
|
-
- Component
|
|
10
|
-
-
|
|
6
|
+
- **Render UI from plain JS objects** ("layouts")—no templates, no JSX. Parser turns objects into DOM (browser) or HTML strings (server).
|
|
7
|
+
- **Public API** re-exported by `src/base.js`: `Builder`, `Component`, `Unit`, `Jot`, `Pod`, `Atom`, `Data`, `SimpleData`, `Model`, `StateTracker`, `Import`, `NavLink`, `router`, `Html`, `Directives`, `Ajax`.
|
|
8
|
+
- **Runtime renderer switch**: `modules/layout/render/*` via `RenderController.setup()` → `BrowserRender` in browser, `ServerRender` for SSR.
|
|
9
|
+
- **Component hierarchy**: `Unit` (lifecycle/context) → `Component` (adds state via `StateTracker` and events).
|
|
10
|
+
- **Shorthand APIs**: `Jot` (functional components), `Pod` (stateful functional), `Atom` (reusable layouts).
|
|
11
|
+
- **Reactive data**: `Data` (deep proxy), `SimpleData` (shallow proxy), `Model` (server-backed). `WatcherHelper` powers `[[prop.path]]` bindings.
|
|
12
|
+
- **State management**: `StateTracker` for global state, component-level states via `setupStates()`.
|
|
13
|
+
- **Routing**: Client-side with History API, reactive `router.data.path`, automatic route parameters.
|
|
14
|
+
- **HTTP requests**: `Ajax` module with shorthand methods (`Ajax.get()`, `Ajax.post()`).
|
|
15
|
+
- **Dynamic imports**: `Import` wrapper for lazy loading with dependencies.
|
|
11
16
|
|
|
12
17
|
## CRITICAL: Common Mistakes to Avoid
|
|
13
18
|
1. **DON'T use templates or JSX** - Base uses plain JavaScript objects for layouts
|
|
14
19
|
2. **DON'T call `render()` directly** - Components call it internally; you return layout objects from `render()`
|
|
15
20
|
3. **DON'T mutate props** - Props are read-only; use `this.data` or `this.state` for mutable values
|
|
16
21
|
4. **DON'T use `this.setState()`** - Use `this.state.set('key', value)` or `this.state.increment('key')`
|
|
17
|
-
5. **DON'T forget `new` with Components** - Always: `new MyComponent()`, never just `MyComponent()`
|
|
18
|
-
6. **DON'T use `new` with Atoms** - Always: `Button()`, never `new Button()
|
|
19
|
-
7. **DON'T
|
|
22
|
+
5. **DON'T forget `new` with Components** - Always: `new MyComponent()`, `new Component()`, never just `MyComponent()`
|
|
23
|
+
6. **DON'T use `new` with Atoms** - Always: `Button()`, never `new Button()`. Atoms are functions, not classes
|
|
24
|
+
7. **DON'T use `new` with Jot/Pod** - Call the returned class: `const MyJot = Jot({...}); new MyJot()` not `new Jot({})`
|
|
25
|
+
8. **DON'T mix data initialization locations** - Use `setData()` for initial setup, not `beforeSetup` or constructor
|
|
26
|
+
9. **DON'T bind without data** - `bind` directive requires `this.data` to be initialized via `setData()`
|
|
27
|
+
10. **DON'T use wrong state methods** - State keys must be defined in `setupStates()` before using `increment`, `decrement`, `toggle`
|
|
28
|
+
11. **DON'T forget Import function form** - Use `Import(() => import('./file.js'))` not `Import('./file.js')` for bundler support
|
|
29
|
+
12. **DON'T use element.remove()** - Use `Html.removeElement(element)` or `Builder.removeNode(element)` for proper cleanup
|
|
30
|
+
13. **DON'T access DOM before afterSetup** - `this.panel` and `this.elem` are only available after `afterSetup()` lifecycle hook
|
|
31
|
+
14. **DON'T return arrays from render()** - Wrap multiple elements: `return { children: [elem1, elem2] }` not `return [elem1, elem2]`
|
|
32
|
+
15. **DON'T use `await` in render()** - Load data in lifecycle hooks, render() must be synchronous
|
|
20
33
|
|
|
21
34
|
## Authoring layouts (house rules)
|
|
22
|
-
- Shape
|
|
35
|
+
- **Shape**: `{ tag: 'div', class: 'name', children: [...] }`. Shorthands: `nest` → `children`; `text` creates text node; `html|innerHTML` sets raw HTML.
|
|
23
36
|
- **Default tag is 'div'** - Omit `tag` for divs: `{ class: 'container' }` renders as `<div class="container"></div>`
|
|
24
|
-
-
|
|
25
|
-
- **
|
|
26
|
-
-
|
|
27
|
-
|
|
37
|
+
- **Button default** - Buttons default to `type: 'button'` (not 'submit')
|
|
38
|
+
- **Events on elements** receive `(event, parentComponent)` and are bound on the element: `{ click(e, parent) { parent.doThing(); } }`
|
|
39
|
+
- **Event names are lowercase** - Use `click`, `mouseover`, `change`, `input`, `submit`, not `onClick` or `CLICK`
|
|
40
|
+
- **Watchers** become `watch` directives automatically:
|
|
41
|
+
- **PREFERRED**: Current component data: `{ class: 'counter-[[count]]' }` (simplest, watches `this.data` or `this.state`)
|
|
28
42
|
- Specific data source: `{ value: ['[[path]]', data] }` (when you need different data than `this.data`)
|
|
29
43
|
- Multi-source with callback: `{ class: ['[[a]] [[b]]', [dataA, dataB], ([a,b]) => `${a}-${b}`] }` (advanced use)
|
|
30
|
-
- Directives are just attrs mapped in `modules/layout/directives/core/default-directives.js` (e.g., `bind`, `watch`, `map`, `for`, `route`, `switch`, `useState`, `useData`, `onCreated`, `onDestroyed`).
|
|
31
44
|
- **Null/undefined props are ignored** - Use `{ class: condition ? 'active' : null }` to conditionally add attributes
|
|
45
|
+
- **Arrays in children** - Flatten automatically: `{ children: [elem1, [elem2, elem3], elem4] }` works
|
|
46
|
+
- **Function children** - Return layout objects: `{ children: [() => ({ tag: 'span', text: 'Dynamic' })] }`
|
|
47
|
+
- **Conditional rendering** - Use logical operators: `{ children: [condition && element, other || fallback] }`
|
|
32
48
|
|
|
33
49
|
## Common directives (quick cookbook)
|
|
34
50
|
- **bind** (two-way by default, binds to `this.data` in component):
|
|
35
51
|
- Text input: `{ tag: 'input', type: 'text', bind: 'form.name' }` (binds to `this.data.form.name`)
|
|
36
52
|
- Checkbox: `{ tag: 'input', type: 'checkbox', bind: 'form.accepted' }` (binds to boolean)
|
|
53
|
+
- Radio: `{ tag: 'input', type: 'radio', name: 'color', value: 'red', bind: 'form.color' }`
|
|
37
54
|
- Select + options: `{ tag: 'select', bind: 'form.color', children: [{ map: ['[[colors]]', data, (c) => ({ tag:'option', value:c, text:c })] }] }`
|
|
38
55
|
- **IMPORTANT**: `bind` requires the component to have `this.data` set via `setData()`
|
|
39
56
|
- Custom attribute: `{ tag: 'a', bind: 'href:link.url' }` (binds to href instead of value)
|
|
40
57
|
- With filter: `{ bind: ['count', (v) => Math.round(v)] }` (transform displayed value)
|
|
58
|
+
- One-way: `{ oneway: 'propPath' }` (element → data only)
|
|
41
59
|
|
|
42
60
|
- **map** (render lists from arrays, signature: `[watcherString, dataSource, callback]`):
|
|
43
61
|
- Basic: `{ tag: 'ul', children: [{ map: ['[[items]]', data, (item, i) => ({ tag:'li', text:item.name })] }] }`
|
|
44
62
|
- Callback receives: `(item, index)` - use both for keyed lists
|
|
45
63
|
- **IMPORTANT**: The callback must return a layout object, not a string
|
|
64
|
+
- With keys: Use `key: item.id` in returned layout for better performance
|
|
65
|
+
|
|
66
|
+
- **for** (repeat element N times):
|
|
67
|
+
- Basic: `{ for: [5, (i) => ({ tag: 'div', text: `Item ${i}` })] }`
|
|
68
|
+
- With data: `{ for: [['[[count]]', data], (i) => ({ tag: 'span', text: i })] }`
|
|
69
|
+
|
|
70
|
+
- **if** (conditional rendering):
|
|
71
|
+
- Basic: `{ if: [() => condition, { tag: 'div', text: 'Shown' }] }`
|
|
72
|
+
- With data: `{ if: [['[[isVisible]]', data], { tag: 'div', text: 'Visible' }] }`
|
|
73
|
+
- **NOTE**: Use regular JavaScript `condition && layout` for simpler cases
|
|
46
74
|
|
|
47
75
|
- **Watchers** (one-way binding, auto-updates when data changes):
|
|
48
|
-
- Simple: `{ class: 'status-[[status]]' }` (watches `this.data.status`)
|
|
76
|
+
- Simple: `{ class: 'status-[[status]]' }` (watches `this.data.status` or `this.state.status`)
|
|
49
77
|
- Multiple: `{ text: 'User: [[name]] Age: [[age]]' }` (watches multiple props)
|
|
50
78
|
- Deep paths: `{ text: '[[user.profile.name]]' }` (nested object access)
|
|
79
|
+
- In arrays: `{ class: ['theme-[[theme]]', 'page'] }` (combines static and dynamic)
|
|
51
80
|
|
|
52
81
|
- **Lifecycle hooks** (element-level, different from component lifecycle):
|
|
53
82
|
- `{ onCreated: (el, parent) => {/* el is DOM node, parent is component */} }`
|
|
54
83
|
- `{ onDestroyed: (el, parent) => {/* cleanup before removal */} }`
|
|
55
84
|
- **IMPORTANT**: These fire for each element, not once per component
|
|
56
85
|
|
|
86
|
+
- **State/Data hooks** (access parent state/data):
|
|
87
|
+
- `{ useData: data }` - use specific data source for watchers in this subtree
|
|
88
|
+
- `{ useState: state }` - use specific state source for watchers in this subtree
|
|
89
|
+
- `{ useContext: context }` - use specific context for this subtree
|
|
90
|
+
- `{ useParent: component }` - explicitly set parent component reference
|
|
91
|
+
- **NOTE**: These propagate to child elements
|
|
92
|
+
|
|
93
|
+
- **onSet** (reactive callback for data changes):
|
|
94
|
+
- Basic: `{ onSet: ['propPath', (value, oldValue) => ({ tag: 'div', text: value })] }`
|
|
95
|
+
- Multiple props: `{ onSet: [['prop1', 'prop2'], ([val1, val2]) => layout] }`
|
|
96
|
+
- With data source: `{ onSet: ['propPath', data, (value) => layout] }`
|
|
97
|
+
|
|
98
|
+
- **Component integration**:
|
|
99
|
+
- `{ addState: { count: 0 } }` - add state properties to component
|
|
100
|
+
- `{ addEvent: { myEvent: (data) => {} } }` - add event to component
|
|
101
|
+
- `{ addContext: { theme: 'dark' } }` - add context to component
|
|
102
|
+
|
|
103
|
+
- **Animation**:
|
|
104
|
+
- Enter: `{ animateEnter: 'fadeIn' }` or `{ animateEnter: { name: 'slide', duration: 300 } }`
|
|
105
|
+
- Exit: `{ animateExit: 'fadeOut' }`
|
|
106
|
+
- Move: `{ animateMove: 'slide' }`
|
|
107
|
+
|
|
108
|
+
- **Accessibility**:
|
|
109
|
+
- `{ a11yHide: true }` - hide from screen readers
|
|
110
|
+
- `{ a11yLabel: 'descriptive text' }` - set aria-label
|
|
111
|
+
- `{ a11yRole: 'button' }` - set role
|
|
112
|
+
- `{ a11yDescribe: 'longer description' }` - set aria-describedby
|
|
113
|
+
|
|
57
114
|
- **Cache/persist**:
|
|
58
115
|
- `cache: 'propName'` - stores DOM element in `this.propName` (use for later access)
|
|
59
116
|
- `persist: true` on parent preserves child component instances across re-renders
|
|
@@ -63,6 +120,10 @@ Purpose: concise, project-specific guidance so AI agents are instantly productiv
|
|
|
63
120
|
- Extend `Component` and implement `render()` - return layout object (never call `render()` manually)
|
|
64
121
|
- Root element auto-cached as `this.panel` - access after `afterSetup()` lifecycle hook
|
|
65
122
|
- Use `this.getId('child')` for stable DOM IDs across re-renders
|
|
123
|
+
- Helper methods available on Component instances:
|
|
124
|
+
- `this.if(condition, layout)` - conditional rendering helper
|
|
125
|
+
- `this.map(array, callback)` - array mapping helper
|
|
126
|
+
- `this.declareProps(schema)` - prop validation and defaults
|
|
66
127
|
|
|
67
128
|
### State Management
|
|
68
129
|
- Override `setupStates()` to define reactive state properties:
|
|
@@ -70,44 +131,310 @@ Purpose: concise, project-specific guidance so AI agents are instantly productiv
|
|
|
70
131
|
setupStates() {
|
|
71
132
|
return {
|
|
72
133
|
count: 0, // Initial value
|
|
73
|
-
active: { state: false, callBack: (val) => {/* fires on change */} }
|
|
134
|
+
active: { state: false, callBack: (val) => {/* fires on change */} },
|
|
135
|
+
items: [] // Arrays work too
|
|
74
136
|
};
|
|
75
137
|
}
|
|
76
138
|
```
|
|
77
139
|
- Access via `this.state.count` or `this.state.get('count')`
|
|
78
|
-
- Update methods:
|
|
140
|
+
- Update methods:
|
|
141
|
+
- `this.state.set('count', 5)` or `this.state.set({ count: 5, active: true })`
|
|
142
|
+
- `this.state.increment('count', amount?)` - add to number (default +1)
|
|
143
|
+
- `this.state.decrement('count', amount?)` - subtract from number (default -1)
|
|
144
|
+
- `this.state.toggle('active')` - flip boolean
|
|
145
|
+
- `this.state.push('items', item)` - add to array
|
|
146
|
+
- `this.state.splice('items', index, count)` - remove from array
|
|
79
147
|
- **NEVER** use `this.setState()` - that method doesn't exist
|
|
148
|
+
- **IMPORTANT**: State keys must be defined in `setupStates()` before using helper methods
|
|
149
|
+
|
|
150
|
+
### Global State (StateTracker)
|
|
151
|
+
- Create global state with `StateTracker.create(id, initialState)`:
|
|
152
|
+
```javascript
|
|
153
|
+
import { StateTracker } from '@base-framework/base';
|
|
154
|
+
const appState = StateTracker.create('app', { user: null, theme: 'light' });
|
|
155
|
+
```
|
|
156
|
+
- Access in components:
|
|
157
|
+
```javascript
|
|
158
|
+
setupStateTarget() {
|
|
159
|
+
this.state = StateTracker.get('app');
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
- All state methods work on global state: `this.state.set('theme', 'dark')`
|
|
163
|
+
- Use `useState` directive to connect elements to global state
|
|
80
164
|
|
|
81
165
|
### Data Management
|
|
82
166
|
- Override `setData()` to attach reactive data (runs during component initialization):
|
|
83
167
|
```javascript
|
|
84
168
|
setData() {
|
|
85
169
|
this.data = new Data({ name: '', items: [] });
|
|
170
|
+
// Optional: local storage persistence
|
|
171
|
+
this.data.setKey('MY_STORAGE_KEY');
|
|
172
|
+
this.data.resume({ name: '', items: [] }); // Load or use defaults
|
|
86
173
|
}
|
|
87
174
|
```
|
|
88
175
|
- **CRITICAL**: Initialize data in `setData()`, NOT in `beforeSetup()` or constructor
|
|
89
176
|
- Use `Data` for deep nested objects, `SimpleData` for flat objects, `Model` for server-backed data
|
|
90
|
-
- Access/modify: `this.data.name = 'test'` or `this.data.set('name', 'test')`
|
|
91
|
-
- Components with `route`/`switch` directives automatically receive `this.route` (a bindable Data object)
|
|
177
|
+
- Access/modify: `this.data.name = 'test'` or `this.data.set('name', 'test')` or `this.data.set({ name: 'test', items: [] })`
|
|
178
|
+
- Components with `route`/`switch` directives automatically receive `this.route` (a bindable Data object with route params)
|
|
179
|
+
- Array methods: `push`, `pop`, `shift`, `unshift`, `splice` (all trigger reactivity)
|
|
180
|
+
- Other methods:
|
|
181
|
+
- `this.data.get('nested.path')` - get nested value
|
|
182
|
+
- `this.data.refresh('key')` - trigger watchers without changing value
|
|
183
|
+
- `this.data.delete('key')` - remove property
|
|
184
|
+
- `this.data.ifNull('key', defaultValue)` - return default if null/undefined
|
|
185
|
+
- `this.data.getIndex('array', predicate)` - find array index
|
|
186
|
+
- `this.data.concat('array', items)` - append to array
|
|
187
|
+
- `this.data.on('change', callback)` - subscribe to changes
|
|
188
|
+
- `this.data.off('change', callback)` - unsubscribe
|
|
189
|
+
- `this.data.store()` - save to local storage (if key set)
|
|
190
|
+
- `this.data.resume(defaults)` - load from local storage
|
|
191
|
+
- `this.data.revert()` - undo changes (Data only, not SimpleData)
|
|
192
|
+
- `this.data.link(otherData, 'prop')` - two-way sync with another data source
|
|
92
193
|
|
|
93
194
|
### Lifecycle Execution Order
|
|
94
195
|
1. `onCreated()` - component instance created, props available, NO DOM yet
|
|
95
196
|
2. `beforeSetup()` - before render, good for computed props
|
|
96
|
-
3. `
|
|
97
|
-
4. `
|
|
98
|
-
5. `
|
|
99
|
-
6. `
|
|
197
|
+
3. `setData()` - initialize reactive data (runs automatically)
|
|
198
|
+
4. `setupStates()` - define state properties (runs automatically)
|
|
199
|
+
5. `setupStateTarget()` - connect to global state (runs automatically if defined)
|
|
200
|
+
6. `render()` - return layout object (called automatically, NEVER call manually)
|
|
201
|
+
7. `afterSetup()` - DOM created but not in document, `this.panel` available
|
|
202
|
+
8. `afterRender()` - alias for afterSetup
|
|
203
|
+
9. `afterLayout()` - DOM in document, safe for measurements/animations
|
|
204
|
+
10. `beforeDestroy()` - cleanup before removal
|
|
205
|
+
11. `onDestroyed()` - final cleanup after removal
|
|
206
|
+
|
|
207
|
+
### Atoms (Reusable Layouts)
|
|
208
|
+
- Create with `Atom((props, children) => layoutObject)`:
|
|
209
|
+
```javascript
|
|
210
|
+
const Button = Atom((props, children) => ({
|
|
211
|
+
tag: 'button',
|
|
212
|
+
type: 'button',
|
|
213
|
+
...props,
|
|
214
|
+
children
|
|
215
|
+
}));
|
|
216
|
+
```
|
|
217
|
+
- Call without `new`: `Button({ class: 'primary', click: handler }, 'Click Me')`
|
|
218
|
+
- Support flexible argument order: `Button('text')`, `Button({ class: 'btn' })`, `Button({ class: 'btn' }, 'text')`
|
|
219
|
+
- **DON'T** use `new` with Atoms - they are functions, not classes
|
|
220
|
+
- Atoms can merge props, children, and have watchers: `Button({ class: 'btn-[[size]]' }, 'Text')`
|
|
221
|
+
|
|
222
|
+
### Jot (Functional Components)
|
|
223
|
+
- Create lightweight components: `const MyJot = Jot({ render() { return { tag: 'div' }; } })`
|
|
224
|
+
- Returns a Component class: `new MyJot()` to instantiate
|
|
225
|
+
- Supports all component features: `setData()`, `setupStates()`, lifecycle hooks
|
|
226
|
+
- Auto-wrapped for non-Component objects in `Builder.render()`
|
|
227
|
+
- **DON'T** use `new Jot({})` - call the returned class: `const MyJot = Jot({}); new MyJot()`
|
|
228
|
+
|
|
229
|
+
### Pod (Stateful Functional Components)
|
|
230
|
+
- Like Jot but with built-in state setup:
|
|
231
|
+
```javascript
|
|
232
|
+
const Counter = Pod({
|
|
233
|
+
states: { count: 0 },
|
|
234
|
+
render() {
|
|
235
|
+
return { tag: 'div', text: 'Count: [[count]]' };
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
```
|
|
239
|
+
- Use `states` property to define state (equivalent to `setupStates()`)
|
|
240
|
+
- Instantiate: `new Counter()`
|
|
241
|
+
|
|
242
|
+
### Persistence & Component Reuse
|
|
243
|
+
- Parent `persist: true` keeps child component instances alive during re-renders
|
|
244
|
+
- Child can opt-out: `persist: false`
|
|
245
|
+
- **WARNING**: Data initialized in `beforeSetup` can cause issues with persistence
|
|
246
|
+
|
|
247
|
+
## HTTP Requests (Ajax Module)
|
|
248
|
+
- **Shorthand methods** (recommended for simple requests):
|
|
249
|
+
```javascript
|
|
250
|
+
import { Ajax } from '@base-framework/base';
|
|
251
|
+
|
|
252
|
+
// GET request
|
|
253
|
+
Ajax.get('/api/users').then(data => console.log(data));
|
|
254
|
+
|
|
255
|
+
// POST request
|
|
256
|
+
Ajax.post('/api/users', { name: 'John' }).then(data => console.log(data));
|
|
257
|
+
|
|
258
|
+
// PUT request
|
|
259
|
+
Ajax.put('/api/users/123', { name: 'Jane' });
|
|
260
|
+
|
|
261
|
+
// DELETE request
|
|
262
|
+
Ajax.delete('/api/users/123');
|
|
263
|
+
|
|
264
|
+
// HEAD request
|
|
265
|
+
Ajax.head('/api/status');
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
- **Object syntax** (for advanced options):
|
|
269
|
+
```javascript
|
|
270
|
+
Ajax({
|
|
271
|
+
url: '/api/users',
|
|
272
|
+
method: 'POST',
|
|
273
|
+
data: { name: 'John' },
|
|
274
|
+
responseType: 'json', // 'json', 'text', 'blob', 'arraybuffer'
|
|
275
|
+
headers: { 'X-Custom': 'value' },
|
|
276
|
+
success: (data) => console.log('Success:', data),
|
|
277
|
+
error: (xhr) => console.error('Error:', xhr.status),
|
|
278
|
+
progress: (e) => console.log('Progress:', e.loaded / e.total)
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
- **Response types**:
|
|
283
|
+
- `'json'` (default) - auto-parse JSON response
|
|
284
|
+
- `'text'` - get response as string
|
|
285
|
+
- `'blob'` - for binary data (files, images)
|
|
286
|
+
- `'arraybuffer'` - for raw binary data
|
|
287
|
+
|
|
288
|
+
- **Global configuration**:
|
|
289
|
+
```javascript
|
|
290
|
+
// Add fixed params to all requests
|
|
291
|
+
Ajax.addFixedParams({ apiKey: 'abc123' });
|
|
292
|
+
|
|
293
|
+
// Pre-request hook
|
|
294
|
+
Ajax.beforeSend((xhr) => {
|
|
295
|
+
xhr.setRequestHeader('Authorization', 'Bearer token');
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// Default settings
|
|
299
|
+
Ajax.ajaxSettings({
|
|
300
|
+
baseURL: '/api',
|
|
301
|
+
timeout: 5000,
|
|
302
|
+
withCredentials: true
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
- **In components**:
|
|
307
|
+
```javascript
|
|
308
|
+
class UserList extends Component {
|
|
309
|
+
onCreated() {
|
|
310
|
+
Ajax.get('/api/users').then(users => {
|
|
311
|
+
this.data.set('users', users);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## Dynamic Module Loading (Import)
|
|
318
|
+
- **Basic usage** (lazy load components/modules):
|
|
319
|
+
```javascript
|
|
320
|
+
import { Import } from '@base-framework/base';
|
|
321
|
+
|
|
322
|
+
// In layouts (function form - works with bundlers)
|
|
323
|
+
{ children: [Import(() => import('./components/heavy.js'))] }
|
|
324
|
+
|
|
325
|
+
// String path (for non-bundler scenarios)
|
|
326
|
+
{ children: [Import('./components/simple.js')] }
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
- **With dependencies** (load CSS/JS before module):
|
|
330
|
+
```javascript
|
|
331
|
+
Import({
|
|
332
|
+
src: () => import('./components/Chart.js'),
|
|
333
|
+
depends: [
|
|
334
|
+
'./styles/chart.css',
|
|
335
|
+
'./vendor/chart-lib.js'
|
|
336
|
+
],
|
|
337
|
+
callback: (module) => console.log('Chart loaded:', module)
|
|
338
|
+
});
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
- **Route-based lazy loading**:
|
|
342
|
+
```javascript
|
|
343
|
+
{
|
|
344
|
+
switch: [
|
|
345
|
+
{ uri: '/dashboard', component: Import(() => import('./pages/Dashboard.js')) },
|
|
346
|
+
{ uri: '/profile', component: Import(() => import('./pages/Profile.js')) },
|
|
347
|
+
{ uri: '/settings', component: Import(() => import('./pages/Settings.js')) }
|
|
348
|
+
]
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
- **Persistent modules** (keep loaded after parent destroyed):
|
|
353
|
+
```javascript
|
|
354
|
+
Import({
|
|
355
|
+
src: () => import('./services/Analytics.js'),
|
|
356
|
+
persist: true // Stays loaded globally
|
|
357
|
+
});
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
- **CRITICAL**: Always use function form `() => import()` for bundler support (Vite, Webpack)
|
|
361
|
+
- **DON'T** use string paths with bundlers: `Import('./file.js')` won't code-split
|
|
362
|
+
- **DO** use function form: `Import(() => import('./file.js'))` enables code-splitting
|
|
100
363
|
|
|
101
|
-
### Persistence
|
|
364
|
+
### Persistence & Component Reuse
|
|
102
365
|
- Parent `persist: true` keeps child component instances alive during re-renders
|
|
103
366
|
- Child can opt-out: `persist: false`
|
|
104
367
|
- **WARNING**: Data initialized in `beforeSetup` can cause issues with persistence
|
|
105
368
|
|
|
106
|
-
###
|
|
107
|
-
|
|
108
|
-
|
|
369
|
+
### Component Helper Methods (Available in all Components)
|
|
370
|
+
```javascript
|
|
371
|
+
class MyComponent extends Component {
|
|
372
|
+
someMethod() {
|
|
373
|
+
// Conditional rendering helper
|
|
374
|
+
this.if(this.state.show, { tag: 'div', text: 'Visible' });
|
|
375
|
+
|
|
376
|
+
// Array mapping helper
|
|
377
|
+
const items = this.map([1, 2, 3], (num) => ({ tag: 'span', text: num }));
|
|
378
|
+
|
|
379
|
+
// Get stable DOM ID
|
|
380
|
+
const id = this.getId('element-name'); // 'component-123-element-name'
|
|
109
381
|
|
|
110
|
-
|
|
382
|
+
// Prop validation (optional)
|
|
383
|
+
this.declareProps({
|
|
384
|
+
title: { type: 'string', required: true },
|
|
385
|
+
count: { type: 'number', default: 0 }
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Lifecycle Execution Order
|
|
392
|
+
1. `onCreated()` - component instance created, props available, NO DOM yet
|
|
393
|
+
2. `beforeSetup()` - before render, good for computed props
|
|
394
|
+
3. `setData()` - initialize reactive data (runs automatically)
|
|
395
|
+
4. `setupStates()` - define state properties (runs automatically)
|
|
396
|
+
5. `setupStateTarget()` - connect to global state (runs automatically if defined)
|
|
397
|
+
6. `render()` - return layout object (called automatically, NEVER call manually)
|
|
398
|
+
7. `afterSetup()` - DOM created but not in document, `this.panel` available
|
|
399
|
+
8. `afterRender()` - alias for afterSetup
|
|
400
|
+
9. `afterLayout()` - DOM in document, safe for measurements/animations
|
|
401
|
+
10. `beforeDestroy()` - cleanup before removal
|
|
402
|
+
11. `onDestroyed()` - final cleanup after removal
|
|
403
|
+
|
|
404
|
+
### Atoms (Reusable Layouts)
|
|
405
|
+
- Create with `Atom((props, children) => layoutObject)`:
|
|
406
|
+
```javascript
|
|
407
|
+
const Button = Atom((props, children) => ({
|
|
408
|
+
tag: 'button',
|
|
409
|
+
type: 'button',
|
|
410
|
+
...props,
|
|
411
|
+
children
|
|
412
|
+
}));
|
|
413
|
+
```
|
|
414
|
+
- Call without `new`: `Button({ class: 'primary', click: handler }, 'Click Me')`
|
|
415
|
+
- Support flexible argument order: `Button('text')`, `Button({ class: 'btn' })`, `Button({ class: 'btn' }, 'text')`
|
|
416
|
+
- **DON'T** use `new` with Atoms - they are functions, not classes
|
|
417
|
+
- Atoms can merge props, children, and have watchers: `Button({ class: 'btn-[[size]]' }, 'Text')`
|
|
418
|
+
|
|
419
|
+
### Jot (Functional Components)
|
|
420
|
+
- Create lightweight components: `const MyJot = Jot({ render() { return { tag: 'div' }; } })`
|
|
421
|
+
- Returns a Component class: `new MyJot()` to instantiate
|
|
422
|
+
- Supports all component features: `setData()`, `setupStates()`, lifecycle hooks
|
|
423
|
+
- Auto-wrapped for non-Component objects in `Builder.render()`
|
|
424
|
+
- **DON'T** use `new Jot({})` - call the returned class: `const MyJot = Jot({}); new MyJot()`
|
|
425
|
+
|
|
426
|
+
### Pod (Stateful Functional Components)
|
|
427
|
+
- Like Jot but with built-in state setup:
|
|
428
|
+
```javascript
|
|
429
|
+
const Counter = Pod({
|
|
430
|
+
states: { count: 0 },
|
|
431
|
+
render() {
|
|
432
|
+
return { tag: 'div', text: 'Count: [[count]]' };
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
- Use `states` property to define state (equivalent to `setupStates()`)
|
|
437
|
+
- Instantiate: `new Counter()`
|
|
111
438
|
- **State helpers** (only work on keys defined in `setupStates()`):
|
|
112
439
|
- `this.state.set('key', value)` - set any value
|
|
113
440
|
- `this.state.toggle('key')` - flip boolean
|