Original Reference: Ryan Farley, “Getting Multi‑select Records from a Creatio Freedom UI List via Code” Customer FX, Jan 30, 2024
The Challenge
In Creatio 8.1+, lists support multi‑selection. Ryan’s post shows how to bind a custom bulk‑action button and read the DataTable_SelectionState
attribute:
const selectedIds = (await request.$context.DataTable_SelectionState).selected;
However, this only covers the case where users manually pick some rows (“specific” selection). If they click “Select All,” the selected
array is empty and selectionState.type === "all"
, with an unselected
array instead. Nor does the basic snippet iterate through all pages of data.
An Alternative, Complete Handler
The code below demonstrates how to:
- Detect whether the user chose a subset (“specific”) or all records (“all”).
- Extract the true list of record IDs in both cases.
- Execute a business process for each record.
- Access other context values (e.g. an Opportunity ID wrapped in a proxy).
{ request: "cfx.ButtonClicked", handler: async (request, next) => { // 1. Retrieve selection state const selectionState = await request.$context.DataTable_SelectionState; // 2. Grab manual selections (if any) const selectedIds = selectionState?.selected || []; // 3. Retrieve another context value (example: Opportunity ID) const opportunityProxy = await request.$context.UsrEntity_66d2fb2DS_UsrOpportunityId_dppjv0g; const opportunityId = opportunityProxy?.value; // Helper: execute business process const runBP = async (recordId) => { return request.$context.executeRequest({ type: "crt.RunBusinessProcessRequest", processName: "UsrProcess_4e23e14", processParameters: { ProcessSchemaParameter1: recordId, opportunityId }, $context: request.$context }); }; // 4. Handle specific selections if (selectionState.type === "specific") { if (!selectedIds.length || !opportunityId) { console.error("No records selected or missing Opportunity ID."); return; } for (const id of selectedIds) { await runBP(id); } // 5. Handle “select all” } else if (selectionState.type === "all") { // a. Get IDs the user explicitly un‑selected (if any) const unselected = selectionState.unselected || []; // b. Read the full page of items bound to the list const items = await request.$context.Items; if (!Array.isArray(items)) { console.error("Items binding is not an array."); return; } // c. Build a final list of IDs: include every item not in unselected const allIds = items .map(item => item.PDS_Id.__zone_symbol__value) .filter(id => !unselected.includes(id)); if (!allIds.length || !opportunityId) { console.error("No records to process or missing Opportunity ID."); return; } for (const id of allIds) { await runBP(id); } } // 6. Continue the chain return next?.handle(request); } }
How This Works
selectionState.selected
vs..unselected
:- When users pick specific rows,
selected
holds their IDs. - When they click the header checkbox (“Select All”), the engine treats it as “all except any I un‑checked,” so
selected
is empty andtype === "all"
. Theunselected
array lists exceptions.
- When users pick specific rows,
- Reading the full list (Items):
- You only get the current page of records in the
Items
binding. If your grid is paginated, you’ll need to iterate through pages server‑side or adjust your viewModel to load all records you intend to process.
- You only get the current page of records in the