Skip to content
Snippets Groups Projects
Select Git revision
  • 8ede41bd9268ae4c726e0f3973b860ec96bbf5f3
  • master default protected
  • 1.31
  • 4.34.1
  • 4.34.0
  • 4.33.1
  • 4.33.0
  • 4.32.2
  • 4.32.1
  • 4.32.0
  • 4.31.0
  • 4.30.1
  • 4.30.0
  • 4.29.1
  • 4.29.0
  • 4.28.0
  • 4.27.0
  • 4.26.0
  • 4.25.5
  • 4.25.4
  • 4.25.3
  • 4.25.2
  • 4.25.1
23 results

dataset.mjs

Blame
  • dataset.mjs 7.25 KiB
    /**
     * Copyright 2023 schukai GmbH
     * SPDX-License-Identifier: AGPL-3.0
     */
    
    import { instanceSymbol, internalSymbol } from "../../constants.mjs";
    import { Pathfinder } from "../../data/pathfinder.mjs";
    import { getLinkedObjects, hasObjectLink } from "../../dom/attributes.mjs";
    import { customElementUpdaterLinkSymbol } from "../../dom/constants.mjs";
    import {
    	assembleMethodSymbol,
    	CustomElement,
    	attributeObserverSymbol,
    	registerCustomElement,
    } from "../../dom/customelement.mjs";
    import { isString } from "../../types/is.mjs";
    import { Observer } from "../../types/observer.mjs";
    import { clone } from "../../util/clone.mjs";
    import {
    	ATTRIBUTE_DATASOURCE_SELECTOR,
    	ATTRIBUTE_DATATABLE_INDEX,
    } from "./constants.mjs";
    import { Datasource } from "./datasource.mjs";
    import { DatasetStyleSheet } from "./stylesheet/dataset.mjs";
    import {
    	handleDataSourceChanges,
    	datasourceLinkedElementSymbol,
    } from "./util.mjs";
    import { FormStyleSheet } from "../stylesheet/form.mjs";
    
    export { DataSet };
    
    /**
     * The data set component is used to show the data of a data source.
     *
     * <img src="./images/dataset.png">
     *
     * Dependencies: the system uses functions of the [monsterjs](https://monsterjs.org/) library
     *
     * You can create this control either by specifying the HTML tag <monster-dataset />` directly in the HTML or using
     * Javascript via the `document.createElement('monster-dataset');` method.
     *
     * ```html
     * <monster-dataset></monster-dataset>
     * ```
     *
     * Or you can create this CustomControl directly in Javascript:
     *
     * ```js
     * import '@schukai/component-datatable/source/dataset.mjs';
     * document.createElement('monster-dataset');
     * ```
     *
     * The Body should have a class "hidden" to ensure that the styles are applied correctly.
     *
     * ```css
     * body.hidden {
     *    visibility: hidden;
     * }
     * ```
     *
     * @startuml dataset.png
     * skinparam monochrome true
     * skinparam shadowing false
     * HTMLElement <|-- CustomElement
     * CustomElement <|-- DataSet
     * @enduml
     *
     * @copyright schukai GmbH
     * @memberOf Monster.Components.Datatable
     * @summary A data set
     */
    class DataSet extends CustomElement {
    	/**
    	 * This method is called by the `instanceof` operator.
    	 * @returns {symbol}
    	 */
    	static get [instanceSymbol]() {
    		return Symbol.for("@schukai/monster/components/dataset@@instance");
    	}
    
    	/**
    	 * This method determines which attributes are to be monitored by `attributeChangedCallback()`.
    	 *
    	 * @return {string[]}
    	 * @since 1.15.0
    	 */
    	static get observedAttributes() {
    		const attributes = super.observedAttributes;
    		attributes.push(ATTRIBUTE_DATATABLE_INDEX);
    		return attributes;
    	}
    
    	/**
    	 * 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 The datasource
    	 * @property {string} datasource.selector The selector of the datasource
    	 * @property {object} mapping The mapping
    	 * @property {string} mapping.data The data
    	 * @property {number} mapping.index The index
    	 * @property {Array} data The data
    	 */
    	get defaults() {
    		const obj = Object.assign({}, super.defaults, {
    			templates: {
    				main: getTemplate(),
    			},
    
    			datasource: {
    				selector: null,
    			},
    
    			mapping: {
    				data: "dataset",
    				index: 0,
    			},
    
    			data: {},
    		});
    
    		updateOptionsFromArguments.call(this, obj);
    		return obj;
    	}
    
    	/**
    	 *
    	 * @return {string}
    	 */
    	static getTag() {
    		return "monster-dataset";
    	}
    
    	write() {;
    
    		return new Promise((resolve, reject) => {
    			if (!this[datasourceLinkedElementSymbol]) {
    				reject(new Error("No datasource"));
    				return;
    			}
    
    			const internalUpdateCloneData = this.getInternalUpdateCloneData();
    			if (!internalUpdateCloneData) {
    				reject(new Error("No update data"));
    				return;
    			}
    
    			const internalData = internalUpdateCloneData?.["data"];
    			if (
    				internalData === undefined ||
    				internalData === null ||
    				internalData === ""
    			) {
    				reject(new Error("No data"));
    				return;
    			}
    
    			setTimeout(() => {
    				const path = this.getOption("mapping.data");
    				const index = this.getOption("mapping.index");
    
    				let pathWithIndex;
    
    				if (isString(path) && path !== "") {
    					pathWithIndex = path + "." + index;
    				} else {
    					pathWithIndex = index;
    				}
    
    				const data = this[datasourceLinkedElementSymbol].data;
    				const unref = JSON.stringify(data);
    				const ref = JSON.parse(unref);
    
    				new Pathfinder(ref).setVia(pathWithIndex, internalData);
    
    				this[datasourceLinkedElementSymbol].data = ref;
    
    				resolve();
    			}, 0);
    		});
    	}
    
    	/**
    	 * This method is responsible for assembling the component.
    	 *
    	 * It calls the parent's assemble method first, then initializes control references and event handlers.
    	 * If the `datasource.selector` option is provided and is a string, it searches for the corresponding
    	 * element in the DOM using that selector.
    	 *
    	 * If the selector matches exactly one element, it checks if the element is an instance of the `Datasource` class.
    	 *
    	 * If it is, the component's `datasourceLinkedElementSymbol` property is set to the element, and the component
    	 * attaches an observer to the datasource's changes.
    	 *
    	 * The observer is a function that calls the `handleDataSourceChanges` method in the context of the component.
    	 * Additionally, the component attaches an observer to itself, which also calls the `handleDataSourceChanges`
    	 * method in the component's context.
    	 */
    	[assembleMethodSymbol]() {
    		super[assembleMethodSymbol]();
    
    		// initControlReferences.call(self);
    		initEventHandler.call(this);
    
    		const selector = this.getOption("datasource.selector");
    
    		if (isString(selector)) {
    			const elements = document.querySelectorAll(selector);
    			if (elements.length !== 1) {
    				throw new Error("the selector must match exactly one element");
    			}
    
    			const element = elements[0];
    			if (!(element instanceof Datasource)) {
    				throw new TypeError("the element must be a datasource");
    			}
    
    			this[datasourceLinkedElementSymbol] = element;
    			element.datasource.attachObserver(
    				new Observer(handleDataSourceChanges.bind(this)),
    			);
    		}
    
    		this.attachObserver(
    			new Observer(() => {
    				handleDataSourceChanges.call(this);
    			}),
    		);
    	}
    
    	/**
    	 *
    	 * @return [CSSStyleSheet]
    	 */
    	static getCSSStyleSheet() {
    		return [FormStyleSheet, DatasetStyleSheet];
    	}
    }
    
    /**
     * @private
     */
    function initEventHandler() {
    	this[attributeObserverSymbol][ATTRIBUTE_DATATABLE_INDEX] = () => {
    		const index = this.getAttribute(ATTRIBUTE_DATATABLE_INDEX);
    		if (index) {
    			this.setOption("mapping.index", parseInt(index, 10));
    		}
    	};
    }
    
    /**
     *
     * @param {Object} options
     */
    function updateOptionsFromArguments(options) {
    	const index = this.getAttribute(ATTRIBUTE_DATATABLE_INDEX);
    
    	if (index !== null && index !== undefined) {
    		options.mapping.index = parseInt(index, 10);
    	}
    
    	const selector = this.getAttribute(ATTRIBUTE_DATASOURCE_SELECTOR);
    
    	if (selector) {
    		options.datasource.selector = selector;
    	}
    }
    
    /**
     * @private
     * @return {string}
     */
    function getTemplate() {
    	// language=HTML
    	return `
            <div data-monster-role="control" part="control">
                <slot></slot>
            </div>
        `;
    }
    
    registerCustomElement(DataSet);