diff --git a/src/Type/y-xml/YXmlElement.js b/src/Type/y-xml/YXmlElement.js index dda31827..3af417be 100644 --- a/src/Type/y-xml/YXmlElement.js +++ b/src/Type/y-xml/YXmlElement.js @@ -5,7 +5,7 @@ import YMap from '../YMap.js' import YXmlFragment from './YXmlFragment.js' export default class YXmlElement extends YXmlFragment { - constructor (arg1, arg2) { + constructor (arg1, arg2, _document) { super() this.nodeName = null this._scrollElement = null @@ -13,7 +13,7 @@ export default class YXmlElement extends YXmlFragment { this.nodeName = arg1.toUpperCase() } else if (arg1 != null && arg1.nodeType != null && arg1.nodeType === arg1.ELEMENT_NODE) { this.nodeName = arg1.nodeName - this._setDom(arg1) + this._setDom(arg1, _document) } else { this.nodeName = 'UNDEFINED' } @@ -26,14 +26,12 @@ export default class YXmlElement extends YXmlFragment { struct.nodeName = this.nodeName return struct } - _setDom (dom) { + _setDom (dom, _document) { if (this._dom != null) { throw new Error('Only call this method if you know what you are doing ;)') } else if (dom._yxml != null) { // TODO do i need to check this? - no.. but for dev purps.. throw new Error('Already bound to an YXml type') } else { - this._dom = dom - dom._yxml = this // tag is already set in constructor // set attributes let attrNames = [] @@ -46,8 +44,8 @@ export default class YXmlElement extends YXmlFragment { let attrValue = dom.getAttribute(attrName) this.setAttribute(attrName, attrValue) } - this.insertDomElements(0, Array.prototype.slice.call(dom.childNodes)) - this._bindToDom(dom) + this.insertDomElements(0, Array.prototype.slice.call(dom.childNodes), _document) + this._bindToDom(dom, _document) return dom } } @@ -115,7 +113,6 @@ export default class YXmlElement extends YXmlFragment { let dom = this._dom if (dom == null) { dom = _document.createElement(this.nodeName) - this._dom = dom dom._yxml = this let attrs = this.getAttributes() for (let key in attrs) { @@ -124,7 +121,7 @@ export default class YXmlElement extends YXmlFragment { this.forEach(yxml => { dom.appendChild(yxml.getDom(_document)) }) - this._bindToDom(dom) + this._bindToDom(dom, _document) } return dom } diff --git a/src/Type/y-xml/YXmlFragment.js b/src/Type/y-xml/YXmlFragment.js index d514e9c5..5362d0a1 100644 --- a/src/Type/y-xml/YXmlFragment.js +++ b/src/Type/y-xml/YXmlFragment.js @@ -9,7 +9,7 @@ import YXmlEvent from './YXmlEvent.js' import { logID } from '../../MessageHandler/messageToString.js' import diff from 'fast-diff' -function domToYXml (parent, doms) { +function domToYXml (parent, doms, _document) { const types = [] doms.forEach(d => { if (d._yxml != null && d._yxml !== false) { @@ -20,7 +20,7 @@ function domToYXml (parent, doms) { if (d.nodeType === d.TEXT_NODE) { type = new YXmlText(d) } else if (d.nodeType === d.ELEMENT_NODE) { - type = new YXmlFragment._YXmlElement(d, parent._domFilter) + type = new YXmlFragment._YXmlElement(d, parent._domFilter, _document) } else { throw new Error('Unsupported node!') } @@ -98,7 +98,9 @@ export default class YXmlFragment extends YArray { } catch (e) { console.error(e) } - this._domObserver.takeRecords() + if (this._domObserver !== null) { + this._domObserver.takeRecords() + } token = true } } @@ -162,113 +164,116 @@ export default class YXmlFragment extends YArray { this._dom = null } } - insertDomElementsAfter (prev, doms) { - const types = domToYXml(this, doms) + insertDomElementsAfter (prev, doms, _document) { + const types = domToYXml(this, doms, _document) this.insertAfter(prev, types) return types } - insertDomElements (pos, doms) { - const types = domToYXml(this, doms) + insertDomElements (pos, doms, _document) { + const types = domToYXml(this, doms, _document) this.insert(pos, types) return types } getDom () { return this._dom } - bindToDom (dom) { + bindToDom (dom, _document) { if (this._dom != null) { this._unbindFromDom() } if (dom._yxml != null) { dom._yxml._unbindFromDom() } - if (MutationObserver == null) { - throw new Error('Not able to bind to a DOM element, because MutationObserver is not available!') - } dom.innerHTML = '' - this._dom = dom - dom._yxml = this this.forEach(t => { - dom.insertBefore(t.getDom(), null) + dom.insertBefore(t.getDom(_document), null) }) - this._bindToDom(dom) + this._bindToDom(dom, _document) } // binds to a dom element // Only call if dom and YXml are isomorph - _bindToDom (dom) { - if (this._parent === null || this._parent._dom != null || typeof MutationObserver === 'undefined') { - // only bind if parent did not already bind + _bindToDom (dom, _document) { + _document = _document || document + this._dom = dom + dom._yxml = this + // TODO: refine this.. + if ((this.constructor !== YXmlFragment && this._parent !== this._y) || this._parent === null) { + // TODO: only top level YXmlFragment can bind. Also allow YXmlElements.. return } - this._y.on('beforeTransaction', () => { - this._domObserverListener(this._domObserver.takeRecords()) - }) this._y.on('beforeTransaction', beforeTransactionSelectionFixer) this._y.on('afterTransaction', afterTransactionSelectionFixer) // Apply Y.Xml events to dom - this.observeDeep(reflectChangesOnDom.bind(this)) + this.observeDeep(events => { + reflectChangesOnDom.call(this, events, _document) + }) // Apply Dom changes on Y.Xml - this._domObserverListener = mutations => { - this._mutualExclude(() => { - this._y.transact(() => { - let diffChildren = new Set() - mutations.forEach(mutation => { - const dom = mutation.target - const yxml = dom._yxml - if (yxml == null) { - // dom element is filtered - return - } - switch (mutation.type) { - case 'characterData': - var diffs = diff(yxml.toString(), dom.nodeValue) - var pos = 0 - for (var i = 0; i < diffs.length; i++) { - var d = diffs[i] - if (d[0] === 0) { // EQUAL - pos += d[1].length - } else if (d[0] === -1) { // DELETE - yxml.delete(pos, d[1].length) - } else { // INSERT - yxml.insert(pos, d[1]) - pos += d[1].length - } - } - break - case 'attributes': - let name = mutation.attributeName - // check if filter accepts attribute - if (this._domFilter(dom, [name]).length > 0 && this.constructor !== YXmlFragment) { - var val = dom.getAttribute(name) - if (yxml.getAttribute(name) !== val) { - if (val == null) { - yxml.removeAttribute(name) - } else { - yxml.setAttribute(name, val) + if (typeof MutationObserver !== 'undefined') { + this._y.on('beforeTransaction', () => { + this._domObserverListener(this._domObserver.takeRecords()) + }) + this._domObserverListener = mutations => { + this._mutualExclude(() => { + this._y.transact(() => { + let diffChildren = new Set() + mutations.forEach(mutation => { + const dom = mutation.target + const yxml = dom._yxml + if (yxml == null) { + // dom element is filtered + return + } + switch (mutation.type) { + case 'characterData': + var diffs = diff(yxml.toString(), dom.nodeValue) + var pos = 0 + for (var i = 0; i < diffs.length; i++) { + var d = diffs[i] + if (d[0] === 0) { // EQUAL + pos += d[1].length + } else if (d[0] === -1) { // DELETE + yxml.delete(pos, d[1].length) + } else { // INSERT + yxml.insert(pos, d[1]) + pos += d[1].length } } - } - break - case 'childList': - diffChildren.add(mutation.target) - break + break + case 'attributes': + let name = mutation.attributeName + // check if filter accepts attribute + if (this._domFilter(dom, [name]).length > 0 && this.constructor !== YXmlFragment) { + var val = dom.getAttribute(name) + if (yxml.getAttribute(name) !== val) { + if (val == null) { + yxml.removeAttribute(name) + } else { + yxml.setAttribute(name, val) + } + } + } + break + case 'childList': + diffChildren.add(mutation.target) + break + } + }) + for (let dom of diffChildren) { + if (dom._yxml != null && dom._yxml !== false) { + applyChangesFromDom(dom) + } } }) - for (let dom of diffChildren) { - if (dom._yxml != null && dom._yxml !== false) { - applyChangesFromDom(dom) - } - } }) + } + this._domObserver = new MutationObserver(this._domObserverListener) + this._domObserver.observe(dom, { + childList: true, + attributes: true, + characterData: true, + subtree: true }) } - this._domObserver = new MutationObserver(this._domObserverListener) - this._domObserver.observe(dom, { - childList: true, - attributes: true, - characterData: true, - subtree: true - }) return dom } _logString () { diff --git a/src/Type/y-xml/utils.js b/src/Type/y-xml/utils.js index a362b75e..94df6c3e 100644 --- a/src/Type/y-xml/utils.js +++ b/src/Type/y-xml/utils.js @@ -135,7 +135,7 @@ export function applyChangesFromDom (dom) { } } -export function reflectChangesOnDom (events) { +export function reflectChangesOnDom (events, _document) { // Make sure that no filtered attributes are applied to the structure // if they were, delete them /* @@ -183,9 +183,9 @@ export function reflectChangesOnDom (events) { }) if (event.childListChanged) { // create fragment of undeleted nodes - const fragment = document.createDocumentFragment() + const fragment = _document.createDocumentFragment() yxml.forEach(function (t) { - fragment.append(t.getDom()) + fragment.appendChild(t.getDom(_document)) }) // remove remainding nodes let lastChild = dom.lastChild @@ -194,7 +194,7 @@ export function reflectChangesOnDom (events) { lastChild = dom.lastChild } // insert fragment of undeleted nodes - dom.append(fragment) + dom.appendChild(fragment) } } /* TODO: smartscrolling diff --git a/tests-lib/helper.js b/tests-lib/helper.js index ec7284cb..03c7722b 100644 --- a/tests-lib/helper.js +++ b/tests-lib/helper.js @@ -153,7 +153,7 @@ export async function initArrays (t, opts) { result['array' + i] = y.define('array', Y.Array) result['map' + i] = y.define('map', Y.Map) result['xml' + i] = y.define('xml', Y.XmlElement) - y.get('xml', Y.Xml).setDomFilter(function (d, attrs) { + y.get('xml').setDomFilter(function (d, attrs) { if (d.nodeName === 'HIDDEN') { return null } else {