Skip to content

Stages & Components: Widget

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"

  • How to create an component based on Widget
  • Use the component element to create a HTML structure
  • Use template variables in our HTML structure
  • Use a local scoped CSS

As we now know, every component has at least one main stage. This also applies to our application component. The main stage of an application component is the content area between tilte bar and border elements.

We now load another component into the main stage of our application component. Here too we have to first to create a component. This time we will create a component based on the base component class Widget.

A component that extends the Widget class creates a container in which you can implement arbitrary user interfaces. For this purpose, the component provides a so-called "component element”. You can load any HTML structure you want into a component element.

Best practice for shared and non-shared components

We put the files for the widget into the same directory as the application file. Components which will be used only by one application should be placed into the same folder.

If you plan to develop a reusable component, best practice would be to put all related files of the component into a separate directory with a main file index.js.

We will put the the widget interface HTML in a separate file and use template variables there. Please note the .ejs.html extension. This is important so that variables can be processed:

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

1
<h2><%= locals.myHeader %></h2>

We also put the CSS of the widget interface in a separate file and use a local scope. Please note the .module.css extension. This is important so that local scopes can be used:

src/demo/apps/component-basics/content.module.css

1
2
3
4
5
6
:local(.widget) {
  padding: 20px;
  & h2 {
    text-decoration: underline dotted gray;
  }
}

We can now import our HTML and our CSS file. The imported HTML and will be used as inner HTML of the component element provided. The component element can be accessed via this.element, this.getElement(), or in more verbose this.getElement("main").

Multiple component elements

A widget component only provides a single component element (named "main"). However, it is quite possible that other components provide several component elements with additional names, or none at all.

Here too we use the async start() method, which is called in each component during its initialization phase to fill the component element:

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.innerHTML = tpl;
    this.element.classList.add(style.widget);
  }
}
Always separate Structure, Style and Code
  • Each component should always save the individual parts: HTML, CSS and JavaScript in separate files.
  • You should always use a local scope in CSS. This converts the class names to unique id's and avoids collisions with other component styles.
Import EJS files

If we import files with the extension .ejs.html, we get a function template() so we can call with the desired values of the variables. This function then returns the final HTML code.

Import CSS modules

If we import files with the extension .module.css, we get an object with all classnames defined with :local() as properties. In our example we have defined the classname :local(widget) so we can access the localized classname with style.widget.

It should be best practice to define one local name for the root of each component element. All other styles should be defined as nested classes.

Now load the widget into the main stage of our application. Again we will use the method load() of the main stage of the application for this, like we previously loaded our system into the initial stage or the application into the system component.

Important: Loading a component is always asynchronous. So do not forget to wait for it with await:

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { Application } from "@codecoupler/cc-ui";
import Content from "./content.js";
export default class extends Application {
  static defaults = {
    "@codecoupler": {
      panel: {
        panelSize: "640 510",
        headerTitle: "Test Component Features"
      }
    }
  };
  async start() {
    await this.stage.load(Content);
  }
}

Start Definition Object vs Shortcut Signature

Here we will use a signature of the load method which expects only one Component. This is just a shortcut signature. The load method normally expects a so called start definition object. This would look like:

load({
  component: ChildComponent
})

Now you should see the Header "Component Basics" in the main area of the application.