Skip to content
Snippets Groups Projects
Commit ed92bea5 authored by Reiter, Christoph's avatar Reiter, Christoph :snake:
Browse files

Move AdapterLitElement from provider to common to break a dep cycle

parent a8ab1f72
No related branches found
No related tags found
No related merge requests found
Pipeline #18254 passed with warnings
...@@ -4,7 +4,7 @@ import {unsafeHTML} from 'lit-html/directives/unsafe-html.js'; ...@@ -4,7 +4,7 @@ import {unsafeHTML} from 'lit-html/directives/unsafe-html.js';
import {ScopedElementsMixin} from '@open-wc/scoped-elements'; import {ScopedElementsMixin} from '@open-wc/scoped-elements';
import * as commonStyles from '@dbp-toolkit/common/styles'; import * as commonStyles from '@dbp-toolkit/common/styles';
import {LoginStatus} from './util.js'; import {LoginStatus} from './util.js';
import {AdapterLitElement} from "../../provider/src/adapter-lit-element"; import {AdapterLitElement} from '@dbp-toolkit/common';
let logoutSVG = ` let logoutSVG = `
<svg <svg
......
import {AdapterLitElement} from "@dbp-toolkit/provider/src/adapter-lit-element"; import {AdapterLitElement} from "./src/adapter-lit-element";
export default class DBPLitElement extends AdapterLitElement { export default class DBPLitElement extends AdapterLitElement {
_(selector) { _(selector) {
......
...@@ -12,4 +12,5 @@ export {MiniSpinner}; ...@@ -12,4 +12,5 @@ export {MiniSpinner};
export {Button, LoadingButton}; export {Button, LoadingButton};
export {Spinner}; export {Spinner};
export {InlineNotification}; export {InlineNotification};
export * from './src/logger.js'; export * from './src/logger.js';
\ No newline at end of file export {AdapterLitElement} from './src/adapter-lit-element.js';
\ No newline at end of file
import {LitElement} from "lit-element";
import {Logger} from "@dbp-toolkit/common";
export class AdapterLitElement extends LitElement {
constructor() {
super();
this.connected = false;
this.deferSubscribe = false;
this.deferUnSubscribe = false;
// attributes (if they exist) will be updated if a property is changed by "subscribe"
this.reflectAttribute = true;
// default values
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 = {};
Logger.debug('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) {
// Logger.debug("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();
if (this.deferUnSubscribe) {
const attrs = this.unsubscribe.split(',');
attrs.forEach(element => this.unSubscribeProviderFor(element));
this.deferSubscribe = false;
this.unsubscribe = '';
}
if (this.deferSubscribe) {
const attrs = this.subscribe.split(',');
attrs.forEach(element => this.subscribeProviderFor(element));
this.deferSubscribe = false;
}
this.connected = true;
const that = this;
this.addEventListener('dbp-subscribe', function (e) {
const name = e.detail.name;
if (that.hasProperty(name) || that.root) {
Logger.debug('AdapterLitElementProvider(' + that.tagName + ') eventListener("dbp-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('dbp-unsubscribe', function (e) {
const name = e.detail.name;
const sender = e.detail.sender;
if (that.hasProperty(name) || that.root) {
Logger.debug('AdapterLitElementProvider(' + that.tagName + ') eventListener("dbp-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);
Logger.debug('AdapterLitElementProvider(' + that.tagName + ') eventListener for name "' + name + '" removed.');
}
});
e.stopPropagation();
}
}, false);
// listen to property changes
this.addEventListener('dbp-set-property', function (e) {
const name = e.detail.name;
const value = e.detail.value;
if (that.hasProperty(name) || that.root) {
Logger.debug('AdapterLitElementProvider(' + that.tagName + ') eventListener("dbp-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)) {
Logger.debug('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);
Logger.debug('AdapterLitElementProvider (' + that.tagName + ') found attribute "' + attrs[i].name + '" = "' + attrs[i].value + '"');
}
}
}
disconnectedCallback() {
const attrs = this.subscribe.split(',');
attrs.forEach(element => this.unSubscribeProviderFor(element));
super.disconnectedCallback();
}
subscribeProviderFor(element) {
Logger.debug('AdapterLitElement(' + this.tagName + ') subscribeProviderFor( ' + element + ' )');
const pair = element.trim().split(':');
const local = pair[0];
const global = pair[1] || local;
const that = this;
const event = new CustomEvent('dbp-subscribe',
{
bubbles: true,
composed: true,
detail: {
name: global,
callback: (value) => {
Logger.debug('AdapterLitElement(' + that.tagName + ') sub/Callback ' + global + ' -> ' + local + ' = ' + value);
// If value is an object set it directly as property
if (typeof value === 'object' && value !== null) {
// Logger.debug("value is object", value);
that.setPropertyByAttributeName(local, value);
} else {
// Logger.debug("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);
}
}
}
},
sender: this,
}
});
this.dispatchEvent(event);
}
unSubscribeProviderFor(element) {
Logger.debug('AdapterLitElement(' + this.tagName + ') unSubscribeProviderFor( ' + element + ' )');
const pair = element.trim().split(':');
const global = pair[1] || pair[0];
const event = new CustomEvent('dbp-unsubscribe',
{
bubbles: true,
composed: true,
detail: {
name: global,
sender: this,
}
});
this.dispatchEvent(event);
}
static get properties() {
return {
subscribe: { type: String },
unsubscribe: { type: String },
};
}
findPropertyName(attributeName) {
let resultName = attributeName;
const properties = this.constructor.properties;
// Logger.debug("properties", properties);
for (const propertyName in properties) {
// Logger.debug("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':
Logger.debug('AdapterLitElement() attributeChangesCallback( ' + name + ', ' + oldValue + ', ' + newValue + ')');
if (this.subscribe && this.subscribe.length > 0) {
if (this.connected) {
const attrs = this.subscribe.split(',');
attrs.forEach(element => this.unSubscribeProviderFor(element));
} else {
this.deferUnSubscribe = this.subscribe.length > 0;
this.unsubscribe = this.subscribe;
}
}
if (newValue !== null) {
this.subscribe = newValue;
if (this.connected) {
const attrs = newValue.split(',');
attrs.forEach(element => this.subscribeProviderFor(element));
} else {
this.deferSubscribe = newValue && newValue.length > 0;
}
}
break;
default:
// The function should not be called if oldValue is an object, oldValue and newValue are empty
// or if newValue is empty but name and oldValue are set
// This should prevent 'Uncaught SyntaxError: JSON.parse unexpected end of data at line 1 column 1 of the JSON data'
if ((typeof oldValue === 'object' && !oldValue && !newValue) || (!newValue && oldValue && name)) {
// Logger.debug("attributeChangedCallback ignored", name, oldValue, newValue);
break;
}
super.attributeChangedCallback(name, oldValue, newValue);
}
// Logger.debug("this.lang", this.tagName, name, this.lang);
// Logger.debug("this.entryPointUrl", this.tagName, name, this.entryPointUrl);
// console.trace();
}
/**
* Send a dbp-set-property event to the provider components
*
* @param name
* @param value
* @returns {boolean}
*/
sendSetPropertyEvent(name, value) {
// Logger.debug("dbp-set-property", name, value);
const event = new CustomEvent('dbp-set-property', {
bubbles: true,
composed: true,
detail: {'name': name, 'value': value}
});
// dispatch the dbp-set-property event to the parent (if there is any) so that the current element
// doesn't terminate the event if it has the attribute set itself
const element = this.parentElement ? this.parentElement : this;
return element.dispatchEvent(event);
}
// update(changedProperties) {
// changedProperties.forEach((oldValue, propName) => {
// switch(propName) {
// case 'subscribe':
// if (this.subscribe && this.subscribe.length > 0) {
// if (this.connected) {
// const attrs = this.subscribe.split(',');
// attrs.forEach(element => this.unSubscribeProviderFor(element));
// } else {
// this.deferUnSubscribe = this.subscribe.length > 0;
// this.unsubscribe = this.subscribe;
// }
// }
// if (this.subscribe !== null) {
// if (this.connected) {
// const attrs = this.subscribe.split(',');
// attrs.forEach(element => this.subscribeProviderFor(element));
// } else {
// this.deferSubscribe = this.subscribe && this.subscribe.length > 0;
// }
// }
// break;
// }
// });
//
// super.update(changedProperties);
// }
}
import {LitElement} from "lit-element"; export {AdapterLitElement} from "@dbp-toolkit/common";
import {Logger} from "@dbp-toolkit/common";
export class AdapterLitElement extends LitElement {
constructor() {
super();
this.connected = false;
this.deferSubscribe = false;
this.deferUnSubscribe = false;
// attributes (if they exist) will be updated if a property is changed by "subscribe"
this.reflectAttribute = true;
// default values
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 = {};
Logger.debug('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) {
// Logger.debug("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();
if (this.deferUnSubscribe) {
const attrs = this.unsubscribe.split(',');
attrs.forEach(element => this.unSubscribeProviderFor(element));
this.deferSubscribe = false;
this.unsubscribe = '';
}
if (this.deferSubscribe) {
const attrs = this.subscribe.split(',');
attrs.forEach(element => this.subscribeProviderFor(element));
this.deferSubscribe = false;
}
this.connected = true;
const that = this;
this.addEventListener('dbp-subscribe', function (e) {
const name = e.detail.name;
if (that.hasProperty(name) || that.root) {
Logger.debug('AdapterLitElementProvider(' + that.tagName + ') eventListener("dbp-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('dbp-unsubscribe', function (e) {
const name = e.detail.name;
const sender = e.detail.sender;
if (that.hasProperty(name) || that.root) {
Logger.debug('AdapterLitElementProvider(' + that.tagName + ') eventListener("dbp-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);
Logger.debug('AdapterLitElementProvider(' + that.tagName + ') eventListener for name "' + name + '" removed.');
}
});
e.stopPropagation();
}
}, false);
// listen to property changes
this.addEventListener('dbp-set-property', function (e) {
const name = e.detail.name;
const value = e.detail.value;
if (that.hasProperty(name) || that.root) {
Logger.debug('AdapterLitElementProvider(' + that.tagName + ') eventListener("dbp-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)) {
Logger.debug('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);
Logger.debug('AdapterLitElementProvider (' + that.tagName + ') found attribute "' + attrs[i].name + '" = "' + attrs[i].value + '"');
}
}
}
disconnectedCallback() {
const attrs = this.subscribe.split(',');
attrs.forEach(element => this.unSubscribeProviderFor(element));
super.disconnectedCallback();
}
subscribeProviderFor(element) {
Logger.debug('AdapterLitElement(' + this.tagName + ') subscribeProviderFor( ' + element + ' )');
const pair = element.trim().split(':');
const local = pair[0];
const global = pair[1] || local;
const that = this;
const event = new CustomEvent('dbp-subscribe',
{
bubbles: true,
composed: true,
detail: {
name: global,
callback: (value) => {
Logger.debug('AdapterLitElement(' + that.tagName + ') sub/Callback ' + global + ' -> ' + local + ' = ' + value);
// If value is an object set it directly as property
if (typeof value === 'object' && value !== null) {
// Logger.debug("value is object", value);
that.setPropertyByAttributeName(local, value);
} else {
// Logger.debug("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);
}
}
}
},
sender: this,
}
});
this.dispatchEvent(event);
}
unSubscribeProviderFor(element) {
Logger.debug('AdapterLitElement(' + this.tagName + ') unSubscribeProviderFor( ' + element + ' )');
const pair = element.trim().split(':');
const global = pair[1] || pair[0];
const event = new CustomEvent('dbp-unsubscribe',
{
bubbles: true,
composed: true,
detail: {
name: global,
sender: this,
}
});
this.dispatchEvent(event);
}
static get properties() {
return {
subscribe: { type: String },
unsubscribe: { type: String },
};
}
findPropertyName(attributeName) {
let resultName = attributeName;
const properties = this.constructor.properties;
// Logger.debug("properties", properties);
for (const propertyName in properties) {
// Logger.debug("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':
Logger.debug('AdapterLitElement() attributeChangesCallback( ' + name + ', ' + oldValue + ', ' + newValue + ')');
if (this.subscribe && this.subscribe.length > 0) {
if (this.connected) {
const attrs = this.subscribe.split(',');
attrs.forEach(element => this.unSubscribeProviderFor(element));
} else {
this.deferUnSubscribe = this.subscribe.length > 0;
this.unsubscribe = this.subscribe;
}
}
if (newValue !== null) {
this.subscribe = newValue;
if (this.connected) {
const attrs = newValue.split(',');
attrs.forEach(element => this.subscribeProviderFor(element));
} else {
this.deferSubscribe = newValue && newValue.length > 0;
}
}
break;
default:
// The function should not be called if oldValue is an object, oldValue and newValue are empty
// or if newValue is empty but name and oldValue are set
// This should prevent 'Uncaught SyntaxError: JSON.parse unexpected end of data at line 1 column 1 of the JSON data'
if ((typeof oldValue === 'object' && !oldValue && !newValue) || (!newValue && oldValue && name)) {
// Logger.debug("attributeChangedCallback ignored", name, oldValue, newValue);
break;
}
super.attributeChangedCallback(name, oldValue, newValue);
}
// Logger.debug("this.lang", this.tagName, name, this.lang);
// Logger.debug("this.entryPointUrl", this.tagName, name, this.entryPointUrl);
// console.trace();
}
/**
* Send a dbp-set-property event to the provider components
*
* @param name
* @param value
* @returns {boolean}
*/
sendSetPropertyEvent(name, value) {
// Logger.debug("dbp-set-property", name, value);
const event = new CustomEvent('dbp-set-property', {
bubbles: true,
composed: true,
detail: {'name': name, 'value': value}
});
// dispatch the dbp-set-property event to the parent (if there is any) so that the current element
// doesn't terminate the event if it has the attribute set itself
const element = this.parentElement ? this.parentElement : this;
return element.dispatchEvent(event);
}
// update(changedProperties) {
// changedProperties.forEach((oldValue, propName) => {
// switch(propName) {
// case 'subscribe':
// if (this.subscribe && this.subscribe.length > 0) {
// if (this.connected) {
// const attrs = this.subscribe.split(',');
// attrs.forEach(element => this.unSubscribeProviderFor(element));
// } else {
// this.deferUnSubscribe = this.subscribe.length > 0;
// this.unsubscribe = this.subscribe;
// }
// }
// if (this.subscribe !== null) {
// if (this.connected) {
// const attrs = this.subscribe.split(',');
// attrs.forEach(element => this.subscribeProviderFor(element));
// } else {
// this.deferSubscribe = this.subscribe && this.subscribe.length > 0;
// }
// }
// break;
// }
// });
//
// super.update(changedProperties);
// }
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment