diff --git a/examples/html-editor-drawing-hook/index.js b/examples/html-editor-drawing-hook/index.js index 27b58a10..dca6a6d3 100644 --- a/examples/html-editor-drawing-hook/index.js +++ b/examples/html-editor-drawing-hook/index.js @@ -1,7 +1,20 @@ /* global Y, d3 */ +const hooks = { + "magic-drawing": { + fillType: function (dom, type) { + initDrawingBindings(type, dom) + }, + createDom: function (type) { + const dom = document.createElement('magic-drawing') + initDrawingBindings(type, dom) + return dom + } + } +} + window.onload = function () { - window.yXmlType.bindToDom(document.body) + window.domBinding = new Y.DomBinding(window.yXmlType, document.body, { hooks }) } window.addMagicDrawing = function addMagicDrawing () { @@ -96,17 +109,6 @@ function initDrawingBindings (type, dom) { } } -Y.XmlHook.addHook('magic-drawing', { - fillType: function (dom, type) { - initDrawingBindings(type, dom) - }, - createDom: function (type) { - const dom = document.createElement('magic-drawing') - initDrawingBindings(type, dom) - return dom - } -}) - let y = new Y('html-editor-drawing-hook-example', { connector: { name: 'websockets-client', diff --git a/src/Bindings/DomBinding/DomBinding.js b/src/Bindings/DomBinding/DomBinding.js index a2d10bf1..ae5a2894 100644 --- a/src/Bindings/DomBinding/DomBinding.js +++ b/src/Bindings/DomBinding/DomBinding.js @@ -30,6 +30,9 @@ export default class DomBinding extends Binding { constructor (type, target, opts = {}) { // Binding handles textType as this.type and domTextarea as this.target super(type, target) + this.opts = opts + opts.document = opts.document || document + opts.hooks = opts.hooks || {} /** * Maps each DOM element to the type that it is associated with. * @type {Map} @@ -49,11 +52,11 @@ export default class DomBinding extends Binding { // set initial value target.innerHTML = '' for (let child of type) { - target.insertBefore(child.toDom(this.domToType, this.typeToDom), null) + target.insertBefore(child.toDom(opts.document, opts.hooks, this), null) } this._typeObserver = typeObserver.bind(this) this._domObserver = (mutations) => { - domObserver.call(this, mutations, opts._document) + domObserver.call(this, mutations, opts.document) } type.observeDeep(this._typeObserver) this._mutationObserver = new MutationObserver(this._domObserver) diff --git a/src/Bindings/DomBinding/domToType.js b/src/Bindings/DomBinding/domToType.js index d95f8d28..fdb939e5 100644 --- a/src/Bindings/DomBinding/domToType.js +++ b/src/Bindings/DomBinding/domToType.js @@ -1,5 +1,5 @@ -import { YXmlText, YXmlElement } from '../../Types/YXml/YXml.js' +import { YXmlText, YXmlElement, YXmlHook } from '../../Types/YXml/YXml.js' import { createAssociation } from './util.js' /** @@ -15,14 +15,29 @@ export default function domToType (element, _document = document, binding) { let type switch (element.nodeType) { case _document.ELEMENT_NODE: - type = new YXmlElement(element.nodeName) - const attrs = element.attributes - for (let i = attrs.length - 1; i >= 0; i--) { - const attr = attrs[i] - type.setAttribute(attr.name, attr.value) + let hookName = element.dataset.yjsHook + let hook + if (hookName !== undefined) { + hook = binding.opts.hooks[hookName] + if (hook === undefined) { + console.error(`Unknown hook "${hookName}". Deleting yjsHook dataset property.`) + delete element.dataset.yjsHook + hookName = undefined + } + } + if (hookName === undefined) { + type = new YXmlElement(element.nodeName) + const attrs = element.attributes + for (let i = attrs.length - 1; i >= 0; i--) { + const attr = attrs[i] + type.setAttribute(attr.name, attr.value) + } + const children = Array.from(element.childNodes).map(e => domToType(e, _document, binding)) + type.insert(0, children) + } else { + type = new YXmlHook(hookName) + hook.fillType(element, type) } - const children = Array.from(element.childNodes).map(e => domToType(e, _document, binding)) - type.insert(0, children) break case _document.TEXT_NODE: type = new YXmlText() diff --git a/src/Bindings/DomBinding/typeObserver.js b/src/Bindings/DomBinding/typeObserver.js index e5b4343a..1417cc4e 100644 --- a/src/Bindings/DomBinding/typeObserver.js +++ b/src/Bindings/DomBinding/typeObserver.js @@ -6,7 +6,7 @@ import { removeDomChildrenUntilElementFound } from './util.js' /** * @private */ -export default function typeObserver (events, _document) { +export default function typeObserver (events) { this._mutualExclude(() => { events.forEach(event => { const yxml = event.target @@ -37,11 +37,10 @@ export default function typeObserver (events, _document) { let currentChild = dom.firstChild yxml.forEach(childType => { const childNode = this.typeToDom.get(childType) - const binding = this switch (childNode) { case undefined: // Does not exist. Create it. - const node = childType.toDom(_document, binding) + const node = childType.toDom(this.opts.document, this.opts.hooks, this) dom.insertBefore(node, currentChild) break case false: diff --git a/src/Types/YXml/YXmlElement.js b/src/Types/YXml/YXmlElement.js index 0e5463e5..43f73e13 100644 --- a/src/Types/YXml/YXmlElement.js +++ b/src/Types/YXml/YXmlElement.js @@ -164,6 +164,8 @@ export default class YXmlElement extends YXmlFragment { * @param {Document} [_document=document] The document object (you must define * this when calling this method in * nodejs) + * @param {Object} [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. @@ -171,14 +173,14 @@ export default class YXmlElement extends YXmlFragment { * * @public */ - toDom (_document = document, binding) { + 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, binding)) + dom.appendChild(yxml.toDom(_document, hooks, binding)) }) createAssociation(binding, dom, this) return dom diff --git a/src/Types/YXml/YXmlFragment.js b/src/Types/YXml/YXmlFragment.js index 8bbe932c..827356a0 100644 --- a/src/Types/YXml/YXmlFragment.js +++ b/src/Types/YXml/YXmlFragment.js @@ -136,18 +136,20 @@ export default class YXmlFragment extends YArray { * @param {Document} [_document=document] The document object (you must define * this when calling this method in * nodejs) + * @param {Object} [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. + * 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, binding) { + toDom (_document = document, hooks = {}, binding) { const fragment = _document.createDocumentFragment() createAssociation(binding, fragment, this) this.forEach(xmlType => { - fragment.insertBefore(xmlType.toDom(_document, binding), null) + fragment.insertBefore(xmlType.toDom(_document, hooks, binding), null) }) return fragment } diff --git a/src/Types/YXml/YXmlHook.js b/src/Types/YXml/YXmlHook.js index d840ab11..ec855f8e 100644 --- a/src/Types/YXml/YXmlHook.js +++ b/src/Types/YXml/YXmlHook.js @@ -1,5 +1,4 @@ import YMap from '../YMap/YMap.js' -import { getHook, addHook } from './hooks.js' /** * You can manage binding to a custom type with YXmlHook. @@ -35,16 +34,24 @@ export default class YXmlHook extends YMap { * @param {Document} [_document=document] The document object (you must define * this when calling this method in * nodejs) + * @param {Object} [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. + * 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) { - const dom = getHook(this.hookName).createDom(this) - dom._yjsHook = this.hookName + toDom (_document = document, hooks = {}, binding) { + const hook = hooks[this.hookName] + let dom + if (hook !== undefined) { + dom = hook.createDom(this) + } else { + dom = document.createElement(this.hookName) + } + dom.dataset.yjsHook = this.hookName return dom } @@ -97,4 +104,3 @@ export default class YXmlHook extends YMap { super._integrate(y) } } -YXmlHook.addHook = addHook diff --git a/src/Types/YXml/YXmlText.js b/src/Types/YXml/YXmlText.js index 81c9ee69..c0339806 100644 --- a/src/Types/YXml/YXmlText.js +++ b/src/Types/YXml/YXmlText.js @@ -14,6 +14,8 @@ export default class YXmlText extends YText { * @param {Document} [_document=document] The document object (you must define * this when calling this method in * nodejs) + * @param {Object} [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. @@ -21,7 +23,7 @@ export default class YXmlText extends YText { * * @public */ - toDom (_document = document, binding) { + toDom (_document = document, hooks, binding) { const dom = _document.createTextNode(this.toString()) createAssociation(binding, dom, this) return dom diff --git a/src/Types/YXml/hooks.js b/src/Types/YXml/hooks.js deleted file mode 100644 index 18e5de5c..00000000 --- a/src/Types/YXml/hooks.js +++ /dev/null @@ -1,14 +0,0 @@ - -const xmlHooks = {} - -export function addHook (name, hook) { - xmlHooks[name] = hook -} - -export function getHook (name) { - const hook = xmlHooks[name] - if (hook === undefined) { - throw new Error(`The hook "${name}" is not specified! You must not access this hook!`) - } - return hook -}