import camelize from "../utils/camelize.js";
import normalizeData from "../utils/normalizeData.js";
import { dispatch, getAttribute, setAttribute } from "../utils/shortcuts.js";
/** @typedef {import('../data-grid').Options} Options */
/**
* Base element that does not contain any specific logic
* related to this project but makes HTMLElemnt usable
*/
class BaseElement extends HTMLElement {
/**
* @param {Object} options
*/
constructor(options = {}) {
super();
/** @type {Options} */
this.options = Object.assign({}, this.defaultOptions, this.normalizedDataset, options);
this.log("constructor");
this.fireEvents = true;
this._ready();
this.log("ready");
}
get defaultOptions() {
return {};
}
/**
* @param {String} opt
* @returns {any}
*/
getOption(opt) {
return this.options[opt];
}
/**
* @param {String} opt
* @param {any} v
*/
setOption(opt, v) {
setAttribute(this, `data-${opt}`, v);
}
/**
* @param {String} opt
*/
toggleOption(opt) {
setAttribute(this, `data-${opt}`, !this.getOption(opt));
}
get normalizedDataset() {
const jsonConfig = this.dataset.config ? JSON.parse(this.dataset.config) : {};
const data = { ...this.dataset };
for (const key in data) {
if (key === "config") {
continue;
}
data[key] = normalizeData(data[key]);
}
// Once normalized, merge into json config
Object.assign(data, jsonConfig);
return data;
}
/**
* @returns {String}
*/
static template() {
return "";
}
/**
* This is called at the end of constructor. Extend in subclass if needed.
*/
_ready() {}
/**
* @param {String|Error} message
*/
log(message) {
if (this.options.debug) {
console.log(`[${getAttribute(this, "id")}] ${message}`);
}
}
/**
* Handle events within the component
* @link https://gist.github.com/WebReflection/ec9f6687842aa385477c4afca625bbf4#handling-events
* @param {Event} event
*/
handleEvent(event) {
if (this[`on${event.type}`]) {
this[`on${event.type}`](event);
}
}
/**
* This is called when connected. Extend in subclass if needed.
*/
_connected() {}
connectedCallback() {
// ensure whenDefined callbacks run first
setTimeout(() => {
this.log("connectedCallback");
// Append only when labels had the opportunity to be set
// Don't use shadow dom as it makes theming super hard
const template = document.createElement("template");
// @ts-ignore
template.innerHTML = this.constructor.template();
this.appendChild(template.content.cloneNode(true));
this._connected();
// @link https://gist.github.com/WebReflection/ec9f6687842aa385477c4afca625bbf4#life-cycle-events
dispatch(this, "connected");
}, 0);
}
/**
* This is called when disconnected. Extend in subclass if needed.
*/
_disconnected() {}
disconnectedCallback() {
this.log("disconnectedCallback");
this._disconnected();
// @link https://gist.github.com/WebReflection/ec9f6687842aa385477c4afca625bbf4#life-cycle-events
dispatch(this, "disconnected");
}
/**
* @link https://gist.github.com/WebReflection/ec9f6687842aa385477c4afca625bbf4#a-props-like-accessor
* @returns {Object}
*/
get transformAttributes() {
return {};
}
/**
* This is only meant to work with data attributes
* This allows us to have properties that reflect automatically in the component
* @link https://gist.github.com/WebReflection/ec9f6687842aa385477c4afca625bbf4#reflected-dataset-attributes
* @param {String} attributeName
* @param {String} oldValue
* @param {String} newValue
*/
attributeChangedCallback(attributeName, oldValue, newValue) {
// It didn't change
if (oldValue === newValue) {
return;
}
this.log(`attributeChangedCallback: ${attributeName}`);
let isOption = false;
const transformer = this.transformAttributes[attributeName] ?? normalizeData;
let attr = attributeName;
// Data attributes are mapped to options while other attributes are mapped as properties
if (attr.indexOf("data-") === 0) {
attr = attr.slice(5);
isOption = true;
}
attr = camelize(attr);
if (isOption) {
this.options[attr] = transformer(newValue);
} else {
this[attr] = transformer(newValue);
}
// Fire internal event
if (this.fireEvents && this[`${attr}Changed`]) {
this[`${attr}Changed`]();
}
}
}
export default BaseElement;