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

template slots: handle slots being added after the first render

Instead of querying the slots in the shadow DOM after the first render
just add all available slots to the light DOM.

This fixes missing filled slots when slots where added after the first render,
for example if they were used only after some property is set.
parent 81eef212
No related branches found
No related tags found
1 merge request!106template slots: handle slots being added after the first render
Pipeline #67937 passed
This commit is part of merge request !106. Comments created here will be created in the context of that merge request.
......@@ -4,12 +4,12 @@ export default class DBPLitElement extends AdapterLitElement {
constructor() {
super();
this.htmlOverrides = '';
this._rootContainer = null;
this._localTemplateSlotsImported = false;
this._globalSlotsContainer = null;
this._globalTemplateSlotsImported = false;
this._renderDone = false;
}
/**
* See: https://lit-element.polymer-project.org/guide/properties#initialize
*/
static get properties() {
return {
...super.properties,
......@@ -18,20 +18,11 @@ export default class DBPLitElement extends AdapterLitElement {
}
connectedCallback() {
this.updateComplete.then(() => {
// transform all global override templates or named template slots in the light DOM to named div slots
this.transformSlots();
});
super.connectedCallback();
}
disconnectedCallback() {
super.disconnectedCallback();
if (this._rootContainer !== null) {
this._rootContainer.remove();
if (this._globalSlotsContainer !== null) {
this._globalSlotsContainer.remove();
}
}
......@@ -39,91 +30,106 @@ export default class DBPLitElement extends AdapterLitElement {
return this.shadowRoot === null ? this.querySelector(selector) : this.shadowRoot.querySelector(selector);
}
getTagName() {
const tagName = this.dataset ? (this.dataset.tagName || '') : '';
firstUpdated() {
super.firstUpdated();
this._renderDone = true;
this._importTemplateSlots();
}
return tagName !== '' ? tagName : this.tagName.toLowerCase();
update(changedProperties) {
changedProperties.forEach((oldValue, propName) => {
switch (propName) {
case "html-overrides":
this._importTemplateSlots();
break;
}
});
super.update(changedProperties);
}
/**
* Transforms all global override templates or named template slots in the light DOM to named div slots
* on the first render.
*
* Global overrides will replace all existing slotted elements with the same slot name.
*/
transformSlots() {
// query all named slots of the component
const slots = this.shadowRoot.querySelectorAll("slot[name]");
// if there are no slots we can exit
if (slots.length === 0) {
_importTemplateSlots() {
if (!this._renderDone) {
return;
}
this._importLocalTemplateSlots();
this._importGlobalTemplateSlots();
}
slots.forEach((slot) => {
const slotName = slot.name;
// search if there is a template with the name of the slot in the light DOM of the component
const templateElem = this.querySelector("template[slot=" + slotName + "]");
if (!templateElem) {
return;
}
_importLocalTemplateSlots() {
if(this._localTemplateSlotsImported ) {
return;
}
// Now extract slots from templates contained in the light dom
let lightTemplateSlots = this.querySelectorAll(':scope > template[slot]:not([slot=""]');
for(let templateElem of lightTemplateSlots) {
// create a slot div container to put in the cloned template content
const divElem = document.createElement('div');
divElem.slot = slotName;
divElem.slot = templateElem.getAttribute('slot');
divElem.appendChild(templateElem.content.cloneNode(true));
// remove the old template
templateElem.remove();
// put the slot div container with the cloned template in the light DOM
this.appendChild(divElem);
});
}
this._localTemplateSlotsImported = true;
}
_importGlobalTemplateSlots() {
if(this.htmlOverrides === '' || this._globalTemplateSlotsImported) {
return;
}
// check if we have an "html-override" attribute set so we need to check for the global override template
if (this.htmlOverrides !== '') {
const globalOverrideTemplateElem = document.querySelector('template#' + this.htmlOverrides);
if (globalOverrideTemplateElem) {
// we need to clone the element so we can access the content
const overrideTemplateElemClone = globalOverrideTemplateElem.content.cloneNode(true);
const tagName = this.getTagName();
// then we will look if there is an override for the current tag
const templateOverrideElem = overrideTemplateElemClone.querySelector('template#' + tagName);
if (templateOverrideElem) {
// if there is an override we again need to clone that template so we can access the content
const templateOverrideElemClone = templateOverrideElem.content.cloneNode(true);
// Create a dummy node and add it to the the same shadow root the templates are from
// By adding it into the template we have the nice side effect that it is not visible
let container = document.createElement("div");
globalOverrideTemplateElem.append(container);
container.appendChild(templateOverrideElemClone);
this._rootContainer = container;
// now we need to look for slots in the override
slots.forEach((slot) => {
const slotName = slot.name;
// if a slot is found we need to remove the current slot in the light DOM
// so we are not showing the old and new content at the same time
if (templateOverrideElemClone.querySelector('[slot="' + slotName + '"]')) {
const currentSlotElement = this.querySelector('[slot="' + slotName + '"]');
if (currentSlotElement) {
currentSlotElement.remove();
}
}
});
// Now move the slots into the light dom of the target.
// The parent node in the other shadow root has to stay around for this to work
while (container.childNodes.length) {
this.appendChild(container.removeChild(container.childNodes[0]));
// First add global override templates as light dom slots
let globalOverrideTemplateElem = document.querySelector('template#' + this.htmlOverrides);
if (globalOverrideTemplateElem !== null) {
// we need to clone the element so we can access the content
const overrideTemplateElemClone = globalOverrideTemplateElem.content.cloneNode(true);
const templateOverrideElem = overrideTemplateElemClone.querySelector('template#' + this.tagName.toLowerCase());
if (templateOverrideElem !== null) {
const templateOverrideElemClone = templateOverrideElem.content.cloneNode(true);
// Find all slots which are direct children (somehow :scope doesn't work here so check parentNode)
let globalTemplateSlots = [];
for(let e of templateOverrideElemClone.querySelectorAll('[slot]:not([slot=""]')) {
if (e.parentNode === templateOverrideElemClone) {
globalTemplateSlots.push(e);
}
}
// Global overrides will replace local ones.
// Either normal slotted elements or the ones we create from templates.
for(let slotElem of globalTemplateSlots) {
for (let elm of this.querySelectorAll('[slot="' + slotElem.slot + '"]')) {
elm.remove();
}
}
// Create a dummy node and add it to the the same shadow root the templates are from
// By adding it into the template we have the nice side effect that it is not visible
let container = document.createElement("div");
globalOverrideTemplateElem.append(container);
this._globalSlotsContainer = container;
for(let slotElem of globalTemplateSlots) {
container.appendChild(slotElem);
}
// Now move the slots into the light dom of the target.
// The parent node in the other shadow root has to stay around for this to work
while (container.childNodes.length) {
this.appendChild(container.removeChild(container.childNodes[0]));
}
}
}
this._globalTemplateSlotsImported = true;
}
}
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