Reusing Umbraco Properties in Umbraco v14
Tags: umbraco, javascript
Published: 2024-04-23 08:00:00

When building user interfaces in Umbraco v14, occasionally there comes a time when you need to build a form based on some dynamically defined properties. One such example in Umbraco Commerce is how Payment Providers expose the settings they need for configuration.

![Payment Provider Settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yfy45klx5d5jz2yilger.png)

When it comes to capturing this configuration, we ideally don't want to have to define these forms manually. Instead, it would be much better if we could use the exposed setting definitions as a pseudo doctype and dynamically render the form reusing the built in property editors in Umbraco to allow capturing different value types.

## Data

I won't in this post go into the server side configuration for the payment provider settings, but needless to say, we have a REST endpoint that returns a JSON configuration for our settings similar to the following:

```json
[
    {
        "alias": "continueUrl",
        "label": "Continue Url",
        "description": "The URL to continue to after the provide has done processing",
        "editorUiAlias": "Umb.PropertyEditorUi.TextBox"
    },
    ...
    {
        "alias": "textMode",
        "label": "Test Mode",
        "description": "Whether to run in test mode or production mode",
        "editorUiAlias": "Umb.PropertyEditorUi.Toggle"
    }
]
```

In addition, our Payment Method entities which hold the configuration values have a properties collection on it similar to the following:

```json
{
    "id": "e06f29df-74fa-4ec8-8240-acccda44e702",
    "alias" : "invoicing",
    "name": "Invoicing",
    "properties": {
        "continueUrl": "/checkout/continue",
        "cancelUrl": "/checkout/cancel",
        "maxRetries": 12,
        "testMode": true
    }
}
```

## Component

Lets start with a basic component that will be responsible for rendering out form.

```javascript
@customElement("my-component")
export class MyComponentElement extends UmbElementMixin(LitElement) {
    render() {
        return html`TODO`
    }
}

export default MyComponentElement;

declare global {
    interface HTMLElementTagNameMap {
        "my-component": MyComponentElement;
    }
}
```

Lets now setup a couple of stateful variables to hold our data structures in and define a constructor that is responsible for populating those properties.

```javascript
@customElement("my-component")
export class MyComponentElement extends UmbElementMixin(LitElement) {

    @state() 
    _settings: Array<MySettingType> = [];

    @state() 
    _model?: MyEntityType;

    constructor(host: UmbControllerHost) {
        super(host);
        // Some code to fetch and populate the _settings + _model
    }

    render() {
        return html`TODO`
    }
}
...
```

I'll leave the fetching of the data up to you as it's not the important part, but we can move on with the knowledge we should now have some data in variables in the structures outlines above.

## Rendering

The two components that are key to reusing umbraco properties are the `umb-property-dataset` and the `umb-property`.

Lets initially setup our rendering function as follows:

```javascript
render() {
    return html`<umb-property-dataset>
        ${repeat(
            this._settings,
            (itm) => itm.key,
            (itm) => html`<umb-property
                alias=${itm.alias}
                label=${itm.label}
                description=${itm.description}
                property-editor-ui-alias=${itm.editorUiAlias}>
            </umb-property>`)}
    </umb-property-dataset>`
}
```

I'll get to why we need a `umb-property-dataset` in a second, but for now we'll just need to know that we must have one wrapped around our rendered properties.

Inside the `umb-property-dataset` tag, we'll then loop through our setting definitions and use the `umb-property` component, passing through the `alias`, `label`, `description` and `editorUiAlias` from our settings.

If we were to now look on our front end you should now see your properties rendering, complete with labels, descriptions and the reusing of umbraco property editors.

Pretty neat for relatively little code 😎

## Binding

Right now however, there won't be any values in our properties, and we aren't capturing any value changes either.

This is where the `umb-property-dataset` comes into play. Rather than needing to set values and handle input events for all properties, we instead populate a single values collection, and listen for a single event from the `umb-property-dataset` instead.

The first thing we need to do is massage our property data into the expected format. The `umb-property-dataset` takes in a value of type `Array<UmbPropertyValueData>` where `UmbPropertyValueData` is defined as:

```javascript
export type UmbPropertyValueData<ValueType = unknown> = {
    alias: string;
    value?: ValueType;
};
```
In our component, lets introduce another stateful variable and populate it after we populated our other settings.

```javascript
@customElement("my-component")
export class MyComponentElement extends UmbElementMixin(LitElement) {

    ...

    @state() 
    _values: Array<UmbPropertyValueData> = [];

    constructor(host: UmbControllerHost) {
        super(host);
        // Some code to fetch and populate the _settings + _model
        this._values = this._settings.map(setting => ({
           alias: setting.alias,
           value: this._model?.properties[setting.alias]
        }));
    }

    ...
}
...
```

Here we loop through all our setting definitions, creating a new `UmbPropertyValueData` entry populating it's alias with the setting alias and then it's value with the value of that setting found in our entity model.

With our property values now in the format we need, we can update our render function as follows:

```javascript
render() {
    return html`<umb-property-dataset
        .value=${this._values}>
        ${repeat(
            this._settings,
            (itm) => itm.key,
            (itm) => html`<umb-property
                alias=${itm.alias}
                label=${itm.label}
                description=${itm.description}
                property-editor-ui-alias=${itm.editorUiAlias}>
            </umb-property>`)}
    </umb-property-dataset>`
}
```
Here we have bound our `_values` variable to the `value` attribute of the `umb-property-dataset` component and that is all we need to do to get our store values to display.

## Change Handling

The last part of the puzzle is reacting to change and persisting the updated values back.

For this we'll create a single event handler to capture all changes like so.

```javascript
@customElement("my-component")
export class MyComponentElement extends UmbElementMixin(LitElement) {

    ...

    #onPropertyDataChange(e: Event) {
        // Grab the value
        const value = (e.target as UmbPropertyDatasetElement).value;
        // Convert the value back into an object
        var data = value.reduce((acc, curr)=>({...acc, [curr.alias]: curr.value}), {});
        // Update our model
        this._model.properties = data;
    }

    ...
}
...
```

Here we get the data set value (which contains values for all our settings) and then we convert it into the same structure of our models property collection, and then reset the value back on the model.

Again, I'll leave the persistence side of things to you here, but for this example I'm just writing back to the main entity model..

Finally, we'll update our render function again to attach our event handler.

```javascript
render() {
    return html`<umb-property-dataset
        .value=${this._values}
        @change=${this.#onPropertyDataChange}>
        ${repeat(
            this._settings,
            (itm) => itm.key,
            (itm) => html`<umb-property
                alias=${itm.alias}
                label=${itm.label}
                description=${itm.description}
                property-editor-ui-alias=${itm.editorUiAlias}>
            </umb-property>`)}
    </umb-property-dataset>`
}
```

And that's it. We now have a dynamically generated form that reuses umbraco property editors for input collection and where we can capture and store the user input.

I hope you found this post helpful.

If you'd like to see an example implementation of using the `umb-property-dataset` and `umb-property` elements there is an example in the Umbraco BackOffice code base [here](https://github.com/umbraco/Umbraco.CMS.Backoffice/blob/main/examples/dashboard-with-property-dataset/dataset-dashboard.ts).

Until next time 👋