@relax.js/core 1.0.3 → 1.0.5
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/README.md +194 -188
- package/dist/DependencyInjection.d.ts +45 -27
- package/dist/collections/LinkedList.d.ts +9 -8
- package/dist/collections/index.js +1 -1
- package/dist/collections/index.js.map +3 -3
- package/dist/collections/index.mjs +1 -1
- package/dist/collections/index.mjs.map +3 -3
- package/dist/di/index.js +1 -1
- package/dist/di/index.js.map +3 -3
- package/dist/di/index.mjs +1 -1
- package/dist/di/index.mjs.map +3 -3
- package/dist/elements/index.js +1 -1
- package/dist/elements/index.js.map +1 -1
- package/dist/errors.d.ts +20 -0
- package/dist/forms/FormValidator.d.ts +3 -22
- package/dist/forms/ValidationRules.d.ts +4 -6
- package/dist/forms/index.js +1 -1
- package/dist/forms/index.js.map +4 -4
- package/dist/forms/index.mjs +1 -1
- package/dist/forms/index.mjs.map +4 -4
- package/dist/forms/setFormData.d.ts +39 -1
- package/dist/html/TableRenderer.d.ts +1 -0
- package/dist/html/index.js +1 -1
- package/dist/html/index.js.map +3 -3
- package/dist/html/index.mjs +1 -1
- package/dist/html/index.mjs.map +3 -3
- package/dist/html/template.d.ts +4 -0
- package/dist/http/ServerSentEvents.d.ts +1 -1
- package/dist/http/SimpleWebSocket.d.ts +1 -1
- package/dist/http/http.d.ts +1 -0
- package/dist/http/index.js +1 -1
- package/dist/http/index.js.map +3 -3
- package/dist/http/index.mjs +1 -1
- package/dist/http/index.mjs.map +3 -3
- package/dist/i18n/icu.d.ts +1 -1
- package/dist/i18n/index.js +1 -1
- package/dist/i18n/index.js.map +2 -2
- package/dist/i18n/index.mjs +1 -1
- package/dist/i18n/index.mjs.map +2 -2
- package/dist/index.js +3 -3
- package/dist/index.js.map +3 -3
- package/dist/index.mjs +3 -3
- package/dist/index.mjs.map +3 -3
- package/dist/routing/NavigateRouteEvent.d.ts +4 -4
- package/dist/routing/index.js +3 -3
- package/dist/routing/index.js.map +3 -3
- package/dist/routing/index.mjs +3 -3
- package/dist/routing/index.mjs.map +3 -3
- package/dist/routing/navigation.d.ts +1 -1
- package/dist/routing/routeTargetRegistry.d.ts +1 -0
- package/dist/routing/types.d.ts +2 -1
- package/dist/templates/NodeTemplate.d.ts +3 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/index.js.map +3 -3
- package/dist/utils/index.mjs +1 -1
- package/dist/utils/index.mjs.map +3 -3
- package/docs/Architecture.md +333 -333
- package/docs/DependencyInjection.md +277 -237
- package/docs/Errors.md +87 -87
- package/docs/GettingStarted.md +238 -231
- package/docs/Pipes.md +5 -5
- package/docs/Translations.md +167 -312
- package/docs/WhyRelaxjs.md +336 -336
- package/docs/api.json +93193 -0
- package/docs/elements/dom.md +102 -102
- package/docs/forms/creating-form-components.md +924 -924
- package/docs/forms/form-api.md +94 -94
- package/docs/forms/forms.md +99 -99
- package/docs/forms/patterns.md +311 -311
- package/docs/forms/reading-writing.md +465 -365
- package/docs/forms/validation.md +351 -351
- package/docs/html/TableRenderer.md +291 -291
- package/docs/html/html.md +175 -175
- package/docs/html/index.md +54 -54
- package/docs/html/template.md +422 -422
- package/docs/http/HttpClient.md +459 -459
- package/docs/http/ServerSentEvents.md +184 -184
- package/docs/http/index.md +109 -109
- package/docs/i18n/i18n.md +49 -4
- package/docs/i18n/intl-standard.md +178 -178
- package/docs/routing/RouteLink.md +98 -98
- package/docs/routing/Routing.md +332 -332
- package/docs/routing/layouts.md +207 -207
- package/docs/setup/bootstrapping.md +154 -0
- package/docs/setup/build-and-deploy.md +183 -0
- package/docs/setup/project-structure.md +170 -0
- package/docs/setup/vite.md +175 -0
- package/docs/utilities.md +143 -143
- package/package.json +4 -2
|
@@ -1,292 +1,292 @@
|
|
|
1
|
-
# TableRenderer for Web Components
|
|
2
|
-
|
|
3
|
-
`TableRenderer` is a small helper class that renders table rows inside Web Components using a `<template>` and an array of data. It supports data binding, row updates, and declarative button handlers that call methods on your Web Component.
|
|
4
|
-
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
### Minimal Web Component Setup
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
import { TableRenderer } from '
|
|
11
|
-
|
|
12
|
-
class UserTable extends HTMLElement {
|
|
13
|
-
private renderer!: TableRenderer;
|
|
14
|
-
|
|
15
|
-
constructor() {
|
|
16
|
-
super();
|
|
17
|
-
this.attachShadow({ mode: 'open' });
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
connectedCallback() {
|
|
21
|
-
this.shadowRoot!.innerHTML = `
|
|
22
|
-
<table>
|
|
23
|
-
<tbody></tbody>
|
|
24
|
-
</table>
|
|
25
|
-
<template id="row-template">
|
|
26
|
-
<tr>
|
|
27
|
-
<td data-field="name"></td>
|
|
28
|
-
<td data-field="role"></td>
|
|
29
|
-
</tr>
|
|
30
|
-
</template>
|
|
31
|
-
`;
|
|
32
|
-
|
|
33
|
-
const table = this.shadowRoot!.querySelector('table')!;
|
|
34
|
-
const template = this.shadowRoot!.querySelector('#row-template') as HTMLTemplateElement;
|
|
35
|
-
|
|
36
|
-
this.renderer = new TableRenderer(table, template, 'id', this);
|
|
37
|
-
this.renderer.render([
|
|
38
|
-
{ id: 1, name: 'Alice', role: 'Admin' },
|
|
39
|
-
{ id: 2, name: 'Bob', role: 'User' }
|
|
40
|
-
]);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
customElements.define('user-table', UserTable);
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
**What this does:**
|
|
48
|
-
- Clones the `<template>` row for each item
|
|
49
|
-
- Fills in `<td data-field="name">` using data values
|
|
50
|
-
- Associates each row with its data via the `id` field
|
|
51
|
-
|
|
52
|
-
##
|
|
53
|
-
|
|
54
|
-
Use `update()` to refresh a single row without re-rendering everything:
|
|
55
|
-
|
|
56
|
-
```ts
|
|
57
|
-
this.renderer.update({ id: 2, name: 'Bob Smith', role: 'Moderator' });
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
If a matching row exists (`idColumn` = `"id"`), it will update in-place. If not, a new row is added.
|
|
61
|
-
|
|
62
|
-
##
|
|
63
|
-
|
|
64
|
-
Add buttons in the template using `onclick="methodName"`.
|
|
65
|
-
|
|
66
|
-
### Updated Template
|
|
67
|
-
|
|
68
|
-
```html
|
|
69
|
-
<template id="row-template">
|
|
70
|
-
<tr>
|
|
71
|
-
<td data-field="name"></td>
|
|
72
|
-
<td data-field="role"></td>
|
|
73
|
-
<td>
|
|
74
|
-
<button onclick="edit">Edit</button>
|
|
75
|
-
</td>
|
|
76
|
-
</tr>
|
|
77
|
-
</template>
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Component Method
|
|
81
|
-
|
|
82
|
-
```ts
|
|
83
|
-
edit(data: any, event: MouseEvent) {
|
|
84
|
-
console.log('Editing user:', data);
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
**Behavior:**
|
|
89
|
-
- `edit` is automatically bound for each row
|
|
90
|
-
- It receives the row's `data` as the first argument
|
|
91
|
-
- The second argument is the `MouseEvent`
|
|
92
|
-
|
|
93
|
-
##
|
|
94
|
-
|
|
95
|
-
You can call methods with literal arguments. The remaining arguments are always `data` and `event`.
|
|
96
|
-
|
|
97
|
-
### Template Example
|
|
98
|
-
|
|
99
|
-
```html
|
|
100
|
-
<button onclick="remove('soft')">Delete</button>
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
### Component Method
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
remove(mode: string, data: any, event: MouseEvent) {
|
|
107
|
-
console.log(`Removing (${mode})`, data);
|
|
108
|
-
}
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
**How It Works:**
|
|
112
|
-
- The string `'soft'` is passed as the first argument
|
|
113
|
-
- `data` and `event` are appended automatically
|
|
114
|
-
|
|
115
|
-
##
|
|
116
|
-
|
|
117
|
-
The `.render()` method replaces all rows and cleans up memory:
|
|
118
|
-
|
|
119
|
-
```ts
|
|
120
|
-
this.renderer.render([
|
|
121
|
-
{ id: 3, name: 'Charlie', role: 'User' }
|
|
122
|
-
]);
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
- Clears old rows and their event listeners
|
|
126
|
-
- Rebuilds new rows from the template and data
|
|
127
|
-
|
|
128
|
-
## 6. Column Sorting with TableSorter
|
|
129
|
-
|
|
130
|
-
`TableSorter` adds click-to-sort behavior to table headers. Click a `<th>` to cycle through ascending, descending, and unsorted.
|
|
131
|
-
|
|
132
|
-
### Setup
|
|
133
|
-
|
|
134
|
-
```ts
|
|
135
|
-
import { TableSorter } from '
|
|
136
|
-
|
|
137
|
-
class UserTable extends HTMLElement {
|
|
138
|
-
connectedCallback() {
|
|
139
|
-
this.innerHTML = `
|
|
140
|
-
<table>
|
|
141
|
-
<thead>
|
|
142
|
-
<tr>
|
|
143
|
-
<th name="name">Name</th>
|
|
144
|
-
<th name="role">Role</th>
|
|
145
|
-
<th name="age">Age</th>
|
|
146
|
-
</tr>
|
|
147
|
-
</thead>
|
|
148
|
-
<tbody></tbody>
|
|
149
|
-
</table>
|
|
150
|
-
`;
|
|
151
|
-
|
|
152
|
-
const table = this.querySelector('table')!;
|
|
153
|
-
const sorter = new TableSorter(table, this);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
**How it works:**
|
|
159
|
-
- Headers must have a `name` attribute matching the data field
|
|
160
|
-
- Click cycles: none -> ascending (arrow up) -> descending (arrow down) -> none
|
|
161
|
-
- Sort indicators (arrows) are added/removed automatically
|
|
162
|
-
|
|
163
|
-
### Handling Sort Events
|
|
164
|
-
|
|
165
|
-
`TableSorter` dispatches a `SortChangeEvent` on the component:
|
|
166
|
-
|
|
167
|
-
```ts
|
|
168
|
-
import { SortChangeEvent, SortColumn } from '
|
|
169
|
-
|
|
170
|
-
this.addEventListener('sortchange', (e: SortChangeEvent) => {
|
|
171
|
-
const sortColumns: SortColumn[] = e.detail;
|
|
172
|
-
// sortColumns = [{ column: 'name', direction: 'asc' }]
|
|
173
|
-
this.fetchSortedData(sortColumns);
|
|
174
|
-
});
|
|
175
|
-
```
|
|
176
|
-
|
|
177
|
-
### API
|
|
178
|
-
|
|
179
|
-
| Method | Description |
|
|
180
|
-
|--------|-------------|
|
|
181
|
-
| `getSortColumns()` | Returns current sort state as `SortColumn[]` |
|
|
182
|
-
| `clear()` | Resets all sorting and emits a `sortchange` event |
|
|
183
|
-
|
|
184
|
-
```typescript
|
|
185
|
-
type SortColumn = { column: string; direction: 'asc' | 'desc' };
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
## 7. Pagination with Pager
|
|
189
|
-
|
|
190
|
-
`Pager` renders page navigation buttons (Previous, page numbers, Next) and dispatches events on page change.
|
|
191
|
-
|
|
192
|
-
### Setup
|
|
193
|
-
|
|
194
|
-
```ts
|
|
195
|
-
import { Pager, PageSelectedEvent } from '
|
|
196
|
-
|
|
197
|
-
class UserTable extends HTMLElement {
|
|
198
|
-
private pager!: Pager;
|
|
199
|
-
|
|
200
|
-
connectedCallback() {
|
|
201
|
-
this.innerHTML = `
|
|
202
|
-
<table><!-- ... --></table>
|
|
203
|
-
<div id="pager"></div>
|
|
204
|
-
`;
|
|
205
|
-
|
|
206
|
-
const pagerContainer = this.querySelector('#pager')!;
|
|
207
|
-
this.pager = new Pager(pagerContainer, 100, 10); // 100 items, 10 per page
|
|
208
|
-
|
|
209
|
-
pagerContainer.addEventListener('pageselected', (e: PageSelectedEvent) => {
|
|
210
|
-
this.loadPage(e.detail);
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
async loadPage(page: number) {
|
|
215
|
-
const data = await fetchUsers(page);
|
|
216
|
-
this.renderer.render(data);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
### Updating Total Count
|
|
222
|
-
|
|
223
|
-
When the total number of items changes (e.g. after filtering):
|
|
224
|
-
|
|
225
|
-
```ts
|
|
226
|
-
this.pager.update(newTotalCount);
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### API
|
|
230
|
-
|
|
231
|
-
| Method | Description |
|
|
232
|
-
|--------|-------------|
|
|
233
|
-
| `update(totalCount)` | Updates total count and re-renders page buttons |
|
|
234
|
-
| `getCurrentPage()` | Returns the current page number |
|
|
235
|
-
|
|
236
|
-
### Combined Example: TableRenderer + TableSorter + Pager
|
|
237
|
-
|
|
238
|
-
```ts
|
|
239
|
-
import { TableRenderer, TableSorter, Pager } from '
|
|
240
|
-
|
|
241
|
-
class ProductList extends HTMLElement {
|
|
242
|
-
private renderer!: TableRenderer;
|
|
243
|
-
private sorter!: TableSorter;
|
|
244
|
-
private pager!: Pager;
|
|
245
|
-
|
|
246
|
-
connectedCallback() {
|
|
247
|
-
this.innerHTML = `
|
|
248
|
-
<table>
|
|
249
|
-
<thead>
|
|
250
|
-
<tr>
|
|
251
|
-
<th name="name">Name</th>
|
|
252
|
-
<th name="price">Price</th>
|
|
253
|
-
</tr>
|
|
254
|
-
</thead>
|
|
255
|
-
<tbody></tbody>
|
|
256
|
-
</table>
|
|
257
|
-
<template id="row-tpl">
|
|
258
|
-
<tr>
|
|
259
|
-
<td data-field="name"></td>
|
|
260
|
-
<td data-field="price"></td>
|
|
261
|
-
<td><button onclick="edit">Edit</button></td>
|
|
262
|
-
</tr>
|
|
263
|
-
</template>
|
|
264
|
-
<div id="pager"></div>
|
|
265
|
-
`;
|
|
266
|
-
|
|
267
|
-
const table = this.querySelector('table')!;
|
|
268
|
-
const template = this.querySelector('#row-tpl') as HTMLTemplateElement;
|
|
269
|
-
|
|
270
|
-
this.renderer = new TableRenderer(table, template, 'id', this);
|
|
271
|
-
this.sorter = new TableSorter(table, this);
|
|
272
|
-
this.pager = new Pager(this.querySelector('#pager')!, 0, 20);
|
|
273
|
-
|
|
274
|
-
this.addEventListener('sortchange', () => this.reload());
|
|
275
|
-
this.querySelector('#pager')!.addEventListener('pageselected', () => this.reload());
|
|
276
|
-
|
|
277
|
-
this.reload();
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
async reload() {
|
|
281
|
-
const page = this.pager.getCurrentPage();
|
|
282
|
-
const sort = this.sorter.getSortColumns();
|
|
283
|
-
const { items, total } = await fetchProducts(page, sort);
|
|
284
|
-
this.renderer.render(items);
|
|
285
|
-
this.pager.update(total);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
edit(data: any) {
|
|
289
|
-
console.log('Edit product:', data);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
1
|
+
# TableRenderer for Web Components
|
|
2
|
+
|
|
3
|
+
`TableRenderer` is a small helper class that renders table rows inside Web Components using a `<template>` and an array of data. It supports data binding, row updates, and declarative button handlers that call methods on your Web Component.
|
|
4
|
+
|
|
5
|
+
## 1. Getting Started
|
|
6
|
+
|
|
7
|
+
### Minimal Web Component Setup
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { TableRenderer } from '@relax.js/core/html';
|
|
11
|
+
|
|
12
|
+
class UserTable extends HTMLElement {
|
|
13
|
+
private renderer!: TableRenderer;
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.attachShadow({ mode: 'open' });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
connectedCallback() {
|
|
21
|
+
this.shadowRoot!.innerHTML = `
|
|
22
|
+
<table>
|
|
23
|
+
<tbody></tbody>
|
|
24
|
+
</table>
|
|
25
|
+
<template id="row-template">
|
|
26
|
+
<tr>
|
|
27
|
+
<td data-field="name"></td>
|
|
28
|
+
<td data-field="role"></td>
|
|
29
|
+
</tr>
|
|
30
|
+
</template>
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
const table = this.shadowRoot!.querySelector('table')!;
|
|
34
|
+
const template = this.shadowRoot!.querySelector('#row-template') as HTMLTemplateElement;
|
|
35
|
+
|
|
36
|
+
this.renderer = new TableRenderer(table, template, 'id', this);
|
|
37
|
+
this.renderer.render([
|
|
38
|
+
{ id: 1, name: 'Alice', role: 'Admin' },
|
|
39
|
+
{ id: 2, name: 'Bob', role: 'User' }
|
|
40
|
+
]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
customElements.define('user-table', UserTable);
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**What this does:**
|
|
48
|
+
- Clones the `<template>` row for each item
|
|
49
|
+
- Fills in `<td data-field="name">` using data values
|
|
50
|
+
- Associates each row with its data via the `id` field
|
|
51
|
+
|
|
52
|
+
## 2. Updating a Row
|
|
53
|
+
|
|
54
|
+
Use `update()` to refresh a single row without re-rendering everything:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
this.renderer.update({ id: 2, name: 'Bob Smith', role: 'Moderator' });
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If a matching row exists (`idColumn` = `"id"`), it will update in-place. If not, a new row is added.
|
|
61
|
+
|
|
62
|
+
## 3. Button Event Binding
|
|
63
|
+
|
|
64
|
+
Add buttons in the template using `onclick="methodName"`.
|
|
65
|
+
|
|
66
|
+
### Updated Template
|
|
67
|
+
|
|
68
|
+
```html
|
|
69
|
+
<template id="row-template">
|
|
70
|
+
<tr>
|
|
71
|
+
<td data-field="name"></td>
|
|
72
|
+
<td data-field="role"></td>
|
|
73
|
+
<td>
|
|
74
|
+
<button onclick="edit">Edit</button>
|
|
75
|
+
</td>
|
|
76
|
+
</tr>
|
|
77
|
+
</template>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Component Method
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
edit(data: any, event: MouseEvent) {
|
|
84
|
+
console.log('Editing user:', data);
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Behavior:**
|
|
89
|
+
- `edit` is automatically bound for each row
|
|
90
|
+
- It receives the row's `data` as the first argument
|
|
91
|
+
- The second argument is the `MouseEvent`
|
|
92
|
+
|
|
93
|
+
## 4. Using Arguments in Handlers
|
|
94
|
+
|
|
95
|
+
You can call methods with literal arguments. The remaining arguments are always `data` and `event`.
|
|
96
|
+
|
|
97
|
+
### Template Example
|
|
98
|
+
|
|
99
|
+
```html
|
|
100
|
+
<button onclick="remove('soft')">Delete</button>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Component Method
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
remove(mode: string, data: any, event: MouseEvent) {
|
|
107
|
+
console.log(`Removing (${mode})`, data);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**How It Works:**
|
|
112
|
+
- The string `'soft'` is passed as the first argument
|
|
113
|
+
- `data` and `event` are appended automatically
|
|
114
|
+
|
|
115
|
+
## 5. Re-rendering Safely
|
|
116
|
+
|
|
117
|
+
The `.render()` method replaces all rows and cleans up memory:
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
this.renderer.render([
|
|
121
|
+
{ id: 3, name: 'Charlie', role: 'User' }
|
|
122
|
+
]);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- Clears old rows and their event listeners
|
|
126
|
+
- Rebuilds new rows from the template and data
|
|
127
|
+
|
|
128
|
+
## 6. Column Sorting with TableSorter
|
|
129
|
+
|
|
130
|
+
`TableSorter` adds click-to-sort behavior to table headers. Click a `<th>` to cycle through ascending, descending, and unsorted.
|
|
131
|
+
|
|
132
|
+
### Setup
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
import { TableSorter } from '@relax.js/core/html';
|
|
136
|
+
|
|
137
|
+
class UserTable extends HTMLElement {
|
|
138
|
+
connectedCallback() {
|
|
139
|
+
this.innerHTML = `
|
|
140
|
+
<table>
|
|
141
|
+
<thead>
|
|
142
|
+
<tr>
|
|
143
|
+
<th name="name">Name</th>
|
|
144
|
+
<th name="role">Role</th>
|
|
145
|
+
<th name="age">Age</th>
|
|
146
|
+
</tr>
|
|
147
|
+
</thead>
|
|
148
|
+
<tbody></tbody>
|
|
149
|
+
</table>
|
|
150
|
+
`;
|
|
151
|
+
|
|
152
|
+
const table = this.querySelector('table')!;
|
|
153
|
+
const sorter = new TableSorter(table, this);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**How it works:**
|
|
159
|
+
- Headers must have a `name` attribute matching the data field
|
|
160
|
+
- Click cycles: none -> ascending (arrow up) -> descending (arrow down) -> none
|
|
161
|
+
- Sort indicators (arrows) are added/removed automatically
|
|
162
|
+
|
|
163
|
+
### Handling Sort Events
|
|
164
|
+
|
|
165
|
+
`TableSorter` dispatches a `SortChangeEvent` on the component:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
import { SortChangeEvent, SortColumn } from '@relax.js/core/html';
|
|
169
|
+
|
|
170
|
+
this.addEventListener('sortchange', (e: SortChangeEvent) => {
|
|
171
|
+
const sortColumns: SortColumn[] = e.detail;
|
|
172
|
+
// sortColumns = [{ column: 'name', direction: 'asc' }]
|
|
173
|
+
this.fetchSortedData(sortColumns);
|
|
174
|
+
});
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### API
|
|
178
|
+
|
|
179
|
+
| Method | Description |
|
|
180
|
+
|--------|-------------|
|
|
181
|
+
| `getSortColumns()` | Returns current sort state as `SortColumn[]` |
|
|
182
|
+
| `clear()` | Resets all sorting and emits a `sortchange` event |
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
type SortColumn = { column: string; direction: 'asc' | 'desc' };
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## 7. Pagination with Pager
|
|
189
|
+
|
|
190
|
+
`Pager` renders page navigation buttons (Previous, page numbers, Next) and dispatches events on page change.
|
|
191
|
+
|
|
192
|
+
### Setup
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import { Pager, PageSelectedEvent } from '@relax.js/core/html';
|
|
196
|
+
|
|
197
|
+
class UserTable extends HTMLElement {
|
|
198
|
+
private pager!: Pager;
|
|
199
|
+
|
|
200
|
+
connectedCallback() {
|
|
201
|
+
this.innerHTML = `
|
|
202
|
+
<table><!-- ... --></table>
|
|
203
|
+
<div id="pager"></div>
|
|
204
|
+
`;
|
|
205
|
+
|
|
206
|
+
const pagerContainer = this.querySelector('#pager')!;
|
|
207
|
+
this.pager = new Pager(pagerContainer, 100, 10); // 100 items, 10 per page
|
|
208
|
+
|
|
209
|
+
pagerContainer.addEventListener('pageselected', (e: PageSelectedEvent) => {
|
|
210
|
+
this.loadPage(e.detail);
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async loadPage(page: number) {
|
|
215
|
+
const data = await fetchUsers(page);
|
|
216
|
+
this.renderer.render(data);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Updating Total Count
|
|
222
|
+
|
|
223
|
+
When the total number of items changes (e.g. after filtering):
|
|
224
|
+
|
|
225
|
+
```ts
|
|
226
|
+
this.pager.update(newTotalCount);
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### API
|
|
230
|
+
|
|
231
|
+
| Method | Description |
|
|
232
|
+
|--------|-------------|
|
|
233
|
+
| `update(totalCount)` | Updates total count and re-renders page buttons |
|
|
234
|
+
| `getCurrentPage()` | Returns the current page number |
|
|
235
|
+
|
|
236
|
+
### Combined Example: TableRenderer + TableSorter + Pager
|
|
237
|
+
|
|
238
|
+
```ts
|
|
239
|
+
import { TableRenderer, TableSorter, Pager } from '@relax.js/core/html';
|
|
240
|
+
|
|
241
|
+
class ProductList extends HTMLElement {
|
|
242
|
+
private renderer!: TableRenderer;
|
|
243
|
+
private sorter!: TableSorter;
|
|
244
|
+
private pager!: Pager;
|
|
245
|
+
|
|
246
|
+
connectedCallback() {
|
|
247
|
+
this.innerHTML = `
|
|
248
|
+
<table>
|
|
249
|
+
<thead>
|
|
250
|
+
<tr>
|
|
251
|
+
<th name="name">Name</th>
|
|
252
|
+
<th name="price">Price</th>
|
|
253
|
+
</tr>
|
|
254
|
+
</thead>
|
|
255
|
+
<tbody></tbody>
|
|
256
|
+
</table>
|
|
257
|
+
<template id="row-tpl">
|
|
258
|
+
<tr>
|
|
259
|
+
<td data-field="name"></td>
|
|
260
|
+
<td data-field="price"></td>
|
|
261
|
+
<td><button onclick="edit">Edit</button></td>
|
|
262
|
+
</tr>
|
|
263
|
+
</template>
|
|
264
|
+
<div id="pager"></div>
|
|
265
|
+
`;
|
|
266
|
+
|
|
267
|
+
const table = this.querySelector('table')!;
|
|
268
|
+
const template = this.querySelector('#row-tpl') as HTMLTemplateElement;
|
|
269
|
+
|
|
270
|
+
this.renderer = new TableRenderer(table, template, 'id', this);
|
|
271
|
+
this.sorter = new TableSorter(table, this);
|
|
272
|
+
this.pager = new Pager(this.querySelector('#pager')!, 0, 20);
|
|
273
|
+
|
|
274
|
+
this.addEventListener('sortchange', () => this.reload());
|
|
275
|
+
this.querySelector('#pager')!.addEventListener('pageselected', () => this.reload());
|
|
276
|
+
|
|
277
|
+
this.reload();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async reload() {
|
|
281
|
+
const page = this.pager.getCurrentPage();
|
|
282
|
+
const sort = this.sorter.getSortColumns();
|
|
283
|
+
const { items, total } = await fetchProducts(page, sort);
|
|
284
|
+
this.renderer.render(items);
|
|
285
|
+
this.pager.update(total);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
edit(data: any) {
|
|
289
|
+
console.log('Edit product:', data);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
292
|
```
|