Client Extensibility
# Client Extensibility
You can extend the functionalities of the control by accessing it's API through a client script. To access the API, you need to register an addOnOutputChange
(opens new window) callback on the control.
When the callback is triggered, you can retrieve the dataset instance. From there, you can use its API to manipulate the control and register event handlers.
function onFormLoad(executionContext) {
const formContext = executionContext.getFormContext();
const control = formContext.getControl('talxis_clientextensibilitybindingfield');
control.addOnOutputChange((executionContext) => {
const control = executionContext.getFormContext().getControl('talxis_clientextensibilitybindingfield');
const dataset = control.getOutputs()['talxis_clientextensibilitybindingfield.fieldControl.DatasetControl'].value;
dataset.addEventListener('onRecordLoaded', (record) => {
//register record expressions
})
dataset.addEventListener('onDatasetItemOpened', (entityReference) => {
openAlertDialog(entityReference);
});
dataset.addEventListener('onRecordsSelected', (ids) => {
showSelectedRecordsNotifications(ids, dataset)
});
})
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Record Expressions
Record expressions allow you to dynamically manipulate specific cells within a record (row) by defining a callback function for certain events. Each expression is tied to a specific record cell. The API currently supports setting expressions for the following types of customizations:
# Dynamic Cell Values
If specified, the control will use it's returned value as cell's value. You can think of it as setting a formula on a cell in Excel. Unlike setValue
, speficying this callback does not trigger any changes on the dataset, even if the value is different between renders. This means the user will not see any pending changes, unless they directly manipulate the cell value. If they do, that value will take precedence over the expression. If the user removes the change, the expression will be used again.
dataset.addEventListener('onRecordLoaded', (record) => {
//value of this cell will always be the sum of talxis_decimal and
//talxis_wholenone
record.expressions?.setValueExpression('talxis_sum__virtual', () => {
const value1 = record.getValue('talxis_decimal') ?? 0;
const value2 = record.getValue('talxis_wholenone') ?? 0;
return value1 + value2;
})
})
2
3
4
5
6
7
8
9
NOTE: When calculating the value, DO NOT call
getValue
on the same record and cell. If you do, you will end up in an infinite loop.
# Cell Validation
You can define custom validation logic to supplement the built-in validation. When triggered, your logic will run alongside the native validation. If your custom validation fails, the user will not be able to save their changes. You can (should) also provide a custom error message that will be displayed in the UI.
dataset.addEventListener('onRecordLoaded', (record) => {
//validation for this cell will fail if it's value is <= 0
record.expressions?.setValidationExpression('talxis_sum__virtual', () => {
const value = record.getValue('talxis_sum__virtual') ?? 0;
return {
error: value <= 0,
errorMessage: value <= 0 ? 'Has to be positive number!' : ''
}
})
})
2
3
4
5
6
7
8
9
10
# Cell Disabling
When a control's EnableEditing
attribute is set to true and a column's IsValidForUpdate
property is also true, all cells within that column are editable by default. However, there may be cases where you want to disable editing for specific cells. This can be achieved by defining a disabled expression for those cells.
record.expressions?.setDisabledExpression('talxis_name', () => {
//disable editing if the value ends with '7'
if (record.getValue('talxis_name')?.endsWith('7')) {
return true;
}
return false;
})
2
3
4
5
6
7
# Cell Editor Parameters
You can tailor the parameters of the Cell Editor control to meet your specific requirements. For instance, if you need to use a custom viewId for a Lookup, you can achieve this by implementing your own getAllViews
method to return the desired viewId
NOTE: Passed parameters always adhere to the Base Control interface for the specified data type.
dataset.addEventListener('onRecordLoaded', (record) => {
record.expressions?.ui.setCellEditorParametersExpression('transactioncurrencyid', (parameters) => {
return {
...parameters,
value: {
...parameters.value,
getAllViews: (entityName) => {
return [{
isDefault: true,
viewId: 'a2420357-be1d-4480-8da8-0ee8c33cd95a'
}]
}
}
}
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NOTE: Never directly manipulate the passed parameters. Always use spread operator and return a new object!
# Dynamic Row Height
You can override the height of a cell in a record by specifying this expression. Your callback function will also receive the current width of the cell and the default row height used by the control. If you set a custom height for a cell, the tallest cell in the row will determine the row's overall height. The remaining cells will automatically adjust to fill the available space.
dataset.addEventListener('onRecordLoaded', (record) => {
record.expressions?.ui.setHeightExpression('talxis_multiline__virtual', (columnWidth, rowHeight) => {
const value = record.getValue('talxis_multiline__virtual') ?? "";
const length = value.length;
let minHeight = rowHeight;
let maxHeight = 200;
if (length === 0) {
return undefined;
}
const avgCharWidth = 14 * 0.5;
// Calculate the max number of characters that fit in one line
const charsPerLine = Math.floor(columnWidth / avgCharWidth);
// Calculate the number of lines needed
const numLines = Math.ceil(value.length / charsPerLine);
// Calculate the height based on the number of lines
const lineHeight = 14 * 1.2; // 1.2 multiplier for line spacing
let totalHeight = numLines * lineHeight;
if (totalHeight < minHeight) {
totalHeight = minHeight;
}
if (totalHeight > maxHeight) {
totalHeight = maxHeight
}
return Math.ceil(totalHeight);
})
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
NOTE: Code for height calculation is AI generated and might need some revision to work correctly in all cases.
The above code dynamically calculates the required row height to accommodate the content of a cell with multiline input. This ensures that the text is fully visible without truncation, as the row height automatically adjusts to fit the length of the text.
# Cell Loading
You can use this expression to tell the control if specific cell should appear as being loaded. This is usefull if you need to do some async operation before the cell value can be calculated.
dataset.addEventListener('onRecordLoaded', (record) => {
//will put all cells into a loading state.
for (const column of dataset.columns) {
record.expressions?.ui.setLoadingExpression(column.name, () => {
return true;
})
}
})
2
3
4
5
6
7
8
# Cell Notifications
You can enhance specific cells by adding buttons, each capable of executing multiple actions. If a button is linked to a single action, clicking it will immediately trigger the action. When multiple actions are assigned, clicking the button will display a pop-up menu, allowing the user to select the desired action. Additionally, notifications can be assigned to an empty virtual column, effectively creating an inline ribbon.
record.expressions?.ui.setNotificationsExpression('talxis_name', () => {
return [{
uniqueId: 'action1',
iconName: 'LightningBolt',
title: 'Single Action',
actions: [{
actions: []
}]
},
{
uniqueId: 'action2',
iconName: 'LightningBolt',
title: 'Two Actions',
messages: ['Choose one of the following actions:'],
actions: [{
message: 'Action 1',
actions: [() => alert('Action 1')]
}, {
message: 'Action 2',
actions: [() => alert('Action 2')]
}]
},
{
uniqueId: 'action3',
iconName: 'LightningBolt',
title: 'Multiple Actions',
messages: ['Choose one of the following actions:'],
actions: [{
message: 'Action 1',
actions: [() => alert('Action 1')]
}, {
message: 'Action 2',
actions: [() => alert('Action 2')]
},
{
message: 'Action 3',
actions: [() => alert('Action 3')]
}, {
message: 'Action 4',
actions: [() => alert('Action 4')]
}
]
}
];
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Notifications can be useful tool for extending the control functionality. For instance, they can be utilized to create an interactive counter, as demonstrated in the following example:
record.expressions?.ui.setNotificationsExpression('talxis_wholenone', () => {
return [{
uniqueId: 'increment',
iconName: 'Add',
compact: true,
title: 'Increment',
actions: [{
actions: [() => {
const value = record.getValue(columnName) ?? 0;
record.setValue(columnName, value + 1);
dataset.render();
}]
}]
},
{
uniqueId: 'decrement',
iconName: 'Remove',
compact: true,
title: 'Decrement',
actions: [{
actions: [() => {
const value = record.getValue(columnName) ?? 0;
record.setValue(columnName, value - 1);
dataset.render();
}]
}]
}
];
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29