From 460df2fed7752dc4d8b444d46ceb4972cda6d0b5 Mon Sep 17 00:00:00 2001
From: Volker Schukai <volker.schukai@schukai.com>
Date: Thu, 21 Nov 2024 12:59:44 +0100
Subject: [PATCH] fix(api-button): check instance

---
 flake.lock                            |  12 +-
 source/components/form/api-button.mjs | 833 +++++++++++++-------------
 2 files changed, 425 insertions(+), 420 deletions(-)

diff --git a/flake.lock b/flake.lock
index 1a499d0e7..c24bd0bff 100644
--- a/flake.lock
+++ b/flake.lock
@@ -89,11 +89,11 @@
         "systems": "systems_3"
       },
       "locked": {
-        "lastModified": 1726560853,
-        "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
+        "lastModified": 1731533236,
+        "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
         "owner": "numtide",
         "repo": "flake-utils",
-        "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
+        "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
         "type": "github"
       },
       "original": {
@@ -168,11 +168,11 @@
     },
     "nixpkgs_3": {
       "locked": {
-        "lastModified": 1731239293,
-        "narHash": "sha256-q2yjIWFFcTzp5REWQUOU9L6kHdCDmFDpqeix86SOvDc=",
+        "lastModified": 1731797254,
+        "narHash": "sha256-df3dJApLPhd11AlueuoN0Q4fHo/hagP75LlM5K1sz9g=",
         "owner": "nixos",
         "repo": "nixpkgs",
-        "rev": "9256f7c71a195ebe7a218043d9f93390d49e6884",
+        "rev": "e8c38b73aeb218e27163376a2d617e61a2ad9b59",
         "type": "github"
       },
       "original": {
diff --git a/source/components/form/api-button.mjs b/source/components/form/api-button.mjs
index 6eccdbbd0..5a4bea7ab 100644
--- a/source/components/form/api-button.mjs
+++ b/source/components/form/api-button.mjs
@@ -12,31 +12,31 @@
  * SPDX-License-Identifier: AGPL-3.0
  */
 
-import { instanceSymbol } from "../../constants.mjs";
-import { buildMap } from "../../data/buildmap.mjs";
-import { Pathfinder } from "../../data/pathfinder.mjs";
-import { addAttributeToken } from "../../dom/attributes.mjs";
+import {instanceSymbol} from "../../constants.mjs";
+import {buildMap} from "../../data/buildmap.mjs";
+import {Pathfinder} from "../../data/pathfinder.mjs";
+import {addAttributeToken} from "../../dom/attributes.mjs";
 import {
-	ATTRIBUTE_ERRORMESSAGE,
-	ATTRIBUTE_ROLE,
+    ATTRIBUTE_ERRORMESSAGE,
+    ATTRIBUTE_ROLE,
 } from "../../dom/constants.mjs";
 import {
-	assembleMethodSymbol,
-	registerCustomElement,
+    assembleMethodSymbol,
+    registerCustomElement,
 } from "../../dom/customelement.mjs";
 import {
-	findTargetElementFromEvent,
-	fireCustomEvent,
+    findTargetElementFromEvent,
+    fireCustomEvent,
 } from "../../dom/events.mjs";
-import { isArray, isPrimitive, isIterable } from "../../types/is.mjs";
-import { validateString } from "../../types/validate.mjs";
-import { ActionButton } from "./action-button.mjs";
-import { ApiButtonStyleSheet } from "./stylesheet/api-button.mjs";
-import { isObject, isFunction } from "../../types/is.mjs";
-import { getGlobal } from "../../types/global.mjs";
-import { Formatter } from "../../text/formatter.mjs";
+import {isArray, isPrimitive, isIterable} from "../../types/is.mjs";
+import {validateString} from "../../types/validate.mjs";
+import {ActionButton} from "./action-button.mjs";
+import {ApiButtonStyleSheet} from "./stylesheet/api-button.mjs";
+import {isObject, isFunction} from "../../types/is.mjs";
+import {getGlobal} from "../../types/global.mjs";
+import {Formatter} from "../../text/formatter.mjs";
 
-export { ApiButton };
+export {ApiButton};
 
 /**
  * @private
@@ -67,220 +67,220 @@ const containerElementSymbol = Symbol("containerElement");
  * @fires monster-api-button-failed
  */
 class ApiButton extends ActionButton {
-	/**
-	 * This method is called by the `instanceof` operator.
-	 * @return {symbol}
-	 */
-	static get [instanceSymbol]() {
-		return Symbol.for("@schukai/monster/components/form/api-button@@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} mapping - The mapping object.
-	 * @property {string} mapping.selector - The selector to find the buttons in the response.
-	 * @property {string} mapping.labelSelector - The selector to find the label for the button.
-	 * @property {string} mapping.labelTemplate - The template to create the label for the button.
-	 * @property {string} mapping.apiTemplate - The template to create the api for the button.
-	 * @property {string} mapping.urlTemplate - The template to create the url for the button.
-	 * @property {function} mapping.filter - The filter function to filter the buttons.
-	 * @property {string} url - The url to fetch the data.
-	 * @property {object} api - The api options.
-	 * @property {object} api.fetch - The fetch options.
-	 * @property {string} api.body - The body template.
-	 * @property {object} callbacks - The callbacks object.
-	 * @property {function} callbacks.beforeApi - The beforeApi callback.
-	 * @property {object} fetch - The fetch options.
-	 * @property {string} fetch.redirect - The redirect option.
-	 * @property {string} fetch.method - The method option.
-	 * @property {string} fetch.mode - The mode option.
-	 * @property {string} fetch.credentials - The credentials option.
-	 * @property {object} fetch.headers - The headers option.
-	 * @property {string} fetch.headers.accept - The acceptance option.
-	 * @extends {ActionButton.defaults}
-	 */
-	get defaults() {
-		const opts = Object.assign({}, super.defaults, {
-			mapping: {
-				selector: "*",
-				labelSelector: "",
-				labelTemplate: "",
-				apiTemplate: "",
-				urlTemplate: "",
-				filter: "",
-			},
-			api: {
-				fetch: {
-					method: "POST",
-					redirect: "error",
-					mode: "same-origin",
-					credentials: "same-origin",
-					headers: {
-						accept: "application/json",
-					},
-				},
-				body: {},
-			},
-			url: "",
-			callbacks: {
-				beforeApi: null,
-			},
-			fetch: {
-				redirect: "error",
-				method: "GET",
-				mode: "same-origin",
-				credentials: "same-origin",
-				headers: {
-					accept: "application/json",
-				},
-			},
-		});
-
-		opts["actions"]["execute"] = executeAPIButton.bind(self);
-
-		return opts;
-	}
-
-	/**
-	 *
-	 * @return {Promise}
-	 */
-	fetch(url) {
-		if (url instanceof URL) {
-			url = url.toString();
-		}
-
-		if (url !== undefined) {
-			url = validateString(url);
-		}
-
-		return fetchData.call(this, url).then((map) => {
-			if (
-				isObject(map) ||
-				isArray(map) | (map instanceof Set) ||
-				map instanceof Map
-			) {
-				this.importButtons(map);
-			}
-		});
-	}
-
-	/**
-	 * Import buttons from a map.
-	 *
-	 * @param {array|object|Map|Set} data
-	 * @return {ApiButton}
-	 * @throws {Error} map is not iterable
-	 * @throws {Error} missing label configuration
-	 */
-	importButtons(data) {
-		const mappingOptions = this.getOption("mapping", {});
-		const selector = mappingOptions?.["selector"];
-		const labelSelector = mappingOptions?.["labelSelector"];
-		const labelTemplate = mappingOptions?.["labelTemplate"];
-		const apiTemplate = mappingOptions?.["apiTemplate"];
-		let urlTemplate = mappingOptions?.["urlTemplate"];
-		const filter = mappingOptions?.["filter"];
-
-		let flag = false;
-		let apiEqualUrl = false;
-		if (labelTemplate === "") {
-			addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "empty label template");
-			flag = true;
-		}
-
-		if (apiTemplate === "") {
-			addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "empty api template");
-			flag = true;
-		}
-
-		if (urlTemplate === "") {
-			urlTemplate = apiTemplate;
-			apiEqualUrl = true;
-		}
-
-		if (flag === true) {
-			throw new Error("missing label or api configuration");
-		}
-
-		if (isPrimitive(labelSelector) && labelSelector !== "") {
-			const finder = new Pathfinder(data);
-			const label = finder.getVia(labelSelector);
-			this.setOption("labels.button", label);
-			this.value = label;
-		}
-
-		let labelMap;
-		const urlMap = buildMap(data, selector, urlTemplate, apiTemplate, filter);
-		if (apiEqualUrl === true) {
-			labelMap = urlMap;
-		} else {
-			labelMap = buildMap(data, selector, labelTemplate, apiTemplate, filter);
-		}
-
-		const buttons = [];
-		if (!isIterable(urlMap)) {
-			throw new Error("map is not iterable");
-		}
-
-		for (const [iterKey] of urlMap) {
-			const vmUrl = urlMap.get(iterKey);
-			const vmLabel = labelMap.get(iterKey);
-			buttons.push({
-				label: vmLabel,
-				class: "monster-button-outline-primary monster-border-0",
-				action: this.getOption("actions.execute"),
-				url: vmUrl,
-				cmd: iterKey,
-			});
-		}
-
-		try {
-			this.updateI18n();
-		} catch (e) {
-			addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
-		}
-
-		this.setOption("buttons", buttons);
-
-		fireCustomEvent(this, "monster-button-set", {
-			buttons: buttons,
-		});
-
-		return this;
-	}
-
-	/**
-	 *
-	 * @return {ApiButton}
-	 */
-	[assembleMethodSymbol]() {
-		super[assembleMethodSymbol]();
-		initControlReferences.call(this);
-		initEventHandler.call(this);
-
-		return this;
-	}
-
-	/**
-	 * @return {string}
-	 */
-	static getTag() {
-		return "monster-api-button";
-	}
-
-	/**
-	 * @return {Array<CSSStyleSheet>}
-	 */
-	static getCSSStyleSheet() {
-		const styles = super.getCSSStyleSheet();
-		styles.push(ApiButtonStyleSheet);
-		return styles;
-	}
+    /**
+     * This method is called by the `instanceof` operator.
+     * @return {symbol}
+     */
+    static get [instanceSymbol]() {
+        return Symbol.for("@schukai/monster/components/form/api-button@@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} mapping - The mapping object.
+     * @property {string} mapping.selector - The selector to find the buttons in the response.
+     * @property {string} mapping.labelSelector - The selector to find the label for the button.
+     * @property {string} mapping.labelTemplate - The template to create the label for the button.
+     * @property {string} mapping.apiTemplate - The template to create the api for the button.
+     * @property {string} mapping.urlTemplate - The template to create the url for the button.
+     * @property {function} mapping.filter - The filter function to filter the buttons.
+     * @property {string} url - The url to fetch the data.
+     * @property {object} api - The api options.
+     * @property {object} api.fetch - The fetch options.
+     * @property {string} api.body - The body template.
+     * @property {object} callbacks - The callbacks object.
+     * @property {function} callbacks.beforeApi - The beforeApi callback.
+     * @property {object} fetch - The fetch options.
+     * @property {string} fetch.redirect - The redirect option.
+     * @property {string} fetch.method - The method option.
+     * @property {string} fetch.mode - The mode option.
+     * @property {string} fetch.credentials - The credentials option.
+     * @property {object} fetch.headers - The headers option.
+     * @property {string} fetch.headers.accept - The acceptance option.
+     * @extends {ActionButton.defaults}
+     */
+    get defaults() {
+        const opts = Object.assign({}, super.defaults, {
+            mapping: {
+                selector: "*",
+                labelSelector: "",
+                labelTemplate: "",
+                apiTemplate: "",
+                urlTemplate: "",
+                filter: "",
+            },
+            api: {
+                fetch: {
+                    method: "POST",
+                    redirect: "error",
+                    mode: "same-origin",
+                    credentials: "same-origin",
+                    headers: {
+                        accept: "application/json",
+                    },
+                },
+                body: {},
+            },
+            url: "",
+            callbacks: {
+                beforeApi: null,
+            },
+            fetch: {
+                redirect: "error",
+                method: "GET",
+                mode: "same-origin",
+                credentials: "same-origin",
+                headers: {
+                    accept: "application/json",
+                },
+            },
+        });
+
+        opts["actions"]["execute"] = executeAPIButton.bind(self);
+
+        return opts;
+    }
+
+    /**
+     *
+     * @return {Promise}
+     */
+    fetch(url) {
+        if (url instanceof URL) {
+            url = url.toString();
+        }
+
+        if (url !== undefined) {
+            url = validateString(url);
+        }
+
+        return fetchData.call(this, url).then((map) => {
+            if (
+                isObject(map) ||
+                isArray(map) | (map instanceof Set) ||
+                map instanceof Map
+            ) {
+                this.importButtons(map);
+            }
+        });
+    }
+
+    /**
+     * Import buttons from a map.
+     *
+     * @param {array|object|Map|Set} data
+     * @return {ApiButton}
+     * @throws {Error} map is not iterable
+     * @throws {Error} missing label configuration
+     */
+    importButtons(data) {
+        const mappingOptions = this.getOption("mapping", {});
+        const selector = mappingOptions?.["selector"];
+        const labelSelector = mappingOptions?.["labelSelector"];
+        const labelTemplate = mappingOptions?.["labelTemplate"];
+        const apiTemplate = mappingOptions?.["apiTemplate"];
+        let urlTemplate = mappingOptions?.["urlTemplate"];
+        const filter = mappingOptions?.["filter"];
+
+        let flag = false;
+        let apiEqualUrl = false;
+        if (labelTemplate === "") {
+            addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "empty label template");
+            flag = true;
+        }
+
+        if (apiTemplate === "") {
+            addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, "empty api template");
+            flag = true;
+        }
+
+        if (urlTemplate === "") {
+            urlTemplate = apiTemplate;
+            apiEqualUrl = true;
+        }
+
+        if (flag === true) {
+            throw new Error("missing label or api configuration");
+        }
+
+        if (isPrimitive(labelSelector) && labelSelector !== "") {
+            const finder = new Pathfinder(data);
+            const label = finder.getVia(labelSelector);
+            this.setOption("labels.button", label);
+            this.value = label;
+        }
+
+        let labelMap;
+        const urlMap = buildMap(data, selector, urlTemplate, apiTemplate, filter);
+        if (apiEqualUrl === true) {
+            labelMap = urlMap;
+        } else {
+            labelMap = buildMap(data, selector, labelTemplate, apiTemplate, filter);
+        }
+
+        const buttons = [];
+        if (!isIterable(urlMap)) {
+            throw new Error("map is not iterable");
+        }
+
+        for (const [iterKey] of urlMap) {
+            const vmUrl = urlMap.get(iterKey);
+            const vmLabel = labelMap.get(iterKey);
+            buttons.push({
+                label: vmLabel,
+                class: "monster-button-outline-primary monster-border-0",
+                action: this.getOption("actions.execute"),
+                url: vmUrl,
+                cmd: iterKey,
+            });
+        }
+
+        try {
+            this.updateI18n();
+        } catch (e) {
+            addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.message);
+        }
+
+        this.setOption("buttons", buttons);
+
+        fireCustomEvent(this, "monster-button-set", {
+            buttons: buttons,
+        });
+
+        return this;
+    }
+
+    /**
+     *
+     * @return {ApiButton}
+     */
+    [assembleMethodSymbol]() {
+        super[assembleMethodSymbol]();
+        initControlReferences.call(this);
+        initEventHandler.call(this);
+
+        return this;
+    }
+
+    /**
+     * @return {string}
+     */
+    static getTag() {
+        return "monster-api-button";
+    }
+
+    /**
+     * @return {Array<CSSStyleSheet>}
+     */
+    static getCSSStyleSheet() {
+        const styles = super.getCSSStyleSheet();
+        styles.push(ApiButtonStyleSheet);
+        return styles;
+    }
 }
 
 /**
@@ -290,145 +290,145 @@ class ApiButton extends ActionButton {
  * @param {Monster.Components.Form.ApiButton} element
  */
 function executeAPIButton(event, button, element) {
-	const self = element;
-
-	const fetchOptions = self.getOption("api.fetch", {});
-
-	const callback = self.getOption("callbacks.beforeApi");
-	if (isFunction(callback)) {
-		callback.call(self, fetchOptions);
-	}
-
-	let url = undefined;
-	let label = undefined;
-	let key = undefined;
-
-	const attr = button.getAttribute("data-monster-insert-reference");
-	if (attr) {
-		const index = attr.split("-")[1];
-		const b = self.getOption("buttons." + index);
-
-		url = b?.["url"];
-		label = b?.["label"];
-		key = b?.["cmd"];
-	}
-
-	const body = self.getOption("api.body");
-	if (isObject(body)) {
-		const bodyString = JSON.stringify(body);
-
-		const obj = {
-			url: url,
-			label: label,
-			value: self.getOption("value"),
-			key: key,
-			id: self.getOption("id"),
-		};
-
-		fetchOptions.body = new Formatter(obj, {}).format(bodyString);
-	}
-
-	if (button instanceof HTMLElement) {
-		button.setState("activity");
-	}
-
-	fireCustomEvent(self, "monster-api-button-click", {
-		button,
-	});
-
-	const global = getGlobal();
-	global
-		.fetch(url, fetchOptions)
-		.then((response) => {
-			if (!response.ok) {
-				if (button instanceof HTMLElement) {
-					button.setState("successful", 4000);
-				}
-				return Promise.reject(response);
-			}
-
-			const contentType = response?.headers?.get("content-type");
-			if (contentType && contentType.indexOf("application/json") !== -1) {
-				return response
-					.text()
-					.then((text) => {
-						try {
-							const data = JSON.parse(text); // Try to parse the response as JSON
-
-							if (button instanceof HTMLElement) {
-								button.setState("successful", 4000);
-							}
-
-							fireCustomEvent(self, "monster-api-button-successful", {
-								button,
-								data,
-								response,
-								contentType: response.headers.get("Content-Type"),
-							});
-						} catch (error) {
-							if (button instanceof HTMLElement) {
-								button.setState("failed", 4000);
-								button.setMessage(error.message).showMessage(2000);
-							}
-
-							fireCustomEvent(self, "monster-api-button-failed", {
-								button,
-								error,
-								response,
-								contentType: response.headers.get("Content-Type"),
-							});
-						}
-					})
-					.catch((error) => {
-						if (button instanceof HTMLElement) {
-							button.setState("failed", 4000);
-							button.setMessage("request failed").showMessage(2000);
-						}
-
-						fireCustomEvent(self, "monster-api-button-failed", {
-							button,
-							error,
-							response,
-							contentType: response.headers.get("Content-Type"),
-						});
-					});
-			} else {
-				return response
-					.blob()
-					.then((data) => {
-						fireCustomEvent(self, "monster-api-button-successful", {
-							button,
-							data,
-							response,
-							contentType: response.headers.get("Content-Type"),
-						});
-					})
-					.catch((error) => {
-						if (button instanceof HTMLElement) {
-							button.setState("failed", 4000);
-							button.setMessage("request failed").showMessage(2000);
-						}
-
-						fireCustomEvent(self, "monster-api-button-failed", {
-							button,
-							error,
-							response,
-							contentType: response.headers.get("Content-Type"),
-						});
-					});
-			}
-		})
-		.catch((error) => {
-			if (button instanceof HTMLElement) {
-				button.setState("failed", 4000);
-				button.setMessage(error.message).showMessage(2000);
-			}
-
-			fireCustomEvent(self, "monster-api-button-failed", {
-				button,
-				error,
-			});
-		});
+    const self = element;
+
+    const fetchOptions = self.getOption("api.fetch", {});
+
+    const callback = self.getOption("callbacks.beforeApi");
+    if (isFunction(callback)) {
+        callback.call(self, fetchOptions);
+    }
+
+    let url = undefined;
+    let label = undefined;
+    let key = undefined;
+
+    const attr = button.getAttribute("data-monster-insert-reference");
+    if (attr) {
+        const index = attr.split("-")[1];
+        const b = self.getOption("buttons." + index);
+
+        url = b?.["url"];
+        label = b?.["label"];
+        key = b?.["cmd"];
+    }
+
+    const body = self.getOption("api.body");
+    if (isObject(body)) {
+        const bodyString = JSON.stringify(body);
+
+        const obj = {
+            url: url,
+            label: label,
+            value: self.getOption("value"),
+            key: key,
+            id: self.getOption("id"),
+        };
+
+        fetchOptions.body = new Formatter(obj, {}).format(bodyString);
+    }
+
+    if (button instanceof HTMLElement) {
+        button.setState("activity");
+    }
+
+    fireCustomEvent(self, "monster-api-button-click", {
+        button,
+    });
+
+    const global = getGlobal();
+    global
+        .fetch(url, fetchOptions)
+        .then((response) => {
+            if (!response.ok) {
+                if (button instanceof HTMLElement) {
+                    button.setState("successful", 4000);
+                }
+                return Promise.reject(response);
+            }
+
+            const contentType = response?.headers?.get("content-type");
+            if (contentType && contentType.indexOf("application/json") !== -1) {
+                return response
+                    .text()
+                    .then((text) => {
+                        try {
+                            const data = JSON.parse(text); // Try to parse the response as JSON
+
+                            if (button instanceof HTMLElement) {
+                                button.setState("successful", 4000);
+                            }
+
+                            fireCustomEvent(self, "monster-api-button-successful", {
+                                button,
+                                data,
+                                response,
+                                contentType: response.headers.get("Content-Type"),
+                            });
+                        } catch (error) {
+                            if (button instanceof HTMLElement) {
+                                button.setState("failed", 4000);
+                                button.setMessage(error.message).showMessage(2000);
+                            }
+
+                            fireCustomEvent(self, "monster-api-button-failed", {
+                                button,
+                                error,
+                                response,
+                                contentType: response.headers.get("Content-Type"),
+                            });
+                        }
+                    })
+                    .catch((error) => {
+                        if (button instanceof HTMLElement) {
+                            button.setState("failed", 4000);
+                            button.setMessage("request failed").showMessage(2000);
+                        }
+
+                        fireCustomEvent(self, "monster-api-button-failed", {
+                            button,
+                            error,
+                            response,
+                            contentType: response.headers.get("Content-Type"),
+                        });
+                    });
+            } else {
+                return response
+                    .blob()
+                    .then((data) => {
+                        fireCustomEvent(self, "monster-api-button-successful", {
+                            button,
+                            data,
+                            response,
+                            contentType: response.headers.get("Content-Type"),
+                        });
+                    })
+                    .catch((error) => {
+                        if (button instanceof HTMLElement) {
+                            button.setState("failed", 4000);
+                            button.setMessage("request failed").showMessage(2000);
+                        }
+
+                        fireCustomEvent(self, "monster-api-button-failed", {
+                            button,
+                            error,
+                            response,
+                            contentType: response.headers.get("Content-Type"),
+                        });
+                    });
+            }
+        })
+        .catch((error) => {
+            if (button instanceof HTMLElement) {
+                button.setState("failed", 4000);
+                button.setMessage(error.message).showMessage(2000);
+            }
+
+            fireCustomEvent(self, "monster-api-button-failed", {
+                button,
+                error,
+            });
+        });
 }
 
 /**
@@ -439,29 +439,29 @@ function executeAPIButton(event, button, element) {
  * @throws {TypeError} unsupported response
  */
 function fetchData(url) {
-	if (!url) url = this.getOption("url");
-	if (!url) return Promise.resolve();
-
-	const fetchOptions = this.getOption("fetch", {});
-
-	const global = getGlobal();
-	return global
-		.fetch(url, fetchOptions)
-		.then((response) => {
-			const contentType = response.headers.get("content-type");
-			if (contentType && contentType.indexOf("application/json") !== -1) {
-				return response.text();
-			}
-
-			throw new TypeError(`unsupported response ${contentType}`);
-		})
-		.then((text) => {
-			try {
-				return Promise.resolve(JSON.parse(text));
-			} catch (e) {
-				throw new TypeError("the result cannot be parsed");
-			}
-		});
+    if (!url) url = this.getOption("url");
+    if (!url) return Promise.resolve();
+
+    const fetchOptions = this.getOption("fetch", {});
+
+    const global = getGlobal();
+    return global
+        .fetch(url, fetchOptions)
+        .then((response) => {
+            const contentType = response.headers.get("content-type");
+            if (contentType && contentType.indexOf("application/json") !== -1) {
+                return response.text();
+            }
+
+            throw new TypeError(`unsupported response ${contentType}`);
+        })
+        .then((text) => {
+            try {
+                return Promise.resolve(JSON.parse(text));
+            } catch (e) {
+                throw new TypeError("the result cannot be parsed");
+            }
+        });
 }
 
 /**
@@ -469,22 +469,27 @@ function fetchData(url) {
  * @return {ApiButton}
  */
 function initEventHandler() {
-	this[containerElementSymbol].addEventListener("click", (event) => {
-		const element = findTargetElementFromEvent(
-			event,
-			"data-monster-insert-reference",
-		);
-		const attr = element.getAttribute("data-monster-insert-reference");
-		if (attr) {
-			const index = attr.split("-")[1];
-			const b = this.getOption("buttons." + index);
-			if (isObject(b) && isFunction(b?.action)) {
-				b.action(event, element, this);
-			}
-		}
-	});
-
-	return this;
+    this[containerElementSymbol].addEventListener("click", (event) => {
+        const element = findTargetElementFromEvent(
+            event,
+            "data-monster-insert-reference",
+        );
+
+        if (!(element instanceof HTMLElement)) {
+            return;
+        }
+
+        const attr = element.getAttribute("data-monster-insert-reference");
+        if (attr) {
+            const index = attr.split("-")[1];
+            const b = this.getOption("buttons." + index);
+            if (isObject(b) && isFunction(b?.action)) {
+                b.action(event, element, this);
+            }
+        }
+    });
+
+    return this;
 }
 
 /**
@@ -492,10 +497,10 @@ function initEventHandler() {
  * @return {ApiButton}
  */
 function initControlReferences() {
-	this[containerElementSymbol] = this.shadowRoot.querySelector(
-		`[${ATTRIBUTE_ROLE}=container]`,
-	);
-	return this;
+    this[containerElementSymbol] = this.shadowRoot.querySelector(
+        `[${ATTRIBUTE_ROLE}=container]`,
+    );
+    return this;
 }
 
 registerCustomElement(ApiButton);
-- 
GitLab