ShadowTemplateMixin

Stamps a template into a component's Shadow DOM when instantiated

Overview

Purpose: Create a component's shadow root, and clone a template into it when it is first rendered.

This mixin forms a core part of the Elix render pipeline:

events → methods → setState → render

Expects the component to provide:

  • internal.template property that returns an HTMLTemplateElement used to populate the shadow tree of a new component.
  • Optional internal.shadowRootMode property to indicate whether to create open (default) or closed shadow roots.

Provides the component with:

  • internal.render implementation that creates and populates the shadow root the first time the component is rendered.
  • internal.ids property that can be used to access elements in the shadow tree that have id attributes. See the ids property below.
  • internal.shadowRoot property holding a reference to an element's shadow root.

All Elix elements use ShadowTemplateMixin to populate their Shadow DOM subtree with template elements, so is included in the ReactiveElement base class.

Usage

import ShadowTemplateMixin from "elix/src/core/ShadowTemplateMixin.js";
class MyElement extends ShadowTemplateMixin(HTMLElement) {}

ShadowTemplateMixin must be applied to your base class before any mixins or classes that want to render to elements in the Shadow DOM subtree. This is unusual for Elix mixins, which by design can usually be applied in any order.

Example

import * as internal from "elix/src/internal.js";
import ShadowTemplateMixin from "elix/src/core/ShadowTemplateMixin.js";

class GreetElement extends ShadowTemplate(HTMLElement) {
  connectedCallback() {
    this[internal.render]();
  }

  get [internal.template]() {
    const template = document.createElement("template");
    template.innerHTML = `Hello, <slot></slot>!`;
    return template;
  }
}

customElements.define("greet-element", GreetElement);
const element = new GreetElement();
element.textContent = "world"; // User sees, "Hello, world!"

When the element above is instantiated, the constructor supplied by ShadowTemplateMixin finds the element's template and stamps it into a new shadow root.

Rendering

This mixin does its primary work in a component's internal.render method. The first time that method is called, the mixin:

  1. Looks for a template property (see below).
  2. Attaches a new shadow root. By default, the shadow root will be open, although you can request a closed shadow root by defining a internal.shadowRootMode property that returns "closed".
  3. Clones the template into the shadow root.

The template property

The ShadowTemplateMixin expects a component to define a property getter identified as internal.template. If this property has not been defined, the mixin issues a console warning, but does not throw an exception.

The template property should return an HTML template (an HTMLTemplateElement). This HTMLTemplateElement can be defined by any means, including directly in JavaScript (as shown in the example above). You may also find it convenient to define a template with the html helper in the template module. Using that helper, the above example becomes:

import * as internal from "elix/src/internal.js";
import * as template from "elix/src/template.js";
import ShadowTemplateMixin from "elix/src/core/ShadowTemplateMixin.js";

class GreetElement extends ShadowTemplate(HTMLElement) {
  get [internal.template]() {
    return template.html`Hello, <slot></slot>!`;
  }
}

For better performance with components that dynamically construct a template, ShadowTemplateMixin caches a component's template.

The ids property

As a convenience, ShadowTemplateMixin defines a property called ids that represents the collection of all subelements in an element's Shadow DOM subtree that have an id attribute. This makes it easier for you to obtain a reference to a specific subelement, e.g., for the purpose of rendering changes to it.

The ReactiveMixin example shows a simple increment/decrement component that wants to render the value in its state to a span with the id "value". We can use the ids property to refer to that span efficiently:

import * as internal from "elix/src/internal.js";
import ReactiveElement from "elix/define/ReactiveElement.js";

class IncrementDecrement extends ReactiveElement {
  [internal.render](changed) {
    if (changed.value) {
      // When the value changes, show the value as the span's text.
      this[internal.ids].value.textContent = this[internal.state].value;
    }
  }
}

The reference this[internal.ids].value is equivalent to this.shadowRoot.getElementById('value').

API

Used by classes AlertDialog, AutoCompleteComboBox, AutoCompleteInput, AutoSizeTextarea, Backdrop, Button, CalendarDay, CalendarDayButton, CalendarDayNamesHeader, CalendarDays, CalendarMonth, CalendarMonthNavigator, CalendarMonthYearHeader, Carousel, CarouselSlideshow, CarouselWithThumbnails, CenteredStrip, CheckListItem, ComboBox, CrossfadeStage, DateComboBox, DateInput, Dialog, Drawer, DrawerWithGrip, DropdownList, ExpandablePanel, ExpandableSection, Explorer, FilterComboBox, FilterListBox, HamburgerMenuButton, Hidden, Input, ListBox, ListComboBox, ListExplorer, ListWithSearch, Menu, MenuButton, MenuItem, MenuSeparator, ModalBackdrop, Modes, MultiSelectListBox, NumberSpinBox, Option, OptionList, Overlay, OverlayFrame, PlainAlertDialog, PlainArrowDirectionButton, PlainAutoCompleteComboBox, PlainAutoCompleteInput, PlainAutoSizeTextarea, PlainBackdrop, PlainBorderButton, PlainButton, PlainCalendarDay, PlainCalendarDayButton, PlainCalendarDayNamesHeader, PlainCalendarDays, PlainCalendarMonth, PlainCalendarMonthNavigator, PlainCalendarMonthYearHeader, PlainCarousel, PlainCarouselSlideshow, PlainCarouselWithThumbnails, PlainCenteredStrip, PlainCenteredStripHighlight, PlainCenteredStripOpacity, PlainChoice, PlainComboBox, PlainCrossfadeStage, PlainDateComboBox, PlainDateInput, PlainDialog, PlainDrawer, PlainDrawerWithGrip, PlainDropdownList, PlainExpandablePanel, PlainExpandableSection, PlainExpandCollapseToggle, PlainExplorer, PlainFilterComboBox, PlainFilterListBox, PlainHamburgerMenuButton, PlainHidden, PlainInput, PlainListBox, PlainListComboBox, PlainListExplorer, PlainListWithSearch, PlainMenu, PlainMenuButton, PlainMenuItem, PlainMenuSeparator, PlainModalBackdrop, PlainModes, PlainMultiSelectListBox, PlainNumberSpinBox, PlainOptionList, PlainOverlay, PlainOverlayFrame, PlainPageDot, PlainPopup, PlainPopupButton, PlainPopupSource, PlainProgressSpinner, PlainPullToRefresh, PlainRepeatButton, PlainSelectableButton, PlainSlideshow, PlainSlideshowWithPlayControls, PlainSlidingPages, PlainSlidingStage, PlainSpinBox, PlainTabButton, PlainTabs, PlainTabStrip, PlainToast, Popup, PopupButton, PopupSource, ProgressSpinner, PullToRefresh, ReactiveElement, RepeatButton, SelectableButton, Slideshow, SlideshowWithPlayControls, SlidingPages, SlidingStage, SpinBox, TabButton, Tabs, TabStrip, Toast, TooltipButton, UpDownToggle, and WrappedStandardElement.

ids property

A convenient shortcut for looking up an element by ID in the component's Shadow DOM subtree.

Example: if component's template contains a shadow element <button id="foo">, you can use the reference this[ids].foo to obtain the corresponding button in the component instance's shadow tree. The ids property is simply a shorthand for getElementById, so this[ids].foo is the same as this[shadowRoot].getElementById('foo').

Type: object