diff --git a/development/issues/closed/194.html b/development/issues/closed/194.html new file mode 100644 index 0000000000000000000000000000000000000000..b561bd1d2a0204dca60053b16ec94651e04ee6d5 --- /dev/null +++ b/development/issues/closed/194.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Monster reload: loaded CSS overwrites existing CSS #194</title> + <script src="./194.mjs" type="module"></script> +</head> +<body> +<h1>only try to transfer the part that has been changed #194</h1> +<p></p> +<ul> + <li><a href="https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/194">Issue #194</a></li> + <li><a href="/">Back to overview</a></li> +</ul> +<main> + + <p>Das ist ein test</p> + + <monster-reload + data-monster-option-shadowmode="open" + data-monster-option-filter="[data-monster-role=inner]" + data-monster-url="/issue-194.html"> + SLOTTEDNODE + + </monster-reload> + + +</main> +</body> +</html> diff --git a/development/issues/closed/194.mjs b/development/issues/closed/194.mjs new file mode 100644 index 0000000000000000000000000000000000000000..e769400bbcdfdacc7854b8a80cacabccd4591035 --- /dev/null +++ b/development/issues/closed/194.mjs @@ -0,0 +1,28 @@ +/** + * @file development/issues/open/194.mjs + * @url https://gitlab.schukai.com/oss/libraries/javascript/monster/-/issues/194 + * @description only try to transfer the part that has been changed + * @issue 194 + */ + +import "../../../source/components/style/property.pcss"; +import "../../../source/components/style/color.pcss"; +import "../../../source/components/style/normalize.pcss"; +import "../../../source/components/style/typography.pcss"; +import "../../../source/components/style/form.pcss"; + +import "../../../source/components/datatable/datasource/rest.mjs"; +import "../../../source/components/datatable/save-button.mjs"; +import "../../../source/components/form/form.mjs"; +import "../../../source/components/form/field-set.mjs"; +import "../../../source/components/form/select.mjs"; +import "../../../source/components/form/reload.mjs"; +import "../../../source/components/form/context-error.mjs"; +import {domReady} from "../../../source/dom/ready.mjs"; + +domReady.then(() => { + + + +}); + diff --git a/development/mock/issue-194.js b/development/mock/issue-194.js new file mode 100644 index 0000000000000000000000000000000000000000..93b605d8b6014da6b4406230c47edddfb4910e89 --- /dev/null +++ b/development/mock/issue-194.js @@ -0,0 +1,35 @@ +// language=HTML +const html = `<div> + <div style="color:red">This should not be displayed</div> + <div data-monster-role="inner"> + + <style> + p { + color: blue; + } + </style> + + <h2>Reload HTML</h2> + <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit</p> + </div> +</div> + + +`; + +export default [ + { + url: '/issue-194.html', + method: 'get', + rawResponse: async (req, res) => { + res.setHeader('Content-Type', 'text/html') + res.statusCode = 200 + + setTimeout(function () { + res.end(html) + }, 2000); + + }, + } + +]; \ No newline at end of file diff --git a/source/components/form/reload.mjs b/source/components/form/reload.mjs index bdc3f0cef4123800dd5d7af83fd5ee89bf4bd4c0..439af80e06161b5ff6ecaef7e33ea10369e63860 100644 --- a/source/components/form/reload.mjs +++ b/source/components/form/reload.mjs @@ -12,24 +12,24 @@ * SPDX-License-Identifier: AGPL-3.0 */ -import { instanceSymbol } from "../../constants.mjs"; -import { addAttributeToken } from "../../dom/attributes.mjs"; +import {instanceSymbol} from "../../constants.mjs"; +import {addAttributeToken} from "../../dom/attributes.mjs"; import { - ATTRIBUTE_ERRORMESSAGE, - ATTRIBUTE_ROLE, + ATTRIBUTE_ERRORMESSAGE, + ATTRIBUTE_ROLE, } from "../../dom/constants.mjs"; import { - assembleMethodSymbol, - attributeObserverSymbol, - CustomElement, - initMethodSymbol, - registerCustomElement, + assembleMethodSymbol, + attributeObserverSymbol, + CustomElement, + initMethodSymbol, + registerCustomElement, } from "../../dom/customelement.mjs"; -import { isString } from "../../types/is.mjs"; -import { ATTRIBUTE_FORM_RELOAD, ATTRIBUTE_FORM_URL } from "./constants.mjs"; -import { loadAndAssignContent } from "./util/fetch.mjs"; +import {isString} from "../../types/is.mjs"; +import {ATTRIBUTE_FORM_RELOAD, ATTRIBUTE_FORM_URL} from "./constants.mjs"; +import {loadAndAssignContent} from "./util/fetch.mjs"; -export { Reload }; +export {Reload}; /** * @private @@ -106,128 +106,128 @@ const intersectionObserverWasInitialized = Symbol("wasInitialized"); * @fires Monster.Components.event:monster-fetched */ class Reload extends CustomElement { - /** - * This method is called by the `instanceof` operator. - * @returns {symbol} - * @since 2.1.0 - */ - static get [instanceSymbol]() { - return Symbol.for("@schukai/monster/components/form/reload"); - } - - /** - * 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 {string} url=undefined - * @property {string} reload=undefined currently the values defined are `onshow` and `always`. The default `onshow` removes the IntersectionObserver. This means that the content is only loaded once. reloading of the content does not occur. - * @property {string} filter=undefined dom selectors to search for elements, if undefined then everything is taken - * @property {Monster.Components.Form.Processor[]} processors - * @property {Object} fetch Fetch [see Using Fetch mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) - * @property {String} fetch.redirect=error - * @property {String} fetch.method=GET - * @property {String} fetch.mode=same-origin - * @property {String} fetch.credentials=same-origin - * @property {Object} fetch.headers={"accept":"text/html"}} - */ - get defaults() { - return Object.assign( - {}, - super.defaults, - { - templates: { - main: getTemplate.call(this), - }, - shadowMode: false, - url: undefined, - reload: "onshow", - filter: undefined, - fetch: { - redirect: "error", - method: "GET", - mode: "same-origin", - credentials: "same-origin", - headers: { - accept: "text/html", - }, - }, - }, - initOptionsFromArguments.call(this), - ); - } - - /** - * This method determines which attributes are to be monitored by `attributeChangedCallback()`. - * - * @return {string[]} - */ - static get observedAttributes() { - const list = super.observedAttributes; - list.push(ATTRIBUTE_FORM_URL); - return list; - } - - /** - * @return {void} - */ - [initMethodSymbol]() { - super[initMethodSymbol](); - - // data-monster-options - this[attributeObserverSymbol][ATTRIBUTE_FORM_URL] = (url) => { - if (this.hasAttribute(ATTRIBUTE_FORM_URL)) { - this.setOption("url", new URL(url, document.location).toString()); - } else { - this.setOption("url", undefined); - } - }; - } - - /** - * This method is called internal and should not be called directly. - * @throws {Error} missing default slot - * @throws {Error} no shadow-root is defined - * @throws {Error} missing url - * @throws {Error} we won't be able to read the data - * @throws {Error} request failed - * @throws {Error} not found - * @throws {Error} undefined status or type - * @fires Monster.Components.event:monster-fetched - * @return {Monster.Components.Form.Form} - */ - [assembleMethodSymbol]() { - super[assembleMethodSymbol](); - initIntersectionObserver.call(this); - } - - /** - * This method is called internal and should not be called directly. - * - * @return {string} - */ - static getTag() { - return "monster-reload"; - } - - /** - * load content from url - * - * It is important to know that with this function the loading is executed - * directly. it is loaded as well when the element is not visible. - * - * @param {string|undefined} url - */ - fetch(url) { - if (isString(url) || url instanceof URL) { - this.setAttribute(ATTRIBUTE_FORM_URL, `${url}`); - } - - return loadContent.call(this); - } + /** + * This method is called by the `instanceof` operator. + * @returns {symbol} + * @since 2.1.0 + */ + static get [instanceSymbol]() { + return Symbol.for("@schukai/monster/components/form/reload"); + } + + /** + * 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 {string} url=undefined + * @property {string} reload=undefined currently the values defined are `onshow` and `always`. The default `onshow` removes the IntersectionObserver. This means that the content is only loaded once. reloading of the content does not occur. + * @property {string} filter=undefined dom selectors to search for elements, if undefined then everything is taken + * @property {Monster.Components.Form.Processor[]} processors + * @property {Object} fetch Fetch [see Using Fetch mozilla.org](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) + * @property {String} fetch.redirect=error + * @property {String} fetch.method=GET + * @property {String} fetch.mode=same-origin + * @property {String} fetch.credentials=same-origin + * @property {Object} fetch.headers={"accept":"text/html"}} + */ + get defaults() { + return Object.assign( + {}, + super.defaults, + { + templates: { + main: getTemplate.call(this), + }, + shadowMode: null, + url: null, + reload: "onshow", + filter: null, + fetch: { + redirect: "error", + method: "GET", + mode: "same-origin", + credentials: "same-origin", + headers: { + accept: "text/html", + }, + }, + }, + initOptionsFromArguments.call(this), + ); + } + + /** + * This method determines which attributes are to be monitored by `attributeChangedCallback()`. + * + * @return {string[]} + */ + static get observedAttributes() { + const list = super.observedAttributes; + list.push(ATTRIBUTE_FORM_URL); + return list; + } + + /** + * @return {void} + */ + [initMethodSymbol]() { + super[initMethodSymbol](); + + // data-monster-options + this[attributeObserverSymbol][ATTRIBUTE_FORM_URL] = (url) => { + if (this.hasAttribute(ATTRIBUTE_FORM_URL)) { + this.setOption("url", new URL(url, document.location).toString()); + } else { + this.setOption("url", undefined); + } + }; + } + + /** + * This method is called internal and should not be called directly. + * @throws {Error} missing default slot + * @throws {Error} no shadow-root is defined + * @throws {Error} missing url + * @throws {Error} we won't be able to read the data + * @throws {Error} request failed + * @throws {Error} not found + * @throws {Error} undefined status or type + * @fires Monster.Components.event:monster-fetched + * @return {Monster.Components.Form.Form} + */ + [assembleMethodSymbol]() { + super[assembleMethodSymbol](); + initIntersectionObserver.call(this); + } + + /** + * This method is called internal and should not be called directly. + * + * @return {string} + */ + static getTag() { + return "monster-reload"; + } + + /** + * load content from url + * + * It is important to know that with this function the loading is executed + * directly. it is loaded as well when the element is not visible. + * + * @param {string|undefined} url + */ + fetch(url) { + if (isString(url) || url instanceof URL) { + this.setAttribute(ATTRIBUTE_FORM_URL, `${url}`); + } + + return loadContent.call(this); + } } /** @@ -241,19 +241,19 @@ class Reload extends CustomElement { * @return {object} */ function initOptionsFromArguments() { - const options = {}; + const options = {}; - const url = this.getAttribute(ATTRIBUTE_FORM_URL); + const url = this.getAttribute(ATTRIBUTE_FORM_URL); - if (isString(url)) { - options["url"] = new URL(url, document.location).toString(); - } + if (isString(url)) { + options["url"] = new URL(url, document.location).toString(); + } - if (this.hasAttribute(ATTRIBUTE_FORM_RELOAD)) { - options["reload"] = this.getAttribute(ATTRIBUTE_FORM_RELOAD).toLowerCase(); - } + if (this.hasAttribute(ATTRIBUTE_FORM_RELOAD)) { + options["reload"] = this.getAttribute(ATTRIBUTE_FORM_RELOAD).toLowerCase(); + } - return options; + return options; } /** @@ -268,37 +268,37 @@ function initOptionsFromArguments() { * @fires Monster.Components.event:monster-fetched */ function initIntersectionObserver() { - if (this[intersectionObserverWasInitialized] === true) { - return; - } - - this[intersectionObserverWasInitialized] = true; - - const options = { - threshold: [0.5], - }; - - const callback = (entries, observer) => { - for (const [, entry] of entries.entries()) { - if (entry.isIntersecting === true) { - // undefined or always do the same - if (this.getOption("reload") === "onshow") { - observer.disconnect(); - } - - try { - loadContent.call(this).catch((e) => { - addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString()); - }); - } catch (e) { - addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString()); - } - } - } - }; - - const observer = new IntersectionObserver(callback, options); - observer.observe(this); + if (this[intersectionObserverWasInitialized] === true) { + return; + } + + this[intersectionObserverWasInitialized] = true; + + const options = { + threshold: [0.5], + }; + + const callback = (entries, observer) => { + for (const [, entry] of entries.entries()) { + if (entry.isIntersecting === true) { + // undefined or always do the same + if (this.getOption("reload") === "onshow") { + observer.disconnect(); + } + + try { + loadContent.call(this).catch((e) => { + addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString()); + }); + } catch (e) { + addAttributeToken(this, ATTRIBUTE_ERRORMESSAGE, e.toString()); + } + } + } + }; + + const observer = new IntersectionObserver(callback, options); + observer.observe(this); } /** @@ -318,41 +318,41 @@ function initIntersectionObserver() { * @return {Promise} */ function loadContent() { - const url = this.getOption("url", undefined); - if (!isString(url) || url === "") { - throw new Error("missing url"); - } - - const options = this.getOption("fetch", {}); - - let parentNode = this; - if (this.shadowRoot) { - parentNode = this.shadowRoot; - } - - let container = parentNode.querySelector(`[${ATTRIBUTE_ROLE}=container]`); - let currentDisplayMode = container?.style?.display; - - if (currentDisplayMode === undefined) { - currentDisplayMode = "inherit"; - } - - if (!(container instanceof HTMLElement)) { - container = document.createElement("div"); - container.style.display = "none"; - container.setAttribute(ATTRIBUTE_ROLE, "container"); - parentNode.appendChild(container); - } - - return loadAndAssignContent(container, url, options, this.getOption("filter")) - .then(() => { - if (currentDisplayMode !== undefined) { - container.style.display = currentDisplayMode; - } - }) - .catch((e) => { - throw e; - }); + const url = this.getOption("url", undefined); + if (!isString(url) || url === "") { + throw new Error("missing url"); + } + + const options = this.getOption("fetch", {}); + + let parentNode = this; + if (this.shadowRoot) { + parentNode = this.shadowRoot; + } + + let container = parentNode.querySelector(`[${ATTRIBUTE_ROLE}=container]`); + let currentDisplayMode = container?.style?.display; + + if (currentDisplayMode === undefined) { + currentDisplayMode = "inherit"; + } + + if (!(container instanceof HTMLElement)) { + container = document.createElement("div"); + container.style.display = "none"; + container.setAttribute(ATTRIBUTE_ROLE, "container"); + parentNode.appendChild(container); + } + + return loadAndAssignContent(container, url, options, this.getOption("filter")) + .then(() => { + if (currentDisplayMode !== undefined) { + container.style.display = currentDisplayMode; + } + }) + .catch((e) => { + throw e; + }); } /** @@ -360,7 +360,7 @@ function loadContent() { * @return {string} */ function getTemplate() { - return this.innerHTML; + return this.innerHTML; } registerCustomElement(Reload);