import View from './view.js'
/**
* Class representing a abstract view.
*/
export class Component {
/**
* Create an abstract view component.
* @param {object} parent - The parent component object.
* @param {string} id - The id should be a unique string idetifier.
* @param {object} setupData - Deprecated paramenter will be removed in future.
*/
constructor(parent, id, setupData) {
this.e = null // The DOM element representing this component on the HTML page.
this.id = id // An identifier, a string which gives access to the component.
this.group = null // A group identifier, a string which can be used to find elements by group.
this.tag = null // A tag, for custom use.
this.parent = parent // The parent component, can be null if this is a root component.
this.childs = [] // Array with the childs of this component.
this.classList = null // Comma separated list of DOM element class names.
this.events = null // Events, that should be handled by this component.
this.eventMode = 'default' // The mode, in which event handlers should be created.
this.routeName = null // Name for routing.
// Add component to parent.
if (parent) {
parent.childs.push(this)
}
}
/**
* Build the DOM element(s) of a customized component.
* This method has to be overriden by derived classes.
*/
build() {
}
/**
* Update component after changes to it.
* This method has to be overriden by derived classes.
*
* This method will be called, when properties has been changed.
* The component has to reflect the changes in its visual representation.
*/
propertiesChanged() {
}
/**
* Build the DOM element(s).
*/
buildDOM() {
// Call the component dependent build function.
this.build()
// Add event handlers, only in 'default' mode.
if (this.eventMode === 'default' && this.events !== null) {
this.events.forEach((event) => {
if (typeof event.handler === 'function') {
this.e.addEventListener(event.type, () => event.handler(this))
}
else if (typeof event.handler === 'object') {
this.e.addEventListener(event.type, () => event.handler.handleEvent(this))
}
})
}
// Build child components.
this.buildChilds()
}
/**
* Build all descendant components.
*/
buildChilds() {
this.childs.forEach((child) => {
child.buildDOM()
})
}
/**
* Get property names.
*
* @param {array} customPropertyNames - An array with the names of a derived class.
* @return {array} An array with property names or undefined, if no properties exist.
*/
getPropertyNames(customPropertyNames) {
const propertyNames = ['id', 'group', 'tag', 'classList', 'events', 'eventMode', 'routeName']
if (customPropertyNames !== undefined) {
return propertyNames.concat(customPropertyNames)
}
else {
return propertyNames
}
}
/**
* Set component properties by data.
*
* @param {array} data - An array with data in the form of name/value pairs.
*/
setProperties(data) {
let changed = false
if (data !== undefined) {
const propertyNames = this.getPropertyNames()
if (propertyNames !== undefined) {
for (const propertyName of this.getPropertyNames()) {
if (data[propertyName] !== undefined) {
this[propertyName] = data[propertyName]
changed = true
}
}
}
}
if (changed === true) {
this.propertiesChanged()
}
}
/**
* Get component by id.
*
* @param {string} id - The id of the component to find.
* @return {Component} The found component or null, if no component has the given id.
*/
getComponentById(id) {
if (id === this.id) {
return this
}
for (const child of this.childs) {
const result = child.getComponentById(id)
if (result !== null) {
return result
}
}
return null
}
/**
* Count all descendant components.
* Descendants are children, grandchildren, and so on.
*
* @return {number} Number of descendants.
*/
countDescendants() {
let n = 0
for (const child of this.childs) {
n += child.countDescendants() + 1
}
return n
}
/**
* Handles a message send to the component.
*
* @param {string} message - The message to be handled.
*/
handleMessage(message) {
}
/**
* Create a DOM element with an optional CSS class.
*
* @param {string} tag - The HTML tag to be created.
* @return {object} The DOM element.
*/
createDomElement(tag) {
return document.createElement(tag)
}
/**
* Add a DOM element with an optional CSS class.
*
* @param {string} tag - The HTML tag to be created.
* @return {object} The DOM element.
*/
addDomElement(tag) {
this.e = this.createDomElement(tag)
this.addElementClasslist(this.e, this.classList)
this.parent.e.appendChild(this.e)
return this.e
}
/**
* Retrieve an element from the DOM.
*
* @param {string} selector - DOM selector to the element.
* @return {HTMLElement} The DOM element or null.
*/
getDomElement(selector) {
return document.querySelector(selector)
}
/**
* Helper method for adding classes to an DOM element.
*
* @param {string} classList - One ore more class names separated with spaces.
*/
addElementClasslist(e, classList) {
if (classList !== undefined && classList !== null) {
const classNames = classList.split(' ')
classNames.forEach((className) => {
className = className.trim()
// TODO: Use try catch
if (className !== '') {
if (!e.classList.contains(className)) {
e.classList.add(className)
}
}
})
}
}
}
Source