diff --git a/packages/pluggableWidgets/events-web/CHANGELOG.md b/packages/pluggableWidgets/events-web/CHANGELOG.md index 0c66851987..be454230d0 100644 --- a/packages/pluggableWidgets/events-web/CHANGELOG.md +++ b/packages/pluggableWidgets/events-web/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +### Added + +- Added support for using expressions to configure both delay and repeat interval values during component load. Expression support was also extended to delay values when attributes change dynamically. + ## [1.0.1] - 2024-04-25 ### Fixed diff --git a/packages/pluggableWidgets/events-web/src/Events.editorConfig.ts b/packages/pluggableWidgets/events-web/src/Events.editorConfig.ts index fb3ea918b4..e380bae933 100644 --- a/packages/pluggableWidgets/events-web/src/Events.editorConfig.ts +++ b/packages/pluggableWidgets/events-web/src/Events.editorConfig.ts @@ -19,7 +19,27 @@ export function getProperties( defaultProperties: Properties /* , target: Platform*/ ): Properties { if (!values.componentLoadRepeat) { - hidePropertiesIn(defaultProperties, values, ["componentLoadRepeatInterval"]); + hidePropertiesIn(defaultProperties, values, [ + "componentLoadRepeatInterval", + "componentLoadRepeatIntervalExpression", + "componentLoadRepeatIntervalParameterType" + ]); + } else { + if (values.componentLoadRepeatIntervalParameterType === "expression") { + hidePropertiesIn(defaultProperties, values, ["componentLoadRepeatInterval"]); + } else { + hidePropertiesIn(defaultProperties, values, ["componentLoadRepeatIntervalExpression"]); + } + } + if (values.componentLoadDelayParameterType === "expression") { + hidePropertiesIn(defaultProperties, values, ["componentLoadDelay"]); + } else { + hidePropertiesIn(defaultProperties, values, ["componentLoadDelayExpression"]); + } + if (values.onEventChangeDelayParameterType === "expression") { + hidePropertiesIn(defaultProperties, values, ["onEventChangeDelay"]); + } else { + hidePropertiesIn(defaultProperties, values, ["onEventChangeDelayExpression"]); } return defaultProperties; } diff --git a/packages/pluggableWidgets/events-web/src/Events.tsx b/packages/pluggableWidgets/events-web/src/Events.tsx index 2820199bdf..dab7edcc84 100644 --- a/packages/pluggableWidgets/events-web/src/Events.tsx +++ b/packages/pluggableWidgets/events-web/src/Events.tsx @@ -3,26 +3,49 @@ import { EditableValue } from "mendix"; import { ReactElement, createElement, useRef } from "react"; import { EventsContainerProps } from "../typings/EventsProps"; import { useActionTimer } from "./hooks/timer"; +import { useParameterValue } from "./hooks/parameterValue"; import "./ui/Events.scss"; export default function Events(props: EventsContainerProps): ReactElement { const { class: className, onComponentLoad, + componentLoadDelayExpression, componentLoadDelay, componentLoadRepeat, - componentLoadRepeatInterval, onEventChangeAttribute, onEventChange, - onEventChangeDelay - // optionsSourceType + componentLoadRepeatInterval, + componentLoadRepeatIntervalParameterType, + componentLoadRepeatIntervalExpression, + componentLoadDelayParameterType, + onEventChangeDelay, + onEventChangeDelayParameterType, + onEventChangeDelayExpression } = props; const prevOnChangeAttributeValue = useRef | undefined>(); + + const delayValue = useParameterValue({ + parameterType: componentLoadDelayParameterType, + parameterValue: componentLoadDelay, + parameterExpression: componentLoadDelayExpression + }); + const intervalValue = useParameterValue({ + parameterType: componentLoadRepeatIntervalParameterType, + parameterValue: componentLoadRepeatInterval, + parameterExpression: componentLoadRepeatIntervalExpression + }); + const onEventChangeDelayValue = useParameterValue({ + parameterType: onEventChangeDelayParameterType, + parameterValue: onEventChangeDelay, + parameterExpression: onEventChangeDelayExpression + }); + useActionTimer({ canExecute: onComponentLoad?.canExecute, execute: onComponentLoad?.execute, - delay: componentLoadDelay, - interval: componentLoadRepeatInterval, + delay: delayValue, + interval: intervalValue, repeat: componentLoadRepeat, attribute: undefined }); @@ -33,7 +56,6 @@ export default function Events(props: EventsContainerProps): ReactElement { return; } if (prevOnChangeAttributeValue?.current?.value === undefined) { - // ignore initial load prevOnChangeAttributeValue.current = onEventChangeAttribute; } else { if (onEventChangeAttribute?.value !== prevOnChangeAttributeValue.current?.value) { @@ -42,7 +64,7 @@ export default function Events(props: EventsContainerProps): ReactElement { } } }, - delay: onEventChangeDelay, + delay: onEventChangeDelayValue, interval: 0, repeat: false, attribute: onEventChangeAttribute diff --git a/packages/pluggableWidgets/events-web/src/Events.xml b/packages/pluggableWidgets/events-web/src/Events.xml index 37b94ab93d..9010cda15e 100644 --- a/packages/pluggableWidgets/events-web/src/Events.xml +++ b/packages/pluggableWidgets/events-web/src/Events.xml @@ -11,18 +11,51 @@ Action + + + Parameter type + + + Value + Expression + + + Delay Timer delay to first action execution. Value is in milliseconds. If set to 0, action will be triggered immediately. + + + Delay + Timer delay to first action execution. Value is in milliseconds. If set to 0, action will be triggered immediately. + + + Repeat + + + Parameter type + + + Value + Expression + + + - Delay + Interval Interval between repeat action execution. Value is in milliseconds. + + + Interval + Interval between repeat action execution. Value is in milliseconds. + + @@ -45,10 +78,26 @@ Action + + + Parameter type + + + Value + Expression + + + Delay Timer delay to first action execution. Value is in milliseconds. If set to 0, action will be triggered immediately. + + + Delay + Timer delay to first action execution. Value is in milliseconds. If set to 0, action will be triggered immediately. + + diff --git a/packages/pluggableWidgets/events-web/src/__tests__/AppEvents.spec.tsx b/packages/pluggableWidgets/events-web/src/__tests__/AppEvents.spec.tsx index 424d67174d..4cfc8e0a2c 100644 --- a/packages/pluggableWidgets/events-web/src/__tests__/AppEvents.spec.tsx +++ b/packages/pluggableWidgets/events-web/src/__tests__/AppEvents.spec.tsx @@ -1,4 +1,4 @@ -import { actionValue } from "@mendix/widget-plugin-test-utils"; +import { actionValue, dynamicValue } from "@mendix/widget-plugin-test-utils"; import "@testing-library/jest-dom"; import { render } from "@testing-library/react"; import { createElement } from "react"; @@ -12,10 +12,18 @@ describe("App events (load)", () => { name: "app events", class: "app-events", onComponentLoad: actionValue(), + componentLoadDelayParameterType: "number", componentLoadDelay: 0, - onEventChangeDelay: 0, + componentLoadDelayExpression: dynamicValue(), componentLoadRepeat: false, - componentLoadRepeatInterval: 0 + componentLoadRepeatIntervalParameterType: "number", + componentLoadRepeatInterval: 0, + componentLoadRepeatIntervalExpression: dynamicValue(), + onEventChangeAttribute: undefined, + onEventChange: undefined, + onEventChangeDelayParameterType: "number", + onEventChangeDelay: 0, + onEventChangeDelayExpression: dynamicValue() }; }); it("render app events", async () => { diff --git a/packages/pluggableWidgets/events-web/src/hooks/parameterValue.ts b/packages/pluggableWidgets/events-web/src/hooks/parameterValue.ts new file mode 100644 index 0000000000..13e134328d --- /dev/null +++ b/packages/pluggableWidgets/events-web/src/hooks/parameterValue.ts @@ -0,0 +1,23 @@ +import { useMemo } from "react"; + +interface parameterValueProps { + parameterType: "number" | "expression"; + parameterValue: number | undefined; + parameterExpression: { status: string; value: { toNumber: () => number } | undefined } | undefined; +} + +export function useParameterValue({ + parameterType, + parameterValue, + parameterExpression +}: parameterValueProps): number | undefined { + return useMemo(() => { + if (parameterType === "number") { + return parameterValue; + } + + return parameterExpression?.status === "available" && parameterExpression.value !== undefined + ? parameterExpression.value.toNumber() + : undefined; + }, [parameterType, parameterValue, parameterExpression]); +} diff --git a/packages/pluggableWidgets/events-web/src/hooks/timer.ts b/packages/pluggableWidgets/events-web/src/hooks/timer.ts index 33d7cef6c0..cd1fd53216 100644 --- a/packages/pluggableWidgets/events-web/src/hooks/timer.ts +++ b/packages/pluggableWidgets/events-web/src/hooks/timer.ts @@ -4,8 +4,8 @@ import { useEffect, useState } from "react"; interface ActionTimerProps { canExecute?: boolean; execute?: () => void; - delay: number; - interval: number; + delay: number | undefined; + interval: number | undefined; repeat: boolean; attribute?: EditableValue; } @@ -14,6 +14,10 @@ export function useActionTimer(props: ActionTimerProps): void { const { canExecute, execute, delay, interval, repeat, attribute } = props; const [toggleTimer, setToggleTimer] = useState(-1); useEffect(() => { + // If the delay is set to undefined, we should not start a timer. + if (delay === undefined || delay < 0 || (interval === undefined && repeat)) { + return; + } let counter: NodeJS.Timeout; if (canExecute) { if (repeat) { diff --git a/packages/pluggableWidgets/events-web/typings/EventsProps.d.ts b/packages/pluggableWidgets/events-web/typings/EventsProps.d.ts index 6b0f9eac8e..6f46be628c 100644 --- a/packages/pluggableWidgets/events-web/typings/EventsProps.d.ts +++ b/packages/pluggableWidgets/events-web/typings/EventsProps.d.ts @@ -4,21 +4,33 @@ * @author Mendix Widgets Framework Team */ import { CSSProperties } from "react"; -import { ActionValue, EditableValue } from "mendix"; +import { ActionValue, DynamicValue, EditableValue } from "mendix"; import { Big } from "big.js"; +export type ComponentLoadDelayParameterTypeEnum = "number" | "expression"; + +export type ComponentLoadRepeatIntervalParameterTypeEnum = "number" | "expression"; + +export type OnEventChangeDelayParameterTypeEnum = "number" | "expression"; + export interface EventsContainerProps { name: string; class: string; style?: CSSProperties; tabIndex?: number; onComponentLoad?: ActionValue; + componentLoadDelayParameterType: ComponentLoadDelayParameterTypeEnum; componentLoadDelay: number; + componentLoadDelayExpression: DynamicValue; componentLoadRepeat: boolean; + componentLoadRepeatIntervalParameterType: ComponentLoadRepeatIntervalParameterTypeEnum; componentLoadRepeatInterval: number; + componentLoadRepeatIntervalExpression?: DynamicValue; onEventChangeAttribute?: EditableValue; onEventChange?: ActionValue; + onEventChangeDelayParameterType: OnEventChangeDelayParameterTypeEnum; onEventChangeDelay: number; + onEventChangeDelayExpression: DynamicValue; } export interface EventsPreviewProps { @@ -33,10 +45,16 @@ export interface EventsPreviewProps { renderMode: "design" | "xray" | "structure"; translate: (text: string) => string; onComponentLoad: {} | null; + componentLoadDelayParameterType: ComponentLoadDelayParameterTypeEnum; componentLoadDelay: number | null; + componentLoadDelayExpression: string; componentLoadRepeat: boolean; + componentLoadRepeatIntervalParameterType: ComponentLoadRepeatIntervalParameterTypeEnum; componentLoadRepeatInterval: number | null; + componentLoadRepeatIntervalExpression: string; onEventChangeAttribute: string; onEventChange: {} | null; + onEventChangeDelayParameterType: OnEventChangeDelayParameterTypeEnum; onEventChangeDelay: number | null; + onEventChangeDelayExpression: string; }