Skip to content

Commit

Permalink
feat(core): Implement support for struct custom field type (#3178)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbromley authored Nov 15, 2024
1 parent 35892a5 commit dffd123
Show file tree
Hide file tree
Showing 63 changed files with 3,308 additions and 504 deletions.
3 changes: 0 additions & 3 deletions .graphqlconfig

This file was deleted.

106 changes: 106 additions & 0 deletions docs/docs/guides/developer-guide/custom-fields/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ The following types are available for custom fields:
| `float` | Floating point number | product review rating |
| `boolean` | Boolean | isDownloadable flag on product |
| `datetime` | A datetime | date that variant is back in stock |
| `struct` | Structured json-like data | Key-value attributes with additional data for products |
| `relation` | A relation to another entity | Asset used as a customer avatar, related Products |

To see the underlying DB data type and GraphQL type used for each, see the [CustomFieldType doc](/reference/typescript-api/custom-fields/custom-field-type).
Expand Down Expand Up @@ -882,6 +883,111 @@ const config = {

The step value. See [the MDN datetime-local docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local#step) to understand how this is used.

### Properties for `struct` fields

:::info
The `struct` custom field type is available from Vendure v3.1.0.
:::

In addition to the common properties, the `struct` custom fields have some type-specific properties:

- [`fields`](#fields)

#### fields

<CustomFieldProperty required={true} type="StructFieldConfig[]" typeLink="/reference/typescript-api/custom-fields/struct-field-config" />

A `struct` is a data structure comprising a set of named fields, each with its own type. The `fields` property is an array of `StructFieldConfig` objects, each of which defines a field within the struct.

```ts title="src/vendure-config.ts"
const config = {
// ...
customFields: {
Product: [
{
name: 'dimensions',
type: 'struct',
// highlight-start
fields: [
{ name: 'length', type: 'int' },
{ name: 'width', type: 'int' },
{ name: 'height', type: 'int' },
],
// highlight-end
},
]
}
};
```

When querying the `Product` entity, the `dimensions` field will be an object with the fields `length`, `width` and `height`:

```graphql
query {
product(id: 1) {
customFields {
dimensions {
length
width
height
}
}
}
}
```

Struct fields support many of the same properties as other custom fields, such as `list`, `label`, `description`, `validate`, `ui` and
type-specific properties such as `options` and `pattern` for string types.

:::note
The following properties are **not** supported for `struct` fields: `public`, `readonly`, `internal`, `defaultValue`, `nullable`, `unique`, `requiresPermission`.
:::

```ts title="src/vendure-config.ts"
import { LanguageCode } from '@vendure/core';

const config = {
// ...
customFields: {
OrderLine: [
{
name: 'customizationOptions',
type: 'struct',
fields: [
{
name: 'color',
type: 'string',
// highlight-start
options: [
{value: 'red', label: [{languageCode: LanguageCode.en, value: 'Red'}]},
{value: 'blue', label: [{languageCode: LanguageCode.en, value: 'Blue'}]},
],
// highlight-end
},
{
name: 'engraving',
type: 'string',
// highlight-start
validate: (value: any) => {
if (value.length > 20) {
return 'Engraving text must be 20 characters or fewer';
}
},
},
{
name: 'notifyEmailAddresses',
type: 'string',
// highlight-start
list: true,
// highlight-end
}
],
},
]
}
};
```

### Properties for `relation` fields

In addition to the common properties, the `relation` custom fields have some type-specific properties:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class BooleanFormInputComponent implements FormInputComponent {

<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/html-editor-form-input.component.ts" sourceLine="23" packageName="@vendure/admin-ui" />

A JSON editor input with syntax highlighting and error detection. Works well
A JSON editor input with syntax highlighting and error detection. Works well
with `text` type fields.

```ts title="Signature"
Expand Down Expand Up @@ -101,7 +101,7 @@ class HtmlEditorFormInputComponent extends BaseCodeEditorFormInputComponent impl

<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.ts" sourceLine="33" packageName="@vendure/admin-ui" />

A JSON editor input with syntax highlighting and error detection. Works well
A JSON editor input with syntax highlighting and error detection. Works well
with `text` type fields.

```ts title="Signature"
Expand Down Expand Up @@ -276,7 +276,7 @@ class CurrencyFormInputComponent implements FormInputComponent {

<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/customer-group-form-input/customer-group-form-input.component.ts" sourceLine="20" packageName="@vendure/admin-ui" />

Allows the selection of a Customer via an autocomplete select input.
Allows the selection of a Customer via an autocomplete select input.
Should be used with `ID` type fields which represent Customer IDs.

```ts title="Signature"
Expand Down Expand Up @@ -415,7 +415,7 @@ class DateFormInputComponent implements FormInputComponent {

<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/facet-value-form-input/facet-value-form-input.component.ts" sourceLine="16" packageName="@vendure/admin-ui" />

Allows the selection of multiple FacetValues via an autocomplete select input.
Allows the selection of multiple FacetValues via an autocomplete select input.
Should be used with `ID` type **list** fields which represent FacetValue IDs.

```ts title="Signature"
Expand All @@ -425,13 +425,13 @@ class FacetValueFormInputComponent implements FormInputComponent {
readonly: boolean;
formControl: UntypedFormControl;
config: InputComponentConfig;
valueTransformFn = (values: FacetValueFragment[]) => {
const isUsedInConfigArg = this.config.__typename === 'ConfigArgDefinition';
if (isUsedInConfigArg) {
return JSON.stringify(values.map(s => s.id));
} else {
return values;
}
valueTransformFn = (values: FacetValueFragment[]) => {
const isUsedInConfigArg = this.config.__typename === 'ConfigArgDefinition';
if (isUsedInConfigArg) {
return JSON.stringify(values.map(s => s.id));
} else {
return values;
}
};
}
```
Expand Down Expand Up @@ -600,7 +600,7 @@ class PasswordFormInputComponent implements FormInputComponent {

<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/product-selector-form-input/product-selector-form-input.component.ts" sourceLine="20" packageName="@vendure/admin-ui" />

Allows the selection of multiple ProductVariants via an autocomplete select input.
Allows the selection of multiple ProductVariants via an autocomplete select input.
Should be used with `ID` type **list** fields which represent ProductVariant IDs.

```ts title="Signature"
Expand Down Expand Up @@ -682,8 +682,8 @@ class ProductSelectorFormInputComponent implements FormInputComponent, OnInit {

<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/relation-form-input/relation-form-input.component.ts" sourceLine="17" packageName="@vendure/admin-ui" />

The default input component for `relation` type custom fields. Allows the selection
of a ProductVariant, Product, Customer or Asset. For other entity types, a custom
The default input component for `relation` type custom fields. Allows the selection
of a ProductVariant, Product, Customer or Asset. For other entity types, a custom
implementation will need to be defined. See <a href='/reference/admin-ui-api/custom-input-components/register-form-input-component#registerforminputcomponent'>registerFormInputComponent</a>.

```ts title="Signature"
Expand Down Expand Up @@ -774,7 +774,7 @@ class RichTextFormInputComponent implements FormInputComponent {

<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/select-form-input/select-form-input.component.ts" sourceLine="18" packageName="@vendure/admin-ui" />

Uses a select input to allow the selection of a string value. Should be used with
Uses a select input to allow the selection of a string value. Should be used with
`string` type fields with options.

```ts title="Signature"
Expand Down Expand Up @@ -843,6 +843,90 @@ class SelectFormInputComponent implements FormInputComponent, OnInit {



</div>


## StructFormInputComponent

<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/struct-form-input/struct-form-input.component.ts" sourceLine="18" packageName="@vendure/admin-ui" />

A checkbox input. The default input component for `boolean` fields.

```ts title="Signature"
class StructFormInputComponent implements FormInputComponent, OnInit, OnDestroy {
static readonly id: DefaultFormComponentId = 'struct-form-input';
readonly: boolean;
formControl: UntypedFormControl;
config: DefaultFormComponentConfig<'struct-form-input'>;
uiLanguage$: Observable<LanguageCode>;
protected structFormGroup = new FormGroup({});
protected fields: Array<{
def: StructCustomFieldFragment['fields'][number];
formControl: FormControl;
}>;
constructor(dataService: DataService)
ngOnInit() => ;
ngOnDestroy() => ;
}
```
* Implements: <code><a href='/reference/admin-ui-api/custom-input-components/form-input-component#forminputcomponent'>FormInputComponent</a></code>, <code>OnInit</code>, <code>OnDestroy</code>



<div className="members-wrapper">

### id

<MemberInfo kind="property" type={`<a href='/reference/typescript-api/configurable-operation-def/default-form-component-id#defaultformcomponentid'>DefaultFormComponentId</a>`} />


### readonly

<MemberInfo kind="property" type={`boolean`} />


### formControl

<MemberInfo kind="property" type={`UntypedFormControl`} />


### config

<MemberInfo kind="property" type={`DefaultFormComponentConfig&#60;'struct-form-input'&#62;`} />


### uiLanguage$

<MemberInfo kind="property" type={`Observable&#60;<a href='/reference/typescript-api/common/language-code#languagecode'>LanguageCode</a>&#62;`} />


### structFormGroup

<MemberInfo kind="property" type={``} />


### fields

<MemberInfo kind="property" type={`Array&#60;{ def: StructCustomFieldFragment['fields'][number]; formControl: FormControl; }&#62;`} />


### constructor

<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>) => StructFormInputComponent`} />


### ngOnInit

<MemberInfo kind="method" type={`() => `} />


### ngOnDestroy

<MemberInfo kind="method" type={`() => `} />




</div>


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ Configuration for the EmailPlugin.
interface EmailPluginOptions {
templatePath?: string;
templateLoader?: TemplateLoader;
transport:
| EmailTransportOptions
| ((
injector?: Injector,
ctx?: RequestContext,
transport:
| EmailTransportOptions
| ((
injector?: Injector,
ctx?: RequestContext,
) => EmailTransportOptions | Promise<EmailTransportOptions>);
handlers: Array<EmailEventHandler<string, any>>;
globalTemplateVars?: { [key: string]: any } | GlobalTemplateVarsFn;
Expand All @@ -38,44 +38,44 @@ interface EmailPluginOptions {

<MemberInfo kind="property" type={`string`} />

The path to the location of the email templates. In a default Vendure installation,
The path to the location of the email templates. In a default Vendure installation,
the templates are installed to `<project root>/vendure/email/templates`.
### templateLoader

<MemberInfo kind="property" type={`<a href='/reference/core-plugins/email-plugin/template-loader#templateloader'>TemplateLoader</a>`} since="2.0.0" />

An optional TemplateLoader which can be used to load templates from a custom location or async service.
An optional TemplateLoader which can be used to load templates from a custom location or async service.
The default uses the FileBasedTemplateLoader which loads templates from `<project root>/vendure/email/templates`
### transport

<MemberInfo kind="property" type={`| <a href='/reference/core-plugins/email-plugin/transport-options#emailtransportoptions'>EmailTransportOptions</a> | (( injector?: <a href='/reference/typescript-api/common/injector#injector'>Injector</a>, ctx?: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, ) =&#62; <a href='/reference/core-plugins/email-plugin/transport-options#emailtransportoptions'>EmailTransportOptions</a> | Promise&#60;<a href='/reference/core-plugins/email-plugin/transport-options#emailtransportoptions'>EmailTransportOptions</a>&#62;)`} />
<MemberInfo kind="property" type={`| <a href='/reference/core-plugins/email-plugin/transport-options#emailtransportoptions'>EmailTransportOptions</a> | (( injector?: <a href='/reference/typescript-api/common/injector#injector'>Injector</a>, ctx?: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, ) =&#62; <a href='/reference/core-plugins/email-plugin/transport-options#emailtransportoptions'>EmailTransportOptions</a> | Promise&#60;<a href='/reference/core-plugins/email-plugin/transport-options#emailtransportoptions'>EmailTransportOptions</a>&#62;)`} />

Configures how the emails are sent.
### handlers

<MemberInfo kind="property" type={`Array&#60;<a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a>&#60;string, any&#62;&#62;`} />

An array of <a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a>s which define which Vendure events will trigger
An array of <a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a>s which define which Vendure events will trigger
emails, and how those emails are generated.
### globalTemplateVars

<MemberInfo kind="property" type={`{ [key: string]: any } | <a href='/reference/core-plugins/email-plugin/email-plugin-options#globaltemplatevarsfn'>GlobalTemplateVarsFn</a>`} />

An object containing variables which are made available to all templates. For example,
the storefront URL could be defined here and then used in the "email address verification"
email. Use the GlobalTemplateVarsFn if you need to retrieve variables from Vendure or
An object containing variables which are made available to all templates. For example,
the storefront URL could be defined here and then used in the "email address verification"
email. Use the GlobalTemplateVarsFn if you need to retrieve variables from Vendure or
plugin services.
### emailSender

<MemberInfo kind="property" type={`<a href='/reference/core-plugins/email-plugin/email-sender#emailsender'>EmailSender</a>`} default={`<a href='/reference/core-plugins/email-plugin/email-sender#nodemaileremailsender'>NodemailerEmailSender</a>`} />

An optional allowed EmailSender, used to allow custom implementations of the send functionality
An optional allowed EmailSender, used to allow custom implementations of the send functionality
while still utilizing the existing emailPlugin functionality.
### emailGenerator

<MemberInfo kind="property" type={`<a href='/reference/core-plugins/email-plugin/email-generator#emailgenerator'>EmailGenerator</a>`} default={`<a href='/reference/core-plugins/email-plugin/email-generator#handlebarsmjmlgenerator'>HandlebarsMjmlGenerator</a>`} />

An optional allowed EmailGenerator, used to allow custom email generation functionality to
An optional allowed EmailGenerator, used to allow custom email generation functionality to
better match with custom email sending functionality.


Expand All @@ -86,8 +86,8 @@ better match with custom email sending functionality.

<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="64" packageName="@vendure/email-plugin" since="2.3.0" />

Allows you to dynamically load the "globalTemplateVars" key async and access Vendure services
to create the object. This is not a requirement. You can also specify a simple static object if your
Allows you to dynamically load the "globalTemplateVars" key async and access Vendure services
to create the object. This is not a requirement. You can also specify a simple static object if your
projects doesn't need to access async or dynamic values.

*Example*
Expand All @@ -112,9 +112,9 @@ EmailPlugin.init({
```

```ts title="Signature"
type GlobalTemplateVarsFn = (
ctx: RequestContext,
injector: Injector,
type GlobalTemplateVarsFn = (
ctx: RequestContext,
injector: Injector,
) => Promise<{ [key: string]: any }>
```
Expand Down
Loading

0 comments on commit dffd123

Please sign in to comment.