Working with collection attributes when implementing Freedom UI Designer setup area
This functionality is available for Creatio 8.3.4 and later.
Depending on your business goals, some custom Freedom UI components are designed to work with lists of items rather than single values. For these components, a Freedom UI Designer setup area needs to bind a component property to a collection of view model attributes rather than a single scalar value. Previously, developers had to work around this limitation or manipulate the source code of the Freedom UI page schema directly.
Since version 8.3.4, Creatio provides a dedicated public API in the @creatio/interface-designer library that makes this possible directly from the setup area code. The API extends the capabilities available to Freedom UI Designer setup areas, providing the following:
- Configure a data source using
SchemaModelEditor. - Manage data source attributes using
EntityDataSourceEditor. - Create and manage collection attributes in the root view model using
SchemaViewModelEditor. Supports constant attributes, scalar model-binding attributes, and collection attributes. - Manage nested item attributes inside the collection using
CollectionViewModelAttributeEditor. Create collection attributes usingcreateAttribute(name, { isCollection: true, bindToModel: { dataSourceName } }). - Bind a component property to the collection attribute using
ViewNodeEditor. - Handle errors predictably using the public
SchemaValidationErrorhierarchy. Learn more: Error handling when working with collection attributes.
Example: Implement a Freedom UI Designer setup area for a collection-based custom Freedom UI component.
The following operations are available for collection attributes when implementing a custom Freedom UI Designer setup area. We recommend keeping data binding state in the collection attribute and visual configuration state in explicit component properties. This separation of concerns makes the setup area easier to maintain.
1. Read the selected component property binding
When a user reopens the setup area for a previously configured component, the setup area needs to reflect the current configuration rather than showing default values. Read the binding from the schema on initialization and restore the setup area fields accordingly using the ViewNodeEditor.getPropertyValue() method. To do this:
- Open the "some-component-setup-area.component.ts" file.
- Read the selected element property from
ViewNodeEditor. - Resolve the collection attribute from the schema view model.
- Save the file.
The following example reads a collection attribute.
/* Read the current property binding of the selected component. */
const propertyValue = await this._viewNodeEditor.getPropertyValue('items');
if (propertyValue?.type === ViewNodePropertyValueType.AttributeBinding) {
/* Resolve the collection attribute from the schema view model. */
const attributeEditor = await schemaEditor.viewModelEditor.getAttributeEditor(
propertyValue.attributePath
);
if (attributeEditor?.attributeType === ViewModelAttributeType.ModelBindingCollection) {
/* Read the data source name from the model binding. */
const modelBinding = await attributeEditor.getModelBinding();
console.log(modelBinding.dataSourceName);
}
}
2. Create or resolve the data source
Use SchemaModelEditor to create and manage entity data sources in the schema. These data sources are the foundation for collection and scalar attribute bindings.
Select the appropriate operation based on whether the data source already exists:
- If the data source does not yet exist in the Freedom UI page schema, create it.
- If the data source was already created, for example, by another Freedom UI component or a previous setup area initialization, resolve it.
Create a data source
- Open the "some-component-setup-area.component.ts" file.
- Check whether the data source already exists using the
getDataSourceEditor()method. - Call the
createDataSource()method and pass the data source name and object schema name as parameters. Call this method only when the data source does not exist — if it already exists, the method throwsDataSourceAlreadyExistsError. - Save the file.
The following example creates a data source.
/* Create an entity data source bound to the City object schema. */
const dataSourceEditor = await schemaEditor.modelEditor.createDataSource('CityDS', {
type: DataSourceType.EntityDataSource,
entitySchemaName: 'City',
});
Resolve an existing data source
-
Open the "some-component-setup-area.component.ts" file.
-
Call the
getDataSourceEditor()method. -
Handle the result based on the returned value. The method returns one of the following results listed in the table below.
Result
Description
EntityDataSourceEditor
The data source exists and is of a supported type.
UnsupportedDataSourceEditor
The data source exists, but its type is not supported by the current API version.
undefined
No data source with that name exists in the schema.
-
Save the file.
The following example resolves an existing data source.
/* Try to resolve an existing data source by name. */
const editor = await schemaEditor.modelEditor.getDataSourceEditor('CityDS');
if (!editor) {
/* Data source does not exist — safe to create. */
} else if (editor.dataSourceType === DataSourceType.UnsupportedDataSource) {
/* Data source exists but is not supported by the current API version. */
console.warn('Data source type is not supported by the current API version.');
} else if (editor.dataSourceType === DataSourceType.EntityDataSource) {
if (editor.entitySchemaName !== 'City') {
/* Data source name conflict — bound to a different object schema. */
console.error(
`Data source 'CityDS' is bound to '${editor.entitySchemaName}', not 'City'.`
);
}
}
3. Perform an operation on data source attributes
Use EntityDataSourceEditor to add, read, and remove attributes on the data source. These attributes are referenced by nested view model item attributes when creating bindings.
Create a data source attribute
-
Open the "some-component-setup-area.component.ts" file.
-
Verify that the data source is bound to the correct object schema using
entitySchemaName. -
Call the
getAttributeConfig()method to check whether the attribute already exists. If the attribute already exists, thecreateAttribute()method throwsDataSourceAttributeAlreadyExistsError. -
Call the
createAttribute()method and pass the following parameters:name— the attribute identifier.path— the path in the object schema. Supports dot notation, for example,Owner.Name.
-
Save the file.
The following example creates data source attributes.
/* Verify the data source is bound to the correct object schema. */
console.log(dataSourceEditor.entitySchemaName);
/* Create simple and nested-path attributes. */
await dataSourceEditor.createAttribute({ name: 'Id', path: 'Id' });
await dataSourceEditor.createAttribute({ name: 'Name', path: 'Name' });
await dataSourceEditor.createAttribute({ name: 'OwnerName', path: 'Owner.Name' });
/* Guard against DataSourceAttributeAlreadyExistsError. */
const existing = await dataSourceEditor.getAttributeConfig('Id');
if (!existing) {
await dataSourceEditor.createAttribute({ name: 'Id', path: 'Id' });
}
Get a data source attribute
- Open the "some-component-setup-area.component.ts" file.
- Verify that the data source is bound to the correct object schema using
entitySchemaName. - Call the
getAttributeConfig()method and pass the attribute name as a parameter. The method returns{ name, path }for an existing attribute, orundefinedif not found. - Handle the result based on the returned value.
- Save the file.
The following example gets a data source attribute.
/* Confirm the data source object schema before reading attributes. */
console.log(dataSourceEditor.entitySchemaName);
/* Read an attribute config by name. */
const existing = await dataSourceEditor.getAttributeConfig('Id');
if (!existing) {
/* Attribute does not exist — safe to create. */
}
Remove a data source attribute
- Open the "some-component-setup-area.component.ts" file.
- Verify that the data source is bound to the correct object schema using
entitySchemaName. - Call the
getAttributeConfig()method to verify the attribute exists. - Call the
removeAttribute()method and pass the attribute name as a parameter. - Save the file.
The following example removes a data source attribute.
/* Confirm the data source object schema before removing attributes. */
console.log(dataSourceEditor.entitySchemaName);
/* Remove an attribute. */
await dataSourceEditor.removeAttribute('OwnerName');
4. Create or rebind a collection attribute
Use SchemaViewModelEditor to create and manage collection attributes in the root schema view model. A collection attribute represents a collection of item view models. This is the key difference from a scalar attribute binding. The collection attribute interacts with the following view models:
SchemaViewModelEditorworks with the root schema view model.CollectionViewModelAttributeEditorworks with the item view model inside the collection.
Select the appropriate operation based on whether the collection attribute already exists:
- If the collection attribute does not yet exist in the Freedom UI page schema, create it.
- If the collection attribute was already created but needs to be bound to a different data source, rebind it.
Create a collection attribute
-
Open the "some-component-setup-area.component.ts" file.
-
Call the
createAttribute()method and pass the following parameters:isCollection: true— returnsCollectionViewModelAttributeEditor.dataSourceName— binds the collection to a data source.
-
Save the file.
The following example creates a collection attribute.
/* Create a collection attribute bound to the ItemsDS data source. */
const itemsAttributeEditor = await schemaEditor.viewModelEditor.createAttribute('Items', {
isCollection: true,
bindToModel: {
dataSourceName: 'ItemsDS',
},
});
A collection binding uses a dedicated binding type:
interface CollectionViewModelAttributeModelBinding {
dataSourceName: string;
}
Unlike a scalar model binding, a collection binding does not specify dataSourceAttributePath. The collection is bound to the data source as a whole.
Rebind a collection attribute
If the collection attribute already exists and has the correct type, rebind it to another data source. To do this:
- Open the "some-component-setup-area.component.ts" file.
- Call the
getAttributeEditor()method to resolve the existing collection attribute. - Call the
bindToModel()method and pass the new data source name as a parameter. - Save the file.
The following example rebinds a collection attribute.
/* Resolve the existing collection attribute. */
const attributeEditor = await schemaEditor.viewModelEditor.getAttributeEditor('Items');
if (attributeEditor?.attributeType === ViewModelAttributeType.ModelBindingCollection) {
/* Rebind the collection attribute to a different data source. */
await attributeEditor.bindToModel({
dataSourceName: 'CityDS',
});
}
5. Perform an operation on nested item attributes
Use CollectionViewModelAttributeEditor to manage nested item attributes inside the collection. This lets you define which fields are available inside collection items.
Create nested attributes
-
Open the "some-component-setup-area.component.ts" file.
-
Verify that the data source is bound to the correct object schema using
EntityDataSourceEditor.entitySchemaName. -
Call the
createAttribute()method for each nested attribute and pass the following parameters:dataSourceName— the name of the data source.dataSourceAttributePath— the path to the attribute in the object schema.
-
Save the file.
The following example creates nested attributes.
/* Create a nested attribute bound to the Id field of the CityDS data source. */
await collectionAttributeEditor.createAttribute('Id', {
bindToModel: {
dataSourceName: 'CityDS',
dataSourceAttributePath: 'Id',
},
});
/* Create a nested attribute bound to the Name field of the CityDS data source. */
await collectionAttributeEditor.createAttribute('Name', {
bindToModel: {
dataSourceName: 'CityDS',
dataSourceAttributePath: 'Name',
},
});
Get nested attributes
-
Open the "some-component-setup-area.component.ts" file.
-
Verify that the data source is bound to the correct object schema using
EntityDataSourceEditor.entitySchemaName. -
Select a method to get attributes:
getAttributeEditor()— to get a specific attribute by name.getAllAttributeEditors()— to get all attributes as a map. The returned keys belong to the collection item view model.
The following example gets nested attributes.
"some-component-setup-area.component.ts" file/* Get a specific nested attribute by name. */const idAttributeEditor = await collectionAttributeEditor.getAttributeEditor('Id');/* Get all nested attributes as a map. */const allNestedAttributes = await collectionAttributeEditor.getAllAttributeEditors(); -
Check
attributeTypebefore using the API to skip attributes usingattributeType === ViewModelAttributeType.Unsupported.The following example iterates nested attributes safely.
"some-component-setup-area.component.ts" filefor (const [name, editor] of allNestedAttributes) {/* Skip attributes not supported by the current API version. */if (editor.attributeType === ViewModelAttributeType.Unsupported) {continue;}/* Safe to use editor. */} -
Save the file.
Remove nested attributes
- Open the "some-component-setup-area.component.ts" file.
- Verify that the data source is bound to the correct object schema using
EntityDataSourceEditor.entitySchemaName. - Call the
canRemoveAttribute()method to verify the attribute can be safely removed. The attribute may already be referenced elsewhere. - Call the
removeAttribute()method to delete the attribute. - Save the file.
The following example removes a nested attribute.
/* Check whether the attribute can be safely removed before deleting it. */
const canRemove = await collectionAttributeEditor.canRemoveAttribute('Code');
if (canRemove) {
await collectionAttributeEditor.removeAttribute('Code');
}
6. Bind the component property to the collection attribute
Use ViewNodeEditor to bind the component property to the collection attribute.
- Open the "some-component-setup-area.component.ts" file.
- Call the
setPropertyValue()method and bind the component property to the collection attribute. - Save the file.
The following example binds a component property to a collection attribute.
/* Bind the items property of the component to the CityCollection attribute. */
await this._viewNodeEditor.setPropertyValue('items', {
bindToAttribute: 'CityCollection',
});
7. Save extra UI settings
Use ViewNodeEditor to store extra UI settings as constant properties. Some custom Freedom UI components expose visual configuration options — such as visible columns, sorting order, or grouping — that are independent of the data binding. Save these settings as separate constant properties to keep visual configuration state separate from data binding state and make the setup area easier to maintain.
The following example sets a constant property.
/* Set the columns property as a constant list of field names. */
await this._viewNodeEditor.setPropertyValue('columns', {
constant: ['Id', 'Name'],
});
Resulting Freedom UI page schema
The schema API stores configuration in the Freedom UI page schema. Understanding the schema structure helps you debug configuration issues and verify that the API calls produce the expected result.
The table below shows the expected Freedom UI page schema after each API call. Use it to verify that the configuration was applied correctly.
Expected result | Freedom UI page schema |
|---|---|
Component property bound to a collection attribute in the | |
Root collection attribute bound to a data source in the | |
UI-specific settings stored as constant values in the | |
Entity data source in the | |
See also
Error handling when working with collection attributes
Freedom UI Designer setup area for a custom Freedom UI component
Implement a Freedom UI Designer setup area for a custom Freedom UI component
Resources
Remote module template (for Creatio version 8.3.4 and later)
Remote module template (for Creatio version 8.3.3 and earlier)