UNPKG

@ulu/frontend

Version:

A versatile SCSS and JavaScript component library offering configurable, accessible components and flexible integration into any project, with SCSS modules suitable for modern JS frameworks.

155 lines (150 loc) 6.27 kB
/** * @module utils/system * @description Core classes and mechanisms that define how UI components are created and managed within the library */ import { hasRequiredProps } from "@ulu/utils/object.js"; import { getDatasetOptionalJson } from "@ulu/utils/browser/dom.js"; import { dataAttributeToDatasetKey } from "./dom.js"; import { getName } from "../events/index.js"; /** * Class serves as a utility for UI modules, handling the selection of elements and the initialization of corresponding component instances, ensuring consistent setup within the module */ export class ComponentInitializer { static defaults = { type: null, baseAttribute: null }; static requiredOptions = [ "type", "baseAttribute" ]; static hasRequiredOptions = hasRequiredProps( ComponentInitializer.requiredOptions ); /** * Create a new instance of ComponentInitializer * @param {Object} options Options for configuring the component initializer. * @param {String} options.type Type of component (used for logs). * @param {String} options.baseAttribute Prefix and base attribute name (used for base attribute and further element attribute names). */ constructor(options) { if (!ComponentInitializer.hasRequiredOptions(options)) { throw new Error( `Missing a required options: ${ ComponentInitializer.requiredOptions.join(", ") }` ); } this.options = Object.assign({}, ComponentInitializer.defaults, options); this.logTitle = `ULU: ${ this.options.type }:\n`; } /** * Initializes the component based on the provided configuration. * @param {Object} config The initialization configuration. * @param {Function} config.setup The setup function to call for each element. * @param {String} config.key [null] The optional key to use with attribute selector. * @param {Boolean} config.withData [null] Whether to retrieve element data. * @param {Array} config.events [null] Ulu events that should call setup when dispatched (ie. pageModified, pageResized) * @param {Boolean} config.onPageResized [null] Whether to bind event listener for page resize end * @param {HTMLElement} config.context [document] The context to query within. */ init(config) { this.setupElements(config); // Attach Handler to reinitialize if page is updated // Specifically checking entire document incase element that dispatched // event made alterations outside of itself if (config.events?.length) { config.events.forEach(name => { document.addEventListener(getName(name), () => this.setupElements(config)); }); } } /** * Processes the elements based on the provided configuration. * @param {object} config The initialization configuration. * @param {function} config.setup The setup function to call for each element. * @param {string} config.key The optional key to use with attribute selector. * @param {boolean} config.withData [false] Whether to retrieve element data. * @param {boolean} config.onPageModified [true] Whether to bind event listener for page modifications. * @param {HTMLElement} config.context [document] The context to query within. */ setupElements(config) { const { setup, key, withData, context } = config; const elementsWithData = this.queryAllInitial(key, withData, context); elementsWithData.forEach(elementWithData => setup(elementWithData, this)); } /** * Get an attribute name * @param {String} key Optional key, if no key will return baseAttribute if key will return key added to base * @returns {String} String like data-ulu-dialog or data-ulu-dialog-element */ getAttribute(key) { const { baseAttribute } = this.options; return key ? `${ baseAttribute }-${ key }` : `${ baseAttribute }`; } /** * Create an attribute selector * @param {String} key Optional key (see getAttribute) */ attributeSelector(key) { return `[${ this.getAttribute(key) }]`; } /** * Create an attribute selector for initial * @return {String} */ attributeSelectorInitial(key) { return `${ this.attributeSelector(key) }:not([${ this.getAttribute("init") }])` } /** * Queries all main elements of a component that have not been initialized and extracts their data attributes. * @param {HTMLElement} context The context to query within. * @param {Boolean} withData Include dataset from element (as optional JSON) * @param {Node} context Element to query elements from * @returns {Array<{element: HTMLElement, data: object, initialize: Function}>} An array of objects containing the elements, their data, and convenience function initialize() which when called will set the init attribute on the element */ queryAllInitial(key, withData, context = document) { const elements = [ ...context.querySelectorAll(this.attributeSelectorInitial(key)) ]; return elements.map((element) => ({ element, data: withData ? this.getData(element, key) : null, initialize: () => this.initializeElement(element) })); } /** * Sets the init attribute on an element, marking it as initialized. * @param {HTMLElement} element The element to initialize. */ initializeElement(element) { element.setAttribute(this.getAttribute("init"), ""); } /** * Get an elements dataset value as JSON or other value * @return {*} The value of the dataset, if JSON will return object else will return string value or undefined */ getData(element, key) { const datasetKey = dataAttributeToDatasetKey(this.getAttribute(key)); return getDatasetOptionalJson(element, datasetKey); } /** * Will output namespaced console logs for the given initializer */ log(...msgs) { console.log(this.logTitle, ...msgs); } /** * Will output namespaced console warnings for the given initializer */ warn(...msgs) { console.warn(this.logTitle, ...msgs); } /** * Will output namespaced console error for the given initializer */ logError(...msgs) { console.error(this.logTitle, ...msgs); } } /** * Class serves as a base for representing individual occurrences of a UI component, providing a consistent structure for each */ export class ComponentInstance { }