Populating Select Boxes
Select boxes can be populated statically (fixed options defined in the Form Builder), or dynamically (options returned by your backend at runtime). This page covers dynamic population from the backend.
When your handler calls SetFieldAllowedValues(), the options are included in the fieldAllowedValues property of the /runevent response. The SDK receives them and populates the dropdown.
When to populate from the backend
- Options come from a database table that changes over time.
- Options depend on the current user, record, or other form values.
- You need cascading dropdowns (selecting one option filters the next dropdown).
- The list is large and should be loaded on demand.
Response structure
Return options in fieldAllowedValues:
{
"fieldAllowedValues": {
"departmentId": {
"1": "Engineering",
"2": "Marketing",
"3": "Operations"
},
"statusId": {
"draft": "Draft",
"active": "Active",
"archived": "Archived"
}
}
}
Populating on form load
public async Task Form_onInit(bool isInitialLoad)
{
// Departments
var departments = await _db.Departments
.Where(d => d.IsActive)
.OrderBy(d => d.Name)
.ToDictionaryAsync(d => d.Id.ToString(), d => d.Name);
SetFieldAllowedValues("departmentId", departments);
// Job levels (static)
SetFieldAllowedValues("jobLevel", new Dictionary<string, string>
{
{ "junior", "Junior" },
{ "mid", "Mid-level" },
{ "senior", "Senior" },
{ "lead", "Lead" },
{ "manager", "Manager" }
});
}
Populating from a table action
You can update a dropdown in response to any event — not just load. For example, after a user selects a project from a table row:
public async Task projecttbl_onTableRunActionEvent(string action, object rowData)
{
if (action == "select")
{
string rowId = ((JObject)rowData).Value<string>("_id")!;
// Load task categories for the selected project
var categories = await _db.TaskCategories
.Where(tc => tc.ProjectId.ToString() == rowId)
.ToDictionaryAsync(tc => tc.Id.ToString(), tc => tc.Name);
SetFieldAllowedValues("categoryId", categories);
record.SetField("projectId", rowId);
}
}
Clearing options
Return an empty dictionary to reset a dropdown (e.g. when the parent selection is cleared):
public async Task countryCode_onChange(string? value)
{
if (string.IsNullOrEmpty(value))
{
// Clear the region dropdown
SetFieldAllowedValues("regionId", new Dictionary<string, string>());
record.SetField("regionId", "");
}
}
Full example — three-level cascade
public async Task continentId_onChange(string? value)
{
var countries = await _db.Countries
.Where(c => c.ContinentId.ToString() == value)
.OrderBy(c => c.Name)
.ToDictionaryAsync(c => c.Id.ToString(), c => c.Name);
SetFieldAllowedValues("countryId", countries);
SetFieldAllowedValues("regionId", new Dictionary<string, string>());
SetFieldAllowedValues("cityId", new Dictionary<string, string>());
record.SetField("countryId", "");
record.SetField("regionId", "");
record.SetField("cityId", "");
}
public async Task countryId_onChange(string? value)
{
var regions = await _db.Regions
.Where(r => r.CountryId.ToString() == value)
.OrderBy(r => r.Name)
.ToDictionaryAsync(r => r.Id.ToString(), r => r.Name);
SetFieldAllowedValues("regionId", regions);
SetFieldAllowedValues("cityId", new Dictionary<string, string>());
record.SetField("regionId", "");
record.SetField("cityId", "");
}
public async Task regionId_onChange(string? value)
{
var cities = await _db.Cities
.Where(c => c.RegionId.ToString() == value)
.OrderBy(c => c.Name)
.ToDictionaryAsync(c => c.Id.ToString(), c => c.Name);
SetFieldAllowedValues("cityId", cities);
record.SetField("cityId", "");
}
For more patterns including search-as-you-type, see Autocomplete Feature.