Components
customitemselectiontable
A table with per-row Add or Remove action buttons used to incrementally build a list of selected items, typically paired (one add table + one remove table).
A table with per-row Add or Remove action buttons used to incrementally build a list of selected items. Typically deployed as a pair: an add mode table shows available rows, a remove mode table shows what the user has picked.
Read Form Rules before using this component — keys, labels, required fields, expressions, visibility, and validation all follow shared conventions.
When to use
- Picking items from a table row-by-row using an Add/Remove action button per row — typical for selecting services, business activities, or records that require multiple visible attributes shown as columns
- Workflows that need a paired source table (
mode: "add") plus a selected-items table (mode: "remove") on the same form - Selection lists driven by an integration gateway endpoint with server-side pagination and search
When NOT to use
Instead of customitemselectiontable, use… | For… |
|---|---|
customdatasettable | Row click selection where the chosen row becomes a single form value (no Add/Remove pairing) |
customdatatable | Pure read-only display of integration-gateway data with no selection |
customeditabletable | Inline-editable grids where the user types into cells row-by-row |
custommultiselectdatafetch | Simple dropdown-style multi-selection where only a label and value per item are needed |
customdropdowndatafetchpaginated | Single-value pick from a large paginated integration-driven list |
How it works
The component operates in one of two modes set via selectionConfig.mode:
addmode — displays a source list; clicking the action button moves the row intoselectionConfig.targetField(another field in the form that tracks selected items). Pair it with aremovemode table to show and manage selected items.removemode — displays the currently selected items (reads from its ownformControl.valueorprops.sourceField); clicking the action button removes the row.
Data sources (priority order)
- API gateway — set
props.url+props.endpointCode+props.datasetto fetch from the integration gateway - Field reference — set
props.sourceFieldto subscribe reactively to another form control's value - Static array — set
props.datadirectly (rarely used in production) - Remove mode self — in
removemode with no other source, reads from own form control value
Props
| Prop | Type | Required? | Description |
|---|---|---|---|
columns | array | required | Column definitions — each entry: { "key": "fieldInRow", "title": "Display Label", "sortable": false, "width": "auto" } |
selectionConfig | object | required | Selection behaviour configuration (see below). |
url | string | required (API mode) | API endpoint URL; always /integration/v1/fetch/sync for gateway calls. Must be on props, not inside dataset. |
endpointCode | string | required (API mode) | Gateway endpoint code. Must be on props, not inside dataset. |
useBaseUrl | boolean | optional | Prepend API gateway base URL to url; default true. |
dataset | object | required (API mode) | Pagination and response mapping config (see below). |
payloadKey | string | optional | Single form field key whose value is sent as payload (simple listener pattern). |
useMultiplePayloadKeys | boolean | optional | If true, use payloadKeyMappings instead of payloadKey. |
payloadKeyMappings | array | optional | Multi-payload mappings — each: { "key": "paramName", "sourceField": "formFieldKey" } or { "key": "paramName", "isStatic": true, "staticValue": "value" }. |
sourceField | string | optional | Key of another form control to read data from reactively (alternative to API/static). |
data | array | optional | Static row data array (lowest priority data source). |
searchable | boolean | optional | Show search input above the table; default true. |
searchPlaceHolder | string | optional | Placeholder text for the search input; default "Search". |
emptyMessage | string | optional | Message when no rows are available; default "No items available". |
title | string | optional | Heading rendered above the table. |
customTableClass | string | optional | Extra CSS class on the <table> element. |
required | boolean | optional | Always false. The required expression controls the runtime value. |
defaultRequired | boolean | optional | Used with the conditional required expression pattern. |
errorMapping | object | optional | Custom API error handling config. |
selectionConfig object
| Field | Type | Required? | Description |
|---|---|---|---|
mode | "add" | "remove" | required | "add" renders an Add button per row; "remove" renders a Remove button. |
targetField | string | required (add) | Key of the form field where added items are stored. |
minItems | number | optional | Minimum items that must be selected; fires minSelection validation error. |
maxItems | number | optional | Maximum items allowed; disables the Add button once reached; fires maxSelection. |
preventDuplicates | boolean | optional | Prevent adding the same row twice; default true. |
actionLabel | string | optional | Text for the action column header and button. |
actionIcon | string | optional | Icon class for the action button (e.g. "icon-plus", "icon-trash"). |
availableIcon | string | optional | Icon shown when an item has not yet been added (add mode). |
selectedIcon | string | optional | Icon shown when an item is already in targetField (add mode). |
removeIcon | string | optional | Icon for the remove action (remove mode). |
displayMode | "icon-only" | "icon-text" | "text-only" | optional | How the action button renders; default "icon-text". |
buttonStyle | "default" | "icon-button" | optional | "default" outlined button; "icon-button" borderless icon link. |
dataset object (API mode)
| Field | Type | Required? | Description |
|---|---|---|---|
dataField | string | optional | Dot-notation path to the rows array in the API response. |
httpMethod | "POST" | "GET" | optional | HTTP method; default "POST". |
params | object | optional | Initial request params (e.g. { "page": 0, "size": 20 }). |
pageSize | number | optional | Rows per page; default 20. |
pageStart | number | optional | Starting page index sent to API; 0 for zero-based, 1 for one-based. |
pageKey | string | optional | Request param key for the page number; default "page". |
sizeKey | string | optional | Request param key for page size; default "size". |
searchKey | string | conditional | Request param key for the search term (required when searchable: true with API data). |
totalPagesKey | string | optional | Dot-notation path to total pages in the response; default "totalPages". |
totalElementsKey | string | optional | Dot-notation path to total element count in the response. |
headers | object | optional | Custom HTTP headers. |
searchLocal | boolean | optional | If true, search filters client-side even when data came from API. |
Validation messages
| Key | When it fires |
|---|---|
minSelection | Selected count is below selectionConfig.minItems |
maxSelection | Selected count exceeds selectionConfig.maxItems |
Examples
Minimal add-mode table
{
"key": "AVAILABLE_SERVICES",
"type": "customitemselectiontable",
"props": {
"label": "Available Services",
"url": "/integration/v1/fetch/sync",
"useBaseUrl": true,
"endpointCode": "GET_SERVICES_LIST",
"dataset": {
"dataField": "data.services",
"httpMethod": "POST",
"pageSize": 10,
"pageStart": 0,
"pageKey": "page",
"sizeKey": "size"
},
"columns": [
{ "key": "name", "title": "Service Name", "width": "70%" },
{ "key": "fee", "title": "Fee (RWF)", "width": "30%" }
],
"selectionConfig": {
"mode": "add",
"targetField": "SELECTED_SERVICES",
"actionIcon": "icon-plus",
"displayMode": "icon-only"
}
},
"validation": {
"messages": {
"minSelection": "Please select at least one service",
"maxSelection": "Maximum services reached"
}
}
}Paired add + remove tables
Source table (add mode) paired with a remove table that shows selected items.
[
{
"key": "AVAILABLE_BUSINESS_ACTIVITIES",
"type": "customitemselectiontable",
"props": {
"label": "Select Business Activity",
"url": "/integration/v1/fetch/sync",
"useBaseUrl": true,
"endpointCode": "RDB_BUSINESS_ACTIVITY_LINES_FETCH",
"searchable": true,
"dataset": {
"dataField": "data.data",
"httpMethod": "POST",
"pageSize": 5,
"pageStart": 1,
"pageKey": "page",
"sizeKey": "size",
"searchKey": "searchKey",
"totalPagesKey": "data.totalPages",
"totalElementsKey": "data.totalElements",
"params": {
"page": 0,
"size": 5
}
},
"useMultiplePayloadKeys": true,
"payloadKeyMappings": [
{
"key": "businessSector",
"isStatic": true,
"staticValue": "A"
}
],
"columns": [
{ "key": "desEng", "title": "Activity", "width": "70%" },
{ "key": "code", "title": "Code", "width": "30%" }
],
"selectionConfig": {
"mode": "add",
"targetField": "SELECTED_BUSINESS_ACTIVITIES",
"maxItems": 3,
"actionIcon": "icon-plus",
"selectedIcon": "icon-check",
"displayMode": "icon-only",
"preventDuplicates": true
},
"emptyMessage": "No activities found",
"required": false,
"defaultRequired": false
},
"expressions": {
"props.hideField": "model.BUSINESS_SECTOR !== 'A'",
"props.required": "!(field?.props?.hideField || field?.hide) && field?.props?.defaultRequired"
},
"validation": {
"messages": {
"maxSelection": "You can select up to 3 activities only",
"minSelection": "Please select at least one activity to proceed"
}
}
},
{
"key": "SELECTED_BUSINESS_ACTIVITIES",
"type": "customitemselectiontable",
"props": {
"label": "Selected Activities",
"columns": [
{ "key": "desEng", "title": "Activity", "width": "70%" },
{ "key": "code", "title": "Code", "width": "30%" }
],
"selectionConfig": {
"mode": "remove",
"actionIcon": "icon-trash",
"displayMode": "icon-only"
},
"searchable": false,
"emptyMessage": "No activities selected yet"
}
}
]Common mistakes
- Putting
urlandendpointCodeinsidedatasetinstead of at the top level ofprops— the component reads them frompropsdirectly; nesting them disables the fetch. - Using
addmode withoutselectionConfig.targetField— selected items have nowhere to go; the selection is silently lost. - Omitting
selectionConfigentirely — the component throws an error on init becauseselectionConfigis required. - Setting
selectionConfig.maxItemswithout amaxSelectionvalidation message — the Add button disables when the limit is reached but no error text appears. - Setting
dataset.pageStart: 1when the API expects 0-based pages — the first page is skipped. - Not pairing an
addmode table with aremovemode table — the user can add items but has no way to undo selections. - Using
searchable: truewith API data but omittingdataset.searchKey— the search input appears but the keyword is never sent.
Checklist
-
keyisUPPER_SNAKE_CASEand unique across the entire form -
props.labelis present, unique, and correctly cased -
selectionConfig.modeis"add"or"remove" -
addmode tables setselectionConfig.targetFieldto a valid form field key - A matching
removemode table exists with the samekeyas thetargetField -
urlandendpointCodeare onprops, not nested insidedataset -
dataset.pageStartmatches the API's pagination indexing (0-based or 1-based) -
searchable: truewith API data includesdataset.searchKey -
selectionConfig.maxItemshas a matchingvalidation.messages.maxSelection -
selectionConfig.minItemshas a matchingvalidation.messages.minSelection - When required:
required: false,defaultRequiredis set, andexpressions["props.required"]is present