(
headerFilterStore.context
);
});
diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateFilterAPI.tsx b/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateFilterAPI.tsx
index 38b85272f1..38f9c77860 100644
--- a/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateFilterAPI.tsx
+++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateFilterAPI.tsx
@@ -6,7 +6,7 @@ export function withDateFilterAPI(
Component: (props: P & Date_FilterAPIv2) => React.ReactElement
): (props: P) => React.ReactElement {
return function FilterAPIProvider(props) {
- const api = useDateFilterAPI("");
+ const api = useDateFilterAPI();
if (api.hasError) {
return {api.error.message};
diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateLinkedAttributes.tsx b/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateLinkedAttributes.tsx
new file mode 100644
index 0000000000..abd3175921
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/hocs/withDateLinkedAttributes.tsx
@@ -0,0 +1,71 @@
+import { createElement } from "react";
+import { AttributeMetaData } from "mendix";
+import { useFilterAPI } from "@mendix/widget-plugin-filtering/context";
+import { APIError } from "@mendix/widget-plugin-filtering/errors";
+import { error, value, Result } from "@mendix/widget-plugin-filtering/result-meta";
+import { Date_InputFilterInterface } from "@mendix/widget-plugin-filtering/typings/InputFilterInterface";
+import { Alert } from "@mendix/widget-plugin-component-kit/Alert";
+import { useConst } from "@mendix/widget-plugin-mobx-kit/react/useConst";
+import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup";
+import { DateStoreProvider } from "@mendix/widget-plugin-filtering/custom-filter-api/DateStoreProvider";
+import { ISetupable } from "@mendix/widget-plugin-mobx-kit/setupable";
+
+interface RequiredProps {
+ attributes: Array<{
+ attribute: AttributeMetaData;
+ }>;
+ name: string;
+}
+
+interface StoreProvider extends ISetupable {
+ store: Date_InputFilterInterface;
+}
+
+type Component = (props: P) => React.ReactElement;
+
+export function withDateLinkedAttributes
(
+ component: Component
+): Component
{
+ const StoreInjector = withInjectedStore(component);
+
+ return function FilterAPIProvider(props) {
+ const api = useStoreProvider(props);
+
+ if (api.hasError) {
+ return {api.error.message};
+ }
+
+ return ;
+ };
+}
+
+function withInjectedStore
(
+ Component: Component
+): Component
{
+ return function StoreInjector(props) {
+ const provider = useSetup(() => props.provider);
+ return ;
+ };
+}
+
+interface InjectableFilterAPI {
+ filterStore: Date_InputFilterInterface;
+ parentChannelName?: string;
+}
+
+function useStoreProvider(props: RequiredProps): Result<{ provider: StoreProvider; channel: string }, APIError> {
+ const filterAPI = useFilterAPI();
+ return useConst(() => {
+ if (filterAPI.hasError) {
+ return error(filterAPI.error);
+ }
+
+ return value({
+ provider: new DateStoreProvider(filterAPI.value, {
+ attributes: props.attributes.map(obj => obj.attribute),
+ dataKey: props.name
+ }),
+ channel: filterAPI.value.parentChannelName
+ });
+ });
+}
diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/typings/DatagridDateFilterProps.d.ts b/packages/pluggableWidgets/datagrid-date-filter-web/typings/DatagridDateFilterProps.d.ts
index 2855ea37b7..970ae14f7d 100644
--- a/packages/pluggableWidgets/datagrid-date-filter-web/typings/DatagridDateFilterProps.d.ts
+++ b/packages/pluggableWidgets/datagrid-date-filter-web/typings/DatagridDateFilterProps.d.ts
@@ -4,16 +4,27 @@
* @author Mendix Widgets Framework Team
*/
import { CSSProperties } from "react";
-import { ActionValue, DynamicValue, EditableValue } from "mendix";
+import { ActionValue, AttributeMetaData, DynamicValue, EditableValue } from "mendix";
+
+export type AttrChoiceEnum = "auto" | "linked";
+
+export interface AttributesType {
+ attribute: AttributeMetaData;
+}
export type DefaultFilterEnum = "between" | "greater" | "greaterEqual" | "equal" | "notEqual" | "smaller" | "smallerEqual" | "empty" | "notEmpty";
+export interface AttributesPreviewType {
+ attribute: string;
+}
+
export interface DatagridDateFilterContainerProps {
name: string;
class: string;
style?: CSSProperties;
tabIndex?: number;
- advanced: boolean;
+ attrChoice: AttrChoiceEnum;
+ attributes: AttributesType[];
defaultValue?: DynamicValue;
defaultStartDate?: DynamicValue;
defaultEndDate?: DynamicValue;
@@ -40,7 +51,8 @@ export interface DatagridDateFilterPreviewProps {
readOnly: boolean;
renderMode: "design" | "xray" | "structure";
translate: (text: string) => string;
- advanced: boolean;
+ attrChoice: AttrChoiceEnum;
+ attributes: AttributesPreviewType[];
defaultValue: string;
defaultStartDate: string;
defaultEndDate: string;
diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/DataGridDropdownFilter.spec.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/DataGridDropdownFilter.spec.tsx
index 2306dd4a38..784ade5f13 100644
--- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/DataGridDropdownFilter.spec.tsx
+++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/DataGridDropdownFilter.spec.tsx
@@ -1,18 +1,14 @@
import "@testing-library/jest-dom";
-import { FilterAPIv2 } from "@mendix/widget-plugin-filtering/context";
+import { FilterAPI } from "@mendix/widget-plugin-filtering/context";
import {
HeaderFiltersStore,
- HeaderFiltersStoreProps
+ HeaderFiltersStoreSpec
} from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore";
import { dynamicValue, ListAttributeValueBuilder } from "@mendix/widget-plugin-test-utils";
import { createContext, createElement } from "react";
import DatagridDropdownFilter from "../../DatagridDropdownFilter";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
-
-interface StaticInfo {
- name: string;
- filtersChannelName: string;
-}
+import { FilterObserver } from "@mendix/widget-plugin-filtering/typings/FilterObserver";
const commonProps = {
class: "filter-custom-class",
@@ -26,10 +22,14 @@ const commonProps = {
selectedItemsStyle: "text" as const
};
-const headerFilterStoreInfo: StaticInfo = {
- name: commonProps.name,
- filtersChannelName: ""
-};
+const mockSpec = (spec: Partial): HeaderFiltersStoreSpec => ({
+ filterList: [],
+ filterChannelName: "datagrid/1",
+ headerInitFilter: [],
+ sharedInitFilter: [],
+ customFilterHost: {} as FilterObserver,
+ ...spec
+});
const consoleError = global.console.error;
jest.spyOn(global.console, "error").mockImplementation((...args: any[]) => {
@@ -50,7 +50,7 @@ describe("Dropdown Filter", () => {
describe("with single attribute", () => {
function mockCtx(universe: string[]): void {
- const props: HeaderFiltersStoreProps = {
+ const spec = mockSpec({
filterList: [
{
filter: new ListAttributeValueBuilder()
@@ -64,9 +64,9 @@ describe("Dropdown Filter", () => {
.build()
}
]
- };
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ });
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
}
@@ -207,7 +207,7 @@ describe("Dropdown Filter", () => {
describe("with multiple attributes", () => {
beforeAll(() => {
- const props: HeaderFiltersStoreProps = {
+ const spec = mockSpec({
filterList: [
{
filter: new ListAttributeValueBuilder()
@@ -234,9 +234,9 @@ describe("Dropdown Filter", () => {
.build()
}
]
- };
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ });
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
@@ -267,15 +267,15 @@ describe("Dropdown Filter", () => {
describe("with wrong attribute's type", () => {
beforeAll(() => {
- const props: HeaderFiltersStoreProps = {
+ const spec = mockSpec({
filterList: [
{
filter: new ListAttributeValueBuilder().withType("String").withFilterable(true).build()
}
]
- };
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ });
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
@@ -297,7 +297,7 @@ describe("Dropdown Filter", () => {
describe("with wrong multiple attributes' types", () => {
beforeAll(() => {
- const props: HeaderFiltersStoreProps = {
+ const spec = mockSpec({
filterList: [
{
filter: new ListAttributeValueBuilder()
@@ -314,9 +314,9 @@ describe("Dropdown Filter", () => {
.build()
}
]
- };
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ });
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
@@ -354,7 +354,7 @@ describe("Dropdown Filter", () => {
describe("with invalid values", () => {
beforeAll(() => {
- const props: HeaderFiltersStoreProps = {
+ const spec = mockSpec({
filterList: [
{
filter: new ListAttributeValueBuilder()
@@ -364,9 +364,9 @@ describe("Dropdown Filter", () => {
.build()
}
]
- };
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ });
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
@@ -392,7 +392,7 @@ describe("Dropdown Filter", () => {
describe("with multiple invalid values", () => {
beforeAll(() => {
- const props: HeaderFiltersStoreProps = {
+ const spec = mockSpec({
filterList: [
{
filter: new ListAttributeValueBuilder()
@@ -409,9 +409,9 @@ describe("Dropdown Filter", () => {
.build()
}
]
- };
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ });
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
@@ -442,7 +442,7 @@ describe("Dropdown Filter", () => {
describe("with multiple instances", () => {
beforeAll(() => {
- const props: HeaderFiltersStoreProps = {
+ const spec = mockSpec({
filterList: [
{
filter: new ListAttributeValueBuilder()
@@ -456,9 +456,9 @@ describe("Dropdown Filter", () => {
.build()
}
]
- };
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ });
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.editorConfig.ts b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.editorConfig.ts
index 4c52aa7205..a6c2f1d30f 100644
--- a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.editorConfig.ts
+++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.editorConfig.ts
@@ -1,10 +1,4 @@
-import {
- ContainerProps,
- ImageProps,
- structurePreviewPalette,
- StructurePreviewProps,
- text
-} from "@mendix/widget-plugin-platform/preview/structure-preview-api";
+import { hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools";
import {
emptyIcon,
emptyIconDark,
@@ -23,25 +17,26 @@ import {
smallerThanIcon,
smallerThanIconDark
} from "@mendix/widget-plugin-filtering/preview/editor-preview-icons";
-import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools";
+import {
+ ContainerProps,
+ ImageProps,
+ structurePreviewPalette,
+ StructurePreviewProps,
+ text
+} from "@mendix/widget-plugin-platform/preview/structure-preview-api";
import { DatagridNumberFilterPreviewProps, DefaultFilterEnum } from "../typings/DatagridNumberFilterProps";
-export function getProperties(
- values: DatagridNumberFilterPreviewProps,
- defaultProperties: Properties,
- platform: "web" | "desktop"
-): Properties {
+export function getProperties(values: DatagridNumberFilterPreviewProps, defaultProperties: Properties): Properties {
if (!values.adjustable) {
hidePropertyIn(defaultProperties, values, "screenReaderButtonCaption");
}
- if (platform === "web") {
- if (!values.advanced) {
- hidePropertiesIn(defaultProperties, values, ["onChange", "valueAttribute"]);
- }
- } else {
- hidePropertyIn(defaultProperties, values, "advanced");
+
+ if (values.attrChoice === "auto") {
+ hidePropertyIn(defaultProperties, values, "attributes");
+ hidePropertyIn(defaultProperties, {} as { linkedDs: unknown }, "linkedDs");
}
+
return defaultProperties;
}
diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.tsx b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.tsx
index f306da389c..8743b68cdd 100644
--- a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.tsx
+++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.tsx
@@ -4,10 +4,18 @@ import { DatagridNumberFilterContainerProps } from "../typings/DatagridNumberFil
import { NumberFilterContainer } from "./components/NumberFilterContainer";
import { isLoadingDefaultValues } from "./utils/widget-utils";
import { withNumberFilterAPI } from "./hocs/withNumberFilterAPI";
+import { withLinkedAttributes } from "./hocs/withLinkedAttributes";
const container = withPreloader(NumberFilterContainer, isLoadingDefaultValues);
-const Widget = withNumberFilterAPI(container);
+const FilterAuto = withNumberFilterAPI(container);
+const FilterLinked = withLinkedAttributes(container);
export default function DatagridNumberFilter(props: DatagridNumberFilterContainerProps): ReactElement {
- return ;
+ const isAuto = props.attrChoice === "auto";
+
+ if (isAuto) {
+ return ;
+ }
+
+ return ;
}
diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.xml b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.xml
index 5c024b076f..e628073200 100644
--- a/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.xml
+++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/DatagridNumberFilter.xml
@@ -8,9 +8,35 @@
-
- Enable advanced options
+
+ Filter attributes
+
+ Auto
+ Custom
+
+
+
+ Datasource to Filter
+
+
+
+ Attributes
+ Select the attributes that the end-user may use for filtering.
+
+
+
+ Attribute
+
+
+
+
+
+
+
+
+
+
Default value
diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/components/__tests__/DatagridNumberFilter.spec.tsx b/packages/pluggableWidgets/datagrid-number-filter-web/src/components/__tests__/DatagridNumberFilter.spec.tsx
index c97e9d1525..92767d5ff2 100644
--- a/packages/pluggableWidgets/datagrid-number-filter-web/src/components/__tests__/DatagridNumberFilter.spec.tsx
+++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/components/__tests__/DatagridNumberFilter.spec.tsx
@@ -1,9 +1,9 @@
import "@testing-library/jest-dom";
-import { FilterAPIv2 } from "@mendix/widget-plugin-filtering/context";
+import { FilterAPI } from "@mendix/widget-plugin-filtering/context";
import { requirePlugin } from "@mendix/widget-plugin-external-events/plugin";
import {
HeaderFiltersStore,
- HeaderFiltersStoreProps
+ HeaderFiltersStoreSpec
} from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore";
import {
actionValue,
@@ -20,11 +20,7 @@ import DatagridNumberFilter from "../../DatagridNumberFilter";
import { Big } from "big.js";
import { DatagridNumberFilterContainerProps } from "../../../typings/DatagridNumberFilterProps";
import { resetIdCounter } from "downshift";
-
-interface StaticInfo {
- name: string;
- filtersChannelName: string;
-}
+import { FilterObserver } from "@mendix/widget-plugin-filtering/typings/FilterObserver";
const commonProps: DatagridNumberFilterContainerProps = {
class: "filter-custom-class",
@@ -36,13 +32,17 @@ const commonProps: DatagridNumberFilterContainerProps = {
delay: 1000
};
-const headerFilterStoreInfo: StaticInfo = {
- name: commonProps.name,
- filtersChannelName: "datagrid1"
-};
-
jest.useFakeTimers();
+const mockSpec = (spec: Partial): HeaderFiltersStoreSpec => ({
+ filterList: [],
+ filterChannelName: "datagrid1",
+ headerInitFilter: [],
+ sharedInitFilter: [],
+ customFilterHost: {} as FilterObserver,
+ ...spec
+});
+
beforeEach(() => {
jest.spyOn(console, "warn").mockImplementation(() => {
// noop
@@ -60,23 +60,23 @@ describe("Number Filter", () => {
describe("with single attribute", () => {
beforeEach(() => {
- const props: HeaderFiltersStoreProps = {
- filterList: [
- {
- filter: new ListAttributeValueBuilder()
- .withType("Long")
- .withFormatter(
- value => (value ? value.toString() : ""),
- (value: string) => ({ valid: true, value })
- )
- .withFilterable(true)
- .build()
- }
- ],
- parentChannelName: headerFilterStoreInfo.filtersChannelName
- };
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ const headerFilterStore = new HeaderFiltersStore(
+ mockSpec({
+ filterList: [
+ {
+ filter: new ListAttributeValueBuilder()
+ .withType("Long")
+ .withFormatter(
+ value => (value ? value.toString() : ""),
+ (value: string) => ({ valid: true, value })
+ )
+ .withFilterable(true)
+ .build()
+ }
+ ]
+ })
+ );
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
@@ -194,39 +194,40 @@ describe("Number Filter", () => {
describe("with multiple attributes", () => {
beforeEach(() => {
- const props: HeaderFiltersStoreProps = {
- filterList: [
- {
- filter: new ListAttributeValueBuilder()
- .withId("attribute1")
- .withType("Long")
- .withFormatter(
- value => value,
- () => {
- // noop
- }
- )
- .withFilterable(true)
- .build()
- },
- {
- filter: new ListAttributeValueBuilder()
- .withId("attribute2")
- .withType("Decimal")
- .withFormatter(
- value => value,
- () => {
- // noop
- }
- )
- .withFilterable(true)
- .build()
- }
- ],
- parentChannelName: headerFilterStoreInfo.filtersChannelName
- };
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ const headerFilterStore = new HeaderFiltersStore(
+ mockSpec({
+ filterList: [
+ {
+ filter: new ListAttributeValueBuilder()
+ .withId("attribute1")
+ .withType("Long")
+ .withFormatter(
+ value => value,
+ () => {
+ // noop
+ }
+ )
+ .withFilterable(true)
+ .build()
+ },
+ {
+ filter: new ListAttributeValueBuilder()
+ .withId("attribute2")
+ .withType("Decimal")
+ .withFormatter(
+ value => value,
+ () => {
+ // noop
+ }
+ )
+ .withFilterable(true)
+ .build()
+ }
+ ]
+ })
+ );
+
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
@@ -296,13 +297,14 @@ describe("Number Filter", () => {
describe("with wrong attribute's type", () => {
beforeAll(() => {
- const props: HeaderFiltersStoreProps = {
+ const spec = mockSpec({
filterList: [
{ filter: new ListAttributeValueBuilder().withType("Boolean").withFilterable(true).build() }
]
- };
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ });
+
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
@@ -320,7 +322,7 @@ describe("Number Filter", () => {
describe("with wrong multiple attributes' types", () => {
beforeAll(() => {
- const props: HeaderFiltersStoreProps = {
+ const spec = mockSpec({
filterList: [
{
filter: new ListAttributeValueBuilder()
@@ -337,9 +339,10 @@ describe("Number Filter", () => {
.build()
}
]
- };
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ });
+
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
@@ -370,7 +373,7 @@ describe("Number Filter", () => {
describe("with multiple instances", () => {
beforeEach(() => {
- const props: HeaderFiltersStoreProps = {
+ const spec = mockSpec({
filterList: [
{
filter: new ListAttributeValueBuilder()
@@ -385,9 +388,9 @@ describe("Number Filter", () => {
.build()
}
]
- };
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ });
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withLinkedAttributes.tsx b/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withLinkedAttributes.tsx
new file mode 100644
index 0000000000..3ec11b8f0d
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withLinkedAttributes.tsx
@@ -0,0 +1,72 @@
+import { createElement } from "react";
+import { AttributeMetaData } from "mendix";
+import { useFilterAPI } from "@mendix/widget-plugin-filtering/context";
+import { APIError } from "@mendix/widget-plugin-filtering/errors";
+import { error, value, Result } from "@mendix/widget-plugin-filtering/result-meta";
+import { Number_InputFilterInterface } from "@mendix/widget-plugin-filtering/typings/InputFilterInterface";
+import { Alert } from "@mendix/widget-plugin-component-kit/Alert";
+import { useConst } from "@mendix/widget-plugin-mobx-kit/react/useConst";
+import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup";
+import { NumberStoreProvider } from "@mendix/widget-plugin-filtering/custom-filter-api/NumberStoreProvider";
+import { ISetupable } from "@mendix/widget-plugin-mobx-kit/setupable";
+import { Big } from "big.js";
+
+interface RequiredProps {
+ attributes: Array<{
+ attribute: AttributeMetaData;
+ }>;
+ name: string;
+}
+
+interface StoreProvider extends ISetupable {
+ store: Number_InputFilterInterface;
+}
+
+type Component = (props: P) => React.ReactElement;
+
+export function withLinkedAttributes
(
+ component: Component
+): Component
{
+ const StoreInjector = withInjectedStore(component);
+
+ return function FilterAPIProvider(props) {
+ const api = useStoreProvider(props);
+
+ if (api.hasError) {
+ return {api.error.message};
+ }
+
+ return ;
+ };
+}
+
+function withInjectedStore
(
+ Component: Component
+): Component
{
+ return function StoreInjector(props) {
+ const provider = useSetup(() => props.provider);
+ return ;
+ };
+}
+
+interface InjectableFilterAPI {
+ filterStore: Number_InputFilterInterface;
+ parentChannelName?: string;
+}
+
+function useStoreProvider(props: RequiredProps): Result<{ provider: StoreProvider; channel: string }, APIError> {
+ const filterAPI = useFilterAPI();
+ return useConst(() => {
+ if (filterAPI.hasError) {
+ return error(filterAPI.error);
+ }
+
+ return value({
+ provider: new NumberStoreProvider(filterAPI.value, {
+ attributes: props.attributes.map(obj => obj.attribute),
+ dataKey: props.name
+ }),
+ channel: filterAPI.value.parentChannelName
+ });
+ });
+}
diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withNumberFilterAPI.tsx b/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withNumberFilterAPI.tsx
index e937dfd70a..73c78450ea 100644
--- a/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withNumberFilterAPI.tsx
+++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/hocs/withNumberFilterAPI.tsx
@@ -6,7 +6,7 @@ export function withNumberFilterAPI
(
Component: (props: P & Number_FilterAPIv2) => React.ReactElement
): (props: P) => React.ReactElement {
return function FilterAPIProvider(props) {
- const api = useNumberFilterAPI("");
+ const api = useNumberFilterAPI();
if (api.hasError) {
return {api.error.message};
diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/typings/DatagridNumberFilterProps.d.ts b/packages/pluggableWidgets/datagrid-number-filter-web/typings/DatagridNumberFilterProps.d.ts
index 0c2f405af5..8b9a872dff 100644
--- a/packages/pluggableWidgets/datagrid-number-filter-web/typings/DatagridNumberFilterProps.d.ts
+++ b/packages/pluggableWidgets/datagrid-number-filter-web/typings/DatagridNumberFilterProps.d.ts
@@ -4,17 +4,28 @@
* @author Mendix Widgets Framework Team
*/
import { CSSProperties } from "react";
-import { ActionValue, DynamicValue, EditableValue } from "mendix";
+import { ActionValue, AttributeMetaData, DynamicValue, EditableValue } from "mendix";
import { Big } from "big.js";
+export type AttrChoiceEnum = "auto" | "linked";
+
+export interface AttributesType {
+ attribute: AttributeMetaData;
+}
+
export type DefaultFilterEnum = "greater" | "greaterEqual" | "equal" | "notEqual" | "smaller" | "smallerEqual" | "empty" | "notEmpty";
+export interface AttributesPreviewType {
+ attribute: string;
+}
+
export interface DatagridNumberFilterContainerProps {
name: string;
class: string;
style?: CSSProperties;
tabIndex?: number;
- advanced: boolean;
+ attrChoice: AttrChoiceEnum;
+ attributes: AttributesType[];
defaultValue?: DynamicValue;
defaultFilter: DefaultFilterEnum;
placeholder?: DynamicValue;
@@ -37,7 +48,8 @@ export interface DatagridNumberFilterPreviewProps {
readOnly: boolean;
renderMode: "design" | "xray" | "structure";
translate: (text: string) => string;
- advanced: boolean;
+ attrChoice: AttrChoiceEnum;
+ attributes: AttributesPreviewType[];
defaultValue: string;
defaultFilter: DefaultFilterEnum;
placeholder: string;
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/package.json b/packages/pluggableWidgets/datagrid-text-filter-web/package.json
index 0d60f0e07c..64a2ac5f76 100644
--- a/packages/pluggableWidgets/datagrid-text-filter-web/package.json
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/package.json
@@ -45,6 +45,7 @@
"@mendix/widget-plugin-external-events": "workspace:*",
"@mendix/widget-plugin-filtering": "workspace:*",
"@mendix/widget-plugin-hooks": "workspace:*",
+ "@mendix/widget-plugin-mobx-kit": "workspace:^",
"@mendix/widget-plugin-platform": "workspace:*",
"classnames": "^2.3.2",
"mobx": "6.12.3",
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.editorConfig.ts b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.editorConfig.ts
index 2414af2eae..787ae0b8c9 100644
--- a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.editorConfig.ts
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.editorConfig.ts
@@ -1,10 +1,4 @@
-import {
- ContainerProps,
- ImageProps,
- StructurePreviewProps,
- text,
- structurePreviewPalette
-} from "@mendix/widget-plugin-platform/preview/structure-preview-api";
+import { hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools";
import {
containsIcon,
containsIconDark,
@@ -29,25 +23,27 @@ import {
startsWithIcon,
startsWithIconDark
} from "@mendix/widget-plugin-filtering/preview/editor-preview-icons";
-import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools";
+import {
+ ContainerProps,
+ ImageProps,
+ structurePreviewPalette,
+ StructurePreviewProps,
+ text
+} from "@mendix/widget-plugin-platform/preview/structure-preview-api";
import { DatagridTextFilterPreviewProps, DefaultFilterEnum } from "../typings/DatagridTextFilterProps";
-export function getProperties(
- values: DatagridTextFilterPreviewProps,
- defaultProperties: Properties,
- platform: "web" | "desktop"
-): Properties {
+export function getProperties(values: DatagridTextFilterPreviewProps, defaultProperties: Properties): Properties {
if (!values.adjustable) {
hidePropertyIn(defaultProperties, values, "screenReaderButtonCaption");
}
- if (platform === "web") {
- if (!values.advanced) {
- hidePropertiesIn(defaultProperties, values, ["onChange", "valueAttribute"]);
- }
- } else {
- hidePropertyIn(defaultProperties, values, "advanced");
+
+ if (values.attrChoice === "auto") {
+ hidePropertyIn(defaultProperties, values, "attributes");
}
+
+ hidePropertyIn(defaultProperties, {} as { linkedDs: unknown }, "linkedDs");
+
return defaultProperties;
}
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.tsx b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.tsx
index 5772f0eb2f..f47d4f1bcc 100644
--- a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.tsx
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.tsx
@@ -4,10 +4,18 @@ import { DatagridTextFilterContainerProps } from "../typings/DatagridTextFilterP
import { TextFilterContainer } from "./components/TextFilterContainer";
import { withTextFilterAPI } from "./hocs/withTextFilterAPI";
import { isLoadingDefaultValues } from "./utils/widget-utils";
+import { withLinkedAttributes } from "./hocs/withLinkedAttributes";
const container = withPreloader(TextFilterContainer, isLoadingDefaultValues);
-const Widget = withTextFilterAPI(container);
+const FilterAuto = withTextFilterAPI(container);
+const FilterLinked = withLinkedAttributes(container);
export default function DatagridTextFilter(props: DatagridTextFilterContainerProps): ReactElement {
- return ;
+ const isAuto = props.attrChoice === "auto";
+
+ if (isAuto) {
+ return ;
+ }
+
+ return ;
}
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.xml b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.xml
index d8ed8c4cfc..893a20f660 100644
--- a/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.xml
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/DatagridTextFilter.xml
@@ -8,9 +8,32 @@
-
- Enable advanced options
+
+ Filter attributes
+
+ Auto
+ Custom
+
+
+
+ Datasource to Filter
+
+
+
+ Attributes
+ Select the attributes that the end-user may use for filtering.
+
+
+
+ Attribute
+
+
+
+
+
+
+
Default value
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/DatagridTextFilter.spec.tsx b/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/DatagridTextFilter.spec.tsx
index 7726d55c48..3b8ae18f20 100644
--- a/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/DatagridTextFilter.spec.tsx
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/components/__tests__/DatagridTextFilter.spec.tsx
@@ -1,8 +1,7 @@
import "@testing-library/jest-dom";
-import { FilterAPIv2 } from "@mendix/widget-plugin-filtering/context";
import {
HeaderFiltersStore,
- HeaderFiltersStoreProps
+ HeaderFiltersStoreSpec
} from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore";
import {
actionValue,
@@ -17,11 +16,8 @@ import { createContext, createElement } from "react";
import DatagridTextFilter from "../../DatagridTextFilter";
import { DatagridTextFilterContainerProps } from "../../../typings/DatagridTextFilterProps";
import { resetIdCounter } from "downshift";
-
-interface StaticInfo {
- name: string;
- filtersChannelName: string;
-}
+import { FilterObserver } from "@mendix/widget-plugin-filtering/typings/FilterObserver";
+import { FilterAPI } from "@mendix/widget-plugin-filtering/context";
const commonProps: DatagridTextFilterContainerProps = {
class: "filter-custom-class",
@@ -29,13 +25,9 @@ const commonProps: DatagridTextFilterContainerProps = {
name: "filter-test",
defaultFilter: "equal" as const,
adjustable: true,
- advanced: false,
- delay: 1000
-};
-
-const headerFilterStoreInfo: StaticInfo = {
- name: commonProps.name,
- filtersChannelName: "datagrid1"
+ delay: 1000,
+ attrChoice: "auto",
+ attributes: []
};
jest.useFakeTimers();
@@ -57,7 +49,7 @@ describe("Text Filter", () => {
describe("with defaultValue prop", () => {
beforeEach(() => {
- const props: HeaderFiltersStoreProps = {
+ const spec: HeaderFiltersStoreSpec = {
filterList: [
{
filter: new ListAttributeValueBuilder()
@@ -70,10 +62,13 @@ describe("Text Filter", () => {
.build()
}
],
- parentChannelName: "datagrid1"
+ filterChannelName: "datagrid1",
+ sharedInitFilter: [],
+ headerInitFilter: [],
+ customFilterHost: {} as FilterObserver
};
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
@@ -170,7 +165,7 @@ describe("Text Filter", () => {
describe("with single attribute", () => {
beforeAll(() => {
- const props: HeaderFiltersStoreProps = {
+ const spec: HeaderFiltersStoreSpec = {
filterList: [
{
filter: new ListAttributeValueBuilder()
@@ -183,10 +178,13 @@ describe("Text Filter", () => {
.build()
}
],
- parentChannelName: "datagrid1"
+ filterChannelName: "datagrid1",
+ sharedInitFilter: [],
+ headerInitFilter: [],
+ customFilterHost: {} as FilterObserver
};
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
@@ -256,7 +254,7 @@ describe("Text Filter", () => {
describe("with multiple attributes", () => {
beforeAll(() => {
- const props: HeaderFiltersStoreProps = {
+ const spec: HeaderFiltersStoreSpec = {
filterList: [
{
filter: new ListAttributeValueBuilder()
@@ -284,10 +282,14 @@ describe("Text Filter", () => {
.withFilterable(true)
.build()
}
- ]
+ ],
+ filterChannelName: "datagrid1",
+ sharedInitFilter: [],
+ headerInitFilter: [],
+ customFilterHost: {} as FilterObserver
};
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
@@ -305,13 +307,17 @@ describe("Text Filter", () => {
describe("with wrong attribute's type", () => {
beforeAll(() => {
- const props: HeaderFiltersStoreProps = {
+ const spec: HeaderFiltersStoreSpec = {
filterList: [
{ filter: new ListAttributeValueBuilder().withType("Decimal").withFilterable(true).build() }
- ]
+ ],
+ filterChannelName: "datagrid1",
+ sharedInitFilter: [],
+ headerInitFilter: [],
+ customFilterHost: {} as FilterObserver
};
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
@@ -329,7 +335,7 @@ describe("Text Filter", () => {
describe("with wrong multiple attributes' types", () => {
beforeAll(() => {
- const props: HeaderFiltersStoreProps = {
+ const spec: HeaderFiltersStoreSpec = {
filterList: [
{
filter: new ListAttributeValueBuilder()
@@ -345,10 +351,14 @@ describe("Text Filter", () => {
.withFilterable(true)
.build()
}
- ]
+ ],
+ filterChannelName: "datagrid1",
+ sharedInitFilter: [],
+ headerInitFilter: [],
+ customFilterHost: {} as FilterObserver
};
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
@@ -379,7 +389,7 @@ describe("Text Filter", () => {
describe("with multiple instances", () => {
beforeAll(() => {
- const props: HeaderFiltersStoreProps = {
+ const spec: HeaderFiltersStoreSpec = {
filterList: [
{
filter: new ListAttributeValueBuilder()
@@ -393,10 +403,14 @@ describe("Text Filter", () => {
.withFilterable(true)
.build()
}
- ]
+ ],
+ filterChannelName: "datagrid1",
+ sharedInitFilter: [],
+ headerInitFilter: [],
+ customFilterHost: {} as FilterObserver
};
- const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
- (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+ const headerFilterStore = new HeaderFiltersStore(spec);
+ (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
});
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withLinkedAttributes.tsx b/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withLinkedAttributes.tsx
new file mode 100644
index 0000000000..839892a368
--- /dev/null
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withLinkedAttributes.tsx
@@ -0,0 +1,71 @@
+import { createElement } from "react";
+import { AttributeMetaData } from "mendix";
+import { useFilterAPI } from "@mendix/widget-plugin-filtering/context";
+import { APIError } from "@mendix/widget-plugin-filtering/errors";
+import { error, value, Result } from "@mendix/widget-plugin-filtering/result-meta";
+import { String_InputFilterInterface } from "@mendix/widget-plugin-filtering/typings/InputFilterInterface";
+import { Alert } from "@mendix/widget-plugin-component-kit/Alert";
+import { useConst } from "@mendix/widget-plugin-mobx-kit/react/useConst";
+import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup";
+import { StringStoreProvider } from "@mendix/widget-plugin-filtering/custom-filter-api/StringStoreProvider";
+import { ISetupable } from "@mendix/widget-plugin-mobx-kit/setupable";
+
+interface RequiredProps {
+ attributes: Array<{
+ attribute: AttributeMetaData;
+ }>;
+ name: string;
+}
+
+interface StoreProvider extends ISetupable {
+ store: String_InputFilterInterface;
+}
+
+type Component = (props: P) => React.ReactElement;
+
+export function withLinkedAttributes
(
+ component: Component
+): Component
{
+ const StoreInjector = withInjectedStore(component);
+
+ return function FilterAPIProvider(props) {
+ const api = useStoreProvider(props);
+
+ if (api.hasError) {
+ return {api.error.message};
+ }
+
+ return ;
+ };
+}
+
+function withInjectedStore
(
+ Component: Component
+): Component
{
+ return function StoreInjector(props) {
+ const provider = useSetup(() => props.provider);
+ return ;
+ };
+}
+
+interface InjectableFilterAPI {
+ filterStore: String_InputFilterInterface;
+ parentChannelName?: string;
+}
+
+function useStoreProvider(props: RequiredProps): Result<{ provider: StoreProvider; channel: string }, APIError> {
+ const filterAPI = useFilterAPI();
+ return useConst(() => {
+ if (filterAPI.hasError) {
+ return error(filterAPI.error);
+ }
+
+ return value({
+ provider: new StringStoreProvider(filterAPI.value, {
+ attributes: props.attributes.map(obj => obj.attribute),
+ dataKey: props.name
+ }),
+ channel: filterAPI.value.parentChannelName
+ });
+ });
+}
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withTextFilterAPI.tsx b/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withTextFilterAPI.tsx
index c54deea135..726f91c054 100644
--- a/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withTextFilterAPI.tsx
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/hocs/withTextFilterAPI.tsx
@@ -6,7 +6,7 @@ export function withTextFilterAPI
(
Component: (props: P & String_FilterAPIv2) => React.ReactElement
): (props: P) => React.ReactElement {
return function FilterAPIProvider(props) {
- const api = useStringFilterAPI("");
+ const api = useStringFilterAPI();
if (api.hasError) {
return {api.error.message};
diff --git a/packages/pluggableWidgets/datagrid-text-filter-web/typings/DatagridTextFilterProps.d.ts b/packages/pluggableWidgets/datagrid-text-filter-web/typings/DatagridTextFilterProps.d.ts
index dfd4193e6a..170eb35aa8 100644
--- a/packages/pluggableWidgets/datagrid-text-filter-web/typings/DatagridTextFilterProps.d.ts
+++ b/packages/pluggableWidgets/datagrid-text-filter-web/typings/DatagridTextFilterProps.d.ts
@@ -4,16 +4,27 @@
* @author Mendix Widgets Framework Team
*/
import { CSSProperties } from "react";
-import { ActionValue, DynamicValue, EditableValue } from "mendix";
+import { ActionValue, AttributeMetaData, DynamicValue, EditableValue } from "mendix";
+
+export type AttrChoiceEnum = "auto" | "linked";
+
+export interface AttributesType {
+ attribute: AttributeMetaData;
+}
export type DefaultFilterEnum = "contains" | "startsWith" | "endsWith" | "greater" | "greaterEqual" | "equal" | "notEqual" | "smaller" | "smallerEqual" | "empty" | "notEmpty";
+export interface AttributesPreviewType {
+ attribute: string;
+}
+
export interface DatagridTextFilterContainerProps {
name: string;
class: string;
style?: CSSProperties;
tabIndex?: number;
- advanced: boolean;
+ attrChoice: AttrChoiceEnum;
+ attributes: AttributesType[];
defaultValue?: DynamicValue;
defaultFilter: DefaultFilterEnum;
placeholder?: DynamicValue;
@@ -36,7 +47,8 @@ export interface DatagridTextFilterPreviewProps {
readOnly: boolean;
renderMode: "design" | "xray" | "structure";
translate: (text: string) => string;
- advanced: boolean;
+ attrChoice: AttrChoiceEnum;
+ attributes: AttributesPreviewType[];
defaultValue: string;
defaultFilter: DefaultFilterEnum;
placeholder: string;
diff --git a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js
index 5402ff52cd..321616b7d9 100644
--- a/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js
+++ b/packages/pluggableWidgets/datagrid-web/e2e/DataGrid.spec.js
@@ -113,14 +113,14 @@ test.describe("capabilities: hiding", () => {
const textAreaValue = await textArea.inputValue();
expect(JSON.parse(textAreaValue)).toEqual({
name: "datagrid5",
- schemaVersion: 2,
+ schemaVersion: 3,
settingsHash: "1530160614",
columns: [
{ columnId: "0", hidden: true },
{ columnId: "1", hidden: false }
],
columnFilters: [],
- groupFilters: [],
+ customFilters: [],
sortOrder: [],
columnOrder: ["0", "1"]
});
diff --git a/packages/pluggableWidgets/datagrid-web/src/controllers/StateSyncController.ts b/packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceParamsController.ts
similarity index 65%
rename from packages/pluggableWidgets/datagrid-web/src/controllers/StateSyncController.ts
rename to packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceParamsController.ts
index 397c73e3c6..9a79deb4e0 100644
--- a/packages/pluggableWidgets/datagrid-web/src/controllers/StateSyncController.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceParamsController.ts
@@ -12,34 +12,41 @@ interface Columns {
sortInstructions: SortInstruction[] | undefined;
}
-interface Header {
+interface FiltersInput {
conditions: Array;
}
-type StateSyncControllerSpec = {
+type DatasourceParamsControllerSpec = {
query: QueryController;
columns: Columns;
- header: Header;
+ header: FiltersInput;
+ customFilters: FiltersInput;
};
-export class StateSyncController implements ReactiveController {
+export class DatasourceParamsController implements ReactiveController {
private columns: Columns;
- private header: Header;
+ private header: FiltersInput;
private query: QueryController;
+ private customFilters: FiltersInput;
- constructor(host: ReactiveControllerHost, spec: StateSyncControllerSpec) {
+ constructor(host: ReactiveControllerHost, spec: DatasourceParamsControllerSpec) {
host.addController(this);
this.columns = spec.columns;
this.header = spec.header;
this.query = spec.query;
+ this.customFilters = spec.customFilters;
makeAutoObservable(this, { setup: false });
}
private get derivedFilter(): FilterCondition | undefined {
- const { columns, header } = this;
+ const { columns, header, customFilters } = this;
- return and(compactArray(columns.conditions), compactArray(header.conditions));
+ return and(
+ compactArray(columns.conditions),
+ compactArray(header.conditions),
+ compactArray(customFilters.conditions)
+ );
}
private get derivedSortOrder(): SortInstruction[] | undefined {
@@ -68,17 +75,22 @@ export class StateSyncController implements ReactiveController {
static unzipFilter(
filter?: FilterCondition
- ): [columns: Array, header: Array] {
+ ): [
+ columns: Array,
+ header: Array,
+ sharedFilter: Array
+ ] {
if (!filter) {
- return [[], []];
+ return [[], [], []];
}
if (!isAnd(filter)) {
- return [[], []];
+ return [[], [], []];
}
- if (filter.args.length !== 2) {
- return [[], []];
+ if (filter.args.length !== 3) {
+ return [[], [], []];
}
- const [columns, header] = filter.args;
- return [fromCompactArray(columns), fromCompactArray(header)];
+
+ const [columns, header, shared] = filter.args;
+ return [fromCompactArray(columns), fromCompactArray(header), fromCompactArray(shared)];
}
}
diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts b/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts
index efc8dd67ad..ae76ecf01b 100644
--- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts
@@ -13,7 +13,7 @@ import {
sortInstructionsToSortRules,
sortRulesToSortInstructions
} from "./ColumnsSortingStore";
-import { ColumnFilterStore } from "./column/ColumnFilterStore";
+import { ColumnFilterStore, ObserverBag } from "./column/ColumnFilterStore";
import { ColumnStore } from "./column/ColumnStore";
export interface IColumnGroupStore {
@@ -46,17 +46,18 @@ export class ColumnGroupStore implements IColumnGroupStore, IColumnParentStore {
constructor(
props: Pick,
info: StaticInfo,
- dsViewState: Array | null
+ initFilter: Array,
+ observerBag: ObserverBag
) {
this._allColumns = [];
this.columnFilters = [];
props.columns.forEach((columnProps, i) => {
- const initCond = dsViewState?.at(i) ?? null;
+ const initCond = initFilter.at(i) ?? null;
const column = new ColumnStore(i, columnProps, this);
this._allColumnsById.set(column.columnId, column);
this._allColumns[i] = column;
- this.columnFilters[i] = new ColumnFilterStore(columnProps, info, initCond);
+ this.columnFilters[i] = new ColumnFilterStore(columnProps, info, initCond, observerBag);
});
this.sorting = new ColumnsSortingStore(
diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts b/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts
index 21c8024aca..c9a062b6ac 100644
--- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts
@@ -1,5 +1,5 @@
import { error, Result, value } from "@mendix/widget-plugin-filtering/result-meta";
-import { HeaderFiltersStore } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore";
+import { FilterObserver } from "@mendix/widget-plugin-filtering/typings/FilterObserver";
import { FiltersSettingsMap } from "@mendix/widget-plugin-filtering/typings/settings";
import { action, comparer, computed, IReactionDisposer, makeObservable, reaction } from "mobx";
import { DatagridContainerProps } from "../../../typings/DatagridProps";
@@ -17,7 +17,7 @@ import { ColumnGroupStore } from "./ColumnGroupStore";
export class GridPersonalizationStore {
private readonly gridName: string;
private readonly gridColumnsHash: string;
- private readonly schemaVersion: GridPersonalizationStorageSettings["schemaVersion"] = 2;
+ private readonly schemaVersion: GridPersonalizationStorageSettings["schemaVersion"] = 3;
private readonly storeFilters: boolean;
private storage: PersonalizationStorage;
@@ -27,7 +27,7 @@ export class GridPersonalizationStore {
constructor(
props: DatagridContainerProps,
private columnsStore: ColumnGroupStore,
- private headerFilters: HeaderFiltersStore
+ private customFilters: FilterObserver
) {
this.gridName = props.name;
this.gridColumnsHash = getHash(this.columnsStore._allColumns, this.gridName);
@@ -35,7 +35,6 @@ export class GridPersonalizationStore {
makeObservable(this, {
settings: computed,
-
applySettings: action
});
@@ -95,6 +94,7 @@ export class GridPersonalizationStore {
private applySettings(settings: GridPersonalizationStorageSettings): void {
this.columnsStore.setColumnSettings(toColumnSettings(settings));
this.columnsStore.setColumnFilterSettings(settings.columnFilters);
+ this.customFilters.settings = new Map(settings.customFilters);
}
private readSettings(
@@ -137,7 +137,7 @@ export class GridPersonalizationStore {
this.gridColumnsHash,
this.columnsStore.columnSettings,
this.storeFilters ? this.columnsStore.filterSettings : new Map(),
- this.storeFilters ? this.headerFilters.settings : new Map()
+ this.storeFilters ? this.customFilters.settings : new Map()
);
}
}
@@ -164,7 +164,7 @@ function toStorageFormat(
gridColumnsHash: string,
columnsSettings: ColumnPersonalizationSettings[],
columnFilters: FiltersSettingsMap,
- groupFilters: FiltersSettingsMap
+ customFilters: FiltersSettingsMap
): GridPersonalizationStorageSettings {
const sortOrder = columnsSettings
.filter(c => c.sortDir && c.sortWeight !== undefined)
@@ -175,7 +175,7 @@ function toStorageFormat(
return {
name: gridName,
- schemaVersion: 2,
+ schemaVersion: 3,
settingsHash: gridColumnsHash,
columns: columnsSettings.map(c => ({
columnId: c.columnId,
@@ -185,7 +185,7 @@ function toStorageFormat(
})),
columnFilters: Array.from(columnFilters),
- groupFilters: Array.from(groupFilters),
+ customFilters: Array.from(customFilters),
sortOrder,
columnOrder
diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts b/packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts
index a98cbf7eff..1e03c6b941 100644
--- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts
@@ -1,3 +1,4 @@
+import { CustomFilterHost } from "@mendix/widget-plugin-filtering/stores/generic/CustomFilterHost";
import { HeaderFiltersStore } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore";
import { BaseControllerHost } from "@mendix/widget-plugin-mobx-kit/BaseControllerHost";
import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch";
@@ -6,10 +7,10 @@ import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-
import { autorun, computed } from "mobx";
import { DatagridContainerProps } from "../../../typings/DatagridProps";
import { DatasourceController } from "../../controllers/DatasourceController";
+import { DatasourceParamsController } from "../../controllers/DatasourceParamsController";
import { DerivedLoaderController } from "../../controllers/DerivedLoaderController";
import { PaginationController } from "../../controllers/PaginationController";
import { RefreshController } from "../../controllers/RefreshController";
-import { StateSyncController } from "../../controllers/StateSyncController";
import { ProgressStore } from "../../features/data-export/ProgressStore";
import { StaticInfo } from "../../typings/static-info";
import { ColumnGroupStore } from "./ColumnGroupStore";
@@ -37,24 +38,37 @@ export class RootGridStore extends BaseControllerHost {
super();
const { props } = gate;
- const [columnsViewState, headerViewState] = StateSyncController.unzipFilter(props.datasource.filter);
+ const [columnsInitFilter, headerInitFilter, sharedInitFilter] = DatasourceParamsController.unzipFilter(
+ props.datasource.filter
+ );
this.gate = gate;
this.staticInfo = {
name: props.name,
filtersChannelName: `datagrid/${generateUUID()}`
};
+ const customFilterHost = new CustomFilterHost();
const query = new DatasourceController(this, { gate });
- const columns = (this.columnsStore = new ColumnGroupStore(props, this.staticInfo, columnsViewState));
- const header = (this.headerFiltersStore = new HeaderFiltersStore(props, this.staticInfo, headerViewState));
- this.settingsStore = new GridPersonalizationStore(props, this.columnsStore, this.headerFiltersStore);
+ const columns = (this.columnsStore = new ColumnGroupStore(props, this.staticInfo, columnsInitFilter, {
+ customFilterHost,
+ sharedInitFilter
+ }));
+ const header = (this.headerFiltersStore = new HeaderFiltersStore({
+ filterList: props.filterList,
+ filterChannelName: this.staticInfo.filtersChannelName,
+ headerInitFilter,
+ sharedInitFilter,
+ customFilterHost
+ }));
+ this.settingsStore = new GridPersonalizationStore(props, this.columnsStore, customFilterHost);
this.paginationCtrl = new PaginationController(this, { gate, query });
this.exportProgressCtrl = exportCtrl;
- new StateSyncController(this, {
+ new DatasourceParamsController(this, {
query,
columns,
- header
+ header,
+ customFilters: customFilterHost
});
new RefreshController(this, {
@@ -73,7 +87,7 @@ export class RootGridStore extends BaseControllerHost {
const [add, disposeAll] = disposeBatch();
add(super.setup());
add(this.columnsStore.setup());
- add(this.headerFiltersStore.setup() ?? (() => {}));
+ add(this.headerFiltersStore.setup());
add(() => this.settingsStore.dispose());
add(autorun(() => this.updateProps(this.gate.props)));
diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/state/column/ColumnFilterStore.tsx b/packages/pluggableWidgets/datagrid-web/src/helpers/state/column/ColumnFilterStore.tsx
index 9d0d9ae7cb..cf930ae8a3 100644
--- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/column/ColumnFilterStore.tsx
+++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/column/ColumnFilterStore.tsx
@@ -1,4 +1,4 @@
-import { FilterAPIv2, getGlobalFilterContextObject } from "@mendix/widget-plugin-filtering/context";
+import { FilterAPI, getGlobalFilterContextObject } from "@mendix/widget-plugin-filtering/context";
import { RefFilterStore, RefFilterStoreProps } from "@mendix/widget-plugin-filtering/stores/picker/RefFilterStore";
import { StaticSelectFilterStore } from "@mendix/widget-plugin-filtering/stores/picker/StaticSelectFilterStore";
import { InputFilterStore, attrgroupFilterStore } from "@mendix/widget-plugin-filtering/stores/input/store-utils";
@@ -12,6 +12,7 @@ import { StaticInfo } from "../../../typings/static-info";
import { FilterData } from "@mendix/widget-plugin-filtering/typings/settings";
import { value } from "@mendix/widget-plugin-filtering/result-meta";
import { disposeFx } from "@mendix/widget-plugin-filtering/mobx-utils";
+import { FilterObserver } from "@mendix/widget-plugin-filtering/typings/FilterObserver";
export interface IColumnFilterStore {
renderFilterWidgets(): ReactNode;
}
@@ -23,9 +24,11 @@ const { Provider } = getGlobalFilterContextObject();
export class ColumnFilterStore implements IColumnFilterStore {
private _widget: ReactNode;
private _filterStore: FilterStore | null = null;
- private _context: FilterAPIv2;
+ private _context: FilterAPI;
+ private _observerBag: ObserverBag;
- constructor(props: ColumnsType, info: StaticInfo, dsViewState: FilterCondition | null) {
+ constructor(props: ColumnsType, info: StaticInfo, dsViewState: FilterCondition | null, observerBag: ObserverBag) {
+ this._observerBag = observerBag;
this._widget = props.filter;
this._filterStore = this.createFilterStore(props, dsViewState);
this._context = this.createContext(this._filterStore, info);
@@ -92,14 +95,16 @@ export class ColumnFilterStore implements IColumnFilterStore {
return null;
}
- private createContext(store: FilterStore | null, info: StaticInfo): FilterAPIv2 {
+ private createContext(store: FilterStore | null, info: StaticInfo): FilterAPI {
return {
- version: 2,
+ version: 3,
parentChannelName: info.filtersChannelName,
provider: value({
type: "direct",
store
- })
+ }),
+ filterObserver: this._observerBag.customFilterHost,
+ sharedInitFilter: this._observerBag.sharedInitFilter
};
}
@@ -132,3 +137,8 @@ const isListAttributeValue = (
const errorMessage = (propName: string): string =>
`Can't map ColumnsType to AssociationProperties: ${propName} is undefined`;
+
+export interface ObserverBag {
+ customFilterHost: FilterObserver;
+ sharedInitFilter: Array;
+}
diff --git a/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts b/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts
index f51a4f9b4e..6c64391741 100644
--- a/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts
+++ b/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts
@@ -19,14 +19,14 @@ interface ColumnPersonalizationStorageSettings {
export type ColumnFilterSettings = Array<[key: ColumnId, data: FilterData]>;
-export type GroupFilterSettings = Array<[key: string, data: FilterData]>;
+export type CustomFilterSettings = Array<[key: string, data: FilterData]>;
export interface GridPersonalizationStorageSettings {
name: string;
- schemaVersion: 2;
+ schemaVersion: 3;
settingsHash: string;
columns: ColumnPersonalizationStorageSettings[];
- groupFilters: GroupFilterSettings;
+ customFilters: CustomFilterSettings;
columnFilters: ColumnFilterSettings;
columnOrder: ColumnId[];
sortOrder: SortRule[];
diff --git a/packages/pluggableWidgets/dropdown-sort-web/src/components/__test__/DropdownSort.spec.tsx b/packages/pluggableWidgets/dropdown-sort-web/src/components/__test__/DropdownSort.spec.tsx
index 099b39208f..bd7ead1a62 100644
--- a/packages/pluggableWidgets/dropdown-sort-web/src/components/__test__/DropdownSort.spec.tsx
+++ b/packages/pluggableWidgets/dropdown-sort-web/src/components/__test__/DropdownSort.spec.tsx
@@ -1,7 +1,7 @@
-import { FilterAPIv2 } from "@mendix/widget-plugin-filtering/context";
+import { FilterAPI } from "@mendix/widget-plugin-filtering/context";
import {
HeaderFiltersStore,
- HeaderFiltersStoreProps
+ HeaderFiltersStoreSpec
} from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore";
import { SortAPI } from "@mendix/widget-plugin-sorting/context";
import { SortAPIProvider, SortListType } from "@mendix/widget-plugin-sorting/providers/SortAPIProvider";
@@ -12,6 +12,7 @@ import { ListValue } from "mendix";
import { createContext, createElement } from "react";
import { DropdownSortContainerProps } from "../../../typings/DropdownSortProps";
import { DropdownSort } from "../../DropdownSort";
+import { FilterObserver } from "@mendix/widget-plugin-filtering/typings/FilterObserver";
const commonProps: DropdownSortContainerProps = {
class: "filter-custom-class",
@@ -19,22 +20,16 @@ const commonProps: DropdownSortContainerProps = {
name: "filter-test"
};
-interface StaticInfo {
- name: string;
- filtersChannelName: string;
-}
-
-const headerFilterStoreInfo: StaticInfo = {
- name: commonProps.name,
- filtersChannelName: ""
+const spec: HeaderFiltersStoreSpec = {
+ filterList: [],
+ sharedInitFilter: [],
+ headerInitFilter: [],
+ filterChannelName: "datagrid",
+ customFilterHost: {} as FilterObserver
};
-// CONTEXT
-const props: HeaderFiltersStoreProps = {
- filterList: []
-};
-const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null);
-(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
+const headerFilterStore = new HeaderFiltersStore(spec);
+(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext(
headerFilterStore.context
);
diff --git a/packages/pluggableWidgets/gallery-web/src/stores/RootGalleryStore.ts b/packages/pluggableWidgets/gallery-web/src/stores/RootGalleryStore.ts
index 0f9bf1ebf6..24aa253be7 100644
--- a/packages/pluggableWidgets/gallery-web/src/stores/RootGalleryStore.ts
+++ b/packages/pluggableWidgets/gallery-web/src/stores/RootGalleryStore.ts
@@ -1,4 +1,5 @@
import { compactArray, fromCompactArray } from "@mendix/widget-plugin-filtering/condition-utils";
+import { CustomFilterHost } from "@mendix/widget-plugin-filtering/stores/generic/CustomFilterHost";
import { HeaderFiltersStore } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore";
import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid";
import { SortAPIProvider } from "@mendix/widget-plugin-sorting/providers/SortAPIProvider";
@@ -25,8 +26,13 @@ export class RootGalleryStore {
filtersChannelName: `datagrid/${generateUUID()}`
};
- const headerViewState = this.getDsViewState(props);
- this.headerFiltersStore = new HeaderFiltersStore(props, this.staticInfo, headerViewState);
+ this.headerFiltersStore = new HeaderFiltersStore({
+ filterList: props.filterList,
+ filterChannelName: this.staticInfo.filtersChannelName,
+ headerInitFilter: this.getDsViewState(props),
+ sharedInitFilter: [],
+ customFilterHost: new CustomFilterHost()
+ });
this.sortProvider = new SortAPIProvider(props);
}
diff --git a/packages/shared/widget-plugin-filtering/package.json b/packages/shared/widget-plugin-filtering/package.json
index 7a31cc4efa..e92778dc25 100644
--- a/packages/shared/widget-plugin-filtering/package.json
+++ b/packages/shared/widget-plugin-filtering/package.json
@@ -36,6 +36,7 @@
"@floating-ui/react-dom": "^2.1.2",
"@mendix/widget-plugin-external-events": "workspace:*",
"@mendix/widget-plugin-hooks": "workspace:*",
+ "@mendix/widget-plugin-mobx-kit": "workspace:^",
"@mendix/widget-plugin-platform": "workspace:*",
"downshift": "^9.0.8",
"mendix": "^10.16.49747",
diff --git a/packages/shared/widget-plugin-filtering/src/__tests__/condition-utils.spec.ts b/packages/shared/widget-plugin-filtering/src/__tests__/condition-utils.spec.ts
index 5e2484ec08..39e062ec82 100644
--- a/packages/shared/widget-plugin-filtering/src/__tests__/condition-utils.spec.ts
+++ b/packages/shared/widget-plugin-filtering/src/__tests__/condition-utils.spec.ts
@@ -1,27 +1,33 @@
jest.mock("mendix/filters/builders");
+import { AndCondition } from "mendix/filters";
import { equals, literal } from "mendix/filters/builders";
import { compactArray, fromCompactArray, tag } from "../condition-utils";
-import { AndCondition } from "mendix/filters";
describe("condition-utils", () => {
describe("compactArray", () => {
- it("returns 'and' condition for zero array", () => {
+ it("returns 'tag' condition for zero array", () => {
const result = compactArray([]);
- expect(result).toMatchObject({ name: "and", type: "function" });
- expect((result as AndCondition).args).toHaveLength(2);
+ expect(result).toMatchObject({
+ name: "!=",
+ type: "function",
+ arg1: { value: "[0,[]]", valueType: "String" }
+ });
});
- it("returns 'and' condition for empty array", () => {
+ it("returns 'tag' condition for array of undefined", () => {
const result = compactArray([undefined, undefined, undefined]);
- expect(result).toMatchObject({ name: "and", type: "function" });
- expect((result as AndCondition).args).toHaveLength(2);
+ expect(result).toMatchObject({
+ name: "!=",
+ type: "function",
+ arg1: { value: "[3,[]]", valueType: "String" }
+ });
});
- it("returns 'and' condition with 4 args", () => {
+ it("returns 'and' condition with 3 args", () => {
const result = compactArray([tag("0"), undefined, tag("2")]);
expect(result).toMatchObject({ name: "and", type: "function" });
- expect((result as AndCondition).args).toHaveLength(4);
+ expect((result as AndCondition).args).toHaveLength(3);
});
});
diff --git a/packages/shared/widget-plugin-filtering/src/condition-utils.ts b/packages/shared/widget-plugin-filtering/src/condition-utils.ts
index 7f14ecbdc4..976da416b5 100644
--- a/packages/shared/widget-plugin-filtering/src/condition-utils.ts
+++ b/packages/shared/widget-plugin-filtering/src/condition-utils.ts
@@ -1,12 +1,12 @@
import {
- FilterCondition,
AndCondition,
- OrCondition,
- LiteralExpression,
ContainsCondition,
- EqualsCondition
+ EqualsCondition,
+ FilterCondition,
+ LiteralExpression,
+ OrCondition
} from "mendix/filters";
-import { equals, literal, and } from "mendix/filters/builders";
+import { and, literal, notEqual } from "mendix/filters/builders";
type BinaryExpression = T extends { arg1: unknown; arg2: object } ? T : never;
type Func = T extends { name: infer Fn } ? Fn : never;
@@ -40,25 +40,33 @@ interface TagName {
readonly valueType: "string";
}
+const MARKER = "#";
+
+interface TagMarker {
+ readonly type: "literal";
+ readonly value: typeof MARKER;
+ readonly valueType: "string";
+}
+
interface TagCond {
readonly type: "function";
- readonly name: "=";
+ readonly name: "!=";
readonly arg1: TagName;
- readonly arg2: TagName;
+ readonly arg2: TagMarker;
}
export function tag(name: string): TagCond {
- return equals(literal(name), literal(name)) as TagCond;
+ return notEqual(literal(name), literal(MARKER)) as TagCond;
}
export function isTag(cond: FilterCondition): cond is TagCond {
return (
- cond.name === "=" &&
+ cond.name === "!=" &&
cond.arg1.type === "literal" &&
cond.arg2.type === "literal" &&
/string/i.test(cond.arg1.valueType) &&
/string/i.test(cond.arg2.valueType) &&
- cond.arg1.value === cond.arg2.value
+ cond.arg2.value === MARKER
);
}
@@ -88,20 +96,19 @@ function shrink(array: Array): [indexes: number[], items: T[]]
export function compactArray(input: Array): FilterCondition {
const [indexes, items] = shrink(input);
- const arrayMeta = [input.length, indexes] as const;
- const metaTag = tag(arrayTag(arrayMeta));
- // As 'and' requires at least 2 args, we add a placeholder
- const placeholder = tag("_");
- return and(metaTag, placeholder, ...items);
+ const metaTag = tag(arrayTag([input.length, indexes] as const));
+
+ if (items.length === 0) {
+ return metaTag;
+ }
+
+ return and(metaTag, ...items);
}
export function fromCompactArray(cond: FilterCondition): Array {
- if (!isAnd(cond)) {
- return [];
- }
+ const tag = isAnd(cond) ? cond.args[0] : cond;
- const [metaTag] = cond.args;
- const arrayMeta = isTag(metaTag) ? fromArrayTag(metaTag.arg1.value) : undefined;
+ const arrayMeta = isTag(tag) ? fromArrayTag(tag.arg1.value) : undefined;
if (!arrayMeta) {
return [];
@@ -109,7 +116,12 @@ export function fromCompactArray(cond: FilterCondition): Array = Array(length).fill(undefined);
- cond.args.slice(2).forEach((cond, i) => {
+
+ if (!isAnd(cond)) {
+ return arr;
+ }
+
+ cond.args.slice(1).forEach((cond, i) => {
arr[indexes[i]] = cond;
});
diff --git a/packages/shared/widget-plugin-filtering/src/context.ts b/packages/shared/widget-plugin-filtering/src/context.ts
index 2a44be0ed0..e34c9f5375 100644
--- a/packages/shared/widget-plugin-filtering/src/context.ts
+++ b/packages/shared/widget-plugin-filtering/src/context.ts
@@ -1,13 +1,17 @@
+import { FilterCondition } from "mendix/filters/index.js";
import { Context, createContext, useContext } from "react";
import { APIError, ENOCONTEXT } from "./errors.js";
import { Result, error, value } from "./result-meta.js";
+import { FilterObserver } from "./typings/FilterObserver.js";
import { InputFilterInterface } from "./typings/InputFilterInterface.js";
import { PickerFilterStore } from "./typings/PickerFilterStore.js";
-export interface FilterAPIv2 {
- version: 2;
+export interface FilterAPI {
+ version: 3;
parentChannelName: string;
provider: Result;
+ filterObserver: FilterObserver;
+ sharedInitFilter: Array;
}
/** @deprecated */
@@ -18,7 +22,7 @@ export enum FilterType {
DATE = "date"
}
-export type FilterStoreProvider = DirectProvider | KeyProvider | LegacyProvider;
+export type FilterStoreProvider = DirectProvider | LegacyProvider;
export type FilterStore = InputFilterInterface | PickerFilterStore;
@@ -27,32 +31,27 @@ interface DirectProvider {
store: FilterStore | null;
}
-export interface KeyProvider {
- type: "key-value";
- get: (key: string) => FilterStore | null;
-}
-
/** @deprecated */
export interface LegacyProvider {
type: "legacy";
get: (type: FilterType) => FilterStore | null;
}
-type Context_v2 = Context;
+type FilterAPIContext = Context;
const CONTEXT_OBJECT_PATH = "com.mendix.widgets.web.filterable.filterContext.v2" as const;
declare global {
interface Window {
- [CONTEXT_OBJECT_PATH]: Context_v2 | undefined;
+ [CONTEXT_OBJECT_PATH]: FilterAPIContext | undefined;
}
}
-export function getGlobalFilterContextObject(): Context_v2 {
- return (window[CONTEXT_OBJECT_PATH] ??= createContext(null));
+export function getGlobalFilterContextObject(): FilterAPIContext {
+ return (window[CONTEXT_OBJECT_PATH] ??= createContext(null));
}
-export function useFilterContextValue(): Result {
+export function useFilterAPI(): Result {
const context = getGlobalFilterContextObject();
const contextValue = useContext(context);
@@ -63,12 +62,13 @@ export function useFilterContextValue(): Result {
return value(contextValue);
}
-export function getFilterStore(provider: FilterStoreProvider, legacyType: FilterType, key: string): FilterStore | null {
+/** @deprecated This hook is renamed, use `useFilterAPI` instead. */
+export const useFilterContextValue = useFilterAPI;
+
+export function getFilterStore(provider: FilterStoreProvider, legacyType: FilterType): FilterStore | null {
switch (provider.type) {
case "direct":
return provider.store;
- case "key-value":
- return provider.get(key);
case "legacy":
return provider.get(legacyType);
default:
diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/BaseStoreProvider.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/BaseStoreProvider.ts
new file mode 100644
index 0000000000..883290b619
--- /dev/null
+++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/BaseStoreProvider.ts
@@ -0,0 +1,32 @@
+import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch";
+import { ISetupable } from "@mendix/widget-plugin-mobx-kit/setupable";
+import { FilterCondition } from "mendix/filters";
+import { isAnd, isTag } from "../condition-utils";
+import { FilterAPI } from "../context";
+import { Filter } from "../typings/FilterObserver";
+
+export abstract class BaseStoreProvider implements ISetupable {
+ protected abstract _store: S;
+ protected abstract filterAPI: FilterAPI;
+ abstract readonly dataKey: string;
+
+ protected findInitFilter(conditions: Array, key: string): FilterCondition | null {
+ for (const cond of conditions) {
+ if (cond && isAnd(cond)) {
+ const [tag, initFilter] = cond.args;
+ if (isTag(tag) && tag.arg1.value === key) {
+ return initFilter;
+ }
+ }
+ }
+ return null;
+ }
+
+ setup(): () => void {
+ const [add, disposeAll] = disposeBatch();
+ this.filterAPI.filterObserver.observe(this.dataKey, this._store);
+ add(() => this.filterAPI.filterObserver.unobserve(this.dataKey));
+ add(this._store.setup?.());
+ return disposeAll;
+ }
+}
diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/DateStoreProvider.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/DateStoreProvider.ts
new file mode 100644
index 0000000000..65cfe580d5
--- /dev/null
+++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/DateStoreProvider.ts
@@ -0,0 +1,25 @@
+import { FilterAPI } from "../context";
+import { DateInputFilterStore } from "../stores/input/DateInputFilterStore";
+import { Date_InputFilterInterface } from "../typings/InputFilterInterface";
+import { BaseStoreProvider } from "./BaseStoreProvider";
+import { FilterSpec } from "./typings";
+
+export class DateStoreProvider extends BaseStoreProvider {
+ protected _store: DateInputFilterStore;
+ protected filterAPI: FilterAPI;
+ readonly dataKey: string;
+
+ constructor(filterAPI: FilterAPI, spec: FilterSpec) {
+ super();
+ this.filterAPI = filterAPI;
+ this.dataKey = spec.dataKey;
+ this._store = new DateInputFilterStore(
+ spec.attributes,
+ this.findInitFilter(filterAPI.sharedInitFilter, this.dataKey)
+ );
+ }
+
+ get store(): Date_InputFilterInterface {
+ return this._store;
+ }
+}
diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/NumberStoreProvider.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/NumberStoreProvider.ts
new file mode 100644
index 0000000000..da7f77e639
--- /dev/null
+++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/NumberStoreProvider.ts
@@ -0,0 +1,25 @@
+import { FilterAPI } from "../context";
+import { NumberInputFilterStore } from "../stores/input/NumberInputFilterStore";
+import { Number_InputFilterInterface } from "../typings/InputFilterInterface";
+import { BaseStoreProvider } from "./BaseStoreProvider";
+import { FilterSpec } from "./typings";
+
+export class NumberStoreProvider extends BaseStoreProvider {
+ protected _store: NumberInputFilterStore;
+ protected filterAPI: FilterAPI;
+ readonly dataKey: string;
+
+ constructor(filterAPI: FilterAPI, spec: FilterSpec) {
+ super();
+ this.filterAPI = filterAPI;
+ this.dataKey = spec.dataKey;
+ this._store = new NumberInputFilterStore(
+ spec.attributes,
+ this.findInitFilter(filterAPI.sharedInitFilter, this.dataKey)
+ );
+ }
+
+ get store(): Number_InputFilterInterface {
+ return this._store;
+ }
+}
diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/StringStoreProvider.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/StringStoreProvider.ts
new file mode 100644
index 0000000000..b1270ac3e3
--- /dev/null
+++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/StringStoreProvider.ts
@@ -0,0 +1,25 @@
+import { FilterAPI } from "../context";
+import { StringInputFilterStore } from "../stores/input/StringInputFilterStore";
+import { String_InputFilterInterface } from "../typings/InputFilterInterface";
+import { BaseStoreProvider } from "./BaseStoreProvider";
+import { FilterSpec } from "./typings";
+
+export class StringStoreProvider extends BaseStoreProvider {
+ protected _store: StringInputFilterStore;
+ protected filterAPI: FilterAPI;
+ readonly dataKey: string;
+
+ constructor(filterAPI: FilterAPI, spec: FilterSpec) {
+ super();
+ this.filterAPI = filterAPI;
+ this.dataKey = spec.dataKey;
+ this._store = new StringInputFilterStore(
+ spec.attributes,
+ this.findInitFilter(filterAPI.sharedInitFilter, this.dataKey)
+ );
+ }
+
+ get store(): String_InputFilterInterface {
+ return this._store;
+ }
+}
diff --git a/packages/shared/widget-plugin-filtering/src/custom-filter-api/typings.ts b/packages/shared/widget-plugin-filtering/src/custom-filter-api/typings.ts
new file mode 100644
index 0000000000..5cf80885d2
--- /dev/null
+++ b/packages/shared/widget-plugin-filtering/src/custom-filter-api/typings.ts
@@ -0,0 +1,8 @@
+import { AttributeMetaData, EditableValue } from "mendix";
+
+type AttributeValue_2 = EditableValue["value"];
+
+export interface FilterSpec {
+ attributes: Array>;
+ dataKey: string;
+}
diff --git a/packages/shared/widget-plugin-filtering/src/helpers/useDateFilterAPI.ts b/packages/shared/widget-plugin-filtering/src/helpers/useDateFilterAPI.ts
index d2391337f3..d6cdab88bf 100644
--- a/packages/shared/widget-plugin-filtering/src/helpers/useDateFilterAPI.ts
+++ b/packages/shared/widget-plugin-filtering/src/helpers/useDateFilterAPI.ts
@@ -1,6 +1,6 @@
import { useRef } from "react";
import { FilterType, getFilterStore, useFilterContextValue } from "../context";
-import { APIError, EKEYMISSING, EMISSINGSTORE, EStoreTypeMisMatch } from "../errors";
+import { APIError, EMISSINGSTORE, EStoreTypeMisMatch } from "../errors";
import { error, Result, value } from "../result-meta";
import { isDateFilter } from "../stores/input/store-utils";
import { Date_InputFilterInterface } from "../typings/InputFilterInterface";
@@ -10,7 +10,7 @@ export interface Date_FilterAPIv2 {
parentChannelName?: string;
}
-export function useDateFilterAPI(key: string): Result {
+export function useDateFilterAPI(): Result {
const ctx = useFilterContextValue();
const dateAPI = useRef();
@@ -24,11 +24,7 @@ export function useDateFilterAPI(key: string): Result {
+export function useNumberFilterAPI(): Result {
const ctx = useFilterContextValue();
const numAPI = useRef();
@@ -24,11 +24,7 @@ export function useNumberFilterAPI(key: string): Result {
+export function useStringFilterAPI(): Result {
const ctx = useFilterContextValue();
const strAPI = useRef();
@@ -24,11 +24,7 @@ export function useStringFilterAPI(key: string): Result = new Map();
+ private settingsBuffer: FiltersSettingsMap = new Map();
+ private disposeMap: Map void> = new Map();
+
+ constructor() {
+ makeAutoObservable(this);
+ }
+
+ get settings(): FiltersSettingsMap {
+ return new Map([...this.filters].map(([key, filter]) => [key, filter.toJSON()]));
+ }
+
+ set settings(data: FiltersSettingsMap) {
+ this.settingsBuffer = data;
+ }
+
+ get conditions(): Array {
+ return [...this.filters].map(([key, { condition }]) => {
+ return condition ? and(tag(key), condition) : undefined;
+ });
+ }
+
+ observe(key: string, filter: Filter): void {
+ const dispose = autorun(() => {
+ if (this.settingsBuffer.has(key)) {
+ filter.fromJSON(this.settingsBuffer.get(key));
+ }
+ });
+ this.disposeMap.set(key, dispose);
+ this.filters.set(key, filter);
+ }
+
+ unobserve(key: string): void {
+ this.disposeMap.get(key)?.();
+ this.disposeMap.delete(key);
+ this.filters.delete(key);
+ }
+}
diff --git a/packages/shared/widget-plugin-filtering/src/stores/generic/HeaderFiltersStore.ts b/packages/shared/widget-plugin-filtering/src/stores/generic/HeaderFiltersStore.ts
index 3836716e43..f627172731 100644
--- a/packages/shared/widget-plugin-filtering/src/stores/generic/HeaderFiltersStore.ts
+++ b/packages/shared/widget-plugin-filtering/src/stores/generic/HeaderFiltersStore.ts
@@ -1,40 +1,43 @@
import { ListAttributeValue } from "mendix";
import { FilterCondition } from "mendix/filters";
import { computed, makeObservable } from "mobx";
-import { FilterAPIv2 } from "../../context";
+import { FilterAPI } from "../../context";
import { APIError } from "../../errors";
import { LegacyPv } from "../../providers/LegacyPv";
import { Result, value } from "../../result-meta";
+import { FilterObserver } from "../../typings/FilterObserver";
import { FiltersSettingsMap } from "../../typings/settings";
export interface FilterListType {
filter: ListAttributeValue;
}
-export interface HeaderFiltersStoreProps {
+export interface HeaderFiltersStoreSpec {
filterList: FilterListType[];
- parentChannelName?: string;
-}
-
-export interface StaticInfo {
- name: string;
- filtersChannelName: string;
+ filterChannelName: string;
+ headerInitFilter: Array;
+ sharedInitFilter: Array;
+ customFilterHost: FilterObserver;
}
export class HeaderFiltersStore {
private provider: Result;
- context: FilterAPIv2;
+ context: FilterAPI;
- constructor(
- props: HeaderFiltersStoreProps,
- info: StaticInfo,
- dsViewState: Array | null
- ) {
- this.provider = this.createProvider(props, dsViewState);
+ constructor({
+ filterList,
+ filterChannelName,
+ headerInitFilter,
+ sharedInitFilter,
+ customFilterHost: filterObserver
+ }: HeaderFiltersStoreSpec) {
+ this.provider = this.createProvider(filterList, headerInitFilter);
this.context = {
- version: 2,
- parentChannelName: info.filtersChannelName ?? "",
- provider: this.provider
+ version: 3,
+ parentChannelName: filterChannelName,
+ provider: this.provider,
+ sharedInitFilter,
+ filterObserver
};
makeObservable(this, {
conditions: computed,
@@ -59,13 +62,13 @@ export class HeaderFiltersStore {
}
createProvider(
- props: HeaderFiltersStoreProps,
- dsViewState: Array | null
+ filterList: FilterListType[],
+ initFilter: Array
): Result {
return value(
new LegacyPv(
- props.filterList.map(f => f.filter),
- dsViewState
+ filterList.map(f => f.filter),
+ initFilter
)
);
}
@@ -77,6 +80,4 @@ export class HeaderFiltersStore {
return this.provider.value.setup();
}
-
- updateProps(): void {}
}
diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/BaseInputFilterStore.ts b/packages/shared/widget-plugin-filtering/src/stores/input/BaseInputFilterStore.ts
index 8dc62bfa6c..49b3eaa5e6 100644
--- a/packages/shared/widget-plugin-filtering/src/stores/input/BaseInputFilterStore.ts
+++ b/packages/shared/widget-plugin-filtering/src/stores/input/BaseInputFilterStore.ts
@@ -1,5 +1,5 @@
import { Big } from "big.js";
-import { ListAttributeValue } from "mendix";
+import { AttributeMetaData } from "mendix";
import { FilterCondition } from "mendix/filters";
import {
and,
@@ -23,8 +23,9 @@ import { Argument } from "./Argument";
type StateTuple = [Fn] | [Fn, V] | [Fn, V, V];
type Val = A["value"];
+
export class BaseInputFilterStore {
- protected _attributes: ListAttributeValue[] = [];
+ protected _attributes: AttributeMetaData[] = [];
private _filterFunction: Fn;
private _isFilterFunctionAdjustable: boolean = true;
arg1: A;
@@ -32,7 +33,7 @@ export class BaseInputFilterStore {
isInitialized = false;
defaultState: StateTuple>;
- constructor(arg1: A, arg2: A, initFn: Fn, attributes: ListAttributeValue[]) {
+ constructor(arg1: A, arg2: A, initFn: Fn, attributes: AttributeMetaData[]) {
this._attributes = attributes;
this.defaultState = [initFn];
this._filterFunction = initFn;
@@ -116,7 +117,7 @@ export class BaseInputFilterStore {
}
function getFilterCondition(
- listAttribute: ListAttributeValue,
+ listAttribute: AttributeMetaData,
value: T | undefined,
valueR: T | undefined,
operation: AllFunctions
diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/DateInputFilterStore.ts b/packages/shared/widget-plugin-filtering/src/stores/input/DateInputFilterStore.ts
index d04bf14a31..7a8807a803 100644
--- a/packages/shared/widget-plugin-filtering/src/stores/input/DateInputFilterStore.ts
+++ b/packages/shared/widget-plugin-filtering/src/stores/input/DateInputFilterStore.ts
@@ -1,4 +1,4 @@
-import { DateTimeFormatter, ListAttributeValue } from "mendix";
+import { AttributeMetaData, DateTimeFormatter, ListAttributeValue, SimpleFormatter } from "mendix";
import { AndCondition, FilterCondition, LiteralExpression } from "mendix/filters";
import {
and,
@@ -26,6 +26,7 @@ import { BaseInputFilterStore } from "./BaseInputFilterStore";
type DateFns = FilterFunctionGeneric | FilterFunctionNonValue | FilterFunctionBinary;
type StateTuple = [DateFns, Date | undefined, Date | undefined];
type InitState = [DateFns, Date | undefined, Date | undefined] | [DateFns, Date | undefined];
+type AttrMeta = AttributeMetaData & { formatter?: SimpleFormatter };
export class DateInputFilterStore
extends BaseInputFilterStore
@@ -36,8 +37,8 @@ export class DateInputFilterStore
private readonly rangeMarkerTag = "__RANGE_MARKER__";
private computedState: StateTuple;
- constructor(attributes: Array>, initCond: FilterCondition | null) {
- const { formatter } = attributes[0];
+ constructor(attributes: Array>, initCond: FilterCondition | null) {
+ const formatter = getFormatter(attributes[0]);
super(new DateArgument(formatter), new DateArgument(formatter), "equal", attributes);
// NOTE: some fields already become observable in `super`.
makeObservable(this, {
@@ -76,7 +77,7 @@ export class DateInputFilterStore
updateProps(attributes: ListAttributeValue[]): void {
if (!comparer.shallow(this._attributes, attributes)) {
this._attributes = attributes;
- const formatter = attributes.at(0)?.formatter;
+ const formatter = getFormatter(attributes[0] as AttributeMetaData);
this.arg1.updateProps(formatter as DateTimeFormatter);
this.arg2.updateProps(formatter as DateTimeFormatter);
}
@@ -103,7 +104,7 @@ export class DateInputFilterStore
}
private getCondition(
- attr: ListAttributeValue,
+ attr: AttributeMetaData,
filterFn: DateFns,
v1: Date | undefined,
v2: Date | undefined
@@ -121,7 +122,7 @@ export class DateInputFilterStore
}
private getAttrCondition(
- attr: ListAttributeValue,
+ attr: AttributeMetaData,
filterFn: Exclude,
date: Date | undefined
): [FilterCondition] | [] {
@@ -153,7 +154,7 @@ export class DateInputFilterStore
}
}
- private getRangeCondition(attr: ListAttributeValue, [start, end]: [Date, Date]): [FilterCondition] | [] {
+ private getRangeCondition(attr: AttributeMetaData, [start, end]: [Date, Date]): [FilterCondition] | [] {
const attrExp = attribute(attr.id);
return [
@@ -296,3 +297,23 @@ function subDay(date: Date): Date {
newDate.setUTCDate(newDate.getUTCDate() - 1);
return newDate;
}
+
+function getFormatter(attr: AttrMeta): SimpleFormatter {
+ if (attr.formatter) {
+ return attr.formatter;
+ }
+
+ return {
+ format: v => v?.toString() ?? "",
+ parse: v => {
+ const date = Date.parse(v);
+ if (isNaN(date)) {
+ return { valid: false };
+ }
+ return {
+ valid: true,
+ value: new Date(date)
+ };
+ }
+ };
+}
diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/NumberInputFilterStore.ts b/packages/shared/widget-plugin-filtering/src/stores/input/NumberInputFilterStore.ts
index a8fbbe08c5..18a302d02d 100644
--- a/packages/shared/widget-plugin-filtering/src/stores/input/NumberInputFilterStore.ts
+++ b/packages/shared/widget-plugin-filtering/src/stores/input/NumberInputFilterStore.ts
@@ -1,5 +1,5 @@
import { Big } from "big.js";
-import { ListAttributeValue } from "mendix";
+import { AttributeMetaData, ListAttributeValue, SimpleFormatter } from "mendix";
import { FilterCondition } from "mendix/filters";
import { action, comparer, makeObservable } from "mobx";
import { inputStateFromCond } from "../../condition-utils";
@@ -11,7 +11,8 @@ import { BaseInputFilterStore } from "./BaseInputFilterStore";
import { baseNames } from "./fn-mappers";
type NumFns = FilterFunctionGeneric | FilterFunctionNonValue | FilterFunctionBinary;
-type Formatter = ListAttributeValue["formatter"];
+type Formatter = SimpleFormatter;
+type AttrMeta = AttributeMetaData & { formatter?: SimpleFormatter };
export class NumberInputFilterStore
extends BaseInputFilterStore
@@ -20,9 +21,8 @@ export class NumberInputFilterStore
readonly storeType = "input";
readonly type = "number";
- constructor(attributes: Array>, initCond: FilterCondition | null) {
- let { formatter } = attributes[0];
- formatter = formatterFix(formatter);
+ constructor(attributes: AttrMeta[], initCond: FilterCondition | null) {
+ const formatter = formatterFix(attributes[0].formatter);
super(new NumberArgument(formatter), new NumberArgument(formatter), "equal", attributes);
makeObservable(this, {
updateProps: action,
@@ -79,11 +79,12 @@ export class NumberInputFilterStore
}
}
-function formatterFix(formatter: Formatter): Formatter {
+function formatterFix(formatter: Formatter | undefined): Formatter {
// Check formatter.parse to see if it is a valid formatter.
- if (formatter.parse("none")?.valid === false) {
+ if (formatter && formatter.parse("none")?.valid === false) {
return formatter;
}
+
// Create a new formatter that will handle the autonumber values.
return {
format: (value: Big) => {
diff --git a/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts b/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts
index 45ab2a30d9..31c720798b 100644
--- a/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts
+++ b/packages/shared/widget-plugin-filtering/src/stores/input/StringInputFilterStore.ts
@@ -1,4 +1,4 @@
-import { ListAttributeValue } from "mendix";
+import { AttributeMetaData, ListAttributeValue, SimpleFormatter } from "mendix";
import { FilterCondition } from "mendix/filters";
import { action, comparer, makeObservable } from "mobx";
import { inputStateFromCond } from "../../condition-utils";
@@ -15,6 +15,8 @@ import { BaseInputFilterStore } from "./BaseInputFilterStore";
import { baseNames } from "./fn-mappers";
type StrFns = FilterFunctionString | FilterFunctionGeneric | FilterFunctionNonValue | FilterFunctionBinary;
+type AttrMeta = AttributeMetaData & { formatter?: SimpleFormatter };
+
export class StringInputFilterStore
extends BaseInputFilterStore
implements String_InputFilterInterface
@@ -22,8 +24,8 @@ export class StringInputFilterStore
readonly storeType = "input";
readonly type = "string";
- constructor(attributes: Array>, initCond: FilterCondition | null) {
- const { formatter } = attributes[0];
+ constructor(attributes: AttrMeta[], initCond: FilterCondition | null) {
+ const formatter = getFormatter(attributes[0]);
super(new StringArgument(formatter), new StringArgument(formatter), "equal", attributes);
makeObservable(this, {
updateProps: action,
@@ -91,3 +93,13 @@ export class StringInputFilterStore
this.isInitialized = true;
}
}
+
+function getFormatter(attr: { formatter?: SimpleFormatter }): SimpleFormatter {
+ return (
+ attr.formatter ??
+ ({
+ format: v => v ?? "",
+ parse: v => ({ valid: true, value: v ?? "" })
+ } as SimpleFormatter)
+ );
+}
diff --git a/packages/shared/widget-plugin-filtering/src/stores/picker/BaseSelectStore.ts b/packages/shared/widget-plugin-filtering/src/stores/picker/BaseSelectStore.ts
index d31e08f06e..ca1b324fd0 100644
--- a/packages/shared/widget-plugin-filtering/src/stores/picker/BaseSelectStore.ts
+++ b/packages/shared/widget-plugin-filtering/src/stores/picker/BaseSelectStore.ts
@@ -40,7 +40,7 @@ export class BaseSelectStore {
}
fromJSON(json: FilterData): void {
- if (json === null || isInputData(json)) {
+ if (json == null || isInputData(json)) {
return;
}
this.setSelected(json);
diff --git a/packages/shared/widget-plugin-filtering/src/typings/FilterObserver.ts b/packages/shared/widget-plugin-filtering/src/typings/FilterObserver.ts
new file mode 100644
index 0000000000..c572ed5d38
--- /dev/null
+++ b/packages/shared/widget-plugin-filtering/src/typings/FilterObserver.ts
@@ -0,0 +1,17 @@
+import { FilterCondition } from "mendix/filters";
+import { FilterData, FiltersSettingsMap } from "./settings";
+
+export interface Filter {
+ toJSON(): FilterData;
+ fromJSON(data: FilterData): void;
+ condition: FilterCondition | undefined;
+ setup?: () => void | void;
+}
+
+export interface FilterObserver {
+ get settings(): FiltersSettingsMap;
+ set settings(settings: FiltersSettingsMap);
+ conditions: Array;
+ observe(key: string, filter: Filter): void;
+ unobserve(key: string): void;
+}
diff --git a/packages/shared/widget-plugin-filtering/src/typings/settings.ts b/packages/shared/widget-plugin-filtering/src/typings/settings.ts
index 7f4f3b6614..1b6cb7a373 100644
--- a/packages/shared/widget-plugin-filtering/src/typings/settings.ts
+++ b/packages/shared/widget-plugin-filtering/src/typings/settings.ts
@@ -4,6 +4,6 @@ export type InputData = [Fn, string | null, string | null];
export type SelectData = string[];
-export type FilterData = InputData | SelectData | null;
+export type FilterData = InputData | SelectData | null | undefined;
export type FiltersSettingsMap = Map;
diff --git a/packages/shared/widget-plugin-filtering/src/useDefaultValue.ts b/packages/shared/widget-plugin-filtering/src/useDefaultValue.ts
deleted file mode 100644
index d61d99ac45..0000000000
--- a/packages/shared/widget-plugin-filtering/src/useDefaultValue.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { DynamicValue } from "mendix";
-import { useRef } from "react";
-
-export function useDefaultValue(defaultValueProp?: DynamicValue): T | undefined | null {
- const defaultValueRef = useRef(null);
-
- if (defaultValueProp?.status !== "loading" && defaultValueRef.current === null) {
- defaultValueRef.current = defaultValueProp?.value;
- }
-
- return defaultValueRef.current;
-}
diff --git a/packages/shared/widget-plugin-mobx-kit/src/disposeBatch.ts b/packages/shared/widget-plugin-mobx-kit/src/disposeBatch.ts
index b3d899c210..7b0caf4fe4 100644
--- a/packages/shared/widget-plugin-mobx-kit/src/disposeBatch.ts
+++ b/packages/shared/widget-plugin-mobx-kit/src/disposeBatch.ts
@@ -1,8 +1,12 @@
-export function disposeBatch(): [add: (fn: () => void) => void, disposeAll: () => void] {
+type MaybeFn = (() => void) | void;
+
+export function disposeBatch(): [add: (fn: MaybeFn) => void, disposeAll: () => void] {
const disposers = new Set<() => void>();
- const add = (fn: () => void): void => {
- disposers.add(fn);
+ const add = (fn: MaybeFn): void => {
+ if (fn) {
+ disposers.add(fn);
+ }
};
const disposeAll = (): void => {
diff --git a/packages/shared/widget-plugin-mobx-kit/src/setupable.ts b/packages/shared/widget-plugin-mobx-kit/src/setupable.ts
new file mode 100644
index 0000000000..cb29e7ccb9
--- /dev/null
+++ b/packages/shared/widget-plugin-mobx-kit/src/setupable.ts
@@ -0,0 +1,3 @@
+export interface ISetupable {
+ setup(): void | (() => void);
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 37c6311295..4403b81b35 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1198,6 +1198,9 @@ importers:
'@mendix/widget-plugin-hooks':
specifier: workspace:*
version: link:../../shared/widget-plugin-hooks
+ '@mendix/widget-plugin-mobx-kit':
+ specifier: workspace:^
+ version: link:../../shared/widget-plugin-mobx-kit
'@mendix/widget-plugin-platform':
specifier: workspace:*
version: link:../../shared/widget-plugin-platform
@@ -2535,6 +2538,9 @@ importers:
'@mendix/widget-plugin-hooks':
specifier: workspace:*
version: link:../widget-plugin-hooks
+ '@mendix/widget-plugin-mobx-kit':
+ specifier: workspace:^
+ version: link:../widget-plugin-mobx-kit
'@mendix/widget-plugin-platform':
specifier: workspace:*
version: link:../widget-plugin-platform