jsgui3-server 0.0.138 → 0.0.140
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/AGENTS.md +87 -0
- package/README.md +12 -0
- package/docs/GUIDE_TO_AGENTIC_WORKFLOWS_BY_GROK.md +19 -0
- package/docs/advanced-usage-examples.md +1360 -0
- package/docs/agent-development-guide.md +386 -0
- package/docs/api-reference.md +916 -0
- package/docs/broken-functionality-tracker.md +285 -0
- package/docs/bundling-system-deep-dive.md +525 -0
- package/docs/cli-reference.md +393 -0
- package/docs/comprehensive-documentation.md +1403 -0
- package/docs/configuration-reference.md +808 -0
- package/docs/controls-development.md +859 -0
- package/docs/documentation-review/CURRENT_REVIEW.md +95 -0
- package/docs/function-publishers-json-apis.md +847 -0
- package/docs/getting-started-with-json.md +518 -0
- package/docs/minification-compression-sourcemaps-status.md +482 -0
- package/docs/minification-compression-sourcemaps-test-results.md +205 -0
- package/docs/publishers-guide.md +313 -0
- package/docs/resources-guide.md +615 -0
- package/docs/serve-helpers.md +406 -0
- package/docs/simple-server-api-design.md +13 -0
- package/docs/system-architecture.md +275 -0
- package/docs/troubleshooting.md +698 -0
- package/examples/json/README.md +115 -0
- package/examples/json/basic-api/README.md +345 -0
- package/examples/json/basic-api/server.js +199 -0
- package/examples/json/simple-api/README.md +125 -0
- package/examples/json/simple-api/diagnostic-report.json +73 -0
- package/examples/json/simple-api/diagnostic-test.js +433 -0
- package/examples/json/simple-api/server-debug.md +58 -0
- package/examples/json/simple-api/server.js +91 -0
- package/examples/json/simple-api/test.js +215 -0
- package/http/responders/static/Static_Route_HTTP_Responder.js +1 -2
- package/package.json +19 -8
- package/publishers/helpers/assigners/static-compressed-response-buffers/Single_Control_Webpage_Server_Static_Compressed_Response_Buffers_Assigner.js +65 -12
- package/publishers/helpers/preparers/static/bundle/Static_Routes_Responses_Webpage_Bundle_Preparer.js +6 -1
- package/publishers/http-function-publisher.js +59 -38
- package/publishers/http-webpage-publisher.js +48 -1
- package/resources/processors/bundlers/js/esbuild/Advanced_JS_Bundler_Using_ESBuild.js +38 -146
- package/resources/processors/bundlers/js/esbuild/Core_JS_Non_Minifying_Bundler_Using_ESBuild.js +54 -5
- package/resources/processors/bundlers/js/esbuild/Core_JS_Single_File_Minifying_Bundler_Using_ESBuild.js +36 -4
- package/serve-factory.js +36 -9
- package/server.js +10 -4
- package/test-report.json +0 -0
- package/tests/README.md +250 -0
- package/tests/assigners.test.js +316 -0
- package/tests/bundlers.test.js +329 -0
- package/tests/configuration-validation.test.js +530 -0
- package/tests/content-analysis.test.js +641 -0
- package/tests/end-to-end.test.js +496 -0
- package/tests/error-handling.test.js +746 -0
- package/tests/performance.test.js +653 -0
- package/tests/publishers.test.js +395 -0
- package/tests/temp_invalid.js +7 -0
- package/tests/temp_invalid_utf8.js +1 -0
- package/tests/temp_malformed.js +10 -0
- package/tests/test-runner.js +261 -0
|
@@ -0,0 +1,859 @@
|
|
|
1
|
+
# Controls Development Guide
|
|
2
|
+
|
|
3
|
+
## When to Read
|
|
4
|
+
|
|
5
|
+
This document explains how to develop custom controls for JSGUI3 Server. Read this when:
|
|
6
|
+
- You want to create custom UI components
|
|
7
|
+
- You need to understand the control lifecycle and patterns
|
|
8
|
+
- You're extending existing controls or creating new ones
|
|
9
|
+
- You want to understand data binding and event handling
|
|
10
|
+
- You're working on client-side JavaScript for JSGUI3 applications
|
|
11
|
+
|
|
12
|
+
**Note:** For using existing controls, see [README.md](../README.md). For system architecture, see [docs/system-architecture.md](docs/system-architecture.md).
|
|
13
|
+
|
|
14
|
+
## Overview
|
|
15
|
+
|
|
16
|
+
Controls are the fundamental UI building blocks in JSGUI3. They represent reusable components that can be composed together to create complex user interfaces. Controls handle their own rendering, event management, and data binding.
|
|
17
|
+
|
|
18
|
+
## Control Hierarchy
|
|
19
|
+
|
|
20
|
+
### Base Classes
|
|
21
|
+
|
|
22
|
+
#### Active_HTML_Document
|
|
23
|
+
|
|
24
|
+
**Purpose:** The root class for all UI controls that render as complete HTML documents.
|
|
25
|
+
|
|
26
|
+
**Key Features:**
|
|
27
|
+
- Provides the main HTML document structure
|
|
28
|
+
- Manages CSS and JavaScript bundling
|
|
29
|
+
- Handles client-server communication
|
|
30
|
+
- Provides context for child controls
|
|
31
|
+
|
|
32
|
+
**Basic Structure:**
|
|
33
|
+
```javascript
|
|
34
|
+
const jsgui = require('jsgui3-client');
|
|
35
|
+
const { controls, Control, Data_Object, field } = jsgui;
|
|
36
|
+
const Active_HTML_Document = require('jsgui3-server/controls/Active_HTML_Document');
|
|
37
|
+
|
|
38
|
+
class My_Custom_Control extends Active_HTML_Document {
|
|
39
|
+
constructor(spec = {}) {
|
|
40
|
+
spec.__type_name = spec.__type_name || 'my_custom_control';
|
|
41
|
+
super(spec);
|
|
42
|
+
const { context } = this;
|
|
43
|
+
|
|
44
|
+
// Defensive CSS class application
|
|
45
|
+
if (typeof this.body.add_class === 'function') {
|
|
46
|
+
this.body.add_class('my-custom-control');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Control composition
|
|
50
|
+
const compose = () => {
|
|
51
|
+
// Create and add child controls here
|
|
52
|
+
const button = new controls.Button({
|
|
53
|
+
context,
|
|
54
|
+
text: 'Click Me'
|
|
55
|
+
});
|
|
56
|
+
this.body.add(button);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Conditional composition (only if not attached to existing DOM)
|
|
60
|
+
if (!spec.el) { compose(); }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
activate() {
|
|
64
|
+
if (!this.__active) {
|
|
65
|
+
super.activate();
|
|
66
|
+
const { context } = this;
|
|
67
|
+
|
|
68
|
+
// Event handlers and activation logic
|
|
69
|
+
context.on('window-resize', e_resize => {
|
|
70
|
+
// Handle resize events
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Static CSS definition
|
|
77
|
+
My_Custom_Control.css = `
|
|
78
|
+
* { margin: 0; padding: 0; }
|
|
79
|
+
body {
|
|
80
|
+
overflow-x: hidden;
|
|
81
|
+
overflow-y: hidden;
|
|
82
|
+
background-color: #E0E0E0;
|
|
83
|
+
}
|
|
84
|
+
.my-custom-control {
|
|
85
|
+
padding: 20px;
|
|
86
|
+
background: #f0f0f0;
|
|
87
|
+
}
|
|
88
|
+
`;
|
|
89
|
+
|
|
90
|
+
// Register control globally
|
|
91
|
+
controls.My_Custom_Control = My_Custom_Control;
|
|
92
|
+
module.exports = jsgui;
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### Control (Base)
|
|
96
|
+
|
|
97
|
+
**Purpose:** The fundamental building block for all UI components.
|
|
98
|
+
|
|
99
|
+
**Key Features:**
|
|
100
|
+
- Basic control lifecycle management
|
|
101
|
+
- Property system
|
|
102
|
+
- Event handling
|
|
103
|
+
- DOM manipulation
|
|
104
|
+
|
|
105
|
+
### Specialized Controls
|
|
106
|
+
|
|
107
|
+
#### Window
|
|
108
|
+
|
|
109
|
+
**Purpose:** Draggable, resizable container control.
|
|
110
|
+
|
|
111
|
+
**Features:**
|
|
112
|
+
- Title bar with drag functionality
|
|
113
|
+
- Resizable borders
|
|
114
|
+
- Child control containment
|
|
115
|
+
- Positioning and sizing
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
const window = new controls.Window({
|
|
119
|
+
context,
|
|
120
|
+
title: 'My Window',
|
|
121
|
+
pos: [100, 100], // [x, y] position
|
|
122
|
+
size: [400, 300] // [width, height]
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Add content to window
|
|
126
|
+
window.inner.add(childControl);
|
|
127
|
+
this.body.add(window);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### Panel
|
|
131
|
+
|
|
132
|
+
**Purpose:** Basic container for grouping controls.
|
|
133
|
+
|
|
134
|
+
**Features:**
|
|
135
|
+
- Simple containment
|
|
136
|
+
- Background styling
|
|
137
|
+
- Border and padding options
|
|
138
|
+
|
|
139
|
+
#### Button
|
|
140
|
+
|
|
141
|
+
**Purpose:** Interactive button control.
|
|
142
|
+
|
|
143
|
+
**Features:**
|
|
144
|
+
- Click event handling
|
|
145
|
+
- Text and styling customization
|
|
146
|
+
- Disabled state support
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
const button = new controls.Button({
|
|
150
|
+
context,
|
|
151
|
+
text: 'Click Me',
|
|
152
|
+
onclick: () => {
|
|
153
|
+
console.log('Button clicked!');
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
#### Text_Input
|
|
159
|
+
|
|
160
|
+
**Purpose:** Text input field control.
|
|
161
|
+
|
|
162
|
+
**Features:**
|
|
163
|
+
- Data binding support
|
|
164
|
+
- Validation
|
|
165
|
+
- Placeholder text
|
|
166
|
+
- Input event handling
|
|
167
|
+
|
|
168
|
+
```javascript
|
|
169
|
+
const input = new controls.Text_Input({
|
|
170
|
+
context,
|
|
171
|
+
placeholder: 'Enter text...',
|
|
172
|
+
data: { model: sharedDataModel, field_name: 'username' }
|
|
173
|
+
});
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Control Lifecycle
|
|
177
|
+
|
|
178
|
+
### 1. Construction
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
constructor(spec = {}) {
|
|
182
|
+
// Set type name for debugging
|
|
183
|
+
spec.__type_name = spec.__type_name || 'control_name';
|
|
184
|
+
|
|
185
|
+
// Call parent constructor
|
|
186
|
+
super(spec);
|
|
187
|
+
|
|
188
|
+
// Extract context (always available)
|
|
189
|
+
const { context } = this;
|
|
190
|
+
|
|
191
|
+
// Initialize control properties
|
|
192
|
+
// Compose UI (if not attached to existing DOM)
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### 2. Composition
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
const compose = () => {
|
|
200
|
+
// Create child controls
|
|
201
|
+
const child = new controls.SomeControl({ context });
|
|
202
|
+
|
|
203
|
+
// Configure child properties
|
|
204
|
+
child.text = 'Hello';
|
|
205
|
+
|
|
206
|
+
// Add to parent
|
|
207
|
+
this.body.add(child);
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// Only compose if not attached to existing element
|
|
211
|
+
if (!spec.el) { compose(); }
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### 3. Activation
|
|
215
|
+
|
|
216
|
+
```javascript
|
|
217
|
+
activate() {
|
|
218
|
+
// Prevent double activation
|
|
219
|
+
if (!this.__active) {
|
|
220
|
+
// Call parent activation FIRST
|
|
221
|
+
super.activate();
|
|
222
|
+
|
|
223
|
+
// Extract context again (convention)
|
|
224
|
+
const { context } = this;
|
|
225
|
+
|
|
226
|
+
// Register event handlers
|
|
227
|
+
context.on('event-name', handler);
|
|
228
|
+
|
|
229
|
+
// Initialize data bindings
|
|
230
|
+
// Start timers or intervals
|
|
231
|
+
// Establish connections
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### 4. Deactivation/Cleanup
|
|
237
|
+
|
|
238
|
+
```javascript
|
|
239
|
+
deactivate() {
|
|
240
|
+
// Remove event handlers
|
|
241
|
+
// Close connections
|
|
242
|
+
// Clear timers
|
|
243
|
+
// Release resources
|
|
244
|
+
|
|
245
|
+
super.deactivate();
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Data Binding
|
|
250
|
+
|
|
251
|
+
### Data_Object and Fields
|
|
252
|
+
|
|
253
|
+
**Purpose:** Reactive data models that automatically update bound controls.
|
|
254
|
+
|
|
255
|
+
```javascript
|
|
256
|
+
// Create data model
|
|
257
|
+
this.data = { model: new Data_Object({ context }) };
|
|
258
|
+
|
|
259
|
+
// Add reactive fields
|
|
260
|
+
field(this.data.model, 'name');
|
|
261
|
+
field(this.data.model, 'email');
|
|
262
|
+
field(this.data.model, 'age');
|
|
263
|
+
|
|
264
|
+
// Register with context for lifecycle management
|
|
265
|
+
context.register_control(this.data.model);
|
|
266
|
+
|
|
267
|
+
// Changes trigger UI updates automatically
|
|
268
|
+
this.data.model.name = 'John Doe';
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Control Data Binding
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
const input = new controls.Text_Input({
|
|
275
|
+
context,
|
|
276
|
+
label: { text: 'Name:' },
|
|
277
|
+
data: {
|
|
278
|
+
model: this.data.model,
|
|
279
|
+
field_name: 'name'
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// Control automatically updates when model changes
|
|
284
|
+
// Model automatically updates when control changes
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### Shared Data Models
|
|
288
|
+
|
|
289
|
+
```javascript
|
|
290
|
+
// Multiple controls can share the same model
|
|
291
|
+
const model = new Data_Object({ context });
|
|
292
|
+
field(model, 'value');
|
|
293
|
+
|
|
294
|
+
const picker1 = new Date_Picker({
|
|
295
|
+
context,
|
|
296
|
+
data: { model: model }
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const picker2 = new Date_Picker({
|
|
300
|
+
context,
|
|
301
|
+
data: { model: model }
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
// Both pickers stay synchronized
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
## Event Handling
|
|
308
|
+
|
|
309
|
+
### Context Events
|
|
310
|
+
|
|
311
|
+
```javascript
|
|
312
|
+
activate() {
|
|
313
|
+
if (!this.__active) {
|
|
314
|
+
super.activate();
|
|
315
|
+
const { context } = this;
|
|
316
|
+
|
|
317
|
+
// Browser events
|
|
318
|
+
context.on('window-resize', e_resize => {
|
|
319
|
+
// Handle window resize
|
|
320
|
+
this.resize_content();
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// Custom application events
|
|
324
|
+
context.on('data-updated', data => {
|
|
325
|
+
// Handle data updates
|
|
326
|
+
this.refresh_display();
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Control Events
|
|
333
|
+
|
|
334
|
+
```javascript
|
|
335
|
+
const button = new controls.Button({
|
|
336
|
+
context,
|
|
337
|
+
text: 'Save',
|
|
338
|
+
onclick: () => {
|
|
339
|
+
// Handle button click
|
|
340
|
+
this.save_data();
|
|
341
|
+
}
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Custom Events
|
|
346
|
+
|
|
347
|
+
```javascript
|
|
348
|
+
// Emit custom events
|
|
349
|
+
this.raise('custom-event', { data: 'value' });
|
|
350
|
+
|
|
351
|
+
// Listen for custom events
|
|
352
|
+
context.on('custom-event', (data) => {
|
|
353
|
+
console.log('Received:', data);
|
|
354
|
+
});
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## CSS and Styling
|
|
358
|
+
|
|
359
|
+
### Static CSS Definition
|
|
360
|
+
|
|
361
|
+
```javascript
|
|
362
|
+
MyControl.css = `
|
|
363
|
+
/* Global resets (common pattern) */
|
|
364
|
+
* {
|
|
365
|
+
margin: 0;
|
|
366
|
+
padding: 0;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/* Body styling */
|
|
370
|
+
body {
|
|
371
|
+
overflow-x: hidden;
|
|
372
|
+
overflow-y: hidden;
|
|
373
|
+
background-color: #E0E0E0;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/* Control-specific styles */
|
|
377
|
+
.my-control {
|
|
378
|
+
padding: 20px;
|
|
379
|
+
background: linear-gradient(45deg, #f0f0f0, #e0e0e0);
|
|
380
|
+
border-radius: 8px;
|
|
381
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/* Responsive design */
|
|
385
|
+
@media (max-width: 768px) {
|
|
386
|
+
.my-control {
|
|
387
|
+
padding: 10px;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
`;
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Dynamic Styling
|
|
394
|
+
|
|
395
|
+
```javascript
|
|
396
|
+
// Add CSS classes
|
|
397
|
+
if (typeof this.body.add_class === 'function') {
|
|
398
|
+
this.body.add_class('my-control');
|
|
399
|
+
this.body.add_class('theme-dark');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Remove classes
|
|
403
|
+
this.body.remove_class('theme-dark');
|
|
404
|
+
|
|
405
|
+
// Check for classes
|
|
406
|
+
if (this.body.has_class('active')) {
|
|
407
|
+
// Handle active state
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### CSS Extraction and Bundling
|
|
412
|
+
|
|
413
|
+
CSS is automatically extracted from control classes and bundled by the server:
|
|
414
|
+
|
|
415
|
+
1. Server scans all control classes for `.css` properties
|
|
416
|
+
2. CSS is concatenated in dependency order
|
|
417
|
+
3. Result is served as a single stylesheet
|
|
418
|
+
4. Updates require server restart (no hot reload)
|
|
419
|
+
|
|
420
|
+
## Control Composition Patterns
|
|
421
|
+
|
|
422
|
+
### Container Pattern
|
|
423
|
+
|
|
424
|
+
```javascript
|
|
425
|
+
class Dashboard extends Active_HTML_Document {
|
|
426
|
+
constructor(spec = {}) {
|
|
427
|
+
super(spec);
|
|
428
|
+
const { context } = this;
|
|
429
|
+
|
|
430
|
+
if (typeof this.body.add_class === 'function') {
|
|
431
|
+
this.body.add_class('dashboard');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const compose = () => {
|
|
435
|
+
// Header section
|
|
436
|
+
const header = new controls.Panel({
|
|
437
|
+
context,
|
|
438
|
+
css_class: 'dashboard-header'
|
|
439
|
+
});
|
|
440
|
+
header.add(new controls.Label({
|
|
441
|
+
context,
|
|
442
|
+
text: 'Dashboard'
|
|
443
|
+
}));
|
|
444
|
+
|
|
445
|
+
// Content grid
|
|
446
|
+
const grid = new controls.Grid({
|
|
447
|
+
context,
|
|
448
|
+
rows: 2,
|
|
449
|
+
cols: 3
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// Add widgets to grid
|
|
453
|
+
grid.add(new StatsWidget({ context }), 0, 0);
|
|
454
|
+
grid.add(new ChartWidget({ context }), 0, 1);
|
|
455
|
+
grid.add(new ListWidget({ context }), 1, 0);
|
|
456
|
+
|
|
457
|
+
this.body.add(header);
|
|
458
|
+
this.body.add(grid);
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
if (!spec.el) { compose(); }
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### Mixin Pattern
|
|
467
|
+
|
|
468
|
+
```javascript
|
|
469
|
+
// Using mixins for reusable functionality
|
|
470
|
+
const { dragable } = mixins;
|
|
471
|
+
|
|
472
|
+
class DraggablePanel extends Control {
|
|
473
|
+
constructor(spec = {}) {
|
|
474
|
+
super(spec);
|
|
475
|
+
|
|
476
|
+
// Apply draggable mixin
|
|
477
|
+
dragable(this, {
|
|
478
|
+
handle: '.drag-handle'
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### Factory Pattern
|
|
485
|
+
|
|
486
|
+
```javascript
|
|
487
|
+
class ControlFactory {
|
|
488
|
+
static createButton(context, config) {
|
|
489
|
+
return new controls.Button({
|
|
490
|
+
context,
|
|
491
|
+
text: config.text || 'Button',
|
|
492
|
+
onclick: config.onClick,
|
|
493
|
+
css_class: config.className
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
static createInput(context, config) {
|
|
498
|
+
return new controls.Text_Input({
|
|
499
|
+
context,
|
|
500
|
+
placeholder: config.placeholder,
|
|
501
|
+
data: config.data,
|
|
502
|
+
validator: config.validator
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Usage
|
|
508
|
+
const saveButton = ControlFactory.createButton(context, {
|
|
509
|
+
text: 'Save',
|
|
510
|
+
onClick: () => this.save()
|
|
511
|
+
});
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
## Error Handling and Validation
|
|
515
|
+
|
|
516
|
+
### Input Validation
|
|
517
|
+
|
|
518
|
+
```javascript
|
|
519
|
+
class ValidatedInput extends Control {
|
|
520
|
+
constructor(spec = {}) {
|
|
521
|
+
super(spec);
|
|
522
|
+
this.validator = spec.validator;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
set_value(value) {
|
|
526
|
+
if (this.validator && !this.validator(value)) {
|
|
527
|
+
this.show_error('Invalid input');
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
this.clear_error();
|
|
532
|
+
super.set_value(value);
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
show_error(message) {
|
|
537
|
+
this.add_class('error');
|
|
538
|
+
// Show error message
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
clear_error() {
|
|
542
|
+
this.remove_class('error');
|
|
543
|
+
// Hide error message
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### Exception Handling
|
|
549
|
+
|
|
550
|
+
```javascript
|
|
551
|
+
activate() {
|
|
552
|
+
if (!this.__active) {
|
|
553
|
+
try {
|
|
554
|
+
super.activate();
|
|
555
|
+
const { context } = this;
|
|
556
|
+
|
|
557
|
+
// Risky initialization
|
|
558
|
+
this.initialize_complex_feature();
|
|
559
|
+
|
|
560
|
+
} catch (error) {
|
|
561
|
+
console.error('Failed to activate control:', error);
|
|
562
|
+
// Fallback behavior
|
|
563
|
+
this.enter_safe_mode();
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
```
|
|
568
|
+
|
|
569
|
+
## Performance Optimization
|
|
570
|
+
|
|
571
|
+
### Lazy Loading
|
|
572
|
+
|
|
573
|
+
```javascript
|
|
574
|
+
class LazyControl extends Control {
|
|
575
|
+
constructor(spec = {}) {
|
|
576
|
+
super(spec);
|
|
577
|
+
this._loaded = false;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
ensure_loaded(callback) {
|
|
581
|
+
if (this._loaded) {
|
|
582
|
+
return callback();
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Load heavy resources
|
|
586
|
+
this.load_resources((err) => {
|
|
587
|
+
if (err) return callback(err);
|
|
588
|
+
this._loaded = true;
|
|
589
|
+
callback();
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
activate() {
|
|
594
|
+
if (!this.__active) {
|
|
595
|
+
super.activate();
|
|
596
|
+
|
|
597
|
+
// Lazy load when first activated
|
|
598
|
+
this.ensure_loaded(() => {
|
|
599
|
+
this.initialize_heavy_components();
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
### Memory Management
|
|
607
|
+
|
|
608
|
+
```javascript
|
|
609
|
+
class MemoryManagedControl extends Control {
|
|
610
|
+
constructor(spec = {}) {
|
|
611
|
+
super(spec);
|
|
612
|
+
this._intervals = [];
|
|
613
|
+
this._timeouts = [];
|
|
614
|
+
this._event_handlers = [];
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
set_interval(callback, delay) {
|
|
618
|
+
const id = setInterval(callback, delay);
|
|
619
|
+
this._intervals.push(id);
|
|
620
|
+
return id;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
set_timeout(callback, delay) {
|
|
624
|
+
const id = setTimeout(callback, delay);
|
|
625
|
+
this._timeouts.push(id);
|
|
626
|
+
return id;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
add_event_handler(handler_id) {
|
|
630
|
+
this._event_handlers.push(handler_id);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
deactivate() {
|
|
634
|
+
// Clean up all timers
|
|
635
|
+
this._intervals.forEach(clearInterval);
|
|
636
|
+
this._timeouts.forEach(clearTimeout);
|
|
637
|
+
|
|
638
|
+
// Remove event handlers
|
|
639
|
+
this._event_handlers.forEach(id => {
|
|
640
|
+
// Remove handler logic
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
super.deactivate();
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
## Testing Controls
|
|
649
|
+
|
|
650
|
+
### Unit Testing
|
|
651
|
+
|
|
652
|
+
```javascript
|
|
653
|
+
const jsgui = require('jsgui3-client');
|
|
654
|
+
|
|
655
|
+
describe('MyControl', () => {
|
|
656
|
+
let control;
|
|
657
|
+
let mock_context;
|
|
658
|
+
|
|
659
|
+
beforeEach(() => {
|
|
660
|
+
mock_context = {
|
|
661
|
+
on: jest.fn(),
|
|
662
|
+
register_control: jest.fn()
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
control = new MyControl({
|
|
666
|
+
context: mock_context
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
it('should initialize correctly', () => {
|
|
671
|
+
expect(control.__type_name).toBe('my_control');
|
|
672
|
+
expect(mock_context.register_control).toHaveBeenCalled();
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
it('should activate properly', () => {
|
|
676
|
+
control.activate();
|
|
677
|
+
expect(control.__active).toBe(true);
|
|
678
|
+
expect(mock_context.on).toHaveBeenCalledWith('window-resize', expect.any(Function));
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
### Integration Testing
|
|
684
|
+
|
|
685
|
+
```javascript
|
|
686
|
+
describe('Control Integration', () => {
|
|
687
|
+
it('should render in browser', async () => {
|
|
688
|
+
const server = await Server.serve({
|
|
689
|
+
ctrl: MyControl,
|
|
690
|
+
port: 0 // Random port
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
// Use puppeteer or similar to test rendering
|
|
694
|
+
const browser = await puppeteer.launch();
|
|
695
|
+
const page = await browser.newPage();
|
|
696
|
+
await page.goto(`http://localhost:${server.port}`);
|
|
697
|
+
|
|
698
|
+
// Test control appears and functions
|
|
699
|
+
const controlExists = await page.$('.my-control');
|
|
700
|
+
expect(controlExists).toBeTruthy();
|
|
701
|
+
|
|
702
|
+
await browser.close();
|
|
703
|
+
await server.close();
|
|
704
|
+
});
|
|
705
|
+
});
|
|
706
|
+
```
|
|
707
|
+
|
|
708
|
+
## Best Practices
|
|
709
|
+
|
|
710
|
+
### Code Organization
|
|
711
|
+
- Use PascalCase for control class names
|
|
712
|
+
- Use snake_case for properties and methods
|
|
713
|
+
- Group related functionality together
|
|
714
|
+
- Document complex logic with comments
|
|
715
|
+
|
|
716
|
+
### Performance
|
|
717
|
+
- Minimize DOM manipulations
|
|
718
|
+
- Use efficient data structures
|
|
719
|
+
- Implement lazy loading for heavy components
|
|
720
|
+
- Clean up resources properly
|
|
721
|
+
|
|
722
|
+
### Maintainability
|
|
723
|
+
- Follow established patterns
|
|
724
|
+
- Write comprehensive tests
|
|
725
|
+
- Document configuration options
|
|
726
|
+
- Use meaningful names
|
|
727
|
+
|
|
728
|
+
### User Experience
|
|
729
|
+
- Provide visual feedback for interactions
|
|
730
|
+
- Handle error states gracefully
|
|
731
|
+
- Support keyboard navigation
|
|
732
|
+
- Ensure responsive design
|
|
733
|
+
|
|
734
|
+
## Common Patterns and Anti-Patterns
|
|
735
|
+
|
|
736
|
+
### ✅ Good Patterns
|
|
737
|
+
|
|
738
|
+
**Consistent Constructor Pattern:**
|
|
739
|
+
```javascript
|
|
740
|
+
constructor(spec = {}) {
|
|
741
|
+
spec.__type_name = spec.__type_name || 'control_name';
|
|
742
|
+
super(spec);
|
|
743
|
+
const { context } = this;
|
|
744
|
+
// ... rest of initialization
|
|
745
|
+
}
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
**Defensive Programming:**
|
|
749
|
+
```javascript
|
|
750
|
+
if (typeof this.body.add_class === 'function') {
|
|
751
|
+
this.body.add_class('control-class');
|
|
752
|
+
}
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
**Proper Activation:**
|
|
756
|
+
```javascript
|
|
757
|
+
activate() {
|
|
758
|
+
if (!this.__active) {
|
|
759
|
+
super.activate(); // Always call first
|
|
760
|
+
const { context } = this;
|
|
761
|
+
// ... activation logic
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
### ❌ Anti-Patterns
|
|
767
|
+
|
|
768
|
+
**Direct DOM Manipulation (avoid when possible):**
|
|
769
|
+
```javascript
|
|
770
|
+
// Bad - bypasses framework
|
|
771
|
+
this.dom_element.style.color = 'red';
|
|
772
|
+
|
|
773
|
+
// Good - uses framework methods
|
|
774
|
+
this.add_class('error');
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
**Tight Coupling:**
|
|
778
|
+
```javascript
|
|
779
|
+
// Bad - assumes specific parent structure
|
|
780
|
+
this.parent.parent.update();
|
|
781
|
+
|
|
782
|
+
// Good - uses events or callbacks
|
|
783
|
+
this.raise('child-updated', { data: this.value });
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
**Memory Leaks:**
|
|
787
|
+
```javascript
|
|
788
|
+
// Bad - no cleanup
|
|
789
|
+
setInterval(() => { /* ... */ }, 1000);
|
|
790
|
+
|
|
791
|
+
// Good - tracked and cleaned up
|
|
792
|
+
this.set_interval(() => { /* ... */ }, 1000);
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
## Migration and Compatibility
|
|
796
|
+
|
|
797
|
+
### Upgrading Controls
|
|
798
|
+
|
|
799
|
+
When modifying existing controls:
|
|
800
|
+
|
|
801
|
+
1. **Maintain API Compatibility:** Don't break existing usage
|
|
802
|
+
2. **Add Deprecation Warnings:** For changed APIs
|
|
803
|
+
3. **Provide Migration Guide:** Document upgrade steps
|
|
804
|
+
4. **Test Thoroughly:** Ensure no regressions
|
|
805
|
+
|
|
806
|
+
### Framework Updates
|
|
807
|
+
|
|
808
|
+
When JSGUI3 updates:
|
|
809
|
+
|
|
810
|
+
1. **Check Breaking Changes:** Review changelog
|
|
811
|
+
2. **Update Dependencies:** Keep jsgui3-client current
|
|
812
|
+
3. **Test Integration:** Verify control still works
|
|
813
|
+
4. **Update Patterns:** Adopt new best practices
|
|
814
|
+
|
|
815
|
+
## Troubleshooting
|
|
816
|
+
|
|
817
|
+
### Common Issues
|
|
818
|
+
|
|
819
|
+
**Control Not Rendering:**
|
|
820
|
+
- Check if `compose()` is called conditionally
|
|
821
|
+
- Verify CSS is properly defined
|
|
822
|
+
- Ensure control is added to a parent
|
|
823
|
+
|
|
824
|
+
**Events Not Firing:**
|
|
825
|
+
- Confirm activation is called
|
|
826
|
+
- Check event handler registration
|
|
827
|
+
- Verify context is available
|
|
828
|
+
|
|
829
|
+
**Data Binding Issues:**
|
|
830
|
+
- Ensure model is registered with context
|
|
831
|
+
- Check field names match
|
|
832
|
+
- Verify model changes trigger updates
|
|
833
|
+
|
|
834
|
+
**Performance Problems:**
|
|
835
|
+
- Check for unnecessary re-renders
|
|
836
|
+
- Profile event handler execution
|
|
837
|
+
- Monitor memory usage
|
|
838
|
+
|
|
839
|
+
### Debug Mode
|
|
840
|
+
|
|
841
|
+
Enable debug logging:
|
|
842
|
+
```javascript
|
|
843
|
+
const control = new MyControl({
|
|
844
|
+
context,
|
|
845
|
+
debug: true
|
|
846
|
+
});
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
### Development Tools
|
|
850
|
+
|
|
851
|
+
Use browser developer tools to:
|
|
852
|
+
- Inspect generated HTML/CSS
|
|
853
|
+
- Monitor JavaScript execution
|
|
854
|
+
- Check network requests
|
|
855
|
+
- Profile performance
|
|
856
|
+
|
|
857
|
+
---
|
|
858
|
+
|
|
859
|
+
This guide provides the foundation for developing controls in JSGUI3. For specific control implementations, refer to the examples in the `examples/` directory and the base classes in `controls/`.
|