Skip to content

Commit

Permalink
feat(admin-ui): Allow custom components in data table columns
Browse files Browse the repository at this point in the history
Closes #2347, closes #2353
  • Loading branch information
michaelbromley committed Sep 6, 2023
1 parent 73a78db commit d3474dd
Show file tree
Hide file tree
Showing 9 changed files with 214 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { APP_INITIALIZER } from '@angular/core';
import {
DataTableComponentConfig,
DataTableCustomComponentService,
} from '../shared/components/data-table-2/data-table-custom-component.service';

/**
* @description
* Allows you to override the default component used to render the data of a particular column in a DataTable.
* The component should implement the {@link CustomDataTableColumnComponent} interface.
*
* @example
* ```ts title="components/custom-table.component.ts"
* import { Component, Input } from '\@angular/core';
* import { CustomColumnComponent } from '\@vendure/admin-ui/core';
*
* @Component({
* selector: 'custom-slug-component',
* template: `
* <a [href]="'https://example.com/products/' + rowItem.slug" target="_blank">{{ rowItem.slug }}</a>
* `,
* standalone: true,
* })
* export class CustomTableComponent implements CustomColumnComponent {
* @Input() rowItem: any;
* }
* ```
*
* ```ts title="providers.ts"
* import { registerDataTableComponent } from '\@vendure/admin-ui/core';
* import { CustomTableComponent } from './components/custom-table.component';
*
* export default [
* registerDataTableComponent({
* component: CustomTableComponent,
* tableId: 'product-list',
* columnId: 'slug',
* }),
* ];
* ```
*
* @docsCategory custom-table-components
*/
export function registerDataTableComponent(config: DataTableComponentConfig) {
return {
provide: APP_INITIALIZER,
multi: true,
useFactory: (dataTableCustomComponentService: DataTableCustomComponentService) => () => {
dataTableCustomComponentService.registerCustomComponent(config);
},
deps: [DataTableCustomComponentService],
};
}
19 changes: 10 additions & 9 deletions packages/admin-ui/src/lib/core/src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,17 +74,24 @@ export * from './data/utils/add-custom-fields';
export * from './data/utils/get-server-location';
export * from './data/utils/remove-readonly-custom-fields';
export * from './data/utils/transform-relation-custom-field-inputs';
export * from './extension/add-action-bar-item';
export * from './extension/add-nav-menu-item';
export * from './extension/components/angular-route.component';
export * from './extension/components/route.component';
export * from './extension/providers/page-metadata.service';
export * from './extension/register-bulk-action';
export * from './extension/register-custom-detail-component';
export * from './extension/register-dashboard-widget';
export * from './extension/register-data-table-component';
export * from './extension/register-form-input-component';
export * from './extension/register-history-entry-component';
export * from './extension/register-route-component';
export * from './extension/types';
export * from './providers/alerts/alerts.service';
export * from './providers/auth/auth.service';
export * from './providers/breadcrumb/breadcrumb.service';
export * from './providers/bulk-action-registry/bulk-action-registry.service';
export * from './providers/bulk-action-registry/bulk-action-types';
export * from './extension/register-bulk-action';
export * from './providers/channel/channel.service';
export * from './providers/component-registry/component-registry.service';
export * from './providers/custom-detail-component/custom-detail-component-types';
Expand All @@ -94,7 +101,6 @@ export * from './providers/custom-history-entry-component/history-entry-componen
export * from './providers/custom-history-entry-component/history-entry-component.service';
export * from './providers/dashboard-widget/dashboard-widget-types';
export * from './providers/dashboard-widget/dashboard-widget.service';
export * from './extension/register-dashboard-widget';
export * from './providers/data-table/data-table-filter-collection';
export * from './providers/data-table/data-table-filter';
export * from './providers/data-table/data-table-sort-collection';
Expand Down Expand Up @@ -143,6 +149,7 @@ export * from './shared/components/customer-label/customer-label.component';
export * from './shared/components/data-table/data-table-column.component';
export * from './shared/components/data-table/data-table.component';
export * from './shared/components/data-table-2/data-table-column.component';
export * from './shared/components/data-table-2/data-table-custom-component.service';
export * from './shared/components/data-table-2/data-table-custom-field-column.component';
export * from './shared/components/data-table-2/data-table-search.component';
export * from './shared/components/data-table-2/data-table2.component';
Expand Down Expand Up @@ -253,13 +260,13 @@ export * from './shared/dynamic-form-inputs/combination-mode-form-input/combinat
export * from './shared/dynamic-form-inputs/currency-form-input/currency-form-input.component';
export * from './shared/dynamic-form-inputs/customer-group-form-input/customer-group-form-input.component';
export * from './shared/dynamic-form-inputs/date-form-input/date-form-input.component';
export * from './shared/dynamic-form-inputs/default-form-inputs';
export * from './shared/dynamic-form-inputs/dynamic-form-input/dynamic-form-input.component';
export * from './shared/dynamic-form-inputs/facet-value-form-input/facet-value-form-input.component';
export * from './shared/dynamic-form-inputs/number-form-input/number-form-input.component';
export * from './shared/dynamic-form-inputs/password-form-input/password-form-input.component';
export * from './shared/dynamic-form-inputs/product-multi-selector-form-input/product-multi-selector-form-input.component';
export * from './shared/dynamic-form-inputs/product-selector-form-input/product-selector-form-input.component';
export * from './shared/dynamic-form-inputs/default-form-inputs';
export * from './shared/dynamic-form-inputs/relation-form-input/asset/relation-asset-input.component';
export * from './shared/dynamic-form-inputs/relation-form-input/customer/relation-customer-input.component';
export * from './shared/dynamic-form-inputs/relation-form-input/generic/relation-generic-input.component';
Expand Down Expand Up @@ -293,9 +300,3 @@ export * from './shared/pipes/time-ago.pipe';
export * from './shared/providers/routing/can-deactivate-detail-guard';
export * from './shared/shared.module';
export * from './validators/unicode-pattern.validator';
export { registerCustomDetailComponent } from './extension/register-custom-detail-component';
export { registerHistoryEntryComponent } from './extension/register-history-entry-component';
export { addNavMenuItem } from './extension/add-nav-menu-item';
export { addNavMenuSection } from './extension/add-nav-menu-item';
export { addActionBarItem } from './extension/add-action-bar-item';
export { registerFormInputComponent } from './extension/register-form-input-component';
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import { DataTableSort } from '../../../providers/data-table/data-table-sort';
exportAs: 'row',
})
export class DataTable2ColumnComponent<T> implements OnInit {
@Input() id: string;
/**
* When set to true, this column will expand to use available width
*/
@Input() id: string;
@Input() expand = false;
@Input() heading: string;
@Input() align: 'left' | 'right' | 'center' = 'left';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { Injectable, Provider, Type } from '@angular/core';
import { PageLocationId } from '../../../common/component-registry-types';

export type DataTableLocationId =
| {
[location in PageLocationId]: location extends `${string}-list` ? location : never;
}[PageLocationId]
| 'collection-contents'
| 'edit-options-list'
| 'manage-product-variant-list'
| 'customer-order-list'
| string;

export type DataTableColumnId =
| 'id'
| 'created-at'
| 'updated-at'
| 'name'
| 'code'
| 'description'
| 'slug'
| 'enabled'
| 'sku'
| 'price'
| 'price-with-tax'
| 'status'
| 'state'
| 'image'
| 'quantity'
| 'total'
| 'stock-on-hand'
| string;

/**
* @description
* Components which are to be used to render custom cells in a data table should implement this interface.
*
* The `rowItem` property is the data object for the row, e.g. the `Product` object if used
* in the `product-list` table.
*
* @docsCategory custom-table-components
*/
export interface CustomColumnComponent {
rowItem: any;
}

/**
* @description
* Configures a {@link CustomDetailComponent} to be placed in the given location.
*
* @docsCategory custom-table-components
*/
export interface DataTableComponentConfig {
/**
* @description
* The location in the UI where the custom component should be placed.
*/
tableId: DataTableLocationId;
/**
* @description
* The column in the table where the custom component should be placed.
*/
columnId: DataTableColumnId;
/**
* @description
* The component to render in the table cell. This component should implement the
* {@link CustomColumnComponent} interface.
*/
component: Type<CustomColumnComponent>;
providers?: Provider[];
}

type CompoundId = `${DataTableLocationId}.${DataTableColumnId}`;

@Injectable({
providedIn: 'root',
})
export class DataTableCustomComponentService {
private configMap = new Map<CompoundId, DataTableComponentConfig>();

registerCustomComponent(config: DataTableComponentConfig) {
const id = this.compoundId(config.tableId, config.columnId);
this.configMap.set(id, config);
}

getCustomComponentsFor(
tableId: DataTableLocationId,
columnId: DataTableColumnId,
): DataTableComponentConfig | undefined {
return this.configMap.get(this.compoundId(tableId, columnId));
}

private compoundId(tableId: DataTableLocationId, columnId: DataTableColumnId): CompoundId {
return `${tableId}.${columnId}`;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,20 @@
<td *ngFor="let column of visibleSortedColumns" [class.active]="activeIndex === i">
<div class="cell-content" [ngClass]="column.align">
<ng-container
*ngTemplateOutlet="column.template; context: { item: item, index: i }"
></ng-container>
*ngIf="
customComponents.get(column.id) as customComponetConfig;
else defaultComponent
"
>
<ng-container
*ngComponentOutlet="customComponetConfig.component; inputs: { rowItem: item }"
></ng-container>
</ng-container>
<ng-template #defaultComponent>
<ng-container
*ngTemplateOutlet="column.template; context: { item: item, index: i }"
></ng-container>
</ng-template>
</div>
</td>
<td [class.active]="activeIndex === i"><!-- column select --></td>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ import { BulkActionMenuComponent } from '../bulk-action-menu/bulk-action-menu.co

import { FilterPresetService } from '../data-table-filter-presets/filter-preset.service';
import { DataTable2ColumnComponent } from './data-table-column.component';
import {
DataTableComponentConfig,
DataTableCustomComponentService,
DataTableLocationId,
} from './data-table-custom-component.service';
import { DataTableCustomFieldColumnComponent } from './data-table-custom-field-column.component';
import { DataTable2SearchComponent } from './data-table-search.component';

Expand Down Expand Up @@ -100,7 +105,7 @@ import { DataTable2SearchComponent } from './data-table-search.component';
providers: [PaginationService, FilterPresetService],
})
export class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDestroy {
@Input() id: string;
@Input() id: DataTableLocationId;
@Input() items: T[];
@Input() itemsPerPage: number;
@Input() currentPage: number;
Expand All @@ -121,6 +126,8 @@ export class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDe

route = inject(ActivatedRoute);
filterPresetService = inject(FilterPresetService);
dataTableCustomComponentService = inject(DataTableCustomComponentService);
protected customComponents = new Map<string, DataTableComponentConfig>();

rowTemplate: TemplateRef<any>;
currentStart: number;
Expand Down Expand Up @@ -219,6 +226,13 @@ export class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDe
column.setVisibility(column.hiddenByDefault);
}
column.onColumnChange(updateColumnVisibility);
const customComponent = this.dataTableCustomComponentService.getCustomComponentsFor(
this.id,
column.id,
);
if (customComponent) {
this.customComponents.set(column.id, customComponent);
}
});

