>,
) => (
- <>
+
{options.length ? (
) : null}
{menu}
- >
+
),
- [isSelectAll, options],
+ [isSelectAll, options, loading],
);
const filterOption = useCallback(
@@ -97,6 +105,16 @@ function MultiSelectComponent({
option?.props.value.toLowerCase().indexOf(input.toLowerCase()) >= 0,
[],
);
+
+ const onClose = useCallback((open) => !open && onFilterChange(""), []);
+
+ const serverSideSearch = React.useMemo(() => {
+ const updateFilter = (filterValue: string) => {
+ onFilterChange(filterValue);
+ };
+ return debounce(updateFilter, DEBOUNCE_TIMEOUT);
+ }, []);
+
return (
}>
@@ -110,7 +128,7 @@ function MultiSelectComponent({
dropdownClassName="multi-select-dropdown"
dropdownRender={dropdownRender}
dropdownStyle={dropdownStyle}
- filterOption={filterOption}
+ filterOption={serverSideFiltering ? false : filterOption}
getPopupContainer={() => getDropdownPosition(_menu.current)}
inputIcon={inputIcon}
loading={loading}
@@ -120,6 +138,8 @@ function MultiSelectComponent({
mode="multiple"
notFoundContent="No item Found"
onChange={onChange}
+ onDropdownVisibleChange={onClose}
+ onSearch={serverSideSearch}
options={options}
placeholder={placeholder || "select option(s)"}
showArrow
diff --git a/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx b/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx
index 01cd8e0676..5b70858116 100644
--- a/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx
+++ b/app/client/src/components/designSystems/blueprint/DropdownComponent.tsx
@@ -145,6 +145,7 @@ const DropdownStyles = createGlobalStyle`
const DropdownContainer = styled.div`
${BlueprintCSSTransform}
`;
+const DEBOUNCE_TIMEOUT = 800;
class DropDownComponent extends React.Component {
render() {
@@ -170,10 +171,17 @@ class DropDownComponent extends React.Component {
className={this.props.isLoading ? Classes.SKELETON : ""}
disabled={this.props.disabled}
filterable={this.props.isFilterable}
- itemListPredicate={this.itemListPredicate}
+ itemListPredicate={
+ !this.props.serverSideFiltering
+ ? this.itemListPredicate
+ : undefined
+ }
itemRenderer={this.renderSingleSelectItem}
items={this.props.options}
onItemSelect={this.onItemSelect}
+ onQueryChange={
+ this.props.serverSideFiltering ? this.serverSideSearch : undefined
+ }
popoverProps={{
boundary: "window",
minimal: true,
@@ -218,6 +226,9 @@ class DropDownComponent extends React.Component {
});
return optionIndex === this.props.selectedIndex;
};
+ serverSideSearch = _.debounce((filterValue: string) => {
+ this.props.onFilterChange(filterValue);
+ }, DEBOUNCE_TIMEOUT);
renderSingleSelectItem = (
option: DropdownOption,
@@ -250,6 +261,8 @@ export interface DropDownComponentProps extends ComponentProps {
isFilterable: boolean;
width: number;
height: number;
+ serverSideFiltering: boolean;
+ onFilterChange: (text: string) => void;
}
export default DropDownComponent;
diff --git a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx
index 7f8448f4a2..8525e304f1 100644
--- a/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx
+++ b/app/client/src/constants/AppsmithActionConstants/ActionConstants.tsx
@@ -65,6 +65,8 @@ export enum EventType {
ON_DATE_SELECTED = "ON_DATE_SELECTED",
ON_DATE_RANGE_SELECTED = "ON_DATE_RANGE_SELECTED",
ON_OPTION_CHANGE = "ON_OPTION_CHANGE",
+ ON_FILTER_CHANGE = "ON_FILTER_CHANGE",
+ ON_FILTER_UPDATE = "ON_FILTER_UPDATE",
ON_MARKER_CLICK = "ON_MARKER_CLICK",
ON_CREATE_MARKER = "ON_CREATE_MARKER",
ON_TAB_CHANGE = "ON_TAB_CHANGE",
diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx
index caa3cb6018..aa0e2477ba 100644
--- a/app/client/src/mockResponses/WidgetConfigResponse.tsx
+++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx
@@ -328,6 +328,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
{ label: "Green", value: "GREEN" },
{ label: "Red", value: "RED" },
],
+ serverSideFiltering: false,
widgetName: "Select",
defaultOptionValue: "GREEN",
version: 1,
@@ -349,6 +350,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
{ label: "Naruto Uzumaki", value: "Seventh" },
],
widgetName: "MultiSelect",
+ serverSideFiltering: false,
defaultOptionValue: ["First", "Seventh"],
version: 1,
isRequired: false,
diff --git a/app/client/src/utils/autocomplete/EntityDefinitions.ts b/app/client/src/utils/autocomplete/EntityDefinitions.ts
index c61457f913..1d8fc0dca1 100644
--- a/app/client/src/utils/autocomplete/EntityDefinitions.ts
+++ b/app/client/src/utils/autocomplete/EntityDefinitions.ts
@@ -83,6 +83,10 @@ export const entityDefinitions = {
"Select is used to capture user input/s from a specified list of permitted inputs. A Select can capture a single choice as well as multiple choices",
"!url": "https://docs.appsmith.com/widget-reference/dropdown",
isVisible: isVisible,
+ filterText: {
+ "!type": "[string]",
+ "!doc": "The filter text for Server side filtering",
+ },
selectedOptionValue: {
"!type": "string",
"!doc": "The value selected in a single select dropdown",
@@ -111,6 +115,10 @@ export const entityDefinitions = {
"MultiSelect is used to capture user input/s from a specified list of permitted inputs. A MultiSelect captures multiple choices from a list of options",
"!url": "https://docs.appsmith.com/widget-reference/dropdown",
isVisible: isVisible,
+ filterText: {
+ "!type": "[string]",
+ "!doc": "The filter text for Server side filtering",
+ },
selectedOptionValues: {
"!type": "[string]",
"!doc": "The array of values selected in a multi select dropdown",
diff --git a/app/client/src/widgets/DropdownWidget.tsx b/app/client/src/widgets/DropdownWidget.tsx
index 42ceb98ba7..b83b2b24d3 100644
--- a/app/client/src/widgets/DropdownWidget.tsx
+++ b/app/client/src/widgets/DropdownWidget.tsx
@@ -134,6 +134,16 @@ class DropdownWidget extends BaseWidget {
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
+ {
+ helpText: "Enables server side filtering of the data",
+ propertyName: "serverSideFiltering",
+ label: "Server Side Filtering",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.BOOLEAN },
+ },
],
},
{
@@ -148,6 +158,17 @@ class DropdownWidget extends BaseWidget {
isBindProperty: true,
isTriggerProperty: true,
},
+ {
+ helpText: "Trigger an action on change of filterText",
+ hidden: (props: DropdownWidgetProps) => !props.serverSideFiltering,
+ dependencies: ["serverSideFiltering"],
+ propertyName: "onFilterUpdate",
+ label: "onFilterUpdate",
+ controlType: "ACTION_SELECTOR",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: true,
+ },
],
},
];
@@ -189,10 +210,12 @@ class DropdownWidget extends BaseWidget {
isFilterable={this.props.isFilterable}
isLoading={this.props.isLoading}
label={`${this.props.label}`}
+ onFilterChange={this.onFilterChange}
onOptionSelected={this.onOptionSelected}
options={options}
placeholder={this.props.placeholderText}
selectedIndex={selectedIndex > -1 ? selectedIndex : undefined}
+ serverSideFiltering={this.props.serverSideFiltering}
widgetId={this.props.widgetId}
width={componentWidth}
/>
@@ -223,6 +246,18 @@ class DropdownWidget extends BaseWidget {
}
};
+ onFilterChange = (value: string) => {
+ this.props.updateWidgetMetaProperty("filterText", value);
+
+ super.executeAction({
+ triggerPropertyName: "onFilterUpdate",
+ dynamicString: this.props.onFilterUpdate,
+ event: {
+ type: EventType.ON_FILTER_UPDATE,
+ },
+ });
+ };
+
getWidgetType(): WidgetType {
return "DROP_DOWN_WIDGET";
}
@@ -251,6 +286,8 @@ export interface DropdownWidgetProps extends WidgetProps, WithMeta {
isFilterable: boolean;
defaultValue: string;
selectedOptionLabel: string;
+ serverSideFiltering: boolean;
+ onFilterUpdate: string;
}
export default DropdownWidget;
diff --git a/app/client/src/widgets/MultiSelectWidget.tsx b/app/client/src/widgets/MultiSelectWidget.tsx
index b8775c0496..787364b245 100644
--- a/app/client/src/widgets/MultiSelectWidget.tsx
+++ b/app/client/src/widgets/MultiSelectWidget.tsx
@@ -151,6 +151,16 @@ class MultiSelectWidget extends BaseWidget<
isTriggerProperty: false,
validation: { type: ValidationTypes.BOOLEAN },
},
+ {
+ helpText: "Enables server side filtering of the data",
+ propertyName: "serverSideFiltering",
+ label: "Server Side Filtering",
+ controlType: "SWITCH",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: false,
+ validation: { type: ValidationTypes.BOOLEAN },
+ },
],
},
{
@@ -165,6 +175,18 @@ class MultiSelectWidget extends BaseWidget<
isBindProperty: true,
isTriggerProperty: true,
},
+ {
+ helpText: "Trigger an action on change of filterText",
+ hidden: (props: MultiSelectWidgetProps) =>
+ !props.serverSideFiltering,
+ dependencies: ["serverSideFiltering"],
+ propertyName: "onFilterUpdate",
+ label: "onFilterUpdate",
+ controlType: "ACTION_SELECTOR",
+ isJSConvertible: true,
+ isBindProperty: true,
+ isTriggerProperty: true,
+ },
],
},
];
@@ -182,12 +204,14 @@ class MultiSelectWidget extends BaseWidget<
static getDefaultPropertiesMap(): Record {
return {
selectedOptionValueArr: "defaultOptionValue",
+ filterText: "",
};
}
static getMetaPropertiesMap(): Record {
return {
selectedOptionValueArr: undefined,
+ filterText: "",
};
}
@@ -196,7 +220,6 @@ class MultiSelectWidget extends BaseWidget<
const values: string[] = isArray(this.props.selectedOptionValues)
? this.props.selectedOptionValues
: [];
-
return (
);
@@ -220,6 +245,21 @@ class MultiSelectWidget extends BaseWidget<
type: EventType.ON_OPTION_CHANGE,
},
});
+
+ // Empty filter after Selection
+ this.onFilterChange("");
+ };
+
+ onFilterChange = (value: string) => {
+ this.props.updateWidgetMetaProperty("filterText", value);
+
+ super.executeAction({
+ triggerPropertyName: "onFilterUpdate",
+ dynamicString: this.props.onFilterUpdate,
+ event: {
+ type: EventType.ON_FILTER_UPDATE,
+ },
+ });
};
getWidgetType(): WidgetType {
@@ -240,12 +280,16 @@ export interface MultiSelectWidgetProps extends WidgetProps, WithMeta {
selectedOption: DropdownOption;
options?: DropdownOption[];
onOptionChange: string;
+ onFilterChange: string;
defaultOptionValue: string | string[];
isRequired: boolean;
isLoading: boolean;
selectedOptionValueArr: string[];
+ filterText: string;
selectedOptionValues: string[];
selectedOptionLabels: string[];
+ serverSideFiltering: boolean;
+ onFilterUpdate: string;
}
export default MultiSelectWidget;