Skip to content

Widgets

Our first widget does not do too much. Let's start writing a second widget explaining some details and best practices.

We start writing three files:

widgets/testing-sizing.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
import { Widget } from "@codecoupler/cc-ui";
import $ from "jquery";

//Write your stylesheet and your HTML code in separate files
import "./testing-sizing.css";
import widgetTemplate from "./testing-sizing.html";

export default class extends Widget {
  #env;
  #settings;

  //Defaults which will applied on all instances
  static defaults = {
    text: "Default Text"
  };

  async init(env, options) {
    //Some Best Practices
    this.#env = env;
    this.#settings = $.extend(true, {}, this.constructor.defaults, options);
    this.#env.$element.addClass("testing-sizing");
    this.#env.$element.html(widgetTemplate);

    //Implement events if needed
    // this.on("resize", () => {
    //   //Do something
    // });
    // this.on("destroy", () => {
    //   //Do something
    // });

    //Now write your code
    this.#env.$element
      .find("[data-role=more-text]")
      .append(`<p>${this.#settings.text}</p>`);
    this.#env.$element.find("[data-role=add-text]").on("click", () => {
      this.#env.$element
        .find("[data-role=more-text]")
        .append(
          "<p>Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy<p>"
        );
    });
    this.#env.$element.find("[data-role=block-info]").on("click", () => {
      this.tryBlock("info", "Block the Widget for 2 seconds");
    });
    this.#env.$element.find("[data-role=block-error]").on("click", () => {
      this.tryBlock("error", "Block the Widget for 2 seconds");
    });
    this.#env.$element.find("[data-role=hint-error]").on("click", () => {
      this.hint("error", "Error");
    });
    this.#env.$element.find("[data-role=hint-info]").on("click", () => {
      this.hint("info", "Info");
    });
    this.#env.$element.find("[data-role=hint-success]").on("click", () => {
      this.hint("success", "Success");
    });
  }

  //Helper
  tryBlock(type, text) {
    this.block(type, text);
    setTimeout(() => {
      this.unblock();
    }, 2000);
  }
}

widgets/testing-sizing.css

 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
.testing-sizing {
  padding: 20px;
  position: relative;

  & .arrow {
    opacity: 0.5;
    position: absolute;
  }

  & .left {
    left: 0;
    top: 40px;
  }

  & .right {
    right: 0;
    top: 40px;
  }

  & .up {
    left: 50%;
    transform: translate(-50%, 0);
    top: 0;
  }

  & .down {
    left: 50%;
    transform: translate(-50%, 0);
    bottom: 0;
  }
}

widgets/testing-sizing.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<div class="arrow left"><i class="fas fa-chevron-circle-left"></i></div>
<div class="arrow right"><i class="fas fa-chevron-circle-right"></i></div>
<div class="arrow up"><i class="fas fa-chevron-circle-up"></i></div>
<div class="arrow down"><i class="fas fa-chevron-circle-down"></i></div>
<div class="container">
  <h1>My Second Widget!</h1>
  <p>
    Let's write some text:
    <button data-role="add-text" class="btn btn-primary">Write!</button>
  </p>
  <p>
    Let's try blocking:
    <button data-role="block-info" class="btn btn-info">Info</button>
    <button data-role="block-error" class="btn btn-danger">Error</button>
  </p>
  <p>
    Let's try hints:
    <button data-role="hint-info" class="btn btn-info">Info</button>
    <button data-role="hint-error" class="btn btn-danger">Error</button>
    <button data-role="hint-success" class="btn btn-success">Success</button>
  </p>
  <div data-role="more-text"></div>
</div>

Some of the best practices explained:

  • We have separated code, layout and style as it should be.
  • We added a unique classname to the widget container so we have a scope in which we can define our styles.
  • We have defined a static member defaults which will always used to merge the options with. The end user can manipulate this static member and define his own default values which will used for all new instances. The widget implementation will use the merged options from the private variable this.#settings.
  • The argument env will be saved into the private variable this.#env.

Let's replace in our application one of the widgets with our new one:

apps/testing-buttons.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
71
72
73
74
75
76
77
import { Application, Layout } from "@codecoupler/cc-ui";
import buttons from "./custom-buttons";
import MyFirstWidget from "../widgets/my-first-widget.js";
import TestingSizingWidget from "../widgets/testing-sizing";
export default class extends Application {
  static ui = {
    name: "Testing Buttons",
    iconHtml: '<i class="fas fa-seedling fa-fw"></i>'
  };
  async init() {
    let layout = {
      header: {
        show: true
      },
      root: {
        type: "row",
        content: [
          {
            type: "widget",
            title: "Number 1",
            widget: TestingSizingWidget,
            options: {
              text: "Hallo Everyone"
            }
          },
          {
            type: "column",
            content: [
              {
                type: "widget",
                title: "Number 2",
                widget: MyFirstWidget,
                options: {
                  text: "How are you?"
                }
              },
              {
                type: "stack",
                content: [
                  {
                    type: "widget",
                    title: "Number 3",
                    widget: MyFirstWidget,
                    options: {
                      text: "I'm fine, thanks!"
                    }
                  },
                  {
                    type: "widget",
                    title: "Number 4",
                    widget: MyFirstWidget,
                    options: {
                      text: "That is amazing!"
                    }
                  }
                ]
              }
            ]
          }
        ]
      }
    };
    await super.init({
      panel: {
        panelSize: "820 600",
        headerTitle: "My First Title",
        footerToolbar:
          '<i class="far fa-hand-point-right mx-1"></i> [blocks][hints][alerts][close]'
      },
      buttons: buttons,
      widget: {
        widget: Layout,
        options: layout
      }
    });
  }
}

Now start the application to examine the following features.

Resizing

If you run the application you will see four arrows. They point to the border of your widget container.

The first thing you will notice is that the container of the widget is always stretched to 100% of width and height, even when you resize the application.

Scrollbar

Now click on the button "Write!" multiple times. You will see the down arrow will disapear because your widget is now bigger as it's container. You can now scroll down and a scrollbar will appear. All this logic will be done automatically. Because of not using the browser scrollbar you can use always the full area of the container for your widget.

Block and Hints

The other buttons shows other built in functionality. You can block the widget or show hints in different themes.