if (this.selectionManager) {
Expand Down
8 changes: 4 additions & 4 deletions packages/admin-ui/src/lib/react/src/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
export * from './components/react-custom-detail.component';
export * from './components/react-form-input.component';
export * from './components/react-route.component';
export * from './directives/react-component-host.directive';
export * from './react-components/Card';
export * from './react-components/Link';
export * from './react-hooks/use-detail-component-data';
export * from './react-hooks/use-form-control';
export * from './react-hooks/use-injector';
export * from './react-hooks/use-page-metadata';
export * from './react-hooks/use-query';
export * from './register-react-custom-detail-component';
export * from './directives/react-component-host.directive';
export * from './react-components/Card';
export * from './react-components/Link';
export * from './register-react-form-input-component';
export * from './register-react-route-component';
export * from './types';
export { registerReactFormInputComponent } from './register-react-form-input-component';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Component, Input } from '@angular/core';
import { CustomColumnComponent } from '@vendure/admin-ui/core';

@Component({
selector: 'custom-slug-component',
template: `
<a [href]="'https://example.com/products/' + rowItem.slug" target="_blank">{{ rowItem.slug }}</a>
`,
standalone: true,
})
export class CustomTableComponent implements CustomColumnComponent {
@Input() rowItem: any;
}
10 changes: 8 additions & 2 deletions packages/dev-server/test-plugins/experimental-ui/providers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { addNavMenuSection } from '@vendure/admin-ui/core';
import { addNavMenuSection, registerDataTableComponent } from '@vendure/admin-ui/core';
import { registerReactFormInputComponent, registerReactCustomDetailComponent } from '@vendure/admin-ui/react';
import { CustomDetailComponent } from './components/CustomDetailComponent';

import { CustomTableComponent } from './components/custom-table.component';
import { CustomDetailComponent } from './components/CustomDetailComponent';
import { ReactNumberInput } from './components/ReactNumberInput';

export default [
Expand Down Expand Up @@ -34,4 +35,9 @@ export default [
foo: 'bar',
},
}),
registerDataTableComponent({
component: CustomTableComponent,
tableId: 'product-list',
columnId: 'slug',
}),
];

0 comments on commit d3474dd

Please sign in to comment.