Browse Source

docs: Add docs on custom data table components

Michael Bromley 2 years ago
parent
commit
7c1454d431

BIN
docs/docs/guides/extending-the-admin-ui/custom-data-table-components/custom-data-table-location.webp


BIN
docs/docs/guides/extending-the-admin-ui/custom-data-table-components/custom-data-table.webp


+ 111 - 0
docs/docs/guides/extending-the-admin-ui/custom-data-table-components/index.md

@@ -0,0 +1,111 @@
+---
+title: 'Custom DataTable Components'
+weight: 6
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+The Admin UI list views are powered by a data table component which features sorting, advanced filtering, pagination and more. It will also give you the option of displaying any configured [custom fields](/guides/developer-guide/custom-fields/) for the entity in question.
+
+With Admin UI extensions, you can specify custom components to use in rendering any column of any data table - both custom fields _and_ built-in fields, using either Angular or React components.
+
+Let's say we want to make the product "slug" column link to the matching product detail page in our storefront.
+
+### 1. Define a component
+
+First we'll define the component we will use to render the "slug" column:
+
+<Tabs groupId="framework">
+<TabItem value="angular" label="Angular">
+
+Angular components will receive the value of the current column as the `rowItem` input. In this case, the `rowItem` will be the Product entity, because we will be adding this to the product list data table.
+
+```ts title="src/plugins/slug-link/ui/components/slug-link/slug-link.component.ts"
+import { Component, Input } from '@angular/core';
+import { CustomColumnComponent } from '@vendure/admin-ui/core';
+
+@Component({
+    selector: 'slug-link',
+    template: `
+        <a [href]="'https://example.com/products/' + rowItem.slug" target="_blank">{{ rowItem.slug }}</a>
+    `,
+    standalone: true,
+})
+export class SlugLinkComponent implements CustomColumnComponent {
+    @Input() rowItem: any;
+}
+```
+
+</TabItem>
+<TabItem value="react" label="React">
+
+React components will receive the value of the current column as the `rowItem` prop. In this case, the `rowItem` will be the Product entity, because we will be adding this to the product list data table.
+
+```tsx title="src/plugins/slug-link/ui/components/SlugLink.tsx"
+import { ReactDataTableComponentProps } from '@vendure/admin-ui/react';
+import React from 'react';
+
+export function SlugLink({ rowItem }: ReactDataTableComponentProps) {
+    const slug: string = rowItem.slug;
+    return (
+        <a href={`https://example.com/category/${slug}`} target="_blank">
+            {slug}
+        </a>
+    );
+}
+```
+
+</TabItem>
+</Tabs>
+
+### 2. Register the component
+
+Next we need to register the component in out `providers.ts` file. We need to pass both a `tableId` and a `columnId` to identify the table and column to which the component should be added. The values for these IDs can be found by pressing the `ctrl + u` shortcut when the Admin UI is in dev mode, and then clicking the extension point icon at the top of the column in question:
+
+![Extension locations](./custom-data-table-location.webp)
+
+In this case we want to target the `product-list` table and the `slug` column.
+
+<Tabs groupId="framework">
+<TabItem value="angular" label="Angular">
+
+```ts title="src/plugins/slug-link/ui/providers.ts"
+import { registerDataTableComponent } from '@vendure/admin-ui/core';
+import { SlugLinkComponent } from './components/slug-link/slug-link.component';
+
+export default [
+    registerDataTableComponent({
+        component: SlugWithLinkComponent,
+        tableId: 'product-list',
+        columnId: 'slug',
+    }),
+];
+```
+
+</TabItem>
+<TabItem value="react" label="React">
+
+```ts title="src/plugins/slug-link/ui/providers.ts"
+import { registerReactDataTableComponent } from '@vendure/admin-ui/react';
+import { SlugLink } from './components/SlugLink';
+
+export default [
+    registerReactDataTableComponent({
+        component: SlugWithLink,
+        tableId: 'collection-list',
+        columnId: 'slug',
+        props: {
+            // Additional props may be passed to the component
+            foo: 'bar',
+        },
+    }),
+];
+```
+
+</TabItem>
+</Tabs>
+
+When running the Admin UI, the product list slug should now be rendered as a link:
+
+![Product list with slug link](./custom-data-table.webp)

BIN
docs/docs/guides/extending-the-admin-ui/custom-detail-components/detail-component.webp


+ 46 - 21
docs/docs/guides/extending-the-admin-ui/custom-detail-components/index.md

@@ -1,13 +1,10 @@
 ---
 title: 'Custom Detail Components'
-weight: 6
 ---
 
 import Tabs from '@theme/Tabs';
 import TabItem from '@theme/TabItem';
 
-# Custom Detail Components
-
 Detail views can be extended with custom Angular or React components using the [`registerCustomDetailComponent`](/reference/admin-ui-api/custom-detail-components/register-custom-detail-component/) and [`registerReactCustomDetailComponent`](/reference/admin-ui-api/react-extensions/register-react-custom-detail-component) functions.
 
 Any components registered in this way will appear below the main detail form.
@@ -18,7 +15,11 @@ The valid locations for embedding custom detail components can be found in the [
 
 Let's imagine that your project has an external content management system (CMS) which is used to store additional details about products. You might want to display some of this information in the product detail page. We will demonstrate the same component in both Angular and React.
 
-## Angular
+### 1. Create a component
+
+
+<Tabs groupId="framework">
+<TabItem value="Angular" label="Angular" default>
 
 ```ts title="src/plugins/cms/ui/components/product-info/product-info.component.ts"
 import { Component, OnInit } from '@angular/core';
