bbdocument/bbelement.js

/** @module bbdocument/bbelement */

import * as Parser from "../parser.js"
import BBNode from "./bbnode.js"
import TreeWalker from "./treewalker.js"

/**
 * @memberof module:bbdocument/bbelement
 */
class BBElement extends BBNode {

	/**
	 * @param  {string}   name
	 * @param  {string} 	[value=null]
	 */
	constructor(name, value = null) {
		super()
		this._nodeType = BBNode.ELEMENT_BBNODE
		this._childNodes = []
		this._tagName = name
		this._keys = new Map([ [name, value] ])
		this._innerBBCode = ""
		const tags = this.tags()
		this._outerBBCode = `${tags.opening}${tags.closing}`
	}

	/**
	 * @readonly
	 * @type {string}
	 */
	get tagName() {
		return this._tagName
	}

	/**
	 * @readonly
	 * @type {BBNode[]}
	 */
	get childNodes() {
		return this._childNodes
	}

	/**
	 * @readonly
	 * @type {Map}
	 */
	get keys() {
		return this._keys
	}

	/**
	 * @type {string}
	 */
	get innerBBCode() {
		return this._innerBBCode
	}

	set innerBBCode(innerBBCode) {
		for(let i = 0; i < this.childNodes.length; i++) {
			this.removeChild(this.childNodes[i])
			i--
		}
		const childNodes = Parser.parse(innerBBCode).documentElement.childNodes
		for(const node of childNodes) {
			this.appendChild(node)
		}
		this._innerBBCode = innerBBCode
	}

	/**
	 * @readonly
	 * @type {string}
	 */
	get outerBBCode() {
		return this._outerBBCode
	}

	/**
	 * @ignore
	 * @param {string}        name
	 * @param {string|number} value
	 */
	setKey(name, value = null) {
		this._keys.set(name, value)
		const tags = this.tags()
		this._outerBBCode = `${tags.opening}${tags.closing}`
	}

	/**
	 * @param  {BBNode} bbNode
	 */
	appendChild(bbNode) {
		if(this.childNodes.length >= 1) {
			const lastBBNode = this.childNodes[this.childNodes.length - 1]
			lastBBNode._nextSibling = bbNode
			bbNode._previousSibling = lastBBNode
		}
		bbNode._parentNode = this
		this.childNodes.push(bbNode)
		this.build()
	}

	/**
	 * @param  {BBNode} bbNode
	 */
	removeChild(bbNode) {
		if(bbNode.previousSibling !== null) {
			bbNode.previousSibling._nextSibling = bbNode.nextSibling
		}
		if(bbNode.nextSibling !== null) {
			bbNode.nextSibling._previousSibling = bbNode.previousSibling
		}
		this.childNodes.splice(this.childNodes.indexOf(bbNode), 1)
		this.build()
	}

	/**
	 * @ignore
	 * Used to rebuild the Node hierarchy after either textContent or innerBBCode was updated
	 */
	build() {
		const nodeList = [this]
		let parentNode = this.parentNode
		while (parentNode !== null) {
			nodeList.push(parentNode)
			parentNode = parentNode.parentNode
		}
		for(const node of nodeList) {
			node._textContent = Parser.serialize(node, { excludeBBCode: true })
			node._innerBBCode = Parser.serialize(node, { excludeBBCode: false })
			node._outerBBCode = Parser.serialize(node, { excludeRoot: false })
		}
	}

	/**
	 * Converts an array of keys into an opening and closing tag string
	 * @param  {Object[]} keys
	 * @returns {object}
	 */
	tags() {
		const tags = { opening: "", closing: "" }
		tags.opening += "["
		for(const [key, value] of this.keys) {
			if (tags.opening.length >= 2) {
				tags.opening += " "
			}
			tags.opening += key
			if (value !== null) {
				if (value.includes(" ") === true) {
					tags.opening += "=" + "\"" + value + "\""
				} else {
					tags.opening += "=" + value
				}
			}
		}
		tags.opening += "]"
		if(this.tagName !== "*") {
			tags.closing = "[/" + this.tagName + "]"
		}
		return tags
	}

	/**
	 * @param   {string} tagName
	 * @returns {BBNode}
	 */
	querySelector(tagName) {
		const treeWalker = new TreeWalker(this)
		let node = null
		while(treeWalker.nextNode()) {
			if(treeWalker.currentNode.tagName === tagName) {
				node = treeWalker.currentNode
				break
			}
		}
		return node
	}

	/**
	 * @param   {string} tagName
	 * @returns {BBNode[]}
	 */
	querySelectorAll(tagName) {
		const treeWalker = new TreeWalker(this)
		let nodeList = []
		while(treeWalker.nextNode()) {
			if(treeWalker.currentNode.tagName === tagName) {
				nodeList.push(treeWalker.currentNode)
			}
		}
		return nodeList
	}

}

export default BBElement