Skip to content

Commit 99c0014

Browse files
committed
chore: start migrating dd filter
1 parent 3167898 commit 99c0014

14 files changed

+371
-360
lines changed

packages/pluggableWidgets/datagrid-dropdown-filter-web/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"verify": "rui-verify-package-format"
4242
},
4343
"dependencies": {
44+
"@mendix/widget-plugin-dropdown-filter": "workspace:^",
4445
"@mendix/widget-plugin-external-events": "workspace:*",
4546
"@mendix/widget-plugin-filtering": "workspace:*",
4647
"classnames": "^2.3.2"

packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml

+7
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@
7474
<caption>Selectable objects</caption>
7575
<description>The options to show in the Drop-down filter widget.</description>
7676
</property>
77+
<property key="refCaption" type="attribute" dataSource="refOptions" required="false">
78+
<caption>Caption</caption>
79+
<description />
80+
<attributeTypes>
81+
<attributeType name="String" />
82+
</attributeTypes>
83+
</property>
7784
<property key="fetchOptionsLazy" type="boolean" defaultValue="false">
7885
<caption>Use lazy load</caption>
7986
<description>Lazy loading enables faster parent loading, but with personalization enabled, value restoration will be limited.</description>

packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/RefFilterContainer.tsx

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { RefSelectController } from "@mendix/widget-plugin-filtering/controllers/picker/RefSelectController";
2-
import { RefComboboxController } from "@mendix/widget-plugin-filtering/controllers/picker/RefComboboxController";
3-
import { RefTagPickerController } from "@mendix/widget-plugin-filtering/controllers/picker/RefTagPickerController";
1+
import { RefSelectController } from "@mendix/widget-plugin-dropdown-filter/controllers/RefSelectController";
2+
import { RefComboboxController } from "@mendix/widget-plugin-dropdown-filter/controllers/RefComboboxController";
3+
import { RefTagPickerController } from "@mendix/widget-plugin-dropdown-filter/controllers/RefTagPickerController";
44
import { Select } from "@mendix/widget-plugin-filtering/controls/select/Select";
55
import { Combobox } from "@mendix/widget-plugin-filtering/controls/combobox/Combobox";
66
import { TagPicker } from "@mendix/widget-plugin-filtering/controls/tag-picker/TagPicker";

packages/pluggableWidgets/datagrid-dropdown-filter-web/typings/DatagridDropdownFilterProps.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export interface DatagridDropdownFilterContainerProps {
3636
filterOptions: FilterOptionsType[];
3737
ref?: ListReferenceValue | ListReferenceSetValue;
3838
refOptions?: ListValue;
39+
refCaption?: ListAttributeValue<string>;
3940
fetchOptionsLazy: boolean;
4041
defaultValue?: DynamicValue<string>;
4142
filterable: boolean;
@@ -67,6 +68,7 @@ export interface DatagridDropdownFilterPreviewProps {
6768
filterOptions: FilterOptionsPreviewType[];
6869
ref: string;
6970
refOptions: {} | { caption: string } | { type: string } | null;
71+
refCaption: string;
7072
fetchOptionsLazy: boolean;
7173
defaultValue: string;
7274
filterable: boolean;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { ComboboxControllerMixin } from "./mixins/ComboboxControllerMixin";
2+
import { RefBaseController, RefBaseControllerProps } from "./RefBaseController";
3+
4+
export class RefComboboxController extends ComboboxControllerMixin(RefBaseController) {
5+
constructor(props: RefBaseControllerProps) {
6+
super({ ...props, multiselect: false });
7+
this.inputPlaceholder = props.placeholder ?? "Search";
8+
}
9+
10+
handleFocus = (event: React.FocusEvent<HTMLInputElement>): void => {
11+
super.handleFocus(event);
12+
this.filterStore.setFetchReady(true);
13+
};
14+
15+
handleMenuScrollEnd = (): void => {
16+
this.filterStore.loadMore();
17+
};
18+
}

packages/shared/widget-plugin-dropdown-filter/src/controllers/RefSelectController.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { RefBaseController, RefBaseControllerProps } from "./RefBaseController";
2-
import { SelectControllerMixin } from "./SelectControllerMixin";
2+
import { SelectControllerMixin } from "./mixins/SelectControllerMixin";
33

44
export class RefSelectController extends SelectControllerMixin(RefBaseController) {
55
constructor(props: RefBaseControllerProps) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { RefBaseController, RefBaseControllerProps } from "./RefBaseController";
2+
import { TagPickerControllerMixin } from "./mixins/TagPickerControllerMixin";
3+
4+
type SelectionMethodEnum = "checkbox" | "rowClick";
5+
type SelectedItemsStyleEnum = "text" | "boxes";
6+
7+
interface Props extends RefBaseControllerProps {
8+
selectionMethod: SelectionMethodEnum;
9+
selectedItemsStyle: SelectedItemsStyleEnum;
10+
}
11+
12+
export class RefTagPickerController extends TagPickerControllerMixin(RefBaseController) {
13+
selectionMethod: SelectionMethodEnum;
14+
selectedStyle: SelectedItemsStyleEnum;
15+
16+
constructor(props: Props) {
17+
super(props);
18+
this.inputPlaceholder = props.placeholder ?? "Search";
19+
this.filterSelectedOptions = props.selectionMethod === "rowClick";
20+
this.selectedStyle = props.selectedItemsStyle;
21+
this.selectionMethod = this.selectedStyle === "boxes" ? props.selectionMethod : "checkbox";
22+
}
23+
24+
handleFocus = (): void => {
25+
this.filterStore.setFetchReady(true);
26+
};
27+
28+
handleMenuScrollEnd = (): void => {
29+
this.filterStore.loadMore();
30+
};
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { StaticBaseController, StaticBaseControllerProps } from "./StaticBaseController";
2+
import { ComboboxControllerMixin } from "./mixins/ComboboxControllerMixin";
3+
4+
export class StaticComboboxController extends ComboboxControllerMixin(StaticBaseController) {
5+
constructor(props: StaticBaseControllerProps) {
6+
super({ ...props, multiselect: false });
7+
this.inputPlaceholder = props.placeholder ?? "Search";
8+
}
9+
}

packages/shared/widget-plugin-dropdown-filter/src/controllers/StaticSelectController.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SelectControllerMixin } from "./SelectControllerMixin";
1+
import { SelectControllerMixin } from "./mixins/SelectControllerMixin";
22
import { StaticBaseController, StaticBaseControllerProps } from "./StaticBaseController";
33

44
export class StaticSelectController extends SelectControllerMixin(StaticBaseController) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { StaticBaseController, StaticBaseControllerProps } from "./StaticBaseController";
2+
import { TagPickerControllerMixin } from "./mixins/TagPickerControllerMixin";
3+
4+
type SelectionMethodEnum = "checkbox" | "rowClick";
5+
type SelectedItemsStyleEnum = "text" | "boxes";
6+
7+
interface Props extends StaticBaseControllerProps {
8+
selectionMethod: SelectionMethodEnum;
9+
selectedItemsStyle: SelectedItemsStyleEnum;
10+
}
11+
12+
export class StaticTagPickerController extends TagPickerControllerMixin(StaticBaseController) {
13+
selectionMethod: SelectionMethodEnum;
14+
selectedStyle: SelectedItemsStyleEnum;
15+
16+
constructor(props: Props) {
17+
super(props);
18+
this.inputPlaceholder = props.placeholder ?? "Search";
19+
this.filterSelectedOptions = props.selectionMethod === "rowClick";
20+
this.selectedStyle = props.selectedItemsStyle;
21+
this.selectionMethod = this.selectedStyle === "boxes" ? props.selectionMethod : "checkbox";
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch";
2+
import { useCombobox, UseComboboxProps } from "downshift";
3+
import { action, autorun, computed, makeObservable, observable, reaction } from "mobx";
4+
import { SearchStore } from "../../stores/SearchStore";
5+
import { OptionWithState } from "../../typings/OptionWithState";
6+
import { GConstructor } from "../../typings/type-utils";
7+
8+
export interface FilterStore {
9+
clear: () => void;
10+
setSelected: (value: Iterable<string>) => void;
11+
selected: Set<string>;
12+
options: OptionWithState[];
13+
selectedOptions: OptionWithState[];
14+
search: SearchStore;
15+
}
16+
17+
type BaseController = GConstructor<{
18+
filterStore: FilterStore;
19+
multiselect: boolean;
20+
setup(): () => void;
21+
}>;
22+
23+
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
24+
export function ComboboxControllerMixin<TBase extends BaseController>(Base: TBase) {
25+
return class ComboboxControllerMixin extends Base {
26+
touched = false;
27+
inputValue = "";
28+
inputPlaceholder = "";
29+
30+
constructor(...args: any[]) {
31+
super(...args);
32+
33+
makeObservable(this, {
34+
inputValue: observable,
35+
setInputValue: action,
36+
touched: observable,
37+
setTouched: action,
38+
selectedIndex: computed,
39+
selectedOption: computed,
40+
isEmpty: computed,
41+
options: computed,
42+
handleBlur: action,
43+
handleClear: action
44+
});
45+
}
46+
47+
setup(): () => void {
48+
const [add, dispose] = disposeBatch();
49+
add(autorun(...this.searchSyncFx()));
50+
51+
// Set input when store state changes
52+
add(reaction(...this.storeSyncFx()));
53+
54+
add(super.setup());
55+
this.setInputValue(this.inputInitValue);
56+
return dispose;
57+
}
58+
59+
searchSyncFx(): Parameters<typeof autorun> {
60+
const effect = (): void => {
61+
const { touched, inputValue } = this;
62+
if (touched) {
63+
this.filterStore.search.setBuffer(inputValue);
64+
} else {
65+
this.filterStore.search.clear();
66+
}
67+
};
68+
69+
return [effect];
70+
}
71+
72+
storeSyncFx(): Parameters<typeof reaction> {
73+
const data = (): string => this.selectedOption?.caption ?? "";
74+
const effect = (caption: string): void => {
75+
if (!this.touched) {
76+
this.setInputValue(caption);
77+
}
78+
};
79+
return [data, effect];
80+
}
81+
82+
get options(): OptionWithState[] {
83+
return this.filterStore.options;
84+
}
85+
86+
get isEmpty(): boolean {
87+
return this.filterStore.selected.size === 0;
88+
}
89+
90+
get selectedIndex(): number {
91+
const index = this.filterStore.options.findIndex(option => option.selected);
92+
return Math.max(index, 0);
93+
}
94+
95+
get selectedOption(): OptionWithState | null {
96+
return this.filterStore.selectedOptions.at(0) ?? null;
97+
}
98+
99+
get inputInitValue(): string {
100+
if (this.selectedOption) {
101+
return this.selectedOption.caption;
102+
}
103+
if (this.filterStore.selected.size === 0) {
104+
return "";
105+
} else {
106+
return "1 item selected (but not applied)";
107+
}
108+
}
109+
110+
setTouched(value: boolean): void {
111+
this.touched = value;
112+
}
113+
114+
setInputValue(value: string): void {
115+
this.inputValue = value;
116+
}
117+
118+
handleFocus(event: React.FocusEvent<HTMLInputElement>): void {
119+
event.target.select();
120+
}
121+
122+
handleBlur = (): void => {
123+
this.setTouched(false);
124+
this.setInputValue(this.selectedOption?.caption ?? "");
125+
this.filterStore.search.clear();
126+
};
127+
128+
handleClear = (): void => {
129+
this.setTouched(false);
130+
this.setInputValue("");
131+
this.filterStore.clear();
132+
};
133+
134+
useComboboxProps = (): UseComboboxProps<OptionWithState> => {
135+
const props: UseComboboxProps<OptionWithState> = {
136+
items: this.filterStore.options,
137+
itemToKey: item => item?.value,
138+
itemToString: item => item?.caption ?? "",
139+
inputValue: this.inputValue,
140+
defaultHighlightedIndex: this.selectedIndex,
141+
onInputValueChange: changes => {
142+
// Blur is handled by handleBlur;
143+
if (changes.type === useCombobox.stateChangeTypes.InputBlur) {
144+
return;
145+
}
146+
if (changes.type === useCombobox.stateChangeTypes.InputKeyDownEscape) {
147+
this.handleClear();
148+
return;
149+
}
150+
if (changes.type === useCombobox.stateChangeTypes.InputChange) {
151+
this.setTouched(true);
152+
}
153+
this.setInputValue(changes.inputValue);
154+
},
155+
onSelectedItemChange: ({ selectedItem, type }) => {
156+
if (
157+
type === useCombobox.stateChangeTypes.InputBlur ||
158+
type === useCombobox.stateChangeTypes.InputKeyDownEscape
159+
) {
160+
return;
161+
}
162+
163+
this.setTouched(false);
164+
this.filterStore.setSelected(selectedItem ? [selectedItem.value] : []);
165+
},
166+
stateReducer(state, { changes }) {
167+
return {
168+
...changes,
169+
highlightedIndex: changes.inputValue !== state.inputValue ? 0 : changes.highlightedIndex
170+
};
171+
}
172+
};
173+
return props;
174+
};
175+
};
176+
}

packages/shared/widget-plugin-dropdown-filter/src/controllers/SelectControllerMixin.ts renamed to packages/shared/widget-plugin-dropdown-filter/src/controllers/mixins/SelectControllerMixin.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useSelect, UseSelectProps } from "downshift";
22
import { action, computed, makeObservable } from "mobx";
3-
import { OptionWithState } from "../typings/OptionWithState";
4-
import { GConstructor } from "../typings/type-utils";
3+
import { OptionWithState } from "../../typings/OptionWithState";
4+
import { GConstructor } from "../../typings/type-utils";
55

66
export interface FilterStore {
77
toggle: (value: string) => void;
@@ -19,7 +19,6 @@ type BaseController = GConstructor<{
1919

2020
const none = "[[__none__]]" as const;
2121

22-
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
2322
export function SelectControllerMixin<TBase extends BaseController>(Base: TBase) {
2423
return class SelectControllerMixin extends Base {
2524
placeholder = "Select";

0 commit comments

Comments
 (0)