@dooboostore/dom-render 1.0.100 → 1.0.103
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/DomRender.d.ts +21 -6
- package/DomRender.js +54 -23
- package/DomRenderProxy.d.ts +7 -3
- package/DomRenderProxy.js +120 -49
- package/README.MD +469 -112
- package/components/ComponentBase.d.ts +12 -10
- package/components/ComponentBase.js +92 -46
- package/components/ComponentSet.d.ts +2 -2
- package/components/appender/Appender.d.ts +2 -2
- package/components/checkbox/CheckBox.d.ts +4 -4
- package/components/choose/Choose.d.ts +4 -4
- package/components/choose/Choose.js +0 -16
- package/components/details/Details.d.ts +5 -5
- package/components/forOf/ForOf.d.ts +2 -2
- package/components/if/If.d.ts +2 -2
- package/components/index.d.ts +26 -24
- package/components/index.js +2 -1
- package/components/input/Input.d.ts +2 -2
- package/components/input/Input.js +27 -24
- package/components/promise/PromiseSwitch.d.ts +6 -6
- package/components/promise/PromiseSwitch.js +15 -0
- package/components/radio/Radio.d.ts +4 -4
- package/components/router/Route.d.ts +32 -0
- package/components/router/Route.js +116 -0
- package/components/select/Select.d.ts +3 -3
- package/components/select/Select.js +2 -2
- package/components/this/This.d.ts +9 -2
- package/components/this/This.js +24 -2
- package/components/timer/Timer.d.ts +2 -2
- package/configs/Config.d.ts +6 -1
- package/decorators/DomRenderNoProxy.d.ts +2 -0
- package/decorators/DomRenderNoProxy.js +15 -0
- package/dist/bundle.js +2 -0
- package/events/EventManager.d.ts +16 -4
- package/events/EventManager.js +320 -140
- package/index.d.ts +48 -1
- package/index.js +40 -0
- package/lifecycle/OnDestroyRender.d.ts +1 -1
- package/lifecycle/OnProxyDomRender.d.ts +1 -0
- package/lifecycle/OnProxyDomRender.js +1 -1
- package/operators/DrForOf.js +2 -1
- package/operators/DrIf.js +3 -2
- package/operators/DrTargetElement.js +2 -1
- package/operators/DrTargetElementIsElement.d.ts +0 -5
- package/operators/DrTargetElementIsElement.js +2 -1
- package/operators/DrThis.js +40 -11
- package/operators/OperatorExecuter.js +3 -1
- package/package.json +5 -4
- package/rawsets/Attrs.d.ts +2 -0
- package/rawsets/RawSet.d.ts +2 -2
- package/rawsets/RawSet.js +48 -33
- package/routers/HashRouter.d.ts +5 -4
- package/routers/HashRouter.js +8 -5
- package/routers/PathRouter.d.ts +5 -4
- package/routers/PathRouter.js +6 -4
- package/routers/Router.d.ts +22 -8
- package/routers/Router.js +44 -24
package/README.MD
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
DOM-RENDER
|
|
2
2
|
===
|
|
3
|
-
[](https://www.npmjs.com/package/dom-render) [](LICENSE.md)
|
|
3
|
+
[](https://www.npmjs.com/package/@dooboostore/dom-render) [](LICENSE.md)
|
|
4
4
|
* view template engine
|
|
5
5
|
* Dom control and reorder and render
|
|
6
6
|
* all internal variables are managed by proxy. (DomRenderProxy)
|
|
7
7
|
|
|
8
8
|
# 🚀 Quick start
|
|
9
|
-
```
|
|
10
|
-
|
|
9
|
+
```typescript
|
|
10
|
+
import { DomRender } from '@dooboostore/dom-render';
|
|
11
|
+
|
|
12
|
+
class AppData {
|
|
13
|
+
name: string = 'my name is dom-render';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const data = new AppData();
|
|
17
|
+
DomRender.run(data, document.querySelector('#app')!);
|
|
11
18
|
```
|
|
12
19
|
```html
|
|
13
20
|
<!DOCTYPE html>
|
|
14
21
|
<html lang="en">
|
|
15
|
-
<head
|
|
22
|
+
<head>
|
|
16
23
|
<meta charset="UTF-8">
|
|
17
24
|
<title>Title</title>
|
|
18
25
|
</head>
|
|
19
26
|
<body id="app">
|
|
20
27
|
${this.name}$
|
|
21
|
-
<script src="https://cdn.jsdelivr.net/npm/dom-render@1.0.86/dist/bundle.js"></script>
|
|
22
|
-
<script>
|
|
23
|
-
let data = {
|
|
24
|
-
name: 'my name is dom-render'
|
|
25
|
-
};
|
|
26
|
-
data = DomRender.run(data , document.querySelector('#app'));
|
|
27
|
-
</script>
|
|
28
28
|
</body>
|
|
29
29
|
</html>
|
|
30
30
|
```
|
|
@@ -42,14 +42,18 @@ ${this.name}$
|
|
|
42
42
|
<body id="app">
|
|
43
43
|
${this.name}$ <!-- outout: <i>my name is dom-render</i> -->
|
|
44
44
|
#{this.name}# <!-- outout text is italic: my name is dom-render -->
|
|
45
|
-
<script>
|
|
46
|
-
let data = {
|
|
47
|
-
name: '<i>my name is dom-render</i>'
|
|
48
|
-
};
|
|
49
|
-
data = DomRender.run(data , document.querySelector('#app'));
|
|
50
|
-
</script>
|
|
51
45
|
</body>
|
|
52
46
|
```
|
|
47
|
+
```typescript
|
|
48
|
+
import { DomRender } from '@dooboostore/dom-render';
|
|
49
|
+
|
|
50
|
+
class AppData {
|
|
51
|
+
name: string = '<i>my name is dom-render</i>';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const data = new AppData();
|
|
55
|
+
DomRender.run(data, document.querySelector('#app')!);
|
|
56
|
+
```
|
|
53
57
|
</details>
|
|
54
58
|
|
|
55
59
|
# dom-render attributes
|
|
@@ -107,7 +111,7 @@ export class IndexComponent implements OnInitRender, OnDestroyRender, OnInit, On
|
|
|
107
111
|
```
|
|
108
112
|
```css
|
|
109
113
|
.#uuid# {
|
|
110
|
-
color:${@this@.color}
|
|
114
|
+
color:${@this@.color}$ ;
|
|
111
115
|
}
|
|
112
116
|
```
|
|
113
117
|
```css
|
|
@@ -141,14 +145,18 @@ uuid: rawSetUUID
|
|
|
141
145
|
<body id="app">
|
|
142
146
|
<div dr-if="true">true</div> <!-- render -->
|
|
143
147
|
<div dr-if="this.gender === 'M'">gender: M</div> <!-- No Render -->
|
|
144
|
-
<script>
|
|
145
|
-
let data = {
|
|
146
|
-
gender: 'F',
|
|
147
|
-
};
|
|
148
|
-
data = DomRender.run(data , document.querySelector('#app'));
|
|
149
|
-
</script>
|
|
150
148
|
</body>
|
|
151
149
|
```
|
|
150
|
+
```typescript
|
|
151
|
+
import { DomRender } from '@dooboostore/dom-render';
|
|
152
|
+
|
|
153
|
+
class AppData {
|
|
154
|
+
gender: string = 'F';
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const data = new AppData();
|
|
158
|
+
DomRender.run(data, document.querySelector('#app')!);
|
|
159
|
+
```
|
|
152
160
|
</details>
|
|
153
161
|
|
|
154
162
|
|
|
@@ -386,6 +394,40 @@ export class MyComponent extends ComponentBase<{ 'title': string, 'show-icon': s
|
|
|
386
394
|
```
|
|
387
395
|
When attributes change, the `converter` function will be executed before the value is assigned to the property. For `showIcon`, the string `"true"` or `"false"` will be converted to a boolean. For `createdAt`, the date string will be converted to a `Date` object.
|
|
388
396
|
|
|
397
|
+
### Binding to Methods
|
|
398
|
+
|
|
399
|
+
The `@attribute` decorator can also be applied to methods. When the corresponding HTML attribute changes, the decorated method will be invoked with the new attribute value as its argument. This allows you to execute custom logic directly in response to attribute changes.
|
|
400
|
+
|
|
401
|
+
**Component (`MyComponent.ts`) - Method Example**
|
|
402
|
+
```typescript
|
|
403
|
+
import { ComponentBase, attribute } from '@dooboostore/dom-render';
|
|
404
|
+
|
|
405
|
+
export class MyComponent extends ComponentBase<{ 'data-value': string }> {
|
|
406
|
+
public receivedData: string = '';
|
|
407
|
+
|
|
408
|
+
@attribute({ name: 'data-value' })
|
|
409
|
+
onDataValueChange(newValue: string): void {
|
|
410
|
+
this.receivedData = `Method received: ${newValue}`;
|
|
411
|
+
console.log(this.receivedData);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Template (`index.html`) - Method Example**
|
|
417
|
+
```html
|
|
418
|
+
<my-component data-value="initial"></my-component>
|
|
419
|
+
|
|
420
|
+
<p>${@this@.receivedData}$</p>
|
|
421
|
+
|
|
422
|
+
<script>
|
|
423
|
+
setTimeout(() => {
|
|
424
|
+
const comp = document.querySelector('my-component');
|
|
425
|
+
comp.setAttribute('data-value', 'updated value');
|
|
426
|
+
}, 1000);
|
|
427
|
+
</script>
|
|
428
|
+
```
|
|
429
|
+
When `data-value` attribute changes, the `onDataValueChange` method will be called with the new value.
|
|
430
|
+
|
|
389
431
|
</details>
|
|
390
432
|
|
|
391
433
|
## Element Query with `@query` decorator
|
|
@@ -396,6 +438,7 @@ You can get a reference to DOM elements within your component's template using t
|
|
|
396
438
|
|
|
397
439
|
- If the decorated property is **not an array type**, it returns the **first** matching element (like `querySelector`).
|
|
398
440
|
- If the decorated property **is an array type** (e.g., `HTMLElement[]`), it returns **all** matching elements as an array (like `querySelectorAll`).
|
|
441
|
+
- When applied to a **method**, the method will be called with the queried element(s) as arguments after the component has been rendered. If the method expects a single element, the first match is passed. If it expects an array, all matches are passed.
|
|
399
442
|
|
|
400
443
|
This is useful for directly accessing and manipulating elements you need to interact with from your component's logic.
|
|
401
444
|
|
|
@@ -428,6 +471,22 @@ export class MyComponent extends ComponentBase<{}> {
|
|
|
428
471
|
});
|
|
429
472
|
}
|
|
430
473
|
}
|
|
474
|
+
|
|
475
|
+
// Method query: This method will be called with the first element matching '.my-button'
|
|
476
|
+
@query('.my-button')
|
|
477
|
+
private setupButton(button: HTMLButtonElement) {
|
|
478
|
+
if (button) {
|
|
479
|
+
button.addEventListener('click', () => alert('Button clicked from method query!'));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Method query: This method will be called with an array of all elements matching 'span.item'
|
|
484
|
+
@query('span.item')
|
|
485
|
+
private processItems(spans: HTMLSpanElement[]) {
|
|
486
|
+
spans.forEach(span => {
|
|
487
|
+
span.style.fontWeight = 'bold';
|
|
488
|
+
});
|
|
489
|
+
}
|
|
431
490
|
}
|
|
432
491
|
```
|
|
433
492
|
|
|
@@ -441,9 +500,11 @@ export class MyComponent extends ComponentBase<{}> {
|
|
|
441
500
|
<li>Item 2</li>
|
|
442
501
|
<li>Item 3</li>
|
|
443
502
|
</ul>
|
|
503
|
+
<button class="my-button">Click Me</button>
|
|
504
|
+
<p><span class="item">First Span</span> <span class="item">Second Span</span></p>
|
|
444
505
|
</div>
|
|
445
506
|
```
|
|
446
|
-
The `@query` decorator will assign the corresponding element(s) to the decorated property. The property will be `null`/`undefined` (for single query) or an empty array (for multiple query) if no elements are found or before the component is rendered.
|
|
507
|
+
The `@query` decorator will assign the corresponding element(s) to the decorated property or pass them to the decorated method. The property will be `null`/`undefined` (for single query) or an empty array (for multiple query) if no elements are found or before the component is rendered. For methods, they will only be called if matching elements are found.
|
|
447
508
|
|
|
448
509
|
</details>
|
|
449
510
|
|
|
@@ -531,6 +592,49 @@ dr-value-link: <input type="text" dr-value-link="@this@.office.addr.street"> <br
|
|
|
531
592
|
```
|
|
532
593
|
</details>
|
|
533
594
|
|
|
595
|
+
## Element State Attributes
|
|
596
|
+
<details>
|
|
597
|
+
<summary>dr-checked, dr-selected, dr-readonly, dr-disabled<strong>🔻(click)</strong></summary>
|
|
598
|
+
|
|
599
|
+
These attributes allow you to bind boolean values directly to the `checked`, `selected`, `readonly`, and `disabled` properties of HTML elements.
|
|
600
|
+
|
|
601
|
+
* `dr-checked`: Binds the `checked` attribute of an input (checkbox/radio) to a boolean value.
|
|
602
|
+
* `dr-selected`: Binds the `selected` attribute of an option to a boolean value.
|
|
603
|
+
* `dr-readonly`: Binds the `readonly` attribute of an input to a boolean value.
|
|
604
|
+
* `dr-disabled`: Binds the `disabled` attribute of an input/button/etc. to a boolean value.
|
|
605
|
+
|
|
606
|
+
### Example
|
|
607
|
+
|
|
608
|
+
```html
|
|
609
|
+
<!-- Example for dr-checked -->
|
|
610
|
+
<input type="checkbox" dr-checked="@this@.isChecked">
|
|
611
|
+
<p>Checkbox is: ${@this@.isChecked}$</p>
|
|
612
|
+
|
|
613
|
+
<!-- Example for dr-selected -->
|
|
614
|
+
<select>
|
|
615
|
+
<option value="apple" dr-selected="@this@.isAppleSelected">Apple</option>
|
|
616
|
+
<option value="banana" dr-selected="@this@.isBananaSelected">Banana</option>
|
|
617
|
+
</select>
|
|
618
|
+
|
|
619
|
+
<!-- Example for dr-readonly -->
|
|
620
|
+
<input type="text" dr-readonly="@this@.isReadOnly" value="Some text">
|
|
621
|
+
|
|
622
|
+
<!-- Example for dr-disabled -->
|
|
623
|
+
<button dr-disabled="@this@.isDisabled">Click Me</button>
|
|
624
|
+
<input type="text" dr-disabled="@this@.isDisabled" value="Disabled input">
|
|
625
|
+
```
|
|
626
|
+
```typescript
|
|
627
|
+
class Data {
|
|
628
|
+
isChecked = true;
|
|
629
|
+
isAppleSelected = false;
|
|
630
|
+
isBananaSelected = true;
|
|
631
|
+
isReadOnly = false;
|
|
632
|
+
isDisabled = true;
|
|
633
|
+
}
|
|
634
|
+
// ... DomRender.run(new Data(), ...)
|
|
635
|
+
```
|
|
636
|
+
</details>
|
|
637
|
+
|
|
534
638
|
## dr-on-rendered-init
|
|
535
639
|
- rendered call
|
|
536
640
|
|
|
@@ -560,7 +664,7 @@ dr-value-link: <input type="text" dr-value-link="@this@.office.addr.street"> <br
|
|
|
560
664
|
<summary>dr-style<strong>🔻(click)</strong></summary>
|
|
561
665
|
|
|
562
666
|
```html
|
|
563
|
-
<div dr-style="{'background-image': 'url(\''+@this@.my?.image+'\')'}" >aa</div>
|
|
667
|
+
<div dr-style="{'background-image': 'url(\' '+@this@.my?.image+'\')'}" >aa</div>
|
|
564
668
|
<div dr-style="{fontSize: @this@.age + 'px'}"> style </div>
|
|
565
669
|
<div dr-style="{'font-size': '20px'}"> style</div>
|
|
566
670
|
<div dr-style="'font-size: ' + @this@.age +'px; margin: ' + @this@.age + 'px'"> style </div>
|
|
@@ -637,19 +741,24 @@ dr-value-link: <input type="text" dr-value-link="@this@.office.addr.street"> <br
|
|
|
637
741
|
age: <input name="age">
|
|
638
742
|
<button type="submit">submit</button>
|
|
639
743
|
</form>
|
|
640
|
-
<script>
|
|
641
|
-
let data = {
|
|
642
|
-
form: {},
|
|
643
|
-
submit() {
|
|
644
|
-
console.log(this.form);
|
|
645
|
-
}
|
|
646
|
-
};
|
|
647
|
-
data = DomRender.run(data , document.querySelector('#app'));
|
|
648
|
-
</script>
|
|
649
744
|
</body>
|
|
745
|
+
```
|
|
746
|
+
```typescript
|
|
747
|
+
import { DomRender } from '@dooboostore/dom-render';
|
|
748
|
+
|
|
749
|
+
class FormData {
|
|
750
|
+
form: any = {};
|
|
751
|
+
|
|
752
|
+
submit() {
|
|
753
|
+
console.log(this.form);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
const data = new FormData();
|
|
758
|
+
DomRender.run(data, document.querySelector('#app')!);
|
|
759
|
+
```
|
|
650
760
|
<!-- 💥 submit call -->
|
|
651
761
|
<!-- console: {name: 'name data', age: 'age data'} -->
|
|
652
|
-
```
|
|
653
762
|
</details>
|
|
654
763
|
|
|
655
764
|
<details>
|
|
@@ -662,24 +771,32 @@ dr-value-link: <input type="text" dr-value-link="@this@.office.addr.street"> <br
|
|
|
662
771
|
age: <input name="age">
|
|
663
772
|
<button type="submit">submit</button>
|
|
664
773
|
</form>
|
|
665
|
-
<script>
|
|
666
|
-
const form = new FormValidator();
|
|
667
|
-
form.name = new NotEmptyValidator();
|
|
668
|
-
form.age = new NotEmptyValidator();
|
|
669
|
-
let data = {
|
|
670
|
-
form,
|
|
671
|
-
submit() {
|
|
672
|
-
if (this.form.valid()){
|
|
673
|
-
console.log('valid');
|
|
674
|
-
} else {
|
|
675
|
-
console.log('inValid');
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
};
|
|
679
|
-
data = DomRender.run(data , document.querySelector('#app'));
|
|
680
|
-
</script>
|
|
681
774
|
</body>
|
|
682
775
|
```
|
|
776
|
+
```typescript
|
|
777
|
+
import { DomRender } from '@dooboostore/dom-render';
|
|
778
|
+
import { FormValidator, NotEmptyValidator } from '@dooboostore/dom-render/validator';
|
|
779
|
+
|
|
780
|
+
class FormDataWithValidator {
|
|
781
|
+
form: FormValidator = new FormValidator();
|
|
782
|
+
|
|
783
|
+
constructor() {
|
|
784
|
+
this.form.name = new NotEmptyValidator();
|
|
785
|
+
this.form.age = new NotEmptyValidator();
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
submit() {
|
|
789
|
+
if (this.form.valid()){
|
|
790
|
+
console.log('valid');
|
|
791
|
+
} else {
|
|
792
|
+
console.log('inValid');
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
const data = new FormDataWithValidator();
|
|
798
|
+
DomRender.run(data, document.querySelector('#app')!);
|
|
799
|
+
```
|
|
683
800
|
validator
|
|
684
801
|
- Validator (abstract)
|
|
685
802
|
- ValidatorArray (abstract)
|
|
@@ -720,88 +837,328 @@ validator
|
|
|
720
837
|
|
|
721
838
|
## Route
|
|
722
839
|
<details>
|
|
723
|
-
<summary>Route
|
|
840
|
+
<summary><strong>Route (click)</strong></summary>
|
|
841
|
+
|
|
842
|
+
Declarative routing is supported using the `dr-route-match` and `dr-route-regexp` components. This allows you to conditionally render parts of your UI based on the current URL path.
|
|
843
|
+
|
|
844
|
+
- [Example](./examples/route)
|
|
724
845
|
|
|
725
|
-
|
|
846
|
+
### 1. Configuration
|
|
847
|
+
|
|
848
|
+
To enable routing, you must set the `routerType` in your `DomRender` configuration.
|
|
849
|
+
|
|
850
|
+
- `routerType`: Can be `'hash'`, `'path'`, or `'none'` (default).
|
|
726
851
|
|
|
727
|
-
* config routerType: 'hash' | 'path' | 'none' (default: 'none')
|
|
728
852
|
```typescript
|
|
729
|
-
//
|
|
730
|
-
|
|
731
|
-
|
|
853
|
+
// In your main setup file (e.g., index.ts)
|
|
854
|
+
import { DomRender, DomRenderRunConfig } from '@dooboostore/dom-render';
|
|
855
|
+
|
|
856
|
+
// ... your root object class (e.g., Index)
|
|
857
|
+
|
|
858
|
+
const config: DomRenderRunConfig<Index> = {
|
|
859
|
+
window: window,
|
|
860
|
+
routerType: 'path' // or 'hash'
|
|
732
861
|
};
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
const data = DomRender.run(new Data(), document.querySelector('#app')!, config);
|
|
862
|
+
|
|
863
|
+
const domRender = new DomRender({
|
|
864
|
+
rootObject: new Index(),
|
|
865
|
+
target: document.querySelector('#app')!,
|
|
866
|
+
config: config
|
|
867
|
+
}, {autoGo: '/'}); // autoGo navigates to the initial path
|
|
740
868
|
```
|
|
869
|
+
|
|
870
|
+
### 2. Template Usage
|
|
871
|
+
|
|
872
|
+
Use the `<dr-route-match>` and `<dr-route-regexp>` components in your HTML template to define which content appears for which route.
|
|
873
|
+
|
|
874
|
+
- **`<dr-route-match>`**: Renders its content when the path matches the `value` attribute. It supports simple paths and path parameters (e.g., `/{id}`).
|
|
875
|
+
- **`<dr-route-regexp>`**: Renders its content when the path matches the regular expression in the `value` attribute.
|
|
876
|
+
|
|
741
877
|
```html
|
|
878
|
+
<!-- index.html -->
|
|
742
879
|
<header>
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
<
|
|
747
|
-
<div>
|
|
748
|
-
<button dr-event-click="$router.go('/second', {secondata: 555})">second</button>
|
|
749
|
-
<button dr-event-click="$router.go('/second/5')">second/1</button>
|
|
750
|
-
<button dr-event-click="$router.go('/second/wow')">second/2</button>
|
|
751
|
-
</div>
|
|
752
|
-
<div><button dr-event-click="$router.go('/detail/25?name=zzz')">detail</button></div>
|
|
753
|
-
</div>
|
|
880
|
+
<!-- Navigation buttons -->
|
|
881
|
+
<button dr-event-click="$router?.go({path:'/'})">Main</button>
|
|
882
|
+
<button dr-event-click="$router?.go({path:'/second'})">Second</button>
|
|
883
|
+
<button dr-event-click="$router?.go({path:'/detail/42'})">Detail 42</button>
|
|
754
884
|
</header>
|
|
755
|
-
|
|
885
|
+
|
|
756
886
|
<main>
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
<
|
|
764
|
-
|
|
887
|
+
<!-- Content for the root path -->
|
|
888
|
+
<dr-route-match value="/">
|
|
889
|
+
<h1>Main Page</h1>
|
|
890
|
+
</dr-route-match>
|
|
891
|
+
|
|
892
|
+
<!-- Content for the /second path -->
|
|
893
|
+
<dr-route-match value="/second">
|
|
894
|
+
<h1>Second Page</h1>
|
|
895
|
+
</dr-route-match>
|
|
896
|
+
|
|
897
|
+
<!-- Content for a path with a numeric parameter -->
|
|
898
|
+
<dr-route-match value="/detail/{id:[0-9]+}">
|
|
899
|
+
<h1>Detail Page</h1>
|
|
900
|
+
<p>The router's pathData will contain the matched 'id'.</p>
|
|
901
|
+
</dr-route-match>
|
|
902
|
+
|
|
903
|
+
<!-- Content using a regular expression -->
|
|
904
|
+
<dr-route-regexp value="/second/[0-9]?$">
|
|
905
|
+
<p>This content appears for paths like /second/1, /second/2, etc.</p>
|
|
906
|
+
</dr-route-regexp>
|
|
765
907
|
</main>
|
|
766
|
-
<hr>
|
|
767
|
-
<footer>footer</footer>
|
|
768
908
|
```
|
|
909
|
+
|
|
910
|
+
The `$router` object is automatically available in your templates for navigation (`$router.go(...)`) and testing (`$router.test(...)`).
|
|
911
|
+
|
|
912
|
+
</details>
|
|
913
|
+
|
|
914
|
+
## Accessing the Root Object Proxy
|
|
915
|
+
|
|
916
|
+
After initializing `DomRender`, you can get a proxied reference to your `rootObject`. Any changes made to this proxy will be automatically detected and reflected in the DOM. This is useful for updating state from outside your component's methods, such as in `setTimeout` or in response to external events.
|
|
917
|
+
|
|
769
918
|
```typescript
|
|
770
|
-
|
|
771
|
-
|
|
919
|
+
import { DomRender } from '@dooboostore/dom-render';
|
|
920
|
+
|
|
921
|
+
class App {
|
|
922
|
+
message = 'Hello, World!';
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
// 1. Initialize DomRender
|
|
926
|
+
const domRender = new DomRender({
|
|
927
|
+
rootObject: new App(),
|
|
928
|
+
target: document.querySelector('#app')!
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
// 2. Get the proxy to the rootObject
|
|
932
|
+
const data = domRender.rootObject;
|
|
933
|
+
|
|
934
|
+
// 3. Now you can modify the state from anywhere
|
|
935
|
+
setTimeout(() => {
|
|
936
|
+
// This change will automatically update the UI
|
|
937
|
+
data.message = 'Hello, DomRender!';
|
|
938
|
+
}, 2000);
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
```html
|
|
942
|
+
<!-- index.html -->
|
|
943
|
+
<div id="app">
|
|
944
|
+
${@this@.message}$
|
|
945
|
+
</div>
|
|
946
|
+
```
|
|
947
|
+
|
|
948
|
+
## Core Components
|
|
949
|
+
This section describes the built-in components provided by `dom-render`.
|
|
950
|
+
|
|
951
|
+
<details>
|
|
952
|
+
<summary><strong>ComponentBase & Decorators (<code>@attribute</code>, <code>@query</code>, <code>@event</code>)</strong>🔻(click)</summary>
|
|
953
|
+
|
|
954
|
+
`ComponentBase` is the foundation for creating custom components that can interact with the `dom-render` lifecycle and attributes. When you extend `ComponentBase`, you gain access to powerful decorators for linking your class to the template.
|
|
955
|
+
|
|
956
|
+
- **`@attribute`**: Binds a class property to an HTML attribute. It can automatically convert values and even bind to methods.
|
|
957
|
+
- **`@query`**: Injects DOM elements from your component's template into class properties or method arguments.
|
|
958
|
+
- **`@event`**: Binds a class method to a DOM event on an element within the template, handling listener cleanup automatically.
|
|
772
959
|
|
|
773
|
-
|
|
774
|
-
|
|
960
|
+
*For detailed examples of these decorators, please see their dedicated sections above in the documentation.*
|
|
961
|
+
|
|
962
|
+
</details>
|
|
963
|
+
|
|
964
|
+
<details>
|
|
965
|
+
<summary><strong>ComponentSet</strong>🔻(click)</summary>
|
|
966
|
+
|
|
967
|
+
`ComponentSet` is a wrapper class that pairs a component instance with its own template and styles. It's the primary mechanism for dynamically rendering and swapping components within a `dr-this` block.
|
|
968
|
+
|
|
969
|
+
### Usage
|
|
970
|
+
```typescript
|
|
971
|
+
import { ComponentSet } from '@dooboostore/dom-render';
|
|
972
|
+
|
|
973
|
+
// Define two simple components
|
|
974
|
+
class ComponentA { message = 'This is Component A'; }
|
|
975
|
+
class ComponentB { message = 'Now you see Component B'; }
|
|
976
|
+
|
|
977
|
+
class App {
|
|
978
|
+
// Create ComponentSet instances for each component
|
|
979
|
+
componentA = new ComponentSet(new ComponentA(), { template: '<h1>${@this@.message}$</h1>' });
|
|
980
|
+
componentB = new ComponentSet(new ComponentB(), { template: '<h2>${@this@.message}$</h2>' });
|
|
981
|
+
|
|
982
|
+
// This property will hold the currently active component
|
|
983
|
+
activeComponent: ComponentSet;
|
|
984
|
+
|
|
985
|
+
constructor() {
|
|
986
|
+
this.activeComponent = this.componentA;
|
|
775
987
|
}
|
|
988
|
+
|
|
989
|
+
showA() { this.activeComponent = this.componentA; }
|
|
990
|
+
showB() { this.activeComponent = this.componentB; }
|
|
991
|
+
}
|
|
992
|
+
```
|
|
993
|
+
|
|
994
|
+
```html
|
|
995
|
+
<!-- This div will dynamically render the component held by 'activeComponent' -->
|
|
996
|
+
<div dr-this="@this@.activeComponent"></div>
|
|
997
|
+
|
|
998
|
+
<button dr-event-click="@this@.showA()">Show Component A</button>
|
|
999
|
+
<button dr-event-click="@this@.showB()">Show Component B</button>
|
|
1000
|
+
```
|
|
1001
|
+
</details>
|
|
1002
|
+
|
|
1003
|
+
<details>
|
|
1004
|
+
<summary><strong><code>dr-this</code></strong>🔻(click)</summary>
|
|
1005
|
+
|
|
1006
|
+
The `dr-this` component is the target for rendering a `ComponentSet`. It dynamically renders the component instance and template defined in the `ComponentSet` object it's bound to.
|
|
1007
|
+
|
|
1008
|
+
See the `ComponentSet` example above for a practical demonstration.
|
|
1009
|
+
|
|
1010
|
+
</details>
|
|
1011
|
+
|
|
1012
|
+
<details>
|
|
1013
|
+
<summary><strong><code>dr-choose</code> / <code>dr-choose-when</code> / <code>dr-choose-other-wise</code></strong>🔻(click)</summary>
|
|
1014
|
+
|
|
1015
|
+
Provides a "switch-case" control flow for rendering blocks of HTML. It evaluates a data object and renders the first `dr-choose-when` block whose `test` condition returns true. If no `when` blocks match, the `dr-choose-other-wise` block is rendered.
|
|
1016
|
+
|
|
1017
|
+
### Usage
|
|
1018
|
+
```typescript
|
|
1019
|
+
class App {
|
|
1020
|
+
status: 'loading' | 'success' | 'error' = 'loading';
|
|
776
1021
|
}
|
|
777
1022
|
```
|
|
1023
|
+
```html
|
|
1024
|
+
<dr-choose data="${@this@.status}$">
|
|
1025
|
+
<dr-choose-when test="(status) => status === 'loading'">
|
|
1026
|
+
<p>Loading...</p>
|
|
1027
|
+
</dr-choose-when>
|
|
1028
|
+
<dr-choose-when test="(status) => status === 'success'">
|
|
1029
|
+
<p>Data loaded successfully!</p>
|
|
1030
|
+
</dr-choose-when>
|
|
1031
|
+
<dr-choose-other-wise>
|
|
1032
|
+
<p>An unknown error occurred.</p>
|
|
1033
|
+
</dr-choose-other-wise>
|
|
1034
|
+
</dr-choose>
|
|
1035
|
+
```
|
|
1036
|
+
</details>
|
|
1037
|
+
|
|
1038
|
+
<details>
|
|
1039
|
+
<summary><strong><code>dr-promise-switch</code></strong>🔻(click)</summary>
|
|
1040
|
+
|
|
1041
|
+
Declaratively handles the different states of a JavaScript `Promise`. It provides child components to render content for `pending`, `fulfilled`, and `rejected` states.
|
|
1042
|
+
|
|
1043
|
+
### Child Components
|
|
1044
|
+
- `<dr-promise-switch-pending>`: Shown while the promise is in flight.
|
|
1045
|
+
- `<dr-promise-switch-fulfilled>`: Shown when the promise resolves successfully. The resolved data is available inside via `#component#.data`.
|
|
1046
|
+
- `<dr-promise-switch-rejected>`: Shown when the promise is rejected. The error is available inside via `#component#.data`.
|
|
1047
|
+
- `<dr-promise-switch-default>`: Shown before the promise is triggered.
|
|
1048
|
+
|
|
1049
|
+
### Usage
|
|
778
1050
|
```typescript
|
|
779
|
-
|
|
780
|
-
|
|
1051
|
+
class App {
|
|
1052
|
+
userPromise = null;
|
|
781
1053
|
|
|
782
|
-
|
|
783
|
-
|
|
1054
|
+
fetchUser() {
|
|
1055
|
+
this.userPromise = fetch('/api/user').then(res => res.json());
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
```
|
|
1059
|
+
```html
|
|
1060
|
+
<button dr-event-click="@this@.fetchUser()">Fetch User</button>
|
|
1061
|
+
|
|
1062
|
+
<dr-promise-switch data="${@this@.userPromise}$">
|
|
1063
|
+
<dr-promise-switch-pending>
|
|
1064
|
+
<p>Fetching user data...</p>
|
|
1065
|
+
</dr-promise-switch-pending>
|
|
1066
|
+
<dr-promise-switch-fulfilled>
|
|
1067
|
+
<p>Welcome, ${#component#.data.name}$!</p>
|
|
1068
|
+
</dr-promise-switch-fulfilled>
|
|
1069
|
+
<dr-promise-switch-rejected>
|
|
1070
|
+
<p>Failed to fetch user: ${#component#.data.message}$</p>
|
|
1071
|
+
</dr-promise-switch-rejected>
|
|
1072
|
+
</dr-promise-switch>
|
|
1073
|
+
```
|
|
1074
|
+
</details>
|
|
784
1075
|
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
}
|
|
1076
|
+
<details>
|
|
1077
|
+
<summary><strong><code>dr-checkbox</code></strong>🔻(click)</summary>
|
|
788
1078
|
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
1079
|
+
A wrapper for creating a custom, stylable checkbox. It uses child components to define the appearance for checked and unchecked states.
|
|
1080
|
+
|
|
1081
|
+
### Child Components
|
|
1082
|
+
- `<dr-checkbox-checked>`: Content to show when the box is checked.
|
|
1083
|
+
- `<dr-checkbox-unchecked>`: Content to show when the box is unchecked.
|
|
1084
|
+
|
|
1085
|
+
### Usage
|
|
1086
|
+
```typescript
|
|
1087
|
+
class App {
|
|
1088
|
+
agreed = false;
|
|
792
1089
|
}
|
|
793
1090
|
```
|
|
1091
|
+
```html
|
|
1092
|
+
<dr-checkbox name="terms" checked="${@this@.agreed}$" change="${(checked) => @this@.agreed = checked}$">
|
|
1093
|
+
<dr-checkbox-checked>
|
|
1094
|
+
<!-- Using FontAwesome icons as an example -->
|
|
1095
|
+
<i class="fas fa-check-square"></i> I agree to the terms.
|
|
1096
|
+
</dr-checkbox-checked>
|
|
1097
|
+
<dr-checkbox-unchecked>
|
|
1098
|
+
<i class="far fa-square"></i> I agree to the terms.
|
|
1099
|
+
</dr-checkbox-unchecked>
|
|
1100
|
+
</dr-checkbox>
|
|
1101
|
+
|
|
1102
|
+
<p>Agreed: ${@this@.agreed}$</p>
|
|
1103
|
+
```
|
|
1104
|
+
</details>
|
|
794
1105
|
|
|
1106
|
+
<details>
|
|
1107
|
+
<summary><strong><code>dr-details</code></strong>🔻(click)</summary>
|
|
1108
|
+
|
|
1109
|
+
An enhanced version of the native `<details>` element. It provides child components for the summary and body, and includes extra functionality like closing when the user clicks outside of it.
|
|
1110
|
+
|
|
1111
|
+
### Child Components
|
|
1112
|
+
- `<dr-details-summary>`: The clickable header of the details element.
|
|
1113
|
+
- `<dr-details-body>`: The collapsible content.
|
|
1114
|
+
- `<dr-details-form>`: A special body that is a form, with features like automatic reset on toggle.
|
|
1115
|
+
|
|
1116
|
+
### Usage
|
|
1117
|
+
```html
|
|
1118
|
+
<dr-details>
|
|
1119
|
+
<dr-details-summary>
|
|
1120
|
+
<h3>More Information</h3>
|
|
1121
|
+
</dr-details-summary>
|
|
1122
|
+
<dr-details-body>
|
|
1123
|
+
<p>Here is the hidden content that appears on click.</p>
|
|
1124
|
+
</dr-details-body>
|
|
1125
|
+
</dr-details>
|
|
1126
|
+
```
|
|
1127
|
+
</details>
|
|
1128
|
+
|
|
1129
|
+
<details>
|
|
1130
|
+
<summary><strong><code>dr-input</code></strong>🔻(click)</summary>
|
|
1131
|
+
|
|
1132
|
+
A custom input component that enhances a standard `<input>` with powerful features like debouncing and distinct value change detection. This is useful for performance-intensive tasks like live search.
|
|
1133
|
+
|
|
1134
|
+
### Attributes
|
|
1135
|
+
- `debounceTime`: (Optional) The time in milliseconds to wait after the user stops typing before firing the `input` event.
|
|
1136
|
+
- `distinct`: (Optional) If set to `true`, the `input` event will only fire if the new value is different from the previous one.
|
|
1137
|
+
- `input`: A callback function that receives the debounced/distinct value.
|
|
1138
|
+
|
|
1139
|
+
### Usage
|
|
795
1140
|
```typescript
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
1141
|
+
class App {
|
|
1142
|
+
searchQuery = '';
|
|
1143
|
+
|
|
1144
|
+
handleSearch(query: string) {
|
|
1145
|
+
console.log('Searching for:', query);
|
|
1146
|
+
this.searchQuery = query;
|
|
1147
|
+
}
|
|
803
1148
|
}
|
|
804
1149
|
```
|
|
1150
|
+
```html
|
|
1151
|
+
<label>Search:</label>
|
|
1152
|
+
<dr-input
|
|
1153
|
+
type="text"
|
|
1154
|
+
class="search-input"
|
|
1155
|
+
debounceTime="300"
|
|
1156
|
+
distinct="true"
|
|
1157
|
+
input="${(value) => @this@.handleSearch(value)}$"
|
|
1158
|
+
></dr-input>
|
|
1159
|
+
|
|
1160
|
+
<p>Current query: ${@this@.searchQuery}$</p>
|
|
1161
|
+
```
|
|
805
1162
|
</details>
|
|
806
1163
|
|
|
807
1164
|
## Messenger (Data transmission)
|
|
@@ -866,7 +1223,7 @@ const rangeArray = new Range(100,55, 10).toArray();
|
|
|
866
1223
|
const appender = new Appender<number>([1, 2]);
|
|
867
1224
|
appender.push(3, 4)
|
|
868
1225
|
for (const data of appender) {
|
|
869
|
-
console.log('----appender item
|
|
1226
|
+
console.log('----appender item---', data);
|
|
870
1227
|
}
|
|
871
1228
|
```
|
|
872
1229
|
</details>
|
|
@@ -880,10 +1237,10 @@ for (const data of appender) {
|
|
|
880
1237
|
{
|
|
881
1238
|
name: 'dom-render'
|
|
882
1239
|
onBeforeReturnSet: (name: string, value: any, fullpath: string[]) => {
|
|
883
|
-
console.log('set name
|
|
1240
|
+
console.log('set name--', name, value, fullpath);
|
|
884
1241
|
}
|
|
885
1242
|
onBeforeReturnGet: (name: string, value: any, fullpath: string[]) => {
|
|
886
|
-
console.log('get name
|
|
1243
|
+
console.log('get name--', name, value, fullpath);
|
|
887
1244
|
}
|
|
888
1245
|
}
|
|
889
1246
|
```
|
|
@@ -1107,4 +1464,4 @@ lazy loading rollup config
|
|
|
1107
1464
|
|
|
1108
1465
|
# License
|
|
1109
1466
|
* MIT
|
|
1110
|
-
* visualkhh@gmail.com
|
|
1467
|
+
* visualkhh@gmail.com
|