Skip to main content

Foundation Forms - Conditional Filtering

Conditional filtering is a powerful feature that enables you to create dynamic, dependent form fields where the options of one field are filtered based on the selection of another field. This is particularly useful for implementing nested or cascading dropdowns, where each selection narrows down the available options in subsequent fields. Common use cases include:

  • Country-city selection
  • Counterparty-trader relationships
  • Category-subcategory hierarchies
  • Department-employee mappings

This creates a more intuitive and guided user experience by preventing invalid selections and reducing the number of choices presented to the user.

Example

The following example demonstrates a location selection form where cities are filtered based on the selected country. The key to this implementation is the data-change event handler, which triggers whenever the form data changes. This event allows us to detect when the country selection changes and update the available cities accordingly:

JSON Schema

const locationJsonSchema = {
properties: {
COUNTRY_ID: {
type: 'number',
title: 'Country'
},
CITY_ID: {
type: 'number',
title: 'City'
}
}
};

UI Schema

const locationUISchema = (countries: any[] = [], cities: any[] = []): UiSchema => ({
type: 'VerticalLayout',
elements: [
{
type: 'Control',
label: 'Country',
scope: '#/properties/COUNTRY_ID',
options: {
valueField: 'COUNTRY_ID',
labelField: 'COUNTRY_NAME',
data: countries
}
},
{
type: 'Control',
label: 'City',
scope: '#/properties/CITY_ID',
options: {
valueField: 'CITY_ID',
labelField: 'CITY_NAME',
data: cities
}
}
]
});

Component Implementation

@customElement({
name: 'location-form',
template: html<LocationForm>`
<foundation-form
:data="${(x) => x.existingData}"
:jsonSchema="${() => locationJsonSchema}"
:uischema="${(x) => x.uiSchema}"
@data-change=${(x, c) => x.handleDataChange(c.event as CustomEvent)}
></foundation-form>
`
})
export class LocationForm extends GenesisElement {
@observable uiSchema: UiSchema;
@observable existingData = { COUNTRY_ID: undefined, CITY_ID: undefined };
@observable countries = [];
@observable cities = [];

async connectedCallback() {
super.connectedCallback();
await this.loadCountries();
await this.loadCities();
this.uiSchema = locationUISchema(this.countries, []);
}

async loadCountries() {
this.countries = [
{ COUNTRY_ID: 1, COUNTRY_NAME: 'United States' },
{ COUNTRY_ID: 2, COUNTRY_NAME: 'United Kingdom' }
];
}

async loadCities() {
this.cities = [
{ CITY_ID: 1, COUNTRY_ID: 1, CITY_NAME: 'New York' },
{ CITY_ID: 2, COUNTRY_ID: 1, CITY_NAME: 'Los Angeles' },
{ CITY_ID: 3, COUNTRY_ID: 2, CITY_NAME: 'London' },
{ CITY_ID: 4, COUNTRY_ID: 2, CITY_NAME: 'Manchester' }
];
}

filterCities(countryId: number) {
return this.cities.filter(city => city.COUNTRY_ID === countryId);
}

handleDataChange(evt: CustomEvent) {
const { data } = evt.detail;
if (this.existingData.COUNTRY_ID !== data.COUNTRY_ID) {
this.existingData = { ...data, CITY_ID: undefined };
const filteredCities = this.filterCities(data.COUNTRY_ID);
this.uiSchema = locationUISchema(this.countries, filteredCities);
}
}
}

Here's a live example of conditional filtering in action: