Skip to content

[WC-2869][WC-2868] improved filtering date time and number #1523

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 37 commits into
base: data-widgets-v3.0
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
9aced30
chore: add attributes to settings
iobuhov Feb 26, 2025
51f972a
chore: change interface
iobuhov Feb 27, 2025
bfed956
chore: rename controller
iobuhov Feb 28, 2025
6e6da81
refactor: rename interface
iobuhov Feb 28, 2025
4564605
feat: add filter observer
iobuhov Feb 28, 2025
8c16a36
refactor: rename type
iobuhov Feb 28, 2025
aca5453
refactor: remove dead code
iobuhov Feb 28, 2025
95747af
feat: add filter observer to filter api
iobuhov Feb 28, 2025
c0a1d94
chore: change personalization
iobuhov Mar 3, 2025
149072b
chore: change attr type
iobuhov Mar 3, 2025
d57bf00
feat: add custom text filter
iobuhov Mar 3, 2025
ea8c786
chore: merge filters
iobuhov Mar 4, 2025
6137dab
feat: add state persistence
iobuhov Mar 4, 2025
02a5ba5
chore: change tag exp to not equal
iobuhov Mar 4, 2025
5c4a8b9
chore: add condition tagging
iobuhov Mar 4, 2025
7601851
chore: add filter initialization
iobuhov Mar 4, 2025
c18dc96
chore: remove placeholder tag
iobuhov Mar 4, 2025
a37e32a
test: fix unit tests
iobuhov Mar 4, 2025
654051e
test: fix unit tests
iobuhov Mar 4, 2025
d58fbd1
chore: change label
iobuhov Mar 5, 2025
812f36f
chore: fix types
iobuhov Mar 5, 2025
b9f0df7
fix: update types
iobuhov Mar 7, 2025
a2a8a37
fix: update types
iobuhov Mar 7, 2025
2a2be0d
fix: update types
iobuhov Mar 7, 2025
44e5b23
fix: change types
iobuhov Mar 7, 2025
1303f90
test: update settings schema
iobuhov Mar 7, 2025
3f6e1e0
chore: update lockfile
iobuhov Apr 4, 2025
f83ae1d
feat: add custom text filter
iobuhov Mar 3, 2025
74aa3b7
feat(datagrid-date-filter-web): add linked ds
iobuhov Apr 4, 2025
95228e6
feat: add date store provider
iobuhov Apr 4, 2025
36d87c2
feat: add store injector to date filter
iobuhov Apr 4, 2025
411e498
chore: show linked ds in settings
iobuhov Apr 9, 2025
04253a3
feat: number filter grid wide filtering
gjulivan Mar 27, 2025
8620a98
chore: update xml
iobuhov Apr 9, 2025
0988131
chore(datagrid-number-filter-web): update editor config
iobuhov Apr 9, 2025
0e3fe88
chore: update formatter logic
iobuhov Apr 9, 2025
93e23a6
chore: fix type error
iobuhov Apr 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
}

Original file line number Diff line number Diff line change
@@ -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 <Widget {...props} />;
const isAuto = props.attrChoice === "auto";

if (isAuto) {
return <FilterAuto {...props} />;
}

return <FilterLinked {...props} />;
}
Original file line number Diff line number Diff line change
@@ -8,9 +8,32 @@
<properties>
<propertyGroup caption="General">
<propertyGroup caption="General">
<property key="advanced" type="boolean" defaultValue="false">
<caption>Enable advanced options</caption>
<property key="attrChoice" type="enumeration" defaultValue="auto">
<caption>Filter attributes</caption>
<description />
<enumerationValues>
<enumerationValue key="auto">Auto</enumerationValue>
<enumerationValue key="linked">Custom</enumerationValue>
</enumerationValues>
</property>
<property key="linkedDs" type="datasource" isLinked="true" isList="true">
<caption>Datasource to Filter</caption>
<description />
</property>
<property key="attributes" type="object" isList="true" required="false">
<caption>Attributes</caption>
<description>Select the attributes that the end-user may use for filtering.</description>
<properties>
<propertyGroup caption="General">
<property key="attribute" type="attribute" dataSource="../linkedDs" isMetaData="true" required="true">
<caption>Attribute</caption>
<description />
<attributeTypes>
<attributeType name="DateTime" />
</attributeTypes>
</property>
</propertyGroup>
</properties>
</property>
<property key="defaultValue" type="expression" required="false">
<caption>Default value</caption>
Original file line number Diff line number Diff line change
@@ -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>): 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<FilterAPIv2>(
});
const headerFilterStore = new HeaderFiltersStore(spec);
(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
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<FilterAPIv2>(
});
const headerFilterStore = new HeaderFiltersStore(spec);
(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
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<FilterAPIv2>(
});
const headerFilterStore = new HeaderFiltersStore(spec);
(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
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<FilterAPIv2>(
});
const headerFilterStore = new HeaderFiltersStore(spec);
(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
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<FilterAPIv2>(
});
const headerFilterStore = new HeaderFiltersStore(spec);
(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
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<FilterAPIv2>(
});
const headerFilterStore = new HeaderFiltersStore(spec);
(window as any)["com.mendix.widgets.web.filterable.filterContext.v2"] = createContext<FilterAPI>(
headerFilterStore.context
);
});
Original file line number Diff line number Diff line change
@@ -6,7 +6,7 @@ export function withDateFilterAPI<P extends object>(
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 <Alert bootstrapStyle="danger">{api.error.message}</Alert>;
Original file line number Diff line number Diff line change
@@ -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<Date>;
}>;
name: string;
}

interface StoreProvider extends ISetupable {
store: Date_InputFilterInterface;
}

type Component<P extends object> = (props: P) => React.ReactElement;

export function withDateLinkedAttributes<P extends RequiredProps>(
component: Component<P & InjectableFilterAPI>
): Component<P> {
const StoreInjector = withInjectedStore(component);

return function FilterAPIProvider(props) {
const api = useStoreProvider(props);

if (api.hasError) {
return <Alert bootstrapStyle="danger">{api.error.message}</Alert>;
}

return <StoreInjector {...props} {...api.value} />;
};
}

function withInjectedStore<P extends object>(
Component: Component<P & InjectableFilterAPI>
): Component<P & { provider: StoreProvider; channel: string }> {
return function StoreInjector(props) {
const provider = useSetup(() => props.provider);
return <Component {...props} filterStore={provider.store} parentChannelName={props.channel} />;
};
}

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
});
});
}
Original file line number Diff line number Diff line change
@@ -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<Date>;
}

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<Date>;
defaultStartDate?: DynamicValue<Date>;
defaultEndDate?: DynamicValue<Date>;
@@ -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;
Loading