Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/pluggableWidgets/combobox-web/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

### Changed

- Moved the debounce interval for filtering operations from the Events tab to Advanced tab.

## [2.5.1] - 2025-09-19

### Fixed
Expand All @@ -15,6 +19,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

- We fixed an issue where combobox lazy load is not working on initial load.

### Added

- We added the option to configure a debounce interval for datasource filter operations as well.

## [2.5.0] - 2025-08-12

### Added
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,6 @@ export function getProperties(
hidePropertiesIn(defaultProperties, values, ["loadingType"]);
}

if (values.onChangeFilterInputEvent === null) {
hidePropertiesIn(defaultProperties, values, ["filterInputDebounceInterval"]);
}

return defaultProperties;
}

Expand Down
9 changes: 4 additions & 5 deletions packages/pluggableWidgets/combobox-web/src/Combobox.xml
Original file line number Diff line number Diff line change
Expand Up @@ -329,18 +329,13 @@
<caption>On leave</caption>
<description />
</property>

<property key="onChangeFilterInputEvent" type="action" required="false">
<caption>On filter input change</caption>
<description />
<actionVariables>
<actionVariable key="filterInput" caption="Filter Input" type="String" />
</actionVariables>
</property>
<property key="filterInputDebounceInterval" type="integer" required="true" defaultValue="200">
<caption>Debounce interval</caption>
<description>The debounce interval for each filter input change event triggered in milliseconds.</description>
</property>
</propertyGroup>
<propertyGroup caption="Accessibility">
<propertyGroup caption="Accessibility">
Expand Down Expand Up @@ -439,6 +434,10 @@
<enumerationValue key="none">None</enumerationValue>
</enumerationValues>
</property>
<property key="filterInputDebounceInterval" type="integer" required="true" defaultValue="200">
<caption>Debounce interval</caption>
<description>The debounce interval for each filter input change event triggered in milliseconds.</description>
</property>
</propertyGroup>
</propertyGroup>
</properties>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ export class BaseAssociationSelector<T extends string | string[], R extends Refe
private _valuesMap: Map<string, ObjectItem> = new Map();
private lazyLoader: LazyLoadProvider = new LazyLoadProvider();

