diff --git a/source/components/datatable/status.mjs b/source/components/datatable/status.mjs index 6ea1294cf0c3e64a234afe1a99529466cde4cd13..7f4ee60d8ed1fa2c16c75c48224c4246747eaa72 100644 --- a/source/components/datatable/status.mjs +++ b/source/components/datatable/status.mjs @@ -13,26 +13,26 @@ */ import { - assembleMethodSymbol, - CustomElement, - registerCustomElement, + assembleMethodSymbol, + CustomElement, + registerCustomElement, } from "../../dom/customelement.mjs"; -import { findElementWithSelectorUpwards } from "../../dom/util.mjs"; -import { ThemeStyleSheet } from "../stylesheet/theme.mjs"; -import { Datasource } from "./datasource.mjs"; -import { SpinnerStyleSheet } from "../stylesheet/spinner.mjs"; -import { isString } from "../../types/is.mjs"; -import { instanceSymbol } from "../../constants.mjs"; +import {findElementWithSelectorUpwards} from "../../dom/util.mjs"; +import {ThemeStyleSheet} from "../stylesheet/theme.mjs"; +import {Datasource} from "./datasource.mjs"; +import {SpinnerStyleSheet} from "../stylesheet/spinner.mjs"; +import {isString} from "../../types/is.mjs"; +import {instanceSymbol} from "../../constants.mjs"; import "../form/select.mjs"; import "./datasource/dom.mjs"; import "./datasource/rest.mjs"; import "../form/popper.mjs"; import "../form/context-error.mjs"; -import { StatusStyleSheet } from "./stylesheet/status.mjs"; +import {StatusStyleSheet} from "./stylesheet/status.mjs"; import {Formatter} from "../../text/formatter.mjs"; -export { DatasourceStatus }; +export {DatasourceStatus}; /** * @private @@ -59,86 +59,97 @@ const datasourceLinkedElementSymbol = Symbol("datasourceLinkedElement"); * @summary The Status component is used to show the current status of a datasource. */ class DatasourceStatus extends CustomElement { - /** - */ - constructor() { - super(); - } - - /** - * This method is called by the `instanceof` operator. - * @return {symbol} - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/components/datatable/status@@instance"); - } - - /** - * To set the options via the HTML tag, the attribute `data-monster-options` must be used. - * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} - * - * The individual configuration values can be found in the table. - * - * @property {Object} templates Template definitions - * @property {string} templates.main Main template - * @property {Object} datasource Datasource configuration - * @property {string} datasource.selector The selector of the datasource - * @property {Object} callbacks Callbacks - * @property {Function} callbacks.onError Callback function for error handling - * @property {Object} timeouts Timeouts - * @property {number} timeouts.message Timeout for the message - * @property {Object} state State - */ - get defaults() { - return Object.assign({}, super.defaults, { - templates: { - main: getTemplate(), - }, - - datasource: { - selector: null, - }, - - callbacks: { - onError: null - }, - - - timeouts: { - message: 4000, - }, - - state: { - spinner: "hide", - }, - }); - } - - /** - * - * @return {string} - */ - static getTag() { - return "monster-datasource-status"; - } - - /** - * @private - */ - [assembleMethodSymbol]() { - super[assembleMethodSymbol](); - - initControlReferences.call(this); - initEventHandler.call(this); - } - - /** - * - * @return [CSSStyleSheet] - */ - static getCSSStyleSheet() { - return [StatusStyleSheet, SpinnerStyleSheet, ThemeStyleSheet]; - } + /** + */ + constructor() { + super(); + } + + /** + * This method is called by the `instanceof` operator. + * @return {symbol} + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/components/datatable/status@@instance"); + } + + /** + * To set the options via the HTML tag, the attribute `data-monster-options` must be used. + * @see {@link https://monsterjs.org/en/doc/#configurate-a-monster-control} + * + * The individual configuration values can be found in the table. + * + * @property {Object} templates Template definitions + * @property {string} templates.main Main template + * @property {Object} datasource Datasource configuration + * @property {string} datasource.selector The selector of the datasource + * @property {Object} callbacks Callbacks + * @property {Function} callbacks.onError Callback function for error handling <code>function(message: string, event: Event): string</code> + * @property {Object} timeouts Timeouts + * @property {number} timeouts.message Timeout for the message + * @property {Object} state State + */ + get defaults() { + return Object.assign({}, super.defaults, { + templates: { + main: getTemplate(), + }, + + datasource: { + selector: null, + }, + + callbacks: { + onError: null + }, + + + timeouts: { + message: 4000, + }, + + state: { + spinner: "hide", + }, + }); + } + + /** + * + * @return {string} + */ + static getTag() { + return "monster-datasource-status"; + } + + /** + * @private + */ + [assembleMethodSymbol]() { + super[assembleMethodSymbol](); + + initControlReferences.call(this); + initEventHandler.call(this); + } + + /** + * + * @param message + * @param timeout + * @returns {DatasourceStatus} + */ + setErrorMessage(message, timeout) { + this[errorElementSymbol].setErrorMessage(message, timeout); + return this; + } + + /** + * + * @return [CSSStyleSheet] + */ + static getCSSStyleSheet() { + return [StatusStyleSheet, SpinnerStyleSheet, ThemeStyleSheet]; + } } /** @@ -147,85 +158,85 @@ class DatasourceStatus extends CustomElement { * @throws {Error} no shadow-root is defined */ function initControlReferences() { - if (!this.shadowRoot) { - throw new Error("no shadow-root is defined"); - } + if (!this.shadowRoot) { + throw new Error("no shadow-root is defined"); + } - this[errorElementSymbol] = this.shadowRoot.querySelector( - "monster-context-error", - ); + this[errorElementSymbol] = this.shadowRoot.querySelector( + "monster-context-error", + ); } /** * @private */ function initEventHandler() { - const selector = this.getOption("datasource.selector", ""); - const self = this; - - if (isString(selector)) { - const element = findElementWithSelectorUpwards(this, selector); - if (element === null) { - throw new Error("the selector must match exactly one element"); - } - - if (!(element instanceof Datasource)) { - throw new TypeError("the element must be a datasource"); - } - - let fadeOutTimer = null; - - this[datasourceLinkedElementSymbol] = element; - element.addEventListener("monster-datasource-fetched", function () { - fadeOutTimer = setTimeout(() => { - self.setOption("state.spinner", "hide"); - }, 800); - }); - - element.addEventListener("monster-datasource-fetch", function () { - if (fadeOutTimer) { - clearTimeout(fadeOutTimer); - fadeOutTimer = null; - } - - self.setOption("state.spinner", "show"); - }); - - element.addEventListener("monster-datasource-error", function (event) { - if (fadeOutTimer) { - clearTimeout(fadeOutTimer); - fadeOutTimer = null; - } - - self.setOption("state.spinner", "hide"); - - const timeout = self.getOption("timeouts.message", 4000); - let msg = "Cannot load data"; - - try { - if (event.detail.error instanceof Error) { - msg = event.detail.error.message; - } else if (event.detail.error instanceof Object) { - msg = JSON.stringify(event.detail.error); - } else if (event.detail.error instanceof String) { - msg = event.detail.error; - } else if (event.detail.error instanceof Number) { - msg = event.detail.error.toString(); - } else { - msg = event.detail.error; - } - } catch (e) { - } finally { - - const callback = self.getOption("callbacks.onError", null); - if (callback) { - msg = callback(msg); - } - - self[errorElementSymbol].setErrorMessage(msg, timeout); - } - }); - } + const selector = this.getOption("datasource.selector", ""); + const self = this; + + if (isString(selector)) { + const element = findElementWithSelectorUpwards(this, selector); + if (element === null) { + throw new Error("the selector must match exactly one element"); + } + + if (!(element instanceof Datasource)) { + throw new TypeError("the element must be a datasource"); + } + + let fadeOutTimer = null; + + this[datasourceLinkedElementSymbol] = element; + element.addEventListener("monster-datasource-fetched", function () { + fadeOutTimer = setTimeout(() => { + self.setOption("state.spinner", "hide"); + }, 800); + }); + + element.addEventListener("monster-datasource-fetch", function () { + if (fadeOutTimer) { + clearTimeout(fadeOutTimer); + fadeOutTimer = null; + } + + self.setOption("state.spinner", "show"); + }); + + element.addEventListener("monster-datasource-error", function (event) { + if (fadeOutTimer) { + clearTimeout(fadeOutTimer); + fadeOutTimer = null; + } + + self.setOption("state.spinner", "hide"); + + const timeout = self.getOption("timeouts.message", 4000); + let msg = "Cannot load data"; + + try { + if (event.detail.error instanceof Error) { + msg = event.detail.error.message; + } else if (event.detail.error instanceof Object) { + msg = JSON.stringify(event.detail.error); + } else if (event.detail.error instanceof String) { + msg = event.detail.error; + } else if (event.detail.error instanceof Number) { + msg = event.detail.error.toString(); + } else { + msg = event.detail.error; + } + } catch (e) { + } finally { + + const callback = self.getOption("callbacks.onError", null); + if (callback) { + callback.call(self, msg, event); + } else { + self[errorElementSymbol].setErrorMessage(msg, timeout); + } + } + }); + } } /** @@ -233,13 +244,13 @@ function initEventHandler() { * @return {string} */ function getTemplate() { - // language=HTML - return ` - <div data-monster-role="control" part="control" - data-monster-attributes="disabled path:disabled | if:true"> + // language=HTML + return ` + <div data-monster-role="control" part="control" + data-monster-attributes="disabled path:disabled | if:true"> <monster-context-error data-monster-option-classes-button="monster-theme-error-2 monster-theme-background-inherit"></monster-context-error> - <div class="monster-spinner" + <div class="monster-spinner" data-monster-attributes="data-monster-state-loader path:state.spinner"></div> </div> `; diff --git a/source/data/datasource/server/restapi.mjs b/source/data/datasource/server/restapi.mjs index 258ffec6020e1fd34329a1fd6f9a9301fedddb79..c997e588f1db6ed66447b6928a587f1fa69e4eb2 100644 --- a/source/data/datasource/server/restapi.mjs +++ b/source/data/datasource/server/restapi.mjs @@ -245,7 +245,7 @@ function fetchData(init, key, callback) { if (body.length > 100) { body = `${body.substring(0, 97)}...`; } - + throw new DataFetchError( getInternalLocalizationMessage( `i18n{the-response-does-not-contain-a-valid-json::actual=${body}}`, @@ -257,6 +257,7 @@ function fetchData(init, key, callback) { if (callback && isFunction(callback)) { callback(obj); } + return response; }) .catch((e) => { diff --git a/source/data/datasource/server/restapi/data-fetch-error.mjs b/source/data/datasource/server/restapi/data-fetch-error.mjs index 4de7d393fe379aac109523dba78266ff1b4c82ae..cb06ebeab0a3c24cdd39bfd3e29a6c361e0f40ce 100644 --- a/source/data/datasource/server/restapi/data-fetch-error.mjs +++ b/source/data/datasource/server/restapi/data-fetch-error.mjs @@ -32,8 +32,20 @@ class DataFetchError extends Error { */ constructor(message, response) { super(message); + + let body = null + + if (response instanceof Response) { + body = response.text(); + } + + if(!(body instanceof Promise)) { + body = Promise.resolve(body); + } + this[internalSymbol] = { response: response, + body : body }; } @@ -47,10 +59,17 @@ class DataFetchError extends Error { ); } + /** + * @return {string|Object} + */ + getBody() { + return this[internalSymbol]?.["body"]; + } + /** * @return {Response} */ getResponse() { - return this[internalSymbol]["response"]; + return this[internalSymbol]?.["response"]; } } diff --git a/source/i18n/internal.mjs b/source/i18n/internal.mjs index 4073e1cbd63c787effe205c66b93ae6f98e3a91f..f1524879122a25a33e75f451a86635de7187dff0 100644 --- a/source/i18n/internal.mjs +++ b/source/i18n/internal.mjs @@ -28,7 +28,7 @@ function getInternalTranslations() { let locale = "en"; try { - let locale = getLocaleOfDocument(); + locale = getLocaleOfDocument(); } catch (error) {} let messages = {};