|
|
@@ -1,11 +1,13 @@
|
|
|
---
|
|
|
-title: 'CustomField Controls'
|
|
|
+title: 'Custom Form Inputs'
|
|
|
weight: 5
|
|
|
---
|
|
|
|
|
|
-# CustomField Controls
|
|
|
+# Custom Form Inputs
|
|
|
|
|
|
-Another way to extend the Admin UI app is to define custom form control components for manipulating any [Custom Fields]({{< ref "/docs/typescript-api/custom-fields" >}}) you have defined on your entities.
|
|
|
+Another way to extend the Admin UI app is to define custom form input components for manipulating any [Custom Fields]({{< ref "/docs/typescript-api/custom-fields" >}}) you have defined on your entities as well as [configurable args]({{< relref "config-args" >}}) used by custom [ConfigurableOperationDefs]({{< relref "configurable-operation-def" >}}).
|
|
|
+
|
|
|
+## For Custom Fields
|
|
|
|
|
|
Let's say you define a custom "intensity" field on the Product entity:
|
|
|
|
|
|
@@ -23,27 +25,27 @@ By default, the "intensity" field will be displayed as a number input:
|
|
|
|
|
|
{{< figure src="./ui-extensions-custom-field-default.jpg" >}}
|
|
|
|
|
|
-But let's say we want to display a range slider instead. Here's how we can do this using our shared extension module combined with the `registerCustomFieldComponent()` function:
|
|
|
+But let's say we want to display a range slider instead. Here's how we can do this using our shared extension module combined with the `registerFormInputComponent()` function:
|
|
|
|
|
|
```TypeScript
|
|
|
import { NgModule, Component } from '@angular/core';
|
|
|
import { FormControl } from '@angular/forms';
|
|
|
-import { SharedModule, CustomFieldControl,
|
|
|
- CustomFieldConfigType, registerCustomFieldComponent } from '@vendure/admin-ui/core';
|
|
|
+import { CustomFieldConfig } from '@vendure/common/lib/generated-types';
|
|
|
+import { SharedModule, FormInputComponent, registerFormInputComponent } from '@vendure/admin-ui/core';
|
|
|
|
|
|
@Component({
|
|
|
template: `
|
|
|
<input
|
|
|
type="range"
|
|
|
- [min]="config.intMin"
|
|
|
- [max]="config.intMax"
|
|
|
+ [min]="config.min || 0"
|
|
|
+ [max]="config.max || 100"
|
|
|
[formControl]="formControl" />
|
|
|
{{ formControl.value }}
|
|
|
`,
|
|
|
})
|
|
|
-export class SliderControl implements CustomFieldControl {
|
|
|
+export class SliderControl implements FormInputComponent<CustomFieldConfig> {
|
|
|
readonly: boolean;
|
|
|
- config: CustomFieldConfigType;
|
|
|
+ config: CustomFieldConfig;
|
|
|
formControl: FormControl;
|
|
|
}
|
|
|
|
|
|
@@ -51,23 +53,46 @@ export class SliderControl implements CustomFieldControl {
|
|
|
imports: [SharedModule],
|
|
|
declarations: [SliderControl],
|
|
|
providers: [
|
|
|
- registerCustomFieldComponent('Product', 'intensity', SliderControl),
|
|
|
+ registerFormInputComponent('slider-form-input', SliderControl),
|
|
|
]
|
|
|
})
|
|
|
-export class SharedExtensionModule { }
|
|
|
+export class SharedExtensionModule {}
|
|
|
```
|
|
|
|
|
|
+Once registered, this new slider input can be used in our custom field config:
|
|
|
+
|
|
|
+```TypeScript {hl_lines=[7]}
|
|
|
+// project/vendure-config.ts
|
|
|
+
|
|
|
+customFields: {
|
|
|
+ Product: [
|
|
|
+ {
|
|
|
+ name: 'intensity', type: 'int', min: 0, max: 100, defaultValue: 0,
|
|
|
+ ui: { component: 'slider-form-input' }
|
|
|
+ },
|
|
|
+ ],
|
|
|
+}
|
|
|
+```
|
|
|
+As we can see, adding the `ui` property to the custom field config allows us to specify our custom slider component.
|
|
|
+The component id _'slider-form-input'_ **must match** the string passed as the first argument to `registerFormInputComponent()`.
|
|
|
+
|
|
|
+{{% alert %}}
|
|
|
+If we want, we can also pass any other arbitrary data in the `ui` object, which will then be available in our component as `this.config.ui.myField`. Note that only JSON-compatible data types are permitted, so no functions or class instances.
|
|
|
+{{< /alert >}}
|
|
|
+
|
|
|
+
|
|
|
Re-compiling the Admin UI will result in our SliderControl now being used for the "intensity" custom field:
|
|
|
|
|
|
{{< figure src="./ui-extensions-custom-field-slider.jpg" >}}
|
|
|
|
|
|
To recap the steps involved:
|
|
|
|
|
|
-1. Create an Angular Component which implements the `CustomFieldControl` interface.
|
|
|
+1. Create an Angular Component which implements the `FormInputComponent` interface.
|
|
|
2. Add this component to your shared extension module's `declarations` array.
|
|
|
-3. Use `registerCustomFieldComponent()` to register your component for the given entity & custom field name.
|
|
|
+3. Use `registerFormInputComponent()` to register your component for the given entity & custom field name.
|
|
|
+4. Specify this component's ID in your custom field config.
|
|
|
|
|
|
-## Custom Field Controls for Relations
|
|
|
+### Custom Field Controls for Relations
|
|
|
|
|
|
If you have a custom field of the `relation` type (which allows you to relate entities with one another), you can also define custom field controls for them. The basic mechanism is exactly the same as with primitive custom field types (i.e. `string`, `int` etc.), but there are a couple of important points to know:
|
|
|
|
|
|
@@ -77,9 +102,10 @@ If you have a custom field of the `relation` type (which allows you to relate en
|
|
|
Here is a simple example taken from the [real-world-vendure](https://github.com/vendure-ecommerce/real-world-vendure/blob/master/src/plugins/reviews/ui/components/featured-review-selector/featured-review-selector.component.ts) repo:
|
|
|
|
|
|
```TypeScript
|
|
|
-import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
|
|
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
|
|
import { FormControl } from '@angular/forms';
|
|
|
import { ActivatedRoute } from '@angular/router';
|
|
|
+import { RelationCustomFieldConfig } from '@vendure/common/lib/generated-types';
|
|
|
import { CustomFieldControl, DataService } from '@vendure/admin-ui/core';
|
|
|
import { Observable } from 'rxjs';
|
|
|
import { switchMap } from 'rxjs/operators';
|
|
|
@@ -107,10 +133,10 @@ import { GetReviewForProduct, ProductReviewFragment } from '../../generated-type
|
|
|
`,
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
|
})
|
|
|
-export class RelationReviewInputComponent implements OnInit, CustomFieldControl {
|
|
|
- @Input() readonly: boolean;
|
|
|
- @Input() formControl: FormControl;
|
|
|
- @Input() config: any;
|
|
|
+export class RelationReviewInputComponent implements OnInit, FormInputComponent<RelationCustomFieldConfig> {
|
|
|
+ readonly: boolean;
|
|
|
+ formControl: FormControl;
|
|
|
+ config: RelationCustomFieldConfig;
|
|
|
|
|
|
reviews$: Observable<ProductReviewFragment[]>;
|
|
|
|
|
|
@@ -133,3 +159,53 @@ export class RelationReviewInputComponent implements OnInit, CustomFieldControl
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
+
|
|
|
+### Legacy `registerCustomFieldComponent`
|
|
|
+
|
|
|
+Prior to v1.4, the function `registerCustomFieldComponent()` was used to register a form control for a custom field. This function has now been deprecated in favour of `registerFormInputComponent()`, but is kept for backward-compatibility and will be removed in v2.0.
|
|
|
+
|
|
|
+`registerCustomFieldComponent` is used like this:
|
|
|
+
|
|
|
+```TypeScript
|
|
|
+import { NgModule, Component } from '@angular/core';
|
|
|
+import { SharedModule, registerCustomFieldComponent } from '@vendure/admin-ui/core';
|
|
|
+
|
|
|
+// SliderControl component definition as above
|
|
|
+
|
|
|
+@NgModule({
|
|
|
+ imports: [SharedModule],
|
|
|
+ declarations: [SliderControl],
|
|
|
+ providers: [
|
|
|
+ registerCustomFieldComponent('Product', 'intensity', SliderControl),
|
|
|
+ ]
|
|
|
+})
|
|
|
+export class SharedExtensionModule { }
|
|
|
+```
|
|
|
+
|
|
|
+## For ConfigArgs
|
|
|
+
|
|
|
+[ConfigArgs]({{< relref "config-args" >}}) are used by classes which extend [ConfigurableOperationDef]({{< relref "configurable-operation-def" >}}) (such as ShippingCalculator or PaymentMethodHandler). These ConfigArgs allow user-input values to be passed to the operation's business logic.
|
|
|
+
|
|
|
+They are configured in a very similar way to custom fields, and likewise can use custom form inputs by specifying the `ui` property.
|
|
|
+
|
|
|
+Here's an example:
|
|
|
+
|
|
|
+```TypeScript {hl_lines=[6,7,8]}
|
|
|
+export const orderFixedDiscount = new PromotionOrderAction({
|
|
|
+ code: 'order_fixed_discount',
|
|
|
+ args: {
|
|
|
+ discount: {
|
|
|
+ type: 'int',
|
|
|
+ ui: {
|
|
|
+ component: 'currency-form-input',
|
|
|
+ },
|
|
|
+ },
|
|
|
+ },
|
|
|
+ execute(ctx, order, args) {
|
|
|
+ return -args.discount;
|
|
|
+ },
|
|
|
+ description: [{ languageCode: LanguageCode.en, value: 'Discount order by fixed amount' }],
|
|
|
+});
|
|
|
+```
|
|
|
+
|
|
|
+
|