@dooboostore/simple-web-component 1.0.38 → 1.0.40
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/LICENSE.md +7 -0
- package/README.md +88 -871
- package/dist/cjs/SwcAppEngine.js.map +2 -2
- package/dist/cjs/decorators/applyAttribute.js +194 -111
- package/dist/cjs/decorators/applyAttribute.js.map +2 -2
- package/dist/cjs/decorators/applyNode.js +82 -80
- package/dist/cjs/decorators/applyNode.js.map +2 -2
- package/dist/cjs/decorators/applyProperty.js +213 -0
- package/dist/cjs/decorators/applyProperty.js.map +7 -0
- package/dist/cjs/decorators/applySlot.js +93 -82
- package/dist/cjs/decorators/applySlot.js.map +3 -3
- package/dist/cjs/decorators/changedAttributeThis.js.map +1 -1
- package/dist/cjs/decorators/elementDefine.js +14 -34
- package/dist/cjs/decorators/elementDefine.js.map +2 -2
- package/dist/cjs/decorators/index.js +2 -2
- package/dist/cjs/decorators/index.js.map +2 -2
- package/dist/cjs/decorators/lifecycles.js +4 -4
- package/dist/cjs/decorators/lifecycles.js.map +2 -2
- package/dist/cjs/decorators/state.js +0 -1
- package/dist/cjs/decorators/state.js.map +2 -2
- package/dist/cjs/elements/SwcApp.js.map +2 -2
- package/dist/cjs/types/index.js.map +1 -1
- package/dist/esm/SwcAppEngine.js.map +2 -2
- package/dist/esm/decorators/applyAttribute.js +196 -113
- package/dist/esm/decorators/applyAttribute.js.map +2 -2
- package/dist/esm/decorators/applyNode.js +82 -80
- package/dist/esm/decorators/applyNode.js.map +2 -2
- package/dist/esm/decorators/applyProperty.js +194 -0
- package/dist/esm/decorators/applyProperty.js.map +7 -0
- package/dist/esm/decorators/applySlot.js +93 -82
- package/dist/esm/decorators/applySlot.js.map +3 -3
- package/dist/esm/decorators/changedAttributeThis.js.map +1 -1
- package/dist/esm/decorators/elementDefine.js +14 -34
- package/dist/esm/decorators/elementDefine.js.map +2 -2
- package/dist/esm/decorators/index.js +2 -2
- package/dist/esm/decorators/index.js.map +1 -1
- package/dist/esm/decorators/lifecycles.js +4 -4
- package/dist/esm/decorators/lifecycles.js.map +2 -2
- package/dist/esm/decorators/state.js +0 -1
- package/dist/esm/decorators/state.js.map +2 -2
- package/dist/esm/elements/SwcApp.js.map +2 -2
- package/dist/esm/types/index.js.map +1 -1
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js +525 -513
- package/dist/esm-bundle/dooboostore-simple-web-component.esm.js.map +4 -4
- package/dist/types/SwcAppEngine.d.ts.map +1 -1
- package/dist/types/decorators/applyAttribute.d.ts +56 -15
- package/dist/types/decorators/applyAttribute.d.ts.map +1 -1
- package/dist/types/decorators/applyNode.d.ts +28 -24
- package/dist/types/decorators/applyNode.d.ts.map +1 -1
- package/dist/types/decorators/applyProperty.d.ts +61 -0
- package/dist/types/decorators/applyProperty.d.ts.map +1 -0
- package/dist/types/decorators/applySlot.d.ts +17 -15
- package/dist/types/decorators/applySlot.d.ts.map +1 -1
- package/dist/types/decorators/elementDefine.d.ts.map +1 -1
- package/dist/types/decorators/index.d.ts +2 -2
- package/dist/types/decorators/index.d.ts.map +1 -1
- package/dist/types/decorators/lifecycles.d.ts +4 -4
- package/dist/types/decorators/lifecycles.d.ts.map +1 -1
- package/dist/types/decorators/state.d.ts.map +1 -1
- package/dist/types/elements/SwcApp.d.ts.map +1 -1
- package/dist/types/types/index.d.ts +1 -6
- package/dist/types/types/index.d.ts.map +1 -1
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js +525 -513
- package/dist/umd-bundle/dooboostore-simple-web-component.umd.js.map +4 -4
- package/package.json +1 -1
- package/src/SwcAppEngine.ts +1 -0
- package/src/decorators/applyAttribute.ts +311 -126
- package/src/decorators/applyNode.ts +108 -105
- package/src/decorators/applyProperty.ts +302 -0
- package/src/decorators/applySlot.ts +112 -94
- package/src/decorators/changedAttributeThis.ts +1 -1
- package/src/decorators/elementDefine.ts +44 -33
- package/src/decorators/index.ts +2 -3
- package/src/decorators/lifecycles.ts +6 -6
- package/src/decorators/state.ts +0 -1
- package/src/elements/SwcApp.ts +0 -1
- package/src/types/index.ts +7 -7
- package/dist/cjs/decorators/attribute.js +0 -187
- package/dist/cjs/decorators/attribute.js.map +0 -7
- package/dist/cjs/decorators/setProperty.js +0 -109
- package/dist/cjs/decorators/setProperty.js.map +0 -7
- package/dist/esm/decorators/attribute.js +0 -168
- package/dist/esm/decorators/attribute.js.map +0 -7
- package/dist/esm/decorators/setProperty.js +0 -90
- package/dist/esm/decorators/setProperty.js.map +0 -7
- package/dist/types/decorators/attribute.d.ts +0 -31
- package/dist/types/decorators/attribute.d.ts.map +0 -1
- package/dist/types/decorators/setProperty.d.ts +0 -17
- package/dist/types/decorators/setProperty.d.ts.map +0 -1
- package/src/decorators/attribute.ts +0 -223
- package/src/decorators/setProperty.ts +0 -112
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ Inject services into Web Components using the `@onInitialize` decorator.
|
|
|
30
30
|
class Dashboard extends HTMLElement {
|
|
31
31
|
@onInitialize
|
|
32
32
|
onconstructor(
|
|
33
|
-
@
|
|
33
|
+
@inject(UserService.SYMBOL) userService: UserService
|
|
34
34
|
) {
|
|
35
35
|
this.userService = userService;
|
|
36
36
|
}
|
|
@@ -39,84 +39,74 @@ class Dashboard extends HTMLElement {
|
|
|
39
39
|
|
|
40
40
|
### 3. **Declarative DOM Updates**
|
|
41
41
|
|
|
42
|
-
#### @
|
|
42
|
+
#### @onConnectedShadow
|
|
43
43
|
Render HTML automatically when component connects to DOM.
|
|
44
44
|
|
|
45
45
|
```typescript
|
|
46
|
-
@
|
|
46
|
+
@onConnectedShadow
|
|
47
47
|
render() {
|
|
48
48
|
return `<div>Hello, <span>${this.name}</span>!</div>`;
|
|
49
49
|
}
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
-
#### @
|
|
52
|
+
#### @innerHtmlLightNodeThis
|
|
53
53
|
Apply HTML to specific host element.
|
|
54
54
|
|
|
55
55
|
```typescript
|
|
56
|
-
@
|
|
56
|
+
@innerHtmlLightNodeThis
|
|
57
57
|
updateContent() {
|
|
58
58
|
return `<p>Updated content</p>`;
|
|
59
59
|
}
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
#### @
|
|
62
|
+
#### @replaceChildrenLightNodeThis
|
|
63
63
|
Replace child nodes with new content.
|
|
64
64
|
|
|
65
65
|
```typescript
|
|
66
|
-
@
|
|
66
|
+
@replaceChildrenLightNodeThis
|
|
67
67
|
replaceChildren(node: Node) {
|
|
68
68
|
return node;
|
|
69
69
|
}
|
|
70
|
+
```
|
|
70
71
|
### 3.5 **Slot Management with @applySlot**
|
|
71
72
|
|
|
72
73
|
Manage dynamic content insertion into named slots using the `@applySlot` decorator. Slots are placeholders in templates that can be updated dynamically.
|
|
73
74
|
|
|
74
75
|
```typescript
|
|
76
|
+
import {clearSlot} from "./applySlot";
|
|
77
|
+
|
|
75
78
|
@elementDefine('content-manager')
|
|
76
79
|
class ContentManager extends HTMLElement {
|
|
77
|
-
// Property: Get NodeSlot instance for manual slot manipulation
|
|
78
|
-
@applySlot('main-content')
|
|
79
|
-
mainSlot?: NodeSlot;
|
|
80
80
|
|
|
81
|
-
@applySlot('sidebar')
|
|
82
|
-
sidebarSlot?: NodeSlot;
|
|
83
81
|
|
|
84
82
|
// Method: Replace children in slot with HTML
|
|
85
83
|
@addEventListener('.update-btn', 'click')
|
|
86
|
-
@
|
|
84
|
+
@replaceChildrenSlot('main-content')
|
|
87
85
|
updateContent() {
|
|
88
86
|
return '<div>Updated content here</div>';
|
|
89
87
|
}
|
|
90
88
|
|
|
91
89
|
// Method: Append HTML to slot
|
|
92
|
-
@
|
|
90
|
+
@appendHtmlSlot('main-content')
|
|
93
91
|
addMoreContent() {
|
|
94
92
|
return '<p>Additional content</p>';
|
|
95
93
|
}
|
|
96
94
|
|
|
97
95
|
// Method: Append text to slot
|
|
98
|
-
@
|
|
96
|
+
@appendTextSlot('sidebar')
|
|
99
97
|
addSidebarText() {
|
|
100
98
|
return 'Sidebar text: ' + new Date().toISOString();
|
|
101
99
|
}
|
|
102
100
|
|
|
103
101
|
// Method: Clear slot content
|
|
104
102
|
@addEventListener('.clear-btn', 'click')
|
|
105
|
-
@
|
|
103
|
+
@clearSlot('main-content')
|
|
106
104
|
clearContent() {
|
|
107
105
|
return true;
|
|
108
106
|
}
|
|
109
107
|
|
|
110
|
-
// Lifecycle: Manual slot manipulation
|
|
111
|
-
@onConnectedAfter
|
|
112
|
-
setupSlots() {
|
|
113
|
-
// Access slot via property for manual manipulation
|
|
114
|
-
if (this.mainSlot) {
|
|
115
|
-
this.mainSlot.appendText('main-content', 'Initial text');
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
108
|
|
|
119
|
-
@
|
|
109
|
+
@onConnectedLight
|
|
120
110
|
render() {
|
|
121
111
|
return `
|
|
122
112
|
<div>
|
|
@@ -140,39 +130,33 @@ class ContentManager extends HTMLElement {
|
|
|
140
130
|
- `<!--[[ slot-id ]]-->` - Named slot placeholder (HTML comment syntax)
|
|
141
131
|
|
|
142
132
|
**@applySlot Decorator Variants:**
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
133
|
+
- clearSlot
|
|
134
|
+
- prependHtmlSlot
|
|
135
|
+
- prependSlot
|
|
136
|
+
- prependTextSlot
|
|
137
|
+
- appendHtmlSlot
|
|
138
|
+
- appendTextSlot
|
|
139
|
+
- appendSlot
|
|
140
|
+
- replaceChildrenHtmlSlot
|
|
141
|
+
- replaceChildrenSlot
|
|
142
|
+
- replaceChildrenTextSlot
|
|
143
|
+
|
|
151
144
|
|
|
152
145
|
**Position Options:**
|
|
153
146
|
```typescript
|
|
154
147
|
export type ApplySlotPosition =
|
|
148
|
+
| 'prepend'
|
|
155
149
|
| 'prependHtml' // Add HTML at beginning
|
|
156
150
|
| 'prependText' // Add text at beginning
|
|
151
|
+
| 'append'
|
|
157
152
|
| 'appendHtml' // Add HTML at end
|
|
158
153
|
| 'appendText' // Add text at end
|
|
154
|
+
| 'replaceChildren'
|
|
159
155
|
| 'replaceChildrenHtml' // Replace all with HTML
|
|
160
156
|
| 'replaceChildrenText' // Replace all with text
|
|
161
157
|
| 'clear'; // Clear all content
|
|
162
158
|
```
|
|
163
159
|
|
|
164
|
-
**Manual Slot Manipulation:**
|
|
165
|
-
```typescript
|
|
166
|
-
// Via @applySlot property
|
|
167
|
-
@applySlot('content')
|
|
168
|
-
contentSlot?: NodeSlot;
|
|
169
|
-
|
|
170
|
-
// In lifecycle or methods
|
|
171
|
-
this.contentSlot?.appendHtml('content', '<p>New content</p>');
|
|
172
|
-
this.contentSlot?.appendText('content', 'Text content');
|
|
173
|
-
this.contentSlot?.replaceChildrenHtml('content', '<div>Replaced</div>');
|
|
174
|
-
this.contentSlot?.clear('content');
|
|
175
|
-
```
|
|
176
160
|
|
|
177
161
|
### 3.6 **State Management with @state**
|
|
178
162
|
|
|
@@ -207,14 +191,13 @@ class CounterApp extends HTMLElement {
|
|
|
207
191
|
<!--[text Count: @count@ ]-->
|
|
208
192
|
|
|
209
193
|
<!-- Attribute binding with a: prefix -->
|
|
210
|
-
<div a:title="Count is
|
|
194
|
+
<div a:title="'Count is'+@count@"></div>
|
|
211
195
|
|
|
212
196
|
<!-- Event binding with e: prefix -->
|
|
213
|
-
<button e:click="increment">Increment (@count@)</button>
|
|
197
|
+
<button e:click="@increment@()">Increment (@count@)</button>
|
|
214
198
|
|
|
215
199
|
<!-- Conditional rendering -->
|
|
216
200
|
<div a:style="@isActive@ ? 'color: green' : 'color: red'">
|
|
217
|
-
Status: @isActive@
|
|
218
201
|
</div>
|
|
219
202
|
</div>
|
|
220
203
|
`;
|
|
@@ -233,7 +216,7 @@ class CounterApp extends HTMLElement {
|
|
|
233
216
|
- `<!--[html @state@ ]-->` - Render state value as HTML
|
|
234
217
|
- `<!--[text @state@ ]-->` - Render state value as text
|
|
235
218
|
- `a:attributeName="@state@"` - Bind state to HTML attributes
|
|
236
|
-
- `e:eventName="methodName"` - Bind events to methods
|
|
219
|
+
- `e:eventName="@methodName@()"` - Bind events to methods
|
|
237
220
|
|
|
238
221
|
**How @state Works:**
|
|
239
222
|
1. Declare property with `@state('stateName')`
|
|
@@ -263,7 +246,7 @@ class UserProfile extends HTMLElement {
|
|
|
263
246
|
this.isEditing = !this.isEditing;
|
|
264
247
|
}
|
|
265
248
|
|
|
266
|
-
@
|
|
249
|
+
@onConnectedShadow
|
|
267
250
|
render() {
|
|
268
251
|
return `
|
|
269
252
|
<div>
|
|
@@ -276,9 +259,7 @@ class UserProfile extends HTMLElement {
|
|
|
276
259
|
<!--[html @isEditing@ ? '<input type="text" />' : '<span>View Mode</span>' ]-->
|
|
277
260
|
</div>
|
|
278
261
|
|
|
279
|
-
<button e:click="toggleEdit">
|
|
280
|
-
@isEditing@ ? 'Save' : 'Edit'
|
|
281
|
-
</button>
|
|
262
|
+
<button e:click="toggleEdit"> toggleEdit </button>
|
|
282
263
|
</div>
|
|
283
264
|
`;
|
|
284
265
|
}
|
|
@@ -307,6 +288,17 @@ onCriticalClick(event: Event) {
|
|
|
307
288
|
}
|
|
308
289
|
```
|
|
309
290
|
|
|
291
|
+
**other Event decorators**
|
|
292
|
+
- addEventListenerDelegateLightDom
|
|
293
|
+
- addEventListenerDelegateShadowDom
|
|
294
|
+
- addEventListenerDelegateAllDom
|
|
295
|
+
- addEventListenerDelegate
|
|
296
|
+
- addEventListenerThis
|
|
297
|
+
- addEventListenerAppHost
|
|
298
|
+
- addEventListenerWindow
|
|
299
|
+
- addEventListenerDocument
|
|
300
|
+
|
|
301
|
+
|
|
310
302
|
#### @emitCustomEvent
|
|
311
303
|
Emit custom events with data.
|
|
312
304
|
|
|
@@ -390,7 +382,7 @@ listItems?: HTMLLIElement[];
|
|
|
390
382
|
```
|
|
391
383
|
|
|
392
384
|
#### @queryThis
|
|
393
|
-
Query the component element itself.
|
|
385
|
+
Query the component element itself. == this
|
|
394
386
|
|
|
395
387
|
```typescript
|
|
396
388
|
@queryThis
|
|
@@ -401,29 +393,17 @@ printTag() {
|
|
|
401
393
|
}
|
|
402
394
|
```
|
|
403
395
|
|
|
404
|
-
#### @queryAllThis
|
|
405
|
-
Query all direct children of the component.
|
|
406
|
-
|
|
407
|
-
```typescript
|
|
408
|
-
@queryAllThis
|
|
409
|
-
children?: HTMLElement[];
|
|
410
|
-
|
|
411
|
-
getChildCount() {
|
|
412
|
-
return this.children?.length ?? 0;
|
|
413
|
-
}
|
|
414
|
-
```
|
|
415
|
-
|
|
416
396
|
### 6. **Attribute Binding**
|
|
417
397
|
|
|
418
398
|
#### @attribute
|
|
419
399
|
Bind HTML attributes to properties (automatic sync).
|
|
420
400
|
|
|
421
401
|
```typescript
|
|
422
|
-
@
|
|
423
|
-
width: string = '100px';
|
|
402
|
+
@attributeThis
|
|
403
|
+
width: string = '100px'; // Bind to this.width
|
|
424
404
|
|
|
425
405
|
@attribute('data-id')
|
|
426
|
-
resourceId: string
|
|
406
|
+
resourceId: string; // element attribute first
|
|
427
407
|
```
|
|
428
408
|
|
|
429
409
|
#### @attributeThis
|
|
@@ -431,7 +411,7 @@ Bind attributes on host element itself.
|
|
|
431
411
|
|
|
432
412
|
```typescript
|
|
433
413
|
@attributeThis('product-id')
|
|
434
|
-
productId
|
|
414
|
+
productId?: string;
|
|
435
415
|
|
|
436
416
|
@onInitialize
|
|
437
417
|
onconstructor() {
|
|
@@ -496,321 +476,6 @@ SWC supports two types of expression syntax for dynamic value evaluation:
|
|
|
496
476
|
|
|
497
477
|
---
|
|
498
478
|
|
|
499
|
-
#### 8.1 **SwcChoose - Multi-Condition Rendering**
|
|
500
|
-
|
|
501
|
-
Switch-case style conditional rendering with `swc-when` and `swc-otherwise` sub-templates.
|
|
502
|
-
|
|
503
|
-
```typescript
|
|
504
|
-
// Component with conditional rendering logic
|
|
505
|
-
@elementDefine('status-display')
|
|
506
|
-
class StatusDisplay extends HTMLElement {
|
|
507
|
-
@attributeThis('status')
|
|
508
|
-
status: string = 'pending';
|
|
509
|
-
|
|
510
|
-
@setProperty('#status-template', 'value')
|
|
511
|
-
getStatus() {
|
|
512
|
-
return this.status;
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
@onConnectedInnerHtml
|
|
516
|
-
render() {
|
|
517
|
-
return `
|
|
518
|
-
<template id="status-template" is="swc-choose">
|
|
519
|
-
<template is="swc-when" test="$value === 'pending'">
|
|
520
|
-
<div style="color: orange;">⏳ Processing...</div>
|
|
521
|
-
</template>
|
|
522
|
-
|
|
523
|
-
<template is="swc-when" test="$value === 'success'">
|
|
524
|
-
<div style="color: green;">✓ Completed</div>
|
|
525
|
-
</template>
|
|
526
|
-
|
|
527
|
-
<template is="swc-when" test="$value === 'error'">
|
|
528
|
-
<div style="color: red;">✗ Failed</div>
|
|
529
|
-
</template>
|
|
530
|
-
|
|
531
|
-
<!-- Fallback -->
|
|
532
|
-
<template is="swc-otherwise">
|
|
533
|
-
<div style="color: gray;">Unknown status</div>
|
|
534
|
-
</template>
|
|
535
|
-
</template>
|
|
536
|
-
`;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
**Expression Features:**
|
|
542
|
-
- `$value` contains the value passed to `swc-choose`
|
|
543
|
-
- `skip-if-same` attribute prevents re-render when same template selected
|
|
544
|
-
- **Attribute substitution with `{{= }}`**: `attribute-name="{{= expression }}"` automatically evaluated
|
|
545
|
-
|
|
546
|
-
**Attribute Substitution Example ({{= }} usage):**
|
|
547
|
-
```html
|
|
548
|
-
<template is="swc-choose">
|
|
549
|
-
<template is="swc-when" test="$value?.type === 'user'">
|
|
550
|
-
<user-card
|
|
551
|
-
data-username="{{= $value?.name }}"
|
|
552
|
-
data-email="{{= $value?.email }}"
|
|
553
|
-
data-id="{{= $value?.id }}"
|
|
554
|
-
></user-card>
|
|
555
|
-
</template>
|
|
556
|
-
|
|
557
|
-
<template is="swc-when" value="$value?.type === 'product'">
|
|
558
|
-
<product-card
|
|
559
|
-
data-product-name="{{= $value?.title }}"
|
|
560
|
-
data-price="{{= $value?.price }}"
|
|
561
|
-
></product-card>
|
|
562
|
-
</template>
|
|
563
|
-
</template>
|
|
564
|
-
```
|
|
565
|
-
|
|
566
|
-
---
|
|
567
|
-
|
|
568
|
-
#### 8.2 **SwcIf - Binary Conditional**
|
|
569
|
-
|
|
570
|
-
If-then-else rendering with optional `swc-default` fallback.
|
|
571
|
-
|
|
572
|
-
```typescript
|
|
573
|
-
@elementDefine('user-profile')
|
|
574
|
-
class UserProfile extends HTMLElement {
|
|
575
|
-
private isLoggedIn = false;
|
|
576
|
-
private userData = { userName: 'Alice', userId: 123 };
|
|
577
|
-
|
|
578
|
-
@setProperty('#content', 'value')
|
|
579
|
-
checkAuth() {
|
|
580
|
-
return this.isLoggedIn;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
@onConnectedInnerHtml
|
|
584
|
-
render() {
|
|
585
|
-
return `
|
|
586
|
-
<!-- True condition: renders main template using {{ }} -->
|
|
587
|
-
<template id="content" is="swc-if" value="{{ $host.isLoggedIn }}">
|
|
588
|
-
<!-- Attribute substitution with {{= }} expressions -->
|
|
589
|
-
<user-card
|
|
590
|
-
data-username="{{= $host.userData.userName }}"
|
|
591
|
-
data-userid="{{= $host.userData.userId }}"
|
|
592
|
-
></user-card>
|
|
593
|
-
<div>
|
|
594
|
-
<h2>Welcome, {{ $host.userData.userName }}</h2>
|
|
595
|
-
<button on-click="logout">Logout</button>
|
|
596
|
-
</div>
|
|
597
|
-
|
|
598
|
-
<!-- Fallback template: renders if condition is false -->
|
|
599
|
-
<template is="swc-default">
|
|
600
|
-
<div>
|
|
601
|
-
<p>Please log in</p>
|
|
602
|
-
<button on-click="login">Login</button>
|
|
603
|
-
</div>
|
|
604
|
-
</template>
|
|
605
|
-
</template>
|
|
606
|
-
`;
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
**Expression Features:**
|
|
612
|
-
- Binary true/false logic with `{{ }} condition`
|
|
613
|
-
- Main template renders when `value` is truthy
|
|
614
|
-
- `swc-default` sub-template renders when value is falsy
|
|
615
|
-
- Dynamic attribute substitution with `{{= }}` supported
|
|
616
|
-
- Access `$host`, `$value`, and other context variables
|
|
617
|
-
- `{{= functionCall() }}` executes functions and returns results for attributes
|
|
618
|
-
|
|
619
|
-
---
|
|
620
|
-
|
|
621
|
-
#### 8.3 **SwcAsync - Promise/Loading State**
|
|
622
|
-
|
|
623
|
-
Automatically handle async operations with loading, success, and error states.
|
|
624
|
-
|
|
625
|
-
```typescript
|
|
626
|
-
@elementDefine('data-loader')
|
|
627
|
-
class DataLoader extends HTMLElement {
|
|
628
|
-
private data: any = null;
|
|
629
|
-
|
|
630
|
-
async fetchData() {
|
|
631
|
-
return new Promise((resolve, reject) => {
|
|
632
|
-
setTimeout(() => {
|
|
633
|
-
resolve({ id: 1, name: 'Sample Data', description: 'API response' });
|
|
634
|
-
}, 2000);
|
|
635
|
-
});
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
@setProperty('#async-content', 'value')
|
|
639
|
-
loadData() {
|
|
640
|
-
return this.fetchData();
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
@onConnectedInnerHtml
|
|
644
|
-
render() {
|
|
645
|
-
return `
|
|
646
|
-
<template id="async-content" is="swc-async" value="{{= $host.fetchData() }}">
|
|
647
|
-
<!-- Loading State: Shows while Promise is pending -->
|
|
648
|
-
<template is="swc-loading">
|
|
649
|
-
<div>🔄 Loading data...</div>
|
|
650
|
-
</template>
|
|
651
|
-
|
|
652
|
-
<!-- Success State: Shows when Promise resolves -->
|
|
653
|
-
<!-- Using {{= }} to extract data from resolved Promise -->
|
|
654
|
-
<template is="swc-success">
|
|
655
|
-
<data-card
|
|
656
|
-
data-id="{{= $value.id }}"
|
|
657
|
-
data-name="{{= $value.name }}"
|
|
658
|
-
></data-card>
|
|
659
|
-
<div>
|
|
660
|
-
<p>Data ID: {{ $value.id }}</p>
|
|
661
|
-
<p>Data Name: {{ $value.name }}</p>
|
|
662
|
-
<p>Description: {{= $value.description }}</p>
|
|
663
|
-
</div>
|
|
664
|
-
</template>
|
|
665
|
-
|
|
666
|
-
<!-- Error State: Shows if Promise rejects -->
|
|
667
|
-
<template is="swc-error">
|
|
668
|
-
<div style="color: red;">❌ Failed to load data</div>
|
|
669
|
-
</template>
|
|
670
|
-
</template>
|
|
671
|
-
`;
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
```
|
|
675
|
-
|
|
676
|
-
**Expression Features:**
|
|
677
|
-
- Promise lifecycle management with `{{ }} conditions`
|
|
678
|
-
- `{{= $host.fetchData() }}` - function call expression that executes and returns Promise
|
|
679
|
-
- `{{= $value.propertyName }}` - extract properties from resolved value
|
|
680
|
-
- `swc-loading` - displays during pending state
|
|
681
|
-
- `swc-success` - displays on successful resolution, `$value` contains resolved result
|
|
682
|
-
- `swc-error` - displays on Promise rejection, `$value` contains error
|
|
683
|
-
- Dynamic attribute substitution with `{{= }}` in all states
|
|
684
|
-
- `on-clone-node` and `on-clone-nodes` callbacks for DOM manipulation
|
|
685
|
-
|
|
686
|
-
---
|
|
687
|
-
|
|
688
|
-
#### 8.4 **SwcLoop - List Rendering**
|
|
689
|
-
|
|
690
|
-
Iterate over arrays and render templates for each item.
|
|
691
|
-
|
|
692
|
-
```typescript
|
|
693
|
-
@elementDefine('product-list')
|
|
694
|
-
class ProductList extends HTMLElement {
|
|
695
|
-
private products = [
|
|
696
|
-
{ id: 1, name: 'Laptop', price: 1200, category: 'electronics' },
|
|
697
|
-
{ id: 2, name: 'Phone', price: 800, category: 'electronics' },
|
|
698
|
-
{ id: 3, name: 'Tablet', price: 500, category: 'electronics' }
|
|
699
|
-
];
|
|
700
|
-
|
|
701
|
-
@setProperty('#products-list', 'value')
|
|
702
|
-
getProducts() {
|
|
703
|
-
return this.products;
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
@onConnectedInnerHtml
|
|
707
|
-
render() {
|
|
708
|
-
return `
|
|
709
|
-
<template id="products-list" is="swc-loop" value="{{= $host.products }}">
|
|
710
|
-
<!-- Loop iteration with {{ }} for display and {{= }} for attributes -->
|
|
711
|
-
<div style="margin: 10px; padding: 10px; border: 1px solid #ddd;">
|
|
712
|
-
<!-- Using {{ }} for template content rendering -->
|
|
713
|
-
<h3>{{ $item.name }}</h3>
|
|
714
|
-
<p>Price: {{ $item.price }}</p>
|
|
715
|
-
<p>ID: {{ $item.id }} (Index: {{ $index }})</p>
|
|
716
|
-
|
|
717
|
-
<!-- Using {{= }} for dynamic attribute substitution -->
|
|
718
|
-
<product-card
|
|
719
|
-
data-product-id="{{= $item.id }}"
|
|
720
|
-
data-product-name="{{= $item.name }}"
|
|
721
|
-
data-product-price="{{= $item.price }}"
|
|
722
|
-
data-product-category="{{= $item.category }}"
|
|
723
|
-
></product-card>
|
|
724
|
-
|
|
725
|
-
<!-- Computed expressions with {{= }} -->
|
|
726
|
-
<button data-id="{{= $item.id }}" data-price="{{= $item.price }}">
|
|
727
|
-
Add to Cart - {{ $item.price }}
|
|
728
|
-
</button>
|
|
729
|
-
</div>
|
|
730
|
-
|
|
731
|
-
<!-- Empty fallback: renders when array is empty -->
|
|
732
|
-
<template is="swc-default">
|
|
733
|
-
<p>No products available</p>
|
|
734
|
-
</template>
|
|
735
|
-
</template>
|
|
736
|
-
`;
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
```
|
|
740
|
-
|
|
741
|
-
**Context Variables (Available in loop):**
|
|
742
|
-
- `$item` - current item in iteration
|
|
743
|
-
- `$index` - zero-based index (0, 1, 2, ...)
|
|
744
|
-
- `$value` - entire array being iterated
|
|
745
|
-
- `$nodes` - cloned DOM nodes
|
|
746
|
-
- `$elements` - cloned HTML elements
|
|
747
|
-
- `$firstElement` - first element in cloned nodes
|
|
748
|
-
|
|
749
|
-
**Expression Features:**
|
|
750
|
-
- `{{= $host.products }}` - function call expression that returns the array to iterate
|
|
751
|
-
- `{{ $item.propertyName }}` - display item properties using standard `{{ }}`
|
|
752
|
-
- `{{= $item.propertyName }}` - extract item properties for attributes using `{{= }}`
|
|
753
|
-
- `{{= computedValue }}` - compute values in attributes (e.g., calculations, method calls)
|
|
754
|
-
- `swc-default` fallback renders when array is empty
|
|
755
|
-
- All cloned attributes support `{{ }}` and `{{= }}` substitution
|
|
756
|
-
- `on-clone-node` callback fires for each cloned node
|
|
757
|
-
- `on-clone-nodes` callback fires after all nodes cloned
|
|
758
|
-
|
|
759
|
-
**Common Patterns:**
|
|
760
|
-
```html
|
|
761
|
-
<!-- Template functions with {{= }} -->
|
|
762
|
-
<template is="swc-loop" value="{{= filterProducts($host.products) }}">
|
|
763
|
-
<!-- Dynamic calculations -->
|
|
764
|
-
<span>Total: {{= $item.price * $item.quantity }}</span>
|
|
765
|
-
|
|
766
|
-
<!-- Conditional attributes -->
|
|
767
|
-
<div class="{{= $item.inStock ? 'available' : 'unavailable' }}"></div>
|
|
768
|
-
|
|
769
|
-
<!-- Method calls as attributes -->
|
|
770
|
-
<button data-formatted="{{= formatPrice($item.price) }}">
|
|
771
|
-
Buy - {{ formatPrice($item.price) }}
|
|
772
|
-
</button>
|
|
773
|
-
</template>
|
|
774
|
-
```
|
|
775
|
-
|
|
776
|
-
**Advanced: replace-wrap-start replace-wrap-end for Custom Expression Syntax**
|
|
777
|
-
|
|
778
|
-
Override default `{{ }}` and `{{= }}` syntax with custom wrappers:
|
|
779
|
-
|
|
780
|
-
```typescript
|
|
781
|
-
@elementDefine('custom-loop')
|
|
782
|
-
class CustomLoop extends HTMLElement {
|
|
783
|
-
private items = [{ id: 1, name: 'Item 1' }];
|
|
784
|
-
|
|
785
|
-
@onConnectedInnerHtml
|
|
786
|
-
render() {
|
|
787
|
-
return `
|
|
788
|
-
<!-- Using custom wrapper syntax [(= ... )] instead of {{= ... }} -->
|
|
789
|
-
<template id="items" is="swc-loop" replace-wrap-start="[(" replace-wrap-end=")]" value="{{= $host.items }}">
|
|
790
|
-
<div>
|
|
791
|
-
<p>Name: [( $item.name )]</p>
|
|
792
|
-
<button data-id="[( $item.id )]">Click me</button>
|
|
793
|
-
</div>
|
|
794
|
-
</template>
|
|
795
|
-
`;
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
```
|
|
799
|
-
|
|
800
|
-
**replace-wrap attribute options:**
|
|
801
|
-
- Change expression wrapper from default `{#{ }#}` to custom pattern
|
|
802
|
-
- Supported on: `SwcLoop`, `SwcChoose`, `SwcIf`, `SwcAsync`, `register()` directives
|
|
803
|
-
|
|
804
|
-
---
|
|
805
|
-
|
|
806
|
-
**Benefits:**
|
|
807
|
-
- ✅ **No imperative route methods** - routes are declarative templates
|
|
808
|
-
- ✅ **Automatic re-rendering** - attributes like `user-id` update automatically
|
|
809
|
-
- ✅ **Performance optimization** - use `skip-if-same` on templates to prevent re-renders
|
|
810
|
-
- ✅ **Dynamic path parameters** - use `{{= $value?.pathData?.id }}` syntax to inject route params with `{{= }}`
|
|
811
|
-
- ✅ **Cleaner DX** - single unified routing pattern
|
|
812
|
-
- ✅ **Full expression support** - use `{{= }}` for complex parameter transformations
|
|
813
|
-
|
|
814
479
|
### 9. **Component Communication - Message Bus**
|
|
815
480
|
|
|
816
481
|
SWC provides a powerful message bus system for inter-component communication through SwcApp. Components can publish and subscribe to typed messages while connected.
|
|
@@ -821,7 +486,7 @@ Subscribe to messages while component is connected to DOM.
|
|
|
821
486
|
```typescript
|
|
822
487
|
@elementDefine('notification-panel')
|
|
823
488
|
class NotificationPanel extends HTMLElement {
|
|
824
|
-
@subscribeSwcAppMessageWhileConnected
|
|
489
|
+
@subscribeSwcAppMessageWhileConnected
|
|
825
490
|
onAnyMessage(message: SwcAppMessage) {
|
|
826
491
|
console.log('Received message:', message);
|
|
827
492
|
}
|
|
@@ -948,7 +613,7 @@ import {
|
|
|
948
613
|
```typescript
|
|
949
614
|
// index.ts
|
|
950
615
|
import 'reflect-metadata';
|
|
951
|
-
import { SwcAppInterface, defineSwcAppAll } from '@dooboostore/simple-web-component';
|
|
616
|
+
import { defineSwcAppBody, SwcAppInterface, defineSwcAppAll } from '@dooboostore/simple-web-component';
|
|
952
617
|
import bootFactory from './bootFactory';
|
|
953
618
|
|
|
954
619
|
const w = window;
|
|
@@ -957,10 +622,8 @@ w.document.addEventListener('DOMContentLoaded', async () => {
|
|
|
957
622
|
const container = Symbol('app');
|
|
958
623
|
|
|
959
624
|
// Initialize services and components
|
|
960
|
-
|
|
625
|
+
await defineSwcAppBody(w);
|
|
961
626
|
|
|
962
|
-
// Register all SwcApp variants
|
|
963
|
-
await defineSwcAppAll(w);
|
|
964
627
|
|
|
965
628
|
// Get app root element
|
|
966
629
|
const appElement = w.document.querySelector('#app') as SwcAppInterface;
|
|
@@ -970,6 +633,7 @@ w.document.addEventListener('DOMContentLoaded', async () => {
|
|
|
970
633
|
path: '/',
|
|
971
634
|
routeType: 'path',
|
|
972
635
|
container: container,
|
|
636
|
+
onStartedLazyDefineComponent: [yourComponentFactory1, yourComponentFactor2],
|
|
973
637
|
window: w,
|
|
974
638
|
onEngineStarted: () => {
|
|
975
639
|
console.log('🚀 Application started successfully');
|
|
@@ -979,64 +643,41 @@ w.document.addEventListener('DOMContentLoaded', async () => {
|
|
|
979
643
|
});
|
|
980
644
|
```
|
|
981
645
|
|
|
982
|
-
#### SwcAppMixin - Reusable Component Logic
|
|
983
|
-
|
|
984
|
-
The `SwcAppMixin` extracts all SwcApp functionality into a reusable mixin that can be applied to any HTMLElement type:
|
|
985
|
-
|
|
986
|
-
```typescript
|
|
987
|
-
import { SwcAppMixin } from '@dooboostore/simple-web-component';
|
|
988
|
-
import { elementDefine } from '@dooboostore/simple-web-component';
|
|
989
|
-
|
|
990
|
-
// Create custom SwcApp variant extending HTMLDivElement
|
|
991
|
-
@elementDefine('custom-app', { window: w, extends: 'div' })
|
|
992
|
-
class CustomApp extends SwcAppMixin(w.HTMLDivElement) implements SwcAppInterface {
|
|
993
|
-
// All SwcApp functionality inherited from mixin
|
|
994
|
-
// Add custom logic here if needed
|
|
995
|
-
}
|
|
996
|
-
```
|
|
997
|
-
|
|
998
|
-
#### SwcAppMixin Features
|
|
999
|
-
|
|
1000
|
-
The mixin provides:
|
|
1001
|
-
- **Lifecycle Management**: `connectedCallback`, `disconnectedCallback` with parent class support
|
|
1002
|
-
- **Routing**: `routing(path)`, `back()`, `forward()`, `reload()`
|
|
1003
|
-
- **Message Bus**: `publishMessage()` for inter-component communication
|
|
1004
|
-
- **Route Subscriptions**: Automatic route change handling for connected components
|
|
1005
|
-
- **SSR Support**: `isSSR()`, `setSSRAttribute()`, `removeSSRAttribute()` utilities
|
|
1006
|
-
- **DI Integration**: Full dependency injection support via `@onInitialize`
|
|
1007
|
-
|
|
1008
646
|
#### Factory Pattern - Component Registration
|
|
1009
647
|
|
|
1010
648
|
```typescript
|
|
1011
649
|
// Component factory
|
|
650
|
+
import {defineSwcApp} from "./SwcApp";
|
|
651
|
+
|
|
1012
652
|
export default (w: Window) => {
|
|
1013
653
|
const tagName = 'my-component';
|
|
1014
654
|
const existing = w.customElements.get(tagName);
|
|
1015
655
|
if (existing) return tagName;
|
|
1016
656
|
|
|
1017
|
-
@elementDefine(tagName, {
|
|
657
|
+
@elementDefine(tagName, {window: w})
|
|
1018
658
|
class MyComponent extends w.HTMLElement {
|
|
1019
659
|
@onInitialize
|
|
1020
660
|
onconstructor(service: MyService) {
|
|
1021
661
|
this.service = service;
|
|
1022
662
|
}
|
|
1023
663
|
}
|
|
1024
|
-
|
|
664
|
+
|
|
1025
665
|
return tagName;
|
|
1026
666
|
};
|
|
1027
667
|
|
|
1028
668
|
// Boot factory
|
|
1029
669
|
export default (w: Window, container: symbol) => {
|
|
1030
670
|
serviceFactories.forEach(s => s(container));
|
|
1031
|
-
register(w, [...pageFactories, ...componentFactories]);
|
|
1032
671
|
};
|
|
1033
672
|
|
|
1034
673
|
// Entry point
|
|
1035
674
|
const container = Symbol('app');
|
|
1036
|
-
|
|
675
|
+
await defineSwcAppBody(w)
|
|
676
|
+
const appElement = w.document.querySelector('#app') as SwcAppInterface;
|
|
1037
677
|
appElement.connect({
|
|
1038
678
|
container,
|
|
1039
679
|
window: w,
|
|
680
|
+
onStartedLazyDefineComponent: [...pageFactories, ...componentFactories],
|
|
1040
681
|
onEngineStarted: () => {
|
|
1041
682
|
appElement.innerHTML = '<root-router></root-router>';
|
|
1042
683
|
}
|
|
@@ -1048,487 +689,62 @@ Factory-based component registration with explicit DI.
|
|
|
1048
689
|
|
|
1049
690
|
```typescript
|
|
1050
691
|
// Component factory
|
|
692
|
+
import {SwcUtils, createElement, CreateElementConfig} from '@dooboostore/simple-web-component';
|
|
693
|
+
const tagName = 'my-component';
|
|
694
|
+
export interface MyComponent extends HTMLElement {
|
|
695
|
+
value: string;
|
|
696
|
+
}
|
|
697
|
+
export const MyComponent = (w: Window|Document, data?: CreateElementConfig) => {
|
|
698
|
+
return createElement<HTMLElement>(w, tagName, data);
|
|
699
|
+
};
|
|
1051
700
|
export default (w: Window) => {
|
|
1052
|
-
const tagName = 'my-component';
|
|
1053
701
|
const existing = w.customElements.get(tagName);
|
|
1054
|
-
if (existing) return
|
|
702
|
+
if (existing) return existing;
|
|
1055
703
|
|
|
1056
704
|
@elementDefine(tagName, { window: w })
|
|
1057
|
-
class
|
|
705
|
+
class MyComponentImp extends w.HTMLElement implements MyComponent{
|
|
706
|
+
value: string = 'value';
|
|
1058
707
|
@onInitialize
|
|
1059
708
|
onconstructor(service: MyService) {
|
|
1060
709
|
this.service = service;
|
|
1061
710
|
}
|
|
1062
711
|
}
|
|
1063
|
-
|
|
1064
|
-
return tagName;
|
|
1065
|
-
};
|
|
1066
|
-
|
|
1067
|
-
// Boot factory
|
|
1068
|
-
export default (w: Window, container: symbol) => {
|
|
1069
|
-
serviceFactories.forEach(s => s(container));
|
|
1070
|
-
register(w, [...pageFactories, ...componentFactories]);
|
|
712
|
+
return w.customElements.whenDefined(swcAppTagName);
|
|
1071
713
|
};
|
|
1072
714
|
|
|
1073
|
-
// Entry point
|
|
1074
|
-
const container = Symbol('app');
|
|
1075
|
-
bootFactory(w, container);
|
|
1076
|
-
appElement.connect({
|
|
1077
|
-
container,
|
|
1078
|
-
window: w,
|
|
1079
|
-
onEngineStarted: () => {
|
|
1080
|
-
appElement.innerHTML = '<root-router></root-router>';
|
|
1081
|
-
}
|
|
1082
|
-
});
|
|
1083
715
|
```
|
|
1084
|
-
|
|
1085
|
-
### 12. **Quickstart - Basic SPA Setup**
|
|
1086
|
-
|
|
1087
|
-
Complete example of setting up a production SPA with routing, services, and components.
|
|
1088
|
-
|
|
1089
|
-
#### Step 1. HTML Entry Point
|
|
1090
|
-
```html
|
|
1091
|
-
<!DOCTYPE html>
|
|
1092
|
-
<html>
|
|
1093
|
-
<head>
|
|
1094
|
-
<meta charset="UTF-8">
|
|
1095
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1096
|
-
<title>My SPA</title>
|
|
1097
|
-
<!-- Web Components polyfill for older browsers -->
|
|
1098
|
-
<script src="https://unpkg.com/@ungap/custom-elements"></script>
|
|
1099
|
-
</head>
|
|
1100
|
-
<!-- Use is="swc-app-body" for semantic HTML root -->
|
|
1101
|
-
<body id="app" is="swc-app-body">
|
|
1102
|
-
<!-- Root router will render pages here -->
|
|
1103
|
-
<root-router></root-router>
|
|
1104
|
-
</body>
|
|
1105
|
-
<script src="bundle.js"></script>
|
|
1106
|
-
</html>
|
|
1107
|
-
```
|
|
1108
|
-
|
|
1109
|
-
#### Step 2. Services (Dependency Injection)
|
|
1110
|
-
```typescript
|
|
1111
|
-
// services/UserService.ts
|
|
1112
|
-
import { Sim } from '@dooboostore/simple-boot';
|
|
1113
|
-
import { ConstructorType } from '@dooboostore/core';
|
|
1114
|
-
|
|
1115
|
-
export namespace UserService {
|
|
1116
|
-
export const SYMBOL = Symbol('UserService');
|
|
1117
|
-
|
|
1118
|
-
export interface User {
|
|
1119
|
-
id: number;
|
|
1120
|
-
name: string;
|
|
1121
|
-
email: string;
|
|
1122
|
-
}
|
|
1123
|
-
|
|
1124
|
-
const users: User[] = [
|
|
1125
|
-
{ id: 1, name: 'Alice', email: 'alice@example.com' },
|
|
1126
|
-
{ id: 2, name: 'Bob', email: 'bob@example.com' }
|
|
1127
|
-
];
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
export interface UserService {
|
|
1131
|
-
getUser(id: number): UserService.User | undefined;
|
|
1132
|
-
getAllUsers(): UserService.User[];
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
// Factory function: accepts container and returns service class
|
|
1136
|
-
export default (container: symbol): ConstructorType<any> => {
|
|
1137
|
-
@Sim({ symbol: UserService.SYMBOL, container: container })
|
|
1138
|
-
class UserServiceImpl implements UserService {
|
|
1139
|
-
getUser(id: number) {
|
|
1140
|
-
return UserService.users.find(u => u.id === id);
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
getAllUsers() {
|
|
1144
|
-
return [...UserService.users];
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
return UserServiceImpl;
|
|
1149
|
-
};
|
|
1150
|
-
```
|
|
1151
|
-
|
|
1152
|
-
```typescript
|
|
1153
|
-
// services/index.ts
|
|
1154
|
-
import userServiceFactory from './UserService';
|
|
1155
|
-
|
|
1156
|
-
export const serviceFactories = [userServiceFactory];
|
|
1157
|
-
```
|
|
1158
|
-
|
|
1159
|
-
#### Step 3. Router Configuration (Declarative with swc-choose)
|
|
1160
|
-
```typescript
|
|
1161
|
-
// pages/RootRouter.ts
|
|
1162
|
-
import { elementDefine, subscribeSwcAppRouteChangeWhileConnected, setProperty, onConnectedInnerHtml, onInitialize } from '@dooboostore/simple-web-component';
|
|
1163
|
-
import { Router, RouterEventType } from '@dooboostore/core-web';
|
|
1164
|
-
|
|
1165
|
-
const rootRouterFactory = (w: Window) => {
|
|
1166
|
-
const tagName = 'root-router';
|
|
1167
|
-
const existing = w.customElements.get(tagName);
|
|
1168
|
-
if (existing) return tagName;
|
|
1169
|
-
|
|
1170
|
-
@elementDefine(tagName, { window: w })
|
|
1171
|
-
class RootRouter extends w.HTMLElement {
|
|
1172
|
-
private router: Router;
|
|
1173
|
-
|
|
1174
|
-
@onInitialize
|
|
1175
|
-
onconstructor(router: Router) {
|
|
1176
|
-
this.router = router;
|
|
1177
|
-
}
|
|
1178
|
-
|
|
1179
|
-
@setProperty('#router', 'value')
|
|
1180
|
-
@subscribeSwcAppRouteChangeWhileConnected(['/', '/users', '/users/{id}'])
|
|
1181
|
-
routeChanged(route: RouterEventType) {
|
|
1182
|
-
return route;
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
navigate(path: string): void {
|
|
1186
|
-
this.router?.go(path);
|
|
1187
|
-
}
|
|
1188
|
-
|
|
1189
|
-
@onConnectedInnerHtml
|
|
1190
|
-
render() {
|
|
1191
|
-
return `
|
|
1192
|
-
<template id="router" is="swc-choose">
|
|
1193
|
-
<!-- Home Page -->
|
|
1194
|
-
<template is="swc-when" test="['', '/'].includes($value?.path)">
|
|
1195
|
-
<home-page/>
|
|
1196
|
-
</template>
|
|
1197
|
-
|
|
1198
|
-
<!-- Users List -->
|
|
1199
|
-
<template is="swc-when" test="$value?.path === '/users'">
|
|
1200
|
-
<users-list-page/>
|
|
1201
|
-
</template>
|
|
1202
|
-
|
|
1203
|
-
<!-- User Detail -->
|
|
1204
|
-
<template is="swc-when" test="$value?.path.startsWith('/users/')">
|
|
1205
|
-
<user-detail-page user-id="{{= $value?.pathData?.id}}"/>
|
|
1206
|
-
</template>
|
|
1207
|
-
|
|
1208
|
-
<!-- Not Found -->
|
|
1209
|
-
<template is="swc-otherwise">
|
|
1210
|
-
<not-found-page/>
|
|
1211
|
-
</template>
|
|
1212
|
-
</template>
|
|
1213
|
-
`;
|
|
1214
|
-
}
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
return tagName;
|
|
1218
|
-
};
|
|
1219
|
-
|
|
1220
|
-
export default rootRouterFactory;
|
|
1221
|
-
```
|
|
1222
|
-
|
|
1223
|
-
#### Step 4. Page Components
|
|
1224
|
-
```typescript
|
|
1225
|
-
// pages/HomePage.ts
|
|
1226
|
-
import { elementDefine, onConnectedInnerHtml } from '@dooboostore/simple-web-component';
|
|
1227
|
-
|
|
1228
|
-
const HomePage = (w: Window) => {
|
|
1229
|
-
const tagName = 'home-page';
|
|
1230
|
-
const existing = w.customElements.get(tagName);
|
|
1231
|
-
if (existing) return tagName;
|
|
1232
|
-
|
|
1233
|
-
@elementDefine(tagName, { window: w })
|
|
1234
|
-
class HomePage extends w.HTMLElement {
|
|
1235
|
-
@onConnectedInnerHtml({ useShadow: true })
|
|
1236
|
-
render() {
|
|
1237
|
-
return `
|
|
1238
|
-
<style>
|
|
1239
|
-
:host { display: block; padding: 20px; }
|
|
1240
|
-
h1 { color: #333; }
|
|
1241
|
-
a { color: #0066cc; text-decoration: none; margin-right: 20px; }
|
|
1242
|
-
</style>
|
|
1243
|
-
<h1>Welcome to My SPA</h1>
|
|
1244
|
-
<nav>
|
|
1245
|
-
<a href="/users">View All Users</a>
|
|
1246
|
-
</nav>
|
|
1247
|
-
`;
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
|
|
1251
|
-
return tagName;
|
|
1252
|
-
};
|
|
1253
|
-
|
|
1254
|
-
export default HomePage;
|
|
1255
|
-
```
|
|
1256
|
-
|
|
1257
|
-
```typescript
|
|
1258
|
-
// pages/UsersListPage.ts
|
|
1259
|
-
import { elementDefine, onConnectedInnerHtml, onConnectedBefore } from '@dooboostore/simple-web-component';
|
|
1260
|
-
import { Inject } from '@dooboostore/simple-boot';
|
|
1261
|
-
import { UserService } from '../services/UserService';
|
|
1262
|
-
|
|
1263
|
-
const UsersListPage = (w: Window) => {
|
|
1264
|
-
const tagName = 'users-list-page';
|
|
1265
|
-
const existing = w.customElements.get(tagName);
|
|
1266
|
-
if (existing) return tagName;
|
|
1267
|
-
|
|
1268
|
-
@elementDefine(tagName, { window: w })
|
|
1269
|
-
class UsersListPage extends w.HTMLElement {
|
|
1270
|
-
private users: any[] = [];
|
|
1271
|
-
|
|
1272
|
-
@onConnectedBefore
|
|
1273
|
-
onconstructor(@Inject({ symbol: UserService.SYMBOL }) userService: UserService) {
|
|
1274
|
-
this.users = userService.getAllUsers();
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
@onConnectedInnerHtml({ useShadow: true })
|
|
1278
|
-
render() {
|
|
1279
|
-
const usersList = this.users
|
|
1280
|
-
.map(u => `<li><a href="/users/${u.id}">${u.name}</a></li>`)
|
|
1281
|
-
.join('');
|
|
1282
|
-
|
|
1283
|
-
return `
|
|
1284
|
-
<style>
|
|
1285
|
-
:host { display: block; padding: 20px; }
|
|
1286
|
-
h1 { color: #333; }
|
|
1287
|
-
ul { list-style: none; padding: 0; }
|
|
1288
|
-
li { margin: 10px 0; }
|
|
1289
|
-
a { color: #0066cc; text-decoration: none; }
|
|
1290
|
-
</style>
|
|
1291
|
-
<h1>Users</h1>
|
|
1292
|
-
<ul>${usersList}</ul>
|
|
1293
|
-
<a href="/">Back to Home</a>
|
|
1294
|
-
`;
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
|
|
1298
|
-
return tagName;
|
|
1299
|
-
};
|
|
1300
|
-
|
|
1301
|
-
export default UsersListPage;
|
|
1302
|
-
```
|
|
1303
|
-
|
|
1304
|
-
#### Step 4-1. Reusable Components
|
|
1305
|
-
```typescript
|
|
1306
|
-
// components/UserCard.ts
|
|
1307
|
-
import { elementDefine, onConnectedInnerHtml, attributeThis } from '@dooboostore/simple-web-component';
|
|
1308
|
-
|
|
1309
|
-
const UserCard = (w: Window) => {
|
|
1310
|
-
const tagName = 'user-card';
|
|
1311
|
-
const existing = w.customElements.get(tagName);
|
|
1312
|
-
if (existing) return tagName;
|
|
1313
|
-
|
|
1314
|
-
@elementDefine(tagName, { window: w })
|
|
1315
|
-
class UserCard extends w.HTMLElement {
|
|
1316
|
-
@attributeThis('data-username')
|
|
1317
|
-
username: string;
|
|
1318
|
-
|
|
1319
|
-
@attributeThis('data-email')
|
|
1320
|
-
email: string;
|
|
1321
|
-
|
|
1322
|
-
@onConnectedInnerHtml({ useShadow: true })
|
|
1323
|
-
render() {
|
|
1324
|
-
return `
|
|
1325
|
-
<style>
|
|
1326
|
-
:host { display: block; }
|
|
1327
|
-
.card { border: 1px solid #ddd; border-radius: 4px; padding: 12px; }
|
|
1328
|
-
.name { font-weight: bold; font-size: 14px; }
|
|
1329
|
-
.email { color: #666; font-size: 12px; }
|
|
1330
|
-
</style>
|
|
1331
|
-
<div class="card">
|
|
1332
|
-
<div class="name">${this.username}</div>
|
|
1333
|
-
<div class="email">${this.email}</div>
|
|
1334
|
-
</div>
|
|
1335
|
-
`;
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
return tagName;
|
|
1340
|
-
};
|
|
1341
|
-
|
|
1342
|
-
export default UserCard;
|
|
1343
|
-
```
|
|
1344
|
-
|
|
1345
|
-
#### Step 5. Export Factories
|
|
1346
|
-
|
|
1347
|
-
**services/index.ts** - Export all service factories
|
|
1348
|
-
```typescript
|
|
1349
|
-
// services/index.ts
|
|
1350
|
-
import userServiceFactory from './UserService';
|
|
1351
|
-
|
|
1352
|
-
export const serviceFactories = [userServiceFactory];
|
|
1353
|
-
```
|
|
1354
|
-
|
|
1355
|
-
**components/index.ts** - Export all component factories
|
|
1356
|
-
```typescript
|
|
1357
|
-
// components/index.ts
|
|
1358
|
-
import UserCard from './UserCard';
|
|
1359
|
-
|
|
1360
|
-
export const componentFactories = [
|
|
1361
|
-
UserCard,
|
|
1362
|
-
];
|
|
1363
|
-
```
|
|
1364
|
-
|
|
1365
|
-
**pages/index.ts** - Export all page factories with router
|
|
1366
|
-
```typescript
|
|
1367
|
-
// pages/index.ts
|
|
1368
|
-
import rootRouterFactory from './RootRouter';
|
|
1369
|
-
import HomePage from './HomePage';
|
|
1370
|
-
import UsersListPage from './UsersListPage';
|
|
1371
|
-
import UserDetailPage from './UserDetailPage';
|
|
1372
|
-
|
|
1373
|
-
export const pageFactories = [
|
|
1374
|
-
rootRouterFactory, // Router first
|
|
1375
|
-
HomePage,
|
|
1376
|
-
UsersListPage,
|
|
1377
|
-
UserDetailPage
|
|
1378
|
-
];
|
|
1379
|
-
```
|
|
1380
|
-
|
|
1381
|
-
#### Step 6. Application Bootstrap
|
|
1382
|
-
```typescript
|
|
1383
|
-
// bootFactory.ts
|
|
1384
|
-
import { register } from '@dooboostore/simple-web-component';
|
|
1385
|
-
import { serviceFactories } from './services';
|
|
1386
|
-
import { componentFactories } from './components';
|
|
1387
|
-
import { pageFactories } from './pages';
|
|
1388
|
-
|
|
1389
|
-
export default (w: Window, container: symbol) => {
|
|
1390
|
-
// Initialize services with container (dependency injection)
|
|
1391
|
-
// Each factory receives the container symbol and registers its service in the DI container
|
|
1392
|
-
serviceFactories.forEach(serviceFactory => serviceFactory(container));
|
|
1393
|
-
|
|
1394
|
-
// Register pages and components
|
|
1395
|
-
register(w, [...pageFactories, ...componentFactories]);
|
|
1396
|
-
};
|
|
1397
|
-
```
|
|
1398
|
-
|
|
1399
|
-
#### Step 7. Application Entry Point
|
|
1400
|
-
```typescript
|
|
1401
|
-
// index.ts
|
|
1402
|
-
import 'reflect-metadata';
|
|
1403
|
-
import { SwcAppInterface, defineSwcAppAll } from '@dooboostore/simple-web-component';
|
|
1404
|
-
import bootFactory from './bootFactory';
|
|
1405
|
-
|
|
1406
|
-
const w = window;
|
|
1407
|
-
|
|
1408
|
-
w.document.addEventListener('DOMContentLoaded', async () => {
|
|
1409
|
-
const container = Symbol('app');
|
|
1410
|
-
|
|
1411
|
-
// Initialize services and components
|
|
1412
|
-
bootFactory(w, container);
|
|
1413
|
-
|
|
1414
|
-
// Register all SwcApp variants (swc-app, swc-app-body, swc-app-div, etc.)
|
|
1415
|
-
await defineSwcAppAll(w);
|
|
1416
|
-
|
|
1417
|
-
// Get app root element
|
|
1418
|
-
const appElement = w.document.querySelector('#app') as SwcAppInterface;
|
|
1419
|
-
|
|
1420
|
-
if (appElement && typeof appElement.connect === 'function') {
|
|
1421
|
-
// Connect SPA with routing and DI
|
|
1422
|
-
appElement.connect({
|
|
1423
|
-
path: '/',
|
|
1424
|
-
routeType: 'path',
|
|
1425
|
-
container: container,
|
|
1426
|
-
window: w,
|
|
1427
|
-
onEngineStarted: () => {
|
|
1428
|
-
console.log('🚀 Application started successfully');
|
|
1429
|
-
// Root router automatically initialized via @elementDefine
|
|
1430
|
-
}
|
|
1431
|
-
});
|
|
1432
|
-
} else {
|
|
1433
|
-
console.error('Failed to initialize SPA');
|
|
1434
|
-
}
|
|
1435
|
-
});
|
|
1436
|
-
```ow;
|
|
1437
|
-
|
|
1438
|
-
w.document.addEventListener('DOMContentLoaded', async () => {
|
|
1439
|
-
const container = Symbol('app');
|
|
1440
|
-
|
|
1441
|
-
// Initialize services and components
|
|
1442
|
-
bootFactory(w, container);
|
|
1443
|
-
|
|
1444
|
-
// Wait for customized built-in element to be defined
|
|
1445
|
-
await w.customElements.whenDefined('swc-app-body');
|
|
1446
|
-
|
|
1447
|
-
// Get app root element
|
|
1448
|
-
const appElement = w.document.querySelector('#app') as SwcAppInterface;
|
|
1449
|
-
|
|
1450
|
-
if (appElement && typeof appElement.connect === 'function') {
|
|
1451
|
-
// Connect SPA with routing and DI
|
|
1452
|
-
appElement.connect({
|
|
1453
|
-
path: '/',
|
|
1454
|
-
routeType: 'path',
|
|
1455
|
-
container: container,
|
|
1456
|
-
window: w,
|
|
1457
|
-
onEngineStarted: () => {
|
|
1458
|
-
console.log('🚀 Application started successfully');
|
|
1459
|
-
// Root router automatically initialized via @elementDefine
|
|
1460
|
-
}
|
|
1461
|
-
});
|
|
1462
|
-
} else {
|
|
1463
|
-
console.error('Failed to initialize SPA');
|
|
1464
|
-
}
|
|
1465
|
-
});
|
|
1466
|
-
```
|
|
1467
|
-
|
|
1468
|
-
#### Resulting Application Structure
|
|
1469
|
-
```
|
|
1470
|
-
src/
|
|
1471
|
-
├── index.ts # Entry point
|
|
1472
|
-
├── index.html # HTML root with <root-router>
|
|
1473
|
-
├── bootFactory.ts # Bootstrap services & components via register()
|
|
1474
|
-
├── services/
|
|
1475
|
-
│ ├── UserService.ts # Service factory (export default)
|
|
1476
|
-
│ └── index.ts # Export serviceFactories
|
|
1477
|
-
├── pages/
|
|
1478
|
-
│ ├── RootRouter.ts # Router factory (export default)
|
|
1479
|
-
│ ├── HomePage.ts # Page factory (export default)
|
|
1480
|
-
│ ├── UsersListPage.ts # Page factory (export default)
|
|
1481
|
-
│ ├── UserDetailPage.ts # Page factory (export default)
|
|
1482
|
-
│ └── index.ts # Export pageFactories
|
|
1483
|
-
└── components/
|
|
1484
|
-
├── UserCard.ts # Component factory (export default)
|
|
1485
|
-
└── index.ts # Export componentFactories
|
|
1486
|
-
```
|
|
1487
|
-
|
|
1488
|
-
**Key Flow:**
|
|
1489
|
-
1. HTML loads → `<root-router>` element already in DOM
|
|
1490
|
-
2. `index.ts` executes → calls `bootFactory`
|
|
1491
|
-
3. `bootFactory` registers services and components (including `rootRouterFactory`)
|
|
1492
|
-
4. App waits for `swc-app-body` to be defined via `defineSwcAppAll()`
|
|
1493
|
-
5. `appElement.connect()` initializes routing & DI
|
|
1494
|
-
6. `<root-router>` becomes active with `@subscribeSwcAppRouteChangeWhileConnected` + `swc-choose` template
|
|
1495
|
-
7. Route changes automatically trigger `swc-choose` re-evaluation and template re-rendering
|
|
1496
|
-
8. Template attributes like `user-id="{{= $value?.pathData?.id}}"` are dynamically populated with route params
|
|
1497
|
-
9. All components have access to injected services via `@onInitialize`
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
716
|
---
|
|
1501
717
|
|
|
1502
718
|
## ⚠️ Critical Rules
|
|
1503
719
|
|
|
1504
720
|
### DO NOT Use @Sim on Web Components
|
|
1505
|
-
`@
|
|
721
|
+
`@sim` is for **Services only**. Web Components should use `@elementDefine`.
|
|
1506
722
|
|
|
1507
723
|
```typescript
|
|
1508
724
|
// ✅ CORRECT
|
|
1509
|
-
@
|
|
725
|
+
@sim
|
|
1510
726
|
export class UserService { }
|
|
1511
727
|
|
|
1512
728
|
@elementDefine(tagName, { window: w })
|
|
1513
729
|
class UserWidget extends w.HTMLElement { }
|
|
1514
730
|
|
|
1515
731
|
// ❌ WRONG
|
|
1516
|
-
@
|
|
732
|
+
@sim
|
|
1517
733
|
@elementDefine(tagName, { window: w })
|
|
1518
734
|
class UserWidget extends w.HTMLElement { }
|
|
1519
735
|
```
|
|
1520
736
|
|
|
1521
|
-
### Factory Always Returns
|
|
737
|
+
### Factory Always Returns
|
|
1522
738
|
```typescript
|
|
1523
739
|
export default (w: Window) => {
|
|
1524
740
|
const tagName = 'my-element';
|
|
1525
741
|
const existing = w.customElements.get(tagName);
|
|
1526
|
-
if (existing) return
|
|
742
|
+
if (existing) return existing;
|
|
1527
743
|
|
|
1528
744
|
@elementDefine(tagName, { window: w })
|
|
1529
745
|
class MyElement extends w.HTMLElement { }
|
|
1530
746
|
|
|
1531
|
-
return
|
|
747
|
+
return existing;
|
|
1532
748
|
};
|
|
1533
749
|
```
|
|
1534
750
|
|
|
@@ -1536,7 +752,7 @@ export default (w: Window) => {
|
|
|
1536
752
|
```typescript
|
|
1537
753
|
// ✅ CORRECT
|
|
1538
754
|
@onInitialize
|
|
1539
|
-
onconstructor(@
|
|
755
|
+
onconstructor(@inject(Service.SYMBOL) service: Service) {
|
|
1540
756
|
this.service = service;
|
|
1541
757
|
}
|
|
1542
758
|
|
|
@@ -1548,19 +764,20 @@ constructor(private service: Service) { super(); } // Web Components can't have
|
|
|
1548
764
|
|
|
1549
765
|
## 📚 Examples
|
|
1550
766
|
|
|
1551
|
-
- **[Commerce (E-Commerce SPA)](
|
|
1552
|
-
- **[Stock (Market Dashboard)](
|
|
1553
|
-
- **[Accommodation (Reference Pattern)](
|
|
767
|
+
- **[Commerce (E-Commerce SPA)](https://github.com/dooboostore-develop/packages/tree/main/%40dooboostore/simple-web-component/examples/commerce)** - Full shopping cart example
|
|
768
|
+
- **[Stock (Market Dashboard)](https://github.com/dooboostore-develop/packages/tree/main/%40dooboostore/simple-web-component/examples/stock)** - Real-time data with routing
|
|
769
|
+
- **[Accommodation (Reference Pattern)](https://github.com/dooboostore-develop/packages/tree/main/%40dooboostore/simple-web-component/examples/accommodation)** - Standard setup pattern
|
|
1554
770
|
|
|
1555
771
|
---
|
|
1556
772
|
|
|
1557
773
|
## 🔗 Related Packages
|
|
1558
774
|
|
|
1559
|
-
- **[@dooboostore/simple-boot](
|
|
1560
|
-
- **[@dooboostore/core-web](
|
|
1561
|
-
- **[@dooboostore/dom-parser](
|
|
775
|
+
- **[@dooboostore/simple-boot](https://github.com/dooboostore-develop/packages/tree/main/%40dooboostore/simple-boot)** - DI & AOP Container
|
|
776
|
+
- **[@dooboostore/core-web](https://github.com/dooboostore-develop/packages/tree/main/%40dooboostore/core-web)** - Web Utilities & Router
|
|
777
|
+
- **[@dooboostore/dom-parser](https://github.com/dooboostore-develop/packages/tree/main/%40dooboostore/dom-parser)** - HTML Parsing & AST
|
|
1562
778
|
|
|
1563
779
|
---
|
|
1564
780
|
|
|
1565
781
|
## 📄 License
|
|
1566
|
-
MIT
|
|
782
|
+
[MIT License](https://github.com/dooboostore-develop/packages/tree/main/%40dooboostore/simple-web-component/LICENSE.md)
|
|
783
|
+
|