Skip to content

Stages & Levels: Base Stage Levels

What are we learning here?

  • A component can be loaded with load(Level, ComponentType, Id)
  • Component with an id cannot be loaded multiple times - they will be fronted instead
  • A component can be destroyed with destroy()

We will now create an application with which we can test the base stage levels background, base and foreground.

We will explain the special levels block, error and hint later by loading special components into these levels. Some of the stages have special features and furthermore it makes no sense to load whatever component into these levels.

Let's start a new layout canvas. It is a flexbox layout with a header element like before, one stage, many buttons above this and an empty element for debuging output:

src/demo/apps/stage-levels/content.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<div class="h-100 d-flex flex-column">
  <div class="p-2 flex-grow-1 border border-success d-flex flex-row">
    <div class="w-25 flex-grow-1 d-flex flex-column p-2">
      <div data-role="widget1" class="mb-1">
        Widget 1:
        <button class="btn btn-success btn-xs">Base</button>
        <button class="btn btn-primary btn-xs">Background</button>
        <button class="btn btn-danger btn-xs">Destroy</button>
      </div>
      <div data-role="widget2" class="mb-1">
        Widget 2:
        <button class="btn btn-success btn-xs">Base</button>
        <button class="btn btn-primary btn-xs">Background</button>
        <button class="btn btn-danger btn-xs">Destroy</button>
      </div>
      <div data-role="widget3" class="mb-1">
        Widget 3:
        <button class="btn btn-success btn-xs">Base</button>
        <button class="btn btn-primary btn-xs">Background</button>
        <button class="btn btn-danger btn-xs">Destroy</button>
      </div>
      <div
        data-cc-stage="mario"
        class="flex-grow-1 border border-primary"
      ></div>
    </div>
    <div class="w-25 flex-grow-1 d-flex flex-column p-2">
      <div class="mb-1">Components in Stage</div>
      <div class="flex-grow-1 border border-primary">
        <pre data-role="debug"></pre>
      </div>
    </div>
  </div>
</div>

Let's start with a simple canvas class as before. We will extend then this step by step:

src/demo/apps/stage-levels/content.js

1
2
3
4
5
6
7
8
import { Canvas } from "@codecoupler/cc-ui";
import template from "./content.html";
export default class extends Canvas {
   async start() {
      this.element.innerHTML = template;
      this.element.registerStages();
   }
}

We use now the canvas in a new application:

src/demo/apps/stage-levels/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: "800 600",
        headerTitle: "Test Stage Levels"
      }
    }
  };
  async start() {
    await this.stage.load(Content);
  }
}

We load the new application in our root.js file (just by replacing the previous one):

src/root.js

1
2
3
4
5
6
7
import { Component } from "@codecoupler/cc-ui";
import TestApp from "./demo/apps/stage-levels";
export default class extends Component {
  async start() {
    await this.stage.load(TestApp);
  }
}

Ok, now you see an empty stage with buttons above. None of the buttons do anything for now. Let's add the handlers and all the other code:

src/demo/apps/stage-levels/content.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import { Canvas, Message } from "@codecoupler/cc-ui";
import template from "./content.html";
export default class extends Canvas {
  async start() {
    this.element.innerHTML = template;
    this.element.registerStages();

    // Define what Components to load into the stage
    let widgets = {
      widget1: {
        component: Message,
        options: () => ({
          text: `Component Number 1\nRandom: ${Math.random()}`,
          css: { backgroundColor: "#fff" }
        })
      },
      widget2: {
        component: Message,
        options: () => ({
          text: `Component Number 2\nRandom: ${Math.random()}`,
          css: { backgroundColor: "#eee" }
        })
      },
      widget3: {
        component: Message,
        options: () => ({
          text: `Component Number 3\nRandom: ${Math.random()}`,
          css: { backgroundColor: "#ddd" }
        })
      }
    };

    // Print the output of getComponentsDebug() into the debug element
    this.getStage("mario").on("change", () => {
      const debugElement = this.element.querySelector(`[data-role=debug]`);
      debugElement.innerHTML = "";
      for (const line of this.getStage("mario").getComponentsDebug()) {
        debugElement.append(`${line}\n`);
      }
    });

    // Attach handlers to the buttons
    for (const widgetId in widgets) {
      this.element
        .querySelector(`[data-role=${widgetId}] .btn-success`)
        .addEventListener("click", async () => {
          await this.getStage("mario").load(
            widgets[widgetId].component,
            widgets[widgetId].options(),
            widgetId
          );
        });
      this.element
        .querySelector(`[data-role=${widgetId}] .btn-primary`)
        .addEventListener("click", async () => {
          await this.getStage("mario").load(
            "background",
            widgets[widgetId].component,
            widgets[widgetId].options(),
            widgetId
          );
        });
      this.element
        .querySelector(`[data-role=${widgetId}] .btn-danger`)
        .addEventListener("click", async () => {
          await this.getStage("mario").getComponent(widgetId)?.destroy();
        });
    }
  }
}

You see here that clicking the green button (with the class btn-success) will load a component with the load method using the signature load(ComponentType, Options, Id) into the stage (which is called mario).

Clicking the blue button (with the class btn-primary) will also load a component into the same stage using the load method signature load(Level, ComponentType, Options, Id). The first argument is used to load the component into the level background.

Start Definition Object vs Shortcut Signature

We will use here a signature of the load method which expects a Component, an id, options and a level. This is just a shortcut signature. The load method normally expects a so called start definition object. This would look like:

load({
  component: this.options[widgetId].component,
  id: widgetId,
  options: this.options[widgetId].options(),
  level: "background"
})

Clicking the red button (with the class btn-danger) will destroy the corresponding component. For this we use the method destroy which is available for every component.

All component types and component options are defined in the object widgets. We will use again the component Message. The component provide the options text and css to change the message and css classes.

On the right side you will see a debugging output, because it's not always quite clear why some buttons don't seem to work. Whenever the stage throw the event change we will write an output into the empty element with the attribute data-role=debug.

We will discuss the event system and other possible events later. For now you just have to know that the change event always occurs after an adjustment of the order of the components or after deleting a component.

For the debug output we use the method getComponentsDebug() of the stage which deliver all components ordered by the z-index and level. So we have always an overview of all layers of the stage. The topmost component in the debug output is also the topmost and visible component in the stage.

Let's try the following combinations (destroy everytime all components and start each point with an empty stage):

  • Click all of the green base buttons. Every button will load a separate widget into the base stage.
  • Clicking one of these button again will not load the widget again. It will only front it. You also see a new random number. This is because the component will front and the new options will be passed to the instance. The component Message can handle such a change and refresh its text.

Now click on all three destroy buttons. Then:

  • Click the first green base button and then the second blue background button. Now you will not see the second component because first component was loaded in the level above.
  • Click the second red destroy button and the first component will appear.

Now click on all three destroy buttons. Then:

  • Click the first both background buttons and the third base button.
  • Now try any of the first and second base or background buttons. The component will never appear in the foreground. This is because they already was loaded into the background. The only thing that happens is changing their layer position. But you will never see them as the third component will always stay in front of them.