diff --git a/packages/provider/src/adapter-lit-element.js b/packages/provider/src/adapter-lit-element.js index bcdf0929d479be253bd3c937117df3869dcc1fe6..c9112f0e4b449132fa0ce10257f8226980bb5ef8 100644 --- a/packages/provider/src/adapter-lit-element.js +++ b/packages/provider/src/adapter-lit-element.js @@ -13,9 +13,53 @@ export class AdapterLitElement extends LitElement { this.subscribe = ''; this.unsubscribe = ''; + this.callbackStore = []; + + // Previously we used direct properties like this["lang"] (instead of this.propertyStore["lang"]) for storing the + // properties, but the "lang" property seems to be updated before the event from the MutationObserver, so we + // cannot observe a value change directly (as workaround we use another property (e.g. "langValue") instead of "lang") + this.propertyStore = {}; + + // We need to store our own "last values" because we cannot be sure what the MutationObserver detects + this.lastProperties = {}; + console.log('AdapterLitElement(' + this.tagName + ') constructor()'); } + getProperty(name) { + return this.propertyStore[name]; + } + + getPropertyByAttributeName(name) { + return this[this.findPropertyName(name)]; + } + + setPropertyByAttributeName(name, value) { + this[this.findPropertyName(name)] = value; + } + + setProperty(name, value) { + // TODO: check if lit attribute really present? + if (typeof value === 'object' && value !== null) { + // console.log("value is object", value); + this.setPropertyByAttributeName(name, value); + } else { + this.attributeChangedCallback(name, this.getPropertyByAttributeName(name), value); + } + + this.lastProperties[name] = value; + this.propertyStore[name] = value; + } + + hasPropertyChanged(name, value) { + return this.lastProperties[name] !== value; + } + + hasProperty(name) { + // return this.hasAttribute("name") + return Object.hasOwnProperty.call(this.propertyStore, name); + } + connectedCallback() { super.connectedCallback(); @@ -33,6 +77,99 @@ export class AdapterLitElement extends LitElement { } this.connected = true; + + const that = this; + + this.addEventListener('subscribe', function (e) { + const name = e.detail.name; + if (that.hasProperty(name) || that.root) { + console.log('AdapterLitElementProvider(' + that.tagName + ') eventListener("subscribe",..) name "' + name + '" found.'); + that.callbackStore.push({name: name, callback: e.detail.callback, sender: e.detail.sender}); + + e.detail.callback(that.getProperty(name)); + e.stopPropagation(); + } + }, false); + + this.addEventListener('unsubscribe', function (e) { + const name = e.detail.name; + const sender = e.detail.sender; + if (that.hasProperty(name) || that.root) { + console.log('AdapterLitElementProvider(' + that.tagName + ') eventListener("unsubscribe",..) name "' + name + '" found.'); + that.callbackStore.forEach(item => { + if (item.sender === sender && item.name === name) { + const index = that.callbackStore.indexOf(item); + that.callbackStore.splice(index, 1); + console.log('AdapterLitElementProvider(' + that.tagName + ') eventListener for name "' + name + '" removed.'); + } + }); + + e.stopPropagation(); + } + }, false); + + // listen to property changes + this.addEventListener('set-property', function (e) { + const name = e.detail.name; + const value = e.detail.value; + + if (that.hasProperty(name) || that.root) { + console.log('AdapterLitElementProvider(' + that.tagName + ') eventListener("set-property",..) name "' + name + '" found.'); + that.setProperty(name, value); + + that.callbackStore.forEach(item => { + if (item.name === name) { + item.callback(value); + } + }); + + e.stopPropagation(); + } + }, false); + + // Options for the observer (which mutations to observe) + const config = { attributes: true, childList: false, subtree: false }; + + // Callback function to execute when mutations are observed + const callback = function(mutationsList, observer) { + // Use traditional 'for loops' for IE 11 + for(const mutation of mutationsList) { + if (mutation.type === 'attributes') { + const name = mutation.attributeName; + const value = that.getAttribute(name); + + if (that.hasPropertyChanged(name, value)) { + console.log('AdapterLitElementProvider (' + that.tagName + ') observed attribute "' + name + '" changed'); + that.setProperty(name, value); + + that.callbackStore.forEach(item => { + if (item.name === name) { + item.callback(value); + } + }); + } + } + } + }; + + // Create an observer instance linked to the callback function + const observer = new MutationObserver(callback); + + // Start observing the target node for configured mutations + observer.observe(this, config); + + // get all *not observed* attributes + if (this.hasAttributes()) { + const attrs = this.attributes; + for(let i = attrs.length - 1; i >= 0; i--) { + if (['id', 'class', 'style', 'data-tag-name'].includes(attrs[i].name)) { + continue; + } + + this.setProperty(attrs[i].name, attrs[i].value); + console.log('AdapterLitElementProvider (' + that.tagName + ') found attribute "' + attrs[i].name + '" = "' + attrs[i].value + '"'); + } + } } subscribeProviderFor(element) { @@ -49,16 +186,24 @@ export class AdapterLitElement extends LitElement { name: global, callback: (value) => { console.log('AdapterLitElement(' + that.tagName + ') sub/Callback ' + global + ' -> ' + local + ' = ' + value); - that.attributeChangedCallback(local, that[local], value); - // check if an attribute also exists in the tag - if (that.getAttribute(local) !== null) { - // we don't support attributes and provider values at the same time - console.warn('Provider callback: "' + local + '" is also an attribute in tag "' + that.tagName + '", this is not supported!'); + // If value is an object set it directly as property + if (typeof value === 'object' && value !== null) { + // console.log("value is object", value); + that.setPropertyByAttributeName(local, value); + } else { + // console.log("local, that.getPropertyByAttributeName(local), value", local, that.getPropertyByAttributeName(local), value); + that.attributeChangedCallback(local, that.getPropertyByAttributeName(local), value); + + // check if an attribute also exists in the tag + if (that.getAttribute(local) !== null) { + // we don't support attributes and provider values at the same time + console.warn('Provider callback: "' + local + '" is also an attribute in tag "' + that.tagName + '", this is not supported!'); - // update attribute if reflectAttribute is enabled - if (that.reflectAttribute) { - that.setAttribute(local, value); + // update attribute if reflectAttribute is enabled + if (that.reflectAttribute) { + that.setAttribute(local, value); + } } } }, @@ -92,9 +237,27 @@ export class AdapterLitElement extends LitElement { } static getProperties(properties = {}) { + // TODO: super.properties doesn't seem to be defined here! return Object.assign(properties, super.properties); } + findPropertyName(attributeName) { + let resultName = attributeName; + const properties = this.constructor.properties; + // console.log("properties", properties); + + for (const propertyName in properties) { + // console.log("findPropertyName", `${propertyName}: ${properties[propertyName]}`); + const attribute = properties[propertyName].attribute; + if (attribute === attributeName) { + resultName = propertyName; + break; + } + } + + return resultName; + } + attributeChangedCallback(name, oldValue, newValue) { switch(name) { case 'subscribe':