spiderly 19.12.4 → 19.14.0
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/agent/docs/angular-customization/index.md +34 -2
- package/agent/docs/entity-design/index.md +2 -2
- package/fesm2022/spiderly.mjs +697 -638
- package/fesm2022/spiderly.mjs.map +1 -1
- package/lib/components/spiderly-data-table/spiderly-data-table.component.d.ts +66 -3
- package/package.json +1 -1
- package/styles/components/spiderly-data-table/spiderly-data-table.component.scss +10 -0
|
@@ -268,9 +268,41 @@ The table renders the store as a **chip bar** above the header: one chip per *ap
|
|
|
268
268
|
|
|
269
269
|
**Views** (`[views]`, `TableView[]`) are saved questions rendered as tabs above the bar: each `apply` receives the cleared store (a view is a state, not an addition) and writes with `setAndCommit`; a view whose apply is a function of *now* ("received today") declares `transient: true` so selecting it re-derives instead of restoring yesterday's answer. Column layout (visibility, order, widths, wrap) persists per view. A stored view id whose view is not in `[views]` yet (async-licensed views) is adopted when it arrives — unless the operator picked another tab meanwhile — and a restored transient view re-derives its question. **Sort is view state too**: entering a view resolves the user's per-view sort override ?? the view's declared `sort` ?? the table's `defaultSortField` ?? unsorted — it never inherits the previous tab's ordering, so give the catch-all first view a table-level default when "newest first" isn't what you want there.
|
|
270
270
|
|
|
271
|
+
**A view tab can show a count**: `count?: number | null` renders a badge beside the label (`null` or absent shows none, `0` shows one). The table never computes it — run your own count query beside each list load and rebuild `[views]` with the fresh numbers, keeping each view's `id` stable. A rebuild neither re-enters the active view nor sends a request: a stored view id is adopted only from a change that *brings* that view, and the tabs are tracked by `id`, so the focused tab keeps focus.
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
this.views = this.views.map((view) => ({ ...view, count: counts[view.id] }));
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**A count can honor each view's saved filters**: the table's `viewFilterPayload(viewId)` returns the `filters` payload that view's own list request carries, so a count query that applies it counts the rows that tab lists (on screen for the active view, after a click for the others), including bar filters the user saved on that view. The active view answers with the live filters; any other view with what a click would enter: its saved filters, else its `apply` (always `apply` for a transient view). It switches nothing and sends no request. `apply` runs on a scratch store for this, so it must write only into the store it receives. It returns `{}` on a table without `[filters]` and `null` for an id not in `[views]` (yet). Call it beside each list load; a non-static `@ViewChild` is still unresolved during the first one, so hand the table in from a template reference:
|
|
278
|
+
|
|
279
|
+
```html
|
|
280
|
+
<spiderly-data-table #table [views]="views" (onLazyLoad)="refreshCounts(table)" ...></spiderly-data-table>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
refreshCounts(table: SpiderlyDataTableComponent): void {
|
|
285
|
+
const filtersByView: Record<string, Record<string, FilterRule[]>> = {};
|
|
286
|
+
for (const view of this.views) {
|
|
287
|
+
const filters = table.viewFilterPayload(view.id);
|
|
288
|
+
if (filters) filtersByView[view.id] = filters; // null: the table does not hold this view yet
|
|
289
|
+
}
|
|
290
|
+
// send filtersByView with your count query, then rebuild [views] with the counts
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**A view can declare its columns**: `columns` is a field → visible map that overrides `Column.visible` in that view only, naming just the deviation — a column it does not name keeps its `Column.visible`. Visibility resolves per field as the user's own per-view choice ?? the view's `columns` ?? `Column.visible`; the user's choice is stored only where it differs from the view's declaration, *Reset to default* returns to the declaration, and `lockVisible` columns always show. To swap one column for another, declare them next to each other in `cols` (the swapped-in one `visible: false`) and the view's column takes the other's place. Type the row as the second generic to check the field names.
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
const views: TableView<OrderFilters, Order>[] = [
|
|
298
|
+
{ id: 'all', label: 'All' },
|
|
299
|
+
{ id: 'late', label: 'Late goods', columns: { why: true, assignedLocation: false } },
|
|
300
|
+
];
|
|
301
|
+
```
|
|
302
|
+
|
|
271
303
|
### Column chooser (show/hide columns)
|
|
272
304
|
|
|
273
|
-
Lazy tables render a **Columns** toolbar button opening a checkbox list of all data columns. Declare rarely-needed columns with `visible: false` (available in the chooser, hidden by default) and pin the row's identifying column with `lockVisible: true`. Hiding a column never touches its filter or sort — the chip bar is where both stay visible. Choices persist per table in `localStorage` under `` `${stateKey}:columns` `` (durable even with `stateStorage: 'session'`; only explicit toggles are stored, so later declared-default changes flow through), the last visible data column can't be hidden, and *Reset to default* restores the declared configuration.
|
|
305
|
+
Lazy tables render a **Columns** toolbar button opening a checkbox list of all data columns. Declare rarely-needed columns with `visible: false` (available in the chooser, hidden by default) and pin the row's identifying column with `lockVisible: true`. Hiding a column never touches its filter or sort — the chip bar is where both stay visible. Choices persist per table in `localStorage` under `` `${stateKey}:columns` `` (durable even with `stateStorage: 'session'`; only explicit toggles are stored, so later declared-default changes flow through), the last visible data column can't be hidden, and *Reset to default* restores the declared configuration. On a table with `[views]` the key carries the view (`` `${stateKey}:${viewId}:columns` ``) and the declared configuration is the active view's `columns` over `Column.visible`.
|
|
274
306
|
|
|
275
307
|
The `onClick` callback receives an `ActionClickEvent` — `{ id, row, element, originalEvent }`. Use `element` (or `originalEvent`) to anchor an overlay/popover to the clicked action; `row` gives you the full row object. It fires only for custom `field` values (never `'Details'` / `'Delete'`).
|
|
276
308
|
|
|
@@ -371,7 +403,7 @@ Set the inset rather than stretching `--spiderly-topbar-height`, which is the to
|
|
|
371
403
|
| ---------------------------------- | ------------------------ | ------- | ----------------------- |
|
|
372
404
|
| `cols` | `Column[]` | — | Column definitions |
|
|
373
405
|
| `filters` | `FilterSource` | — | The page's filter store (see Filtering); read once at init |
|
|
374
|
-
| `views` | `TableView[]` | — | Saved questions rendered as tabs above the bar |
|
|
406
|
+
| `views` | `TableView[]` | — | Saved questions rendered as tabs above the bar, each with an optional `count` badge and declared `columns` |
|
|
375
407
|
| `getPaginatedListObservableMethod` | `(filter) => Observable` | — | Server-side data source |
|
|
376
408
|
| `additionalFilterIdLong` | `number` | — | Parent entity filter |
|
|
377
409
|
| `hasLazyLoad` | `boolean` | `true` | Server vs client mode |
|
|
@@ -89,8 +89,8 @@ public virtual Brand Brand { get; set; }
|
|
|
89
89
|
### Rules
|
|
90
90
|
|
|
91
91
|
- Naming convention: `{NavigationName}Id` — resolved automatically. Use `[ForeignKey(nameof(OtherName))]` only when you need a different scalar name.
|
|
92
|
-
- Nullability must match the relationship: `[Required]` navigation → non-nullable scalar (`long BrandId`); optional nav (`[SetNull]`) → nullable scalar (`long? BrandId`). Mismatch raises **
|
|
93
|
-
- Scalar type must match the parent's `Id` type (`byte`/`int`/`long`). Mismatch raises **
|
|
92
|
+
- Nullability must match the relationship: `[Required]` navigation → non-nullable scalar (`long BrandId`); optional nav (`[SetNull]`) → nullable scalar (`long? BrandId`). Mismatch raises **SPIDERLY006**.
|
|
93
|
+
- Scalar type must match the parent's `Id` type (`byte`/`int`/`long`). Mismatch raises **SPIDERLY004**.
|
|
94
94
|
|
|
95
95
|
### Caveat — generated CRUD still loads the nav
|
|
96
96
|
|