constructor() {
constructor(props: { filterInputDebounceInterval: number }) {
this.caption = new AssociationSimpleCaptionsProvider(this._valuesMap);
this.options = new AssociationOptionsProvider(this.caption, this._valuesMap);
this.options = new AssociationOptionsProvider(this.caption, this._valuesMap, props.filterInputDebounceInterval);
}

updateProps(props: ComboboxContainerProps): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ type ExtractionReturnValue = [
ListWidgetValue | undefined,
OptionsSourceAssociationCustomContentTypeEnum,
boolean,
LoadingTypeEnum
LoadingTypeEnum,
number
];

export function extractAssociationProps(props: ComboboxContainerProps): ExtractionReturnValue {
const attr = props.attributeAssociation;
const filterType = props.filterType;
const onChangeEvent = props.onChangeEvent;
const filterInputDebounceInterval = props.filterInputDebounceInterval;

if (!attr) {
throw new Error("'optionsSourceType' type is 'association' but 'attributeAssociation' is not defined.");
Expand Down Expand Up @@ -79,6 +81,7 @@ export function extractAssociationProps(props: ComboboxContainerProps): Extracti
customContent,
customContentType,
lazyLoading,
loadingType
loadingType,
filterInputDebounceInterval
];
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
import { debounce } from "@mendix/widget-plugin-platform/utils/debounce";
import { ListAttributeValue, ListValue, ObjectItem } from "mendix";
import { FilterTypeEnum } from "../../typings/ComboboxProps";
import { BaseOptionsProvider } from "./BaseOptionsProvider";
import { datasourceFilter } from "./datasourceFilter";
import { CaptionsProvider, SortOrder, Status } from "./types";
import { DEFAULT_LIMIT_SIZE } from "./utils";
import { FilterCondition } from "mendix/filters";

export interface BaseProps {
attributeId?: ListAttributeValue["id"];
ds: ListValue;
filterType: FilterTypeEnum;
lazyLoading: boolean;
filterInputDebounceInterval?: number;
}

export class BaseDatasourceOptionsProvider extends BaseOptionsProvider<ObjectItem, BaseProps> {
private ds?: ListValue;
private attributeId?: ListAttributeValue["id"];
protected loading: boolean = false;
private debouncedSetFilter: (filterCondition: FilterCondition | undefined) => void;

constructor(
caption: CaptionsProvider,
protected valuesMap: Map<string, ObjectItem>
protected valuesMap: Map<string, ObjectItem>,
filterInputDebounceInterval: number = 200
) {
super(caption);

const [debouncedFn] = debounce((filterCondition: FilterCondition | undefined) => {
this.ds?.setFilter(filterCondition);
}, filterInputDebounceInterval);

this.debouncedSetFilter = debouncedFn;
}

get sortOrder(): SortOrder {
Expand Down Expand Up @@ -51,10 +62,10 @@ export class BaseDatasourceOptionsProvider extends BaseOptionsProvider<ObjectIte
getAll(): string[] {
if (this.lazyLoading && this.attributeId) {
if (this.searchTerm === "") {
this.ds?.setFilter(undefined);
this.debouncedSetFilter(undefined);
} else {
const filterCondition = datasourceFilter(this.filterType, this.searchTerm, this.attributeId);
this.ds?.setFilter(filterCondition);
this.debouncedSetFilter(filterCondition);
}

return this.options;
Expand Down Expand Up @@ -89,7 +100,7 @@ export class BaseDatasourceOptionsProvider extends BaseOptionsProvider<ObjectIte
loadSelectedValue(attributeValue: string, attrId?: ListAttributeValue["id"]): void {
if (this.lazyLoading && this.ds && this.attributeId) {
const filterCondition = datasourceFilter("containsExact", attributeValue, attrId ?? this.attributeId);
this.ds?.setFilter(filterCondition);
this.debouncedSetFilter(filterCondition);
this.ds.setLimit(1);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ export class DatabaseMultiSelectionSelector implements MultiSelector {
private _objectsMap: Map<string, ObjectItem> = new Map();
selectedItemsSorting: SelectedItemsSortingEnum = "none";

constructor() {
constructor(props: { filterInputDebounceInterval: number }) {
this.caption = new DatabaseCaptionsProvider(this._objectsMap);
this.options = new DatabaseOptionsProvider(this.caption, this._objectsMap);
this.options = new DatabaseOptionsProvider(this.caption, this._objectsMap, props.filterInputDebounceInterval);
}

getOptions(): string[] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ export class DatabaseSingleSelectionSelector<T extends string | Big, R extends E
protected _attr: R | undefined;
private selection?: SelectionSingleValue;

constructor() {
constructor(props: { filterInputDebounceInterval: number }) {
this.caption = new DatabaseCaptionsProvider(this._objectsMap);
this.options = new DatabaseOptionsProvider(this.caption, this._objectsMap);
this.options = new DatabaseOptionsProvider(this.caption, this._objectsMap, props.filterInputDebounceInterval);
this.values = new DatabaseValuesProvider(this._objectsMap);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ type ExtractionReturnValue = {
lazyLoading: boolean;
loadingType: LoadingTypeEnum;
valueSourceAttribute: ListAttributeValue<string | Big> | undefined;
filterInputDebounceInterval: number;
};

export function extractDatabaseProps(props: ComboboxContainerProps): ExtractionReturnValue {
const targetAttribute = props.databaseAttributeString;
const filterType = props.filterType;

const filterInputDebounceInterval = props.filterInputDebounceInterval;
const ds = props.optionsSourceDatabaseDataSource;
if (!ds) {
throw new Error("'optionsSourceType' type is 'database' but 'optionsSourceDatabaseDataSource' is not defined.");
Expand Down Expand Up @@ -83,7 +84,8 @@ export function extractDatabaseProps(props: ComboboxContainerProps): ExtractionR
filterType,
lazyLoading,
loadingType,
valueSourceAttribute
valueSourceAttribute,
filterInputDebounceInterval
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,20 @@ export function getSelector(props: ComboboxContainerProps): Selector {
return new EnumBooleanSingleSelector();
} else if (props.optionsSourceType === "association") {
return props.attributeAssociation.type === "Reference"
? new AssociationSingleSelector()
: new AssociationMultiSelector();
? new AssociationSingleSelector({ filterInputDebounceInterval: props.filterInputDebounceInterval })
: new AssociationMultiSelector({ filterInputDebounceInterval: props.filterInputDebounceInterval });
} else {
throw new Error(`'optionsSourceType' of type '${props.optionsSourceType}' is not supported`);
}
} else if (props.source === "database") {
if (props.optionsSourceDatabaseItemSelection?.type === "Multi") {
return new DatabaseMultiSelectionSelector();
return new DatabaseMultiSelectionSelector({
filterInputDebounceInterval: props.filterInputDebounceInterval
});
} else {
return new DatabaseSingleSelectionSelector();
return new DatabaseSingleSelectionSelector({
filterInputDebounceInterval: props.filterInputDebounceInterval
});
}
} else if (props.source === "static") {
return new StaticSingleSelector();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ export interface ComboboxContainerProps {
onEnterEvent?: ActionValue;
onLeaveEvent?: ActionValue;
onChangeFilterInputEvent?: ActionValue<{ filterInput: Option<string> }>;
filterInputDebounceInterval: number;
ariaRequired: DynamicValue<boolean>;
ariaLabel?: DynamicValue<string>;
clearButtonAriaLabel?: DynamicValue<string>;
Expand All @@ -102,6 +101,7 @@ export interface ComboboxContainerProps {
loadingType: LoadingTypeEnum;
selectedItemsSorting: SelectedItemsSortingEnum;
filterType: FilterTypeEnum;
filterInputDebounceInterval: number;
}

export interface ComboboxPreviewProps {
Expand Down Expand Up @@ -148,7 +148,6 @@ export interface ComboboxPreviewProps {
onEnterEvent: {} | null;
onLeaveEvent: {} | null;
onChangeFilterInputEvent: {} | null;
filterInputDebounceInterval: number | null;
ariaRequired: string;
ariaLabel: string;
clearButtonAriaLabel: string;
Expand All @@ -160,4 +159,5 @@ export interface ComboboxPreviewProps {
loadingType: LoadingTypeEnum;
selectedItemsSorting: SelectedItemsSortingEnum;
filterType: FilterTypeEnum;
filterInputDebounceInterval: number | null;
}
Loading