Skip to content

Stages & Components: Single Root Template

What are we learning here?

Stages & Components Relations:                                 ┌──────────────────────┐
                                                ┌─optional────►│ Component Element(s) │
┌───────────────┐        ┌───────────┐          │              └──────────────────────┘
│ Initial Stage ├─load──►│ Component ├─provides─┤
└───────────────┘        └───────────┘          │               ┌──────────┐
                               ▲                └─at least one─►│ Stage(s) │
                               │                                └────┬─────┘
                               │                                     │
                               └ ─ ─ ─ ─ ─ ─ ─ ─ load ─ ─ ─ ─ ─ ─ ─ ─┘
System Hierarchy:

► Initial Stage
  └► Root Component
     └► Stage "main"
        └► Application
           └► Stage "main"
              └► Widget
                 └► Component Element "main"

  • Each component element can be replaced with this.element.replaceWith()

As you have seen we have used a separate HTML file where we have defined the inner structure for use in a component element. Some times such a HTML template have to be a single root template. This means that the template has only one HTML element in the root.

In this case it makes sense to replace the component element with the root element of the template instead of using this as inner HTML. If you use such a template, you should always use it with replaceWith instead of innerHTML, as this saves a div element.

Let's wrap our previous template for our first widget in a div (because we do not want to use the h2 tag as root element):

src/demo/apps/component-basics/content.ejs.html

1
2
3
<div>
  <h2><%= locals.myHeader %></h2>
</div>

And now we will replace the statement this.element.innerHTML = tpl; with this.element.replaceWith(tpl);:

src/demo/apps/component-basics/content.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { Widget } from "@codecoupler/cc-ui";
import template from "./content.ejs.html";
import style from "./content.module.css";
export default class extends Widget {
  myHeader = "Component Basics";
  async start() {
    let tpl = template({
      myHeader: this.myHeader
    });
    this.element.replaceWith(tpl);
    this.element.classList.add(style.widget);
  }
}

But why do we mention this here in a separate chapter? Isn't that a standard method of an Element instance?

No it is not exactly the case here. The property this.element is a proxy of an Element instance and handles some functions differently and provides some additional functions that we will explore in future chapters.

For example, the replaceWith method we use here will copy the styles, classes and some other attributes from the origin element. And as you can see, you can continue to use this.element even after the replacement.

Furthermore the proxy do not allow to replace the component element with multiple nodes or remove them.

Try the edge cases

All the following code examples replacing this.element.replaceWith(tpl) will throw an error:

this.element.replaceWith(
  document.createElement("div"),
  document.createElement("div")
);
this.element.replaceWith("<div>one</div>", "<div>two</div>");
this.element.replaceWith("<div>one</div><div>two</div>");
this.element.remove();

Don't Remove Styles and Classes manually

The proxy already makes sure that if you want to replace the element, the original styles and classes are copied. However, this does not prevent you from removing the styles and classes individually or all manually (e.g. with element.styles.cssText="" or element.className="").

This is dangerous because each component element is prepared for its environment. Removing classes or styles can cause the layout to collapse.

For this purpose, the component element offers two helper functions: element.resetStyle() and .resetClass(). These functions remove all styles and classes, but restore the original state.