Posted on • Edited on • Originally published atmattbrailsford.dev
Reusing Umbraco Properties in Umbraco v14
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.
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:
[{"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:
{"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.
@customElement("my-component")exportclassMyComponentElementextendsUmbElementMixin(LitElement){render(){returnhtml`TODO`}}exportdefaultMyComponentElement;declareglobal{interfaceHTMLElementTagNameMap{"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.
@customElement("my-component")exportclassMyComponentElementextendsUmbElementMixin(LitElement){@state()_settings:Array<MySettingType>=[];@state()_model?:MyEntityType;constructor(host:UmbControllerHost){super(host);// Some code to fetch and populate the _settings + _model}render(){returnhtml`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 theumb-property-dataset
and theumb-property
.
Lets initially setup our rendering function as follows:
render(){returnhtml`<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 aumb-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 theumb-property-dataset
tag, we'll then loop through our setting definitions and use theumb-property
component, passing through thealias
,label
,description
andeditorUiAlias
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 theumb-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 theumb-property-dataset
instead.
The first thing we need to do is massage our property data into the expected format. Theumb-property-dataset
takes in a value of typeArray<UmbPropertyValueData>
whereUmbPropertyValueData
is defined as:
exporttypeUmbPropertyValueData<ValueType=unknown>={alias:string;value?:ValueType;};
In our component, lets introduce another stateful variable and populate it after we populated our other settings.
@customElement("my-component")exportclassMyComponentElementextendsUmbElementMixin(LitElement){...@state()_values:Array<UmbPropertyValueData>=[];constructor(host:UmbControllerHost){super(host);// Some code to fetch and populate the _settings + _modelthis._values=this._settings.map(setting=>({alias:setting.alias,value:this._model?.properties[setting.alias]}));}...}...
Here we loop through all our setting definitions, creating a newUmbPropertyValueData
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:
render(){returnhtml`<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 thevalue
attribute of theumb-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.
@customElement("my-component")exportclassMyComponentElementextendsUmbElementMixin(LitElement){...#onPropertyDataChange(e:Event){// Grab the valueconstvalue=(e.targetasUmbPropertyDatasetElement).value;// Convert the value back into an objectvardata=value.reduce((acc,curr)=>({...acc,[curr.alias]:curr.value}),{});// Update our modelthis._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.
render(){returnhtml`<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 theumb-property-dataset
andumb-property
elements there is an example in the Umbraco BackOffice code basehere.
Until next time 👋
Top comments(1)
For further actions, you may consider blocking this person and/orreporting abuse