From ea1720d9d13f9d1644286bc2c7af3606b15e47a3 Mon Sep 17 00:00:00 2001 From: Philipp Sommer <philipp.sommer@hereon.de> Date: Sun, 30 Mar 2025 22:11:36 +0200 Subject: [PATCH 1/2] use events for web components and revise default handler api --- index.html | 13 ++-- src/components/ClassContainer.tsx | 8 +- src/components/FunctionContainer.tsx | 54 ++++++++++++- src/components/ModuleContainer.tsx | 12 +-- src/main.tsx | 41 ++++++++-- src/resources/ClassContainerOptions.ts | 48 +----------- src/resources/ContainerBaseOptions.ts | 95 +++++++++++++++++++++++ src/resources/FunctionContainerOptions.ts | 47 +---------- src/resources/ModuleContainerOptions.ts | 48 +----------- 9 files changed, 201 insertions(+), 165 deletions(-) create mode 100644 src/resources/ContainerBaseOptions.ts diff --git a/index.html b/index.html index 011f498..cdf1fdd 100644 --- a/index.html +++ b/index.html @@ -18,10 +18,6 @@ SPDX-License-Identifier: Apache-2.0 DASFConnection, WebsocketUrlBuilder, } from '/node_modules/@dasf/dasf-messaging'; - function showinLog(response) { - console.log(response); - return true; - } const connection = new DASFConnection( new WebsocketUrlBuilder('ws://localhost:8080/ws', 'mytesttopic'), @@ -30,8 +26,13 @@ SPDX-License-Identifier: Apache-2.0 const getConnection = () => { return connection; }; - window['showinLog'] = showinLog; window['getConnection'] = getConnection; + setTimeout(() => { + const element = document.querySelector('dasf-function'); + element.addEventListener('response', (event) => { + console.log('received', event); + }); + }); </script> <h1>Demo Site for dasf-web-component</h1> @@ -40,8 +41,8 @@ SPDX-License-Identifier: Apache-2.0 <dasf-function connection="getConnection" output-description="A list of the grid definitions with the closest grid width." - on-response="showinLog" function-name="test_function" + skip-default-response-handler="true" > </dasf-function> diff --git a/src/components/ClassContainer.tsx b/src/components/ClassContainer.tsx index 941f083..46d9750 100644 --- a/src/components/ClassContainer.tsx +++ b/src/components/ClassContainer.tsx @@ -58,9 +58,7 @@ function ClassContainer({ websocketUrl, topic, className, - onResponse, - onError, - onProgress, + ...props }: ClassContainerOptions) { if (typeof apiInfoElement != 'undefined') { apiInfo = JSON.parse( @@ -174,10 +172,8 @@ function ClassContainer({ uiSchema={uiSchema} outputDescription={functionInfo.returnSchema.description} connection={dasfConnection} - onResponse={onResponse} - onError={onError} - onProgress={onProgress} constructorData={constructorData} + {...props} /> </Collapse> ), diff --git a/src/components/FunctionContainer.tsx b/src/components/FunctionContainer.tsx index a1b48a1..50b0c06 100644 --- a/src/components/FunctionContainer.tsx +++ b/src/components/FunctionContainer.tsx @@ -55,8 +55,14 @@ function FunctionContainer({ topic, functionName, onResponse, + skipDefaultResponseHandler = false, + skipDefaultResponseHandlerCheck, onError, + skipDefaultErrorHandler = false, + skipDefaultErrorHandlerCheck, onProgress, + skipDefaultProgressHandler = false, + skipDefaultProgressHandlerCheck, constructorData, }: FunctionContainerOptions) { if (typeof schemaElement != 'undefined') { @@ -72,6 +78,33 @@ function FunctionContainer({ // @ts-expect-error:next-line const buttonRef: { current: HTMLButtonElement } = useRef(null); + function checkskipDefaultOnResponse(responseData: unknown): boolean { + if (typeof skipDefaultResponseHandlerCheck === 'undefined') { + return skipDefaultResponseHandler; + } else { + return skipDefaultResponseHandlerCheck(responseData); + } + } + + function checkskipDefaultOnProgress(value: { + message: DASFProgressReport; + props?: object; + }): boolean { + if (typeof skipDefaultProgressHandlerCheck === 'undefined') { + return skipDefaultProgressHandler; + } else { + return skipDefaultProgressHandlerCheck(value); + } + } + + function checkskipDefaultOnError(error: Error): boolean { + if (typeof skipDefaultErrorHandlerCheck === 'undefined') { + return skipDefaultErrorHandler; + } else { + return skipDefaultErrorHandlerCheck(error); + } + } + const defaultOnReponse = (responseData: unknown) => { if (typeof responseData === 'string') { setOutput(responseData); @@ -94,8 +127,9 @@ function FunctionContainer({ if (typeof onProgress === 'undefined') { defaultOnProgress(value); } else { - if (!onProgress(value)) { - defaultOnProgress(value); + onProgress(value); + if (!checkskipDefaultOnProgress(value)) { + defaultOnReponse(value); } } }; @@ -108,12 +142,24 @@ function FunctionContainer({ if (typeof onResponse === 'undefined') { defaultOnReponse(response); } else { - if (!onResponse(response)) { + onResponse(response); + if (!checkskipDefaultOnResponse(response)) { defaultOnReponse(response); } } }; + const handleError = (error: Error) => { + if (typeof onError === 'undefined') { + console.error(error); + } else { + onError(error); + if (!checkskipDefaultOnError(error)) { + console.error(error); + } + } + }; + const dasfConnection: DASFConnection = getConnection({ connection, websocketUrl, @@ -132,7 +178,7 @@ function FunctionContainer({ dasfConnection .sendRequest(formData, handleProgress) .then(handleResponse) - .catch(onError ? onError : console.error); + .catch(handleError); }; if (typeof uiSchema === 'undefined') { uiSchema = {}; diff --git a/src/components/ModuleContainer.tsx b/src/components/ModuleContainer.tsx index 59a1a0b..d547477 100644 --- a/src/components/ModuleContainer.tsx +++ b/src/components/ModuleContainer.tsx @@ -62,9 +62,7 @@ function ModuleContainer({ connection, websocketUrl, topic, - onResponse, - onError, - onProgress, + ...props }: ModuleContainerOptions) { if (typeof apiInfoElement != 'undefined') { apiInfo = JSON.parse( @@ -157,9 +155,7 @@ function ModuleContainer({ uiSchema={uiSchema} outputDescription={functionInfo.returnSchema.description} connection={dasfConnection} - onResponse={onResponse} - onError={onError} - onProgress={onProgress} + {...props} /> </Collapse> ), @@ -184,9 +180,7 @@ function ModuleContainer({ apiInfo={classInfo} uiSchema={uiSchema} connection={dasfConnection} - onResponse={onResponse} - onError={onError} - onProgress={onProgress} + {...props} /> </Collapse> ))} diff --git a/src/main.tsx b/src/main.tsx index 44e985c..225e9cf 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -17,11 +17,19 @@ const WebFunctionContainer = r2wc(FunctionContainer, { topic: 'string', outputDescription: 'string', functionName: 'string', - onResponse: 'function', - onError: 'function', - onProgress: 'function', + skipDefaultResponseHandler: 'boolean', + skipDefaultResponseHandlerCheck: 'function', + skipDefaultErrorHandler: 'boolean', + skipDefaultErrorHandlerCheck: 'function', + skipDefaultProgressHandler: 'boolean', + skipDefaultProgressHandlerCheck: 'function', constructorData: 'json', }, + events: { + onResponse: {}, + onError: {}, + onProgress: {}, + }, }); const WebClassContainer = r2wc(ClassContainer, { @@ -33,9 +41,17 @@ const WebClassContainer = r2wc(ClassContainer, { websocketUrl: 'string', topic: 'string', className: 'string', - onResponse: 'function', - onError: 'function', - onProgress: 'function', + skipDefaultResponseHandler: 'boolean', + skipDefaultResponseHandlerCheck: 'function', + skipDefaultErrorHandler: 'boolean', + skipDefaultErrorHandlerCheck: 'function', + skipDefaultProgressHandler: 'boolean', + skipDefaultProgressHandlerCheck: 'function', + }, + events: { + onResponse: {}, + onError: {}, + onProgress: {}, }, }); @@ -47,8 +63,17 @@ const WebModuleContainer = r2wc(ModuleContainer, { connection: 'function', websocketUrl: 'string', topic: 'string', - onResponse: 'function', - onError: 'function', + skipDefaultResponseHandler: 'boolean', + skipDefaultResponseHandlerCheck: 'function', + skipDefaultErrorHandler: 'boolean', + skipDefaultErrorHandlerCheck: 'function', + skipDefaultProgressHandler: 'boolean', + skipDefaultProgressHandlerCheck: 'function', + }, + events: { + onResponse: {}, + onError: {}, + onProgress: {}, }, }); diff --git a/src/resources/ClassContainerOptions.ts b/src/resources/ClassContainerOptions.ts index 638ca16..7e0231c 100644 --- a/src/resources/ClassContainerOptions.ts +++ b/src/resources/ClassContainerOptions.ts @@ -2,14 +2,14 @@ // // SPDX-License-Identifier: Apache-2.0 import { UiSchema } from '@rjsf/utils'; -import { ClassApiInfo, DASFProgressReport } from '@dasf/dasf-messaging'; -import { ConnectionOptions } from './connection'; +import { ClassApiInfo } from '@dasf/dasf-messaging'; +import { ContainerBaseOptions } from './ContainerBaseOptions'; /** Options for the :func:`ClassContainer <ClassContainer.default>` component * - * See also :class:`ConnectionOptions` for further options. + * See also :class:`ContainerBaseOptions` for further options. */ -export interface ClassContainerOptions extends ConnectionOptions { +export interface ClassContainerOptions extends ContainerBaseOptions { /** The id of a ``<script>`` element with JSON-encoded API Info that is * used to render the function forms. * @@ -112,44 +112,4 @@ export interface ClassContainerOptions extends ConnectionOptions { * list. */ className?: string; - - /** Response handler for requests from the backend module. - * - * This argument can be used to overwrite the default response handler (which - * is a plain JSON-dump of the response of the backend module). It must take - * the response from the function in the backend module. - * - * This handler has to return a boolean. If this boolean is True, we assume - * that the handler has processed the responseData. Otherwise the default - * response handler will render the response. - */ - onResponse?: (responseData: unknown) => boolean; - - /** Error handler for requests from the backend module. - * - * This argument can be used to overwrite the default error handler (which - * is simple call to ``console.error``). It must take the error from the - * function in the backend module. - * - * This handler has to return a boolean. If this boolean is True, we assume - * that the handler has handled the error. Otherwise the default error - * handler will handle it. - */ - onError?: (error: Error) => boolean; - - /** Progress handler for progress reports from the backend module. - * - * This argument can be used to overwrite the default progress handler (which - * is simple dump of the progress report). It must take an object holding the - * :class:`dasf-messaging-typescript:DASFProgressReport` as `message` and the - * report properties as `props`. - * - * This handler has to return a boolean. If this boolean is True, we assume - * that the handler has handled the progress report. Otherwise the default - * progress report handler will handle it. - */ - onProgress?: (value: { - message: DASFProgressReport; - props?: object; - }) => boolean; } diff --git a/src/resources/ContainerBaseOptions.ts b/src/resources/ContainerBaseOptions.ts new file mode 100644 index 0000000..03586f5 --- /dev/null +++ b/src/resources/ContainerBaseOptions.ts @@ -0,0 +1,95 @@ +// SPDX-FileCopyrightText: 2025 Helmholtz-Zentrum hereon GmbH +// +// SPDX-License-Identifier: Apache-2.0 + +import { DASFProgressReport } from '@dasf/dasf-messaging'; + +import { ConnectionOptions } from './connection'; + +export interface ContainerBaseOptions extends ConnectionOptions { + /** Response handler for requests from the backend module. + * + * This argument can be used to overwrite the default response handler (which + * is a plain JSON-dump of the response of the backend module). It must take + * the response from the function in the backend module. + */ + onResponse?: (responseData: unknown) => void; + + /** Prevent the default handling of responses + * + * This option can be used to prevent the default response handler in favor + * of the given `onResponse` handler (in case + * :attr:`skipDefaultResponseHandlerCheck` is ``undefined``). Setting this to + * ``true`` means that the default response handler should be skipped. + */ + skipDefaultResponseHandler: boolean; + + /** Handler to check if the default handling of responses should be skipped + * + * This option can be used to prevent the default response handler in favor + * of the given `onResponse` handler. This argument takes a callable that + * takes the response as an argument and returns a boolean + * that specifies whether the default response handler should be used or not. + * If omitted, the :attr:`skipDefaultResponseHandler` determines the behaviour. + */ + skipDefaultResponseHandlerCheck?: (responseData: unknown) => boolean; + + /** Error handler for requests from the backend module. + * + * This argument can be used to overwrite the default error handler (which + * is simple call to ``console.error``). It must take the error from the + * function in the backend module. + */ + onError?: (error: Error) => void; + + /** Prevent the default handling of errors + * + * This option can be used to prevent the default error handler in favor + * of the given `onError` handler (in case + * :attr:`skipDefaultErrorHandlerCheck` is ``undefined``). Setting this to + * ``true`` means that the default error handler should be skipped. + */ + skipDefaultErrorHandler: boolean; + + /** Handler to check if the the default handling of errors should be skipped + * + * This option can be used to prevent the default error handler in favor + * of the given `onError` handler. This argument takes a callable that takes + * the error as an argument and returns a boolean that specifies whether the + * default error handler should be used or not. If omitted, the + * :attr:`skipDefaultErrorHandler` determines the behaviour. + */ + skipDefaultErrorHandlerCheck?: (error: Error) => boolean; + + /** Progress handler for progress reports from the backend module. + * + * This argument can be used to overwrite the default progress handler (which + * is simple dump of the progress report). It must take an object holding the + * :class:`dasf-messaging-typescript:DASFProgressReport` as `message` and the + * report properties as `props`. + */ + onProgress?: (value: { message: DASFProgressReport; props?: object }) => void; + + /** Prevent the default handling of progress reports + * + * This option can be used to prevent the default progress report handler in + * favor of the given `onProgress` handler (in case + * :attr:`skipDefaultProgressHandlerCheck` is ``undefined``). Setting this to + * ``true`` means that the default progress report handler should be skipped. + */ + skipDefaultProgressHandler?: boolean; + + /** Handler to check if the default progress report handler should be skipped + * + * This option can be used to prevent the default progress report handler in + * favor of the given `onProgress` handler. It should be a callable that + * takes the ``message`` and ``props`` as an argument and + * returns a boolean that specifies whether the default progress report + * handler should be used or not. If omitted, the + * :attr:`skipDefaultProgressHandler` determines the behaviour. + */ + skipDefaultProgressHandlerCheck?: (value: { + message: DASFProgressReport; + props?: object; + }) => boolean; +} diff --git a/src/resources/FunctionContainerOptions.ts b/src/resources/FunctionContainerOptions.ts index f1a550e..86f09f1 100644 --- a/src/resources/FunctionContainerOptions.ts +++ b/src/resources/FunctionContainerOptions.ts @@ -3,15 +3,14 @@ // SPDX-License-Identifier: Apache-2.0 import { RJSFSchema, UiSchema } from '@rjsf/utils'; -import { DASFProgressReport } from '@dasf/dasf-messaging'; -import { ConnectionOptions } from './connection'; +import { ContainerBaseOptions } from './ContainerBaseOptions'; /** Options for the :func:`FunctionContainer <FunctionContainer.default>` component * - * See also :class:`ConnectionOptions` for further options. + * See also :class:`ContainerBaseOptions` for further options. * */ -export interface FunctionContainerOptions extends ConnectionOptions { +export interface FunctionContainerOptions extends ContainerBaseOptions { /** The id of a ``<script>`` element with JSON-encoded JSONSchema that is * used to render the function form. * @@ -77,46 +76,6 @@ export interface FunctionContainerOptions extends ConnectionOptions { */ functionName?: string; - /** Response handler for requests from the backend module. - * - * This argument can be used to overwrite the default response handler (which - * is a plain JSON-dump of the response of the backend module). It must take - * the response from the function in the backend module. - * - * This handler has to return a boolean. If this boolean is True, we assume - * that the handler has processed the responseData. Otherwise the default - * response handler will render the response. - */ - onResponse?: (responseData: unknown) => boolean; - - /** Error handler for requests from the backend module. - * - * This argument can be used to overwrite the default error handler (which - * is simple call to ``console.error``). It must take the error from the - * function in the backend module. - * - * This handler has to return a boolean. If this boolean is True, we assume - * that the handler has handled the error. Otherwise the default error - * handler will handle it. - */ - onError?: (error: Error) => boolean; - - /** Progress handler for progress reports from the backend module. - * - * This argument can be used to overwrite the default progress handler (which - * is simple dump of the progress report). It must take an object holding the - * :class:`dasf-messaging-typescript:DASFProgressReport` as `message` and the - * report properties as `props`. - * - * This handler has to return a boolean. If this boolean is True, we assume - * that the handler has handled the progress report. Otherwise the default - * progress report handler will handle it. - */ - onProgress?: (value: { - message: DASFProgressReport; - props?: object; - }) => boolean; - /** Constructor data for the backend class * * If this function is not an independent function but rather a method of diff --git a/src/resources/ModuleContainerOptions.ts b/src/resources/ModuleContainerOptions.ts index e9af133..469c2cc 100644 --- a/src/resources/ModuleContainerOptions.ts +++ b/src/resources/ModuleContainerOptions.ts @@ -3,15 +3,15 @@ // SPDX-License-Identifier: Apache-2.0 import { UiSchema } from '@rjsf/utils'; -import { DASFProgressReport, ModuleApiInfo } from '@dasf/dasf-messaging'; +import { ModuleApiInfo } from '@dasf/dasf-messaging'; -import { ConnectionOptions } from './connection'; +import { ContainerBaseOptions } from './ContainerBaseOptions'; /** Options for the :func:`ModuleContainer <ModuleContainer.default>` component * - * See also :class:`ConnectionOptions` for further options. + * See also :class:`ContainerBaseOptions` for further options. * */ -export interface ModuleContainerOptions extends ConnectionOptions { +export interface ModuleContainerOptions extends ContainerBaseOptions { /** The id of a ``<script>`` element with JSON-encoded API Info that is * used to render the function forms. * @@ -111,44 +111,4 @@ export interface ModuleContainerOptions extends ConnectionOptions { * specified, we will render an overview. */ member?: string; - - /** Response handler for requests from the backend module. - * - * This argument can be used to overwrite the default response handler (which - * is a plain JSON-dump of the response of the backend module). It must take - * the response from the function in the backend module. - * - * This handler has to return a boolean. If this boolean is True, we assume - * that the handler has processed the responseData. Otherwise the default - * response handler will render the response. - */ - onResponse?: (responseData: unknown) => boolean; - - /** Error handler for requests from the backend module. - * - * This argument can be used to overwrite the default error handler (which - * is simple call to ``console.error``). It must take the error from the - * function in the backend module. - * - * This handler has to return a boolean. If this boolean is True, we assume - * that the handler has handled the error. Otherwise the default error - * handler will handle it. - */ - onError?: (error: Error) => boolean; - - /** Progress handler for progress reports from the backend module. - * - * This argument can be used to overwrite the default progress handler (which - * is simple dump of the progress report). It must take an object holding the - * :class:`dasf-messaging-typescript:DASFProgressReport` as `message` and the - * report properties as `props`. - * - * This handler has to return a boolean. If this boolean is True, we assume - * that the handler has handled the progress report. Otherwise the default - * progress report handler will handle it. - */ - onProgress?: (value: { - message: DASFProgressReport; - props?: object; - }) => boolean; } -- GitLab From 2293d4fe922bc71e7fea66de03801d772c330ed6 Mon Sep 17 00:00:00 2001 From: Philipp Sommer <philipp.sommer@hereon.de> Date: Sun, 30 Mar 2025 22:27:06 +0200 Subject: [PATCH 2/2] document usage of events --- docs/react-api/{connection.md => base.md} | 15 +++++++++++- docs/react-api/index.md | 2 +- docs/web-components/index.md | 29 +++++++++++++++++------ src/resources/ContainerBaseOptions.ts | 9 +++++++ 4 files changed, 46 insertions(+), 9 deletions(-) rename docs/react-api/{connection.md => base.md} (54%) diff --git a/docs/react-api/connection.md b/docs/react-api/base.md similarity index 54% rename from docs/react-api/connection.md rename to docs/react-api/base.md index 4fdded9..d1554f7 100644 --- a/docs/react-api/connection.md +++ b/docs/react-api/base.md @@ -4,9 +4,13 @@ SPDX-FileCopyrightText: 2025 Helmholtz-Zentrum hereon GmbH SPDX-License-Identifier: CC-BY-4.0 --> +(base-api)= + +# Basic interfaces + (connection-api)= -# Connection parameters +## Connection parameters ```{eval-rst} .. autoclass:: ConnectionOptions @@ -14,3 +18,12 @@ SPDX-License-Identifier: CC-BY-4.0 .. autofunction:: getConnection ``` + +(container-base-options-api)= + +## Container Base Options + +```{eval-rst} +.. autoclass:: ContainerBaseOptions + :members: +``` diff --git a/docs/react-api/index.md b/docs/react-api/index.md index 26f7d43..7e3f7fa 100644 --- a/docs/react-api/index.md +++ b/docs/react-api/index.md @@ -21,7 +21,7 @@ This package exports three components: maxdepth: 1 --- -connection +base module class function diff --git a/docs/web-components/index.md b/docs/web-components/index.md index 4666680..f6f2544 100644 --- a/docs/web-components/index.md +++ b/docs/web-components/index.md @@ -51,25 +51,40 @@ Here the {ref}`dasf-module` custom web component is used with a `websocket-url` and a `topic` that are used to create the connection to the message broker. Furthermore we have to load this libary in a `script` tag. +### Custom response, error and progress handlers + If we want to handle the response ourselve using the -{attr}`~ModuleContainerOptions.onResponse` option, we have to create a global -function and pass this to the `dasf-module` tag: +{attr}`~ModuleContainerOptions.onResponse` option, we to listen to the +`response` event of the custom web component. The response data can then be +accessed via the `event.detail` attribute: ```html <script> - function showResponseInLog(response) { - console.log(response); - return true; - } + setTimeout(() => { + const element = document.querySelector('dasf-module'); + element.addEventListener('response', (event) => { + console.log('received response', event.detail); + }); + }); </script> <dasf-module websocket-url="ws://<someserver>/ws" topic="sometopic" - onResponse="showResponseInLog" ></dasf-module> <script type="module" src="/node_modules/@dasf/dasf-messaging"></script> ``` +The same works for the errors via the `error` event, and progress reports via +the `progress` event. + +If we want to skip the default handling of response, error or progress reports, +we can set the corresponding attribute out of `skip-default-response-handler`, +`skip-default-error-handler` and `skip-default-progress-handler` or use the +corresponding `...-check` functions for this. See the +:class:`ContainerBaseOptions` for more information on these arguments.. + +### Creating the connection via function + When we want to create the connection ourselve as we may want to use it in several places, we have to create a function that returns the connection we created diff --git a/src/resources/ContainerBaseOptions.ts b/src/resources/ContainerBaseOptions.ts index 03586f5..71653f0 100644 --- a/src/resources/ContainerBaseOptions.ts +++ b/src/resources/ContainerBaseOptions.ts @@ -6,6 +6,15 @@ import { DASFProgressReport } from '@dasf/dasf-messaging'; import { ConnectionOptions } from './connection'; +/** Base options for creating a container for a DASF module, class or function + * + * This interface holds the options for handling responses, errors and progress + * reports of the DASF backend module and serves as a basis for the + * :class:`FunctionContainerOptions`, :class:`ClassContainerOptions` and + * :class:`ModuleContainerOptions`. + * + * See also :class:`ConnectionOptions` for further options. + */ export interface ContainerBaseOptions extends ConnectionOptions { /** Response handler for requests from the backend module. * -- GitLab