@@ -56,21 +57,10 @@ export class ProductInfoComponent implements CustomDetailComponent, OnInit {
 }
 ```
 
-```ts title="src/plugins/cms/ui/providers.ts"
-import { registerCustomDetailComponent } from '@vendure/admin-ui/core';
-import { ProductInfoComponent } from './components/product-info/product-info.component';
-
-export default [
-    registerCustomDetailComponent({
-        locationId: 'product-detail',
-        component: ProductInfoComponent,
-    }),
-];
-```
-
-## React
+</TabItem>
+<TabItem value="React" label="React">
 
-When using React, we can use the [`useDetailComponentData` hook](/reference/admin-ui-api/react-hooks/use-detail-component-data) to access the same data (the entity and the form) as the Angular example above.
+When using React, we can use the [`useDetailComponentData` hook](/reference/admin-ui-api/react-hooks/use-detail-component-data) to access the entity and form data.
 
 ```tsx title="src/plugins/cms/ui/components/ProductInfo.tsx"
 import React, { useEffect, useState } from 'react';
@@ -85,11 +75,14 @@ export function ProductInfo() {
     const [extraInfo, setExtraInfo] = useState<any>();
     
     useEffect(() => {
-        const subscription = cmsDataService.getDataFor(entity.id).subscribe(data => {
+        if (!entity?.id) {
+            return;
+        }
+        const subscription = cmsDataService.getDataFor(entity?.id).subscribe(data => {
             setExtraInfo(data);
         });
         return () => subscription.unsubscribe();
-    }, [entity.id]);
+    }, [entity?.id]);
     
     return (
         <Card title="CMS Info">
@@ -99,6 +92,31 @@ export function ProductInfo() {
 }
 ```
 
+</TabItem>
+</Tabs>
+
+### 2. Register the component
+
+We can then register the component in our `providers.ts` file:
+
+<Tabs groupId="framework">
+<TabItem value="Angular" label="Angular" default>
+
+```ts title="src/plugins/cms/ui/providers.ts"
+import { registerCustomDetailComponent } from '@vendure/admin-ui/core';
+import { ProductInfoComponent } from './components/product-info/product-info.component';
+
+export default [
+    registerCustomDetailComponent({
+        locationId: 'product-detail',
+        component: ProductInfoComponent,
+    }),
+];
+```
+
+</TabItem>
+<TabItem value="React" label="React">
+
 ```ts title="src/plugins/cms/ui/providers.ts"
 import { registerReactCustomDetailComponent } from '@vendure/admin-ui/react';
 import { ProductInfo } from './components/ProductInfo';
@@ -111,11 +129,18 @@ export default [
 ];
 ```
 
+</TabItem>
+</Tabs>
+
+When running the Admin UI, the component should now appear in the product detail page:
+
+![Product detail with custom component](./detail-component.webp)
+
 ## Manipulating the detail form
 
 The `detailForm` property is an instance of the Angular [FormGroup](https://angular.io/api/forms/FormGroup) which can be used to manipulate the form fields, set the validity of the form, mark the form as dirty etc. For example, we could add a button which updates the `description` field of the product:
 
-<Tabs>
+<Tabs groupId="framework">
 <TabItem value="Angular" label="Angular" default>
 
 ```ts title="src/plugins/cms/ui/components/product-info/product-info.component.ts"

+ 2 - 2
docs/docs/guides/extending-the-admin-ui/custom-form-inputs/index.md

@@ -34,7 +34,7 @@ But let's say we want to display a **range slider** instead.
 
 First we need to define a new Angular or React component to render the slider:
 
-<Tabs>
+<Tabs groupId="framework">
 <TabItem value="Angular" label="Angular" default>
 
 Angular components will have the `readonly`, `config` and `formControl` properties populated automatically.
@@ -102,7 +102,7 @@ export function SliderFormInput({ readonly, config }: ReactFormInputOptions) {
 
 Next we will register this component in our `providers.ts` file and give it a unique ID, `'slider-form-input'`:
 
-<Tabs>
+<Tabs groupId="framework">
 <TabItem value="Angular" label="Angular" default>
 
 ```ts title="src/plugins/common/ui/providers.ts"

+ 1 - 1
docs/docs/guides/extending-the-admin-ui/getting-started/index.md

@@ -172,7 +172,7 @@ export default [
 
 Your `routes.ts` file exports an array of objects which define new routes in the Admin UI. For example, imagine you have created a plugin which implements a simple content management system. You can define a route for the list of articles, and another for the detail view of an article.
 
-<Tabs>
+<Tabs groupId="framework">
 <TabItem value="Angular" label="Angular" default>
 
 Using [`registerRouteComponent`](/reference/admin-ui-api/routes/register-route-component) you can define a new route based on an Angular component. Here's a simple example:

+ 1 - 0
docs/sidebars.js

@@ -133,6 +133,7 @@ const sidebars = {
                 'guides/extending-the-admin-ui/add-actions-to-pages/index',
                 'guides/extending-the-admin-ui/page-tabs/index',
                 'guides/extending-the-admin-ui/custom-form-inputs/index',
+                'guides/extending-the-admin-ui/custom-data-table-components/index',
                 'guides/extending-the-admin-ui/custom-detail-components/index',
                 'guides/extending-the-admin-ui/bulk-actions/index',
                 'guides/extending-the-admin-ui/dashboard-widgets/index',

+ 2 - 2
packages/admin-ui/src/lib/react/src/react-hooks/use-detail-component-data.ts

@@ -31,7 +31,7 @@ import { HostedReactComponentContext } from '../types';
  *
  * @docsCategory react-hooks
  */
-export function useDetailComponentData() {
+export function useDetailComponentData<T = any>() {
     const context = useContext(
         HostedComponentContext,
     ) as HostedReactComponentContext<ReactCustomDetailComponentContext>;
@@ -40,7 +40,7 @@ export function useDetailComponentData() {
         throw new Error(`The useDetailComponentData hook can only be used within a CustomDetailComponent`);
     }
 
-    const [entity, setEntity] = useState(null);
+    const [entity, setEntity] = useState<T | null>(null);
 
     useEffect(() => {
         const subscription = context.entity$.subscribe(value => {