From c019d19205f0169c18a81604315f3e708613f773 Mon Sep 17 00:00:00 2001 From: Olympe Lespagnon Date: Fri, 19 Sep 2025 11:45:40 +0200 Subject: [PATCH 1/7] Document hypertable V2 - WIP --- README-2.md | 521 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 521 insertions(+) create mode 100644 README-2.md diff --git a/README-2.md b/README-2.md new file mode 100644 index 00000000..bdce5491 --- /dev/null +++ b/README-2.md @@ -0,0 +1,521 @@ +Hypertable + +============================================================================== + +To follow the early life of this project, see +[oss-components#pn-flexbox-tables](https://github.com/upfluence/oss-components/pull/54/files) + +# Compatibility + +- Ember.js v3.24 or above +- Ember CLI v3.24 or above +- Node.js v12 or above + +# Installation + +``` +ember install hypertable +``` + +# Usage + +## Architecture + +- **TableHandler** : The core system that manages data state, columns, filters, and selections +- **TableManager** : Interface for fetching and persisting column definitions +- **RowsFetcher** : Interface for fetching data in a paginated manner +- **Renderers** : Modular components for displaying cells and headers + +## Basic Usage + +### Quick Start + +Follow these steps in order to set up Hypertable: + +- **Create a TableManager implementation** +- **Create a RowsFetcher implementation** +- **Create a RenderingResolver implementation and custom rendering resolvers** +- **Initialize the TableHandler** +- **Use the HyperTableV2 component in the template** + +```typescript +export default class MyController extends Controller { + @tracked tableHandler: TableHandler; + + constructor() { + super(...arguments); + + const manager = new MyTableManager(); + const fetcher = new MyRowsFetcher(); + const renderingResolver = new MyRenderingResolver(); + + this.tableHandler = new TableHandler(this, manager, fetcher, renderingResolver); + } + + features = { + selection: true, + searchable: true, + manageable_fields: true, + global_filters_reset: true + }; +} +``` + +### Template Usage + +```handlebars + + {{! Custom search block (optional) }} + <:search> + + + + {{! Contextual actions (optional) }} + <:contextual-actions> + + + + {{! Table actions (optional) }} + <:table-actions> + + + + {{! Custom empty state (optional) }} + <:empty-state> +
This is an empty state
+ +
+``` + +### Implementation Details + +To use Hypertable, `TableManager` and `RowsFetcher` classes must be implemented to define how Hypertable interacts with data. + +#### TableManager Implementation + +```typescript +import { TableManager, ColumnDefinition, Column } from '@upfluence/hypertable/core/interfaces'; + +class MyTableManager implements TableManager { + async fetchColumnDefinitions(): Promise<{ column_definitions: ColumnDefinition[] }> { + // Fetch available column definitions from API + const response = await fetch('/api/column-definitions'); + return response.json(); + } + + async fetchColumns(): Promise<{ columns: Column[] }> { + // Fetch current column configuration from API + const response = await fetch('/api/columns'); + return response.json(); + } + + async upsertColumns(request: { columns: Column[] }): Promise<{ columns: Column[] }> { + // Save column configuration to API + const response = await fetch('/api/columns', { + method: 'POST', + body: JSON.stringify(request) + }); + return response.json(); + } + + // Optional: for faceted filtering + async fetchFacets(columnKey: string, filteringKey: string, searchValue?: string): Promise { + const response = await fetch(`/api/facets/${columnKey}?filtering_key=${filteringKey}&search=${searchValue}`); + return response.json(); + } +} +``` + +Methods available on TableManager: + +- `fetchColumnDefinitions()` - Returns available column definitions +- `fetchColumns()` - Returns current column configuration +- `upsertColumns()` - Saves column configuration +- `fetchFacets()` - Optional: provides faceted filtering + +#### RowsFetcher Implementation + +```typescript +import { RowsFetcher, Row } from '@upfluence/hypertable/core/interfaces'; + +class MyRowsFetcher implements RowsFetcher { + async fetch(page: number, perPage: number): Promise<{ rows: Row[]; meta: { total: number } }> { + // Fetch paginated data from API + const response = await fetch(`/api/data?page=${page}&per_page=${perPage}`); + return response.json(); + } + + // Optional: for individual row updates + async fetchById(recordId: number): Promise { + const response = await fetch(`/api/data/${recordId}`); + return response.json(); + } +} +``` + +Methods available on RowsFetcher: + +- `fetch(page, perPage)` - Returns paginated data +- `fetchById(id)` - Optional: fetches individual rows + +## Core Concepts + +### Column Definitions + +Column definitions describe the structure and capabilities of table columns. See `@upfluence/hypertable/core/interfaces/column.ts` for the complete type definition: + +```typescript +type ColumnDefinition = { + key: string; // Unique identifier + type: string; // Data type (text, numeric, date) + name: string; // Display name + category: string; // Grouping identifier + clustering_key: string; // Grouping identifier within a category + size: FieldSize; // Column width (XS, S, M, L, XL) + orderable: boolean; // Can be sorted + orderable_by: string[] | null; // Fields to sort by + filterable: boolean; // Can be filtered + filterable_by: string[] | null; // Fields to filter by + facetable: boolean; // Supports faceted search + facetable_by: string[] | null; // Fields for facets + empty_state_message?: string; // Message for empty values + position?: { + sticky: boolean; // Sticky column + side?: 'left' | 'right'; // Which side to stick to + }; +}; +``` + +### Row Data Structure + +Row data is used by cell renderers to display values and by the selection system to identify records. See `@upfluence/hypertable/core/interfaces/rows-fetcher.ts` for the complete type definition: + +```typescript +type Row = { + influencerId: number; + recordId: number; + holderId: number; + holderType: string; + [key: string]: any; // Additional dynamic fields based on column definitions +}; +``` + +### Filters and Ordering + +Filter and order structures are used internally by the TableHandler to manage table state and are passed to the TableManager for persistence. See `@upfluence/hypertable/core/interfaces/column.ts` for complete type definitions: + +```typescript +// Filter structure - used when applying column filters +type Filter = { + key: string; // Field to filter on + value: string; // Filter value + extra?: any; // Additional filter parameters +}; + +// Ordering structure - used when sorting columns +type Order = { + key: string; // Field to order by + direction: 'asc' | 'desc'; // Sort direction +}; +``` + +## Header Renderers + +Header renderers provide functionality for column headers: + +- **base** : Basic header with sorting +- **cell** : Individual cell header +- **column** : Column management +- **index** : Main header component +- **manage-columns** : Column visibility management +- **search** : Column-specific search +- **selection** : Column selection functionality + +## Cell Renderers + +Hypertable includes built-in cell renderers for common data types: + +- **text** : Basic text, with ellipsis and tooltip +- **numeric** : Numbers, formatted +- **date** : Date, formatted + +## Filtering Renderers + +Filtering Renderers provide functionality for column filters: + +- **text** : Text input filtering +- **numeric** : Numeric range filtering +- **date** : Date range filtering + +## Custom Renderers + +Create custom cell, header or filter renderers by extending the base component: + +```typescript +// addon/components/my-custom-renderer.ts +interface MyCustomRendererArgs { + handler: TableHandler; + column: Column; + row: Row; + extra?: { [key: string]: any }; +} + +export default class MyCustomRenderer extends Component {} +``` + +```handlebars +{{! addon/components/my-custom-renderer.hbs }} +
+
{{this.value}}
+
+``` + +### Custom rendering resolver for header, cells, filters mapping + +The Rendering Resolver extends BaseRenderingResolver from @upfluence/hypertable/core/rendering-resolver and determines which component should be used to render each cell, filter, and header. + +```typescript +type RendererDictionaryItem = { cell?: any; header?: any; filter?: any }; + +// Define mapping dictionary : all the custom columns and their renderers +const rendererMatchers: { [key: string]: RendererDictionaryItem } = { + columnName: { + cell: CustomCellRenderer, + filter: CustomFilterRenderer, + header: CustomHeaderRenderer + } +}; + +export default class MyRenderingResolver extends BaseRenderingResolver { + private context; + + constructor(emberContext: unknown) { + super(emberContext); + this.context = emberContext; + } + + lookupCellComponent(columnDef: ColumnDefinition): Promise { + return this._lookupComponent(columnDef, 'cell'); + } + + lookupFilteringComponent(columnDef: ColumnDefinition): Promise { + return this._lookupComponent(columnDef, 'filter'); + } + + lookupHeaderComponent(columnDef: ColumnDefinition): Promise { + return this._lookupComponent(columnDef, 'header'); + } + + private _lookupComponent( + columnDef: ColumnDefinition, + type: 'header' | 'filter' | 'cell' + ): Promise { + let rendererMatch = rendererMatchers[camelize(columnDef.key)]; + + if (rendererMatch && rendererMatch[type]) { + return Promise.resolve({ + component: ensureSafeComponent(rendererMatch[type], this.context) as GlimmerComponent + }); + } + + return super.lookupComponent(columnDef, type); + } +} +``` + +## Built-in Components + +HyperTableV2 includes several built-in components that provide table-level functionality. These components are used automatically based on the `@features` configuration and are not directly interacted with by the user: + +### Automatic Components + +- **HyperTableV2::Search** : Global search functionality - automatically included when `features.searchable: true` +- **HyperTableV2::Selection** : Row selection controls and display - automatically included when `features.selection: true` +- **HyperTableV2::ManageColumns** : Column visibility management interface - automatically included when `features.manageable_fields: true` +- **HyperTableV2::Column** : Column wrapper with header rendering - used internally for each table column +- **HyperTableV2::Cell** : Individual cell component with data rendering - used internally for each table cell + +### User Interaction + +These components are controlled through the `@features` parameter: + +```typescript +features = { + selection: true, // Enables HyperTableV2::Selection + searchable: true, // Enables HyperTableV2::Search + manageable_fields: true, // Enables HyperTableV2::ManageColumns + global_filters_reset: true // Enables reset filters button +}; +``` + +Users interact with these components through: + +- **Search**: Typing in the search input (when searchable is enabled) +- **Selection**: Clicking checkboxes to select rows (when selection is enabled) +- **ManageColumns**: Clicking the manage columns button to show/hide columns (when manageable_fields is enabled) +- **Reset Filters**: Clicking the reset button to clear all filters (when global_filters_reset is enabled) + +The components handle their own internal state and communicate with the TableHandler automatically. + +## Faceted Filtering + +Hypertable provides built-in support for **faceted filtering**, allowing users to select multiple values for a column filter from a dynamic list of facets. + +1. **Enable Facets in `ColumnDefinition`** + +To make a column facetable, set `facetable: true` and provide at least one key in `facetable_by`. + +2. **Implement `fetchFacets` in `TableManager`** + +The `TableManager` must implement a `fetchFacets` method to return facets for a given column. + +```ts +class MyTableManager implements TableManager { + async fetchFacets(columnKey: string, filteringKey: string, searchValue?: string): Promise { + const response = await fetch(`/api/facets/${columnKey}?filtering_key=${filteringKey}&search=${searchValue}`); + return response.json(); + } +} +``` + +The `FacetsResponse` type should have the following structure: + +```ts +type FacetsResponse = { + filtering_key: string; + facets: Facet[]; +}; + +type Facet = { + identifier: string; // Unique value sent in the filter request + display_name: string; // Text displayed to the user + count?: number; // (Optional) Used for sorting and displaying counts +}; +``` + +3. **Built-in Facet Loader** + +Hypertable ships with a `FacetsLoader` component that automatically: + +- Fetches facets when the filter UI opens +- Displays a skeleton loader while loading +- Supports search with a 300 ms debounce +- Allows multiple facet selection +- Sorts facets by `count` descending by default (custom sorting function can be provided) + +4. **Applying and Removing Facets** + +When a user toggles a facet: + +- `FacetsLoader` builds a filter object `{ key, value }` +- Calls `TableHandler.applyFilters()` +- Updates the `appliedFacets` list for that column + +This behavior is automatic and does not require additional setup. + +## Events + +The TableHandler emits events throughout its lifecycle: + +```typescript +this.tableHandler.on('columns-loaded', () => { + console.log('Columns have been loaded'); +}); + +// Available events: +// - 'columns-loaded' +// - 'row-click' +// - 'apply-filters' +// - 'reset-columns' +// - 'remove-column' +// - 'remove-row' +// - 'mutate-rows' +// - 'reset-rows' +``` + +## Advanced Usage + +### Row Methods + +Methods available on TableHandler for row manipulation: + +```typescript +// Update a specific row from the data source +await this.tableHandler.updateRowById(123); + +// Remove a row from the table +this.tableHandler.removeRow(123); + +// Mutate a row in place +this.tableHandler.mutateRow(123, (row) => { + row.someField = 'new value'; + return true; // Return true to trigger table redraw +}); + +// Toggle loading state for a specific row +this.tableHandler.toggleRowLoadingState(123); +``` + +### Column Methods + +Methods available on TableHandler for column management: + +```typescript +// Add a column to the table +await this.tableHandler.addColumn(column); + +// Remove a column from the table +await this.tableHandler.removeColumn(columnDefinition); + +// Reorder columns +this.tableHandler.reorderColumns(newColumnOrder); +``` + +### Selection Methods + +Methods available on TableHandler for selection management: + +```typescript +// Select all visible rows +this.tableHandler.toggleSelectAll(true); + +// Select all rows globally (including those not currently loaded) +this.tableHandler.selectAllGlobal(); + +// Clear all selections +this.tableHandler.clearSelection(); + +// Update individual row selection +this.tableHandler.updateSelection(row); +``` + +## Contributing + +### Installation + +- `git clone ` +- `cd hypertable` +- `pnpm install` + +### Linting + +- `pnpm lint:hbs` +- `pnpm lint:js` +- `pnpm lint:js --fix` + +### Running tests + +- `ember test` – Runs the test suite on the current Ember version +- `ember test --server` – Runs the test suite in "watch mode" +- `ember try:each` – Runs the test suite against multiple Ember versions + +### Running the dummy application + +- `ember serve` +- Visit the dummy application at [http://localhost:4200](http://localhost:4200). + +See the [Contributing](CONTRIBUTING.md) guide for details. + +## License + +This project is licensed under the [MIT License](LICENSE.md). From d9541ebc3e034fb57a653b495f1181ef3a40f245 Mon Sep 17 00:00:00 2001 From: Olympe Lespagnon Date: Fri, 19 Sep 2025 15:01:44 +0200 Subject: [PATCH 2/7] Reorganize --- README-2.md | 476 ++++++++++++++++++++++++++++------------------------ 1 file changed, 255 insertions(+), 221 deletions(-) diff --git a/README-2.md b/README-2.md index bdce5491..89bedab6 100644 --- a/README-2.md +++ b/README-2.md @@ -1,55 +1,56 @@ -Hypertable - -============================================================================== +# Hypertable To follow the early life of this project, see [oss-components#pn-flexbox-tables](https://github.com/upfluence/oss-components/pull/54/files) -# Compatibility +## Table of Contents + +- [Compatibility](#compatibility) +- [Installation](#installation) +- [Architecture](#architecture) +- [Quick Start](#quick-start) +- [Core Concepts](#core-concepts) +- [Rendering System](#rendering-system) +- [Built-in Components](#built-in-components) +- [Events](#events) +- [Contributing](#contributing) + +## Compatibility - Ember.js v3.24 or above - Ember CLI v3.24 or above - Node.js v12 or above -# Installation +## Installation ``` ember install hypertable ``` -# Usage - ## Architecture -- **TableHandler** : The core system that manages data state, columns, filters, and selections -- **TableManager** : Interface for fetching and persisting column definitions -- **RowsFetcher** : Interface for fetching data in a paginated manner -- **Renderers** : Modular components for displaying cells and headers +- **TableHandler** - The core system that manages data state, columns, filters, and selections +- **TableManager** - Interface for fetching and persisting column definitions and configurations +- **RowsFetcher** - Interface for fetching paginated data +- **RenderingResolver** - Maps column definitions to appropriate cell, header, and filter renderers +- **Renderers** - Modular components for displaying cells, headers, and filters -## Basic Usage +## Quick Start -### Quick Start +### 1. Implement Required Interfaces -Follow these steps in order to set up Hypertable: +Create implementations for the two required interfaces and add a rendering resolver if needed: -- **Create a TableManager implementation** -- **Create a RowsFetcher implementation** -- **Create a RenderingResolver implementation and custom rendering resolvers** -- **Initialize the TableHandler** -- **Use the HyperTableV2 component in the template** - -```typescript +```ts export default class MyController extends Controller { - @tracked tableHandler: TableHandler; - constructor() { super(...arguments); const manager = new MyTableManager(); const fetcher = new MyRowsFetcher(); - const renderingResolver = new MyRenderingResolver(); + const renderingResolver = new MyRenderingResolver(); // Optional - this.tableHandler = new TableHandler(this, manager, fetcher, renderingResolver); + this.handler = new TableHandler(this, manager, fetcher, renderingResolver); } features = { @@ -61,9 +62,9 @@ export default class MyController extends Controller { } ``` -### Template Usage +### 2. Use HyperTableV2 Component in template -```handlebars +```hbs {{! Custom search block (optional) }} <:search> @@ -87,15 +88,11 @@ export default class MyController extends Controller { ``` -### Implementation Details +### 3. Interface Implementations -To use Hypertable, `TableManager` and `RowsFetcher` classes must be implemented to define how Hypertable interacts with data. - -#### TableManager Implementation +#### TableManager ```typescript -import { TableManager, ColumnDefinition, Column } from '@upfluence/hypertable/core/interfaces'; - class MyTableManager implements TableManager { async fetchColumnDefinitions(): Promise<{ column_definitions: ColumnDefinition[] }> { // Fetch available column definitions from API @@ -126,18 +123,16 @@ class MyTableManager implements TableManager { } ``` -Methods available on TableManager: +**Available methods:** - `fetchColumnDefinitions()` - Returns available column definitions - `fetchColumns()` - Returns current column configuration -- `upsertColumns()` - Saves column configuration -- `fetchFacets()` - Optional: provides faceted filtering +- `upsertColumns(request)` - Saves column configuration +- `fetchFacets(columnKey, filteringKey, searchValue)` - Optional: provides faceted filtering -#### RowsFetcher Implementation - -```typescript -import { RowsFetcher, Row } from '@upfluence/hypertable/core/interfaces'; +#### RowsFetcher +```ts class MyRowsFetcher implements RowsFetcher { async fetch(page: number, perPage: number): Promise<{ rows: Row[]; meta: { total: number } }> { // Fetch paginated data from API @@ -153,18 +148,72 @@ class MyRowsFetcher implements RowsFetcher { } ``` -Methods available on RowsFetcher: +**Available methods:** - `fetch(page, perPage)` - Returns paginated data - `fetchById(id)` - Optional: fetches individual rows +#### Custom rendering resolver (Optional) + +If no RenderingResolver is provided, Hypertable uses a default resolver with built-in renderers. +The Rendering Resolver extends BaseRenderingResolver from @upfluence/hypertable/core/rendering-resolver and determines which component should be used to render each cell, filter, and header according to column key. + +```typescript +type RendererDictionaryItem = { cell?: any; header?: any; filter?: any }; + +// Define mapping dictionary: all the custom columns and their renderers +const rendererMatchers: { [key: string]: RendererDictionaryItem } = { + columnName: { + cell: CustomCellRenderer, + filter: CustomFilterRenderer, + header: CustomHeaderRenderer + } +}; + +export default class MyRenderingResolver extends BaseRenderingResolver { + private context; + + constructor(emberContext: unknown) { + super(emberContext); + this.context = emberContext; + } + + lookupCellComponent(columnDef: ColumnDefinition): Promise { + return this._lookupComponent(columnDef, 'cell'); + } + + lookupFilteringComponent(columnDef: ColumnDefinition): Promise { + return this._lookupComponent(columnDef, 'filter'); + } + + lookupHeaderComponent(columnDef: ColumnDefinition): Promise { + return this._lookupComponent(columnDef, 'header'); + } + + private _lookupComponent( + columnDef: ColumnDefinition, + type: 'header' | 'filter' | 'cell' + ): Promise { + let rendererMatch = rendererMatchers[camelize(columnDef.key)]; + + if (rendererMatch && rendererMatch[type]) { + return Promise.resolve({ + component: ensureSafeComponent(rendererMatch[type], this.context) + }); + } + + return super.lookupComponent(columnDef, type); + } +} +``` + ## Core Concepts ### Column Definitions Column definitions describe the structure and capabilities of table columns. See `@upfluence/hypertable/core/interfaces/column.ts` for the complete type definition: -```typescript +```ts type ColumnDefinition = { key: string; // Unique identifier type: string; // Data type (text, numeric, date) @@ -190,7 +239,7 @@ type ColumnDefinition = { Row data is used by cell renderers to display values and by the selection system to identify records. See `@upfluence/hypertable/core/interfaces/rows-fetcher.ts` for the complete type definition: -```typescript +```ts type Row = { influencerId: number; recordId: number; @@ -200,164 +249,100 @@ type Row = { }; ``` -### Filters and Ordering - -Filter and order structures are used internally by the TableHandler to manage table state and are passed to the TableManager for persistence. See `@upfluence/hypertable/core/interfaces/column.ts` for complete type definitions: +### Column Management ```typescript -// Filter structure - used when applying column filters -type Filter = { - key: string; // Field to filter on - value: string; // Filter value - extra?: any; // Additional filter parameters -}; - -// Ordering structure - used when sorting columns -type Order = { - key: string; // Field to order by - direction: 'asc' | 'desc'; // Sort direction -}; -``` - -## Header Renderers - -Header renderers provide functionality for column headers: - -- **base** : Basic header with sorting -- **cell** : Individual cell header -- **column** : Column management -- **index** : Main header component -- **manage-columns** : Column visibility management -- **search** : Column-specific search -- **selection** : Column selection functionality - -## Cell Renderers - -Hypertable includes built-in cell renderers for common data types: - -- **text** : Basic text, with ellipsis and tooltip -- **numeric** : Numbers, formatted -- **date** : Date, formatted - -## Filtering Renderers - -Filtering Renderers provide functionality for column filters: - -- **text** : Text input filtering -- **numeric** : Numeric range filtering -- **date** : Date range filtering - -## Custom Renderers - -Create custom cell, header or filter renderers by extending the base component: +// Add a column to the table +await this.handler.addColumn(columnDefinition); -```typescript -// addon/components/my-custom-renderer.ts -interface MyCustomRendererArgs { - handler: TableHandler; - column: Column; - row: Row; - extra?: { [key: string]: any }; -} +// Remove a column from the table +await this.handler.removeColumn(columnDefinition); -export default class MyCustomRenderer extends Component {} -``` +// Reorder columns +this.handler.reorderColumns(newColumnOrder); -```handlebars -{{! addon/components/my-custom-renderer.hbs }} -
-
{{this.value}}
-
+// Reset column filters and ordering +await this.handler.resetColumns(columnsToReset); ``` -### Custom rendering resolver for header, cells, filters mapping - -The Rendering Resolver extends BaseRenderingResolver from @upfluence/hypertable/core/rendering-resolver and determines which component should be used to render each cell, filter, and header. +### Row Management ```typescript -type RendererDictionaryItem = { cell?: any; header?: any; filter?: any }; - -// Define mapping dictionary : all the custom columns and their renderers -const rendererMatchers: { [key: string]: RendererDictionaryItem } = { - columnName: { - cell: CustomCellRenderer, - filter: CustomFilterRenderer, - header: CustomHeaderRenderer - } -}; +// Update a specific row from the data source +await this.handler.updateRowById(123); -export default class MyRenderingResolver extends BaseRenderingResolver { - private context; +// Remove a row from the table +this.handler.removeRow(123); - constructor(emberContext: unknown) { - super(emberContext); - this.context = emberContext; - } +// Mutate a row in place and trigger redraw +this.handler.mutateRow(123, (row) => { + row.someField = 'new value'; + return true; // Return true to trigger table redraw +}); - lookupCellComponent(columnDef: ColumnDefinition): Promise { - return this._lookupComponent(columnDef, 'cell'); - } +// Toggle loading state for a specific row +this.handler.toggleRowLoadingState(123); - lookupFilteringComponent(columnDef: ColumnDefinition): Promise { - return this._lookupComponent(columnDef, 'filter'); - } +// Reset all rows and refetch +await this.handler.resetRows(); +``` - lookupHeaderComponent(columnDef: ColumnDefinition): Promise { - return this._lookupComponent(columnDef, 'header'); - } +### Selection Management - private _lookupComponent( - columnDef: ColumnDefinition, - type: 'header' | 'filter' | 'cell' - ): Promise { - let rendererMatch = rendererMatchers[camelize(columnDef.key)]; +Hypertable supports two selection modes: - if (rendererMatch && rendererMatch[type]) { - return Promise.resolve({ - component: ensureSafeComponent(rendererMatch[type], this.context) as GlimmerComponent - }); - } +1. **Array mode**: `selection` contains selected rows +2. **Global mode**: `selection = 'all'` with `exclusion` containing unselected rows - return super.lookupComponent(columnDef, type); - } -} -``` +```ts +// Select all visible rows +this.tableHandler.toggleSelectAll(true); -## Built-in Components +// Select all rows globally (including those not currently loaded) +this.tableHandler.selectAllGlobal(); -HyperTableV2 includes several built-in components that provide table-level functionality. These components are used automatically based on the `@features` configuration and are not directly interacted with by the user: +// Clear all selections +this.tableHandler.clearSelection(); -### Automatic Components +// Update individual row selection +this.tableHandler.updateSelection(row); -- **HyperTableV2::Search** : Global search functionality - automatically included when `features.searchable: true` -- **HyperTableV2::Selection** : Row selection controls and display - automatically included when `features.selection: true` -- **HyperTableV2::ManageColumns** : Column visibility management interface - automatically included when `features.manageable_fields: true` -- **HyperTableV2::Column** : Column wrapper with header rendering - used internally for each table column -- **HyperTableV2::Cell** : Individual cell component with data rendering - used internally for each table cell +// Access current selection state +const selection = this.handler.selection; // Row[] | 'all' +const exclusions = this.handler.exclusion; // Row[] (when selection is 'all') +``` -### User Interaction +### Filtering and Ordering -These components are controlled through the `@features` parameter: +Filter and order structures are used internally by the TableHandler to manage table state and are passed to the TableManager for persistence. See `@upfluence/hypertable/core/interfaces/column.ts` for complete type definitions: ```typescript -features = { - selection: true, // Enables HyperTableV2::Selection - searchable: true, // Enables HyperTableV2::Search - manageable_fields: true, // Enables HyperTableV2::ManageColumns - global_filters_reset: true // Enables reset filters button +// Filter structure - used when applying column filters +type Filter = { + key: string; // Field to filter on + value: string; // Filter value + extra?: any; // Additional filter parameters +}; + +// Ordering structure - used when sorting columns +type Order = { + key: string; // Field to order by + direction: 'asc' | 'desc'; // Sort direction }; ``` -Users interact with these components through: +```ts +// Apply filters to a column +await this.handler.applyFilters(column, [{ key: 'name', value: 'value' }]); -- **Search**: Typing in the search input (when searchable is enabled) -- **Selection**: Clicking checkboxes to select rows (when selection is enabled) -- **ManageColumns**: Clicking the manage columns button to show/hide columns (when manageable_fields is enabled) -- **Reset Filters**: Clicking the reset button to clear all filters (when global_filters_reset is enabled) +// Apply ordering to a column +await this.handler.applyOrder(column, 'asc'); // or 'desc' -The components handle their own internal state and communicate with the TableHandler automatically. +// Fetch facets for filtering (requires TableManager.fetchFacets) +const facets = await this.handler.fetchFacets('value', 'name', 'searchValue'); +``` -## Faceted Filtering +#### Faceted Filtering Hypertable provides built-in support for **faceted filtering**, allowing users to select multiple values for a column filter from a dynamic list of facets. @@ -413,82 +398,131 @@ When a user toggles a facet: This behavior is automatic and does not require additional setup. -## Events +## Rendering System -The TableHandler emits events throughout its lifecycle: +### Built-in Renderers -```typescript -this.tableHandler.on('columns-loaded', () => { - console.log('Columns have been loaded'); -}); +Hypertable includes built-in renderers for common data types: -// Available events: -// - 'columns-loaded' -// - 'row-click' -// - 'apply-filters' -// - 'reset-columns' -// - 'remove-column' -// - 'remove-row' -// - 'mutate-rows' -// - 'reset-rows' -``` +#### Cell Renderers -## Advanced Usage +- **text** - Basic text with ellipsis and tooltip +- **numeric** - Formatted numbers +- **date** - Formatted dates -### Row Methods +#### Filter Renderers -Methods available on TableHandler for row manipulation: +- **text** - Text input filtering +- **numeric** - Numeric range filtering +- **date** - Date range filtering -```typescript -// Update a specific row from the data source -await this.tableHandler.updateRowById(123); +#### Header Renderers -// Remove a row from the table -this.tableHandler.removeRow(123); +- **base** - Basic header with sorting capabilities +- **cell** - Individual cell header functionality +- **column** - Column management features +- **index** - Main header component +- **manage-columns** - Column visibility management +- **search** - Column-specific search +- **selection** - Column selection functionality -// Mutate a row in place -this.tableHandler.mutateRow(123, (row) => { - row.someField = 'new value'; - return true; // Return true to trigger table redraw -}); +### Custom Renderers -// Toggle loading state for a specific row -this.tableHandler.toggleRowLoadingState(123); +Create custom renderers by extending base components: + +```ts +// addon/components/my-custom-renderer.ts +interface MyCustomRendererArgs { + handler: TableHandler; + column: Column; + row: Row; + extra?: { [key: string]: any }; +} + +export default class MyCustomRenderer extends Component {} ``` -### Column Methods +```hbs +{{! Template }} +
+
{{this.value}}
+
+``` -Methods available on TableHandler for column management: +Then register it in the RenderingResolver: ```typescript -// Add a column to the table -await this.tableHandler.addColumn(column); +const rendererMatchers = { + my_column_key: { + cell: MyCustomCellRenderer, + filter: MyCustomFilterRenderer, + header: MyCustomHeaderRenderer + } +}; +``` -// Remove a column from the table -await this.tableHandler.removeColumn(columnDefinition); +### Built-in Components -// Reorder columns -this.tableHandler.reorderColumns(newColumnOrder); +HyperTableV2 includes several automatic components controlled by the `@features` configuration: + +### Feature-Controlled Components + +```ts +features = { + selection: true, // Enables row selection checkboxes + searchable: true, // Enables global search input + manageable_fields: true, // Enables column visibility management + global_filters_reset: true // Enables reset filters button +}; ``` -### Selection Methods +These components are automatically included and handle their own state: -Methods available on TableHandler for selection management: +- **HyperTableV2::Search** - Global search across all data +- **HyperTableV2::Selection** - Row selection controls and display +- **HyperTableV2::ManageColumns** - Column visibility management +- **HyperTableV2::Column** - Column wrapper with header rendering +- **HyperTableV2::Cell** - Individual cell component -```typescript -// Select all visible rows -this.tableHandler.toggleSelectAll(true); +Users interact with these components through: -// Select all rows globally (including those not currently loaded) -this.tableHandler.selectAllGlobal(); +- **Search**: Typing in the search input (when searchable is enabled) +- **Selection**: Clicking checkboxes to select rows (when selection is enabled) +- **ManageColumns**: Clicking the manage columns button to show/hide columns (when manageable_fields is enabled) +- **Reset Filters**: Clicking the reset button to clear all filters (when global_filters_reset is enabled) -// Clear all selections -this.tableHandler.clearSelection(); +The components handle their own internal state and communicate with the TableHandler automatically. -// Update individual row selection -this.tableHandler.updateSelection(row); +## Events + +TableHandler emits events throughout its lifecycle. Listen to them using the `on` method: + +```ts +// Listen to specific events +this.handler.on('row-click', (row) => { + console.log('Row clicked:', row); +}); + +this.handler.on('apply-filters', (column, filters) => { + console.log('Filters applied:', column.definition.key, filters); +}); + +this.handler.on('columns-loaded', () => { + console.log('Columns have been loaded and are ready'); +}); ``` +**Available events:** + +- `'columns-loaded'` - When columns are fetched and ready +- `'row-click'` - When a user clicks on a row +- `'apply-filters'` - When filters are applied to a column +- `'reset-columns'` - When columns are reset +- `'remove-column'` - When a column is removed +- `'remove-row'` - When a row is removed +- `'mutate-rows'` - When rows are mutated +- `'reset-rows'` - When rows are reset + ## Contributing ### Installation @@ -516,6 +550,6 @@ this.tableHandler.updateSelection(row); See the [Contributing](CONTRIBUTING.md) guide for details. -## License +### License This project is licensed under the [MIT License](LICENSE.md). From 52197d92dc5b1cdd1d3731ecd68d925e98f3ef4e Mon Sep 17 00:00:00 2001 From: Olympe Lespagnon Date: Fri, 19 Sep 2025 15:13:14 +0200 Subject: [PATCH 3/7] Update Readme --- README-2.md | 555 ---------------------------------------------------- README.md | 549 ++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 521 insertions(+), 583 deletions(-) delete mode 100644 README-2.md diff --git a/README-2.md b/README-2.md deleted file mode 100644 index 89bedab6..00000000 --- a/README-2.md +++ /dev/null @@ -1,555 +0,0 @@ -# Hypertable - -To follow the early life of this project, see -[oss-components#pn-flexbox-tables](https://github.com/upfluence/oss-components/pull/54/files) - -## Table of Contents - -- [Compatibility](#compatibility) -- [Installation](#installation) -- [Architecture](#architecture) -- [Quick Start](#quick-start) -- [Core Concepts](#core-concepts) -- [Rendering System](#rendering-system) -- [Built-in Components](#built-in-components) -- [Events](#events) -- [Contributing](#contributing) - -## Compatibility - -- Ember.js v3.24 or above -- Ember CLI v3.24 or above -- Node.js v12 or above - -## Installation - -``` -ember install hypertable -``` - -## Architecture - -- **TableHandler** - The core system that manages data state, columns, filters, and selections -- **TableManager** - Interface for fetching and persisting column definitions and configurations -- **RowsFetcher** - Interface for fetching paginated data -- **RenderingResolver** - Maps column definitions to appropriate cell, header, and filter renderers -- **Renderers** - Modular components for displaying cells, headers, and filters - -## Quick Start - -### 1. Implement Required Interfaces - -Create implementations for the two required interfaces and add a rendering resolver if needed: - -```ts -export default class MyController extends Controller { - constructor() { - super(...arguments); - - const manager = new MyTableManager(); - const fetcher = new MyRowsFetcher(); - const renderingResolver = new MyRenderingResolver(); // Optional - - this.handler = new TableHandler(this, manager, fetcher, renderingResolver); - } - - features = { - selection: true, - searchable: true, - manageable_fields: true, - global_filters_reset: true - }; -} -``` - -### 2. Use HyperTableV2 Component in template - -```hbs - - {{! Custom search block (optional) }} - <:search> - - - - {{! Contextual actions (optional) }} - <:contextual-actions> - - - - {{! Table actions (optional) }} - <:table-actions> - - - - {{! Custom empty state (optional) }} - <:empty-state> -
This is an empty state
- -
-``` - -### 3. Interface Implementations - -#### TableManager - -```typescript -class MyTableManager implements TableManager { - async fetchColumnDefinitions(): Promise<{ column_definitions: ColumnDefinition[] }> { - // Fetch available column definitions from API - const response = await fetch('/api/column-definitions'); - return response.json(); - } - - async fetchColumns(): Promise<{ columns: Column[] }> { - // Fetch current column configuration from API - const response = await fetch('/api/columns'); - return response.json(); - } - - async upsertColumns(request: { columns: Column[] }): Promise<{ columns: Column[] }> { - // Save column configuration to API - const response = await fetch('/api/columns', { - method: 'POST', - body: JSON.stringify(request) - }); - return response.json(); - } - - // Optional: for faceted filtering - async fetchFacets(columnKey: string, filteringKey: string, searchValue?: string): Promise { - const response = await fetch(`/api/facets/${columnKey}?filtering_key=${filteringKey}&search=${searchValue}`); - return response.json(); - } -} -``` - -**Available methods:** - -- `fetchColumnDefinitions()` - Returns available column definitions -- `fetchColumns()` - Returns current column configuration -- `upsertColumns(request)` - Saves column configuration -- `fetchFacets(columnKey, filteringKey, searchValue)` - Optional: provides faceted filtering - -#### RowsFetcher - -```ts -class MyRowsFetcher implements RowsFetcher { - async fetch(page: number, perPage: number): Promise<{ rows: Row[]; meta: { total: number } }> { - // Fetch paginated data from API - const response = await fetch(`/api/data?page=${page}&per_page=${perPage}`); - return response.json(); - } - - // Optional: for individual row updates - async fetchById(recordId: number): Promise { - const response = await fetch(`/api/data/${recordId}`); - return response.json(); - } -} -``` - -**Available methods:** - -- `fetch(page, perPage)` - Returns paginated data -- `fetchById(id)` - Optional: fetches individual rows - -#### Custom rendering resolver (Optional) - -If no RenderingResolver is provided, Hypertable uses a default resolver with built-in renderers. -The Rendering Resolver extends BaseRenderingResolver from @upfluence/hypertable/core/rendering-resolver and determines which component should be used to render each cell, filter, and header according to column key. - -```typescript -type RendererDictionaryItem = { cell?: any; header?: any; filter?: any }; - -// Define mapping dictionary: all the custom columns and their renderers -const rendererMatchers: { [key: string]: RendererDictionaryItem } = { - columnName: { - cell: CustomCellRenderer, - filter: CustomFilterRenderer, - header: CustomHeaderRenderer - } -}; - -export default class MyRenderingResolver extends BaseRenderingResolver { - private context; - - constructor(emberContext: unknown) { - super(emberContext); - this.context = emberContext; - } - - lookupCellComponent(columnDef: ColumnDefinition): Promise { - return this._lookupComponent(columnDef, 'cell'); - } - - lookupFilteringComponent(columnDef: ColumnDefinition): Promise { - return this._lookupComponent(columnDef, 'filter'); - } - - lookupHeaderComponent(columnDef: ColumnDefinition): Promise { - return this._lookupComponent(columnDef, 'header'); - } - - private _lookupComponent( - columnDef: ColumnDefinition, - type: 'header' | 'filter' | 'cell' - ): Promise { - let rendererMatch = rendererMatchers[camelize(columnDef.key)]; - - if (rendererMatch && rendererMatch[type]) { - return Promise.resolve({ - component: ensureSafeComponent(rendererMatch[type], this.context) - }); - } - - return super.lookupComponent(columnDef, type); - } -} -``` - -## Core Concepts - -### Column Definitions - -Column definitions describe the structure and capabilities of table columns. See `@upfluence/hypertable/core/interfaces/column.ts` for the complete type definition: - -```ts -type ColumnDefinition = { - key: string; // Unique identifier - type: string; // Data type (text, numeric, date) - name: string; // Display name - category: string; // Grouping identifier - clustering_key: string; // Grouping identifier within a category - size: FieldSize; // Column width (XS, S, M, L, XL) - orderable: boolean; // Can be sorted - orderable_by: string[] | null; // Fields to sort by - filterable: boolean; // Can be filtered - filterable_by: string[] | null; // Fields to filter by - facetable: boolean; // Supports faceted search - facetable_by: string[] | null; // Fields for facets - empty_state_message?: string; // Message for empty values - position?: { - sticky: boolean; // Sticky column - side?: 'left' | 'right'; // Which side to stick to - }; -}; -``` - -### Row Data Structure - -Row data is used by cell renderers to display values and by the selection system to identify records. See `@upfluence/hypertable/core/interfaces/rows-fetcher.ts` for the complete type definition: - -```ts -type Row = { - influencerId: number; - recordId: number; - holderId: number; - holderType: string; - [key: string]: any; // Additional dynamic fields based on column definitions -}; -``` - -### Column Management - -```typescript -// Add a column to the table -await this.handler.addColumn(columnDefinition); - -// Remove a column from the table -await this.handler.removeColumn(columnDefinition); - -// Reorder columns -this.handler.reorderColumns(newColumnOrder); - -// Reset column filters and ordering -await this.handler.resetColumns(columnsToReset); -``` - -### Row Management - -```typescript -// Update a specific row from the data source -await this.handler.updateRowById(123); - -// Remove a row from the table -this.handler.removeRow(123); - -// Mutate a row in place and trigger redraw -this.handler.mutateRow(123, (row) => { - row.someField = 'new value'; - return true; // Return true to trigger table redraw -}); - -// Toggle loading state for a specific row -this.handler.toggleRowLoadingState(123); - -// Reset all rows and refetch -await this.handler.resetRows(); -``` - -### Selection Management - -Hypertable supports two selection modes: - -1. **Array mode**: `selection` contains selected rows -2. **Global mode**: `selection = 'all'` with `exclusion` containing unselected rows - -```ts -// Select all visible rows -this.tableHandler.toggleSelectAll(true); - -// Select all rows globally (including those not currently loaded) -this.tableHandler.selectAllGlobal(); - -// Clear all selections -this.tableHandler.clearSelection(); - -// Update individual row selection -this.tableHandler.updateSelection(row); - -// Access current selection state -const selection = this.handler.selection; // Row[] | 'all' -const exclusions = this.handler.exclusion; // Row[] (when selection is 'all') -``` - -### Filtering and Ordering - -Filter and order structures are used internally by the TableHandler to manage table state and are passed to the TableManager for persistence. See `@upfluence/hypertable/core/interfaces/column.ts` for complete type definitions: - -```typescript -// Filter structure - used when applying column filters -type Filter = { - key: string; // Field to filter on - value: string; // Filter value - extra?: any; // Additional filter parameters -}; - -// Ordering structure - used when sorting columns -type Order = { - key: string; // Field to order by - direction: 'asc' | 'desc'; // Sort direction -}; -``` - -```ts -// Apply filters to a column -await this.handler.applyFilters(column, [{ key: 'name', value: 'value' }]); - -// Apply ordering to a column -await this.handler.applyOrder(column, 'asc'); // or 'desc' - -// Fetch facets for filtering (requires TableManager.fetchFacets) -const facets = await this.handler.fetchFacets('value', 'name', 'searchValue'); -``` - -#### Faceted Filtering - -Hypertable provides built-in support for **faceted filtering**, allowing users to select multiple values for a column filter from a dynamic list of facets. - -1. **Enable Facets in `ColumnDefinition`** - -To make a column facetable, set `facetable: true` and provide at least one key in `facetable_by`. - -2. **Implement `fetchFacets` in `TableManager`** - -The `TableManager` must implement a `fetchFacets` method to return facets for a given column. - -```ts -class MyTableManager implements TableManager { - async fetchFacets(columnKey: string, filteringKey: string, searchValue?: string): Promise { - const response = await fetch(`/api/facets/${columnKey}?filtering_key=${filteringKey}&search=${searchValue}`); - return response.json(); - } -} -``` - -The `FacetsResponse` type should have the following structure: - -```ts -type FacetsResponse = { - filtering_key: string; - facets: Facet[]; -}; - -type Facet = { - identifier: string; // Unique value sent in the filter request - display_name: string; // Text displayed to the user - count?: number; // (Optional) Used for sorting and displaying counts -}; -``` - -3. **Built-in Facet Loader** - -Hypertable ships with a `FacetsLoader` component that automatically: - -- Fetches facets when the filter UI opens -- Displays a skeleton loader while loading -- Supports search with a 300 ms debounce -- Allows multiple facet selection -- Sorts facets by `count` descending by default (custom sorting function can be provided) - -4. **Applying and Removing Facets** - -When a user toggles a facet: - -- `FacetsLoader` builds a filter object `{ key, value }` -- Calls `TableHandler.applyFilters()` -- Updates the `appliedFacets` list for that column - -This behavior is automatic and does not require additional setup. - -## Rendering System - -### Built-in Renderers - -Hypertable includes built-in renderers for common data types: - -#### Cell Renderers - -- **text** - Basic text with ellipsis and tooltip -- **numeric** - Formatted numbers -- **date** - Formatted dates - -#### Filter Renderers - -- **text** - Text input filtering -- **numeric** - Numeric range filtering -- **date** - Date range filtering - -#### Header Renderers - -- **base** - Basic header with sorting capabilities -- **cell** - Individual cell header functionality -- **column** - Column management features -- **index** - Main header component -- **manage-columns** - Column visibility management -- **search** - Column-specific search -- **selection** - Column selection functionality - -### Custom Renderers - -Create custom renderers by extending base components: - -```ts -// addon/components/my-custom-renderer.ts -interface MyCustomRendererArgs { - handler: TableHandler; - column: Column; - row: Row; - extra?: { [key: string]: any }; -} - -export default class MyCustomRenderer extends Component {} -``` - -```hbs -{{! Template }} -
-
{{this.value}}
-
-``` - -Then register it in the RenderingResolver: - -```typescript -const rendererMatchers = { - my_column_key: { - cell: MyCustomCellRenderer, - filter: MyCustomFilterRenderer, - header: MyCustomHeaderRenderer - } -}; -``` - -### Built-in Components - -HyperTableV2 includes several automatic components controlled by the `@features` configuration: - -### Feature-Controlled Components - -```ts -features = { - selection: true, // Enables row selection checkboxes - searchable: true, // Enables global search input - manageable_fields: true, // Enables column visibility management - global_filters_reset: true // Enables reset filters button -}; -``` - -These components are automatically included and handle their own state: - -- **HyperTableV2::Search** - Global search across all data -- **HyperTableV2::Selection** - Row selection controls and display -- **HyperTableV2::ManageColumns** - Column visibility management -- **HyperTableV2::Column** - Column wrapper with header rendering -- **HyperTableV2::Cell** - Individual cell component - -Users interact with these components through: - -- **Search**: Typing in the search input (when searchable is enabled) -- **Selection**: Clicking checkboxes to select rows (when selection is enabled) -- **ManageColumns**: Clicking the manage columns button to show/hide columns (when manageable_fields is enabled) -- **Reset Filters**: Clicking the reset button to clear all filters (when global_filters_reset is enabled) - -The components handle their own internal state and communicate with the TableHandler automatically. - -## Events - -TableHandler emits events throughout its lifecycle. Listen to them using the `on` method: - -```ts -// Listen to specific events -this.handler.on('row-click', (row) => { - console.log('Row clicked:', row); -}); - -this.handler.on('apply-filters', (column, filters) => { - console.log('Filters applied:', column.definition.key, filters); -}); - -this.handler.on('columns-loaded', () => { - console.log('Columns have been loaded and are ready'); -}); -``` - -**Available events:** - -- `'columns-loaded'` - When columns are fetched and ready -- `'row-click'` - When a user clicks on a row -- `'apply-filters'` - When filters are applied to a column -- `'reset-columns'` - When columns are reset -- `'remove-column'` - When a column is removed -- `'remove-row'` - When a row is removed -- `'mutate-rows'` - When rows are mutated -- `'reset-rows'` - When rows are reset - -## Contributing - -### Installation - -- `git clone ` -- `cd hypertable` -- `pnpm install` - -### Linting - -- `pnpm lint:hbs` -- `pnpm lint:js` -- `pnpm lint:js --fix` - -### Running tests - -- `ember test` – Runs the test suite on the current Ember version -- `ember test --server` – Runs the test suite in "watch mode" -- `ember try:each` – Runs the test suite against multiple Ember versions - -### Running the dummy application - -- `ember serve` -- Visit the dummy application at [http://localhost:4200](http://localhost:4200). - -See the [Contributing](CONTRIBUTING.md) guide for details. - -### License - -This project is licensed under the [MIT License](LICENSE.md). diff --git a/README.md b/README.md index 5b30ee45..df11e613 100644 --- a/README.md +++ b/README.md @@ -1,62 +1,555 @@ -hypertable -============================================================================== +# Hypertable To follow the early life of this project, see [oss-components#pn-flexbox-tables](https://github.com/upfluence/oss-components/pull/54/files) +## Table of Contents -Compatibility ------------------------------------------------------------------------------- +- [Compatibility](#compatibility) +- [Installation](#installation) +- [Architecture](#architecture) +- [Quick Start](#quick-start) +- [Core Concepts](#core-concepts) +- [Rendering System](#rendering-system) +- [Built-in Components](#built-in-components) +- [Events](#events) +- [Contributing](#contributing) -* Ember.js v3.24 or above -* Ember CLI v3.24 or above -* Node.js v12 or above +## Compatibility +- Ember.js v3.24 or above +- Ember CLI v3.24 or above +- Node.js v12 or above -Installation ------------------------------------------------------------------------------- +## Installation ``` ember install hypertable ``` +## Architecture -Usage ------------------------------------------------------------------------------- +- **TableHandler** - The core system that manages data state, columns, filters, and selections +- **TableManager** - Interface for fetching and persisting column definitions and configurations +- **RowsFetcher** - Interface for fetching paginated data +- **RenderingResolver** - Maps column definitions to appropriate cell, header, and filter renderers +- **Renderers** - Modular components for displaying cells, headers, and filters -[Longer description of how to use the addon in apps.] +## Quick Start +### 1. Implement Required Interfaces -Contributing ------------------------------------------------------------------------------- +Create implementations for the two required interfaces and add a rendering resolver if needed: + +```ts +export default class MyController extends Controller { + constructor() { + super(...arguments); + + const manager = new MyTableManager(); + const fetcher = new MyRowsFetcher(); + const renderingResolver = new MyRenderingResolver(); // Optional + + this.handler = new TableHandler(this, manager, fetcher, renderingResolver); + } + + features = { + selection: true, + searchable: true, + manageable_fields: true, + global_filters_reset: true + }; +} +``` + +### 2. Use HyperTableV2 Component in template + +```hbs + + {{! Custom search block (optional) }} + <:search> + + + + {{! Contextual actions (optional) }} + <:contextual-actions> + + + + {{! Table actions (optional) }} + <:table-actions> + + + + {{! Custom empty state (optional) }} + <:empty-state> +
This is an empty state
+ +
+``` + +### 3. Interface Implementations + +#### TableManager + +```typescript +class MyTableManager implements TableManager { + async fetchColumnDefinitions(): Promise<{ column_definitions: ColumnDefinition[] }> { + // Fetch available column definitions from API + const response = await fetch('/api/column-definitions'); + return response.json(); + } + + async fetchColumns(): Promise<{ columns: Column[] }> { + // Fetch current column configuration from API + const response = await fetch('/api/columns'); + return response.json(); + } + + async upsertColumns(request: { columns: Column[] }): Promise<{ columns: Column[] }> { + // Save column configuration to API + const response = await fetch('/api/columns', { + method: 'POST', + body: JSON.stringify(request) + }); + return response.json(); + } + + // Optional: for faceted filtering + async fetchFacets(columnKey: string, filteringKey: string, searchValue?: string): Promise { + const response = await fetch(`/api/facets/${columnKey}?filtering_key=${filteringKey}&search=${searchValue}`); + return response.json(); + } +} +``` + +**Available methods:** + +- `fetchColumnDefinitions()` - Returns available column definitions +- `fetchColumns()` - Returns current column configuration +- `upsertColumns(request)` - Saves column configuration +- `fetchFacets(columnKey, filteringKey, searchValue)` - Optional: provides faceted filtering + +#### RowsFetcher + +```ts +class MyRowsFetcher implements RowsFetcher { + async fetch(page: number, perPage: number): Promise<{ rows: Row[]; meta: { total: number } }> { + // Fetch paginated data from API + const response = await fetch(`/api/data?page=${page}&per_page=${perPage}`); + return response.json(); + } + + // Optional: for individual row updates + async fetchById(recordId: number): Promise { + const response = await fetch(`/api/data/${recordId}`); + return response.json(); + } +} +``` + +**Available methods:** + +- `fetch(page, perPage)` - Returns paginated data +- `fetchById(id)` - Optional: fetches individual rows + +#### Custom rendering resolver (Optional) + +If no RenderingResolver is provided, Hypertable uses a default resolver with built-in renderers. +The Rendering Resolver extends BaseRenderingResolver from `@upfluence/hypertable/core/rendering-resolver` and determines which component should be used to render each cell, filter, and header according to column key. + +```typescript +type RendererDictionaryItem = { cell?: any; header?: any; filter?: any }; + +// Define mapping dictionary: all the custom columns and their renderers +const rendererMatchers: { [key: string]: RendererDictionaryItem } = { + columnName: { + cell: CustomCellRenderer, + filter: CustomFilterRenderer, + header: CustomHeaderRenderer + } +}; + +export default class MyRenderingResolver extends BaseRenderingResolver { + private context; + + constructor(emberContext: unknown) { + super(emberContext); + this.context = emberContext; + } + + lookupCellComponent(columnDef: ColumnDefinition): Promise { + return this._lookupComponent(columnDef, 'cell'); + } + + lookupFilteringComponent(columnDef: ColumnDefinition): Promise { + return this._lookupComponent(columnDef, 'filter'); + } + + lookupHeaderComponent(columnDef: ColumnDefinition): Promise { + return this._lookupComponent(columnDef, 'header'); + } + + private _lookupComponent( + columnDef: ColumnDefinition, + type: 'header' | 'filter' | 'cell' + ): Promise { + let rendererMatch = rendererMatchers[camelize(columnDef.key)]; + + if (rendererMatch && rendererMatch[type]) { + return Promise.resolve({ + component: ensureSafeComponent(rendererMatch[type], this.context) + }); + } + + return super.lookupComponent(columnDef, type); + } +} +``` + +## Core Concepts + +### Column Definitions + +Column definitions describe the structure and capabilities of table columns. See `@upfluence/hypertable/core/interfaces/column.ts` for the complete type definition: + +```ts +type ColumnDefinition = { + key: string; // Unique identifier + type: string; // Data type (text, numeric, date) + name: string; // Display name + category: string; // Grouping identifier + clustering_key: string; // Grouping identifier within a category + size: FieldSize; // Column width (XS, S, M, L, XL) + orderable: boolean; // Can be sorted + orderable_by: string[] | null; // Fields to sort by + filterable: boolean; // Can be filtered + filterable_by: string[] | null; // Fields to filter by + facetable: boolean; // Supports faceted search + facetable_by: string[] | null; // Fields for facets + empty_state_message?: string; // Message for empty values + position?: { + sticky: boolean; // Sticky column + side?: 'left' | 'right'; // Which side to stick to + }; +}; +``` + +### Row Data Structure + +Row data is used by cell renderers to display values and by the selection system to identify records. See `@upfluence/hypertable/core/interfaces/rows-fetcher.ts` for the complete type definition: + +```ts +type Row = { + influencerId: number; + recordId: number; + holderId: number; + holderType: string; + [key: string]: any; // Additional dynamic fields based on column definitions +}; +``` + +### Column Management + +```typescript +// Add a column to the table +await this.handler.addColumn(columnDefinition); + +// Remove a column from the table +await this.handler.removeColumn(columnDefinition); + +// Reorder columns +this.handler.reorderColumns(newColumnOrder); + +// Reset column filters and ordering +await this.handler.resetColumns(columnsToReset); +``` + +### Row Management + +```typescript +// Update a specific row from the data source +await this.handler.updateRowById(123); + +// Remove a row from the table +this.handler.removeRow(123); + +// Mutate a row in place and trigger redraw +this.handler.mutateRow(123, (row) => { + row.someField = 'new value'; + return true; // Return true to trigger table redraw +}); + +// Toggle loading state for a specific row +this.handler.toggleRowLoadingState(123); + +// Reset all rows and refetch +await this.handler.resetRows(); +``` + +### Selection Management + +Hypertable supports two selection modes: + +1. **Array mode**: `selection` contains selected rows +2. **Global mode**: `selection = 'all'` with `exclusion` containing unselected rows + +```ts +// Select all visible rows +this.tableHandler.toggleSelectAll(true); + +// Select all rows globally (including those not currently loaded) +this.tableHandler.selectAllGlobal(); + +// Clear all selections +this.tableHandler.clearSelection(); + +// Update individual row selection +this.tableHandler.updateSelection(row); + +// Access current selection state +const selection = this.handler.selection; // Row[] | 'all' +const exclusions = this.handler.exclusion; // Row[] (when selection is 'all') +``` + +### Filtering and Ordering + +Filter and order structures are used internally by the TableHandler to manage table state and are passed to the TableManager for persistence. See `@upfluence/hypertable/core/interfaces/column.ts` for complete type definitions: + +```typescript +// Filter structure - used when applying column filters +type Filter = { + key: string; // Field to filter on + value: string; // Filter value + extra?: any; // Additional filter parameters +}; + +// Ordering structure - used when sorting columns +type Order = { + key: string; // Field to order by + direction: 'asc' | 'desc'; // Sort direction +}; +``` + +```ts +// Apply filters to a column +await this.handler.applyFilters(column, [{ key: 'name', value: 'value' }]); + +// Apply ordering to a column +await this.handler.applyOrder(column, 'asc'); // or 'desc' + +// Fetch facets for filtering (requires TableManager.fetchFacets) +const facets = await this.handler.fetchFacets('value', 'name', 'searchValue'); +``` + +#### Faceted Filtering + +Hypertable provides built-in support for **faceted filtering**, allowing users to select multiple values for a column filter from a dynamic list of facets. + +1. **Enable Facets in `ColumnDefinition`** + +To make a column facetable, set `facetable: true` and provide at least one key in `facetable_by`. + +2. **Implement `fetchFacets` in `TableManager`** + +The `TableManager` must implement a `fetchFacets` method to return facets for a given column. + +```ts +class MyTableManager implements TableManager { + async fetchFacets(columnKey: string, filteringKey: string, searchValue?: string): Promise { + const response = await fetch(`/api/facets/${columnKey}?filtering_key=${filteringKey}&search=${searchValue}`); + return response.json(); + } +} +``` + +The `FacetsResponse` type should have the following structure: + +```ts +type FacetsResponse = { + filtering_key: string; + facets: Facet[]; +}; + +type Facet = { + identifier: string; // Unique value sent in the filter request + display_name: string; // Text displayed to the user + count?: number; // (Optional) Used for sorting and displaying counts +}; +``` + +3. **Built-in Facet Loader** + +Hypertable ships with a `FacetsLoader` component that automatically: + +- Fetches facets when the filter UI opens +- Displays a skeleton loader while loading +- Supports search with a 300 ms debounce +- Allows multiple facet selection +- Sorts facets by `count` descending by default (custom sorting function can be provided) + +4. **Applying and Removing Facets** + +When a user toggles a facet: + +- `FacetsLoader` builds a filter object `{ key, value }` +- Calls `TableHandler.applyFilters()` +- Updates the `appliedFacets` list for that column + +This behavior is automatic and does not require additional setup. + +## Rendering System + +### Built-in Renderers + +Hypertable includes built-in renderers for common data types: + +#### Cell Renderers + +- **text** - Basic text with ellipsis and tooltip +- **numeric** - Formatted numbers +- **date** - Formatted dates + +#### Filter Renderers + +- **text** - Text input filtering +- **numeric** - Numeric range filtering +- **date** - Date range filtering + +#### Header Renderers + +- **base** - Basic header with sorting capabilities +- **cell** - Individual cell header functionality +- **column** - Column management features +- **index** - Main header component +- **manage-columns** - Column visibility management +- **search** - Column-specific search +- **selection** - Column selection functionality + +### Custom Renderers + +Create custom renderers by extending base components: + +```ts +// addon/components/my-custom-renderer.ts +interface MyCustomRendererArgs { + handler: TableHandler; + column: Column; + row: Row; + extra?: { [key: string]: any }; +} + +export default class MyCustomRenderer extends Component {} +``` + +```hbs +{{! Template }} +
+
{{this.value}}
+
+``` + +Then register it in the RenderingResolver: + +```typescript +const rendererMatchers = { + my_column_key: { + cell: MyCustomCellRenderer, + filter: MyCustomFilterRenderer, + header: MyCustomHeaderRenderer + } +}; +``` + +### Built-in Components + +HyperTableV2 includes several automatic components controlled by the `@features` configuration: + +### Feature-Controlled Components + +```ts +features = { + selection: true, // Enables row selection checkboxes + searchable: true, // Enables global search input + manageable_fields: true, // Enables column visibility management + global_filters_reset: true // Enables reset filters button +}; +``` + +These components are automatically included and handle their own state: + +- **HyperTableV2::Search** - Global search across all data +- **HyperTableV2::Selection** - Row selection controls and display +- **HyperTableV2::ManageColumns** - Column visibility management +- **HyperTableV2::Column** - Column wrapper with header rendering +- **HyperTableV2::Cell** - Individual cell component + +Users interact with these components through: + +- **Search**: Typing in the search input (when searchable is enabled) +- **Selection**: Clicking checkboxes to select rows (when selection is enabled) +- **ManageColumns**: Clicking the manage columns button to show/hide columns (when manageable_fields is enabled) +- **Reset Filters**: Clicking the reset button to clear all filters (when global_filters_reset is enabled) + +The components handle their own internal state and communicate with the TableHandler automatically. + +## Events + +TableHandler emits events throughout its lifecycle. Listen to them using the `on` method: + +```ts +// Listen to specific events +this.handler.on('row-click', (row) => { + console.log('Row clicked:', row); +}); + +this.handler.on('apply-filters', (column, filters) => { + console.log('Filters applied:', column.definition.key, filters); +}); + +this.handler.on('columns-loaded', () => { + console.log('Columns have been loaded and are ready'); +}); +``` + +**Available events:** + +- `'columns-loaded'` - When columns are fetched and ready +- `'row-click'` - When a user clicks on a row +- `'apply-filters'` - When filters are applied to a column +- `'reset-columns'` - When columns are reset +- `'remove-column'` - When a column is removed +- `'remove-row'` - When a row is removed +- `'mutate-rows'` - When rows are mutated +- `'reset-rows'` - When rows are reset + +## Contributing ### Installation -* `git clone ` -* `cd hypertable` -* `pnpm install` +- `git clone ` +- `cd hypertable` +- `pnpm install` ### Linting -* `pnpm lint:hbs` -* `pnpm lint:js` -* `pnpm lint:js --fix` +- `pnpm lint:hbs` +- `pnpm lint:js` +- `pnpm lint:js --fix` ### Running tests -* `ember test` – Runs the test suite on the current Ember version -* `ember test --server` – Runs the test suite in "watch mode" -* `ember try:each` – Runs the test suite against multiple Ember versions +- `ember test` – Runs the test suite on the current Ember version +- `ember test --server` – Runs the test suite in "watch mode" +- `ember try:each` – Runs the test suite against multiple Ember versions ### Running the dummy application -* `ember serve` -* Visit the dummy application at [http://localhost:4200](http://localhost:4200). +- `ember serve` +- Visit the dummy application at [http://localhost:4200](http://localhost:4200). See the [Contributing](CONTRIBUTING.md) guide for details. - -License ------------------------------------------------------------------------------- +### License This project is licensed under the [MIT License](LICENSE.md). From 8cd95ccb4108aa02bebc6e3d72d9d725332f27c6 Mon Sep 17 00:00:00 2001 From: Olympe Lespagnon Date: Wed, 22 Oct 2025 17:28:05 +0200 Subject: [PATCH 4/7] Comments --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index df11e613..71538716 100644 --- a/README.md +++ b/README.md @@ -419,12 +419,6 @@ Hypertable includes built-in renderers for common data types: #### Header Renderers - **base** - Basic header with sorting capabilities -- **cell** - Individual cell header functionality -- **column** - Column management features -- **index** - Main header component -- **manage-columns** - Column visibility management -- **search** - Column-specific search -- **selection** - Column selection functionality ### Custom Renderers From ff2032156b2e778c000d39fb2bac23a162f8e4e7 Mon Sep 17 00:00:00 2001 From: Olympe Lespagnon Date: Thu, 23 Oct 2025 16:34:52 +0200 Subject: [PATCH 5/7] Add an abstract section --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 71538716..528c739c 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,13 @@ To follow the early life of this project, see [oss-components#pn-flexbox-tables](https://github.com/upfluence/oss-components/pull/54/files) +## Abstract + +Hypertable is an Ember.js library designed to simplify the creation and management of complex data tables. +It separates concerns between data handling (TableHandler), configuration persistence (TableManager), and data retrieval (RowsFetcher), ensuring consistent and predictable state management across all table operations such as filtering, sorting, selection, and pagination. +Rendering is managed through a resolver system that maps each column to its corresponding header, cell, and filter component — built-in or custom. +The goal is to provide a consistent and maintainable way to build tables across projects, without duplicating logic or UI patterns. + ## Table of Contents - [Compatibility](#compatibility) From 4fdcac2d0006a7feeb58e94db819379fb80f118a Mon Sep 17 00:00:00 2001 From: Olympe Lespagnon Date: Thu, 23 Oct 2025 17:50:20 +0200 Subject: [PATCH 6/7] Review --- ARCHITECTURE.md | 7 ++ README.md | 229 ++++++++++++++++++------------------------------ 2 files changed, 94 insertions(+), 142 deletions(-) create mode 100644 ARCHITECTURE.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 00000000..9c78a69b --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,7 @@ +# Architecture + +- **TableHandler** - The core system that manages data state, columns, filters, and selections +- **TableManager** - Interface for fetching and persisting column definitions and configurations +- **RowsFetcher** - Interface for fetching paginated data +- **RenderingResolver** - Maps column definitions to appropriate cell, header, and filter renderers +- **Renderers** - Modular components for displaying cells, headers, and filters diff --git a/README.md b/README.md index 528c739c..cd95f201 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ The goal is to provide a consistent and maintainable way to build tables across - [Compatibility](#compatibility) - [Installation](#installation) -- [Architecture](#architecture) - [Quick Start](#quick-start) - [Core Concepts](#core-concepts) - [Rendering System](#rendering-system) @@ -31,141 +30,60 @@ The goal is to provide a consistent and maintainable way to build tables across ## Installation ``` -ember install hypertable +pnpm install @upfluence/hypertable ``` -## Architecture +Additionally, the following registry should be added to the `.npmrc` file: -- **TableHandler** - The core system that manages data state, columns, filters, and selections -- **TableManager** - Interface for fetching and persisting column definitions and configurations -- **RowsFetcher** - Interface for fetching paginated data -- **RenderingResolver** - Maps column definitions to appropriate cell, header, and filter renderers -- **Renderers** - Modular components for displaying cells, headers, and filters - -## Quick Start - -### 1. Implement Required Interfaces - -Create implementations for the two required interfaces and add a rendering resolver if needed: - -```ts -export default class MyController extends Controller { - constructor() { - super(...arguments); - - const manager = new MyTableManager(); - const fetcher = new MyRowsFetcher(); - const renderingResolver = new MyRenderingResolver(); // Optional - - this.handler = new TableHandler(this, manager, fetcher, renderingResolver); - } - - features = { - selection: true, - searchable: true, - manageable_fields: true, - global_filters_reset: true - }; -} +``` +@upfluence:registry=https://npm.pkg.github.com ``` -### 2. Use HyperTableV2 Component in template - -```hbs - - {{! Custom search block (optional) }} - <:search> - - - - {{! Contextual actions (optional) }} - <:contextual-actions> - - - - {{! Table actions (optional) }} - <:table-actions> - - +## Quick Start - {{! Custom empty state (optional) }} - <:empty-state> -
This is an empty state
- -
-``` +### 1. Required Interfaces -### 3. Interface Implementations +Hypertable requires implementations for two interfaces — `TableManager` and `RowsFetcher` — and optionally a custom `RenderingResolver`. These classes define how the table fetches data, stores configuration, and resolves which components to render. #### TableManager -```typescript -class MyTableManager implements TableManager { - async fetchColumnDefinitions(): Promise<{ column_definitions: ColumnDefinition[] }> { - // Fetch available column definitions from API - const response = await fetch('/api/column-definitions'); - return response.json(); - } +The `TableManager` interface handles column configuration and persistence. It defines how the table fetches available column definitions, retrieves the current column setup, and saves column changes. - async fetchColumns(): Promise<{ columns: Column[] }> { - // Fetch current column configuration from API - const response = await fetch('/api/columns'); - return response.json(); - } +**Required methods:** - async upsertColumns(request: { columns: Column[] }): Promise<{ columns: Column[] }> { - // Save column configuration to API - const response = await fetch('/api/columns', { - method: 'POST', - body: JSON.stringify(request) - }); - return response.json(); - } +- `fetchColumnDefinitions()` - Returns all available column definitions that can be added to the table. This typically comes from your API and defines the structure, type, and capabilities of each column. +- `fetchColumns()` - Returns the current column configuration (which columns are visible, their order, applied filters, and sorting). This represents the user's saved table state. +- `upsertColumns(request)` - Saves the column configuration to persist user preferences. Called whenever columns are added, removed, reordered, or have their filters/sorting modified. - // Optional: for faceted filtering - async fetchFacets(columnKey: string, filteringKey: string, searchValue?: string): Promise { - const response = await fetch(`/api/facets/${columnKey}?filtering_key=${filteringKey}&search=${searchValue}`); - return response.json(); - } -} -``` +**Optional methods:** -**Available methods:** - -- `fetchColumnDefinitions()` - Returns available column definitions -- `fetchColumns()` - Returns current column configuration -- `upsertColumns(request)` - Saves column configuration -- `fetchFacets(columnKey, filteringKey, searchValue)` - Optional: provides faceted filtering +- `fetchFacets(columnKey, filteringKey, searchValue)` - Provides faceted filtering by returning a list of available filter values for a given column. Only needed if you want to enable faceted filtering (see [Faceted Filtering](#faceted-filtering)). #### RowsFetcher -```ts -class MyRowsFetcher implements RowsFetcher { - async fetch(page: number, perPage: number): Promise<{ rows: Row[]; meta: { total: number } }> { - // Fetch paginated data from API - const response = await fetch(`/api/data?page=${page}&per_page=${perPage}`); - return response.json(); - } +The `RowsFetcher` interface handles data retrieval. It defines how the table fetches paginated rows and optionally updates individual rows. - // Optional: for individual row updates - async fetchById(recordId: number): Promise { - const response = await fetch(`/api/data/${recordId}`); - return response.json(); - } -} -``` +**Required methods:** + +- `fetch(page, perPage)` - Returns paginated data for the table. Called whenever the table needs to load or refresh its data (pagination, filtering, sorting changes). Must return both the rows and a total count for pagination. -**Available methods:** +**Optional methods:** -- `fetch(page, perPage)` - Returns paginated data -- `fetchById(id)` - Optional: fetches individual rows +- `fetchById(recordId)` - Fetches a single row by its ID to refresh its data without reloading the entire table. Useful when a row is updated externally and needs to be refreshed in place. #### Custom rendering resolver (Optional) -If no RenderingResolver is provided, Hypertable uses a default resolver with built-in renderers. +If no RenderingResolver is provided, Hypertable uses a default resolver with built-in renderers with built-in renderers that support: + +- `integer` type: this will properly format numbers in cells and brings range-based filtering +- `timestamp` type: properly displays dates and brings calendar-based filtering +- a default `text` renderer to display textual content and filter using a typeahead input + The Rendering Resolver extends BaseRenderingResolver from `@upfluence/hypertable/core/rendering-resolver` and determines which component should be used to render each cell, filter, and header according to column key. ```typescript +import { ResolvedRenderingComponent } from '@upfluence/hypertable/core/interfaces'; + type RendererDictionaryItem = { cell?: any; header?: any; filter?: any }; // Define mapping dictionary: all the custom columns and their renderers @@ -214,48 +132,75 @@ export default class MyRenderingResolver extends BaseRenderingResolver { } ``` -## Core Concepts - -### Column Definitions +### 2. Integration example -Column definitions describe the structure and capabilities of table columns. See `@upfluence/hypertable/core/interfaces/column.ts` for the complete type definition: +#### Controller ```ts -type ColumnDefinition = { - key: string; // Unique identifier - type: string; // Data type (text, numeric, date) - name: string; // Display name - category: string; // Grouping identifier - clustering_key: string; // Grouping identifier within a category - size: FieldSize; // Column width (XS, S, M, L, XL) - orderable: boolean; // Can be sorted - orderable_by: string[] | null; // Fields to sort by - filterable: boolean; // Can be filtered - filterable_by: string[] | null; // Fields to filter by - facetable: boolean; // Supports faceted search - facetable_by: string[] | null; // Fields for facets - empty_state_message?: string; // Message for empty values - position?: { - sticky: boolean; // Sticky column - side?: 'left' | 'right'; // Which side to stick to +import { TableHandler } from '@upfluence/hypertable/core/handler'; +import { MyTableManager } from './table-manager'; +import { MyRowsFetcher } from './rows-fetcher'; +import { MyRenderingResolver } from './rendering-resolver'; + +export default class MyController extends Controller { + constructor() { + super(...arguments); + + const manager = new MyTableManager(); + const fetcher = new MyRowsFetcher(); + const renderingResolver = new MyRenderingResolver(); // Optional + + this.handler = new TableHandler(this, manager, fetcher, renderingResolver); + } + + features = { + selection: true, + searchable: true, + manageable_fields: true, + global_filters_reset: true }; -}; +} ``` -### Row Data Structure +#### Template -Row data is used by cell renderers to display values and by the selection system to identify records. See `@upfluence/hypertable/core/interfaces/rows-fetcher.ts` for the complete type definition: +HyperTableV2 supports several named blocks that allow you to customize specific areas of the table interface. These blocks are optional and can be used to inject custom content or components into predefined slots. -```ts -type Row = { - influencerId: number; - recordId: number; - holderId: number; - holderType: string; - [key: string]: any; // Additional dynamic fields based on column definitions -}; +- **`:search`** - For a custom search behavior or UI that differs from the default search input +- **`:contextual-actions`** - For bulk operations that only make sense when items are selected (export, delete, bulk edit) +- **`:table-actions`** - For actions that are always relevant (create new, import, settings) +- **`:empty-state`** - For a custom messaging or actions when no data is available + +```hbs + + <:search> + + + + <:contextual-actions> + + + + <:table-actions> + + + + <:empty-state> +
This is an empty state
+ +
``` +## Core Concepts + +### Column Definitions + +Column definitions describe the structure and capabilities of table columns. See `@upfluence/hypertable/core/interfaces/column.ts` for the complete type definition. + +### Row Data Structure + +Row data is used by cell renderers to display values and by the selection system to identify records. See `@upfluence/hypertable/core/interfaces/rows-fetcher.ts` for the complete type definition. + ### Column Management ```typescript From 5c725736652082c7a936f010ebf1a73e08b65f57 Mon Sep 17 00:00:00 2001 From: Olympe Lespagnon Date: Thu, 27 Nov 2025 17:29:44 +0100 Subject: [PATCH 7/7] Review Elodie --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index cd95f201..fd5862e9 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,9 @@ The `RowsFetcher` interface handles data retrieval. It defines how the table fet #### Custom rendering resolver (Optional) -If no RenderingResolver is provided, Hypertable uses a default resolver with built-in renderers with built-in renderers that support: +If no RenderingResolver is provided, Hypertable uses a default resolver with built-in renderers that support: -- `integer` type: this will properly format numbers in cells and brings range-based filtering +- `integer` type: properly formats numbers in cells and brings range-based filtering - `timestamp` type: properly displays dates and brings calendar-based filtering - a default `text` renderer to display textual content and filter using a typeahead input @@ -119,7 +119,7 @@ export default class MyRenderingResolver extends BaseRenderingResolver { columnDef: ColumnDefinition, type: 'header' | 'filter' | 'cell' ): Promise { - let rendererMatch = rendererMatchers[camelize(columnDef.key)]; + const rendererMatch = rendererMatchers[camelize(columnDef.key)]; if (rendererMatch && rendererMatch[type]) { return Promise.resolve({