META-INF.frontend.uibuilder-dom-bind.uibuilder-dom-bind.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of uibuilder-core Show documentation
Show all versions of uibuilder-core Show documentation
Core implementation for the UIBuilder Framework
import { PolymerElement } from '@polymer/polymer/polymer-element.js';
import { GestureEventListeners } from '@polymer/polymer/lib/mixins/gesture-event-listeners.js';
import { PropertyEffects } from '@polymer/polymer/lib/mixins/property-effects.js';
import { OptionalMutableData } from '@polymer/polymer/lib/mixins/mutable-data.js';
class UIBuilderDomBind extends GestureEventListeners(OptionalMutableData(PropertyEffects(PolymerElement))) {
static get is() {
return "uibuilder-dom-bind";
}
static get properties() {
return {};
}
static get observedAttributes() {
return ['mutable-data'];
}
constructor() {
super();
this.root = null;
this.$ = null;
this.__children = null;
}
attributeChangedCallback() {
this.mutableData = true;
}
connectedCallback() {
this.style.display = 'none';
}
disconnectedCallback() {
this.__removeChildren();
}
__insertChildren() {
this.parentNode.insertBefore(this.root, this);
}
__removeChildren() {
if (this.__children) {
for (let i = 0; i < this.__children.length; i++) {
this.root.appendChild(this.__children[i]);
}
}
}
render() {
this._render(null);
}
_render(template) {
if (!this.__children) {
template = /** @type {HTMLTemplateElement} */(template || this.querySelector('template'));
if (!template) {
// Wait until childList changes and template should be there by then
let observer = new MutationObserver(() => {
template = /** @type {HTMLTemplateElement} */(this.querySelector('template'));
if (template) {
observer.disconnect();
this.render();
} else {
throw new Error('dom-bind requires a child');
}
});
observer.observe(this, {childList: true});
return;
}
this.root = this._stampTemplate(template);
// after the vaadin 14.4.10 release, starting from the 14.5.1, the vaadin-client code
// has been changed about appending virtual child with the inject_by_id method. Earlier
// the target element was searched with a simple this.$[id] where id is the id of the
// element we want to link with the backend component. So here, the code were
// this.$ = this.root.$ to add all the element references which has an id to this dom-bind
// element, which is the context when searching for the element to link to the backend component.
// From vaadin 14.5, the vaadin-client code has been changed, and the code to search
// for the element is now in ElementUtil.getElementById(Node context, String id), which first
// searches the id in the document.body.$ and if found, returns that element. But, if not
// the function will check if the context (this dom-bind) has a shadowRoot (it has), and if so,
// is will run the .getElementById(id) on the context.shadowRoot and return the result, even
// if this is null (and it is null in our case...)
//
// tl.dr: sa for now, this is the best workaround we found
this.shadowRoot.getElementById = (id) => this.root.$[id];
this.__children = [];
for (let n = this.root.firstChild; n; n = n.nextSibling) {
this.__children[this.__children.length] = n;
}
this._enableProperties();
}
this.__insertChildren();
this.dispatchEvent(new CustomEvent('dom-change', {
bubbles: true,
composed: true
}));
}
_attachDom(dom) {
// DO NOT DELETE, we don't want any shadowdom to be attached to this component
}
ready() {
// DO NOT DELETE, DO NOT CALL super, otherwise it will try to lock bindings to the top level content
}
_setupInnerHTML(html) {
let template = document.createElement("template");
let dummyContainer = document.createElement("div");
dummyContainer.innerHTML = html;
let content = template.content;
this._reactivateScripts(dummyContainer, content);
while (dummyContainer.childNodes.length) {
content.appendChild(dummyContainer.childNodes[0]);
}
dummyContainer = null;
this._render(template);
this.__flushChanges();
this.dispatchEvent(new CustomEvent('innerHTMLSet', {
detail: {
fragment: false
}
}));
}
_setupInnerHTMLFragment(html, parentId) {
let modifiedHtmlFragmentContainer = document.createElement("div");
modifiedHtmlFragmentContainer.innerHTML = html;
let parentElement = parentId ? document.querySelector("[id='" + parentId + "']") : this.parentNode;
let fragmentElementsWithId = [...modifiedHtmlFragmentContainer.querySelectorAll("*[id]")];
let documentElementsWithId = fragmentElementsWithId
.map(fragmentElement => fragmentElement.getAttribute("id"))
.map(id => {
let elem = parentElement.querySelector("[id='" + id + "']");
if (!elem && parentElement.shadowRoot) {
elem = parentElement.shadowRoot.querySelector("[id='" + id + "']");
}
return elem;
})
.filter(element => element != null);
documentElementsWithId.forEach(element => {
const id = element.getAttribute("id");
let fragmentElement = fragmentElementsWithId.find(fragmentElement => fragmentElement.getAttribute("id") === id);
if (fragmentElement) {
fragmentElement
.getAttributeNames()
.filter(attributeName => attributeName !== "id")
.forEach(attributeName => {
element.setAttribute(attributeName, fragmentElement.getAttribute(attributeName));
});
[...element.getAttributeNames()
.filter(attributeName => attributeName !== "id")]
.forEach(attributeName => {
if (!fragmentElement.hasAttribute(attributeName)) {
element.removeAttribute(attributeName);
}
});
}
});
if (parentElement) {
documentElementsWithId.forEach(element => {
const id = element.getAttribute("id");
parentElement.$[id] = element;
});
}
modifiedHtmlFragmentContainer = null;
this._enableProperties();
this.__flushChanges();
this.dispatchEvent(new CustomEvent('innerHTMLSet', {
detail: {
fragment: true
}
}));
}
__flushChanges() {
this._flushProperties();
if (!this.__dataClientsReady) {
this._flushClients();
}
if (this.__dataPending) {
this._flushProperties();
}
}
_reactivateScripts(container, content) {
const scripts = container.querySelectorAll('script:not([type]), script[type="text/javascript"]');
scripts.forEach(script => {
let reactivatedScript = document.createElement('script');
reactivatedScript.type = 'text/javascript';
if (script.src) {
reactivatedScript.src = script.src;
} else {
reactivatedScript.textContent = script.innerText;
}
content.appendChild(reactivatedScript);
container.removeChild(script);
})
}
}
customElements.define(UIBuilderDomBind.is, UIBuilderDomBind);