diff --git a/packages/modules/data-widgets/package.json b/packages/modules/data-widgets/package.json index 96565880bc..6bf8716182 100644 --- a/packages/modules/data-widgets/package.json +++ b/packages/modules/data-widgets/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/data-widgets", "moduleName": "Data Widgets", - "version": "2.31.1", + "version": "3.0.0", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", "private": true, diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/package.json b/packages/pluggableWidgets/datagrid-date-filter-web/package.json index 3520343102..d8d1d611dd 100644 --- a/packages/pluggableWidgets/datagrid-date-filter-web/package.json +++ b/packages/pluggableWidgets/datagrid-date-filter-web/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/datagrid-date-filter-web", "widgetName": "DatagridDateFilter", - "version": "2.11.2", + "version": "3.0.0", "description": "", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.editorConfig.ts b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.editorConfig.ts index 134c00a117..27bcfee192 100644 --- a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.editorConfig.ts +++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.editorConfig.ts @@ -1,3 +1,4 @@ +import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools"; import { betweenIcon, betweenIconDark, @@ -23,22 +24,23 @@ import { import { ContainerProps, ImageProps, + structurePreviewPalette, StructurePreviewProps, - text, - structurePreviewPalette + text } from "@mendix/widget-plugin-platform/preview/structure-preview-api"; -import { hidePropertiesIn, hidePropertyIn, Properties } from "@mendix/pluggable-widgets-tools"; import { DatagridDateFilterPreviewProps, DefaultFilterEnum } from "../typings/DatagridDateFilterProps"; -export function getProperties( - values: DatagridDateFilterPreviewProps, - defaultProperties: Properties, - platform: "web" | "desktop" -): Properties { +export function getProperties(values: DatagridDateFilterPreviewProps, defaultProperties: Properties): Properties { if (!values.adjustable) { hidePropertyIn(defaultProperties, values, "screenReaderButtonCaption"); } + + if (values.attrChoice === "auto") { + hidePropertyIn(defaultProperties, values, "attributes"); + hidePropertyIn(defaultProperties, {} as { linkedDs: unknown }, "linkedDs"); + } + if (values.defaultFilter !== "between") { hidePropertiesIn(defaultProperties, values, [ "defaultStartDate", @@ -49,13 +51,7 @@ export function getProperties( } else { hidePropertiesIn(defaultProperties, values, ["defaultValue", "valueAttribute"]); } - if (platform === "web") { - if (!values.advanced) { - hidePropertiesIn(defaultProperties, values, ["onChange", "valueAttribute"]); - } - } else { - hidePropertyIn(defaultProperties, values, "advanced"); - } + return defaultProperties; } diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.tsx b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.tsx index d3e977451d..3b5ec9cc5d 100644 --- a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.tsx +++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.tsx @@ -4,10 +4,18 @@ import { DatagridDateFilterContainerProps } from "../typings/DatagridDateFilterP import { Container } from "./components/DateFilterContainer"; import { withDateFilterAPI } from "./hocs/withDateFilterAPI"; import { isLoadingDefaultValues } from "./utils/widget-utils"; +import { withDateLinkedAttributes } from "./hocs/withDateLinkedAttributes"; const container = withPreloader(Container, isLoadingDefaultValues); -const Widget = withDateFilterAPI(container); +const FilterAuto = withDateFilterAPI(container); +const FilterLinked = withDateLinkedAttributes(container); export default function DatagridDateFilter(props: DatagridDateFilterContainerProps): ReactElement | null { - return ; + const isAuto = props.attrChoice === "auto"; + + if (isAuto) { + return ; + } + + return ; } diff --git a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.xml b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.xml index 39ed04bf46..bc9800a53f 100644 --- a/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.xml +++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/DatagridDateFilter.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-date-filter-web/src/components/__tests__/DatagridDateFilter.spec.tsx b/packages/pluggableWidgets/datagrid-date-filter-web/src/components/__tests__/DatagridDateFilter.spec.tsx index 6cfbed5d16..3deebdf5fe 100644 --- a/packages/pluggableWidgets/datagrid-date-filter-web/src/components/__tests__/DatagridDateFilter.spec.tsx +++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/components/__tests__/DatagridDateFilter.spec.tsx @@ -1,8 +1,8 @@ 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 { actionValue, @@ -15,11 +15,7 @@ import { createContext, createElement } from "react"; import DatagridDateFilter from "../../DatagridDateFilter"; import { DatagridDateFilterContainerProps } from "../../../typings/DatagridDateFilterProps"; import { MXGlobalObject, MXSessionConfig } from "../../../typings/global"; - -interface StaticInfo { - name: string; - filtersChannelName: string; -} +import { FilterObserver } from "@mendix/widget-plugin-filtering/typings/FilterObserver"; function createMXObjectMock( code: string, @@ -54,13 +50,17 @@ const commonProps: DatagridDateFilterContainerProps = { advanced: false }; -const headerFilterStoreInfo: StaticInfo = { - name: commonProps.name, - filtersChannelName: "" -}; - const mxObject = createMXObjectMock("en_US", "en-US"); +const mockSpec = (spec: Partial): HeaderFiltersStoreSpec => ({ + filterList: [], + filterChannelName: "datagrid/1", + headerInitFilter: [], + sharedInitFilter: [], + customFilterHost: {} as FilterObserver, + ...spec +}); + describe("Date Filter", () => { describe("with single instance", () => { afterEach(() => { @@ -69,13 +69,13 @@ describe("Date Filter", () => { describe("with single attribute", () => { beforeEach(() => { - const props: HeaderFiltersStoreProps = { + const spec = mockSpec({ filterList: [ { filter: new ListAttributeValueBuilder().withType("DateTime").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 ); (window as any).mx = mxObject; @@ -144,7 +144,7 @@ describe("Date Filter", () => { describe("with double attributes", () => { beforeAll(() => { - const props: HeaderFiltersStoreProps = { + const spec = mockSpec({ filterList: [ { filter: new ListAttributeValueBuilder() @@ -161,9 +161,9 @@ describe("Date 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 ); (window as any).mx = mxObject; @@ -184,13 +184,13 @@ describe("Date Filter", () => { describe("with wrong attribute's type", () => { beforeAll(() => { - const props: HeaderFiltersStoreProps = { + const spec = mockSpec({ filterList: [ { filter: new ListAttributeValueBuilder().withType("Decimal").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 ); (window as any).mx = mxObject; @@ -211,7 +211,7 @@ describe("Date Filter", () => { describe("with wrong multiple attributes' types", () => { beforeAll(() => { - const props: HeaderFiltersStoreProps = { + const spec = mockSpec({ filterList: [ { filter: new ListAttributeValueBuilder() @@ -228,9 +228,9 @@ describe("Date 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 ); (window as any).mx = mxObject; @@ -267,13 +267,13 @@ describe("Date Filter", () => { describe("with multiple instances", () => { beforeAll(() => { - const props: HeaderFiltersStoreProps = { + const spec = mockSpec({ filterList: [ { filter: new ListAttributeValueBuilder().withType("DateTime").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 ); (window as any).mx = mxObject; @@ -296,13 +296,13 @@ describe("Date Filter", () => { describe("with session config", () => { beforeEach(() => { - const props: HeaderFiltersStoreProps = { + const spec = mockSpec({ filterList: [ { filter: new ListAttributeValueBuilder().withType("DateTime").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 ); }); 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/src/package.xml b/packages/pluggableWidgets/datagrid-date-filter-web/src/package.xml index c523a2756a..78fe98c72a 100644 --- a/packages/pluggableWidgets/datagrid-date-filter-web/src/package.xml +++ b/packages/pluggableWidgets/datagrid-date-filter-web/src/package.xml @@ -1,6 +1,6 @@ - + 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/package.json b/packages/pluggableWidgets/datagrid-dropdown-filter-web/package.json index 888026f3fc..b076e7fd62 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/package.json +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/datagrid-dropdown-filter-web", "widgetName": "DatagridDropdownFilter", - "version": "2.10.1", + "version": "3.0.0", "description": "", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", @@ -41,6 +41,7 @@ "verify": "rui-verify-package-format" }, "dependencies": { + "@mendix/widget-plugin-dropdown-filter": "workspace:^", "@mendix/widget-plugin-external-events": "workspace:*", "@mendix/widget-plugin-filtering": "workspace:*", "classnames": "^2.3.2" diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorConfig.ts b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorConfig.ts index cd2dbe6373..fd528df8a8 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorConfig.ts +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorConfig.ts @@ -1,4 +1,4 @@ -import { hidePropertyIn, Problem, Properties } from "@mendix/pluggable-widgets-tools"; +import { hidePropertiesIn, hidePropertyIn, Problem, Properties } from "@mendix/pluggable-widgets-tools"; import { chevronDownIcon, chevronDownIconDark } from "@mendix/widget-plugin-filtering/preview/editor-preview-icons"; import { ContainerProps, @@ -13,8 +13,10 @@ export function getProperties(values: DatagridDropdownFilterPreviewProps, defaul const showSelectedItemsStyle = values.filterable && values.multiSelect; const showSelectionMethod = showSelectedItemsStyle && values.selectedItemsStyle === "boxes"; - if (values.auto) { - hidePropertyIn(defaultProperties, values, "filterOptions"); + if (values.baseType === "attr") { + defaultProperties = attrGroupProperties(values, defaultProperties); + } else { + hidePropertiesIn(defaultProperties, values, ["attr", "attrChoice", "filterOptions", "auto"]); } if (values.filterable) { @@ -33,6 +35,21 @@ export function getProperties(values: DatagridDropdownFilterPreviewProps, defaul return defaultProperties; } +function attrGroupProperties(values: DatagridDropdownFilterPreviewProps, defaultProperties: Properties): Properties { + hidePropertiesIn(defaultProperties, values, ["refEntity", "refOptions", "refCaption", "fetchOptionsLazy"]); + + if (values.attrChoice === "auto") { + hidePropertyIn(defaultProperties, {} as { linkedDs: unknown }, "linkedDs"); + hidePropertyIn(defaultProperties, values, "attr"); + } + + if (values.auto) { + hidePropertyIn(defaultProperties, values, "filterOptions"); + } + + return defaultProperties; +} + export const getPreview = (values: DatagridDropdownFilterPreviewProps, isDarkMode: boolean): StructurePreviewProps => { const palette = structurePreviewPalette[isDarkMode ? "dark" : "light"]; return { diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorPreview.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorPreview.tsx index 9d42df5e76..59596a465a 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorPreview.tsx +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.editorPreview.tsx @@ -1,10 +1,10 @@ import { enableStaticRendering } from "mobx-react-lite"; -enableStaticRendering(true); - import { createElement, ReactElement } from "react"; import { DatagridDropdownFilterPreviewProps } from "../typings/DatagridDropdownFilterProps"; import { parseStyle } from "@mendix/widget-plugin-platform/preview/parse-style"; -import { Select } from "@mendix/widget-plugin-filtering/controls/select/Select"; +import { Select } from "@mendix/widget-plugin-dropdown-filter/controls/select/Select"; + +enableStaticRendering(true); function Preview(props: DatagridDropdownFilterPreviewProps): ReactElement { return ( diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx index 5435f96422..b116cc4667 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.tsx @@ -1,42 +1,17 @@ import { createElement, ReactElement } from "react"; import { withPreloader } from "@mendix/widget-plugin-platform/hoc/withPreloader"; import { DatagridDropdownFilterContainerProps } from "../typings/DatagridDropdownFilterProps"; -import { StaticFilterContainer } from "./components/StaticFilterContainer"; -import { withSelectFilterAPI, Select_FilterAPIv2 } from "./hocs/withSelectFilterAPI"; -import { RefFilterContainer } from "./components/RefFilterContainer"; +import { AttrFilter } from "./components/AttrFilter"; +import { RefFilter } from "./components/RefFilter"; -function Container(props: DatagridDropdownFilterContainerProps & Select_FilterAPIv2): React.ReactElement { - const commonProps = { - ariaLabel: props.ariaLabel?.value, - className: props.class, - tabIndex: props.tabIndex, - styles: props.style, - onChange: props.onChange, - valueAttribute: props.valueAttribute, - parentChannelName: props.parentChannelName, - name: props.name, - multiselect: props.multiSelect, - emptyCaption: props.emptyOptionCaption?.value, - defaultValue: props.defaultValue?.value, - filterable: props.filterable, - selectionMethod: props.selectionMethod, - selectedItemsStyle: props.selectedItemsStyle, - clearable: props.clearable - }; - - if (props.filterStore.storeType === "refselect") { - return ; +function Container(props: DatagridDropdownFilterContainerProps): ReactElement { + if (props.baseType === "attr") { + return ; } - return ( - - ); + return ; } -const container = withPreloader(Container, props => props.defaultValue?.status === "loading"); - -const Widget = withSelectFilterAPI(container); +const DatagridDropdownFilter = withPreloader(Container, props => props.defaultValue?.status === "loading"); -export default function DatagridDropdownFilter(props: DatagridDropdownFilterContainerProps): ReactElement { - return ; -} +export default DatagridDropdownFilter; diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml index 5e32a4a329..1da95f1c1b 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/DatagridDropdownFilter.xml @@ -7,16 +7,41 @@ https://docs.mendix.com/appstore/modules/data-grid-2#7-2-drop-down-filter - + + + Filter by + + + Attribute + Association + + + + Datasource to Filter + + + + + + Attribute config + "Auto" works only when the widget is placed in a Data grid column. + + Auto + Custom + + + + Attribute + + + + + + Automatic options Show options based on the references or the enumeration values and captions. - - Default value - Empty option caption will be shown by default or if configured default value matches none of the options - - Options @@ -34,6 +59,40 @@ + + + + + Entity + Set the entity to enable filtering over association. + + + + + + + Selectable objects + The options to show in the Drop-down filter widget. + + + Caption + + + + + + + Use lazy load + Lazy loading enables faster parent loading, but with personalization enabled, value restoration will be limited. + + + + + + Default value + Empty option caption will be shown by default or if configured default value matches none of the options + + Filterable diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/AttrFilter.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/AttrFilter.tsx new file mode 100644 index 0000000000..1c21f1aa04 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/AttrFilter.tsx @@ -0,0 +1,34 @@ +import { ReactElement, createElement } from "react"; +import { StaticFilterContainer } from "@mendix/widget-plugin-dropdown-filter/containers/StaticFilterContainer"; +import { withFilterAPI } from "@mendix/widget-plugin-filtering/helpers/withFilterAPI"; +import { withParentProvidedEnumStore } from "../hocs/withParentProvidedEnumStore"; +import { DatagridDropdownFilterContainerProps } from "../../typings/DatagridDropdownFilterProps"; +import { withLinkedEnumStore } from "../hocs/withLinkedEnumStore"; +import { EnumFilterProps } from "./typings"; + +export function AttrFilter(props: DatagridDropdownFilterContainerProps): ReactElement { + if (props.auto) { + return ; + } + + return ; +} + +const AutoAttrFilter = withParentProvidedEnumStore(Connector); + +const LinkedAttrFilter = withFilterAPI(withLinkedEnumStore(Connector)); + +function Connector(props: DatagridDropdownFilterContainerProps & EnumFilterProps): ReactElement { + return ( + + ); +} diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/RefFilter.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/RefFilter.tsx new file mode 100644 index 0000000000..3f61855887 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/RefFilter.tsx @@ -0,0 +1,22 @@ +import { createElement, ReactElement } from "react"; +import { withLinkedRefStore } from "../hocs/withLinkedRefStore"; +import { RefFilterContainer } from "@mendix/widget-plugin-dropdown-filter/containers/RefFilterContainer"; +import { RefFilterProps } from "./typings"; +import { DatagridDropdownFilterContainerProps } from "../../typings/DatagridDropdownFilterProps"; + +function Connector(props: DatagridDropdownFilterContainerProps & RefFilterProps): ReactElement { + return ( + + ); +} + +export const RefFilter = withLinkedRefStore(Connector); 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 deleted file mode 100644 index 2306dd4a38..0000000000 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/DataGridDropdownFilter.spec.tsx +++ /dev/null @@ -1,484 +0,0 @@ -import "@testing-library/jest-dom"; -import { FilterAPIv2 } from "@mendix/widget-plugin-filtering/context"; -import { - HeaderFiltersStore, - HeaderFiltersStoreProps -} 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; -} - -const commonProps = { - class: "filter-custom-class", - tabIndex: 0, - name: "filter-test", - advanced: false, - groupKey: "dropdown-filter", - filterable: false, - clearable: true, - selectionMethod: "checkbox" as const, - selectedItemsStyle: "text" as const -}; - -const headerFilterStoreInfo: StaticInfo = { - name: commonProps.name, - filtersChannelName: "" -}; - -const consoleError = global.console.error; -jest.spyOn(global.console, "error").mockImplementation((...args: any[]) => { - const [msg] = args; - - if (typeof msg === "string" && msg.startsWith("downshift:")) { - return; - } - - consoleError(...args); -}); - -describe("Dropdown Filter", () => { - describe("with single instance", () => { - afterEach(() => { - delete (global as any)["com.mendix.widgets.web.UUID"]; - }); - - describe("with single attribute", () => { - function mockCtx(universe: string[]): void { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withUniverse(universe) - .withType("Enum") - .withFilterable(true) - .withFormatter( - value => value, - () => console.log("Parsed") - ) - .build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - } - beforeEach(() => { - mockCtx(["enum_value_1", "enum_value_2"]); - }); - - describe("with auto options", () => { - it("loads correct values from universe", async () => { - const filter = render( - - ); - - const trigger = filter.getByRole("combobox"); - - await fireEvent.click(trigger); - - const items = filter.getAllByRole("option"); - - items.forEach((item, index) => { - if (index === 0) { - return; - } - expect(item.textContent).toEqual(`enum_value_${index}`); - }); - }); - }); - - describe("DOM structure", () => { - it("renders correctly", () => { - const { asFragment } = render( - - ); - - expect(asFragment()).toMatchSnapshot(); - }); - }); - - describe("with defaultValue", () => { - it("initialize component with defaultValue", () => { - render( - ("enum_value_1")} - /> - ); - - expect(screen.getByRole("combobox")).toHaveAccessibleName("enum_value_1"); - }); - - it("don't sync defaultValue with state when defaultValue changes from undefined to string", async () => { - const { rerender } = render( - ("")} - /> - ); - - await waitFor(() => { - expect(screen.getByRole("combobox")).toHaveAccessibleName("Select"); - }); - - // “Real” context causes widgets to re-renders multiple times, replicate this in mocked context. - rerender( - ("")} - /> - ); - rerender( - ("enum_value_1")} - /> - ); - - await waitFor(() => { - expect(screen.getByRole("combobox")).toHaveAccessibleName("Select"); - }); - }); - - it("don't sync defaultValue with state when defaultValue changes from string to undefined", async () => { - mockCtx(["xyz", "abc"]); - const { rerender } = render( - ("xyz")} - /> - ); - - expect(screen.getByRole("combobox")).toHaveAccessibleName("xyz"); - - // “Real” context causes widgets to re-renders multiple times, replicate this in mocked context. - rerender( - ("xyz")} - /> - ); - rerender( - - ); - - await waitFor(() => { - expect(screen.getByRole("combobox")).toHaveAccessibleName("xyz"); - }); - }); - }); - - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - }); - - describe("with multiple attributes", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withId("attribute1") - .withUniverse(["enum_value_1", "enum_value_2"]) - .withType("Enum") - .withFilterable(true) - .withFormatter( - value => value, - () => console.log("Parsed") - ) - .build() - }, - { - filter: new ListAttributeValueBuilder() - .withId("attribute2") - .withUniverse([true, false]) - .withType("Boolean") - .withFilterable(true) - .withFormatter( - value => (value ? "Yes" : "No"), - () => console.log("Parsed") - ) - .build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); - - describe("with auto options", () => { - it("loads correct values from universes", async () => { - const filter = render( - - ); - - const trigger = filter.getByRole("combobox"); - await fireEvent.click(trigger); - - expect(filter.getAllByRole("option").map(item => item.textContent)).toStrictEqual([ - "None", - "enum_value_1", - "enum_value_2", - "Yes", - "No" - ]); - }); - }); - - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - }); - - describe("with wrong attribute's type", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - 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( - headerFilterStore.context - ); - }); - - it("renders error message", () => { - const { container } = render( - - ); - - expect(container.querySelector(".alert")?.textContent).toBe( - "Unable to get filter store. Check parent widget configuration." - ); - }); - - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - }); - - describe("with wrong multiple attributes' types", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withId("attribute1") - .withType("String") - .withFilterable(true) - .build() - }, - { - filter: new ListAttributeValueBuilder() - .withId("attribute2") - .withType("Decimal") - .withFilterable(true) - .build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); - - it("renders error message", () => { - const { container } = render( - - ); - - expect(container.querySelector(".alert")?.textContent).toBe( - "Unable to get filter store. Check parent widget configuration." - ); - }); - - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - }); - - describe("with no context", () => { - beforeAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - }); - - it("renders error message", () => { - const { container } = render( - - ); - - expect(container.querySelector(".alert")?.textContent).toBe( - "The filter widget must be placed inside the column or header of the Data grid 2.0 or inside header of the Gallery widget." - ); - }); - }); - - describe("with invalid values", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withUniverse(["enum_value_1", "enum_value_2"]) - .withType("Enum") - .withFilterable(true) - .build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); - - it("renders error message", () => { - const { container } = render( - ("wrong value"), - value: dynamicValue("enum_value_3") - } - ]} - /> - ); - - expect(container.querySelector(".alert")?.textContent).toBe("Invalid option value: 'enum_value_3'"); - }); - }); - - describe("with multiple invalid values", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withUniverse(["enum_value_1", "enum_value_2"]) - .withType("Enum") - .withFilterable(true) - .build() - }, - { - filter: new ListAttributeValueBuilder() - .withUniverse([true, false]) - .withType("Boolean") - .withFilterable(true) - .build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); - - it("renders error message", () => { - const { container } = render( - ("wrong enum value"), - value: dynamicValue("enum_value_3") - }, - { - caption: dynamicValue("wrong boolean value"), - value: dynamicValue("no") - } - ]} - /> - ); - - expect(container.querySelector(".alert")?.textContent).toBe("Invalid option value: 'enum_value_3'"); - }); - }); - }); - - describe("with multiple instances", () => { - beforeAll(() => { - const props: HeaderFiltersStoreProps = { - filterList: [ - { - filter: new ListAttributeValueBuilder() - .withUniverse(["enum_value_1", "enum_value_2"]) - .withType("Enum") - .withFilterable(true) - .withFormatter( - value => value, - () => console.log("Parsed") - ) - .build() - } - ] - }; - const headerFilterStore = new HeaderFiltersStore(props, headerFilterStoreInfo, null); - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext( - headerFilterStore.context - ); - }); - - it("renders with a unique id", () => { - const { asFragment: fragment1 } = render( - - ); - const { asFragment: fragment2 } = render( - - ); - - expect(fragment1().querySelector("button")?.getAttribute("aria-controls")).not.toBe( - fragment2().querySelector("button")?.getAttribute("aria-controls") - ); - }); - - afterAll(() => { - (window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = undefined; - delete (global as any)["com.mendix.widgets.web.UUID"]; - }); - }); -}); diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/__snapshots__/DataGridDropdownFilter.spec.tsx.snap b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/__snapshots__/DataGridDropdownFilter.spec.tsx.snap deleted file mode 100644 index 28606ed0ee..0000000000 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/__tests__/__snapshots__/DataGridDropdownFilter.spec.tsx.snap +++ /dev/null @@ -1,59 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Dropdown Filter with single instance with single attribute DOM structure renders correctly 1`] = ` - -

- - -
- -`; diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/typings.ts b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/typings.ts new file mode 100644 index 0000000000..f936c3472c --- /dev/null +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/components/typings.ts @@ -0,0 +1,12 @@ +import { RefFilterStore } from "@mendix/widget-plugin-dropdown-filter/stores/RefFilterStore"; +import { StaticSelectFilterStore } from "@mendix/widget-plugin-dropdown-filter/stores/StaticSelectFilterStore"; + +export interface EnumFilterProps { + filterStore: StaticSelectFilterStore; + parentChannelName?: string; +} + +export interface RefFilterProps { + filterStore: RefFilterStore; + parentChannelName?: string; +} diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withLinkedEnumStore.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withLinkedEnumStore.tsx new file mode 100644 index 0000000000..f246242a5e --- /dev/null +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withLinkedEnumStore.tsx @@ -0,0 +1,22 @@ +import { FilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { createElement, FC } from "react"; +import { EnumStoreProvider } from "@mendix/widget-plugin-filtering/custom-filter-api/EnumStoreProvider"; +import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup"; +import { AttributeMetaData } from "mendix"; +import { EnumFilterProps } from "../components/typings"; + +interface RequiredProps { + attr: AttributeMetaData; + name: string; +} + +export function withLinkedEnumStore

( + Component: FC

+): FC

{ + return function ProviderHost(props) { + const { store } = useSetup( + () => new EnumStoreProvider(props.filterAPI, { attributes: [props.attr], dataKey: props.name }) + ); + return ; + }; +} diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withLinkedRefStore.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withLinkedRefStore.tsx new file mode 100644 index 0000000000..e07634f95d --- /dev/null +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withLinkedRefStore.tsx @@ -0,0 +1,90 @@ +import { useEffect, createElement } from "react"; +import { Alert } from "@mendix/widget-plugin-component-kit/Alert"; +import { RefFilterStore } from "@mendix/widget-plugin-dropdown-filter/stores/RefFilterStore"; +import { FilterAPI, useFilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { BaseStoreProvider } from "@mendix/widget-plugin-filtering/custom-filter-api/BaseStoreProvider"; +import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/props-gate"; +import { GateProvider } from "@mendix/widget-plugin-mobx-kit/GateProvider"; +import { useConst } from "@mendix/widget-plugin-mobx-kit/react/useConst"; +import { ListValue, ListAttributeValue, AssociationMetaData } from "mendix"; +import { DatagridDropdownFilterContainerProps } from "../../typings/DatagridDropdownFilterProps"; +import { useSetup } from "@mendix/widget-plugin-mobx-kit/react/useSetup"; +import { RefFilterProps } from "../components/typings"; + +type WidgetProps = Pick; + +export interface RequiredProps { + name: string; + refEntity: AssociationMetaData; + refOptions: ListValue; + refCaption: ListAttributeValue; + searchAttrId: ListAttributeValue["id"]; +} + +type Component

= (props: P) => React.ReactElement; + +export function withLinkedRefStore

(Cmp: Component

): Component

{ + function StoreProvider(props: P & { filterAPI: FilterAPI }): React.ReactElement { + const gate = useGate(props); + const provider = useSetup(() => new RefStoreProvider(props.filterAPI, gate)); + return ; + } + return function FilterAPIProvider(props) { + const api = useFilterAPI(); + + if (api.hasError) { + return {api.error.message}; + } + + return ; + }; +} + +function mapProps(props: WidgetProps): RequiredProps { + if (!props.refEntity) { + throw new Error("RefFilterStoreProvider: refEntity is required"); + } + + if (!props.refOptions) { + throw new Error("RefFilterStoreProvider: refOptions is required"); + } + + if (!props.refCaption) { + throw new Error("RefFilterStoreProvider: refCaption is required"); + } + return { + name: props.name, + refEntity: props.refEntity, + refOptions: props.refOptions, + refCaption: props.refCaption, + searchAttrId: props.refCaption.id + }; +} + +function useGate(props: WidgetProps): DerivedPropsGate { + const gp = useConst(() => new GateProvider(mapProps(props))); + useEffect(() => { + gp.setProps(mapProps(props)); + }); + return gp.gate; +} + +class RefStoreProvider extends BaseStoreProvider { + protected _store: RefFilterStore; + protected filterAPI: FilterAPI; + readonly dataKey: string; + + constructor(filterAPI: FilterAPI, gate: DerivedPropsGate) { + super(); + this.filterAPI = filterAPI; + this.dataKey = gate.props.name; + this._store = new RefFilterStore({ + gate, + initCond: this.findInitFilter(filterAPI.sharedInitFilter, this.dataKey) + }); + } + + get store(): RefFilterStore { + return this._store; + } +} diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withParentProvidedEnumStore.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withParentProvidedEnumStore.tsx new file mode 100644 index 0000000000..e80b89aed2 --- /dev/null +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withParentProvidedEnumStore.tsx @@ -0,0 +1,48 @@ +import { Alert } from "@mendix/widget-plugin-component-kit/Alert"; +import { useRef, createElement } from "react"; +import { useFilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { APIError, EMISSINGSTORE, EStoreTypeMisMatch } from "@mendix/widget-plugin-filtering/errors"; +import { Result, error, value } from "@mendix/widget-plugin-filtering/result-meta"; +import { EnumFilterProps } from "../components/typings"; + +export function withParentProvidedEnumStore

( + Component: (props: P & EnumFilterProps) => React.ReactElement +): (props: P) => React.ReactElement { + return function FilterAPIProvider(props: P): React.ReactElement { + const api = useEnumFilterAPI(); + if (api.hasError) { + return {api.error.message}; + } + + return ( + + ); + }; +} + +function useEnumFilterAPI(): Result { + const ctx = useFilterAPI(); + const slctAPI = useRef(); + + if (ctx.hasError) { + return error(ctx.error); + } + + const api = ctx.value; + + if (api.provider.hasError) { + return error(api.provider.error); + } + + const store = api.provider.value.type === "direct" ? api.provider.value.store : null; + + if (store === null) { + return error(EMISSINGSTORE); + } + + if (store.storeType !== "select") { + return error(EStoreTypeMisMatch("dropdown filter", store.arg1.type)); + } + + return value((slctAPI.current ??= { filterStore: store, parentChannelName: api.parentChannelName })); +} diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withSelectFilterAPI.tsx b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withSelectFilterAPI.tsx deleted file mode 100644 index f8f6a74f88..0000000000 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/hocs/withSelectFilterAPI.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Alert } from "@mendix/widget-plugin-component-kit/Alert"; -import { Select_FilterAPIv2, useSelectFilterAPI } from "@mendix/widget-plugin-filtering/helpers/useSelectFilterAPI"; -import { createElement } from "react"; - -export { Select_FilterAPIv2 }; - -export function withSelectFilterAPI

( - Component: (props: P & Select_FilterAPIv2) => React.ReactElement -): (props: P) => React.ReactElement { - return function FilterAPIProvider(props: P): React.ReactElement { - const api = useSelectFilterAPI(props); - if (api.hasError) { - return {api.error.message}; - } - - return ( - - ); - }; -} diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/package.xml b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/package.xml index 800d1df062..fc38a0b891 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/package.xml +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/src/package.xml @@ -1,6 +1,6 @@ - + diff --git a/packages/pluggableWidgets/datagrid-dropdown-filter-web/typings/DatagridDropdownFilterProps.d.ts b/packages/pluggableWidgets/datagrid-dropdown-filter-web/typings/DatagridDropdownFilterProps.d.ts index c1c4cd5e41..a443bd6589 100644 --- a/packages/pluggableWidgets/datagrid-dropdown-filter-web/typings/DatagridDropdownFilterProps.d.ts +++ b/packages/pluggableWidgets/datagrid-dropdown-filter-web/typings/DatagridDropdownFilterProps.d.ts @@ -4,7 +4,11 @@ * @author Mendix Widgets Framework Team */ import { CSSProperties } from "react"; -import { ActionValue, DynamicValue, EditableValue } from "mendix"; +import { ActionValue, AssociationMetaData, AttributeMetaData, DynamicValue, EditableValue, ListValue, ListAttributeValue } from "mendix"; + +export type BaseTypeEnum = "attr" | "ref"; + +export type AttrChoiceEnum = "auto" | "linked"; export interface FilterOptionsType { caption: DynamicValue; @@ -25,9 +29,16 @@ export interface DatagridDropdownFilterContainerProps { class: string; style?: CSSProperties; tabIndex?: number; + baseType: BaseTypeEnum; + attrChoice: AttrChoiceEnum; + attr: AttributeMetaData; auto: boolean; - defaultValue?: DynamicValue; filterOptions: FilterOptionsType[]; + refEntity: AssociationMetaData; + refOptions?: ListValue; + refCaption?: ListAttributeValue; + fetchOptionsLazy: boolean; + defaultValue?: DynamicValue; filterable: boolean; multiSelect: boolean; emptyOptionCaption?: DynamicValue; @@ -50,9 +61,16 @@ export interface DatagridDropdownFilterPreviewProps { readOnly: boolean; renderMode: "design" | "xray" | "structure"; translate: (text: string) => string; + baseType: BaseTypeEnum; + attrChoice: AttrChoiceEnum; + attr: string; auto: boolean; - defaultValue: string; filterOptions: FilterOptionsPreviewType[]; + refEntity: string; + refOptions: {} | { caption: string } | { type: string } | null; + refCaption: string; + fetchOptionsLazy: boolean; + defaultValue: string; filterable: boolean; multiSelect: boolean; emptyOptionCaption: string; diff --git a/packages/pluggableWidgets/datagrid-number-filter-web/package.json b/packages/pluggableWidgets/datagrid-number-filter-web/package.json index 81c719e2e9..869959239e 100644 --- a/packages/pluggableWidgets/datagrid-number-filter-web/package.json +++ b/packages/pluggableWidgets/datagrid-number-filter-web/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/datagrid-number-filter-web", "widgetName": "DatagridNumberFilter", - "version": "2.9.2", + "version": "3.0.0", "description": "", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", 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 413fef4513..abfb3a38a3 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/src/package.xml b/packages/pluggableWidgets/datagrid-number-filter-web/src/package.xml index e34fd6e137..e039d03a35 100644 --- a/packages/pluggableWidgets/datagrid-number-filter-web/src/package.xml +++ b/packages/pluggableWidgets/datagrid-number-filter-web/src/package.xml @@ -1,6 +1,6 @@ - + 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 0aecea7bf8..6073ee1aa4 100644 --- a/packages/pluggableWidgets/datagrid-text-filter-web/package.json +++ b/packages/pluggableWidgets/datagrid-text-filter-web/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/datagrid-text-filter-web", "widgetName": "DatagridTextFilter", - "version": "2.9.1", + "version": "3.0.0", "description": "", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", @@ -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/src/package.xml b/packages/pluggableWidgets/datagrid-text-filter-web/src/package.xml index 144d3e9453..48e78f1246 100644 --- a/packages/pluggableWidgets/datagrid-text-filter-web/src/package.xml +++ b/packages/pluggableWidgets/datagrid-text-filter-web/src/package.xml @@ -1,6 +1,6 @@ - + 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/package.json b/packages/pluggableWidgets/datagrid-web/package.json index 0c7fb50a83..d166365111 100644 --- a/packages/pluggableWidgets/datagrid-web/package.json +++ b/packages/pluggableWidgets/datagrid-web/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/datagrid-web", "widgetName": "Datagrid", - "version": "2.30.4", + "version": "3.0.0", "description": "", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts index bdf7464d68..411f28f843 100644 --- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts +++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorConfig.ts @@ -74,21 +74,6 @@ export function getProperties( "hidable" ]); } - - if (!column.filterAssociation) { - hideNestedPropertiesIn(defaultProperties, values, "columns", index, [ - "filterAssociationOptions", - "filterAssociationOptionLabel", - "fetchOptionsLazy", - "filterCaptionType", - "filterAssociationOptionLabelAttr" - ]); - } - if (column.filterCaptionType === "attribute") { - hidePropertyIn(defaultProperties, values, "columns", index, "filterAssociationOptionLabel"); - } else { - hidePropertyIn(defaultProperties, values, "columns", index, "filterAssociationOptionLabelAttr"); - } }); if (values.pagination === "buttons") { hidePropertyIn(defaultProperties, values, "showNumberOfRows"); @@ -152,8 +137,6 @@ export function getProperties( "columnsHidable", "configurationAttribute", "onConfigurationChange", - "filterList", - "filtersPlaceholder", "filterSectionTitle" ]); } @@ -209,11 +192,6 @@ export const getPreview = ( draggable: false, dynamicText: "Dynamic text", filter: { widgetCount: 0, renderer: () => null }, - filterAssociation: "", - filterAssociationOptionLabel: "", - filterAssociationOptionLabelAttr: "", - filterAssociationOptions: {}, - filterCaptionType: "attribute", header: "Column", hidable: "no", resizable: false, @@ -227,8 +205,7 @@ export const getPreview = ( minWidth: "auto", minWidthLimit: 100, allowEventPropagation: true, - exportValue: "", - fetchOptionsLazy: true + exportValue: "" } ]; const columns = rowLayout({ diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx index 1bef235a15..ba1df3f098 100644 --- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.editorPreview.tsx @@ -35,11 +35,7 @@ const initColumns: ColumnsPreviewType[] = [ draggable: false, dynamicText: "Dynamic Text", filter: { renderer: () =>

, widgetCount: 0 }, - filterAssociation: "", - filterAssociationOptionLabel: "", - filterAssociationOptionLabelAttr: "", - filterAssociationOptions: {}, - filterCaptionType: "expression", + header: "Column", hidable: "no", resizable: false, @@ -53,8 +49,7 @@ const initColumns: ColumnsPreviewType[] = [ minWidth: "auto", minWidthLimit: 100, allowEventPropagation: true, - exportValue: "", - fetchOptionsLazy: true + exportValue: "" } ]; diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx b/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx index 0178fe3ea8..018b3a94d4 100644 --- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.tsx @@ -82,7 +82,7 @@ const Container = observer((props: Props): ReactElement => { headerTitle={props.filterSectionTitle?.value} headerContent={ props.filtersPlaceholder && ( - + {props.filtersPlaceholder} ) diff --git a/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml b/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml index 3f00e78f9c..b403c9134c 100644 --- a/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml +++ b/packages/pluggableWidgets/datagrid-web/src/Datagrid.xml @@ -121,44 +121,6 @@ - - - Entity - Set the entity to enable filtering over association with the Drop-down filter widget. - - - - - - - Selectable objects - The options to show in the Drop-down filter widget. - - - Use lazy load - Lazy loading enables faster data grid loading, but with personalization enabled, value restoration will be limited. - - - Option caption type - - - Attribute - Expression - - - - Option caption - - - - - Option caption - - - - - - Can sort @@ -316,6 +278,10 @@ On selection change + + Filters placeholder + + @@ -363,36 +329,6 @@ - - - - Filters - The list of attributes is used by the filter widgets that are placed in the placeholder above the data grid. This enables filtering across multiple attributes or columns instead of limiting to a single column. - - - - Filter attribute - - - - - - - - - - - - - - - - - Filters placeholder - - - - diff --git a/packages/pluggableWidgets/datagrid-web/src/components/StickySentinel.tsx b/packages/pluggableWidgets/datagrid-web/src/components/StickySentinel.tsx deleted file mode 100644 index cde33fdd50..0000000000 --- a/packages/pluggableWidgets/datagrid-web/src/components/StickySentinel.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import classNames from "classnames"; -import { createElement, ReactElement, useState, useEffect, useRef } from "react"; - -/** - * StickySentinel - A small hidden element that uses "IntersectionObserver" - * to detect the "scrolled" state of the grid. By toggling the "container-stuck" class - * on this element, we can force "position: sticky" for column headers. - */ -export function StickySentinel(): ReactElement { - const sentinelRef = useRef(null); - const [ratio, setRatio] = useState(1); - - useEffect(() => { - const target = sentinelRef.current; - - if (target === null) { - return; - } - - return createObserver(target, setRatio); - }, []); - - return ( -
- ); -} - -function createObserver(target: Element, onIntersectionChange: (ratio: number) => void): () => void { - const options = { threshold: [0, 1] }; - - const observer = new IntersectionObserver(([entry]) => { - if (entry.intersectionRatio === 0 || entry.intersectionRatio === 1) { - onIntersectionChange(entry.intersectionRatio); - } - }, options); - - observer.observe(target); - - return () => observer.unobserve(target); -} diff --git a/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeaderContext.tsx b/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeaderContext.tsx index 840cf55d2d..651c04806a 100644 --- a/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeaderContext.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/components/WidgetHeaderContext.tsx @@ -1,35 +1,27 @@ import { getGlobalFilterContextObject } from "@mendix/widget-plugin-filtering/context"; -import { HeaderFiltersStore } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore"; import { getGlobalSelectionContext, SelectionHelper, useCreateSelectionContextValue } from "@mendix/widget-plugin-grid/selection"; import { createElement, memo, ReactElement, ReactNode } from "react"; +import { RootGridStore } from "../helpers/state/RootGridStore"; interface WidgetHeaderContextProps { children?: ReactNode; - filtersStore: HeaderFiltersStore; selectionHelper?: SelectionHelper; + rootStore: RootGridStore; } const SelectionContext = getGlobalSelectionContext(); const FilterContext = getGlobalFilterContextObject(); -function FilterAPIProvider(props: { filtersStore: HeaderFiltersStore; children?: ReactNode }): ReactElement { - return {props.children}; -} - -function SelectionStatusProvider(props: { selectionHelper?: SelectionHelper; children?: ReactNode }): ReactElement { - const value = useCreateSelectionContextValue(props.selectionHelper); - return {props.children}; -} - function HeaderContainer(props: WidgetHeaderContextProps): ReactElement { + const selectionContext = useCreateSelectionContextValue(props.selectionHelper); return ( - - {props.children} - + + {props.children} + ); } diff --git a/packages/pluggableWidgets/datagrid-web/src/consistency-check.ts b/packages/pluggableWidgets/datagrid-web/src/consistency-check.ts index bd6a615c83..de02c76a80 100644 --- a/packages/pluggableWidgets/datagrid-web/src/consistency-check.ts +++ b/packages/pluggableWidgets/datagrid-web/src/consistency-check.ts @@ -4,13 +4,7 @@ import { ColumnsPreviewType, DatagridPreviewProps } from "../typings/DatagridPro export function check(values: DatagridPreviewProps): Problem[] { const errors: Problem[] = []; - const columnChecks = [ - checkAssociationSettings, - checkFilteringSettings, - checkDisplaySettings, - checkSortingSettings, - checkHidableSettings - ]; + const columnChecks = [checkDisplaySettings, checkSortingSettings, checkHidableSettings]; values.columns.forEach((column: ColumnsPreviewType, index) => { for (const check of columnChecks) { @@ -28,51 +22,6 @@ export function check(values: DatagridPreviewProps): Problem[] { const columnPropPath = (prop: string, index: number): string => `columns/${index + 1}/${prop}`; -const checkAssociationSettings = ( - values: DatagridPreviewProps, - column: ColumnsPreviewType, - index: number -): Problem | undefined => { - if (!values.columnsFilterable) { - return; - } - - if (!column.filterAssociation) { - return; - } - - if (column.filterCaptionType === "expression" && !column.filterAssociationOptionLabel) { - return { - property: columnPropPath("filterAssociationOptionLabel", index), - message: `A caption is required when using associations. Please set 'Option caption' property for column (${column.header})` - }; - } - - if (column.filterCaptionType === "attribute" && !column.filterAssociationOptionLabelAttr) { - return { - property: columnPropPath("filterAssociationOptionLabelAttr", index), - message: `A caption is required when using associations. Please set 'Option caption' property for column (${column.header})` - }; - } -}; - -const checkFilteringSettings = ( - values: DatagridPreviewProps, - column: ColumnsPreviewType, - index: number -): Problem | undefined => { - if (!values.columnsFilterable) { - return; - } - - if (!column.attribute && !column.filterAssociation) { - return { - property: columnPropPath("attribute", index), - message: `An attribute or reference is required when filtering is enabled. Please select 'Attribute' or 'Reference' property for column (${column.header})` - }; - } -}; - const checkDisplaySettings = ( _values: DatagridPreviewProps, column: ColumnsPreviewType, diff --git a/packages/pluggableWidgets/datagrid-web/src/controllers/StateSyncController.ts b/packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceParamsController.ts similarity index 69% rename from packages/pluggableWidgets/datagrid-web/src/controllers/StateSyncController.ts rename to packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceParamsController.ts index 397c73e3c6..d99e7f2d06 100644 --- a/packages/pluggableWidgets/datagrid-web/src/controllers/StateSyncController.ts +++ b/packages/pluggableWidgets/datagrid-web/src/controllers/DatasourceParamsController.ts @@ -1,45 +1,45 @@ -import { compactArray, fromCompactArray, isAnd } from "@mendix/widget-plugin-filtering/condition-utils"; +import { compactArray, fromCompactArray, isAnd } from "@mendix/filter-commons/condition-utils"; +import { QueryController } from "@mendix/widget-plugin-grid/query/query-controller"; import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch"; import { ReactiveController, ReactiveControllerHost } from "@mendix/widget-plugin-mobx-kit/reactive-controller"; import { FilterCondition } from "mendix/filters"; import { and } from "mendix/filters/builders"; import { makeAutoObservable, reaction } from "mobx"; import { SortInstruction } from "../typings/sorting"; -import { QueryController } from "./query-controller"; interface Columns { conditions: Array; sortInstructions: SortInstruction[] | undefined; } -interface Header { +interface FiltersInput { conditions: Array; } -type StateSyncControllerSpec = { +type DatasourceParamsControllerSpec = { query: QueryController; columns: Columns; - header: Header; + customFilters: FiltersInput; }; -export class StateSyncController implements ReactiveController { +export class DatasourceParamsController implements ReactiveController { private columns: Columns; - private header: Header; 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, customFilters } = this; - return and(compactArray(columns.conditions), compactArray(header.conditions)); + return and(compactArray(columns.conditions), compactArray(customFilters.conditions)); } private get derivedSortOrder(): SortInstruction[] | undefined { @@ -68,7 +68,7 @@ export class StateSyncController implements ReactiveController { static unzipFilter( filter?: FilterCondition - ): [columns: Array, header: Array] { + ): [columns: Array, sharedFilter: Array] { if (!filter) { return [[], []]; } @@ -78,7 +78,8 @@ export class StateSyncController implements ReactiveController { if (filter.args.length !== 2) { return [[], []]; } - const [columns, header] = filter.args; - return [fromCompactArray(columns), fromCompactArray(header)]; + + const [columns, shared] = filter.args; + return [fromCompactArray(columns), fromCompactArray(shared)]; } } diff --git a/packages/pluggableWidgets/datagrid-web/src/controllers/PaginationController.ts b/packages/pluggableWidgets/datagrid-web/src/controllers/PaginationController.ts index 28a966e029..454c47a674 100644 --- a/packages/pluggableWidgets/datagrid-web/src/controllers/PaginationController.ts +++ b/packages/pluggableWidgets/datagrid-web/src/controllers/PaginationController.ts @@ -1,7 +1,7 @@ +import { QueryController } from "@mendix/widget-plugin-grid/query/query-controller"; import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/props-gate"; import { ReactiveController, ReactiveControllerHost } from "@mendix/widget-plugin-mobx-kit/reactive-controller"; import { PaginationEnum, ShowPagingButtonsEnum } from "../../typings/DatagridProps"; -import { QueryController } from "./query-controller"; type Gate = DerivedPropsGate<{ pageSize: number; diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts b/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts index efc8dd67ad..b3660c0767 100644 --- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts +++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/ColumnGroupStore.ts @@ -1,5 +1,5 @@ -import { disposeFx } from "@mendix/widget-plugin-filtering/mobx-utils"; -import { FiltersSettingsMap } from "@mendix/widget-plugin-filtering/typings/settings"; +import { FiltersSettingsMap } from "@mendix/filter-commons/typings/settings"; +import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch"; import { FilterCondition } from "mendix/filters"; import { action, computed, makeObservable, observable } from "mobx"; import { DatagridContainerProps } from "../../../typings/DatagridProps"; @@ -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( @@ -82,9 +83,9 @@ export class ColumnGroupStore implements IColumnGroupStore, IColumnParentStore { } setup(): () => void { - const [disposers, dispose] = disposeFx(); + const [add, dispose] = disposeBatch(); for (const filter of this.columnFilters) { - disposers.push(filter.setup()); + add(filter.setup()); } return dispose; } @@ -92,7 +93,6 @@ export class ColumnGroupStore implements IColumnGroupStore, IColumnParentStore { updateProps(props: Pick): void { props.columns.forEach((columnProps, i) => { this._allColumns[i].updateProps(columnProps); - this.columnFilters[i].updateProps(columnProps); }); if (this.visibleColumns.length < 1) { @@ -144,7 +144,7 @@ export class ColumnGroupStore implements IColumnGroupStore, IColumnParentStore { get conditions(): Array { return this.columnFilters.map((store, index) => { - return this._allColumns[index].isHidden ? undefined : store.condition2; + return this._allColumns[index].isHidden ? undefined : store.condition; }); } diff --git a/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts b/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts index 21c8024aca..801798b61e 100644 --- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts +++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/GridPersonalizationStore.ts @@ -1,6 +1,6 @@ +import { FiltersSettingsMap } from "@mendix/filter-commons/typings/settings"; import { error, Result, value } from "@mendix/widget-plugin-filtering/result-meta"; -import { HeaderFiltersStore } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore"; -import { FiltersSettingsMap } from "@mendix/widget-plugin-filtering/typings/settings"; +import { ObservableFilterHost } from "@mendix/widget-plugin-filtering/typings/ObservableFilterHost"; import { action, comparer, computed, IReactionDisposer, makeObservable, reaction } from "mobx"; import { DatagridContainerProps } from "../../../typings/DatagridProps"; import { ColumnId } from "../../typings/GridColumn"; @@ -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: ObservableFilterHost ) { 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..cc7e718a08 100644 --- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts +++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/RootGridStore.ts @@ -1,15 +1,16 @@ -import { HeaderFiltersStore } from "@mendix/widget-plugin-filtering/stores/generic/HeaderFiltersStore"; +import { createContextWithStub, FilterAPI } from "@mendix/widget-plugin-filtering/context"; +import { CustomFilterHost } from "@mendix/widget-plugin-filtering/stores/generic/CustomFilterHost"; +import { DatasourceController } from "@mendix/widget-plugin-grid/query/DatasourceController"; +import { RefreshController } from "@mendix/widget-plugin-grid/query/RefreshController"; import { BaseControllerHost } from "@mendix/widget-plugin-mobx-kit/BaseControllerHost"; import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch"; import { DerivedPropsGate } from "@mendix/widget-plugin-mobx-kit/props-gate"; import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; -import { autorun, computed } from "mobx"; +import { autorun } 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"; @@ -24,12 +25,12 @@ type Spec = { export class RootGridStore extends BaseControllerHost { columnsStore: ColumnGroupStore; - headerFiltersStore: HeaderFiltersStore; settingsStore: GridPersonalizationStore; staticInfo: StaticInfo; exportProgressCtrl: ProgressStore; loaderCtrl: DerivedLoaderController; paginationCtrl: PaginationController; + readonly autonomousFilterAPI: FilterAPI; private gate: Gate; @@ -37,28 +38,36 @@ export class RootGridStore extends BaseControllerHost { super(); const { props } = gate; - const [columnsViewState, headerViewState] = StateSyncController.unzipFilter(props.datasource.filter); + const [columnsInitFilter, 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); + this.autonomousFilterAPI = createContextWithStub({ + filterObserver: customFilterHost, + sharedInitFilter, + parentChannelName: this.staticInfo.filtersChannelName + }); + const columns = (this.columnsStore = new ColumnGroupStore(props, this.staticInfo, columnsInitFilter, { + customFilterHost, + sharedInitFilter + })); + 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 + customFilters: customFilterHost }); new RefreshController(this, { - query: computed(() => query.computedCopy), + query: query.derivedQuery, delay: props.refreshInterval * 1000 }); @@ -73,7 +82,6 @@ export class RootGridStore extends BaseControllerHost { const [add, disposeAll] = disposeBatch(); add(super.setup()); add(this.columnsStore.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..fcf274a8aa 100644 --- a/packages/pluggableWidgets/datagrid-web/src/helpers/state/column/ColumnFilterStore.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/helpers/state/column/ColumnFilterStore.tsx @@ -1,90 +1,51 @@ -import { FilterAPIv2, 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 { FilterAPI, getGlobalFilterContextObject } from "@mendix/widget-plugin-filtering/context"; +import { StaticSelectFilterStore } from "@mendix/widget-plugin-dropdown-filter/stores/StaticSelectFilterStore"; import { InputFilterStore, attrgroupFilterStore } from "@mendix/widget-plugin-filtering/stores/input/store-utils"; -import { ensure } from "@mendix/widget-plugin-platform/utils/ensure"; +import { FilterData } from "@mendix/filter-commons/typings/settings"; +import { value } from "@mendix/widget-plugin-filtering/result-meta"; +import { ObservableFilterHost } from "@mendix/widget-plugin-filtering/typings/ObservableFilterHost"; +import { disposeBatch } from "@mendix/widget-plugin-mobx-kit/disposeBatch"; +import { ListAttributeListValue, ListAttributeValue } from "mendix"; import { FilterCondition } from "mendix/filters"; -import { ListAttributeValue, ListAttributeListValue } from "mendix"; -import { action, computed, makeObservable } from "mobx"; +import { computed, makeObservable } from "mobx"; import { ReactNode, createElement } from "react"; import { ColumnsType } from "../../../../typings/DatagridProps"; 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"; + export interface IColumnFilterStore { renderFilterWidgets(): ReactNode; } -type FilterStore = InputFilterStore | StaticSelectFilterStore | RefFilterStore; +type FilterStore = InputFilterStore | StaticSelectFilterStore; 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); - makeObservable(this, { - _updateStore: action, - condition2: computed, - updateProps: action + makeObservable(this, { + condition: computed }); } setup(): () => void { - const [disposers, dispose] = disposeFx(); + const [add, disposeAll] = disposeBatch(); if (this._filterStore && "setup" in this._filterStore) { - disposers.push(this._filterStore.setup()); - } - return dispose; - } - - updateProps(props: ColumnsType): void { - this._widget = props.filter; - this._updateStore(props); - } - - private _updateStore(props: ColumnsType): void { - const store = this._filterStore; - - if (store === null) { - return; + add(this._filterStore.setup()); } - - if (store.storeType === "refselect") { - store.updateProps(this.toRefselectProps(props)); - } else if (isListAttributeValue(props.attribute)) { - store.updateProps([props.attribute]); - } - } - - private toRefselectProps(props: ColumnsType): RefFilterStoreProps { - const searchAttrId = props.filterAssociationOptionLabelAttr?.id; - const caption = - props.filterCaptionType === "expression" - ? ensure(props.filterAssociationOptionLabel, errorMessage("filterAssociationOptionLabel")) - : ensure(props.filterAssociationOptionLabelAttr, errorMessage("filterAssociationOptionLabelAttr")); - - return { - ref: ensure(props.filterAssociation, errorMessage("filterAssociation")), - datasource: ensure(props.filterAssociationOptions, errorMessage("filterAssociationOptions")), - searchAttrId, - fetchOptionsLazy: props.fetchOptionsLazy, - caption - }; + return disposeAll; } private createFilterStore(props: ColumnsType, dsViewState: FilterCondition | null): FilterStore | null { - if (props.filterAssociation) { - return new RefFilterStore(this.toRefselectProps(props), dsViewState); - } - if (isListAttributeValue(props.attribute)) { return attrgroupFilterStore(props.attribute.type, [props.attribute], dsViewState); } @@ -92,14 +53,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 }; } @@ -107,7 +70,7 @@ export class ColumnFilterStore implements IColumnFilterStore { return {this._widget}; } - get condition2(): FilterCondition | undefined { + get condition(): FilterCondition | undefined { return this._filterStore ? this._filterStore.condition : undefined; } @@ -130,5 +93,7 @@ const isListAttributeValue = ( return !!(attribute && attribute.isList === false); }; -const errorMessage = (propName: string): string => - `Can't map ColumnsType to AssociationProperties: ${propName} is undefined`; +export interface ObserverBag { + customFilterHost: ObservableFilterHost; + sharedInitFilter: Array; +} diff --git a/packages/pluggableWidgets/datagrid-web/src/package.xml b/packages/pluggableWidgets/datagrid-web/src/package.xml index c9f6330fb2..8bf5807932 100644 --- a/packages/pluggableWidgets/datagrid-web/src/package.xml +++ b/packages/pluggableWidgets/datagrid-web/src/package.xml @@ -1,6 +1,6 @@ - + diff --git a/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts b/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts index f51a4f9b4e..663dcb3b32 100644 --- a/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts +++ b/packages/pluggableWidgets/datagrid-web/src/typings/personalization-settings.ts @@ -1,4 +1,4 @@ -import { FilterData } from "@mendix/widget-plugin-filtering/typings/settings"; +import { FilterData } from "@mendix/filter-commons/typings/settings"; import { ColumnId } from "./GridColumn"; import { SortDirection, SortRule } from "./sorting"; @@ -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/datagrid-web/src/utils/test-utils.tsx b/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx index 3a9a345e20..9a347d20aa 100644 --- a/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx +++ b/packages/pluggableWidgets/datagrid-web/src/utils/test-utils.tsx @@ -29,9 +29,7 @@ export const column = (header = "Test", patch?: (col: ColumnsType) => void): Col visible: dynamicValue(true), minWidth: "auto", minWidthLimit: 100, - allowEventPropagation: true, - fetchOptionsLazy: true, - filterCaptionType: "attribute" + allowEventPropagation: true }; if (patch) { diff --git a/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts b/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts index 3749768048..262d9ab434 100644 --- a/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts +++ b/packages/pluggableWidgets/datagrid-web/typings/DatagridProps.d.ts @@ -4,7 +4,7 @@ * @author Mendix Widgets Framework Team */ import { ComponentType, CSSProperties, ReactNode } from "react"; -import { ActionValue, DynamicValue, EditableValue, ListValue, ListActionValue, ListAttributeValue, ListAttributeListValue, ListExpressionValue, ListReferenceValue, ListReferenceSetValue, ListWidgetValue, SelectionSingleValue, SelectionMultiValue } from "mendix"; +import { ActionValue, DynamicValue, EditableValue, ListValue, ListActionValue, ListAttributeValue, ListAttributeListValue, ListExpressionValue, ListWidgetValue, SelectionSingleValue, SelectionMultiValue } from "mendix"; import { Big } from "big.js"; export type ItemSelectionMethodEnum = "checkbox" | "rowClick"; @@ -15,8 +15,6 @@ export type LoadingTypeEnum = "spinner" | "skeleton"; export type ShowContentAsEnum = "attribute" | "dynamicText" | "customContent"; -export type FilterCaptionTypeEnum = "attribute" | "expression"; - export type HidableEnum = "yes" | "hidden" | "no"; export type WidthEnum = "autoFill" | "autoFit" | "manual"; @@ -35,12 +33,6 @@ export interface ColumnsType { tooltip?: ListExpressionValue; filter?: ReactNode; visible: DynamicValue; - filterAssociation?: ListReferenceValue | ListReferenceSetValue; - filterAssociationOptions?: ListValue; - fetchOptionsLazy: boolean; - filterCaptionType: FilterCaptionTypeEnum; - filterAssociationOptionLabel?: ListExpressionValue; - filterAssociationOptionLabelAttr?: ListAttributeValue; sortable: boolean; resizable: boolean; draggable: boolean; @@ -67,10 +59,6 @@ export type OnClickTriggerEnum = "single" | "double"; export type ConfigurationStorageTypeEnum = "attribute" | "localStorage"; -export interface FilterListType { - filter: ListAttributeValue; -} - export interface ColumnsPreviewType { showContentAs: ShowContentAsEnum; attribute: string; @@ -81,12 +69,6 @@ export interface ColumnsPreviewType { tooltip: string; filter: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; visible: string; - filterAssociation: string; - filterAssociationOptions: {} | { caption: string } | { type: string } | null; - fetchOptionsLazy: boolean; - filterCaptionType: FilterCaptionTypeEnum; - filterAssociationOptionLabel: string; - filterAssociationOptionLabelAttr: string; sortable: boolean; resizable: boolean; draggable: boolean; @@ -101,10 +83,6 @@ export interface ColumnsPreviewType { wrapText: boolean; } -export interface FilterListPreviewType { - filter: string; -} - export interface DatagridContainerProps { name: string; class: string; @@ -132,6 +110,7 @@ export interface DatagridContainerProps { onClickTrigger: OnClickTriggerEnum; onClick?: ListActionValue; onSelectionChange?: ActionValue; + filtersPlaceholder?: ReactNode; columnsSortable: boolean; columnsResizable: boolean; columnsDraggable: boolean; @@ -139,8 +118,6 @@ export interface DatagridContainerProps { configurationStorageType: ConfigurationStorageTypeEnum; configurationAttribute?: EditableValue; storeFiltersInPersonalization: boolean; - filterList: FilterListType[]; - filtersPlaceholder?: ReactNode; filterSectionTitle?: DynamicValue; exportDialogLabel?: DynamicValue; cancelExportLabel?: DynamicValue; @@ -180,6 +157,7 @@ export interface DatagridPreviewProps { onClickTrigger: OnClickTriggerEnum; onClick: {} | null; onSelectionChange: {} | null; + filtersPlaceholder: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; columnsSortable: boolean; columnsResizable: boolean; columnsDraggable: boolean; @@ -188,8 +166,6 @@ export interface DatagridPreviewProps { configurationAttribute: string; storeFiltersInPersonalization: boolean; onConfigurationChange: {} | null; - filterList: FilterListPreviewType[]; - filtersPlaceholder: { widgetCount: number; renderer: ComponentType<{ children: ReactNode; caption?: string }> }; filterSectionTitle: string; exportDialogLabel: string; cancelExportLabel: string; diff --git a/packages/pluggableWidgets/dropdown-sort-web/package.json b/packages/pluggableWidgets/dropdown-sort-web/package.json index 2070899fb4..7115ffcdce 100644 --- a/packages/pluggableWidgets/dropdown-sort-web/package.json +++ b/packages/pluggableWidgets/dropdown-sort-web/package.json @@ -1,7 +1,7 @@ { "name": "@mendix/dropdown-sort-web", "widgetName": "DropdownSort", - "version": "1.2.2", + "version": "3.0.0", "description": "", "copyright": "© Mendix Technology BV 2025. All rights reserved.", "license": "Apache-2.0", diff --git a/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.tsx b/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.tsx index 1caef896bf..2a35f534d6 100644 --- a/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.tsx +++ b/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.tsx @@ -1,14 +1,16 @@ import { observer } from "mobx-react-lite"; -import { createElement, ReactElement, useRef } from "react"; +import { createElement, ReactElement } from "react"; import { useSortControl } from "@mendix/widget-plugin-sorting/helpers/useSortControl"; -import { SortingStoreInterface } from "@mendix/widget-plugin-sorting/typings"; +import { SortingStoreInterface } from "@mendix/widget-plugin-sorting/SortingStoreInterface"; import { generateUUID } from "@mendix/widget-plugin-platform/framework/generate-uuid"; import { DropdownSortContainerProps } from "../typings/DropdownSortProps"; import { SortComponent } from "./components/SortComponent"; -import { withSortStore } from "./hocs/withSortStore"; +import { withLinkedSortStore, withSortAPI } from "./hocs/withLinkedSortStore"; +import { useConst } from "@mendix/widget-plugin-mobx-kit/react/useConst"; function Container(props: DropdownSortContainerProps & { sortStore: SortingStoreInterface }): ReactElement { - const id = (useRef().current ??= `DropdownSort${generateUUID()}`); + const id = useConst(() => `DropdownSort${generateUUID()}`); + const sortProps = useSortControl( { ...props, emptyOptionCaption: props.emptyOptionCaption?.value }, props.sortStore @@ -28,8 +30,4 @@ function Container(props: DropdownSortContainerProps & { sortStore: SortingStore ); } -const Widget = withSortStore(observer(Container)); - -export function DropdownSort(props: DropdownSortContainerProps): ReactElement { - return ; -} +export const DropdownSort = withSortAPI(withLinkedSortStore(observer(Container))); diff --git a/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.xml b/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.xml index 09af998512..9dbb977d8a 100644 --- a/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.xml +++ b/packages/pluggableWidgets/dropdown-sort-web/src/DropdownSort.xml @@ -8,6 +8,36 @@ + + Datasource to sort + + + + Attributes + Select the attributes that the end-user may use for sorting + + + + Attribute + + + + + + + + + + + + + + Caption + + + + + Empty option caption diff --git a/packages/pluggableWidgets/dropdown-sort-web/src/components/SortComponent.tsx b/packages/pluggableWidgets/dropdown-sort-web/src/components/SortComponent.tsx index 723c659a9c..fc89199d11 100644 --- a/packages/pluggableWidgets/dropdown-sort-web/src/components/SortComponent.tsx +++ b/packages/pluggableWidgets/dropdown-sort-web/src/components/SortComponent.tsx @@ -125,6 +125,7 @@ export function SortComponent(props: SortComponentProps): ReactElement { aria-expanded={show} aria-controls={`${props.id}-dropdown-list`} aria-label={props.screenReaderInputCaption} + onChange={() => {}} />