Custom fields
How to use the Custom fields in an Iris App
When you have a Tile Extension and a Tile App, you would probably like to store some data from your tile.
To do this, you need to define one or more custom fields in the tile manifest. When a field has been defined, a value can be set per asset using the tile runtime.
The definition is created once the tile is approved and shared accross all assets. Whereas the value can be set on each asset by the tile developer.
1. Defining a custom field
To define a custom field, you need to add a customFieldDefinitions array to your tile manifest:
customFieldDefinitions: [
{
type: 'STRING',
entityType: 'ASSET',
key: 'myKey',
title: 'String Field',
uiEditable: true,
uiVisible: true,
defaultValue: 'Some default value',
}
]
-
typedefines the custom field type (see below for all options). -
entityTypedefines what type of entity a custom field can be used on. Currently onlyASSETis supported. -
keydefines the programmatic name of the field and cannot be changed after it has been created. -
titledefines the UI visible name of the field. -
uiEditable/uiVisiblecontrols how the field will be shown in the manager UI. This does not limit how the field is used inside the tile. -
defaultValueTBD (might not make sense)
Apart from the these standard properties, some field types have extra properties defined below.
All custom fields defined by a tile will be owned by the tile, and the tile developer should consider existing data when changing field definitions.
Custom field types
| Field type | Description | Extra properties |
|---|---|---|
| BOOLEAN | Boolean value. | |
| DATE | Date stored in ISO8601 format. | |
| DROPDOWN | A predefined list of options. Supports both single and multi selects. | allValues, multiSelect, valueReplacements |
| Email field. Will be rendered with a mailto link in the UI. | ||
| NUMBER | Numeric | minimum, maximum, isInteger |
| PHONE_NUMBER | Phone number stored in E.164 format. Validated using Google libphonenumber. | |
| STRING | Free text field | minimumLength, maximumLength, pattern |
| STRING_LIST | List of free text values | maximumItems, itemMinimumLength, itemMaximumLength, pattern |
| WEB_ADDRESS | Web address. Will be shown as a link to open a new window in the UI. | |
| JSON | JSON values. | |
| MONETARY | Monetary values with currency in ISO 4217 standard. | minimum, maximum |
| FILE | File reference. Files are uploaded via a presigned URL. Maximum file size is 20 MB. |
Type specific properties
| Property | Field type | Required | Description |
|---|---|---|---|
maximum | NUMBER | No | Maximum numeric value |
minimum | NUMBER | No | Minimum numeric value |
isInteger | NUMBER | No | Disallow decimal values |
maximumLength | STRING | No | Maximum length of the text |
minimumLength | STRING | No | Minimum length of the text |
pattern | STRING | No | The allowed regular expression. Syntax is documented here. |
itemMinimumLength | STRING_LIST | No | Minimum length of each string item |
itemMaximumLength | STRING_LIST | No | Maximum length of each string item |
maximumItems | STRING_LIST | No | Maximum items allowed in the list |
allValues | DROPDOWN | Yes | All allowed values |
multiSelect | DROPDOWN | No | Allow multiple options to be selected. Default false |
valueReplacements | DROPDOWN | No | Map from old values no longer allowed to new values. Used for updating existing data |
currency | MONETARY | Yes | Currency in ISO 4217 standard |
2. Using a custom field
Programmatic access
First install the Iris App Runtime if you haven't got it already.
npm i @trackunit/iris-app-runtime-core
Using the CustomFieldRuntime you will be able to obtain all custom fields values and definitions owned by the tile:
import { CustomFieldRuntime, CustomFieldType } from '@trackunit/iris-app-runtime-core';
const customFieldRuntime = new CustomFieldRuntime();
// Get all custom field values and definitions for your asset ID.
const myCustomFieldsPromise = customFieldRuntime.getCustomFieldsFor({
type: 'ASSET',
id: '<my-asset-id>',
});
The API returns both the definition of the custom field and any value saved on the given asset ID.
If a value has not been saved for the asset ID only the definition will be returned.
To save a custom field value you need to provide the definition key, the entity Id and the new value:
// Save a custom field value
customFieldRuntime.setCustomFieldsFor(
{
type: 'ASSET',
id: '<my-asset-id>',
},
[
{
definitionKey: 'myKey',
value: {
type: CustomFieldType.STRING,
stringValue: 'My new value',
}
}
]
);
UI components
To ease the life of the tile developer we provide a React UI component that renders a custom field input box according
to a custom field definition.
First install the Tile Runtime if you haven't got it already.
npm i @trackunit/custom-field-components
import { CustomField } from '@trackunit/custom-field-components';
With the component it is possible to render an input component for any custom field by providing a field retrieved from getCustomFieldsFor to the component:
<CustomField
field={field}
key={field.definition.key}
register={register}
formState={formState}
setValue={setValue}
/>
This complete example demonstrates how to render all custom fields owner by the current Iris App:
import React, { useEffect, useState } from 'react';
import {
CustomFieldRuntime,
AssetRuntime,
AssetInfo,
ValueAndDefinition,
} from '@trackunit/iris-app-runtime-core';
import {
Button,
Card,
CardBody,
CardFooter,
CardHeader,
} from '@trackunit/react-components';
import { CustomField } from '@trackunit/custom-field-components';
import { useForm } from 'react-hook-form';
import { TrackunitProviders } from '@trackunit/react-core-contexts';
const assetRuntime = new AssetRuntime();
const customFieldRuntime = new CustomFieldRuntime();
export const App: React.FC = () => {
const [customFields, setCustomFields] = useState<ValueAndDefinition[]>();
const [asset, setAsset] = useState<AssetInfo>();
const { register, handleSubmit, formState, setValue } = useForm({
shouldUnregister: false,
});
useEffect(() => {
(async () => {
const updatedAssetInfo = await assetRuntime.getAssetInfo();
setAsset(updatedAssetInfo);
const myCustomFields = await customFieldRuntime.getCustomFieldsFor({
id: updatedAssetInfo.assetId,
type: 'ASSET',
});
setCustomFields(myCustomFields);
})();
}, []);
return (
<TrackunitProviders>
<Card>
<CardHeader
heading="Custom Fields"
subHeading="Showcase for custom fields."
/>
<CardBody>
{customFields?.map((field) => {
return (
<CustomField
field={field}
key={field.definition.key}
register={register}
formState={formState}
setValue={setValue}
/>
);
})}
</CardBody>
<CardFooter>
<Button
onClick={handleSubmit((data) => {
customFieldRuntime.setCustomFieldsFromFormData(
{
id: asset?.assetId || '',
type: 'ASSET',
},
data,
customFields || []
);
})}
>
Save Changes
</Button>
</CardFooter>
</Card>
</TrackunitProviders>
);
};
3. Local development
When running in local dev mode the tile runtime will store all custom fields in local storage in the browser.
This means that tile developers can easily change custom fields and add new fields without getting a new tile version approved.
Please note, that since the custom fields only exists in local storage inside the browser any values stored during local dev mode will only be available inside the tile and not in other parts of the UI.
The validation rules will also be less strict since we are only emulating a custom fields backend.
4. Deploying a new custom field
When a tile package with a custom field is published, we will validate it during the publish flow. So watch out for any warnings during publish. After publish, we will review the tile and when it is approved we will save the new field definitions.
If any existing values do not follow the new definition, they will be replaced with the default value.