Autocomplete
Autocomplete allows users to search a large dataset by typing, with your backend filtering and returning matching options dynamically. It is powered by the onChange event on a Select widget configured for autocomplete mode.
Widget configuration
Before writing any backend code, two properties must be enabled on the Select widget in the Form Builder:
| Property | Description |
|---|---|
| Search Box | Renders a text input inside the dropdown so the user can type to filter options |
| On Change | Fires an onChange event to your backend as the user types, enabling dynamic results |
Both must be turned on for autocomplete to work.
How it works
- The user types characters into the Select widget.
- The SDK fires an
onChangeevent withwidgetValueset to the current search string. - Your handler queries the database and returns matching options via
fieldAllowedValues. - The SDK renders the results as a dropdown below the input.
Handler implementation
public async Task customerSearch_onChange(string? value)
{
string query = value ?? "";
// Search customers by name or email
var matches = await _db.Customers
.Where(c =>
c.Name.Contains(query) ||
c.Email.Contains(query))
.OrderBy(c => c.Name)
.Take(20) // limit results to keep the list manageable
.ToDictionaryAsync(c => c.Id.ToString(), c => $"{c.Name} ({c.Email})");
SetFieldAllowedValues("customerSearch", matches);
}
Returning structured results
For more control over what is displayed vs. what is stored, you can include additional context in the display label:
var matches = await _db.Products
.Where(p => p.Name.Contains(query) || p.Sku.Contains(query))
.Take(20)
.ToDictionaryAsync(
p => p.Id.ToString(),
p => $"{p.Sku} — {p.Name} (${p.Price:F2})"
);
SetFieldAllowedValues("productSearch", matches);
Populating related fields on selection
After the user selects a result, fire an additional onChange to populate related fields:
public async Task customerSearch_onChange(string? value)
{
if (!string.IsNullOrEmpty(value))
{
// Distinguish search typing (multiple chars) from selection (a GUID)
bool isSelection = Guid.TryParse(value, out _);
if (isSelection)
{
// User selected a customer — fill in related fields
var customer = await _db.Customers.FindAsync(Guid.Parse(value));
if (customer != null)
{
record.SetField("customerId", customer.Id.ToString());
record.SetField("customerName", customer.Name);
record.SetField("billingAddress", customer.BillingAddress);
record.SetField("creditLimit", customer.CreditLimit.ToString());
}
}
else
{
// User is still typing — return search results
var matches = await _db.Customers
.Where(c => c.Name.Contains(value))
.Take(20)
.ToDictionaryAsync(c => c.Id.ToString(), c => c.Name);
SetFieldAllowedValues("customerSearch", matches);
}
}
}
Performance considerations
- Always use
Take(N)to cap result sets — returning thousands of options will degrade UI performance. - Add database indexes on the columns you search (
Name,Email,Sku, etc.). - For very large datasets, consider a full-text search index or a dedicated search service.