src/Types/YXml/YXmlElement.js
import YMap from '../YMap/YMap.js'
import { YXmlFragment } from './YXml.js'
import { createAssociation } from '../../Bindings/DomBinding/util.js'
/**
* An YXmlElement imitates the behavior of a
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}.
*
* * An YXmlElement has attributes (key value pairs)
* * An YXmlElement has childElements that must inherit from YXmlElement
*
* @param {String} nodeName Node name
*/
export default class YXmlElement extends YXmlFragment {
constructor (nodeName = 'UNDEFINED') {
super()
this.nodeName = nodeName.toUpperCase()
}
/**
* @private
* Creates an Item with the same effect as this Item (without position effect)
*/
_copy () {
let struct = super._copy()
struct.nodeName = this.nodeName
return struct
}
/**
* @private
* Read the next Item in a Decoder and fill this Item with the read data.
*
* This is called when data is received from a remote peer.
*
* @param {Y} y The Yjs instance that this Item belongs to.
* @param {BinaryDecoder} decoder The decoder object to read data from.
*/
_fromBinary (y, decoder) {
const missing = super._fromBinary(y, decoder)
this.nodeName = decoder.readVarString()
return missing
}
/**
* Transform the properties of this type to binary and write it to an
* BinaryEncoder.
*
* This is called when this Item is sent to a remote peer.
*
* @param {BinaryEncoder} encoder The encoder to write data to.
*
* @private
*/
_toBinary (encoder) {
super._toBinary(encoder)
encoder.writeVarString(this.nodeName)
}
/**
* Integrates this Item into the shared structure.
*
* This method actually applies the change to the Yjs instance. In case of
* Item it connects _left and _right to this Item and calls the
* {@link Item#beforeChange} method.
*
* * Checks for nodeName
* * Sets domFilter
*
* @param {Y} y The Yjs instance
*
* @private
*/
_integrate (y) {
if (this.nodeName === null) {
throw new Error('nodeName must be defined!')
}
super._integrate(y)
}
/**
* Returns the string representation of this YXmlElement.
* The attributes are ordered by attribute-name, so you can easily use this
* method to compare YXmlElements
*
* @return {String} The string representation of this type.
*
* @public
*/
toString () {
const attrs = this.getAttributes()
const stringBuilder = []
const keys = []
for (let key in attrs) {
keys.push(key)
}
keys.sort()
const keysLen = keys.length
for (let i = 0; i < keysLen; i++) {
const key = keys[i]
stringBuilder.push(key + '="' + attrs[key] + '"')
}
const nodeName = this.nodeName.toLocaleLowerCase()
const attrsString = stringBuilder.length > 0 ? ' ' + stringBuilder.join(' ') : ''
return `<${nodeName}${attrsString}>${super.toString()}</${nodeName}>`
}
/**
* Removes an attribute from this YXmlElement.
*
* @param {String} attributeName The attribute name that is to be removed.
*
* @public
*/
removeAttribute (attributeName) {
return YMap.prototype.delete.call(this, attributeName)
}
/**
* Sets or updates an attribute.
*
* @param {String} attributeName The attribute name that is to be set.
* @param {String} attributeValue The attribute value that is to be set.
*
* @public
*/
setAttribute (attributeName, attributeValue) {
return YMap.prototype.set.call(this, attributeName, attributeValue)
}
/**
* Returns an attribute value that belongs to the attribute name.
*
* @param {String} attributeName The attribute name that identifies the
* queried value.
* @return {String} The queried attribute value.
*
* @public
*/
getAttribute (attributeName) {
return YMap.prototype.get.call(this, attributeName)
}
/**
* Returns all attribute name/value pairs in a JSON Object.
*
* @return {Object} A JSON Object that describes the attributes.
*
* @public
*/
getAttributes () {
const obj = {}
for (let [key, value] of this._map) {
if (!value._deleted) {
obj[key] = value._content[0]
}
}
return obj
}
// TODO: outsource the binding property.
/**
* Creates a Dom Element that mirrors this YXmlElement.
*
* @param {Document} [_document=document] The document object (you must define
* this when calling this method in
* nodejs)
* @param {Object<key:hookDefinition>} [hooks={}] Optional property to customize how hooks
* are presented in the DOM
* @param {DomBinding} [binding] You should not set this property. This is
* used if DomBinding wants to create a
* association to the created DOM type.
* @return {Element} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}
*
* @public
*/
toDom (_document = document, hooks = {}, binding) {
const dom = _document.createElement(this.nodeName)
let attrs = this.getAttributes()
for (let key in attrs) {
dom.setAttribute(key, attrs[key])
}
this.forEach(yxml => {
dom.appendChild(yxml.toDom(_document, hooks, binding))
})
createAssociation(binding, dom, this)
return dom
}
}