-
Notifications
You must be signed in to change notification settings - Fork 0
Table card search and scopes #182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
efe7e31
4c58e82
4e6d89d
84b05bb
7f0b4eb
ad237af
b935241
9e649fd
bb8e613
8c6c34a
8a6baaf
e2674fd
c4d6f88
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -90,7 +90,9 @@ import { | |||||||||
| (createSubmit)="onCreateSubmit($event, tableCard)" | ||||||||||
| (editSubmit)="onEditSubmit($event, tableCard)" | ||||||||||
| (deleteSubmit)="onDeleteSubmit($event, tableCard)" | ||||||||||
| (searchChanged)="onSearch($event)" | ||||||||||
| (searchChanged)="onSearchChanged($event)" | ||||||||||
| (searchSubmit)="onSearchSubmit($event)" | ||||||||||
| (scopeChanged)="onScopeChanged($event)" | ||||||||||
| /> | ||||||||||
| `, | ||||||||||
| }) | ||||||||||
|
|
@@ -164,6 +166,23 @@ export class MyComponent { | |||||||||
| await this.deletePod(pod); | ||||||||||
| tableCard.closeDeleteDialog(); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| onSearchChanged(value: string | null): void { | ||||||||||
| // Debounced (~300 ms) — fires while the user types and on clear-icon click. | ||||||||||
| // The currently active scope is tracked separately via `scopeChanged`. | ||||||||||
| this.reloadPods({ query: value ?? '' }); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| onSearchSubmit(value: string | null): void { | ||||||||||
| // Synchronous — fired on Enter or the search icon. Useful for forcing | ||||||||||
| // an immediate refresh that bypasses the debounce. | ||||||||||
| this.reloadPods({ query: value ?? '' }); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| onScopeChanged(scope: Scope | undefined): void { | ||||||||||
| // Synchronous — re-fetch using the new scope. | ||||||||||
| this.reloadPods({ scope }); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
|
|
@@ -189,7 +208,9 @@ export class MyComponent { | |||||||||
| | `createSubmit` | `Record<string, unknown>` | Fires when the create dialog Save button is clicked | | ||||||||||
| | `editSubmit` | `{ resource: T; value: Record<string, unknown> }` | Fires when the edit dialog Save button is clicked | | ||||||||||
| | `deleteSubmit` | `T` | Fires when the delete dialog Delete button is clicked | | ||||||||||
| | `searchChanged` | `string` | Emits 300 ms after the search input changes | | ||||||||||
| | `searchChanged` | `string \| null` | Emits ~300 ms after the search input changes (typing or clear icon click). The empty string is emitted immediately on clear. | | ||||||||||
| | `searchSubmit` | `string \| null` | Emits synchronously when the user submits the search (Enter or search icon) | | ||||||||||
| | `scopeChanged` | `Scope \| undefined` | Emits synchronously when the user picks a different scope from the dropdown. The full scope object is forwarded; `undefined` means "no scope selected". | | ||||||||||
| | `tableRowClicked` | `T` | Emits when a table row is clicked | | ||||||||||
| | `loadMoreResources` | - | Emits when the user triggers load more | | ||||||||||
| | `paginationLimitChanged` | `number` | Emits when the user changes page size | | ||||||||||
|
|
@@ -211,15 +232,44 @@ Submit events do not close dialogs automatically. Close the dialog after success | |||||||||
|
|
||||||||||
| ```ts | ||||||||||
| interface TableCardConfig { | ||||||||||
| header: string; | ||||||||||
| header?: string; | ||||||||||
| headerTooltip?: string; | ||||||||||
| tableConfig: TableConfig; | ||||||||||
| buttonSettings?: TableCardButtonSettings; | ||||||||||
| searchConfig?: TableCardSearchConfig; | ||||||||||
| createResourceFormConfig?: ResourceFormConfig; | ||||||||||
| editResourceFormConfig?: ResourceFormConfig; | ||||||||||
| deleteResourceConfirmationConfig?: DeleteResourceConfirmationConfig; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /** One option in the `<ui5-search>` scopes dropdown. */ | ||||||||||
| interface Scope { | ||||||||||
| /** Stable identifier used to match `initialScopeValue` and as `<ui5-search-scope value>`. */ | ||||||||||
| id: string; | ||||||||||
| /** Visible label shown in the dropdown. */ | ||||||||||
| label: string; | ||||||||||
| /** Logical value forwarded to the host (e.g. for filtering, URL query string). */ | ||||||||||
| value: string; | ||||||||||
| /** Name of the property this scope filters by — used by the host to build the filter expression. */ | ||||||||||
| property: string; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| /** Configuration for the `<ui5-search>` element rendered in the table-card header. */ | ||||||||||
| interface TableCardSearchConfig { | ||||||||||
| /** ARIA name for the search input. */ | ||||||||||
| accessibleName?: string; | ||||||||||
| /** Placeholder text shown when the input is empty. */ | ||||||||||
| placeholder?: string; | ||||||||||
| /** When `true`, the clear icon is shown inside the input. Default: `true`. */ | ||||||||||
| showClearIcon?: boolean; | ||||||||||
| /** Initial / controlled scope — must be one of the entries in `scopes`. Matched against `scopes[].id`. */ | ||||||||||
| initialScopeValue?: Scope; | ||||||||||
|
Comment on lines
+265
to
+266
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Correct the The documentation claims - /** Initial / controlled scope — must be one of the entries in `scopes`. Matched against `scopes[].id`. */
+ /** Initial / controlled scope object. Should reference one of the objects in `scopes` (or an equivalent `Scope` value). */📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| /** Initial / controlled search text value. */ | ||||||||||
| value?: string; | ||||||||||
| /** Scope options shown in the scopes dropdown. Omit or leave empty to render the input without a scope dropdown. */ | ||||||||||
| scopes?: Scope[]; | ||||||||||
| } | ||||||||||
|
|
||||||||||
| interface TableConfig { | ||||||||||
| fields: TableFieldDefinition[]; | ||||||||||
| totalItemsCount?: number; | ||||||||||
|
|
@@ -244,6 +294,126 @@ interface TableCardFormState { | |||||||||
|
|
||||||||||
| `ResourceFormConfig` is static. Keep runtime errors in `createFormState` / `editFormState`. The submit button is disabled when any entry in `fieldErrors` is truthy. | ||||||||||
|
|
||||||||||
| > `TableConfig` is declared in `declarative-ui/table` and re-exported from `declarative-ui/table-card`. Importing it from `@openmfp/webcomponents` (the public-api barrel) continues to work unchanged. | ||||||||||
|
|
||||||||||
| --- | ||||||||||
|
|
||||||||||
| ## Search & Scopes | ||||||||||
|
|
||||||||||
| When `searchConfig` is set on `TableCardConfig`, the card renders a [`<ui5-search>`](https://ui5.github.io/webcomponents/components/fiori/Search/) element inline in the toolbar. Omit `searchConfig` to hide the search entirely. | ||||||||||
|
|
||||||||||
| ### Clearing the input | ||||||||||
|
|
||||||||||
| `showClearIcon` defaults to `true` — the user can click the inline X icon to empty the input. Clearing emits `searchChanged` with an empty string **immediately** (bypassing the typing debounce), so the host can refetch / un-filter without waiting. | ||||||||||
|
|
||||||||||
| ### Scopes | ||||||||||
|
|
||||||||||
| `scopes` is an array of `Scope` objects shown in the dropdown next to the search input. Each scope is matched by its `id`; `initialScopeValue` (a full `Scope`) selects the initially-active scope. Omit `scopes` (or pass an empty array) to render the input without a scope dropdown. | ||||||||||
|
|
||||||||||
| The `value` and `property` fields on `Scope` are passed through verbatim in `scopeChanged` — the host decides how to use them (e.g. as `?<property>=<value>` in the URL, as an OpenSearch `filter=<property>=<value>` parameter, etc.). | ||||||||||
|
|
||||||||||
| ### Event contract | ||||||||||
|
|
||||||||||
| The host owns data fetching and filtering. The card forwards user actions verbatim: | ||||||||||
|
|
||||||||||
| | Event | When | Payload | | ||||||||||
| | --------------- | ---- | ------- | | ||||||||||
| | `searchChanged` | ~300 ms after the input value changes while typing; **immediately** when the clear icon is clicked | `string \| null` — the current input value | | ||||||||||
| | `searchSubmit` | User presses Enter or clicks the search icon (synchronous) | `string \| null` — the current input value | | ||||||||||
| | `scopeChanged` | User picks a different scope from the dropdown (synchronous) | `Scope \| undefined` — the full scope object, or `undefined` when no scope is active | | ||||||||||
|
|
||||||||||
| The search text and the active scope are emitted on separate events. The host is responsible for keeping the most recent value of each and combining them when issuing the next request. | ||||||||||
|
|
||||||||||
| ### Example — "My Contributions" / "All" scopes | ||||||||||
|
|
||||||||||
| ```ts | ||||||||||
| import { | ||||||||||
| DeclarativeTableCard, | ||||||||||
| Scope, | ||||||||||
| TableCardConfig, | ||||||||||
| } from '@openmfp/webcomponents'; | ||||||||||
|
|
||||||||||
| @Component({ | ||||||||||
| imports: [DeclarativeTableCard], | ||||||||||
| template: ` | ||||||||||
| <mfp-declarative-table-card | ||||||||||
| [config]="config" | ||||||||||
| [resources]="pods" | ||||||||||
| (searchChanged)="onSearchChanged($event)" | ||||||||||
| (searchSubmit)="onSearchSubmit($event)" | ||||||||||
| (scopeChanged)="onScopeChanged($event)" | ||||||||||
| /> | ||||||||||
| `, | ||||||||||
| }) | ||||||||||
| export class MyComponent { | ||||||||||
| pods: Pod[] = []; | ||||||||||
|
|
||||||||||
| private currentQuery = ''; | ||||||||||
| private currentScope: Scope | undefined; | ||||||||||
|
|
||||||||||
| private readonly ALL_SCOPE: Scope = { | ||||||||||
| id: 'all', | ||||||||||
| label: 'All', | ||||||||||
| value: '*', | ||||||||||
| property: 'owner', | ||||||||||
| }; | ||||||||||
| private readonly MINE_SCOPE: Scope = { | ||||||||||
| id: 'mine', | ||||||||||
| label: 'My Contributions', | ||||||||||
| value: 'me', | ||||||||||
| property: 'owner', | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| config: TableCardConfig = { | ||||||||||
| header: 'Pods', | ||||||||||
| tableConfig: { | ||||||||||
| fields: [ | ||||||||||
| { label: 'Name', property: 'metadata.name' }, | ||||||||||
| { label: 'Namespace', property: 'metadata.namespace' }, | ||||||||||
| ], | ||||||||||
| }, | ||||||||||
| searchConfig: { | ||||||||||
| placeholder: 'Search pods…', | ||||||||||
| accessibleName: 'Search pods', | ||||||||||
| scopes: [this.ALL_SCOPE, this.MINE_SCOPE], | ||||||||||
| initialScopeValue: this.ALL_SCOPE, | ||||||||||
| }, | ||||||||||
| }; | ||||||||||
|
|
||||||||||
| onSearchChanged(value: string | null): void { | ||||||||||
| // Debounced while typing; instant on clear. Combine with the current scope | ||||||||||
| // when reloading. | ||||||||||
| this.currentQuery = value ?? ''; | ||||||||||
| this.reload(); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| onSearchSubmit(value: string | null): void { | ||||||||||
| // Synchronous — fired on Enter or the search icon. Useful for forcing | ||||||||||
| // an immediate refresh that bypasses the typing debounce. | ||||||||||
| this.currentQuery = value ?? ''; | ||||||||||
| this.reload(); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| onScopeChanged(scope: Scope | undefined): void { | ||||||||||
| // Synchronous — re-fetch with the current in-flight search text and the | ||||||||||
| // newly-selected scope. | ||||||||||
| this.currentScope = scope; | ||||||||||
| this.reload(); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| private reload(): void { | ||||||||||
| this.reloadPods({ | ||||||||||
| query: this.currentQuery, | ||||||||||
| filter: this.currentScope | ||||||||||
| ? `${this.currentScope.property}=${this.currentScope.value}` | ||||||||||
| : undefined, | ||||||||||
| }); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| Omit `scopes` (or pass an empty array) to render the input without a scope dropdown. | ||||||||||
|
|
||||||||||
| --- | ||||||||||
|
|
||||||||||
| ## Actions column | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "watch": ["projects", "scripts"], | ||
| "ignore": ["dist", "public", ".angular", "node_modules", "documentation.json"], | ||
| "ext": "js,yml,yaml,ts,html,css,scss,json,md", | ||
| "delay": 1000, | ||
| "exec": "rimraf dist && npm run build:dev && npm run yalc:publish" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| export * from './resource'; | ||
| export * from './ui-definition'; | ||
| export type { TableFieldDefinition, ResourceFieldButtonClickEvent } from '../table/models/table-config'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I was changing that already .... moght has been lost somewhere