Component initialization

As was mentioned earlier, Webface.js scans the DOM and creates components on the fly. It works recursively: an already initialized component runs an initChildComponent() method and scans its .innerHtml for elements with data-component-class attributes. When it finds such dom element, it creates an instance of a class that's specified as a value for this attribute and then calls initChildComponents() on it too and so on. And it all starts with RootComponent which is initialized by Webface.js app.

Initialization tasks

Let us now take a closer look at what happens during initialization in the Javascript code. Here are the things that happen under the hood when a new Component is created:

  • Validations are initialized
  • The component is assigned a template DOM element if one is present in the DOM: the one with data-component-template attribute with the value matching component class name.
  • The associated HtmlElement is assigned to the Component.dom_element property
  • The element is added into the children list of its parent and the parent component is accessible through the .parent property.
  • afterInitialize() hook is run.
  • The initialized component then scans for elements with data-component-class attributes, creates components out of them and repeats this list for each child component.

For you to be able to control the initialization process, you need to be aware of a few places in the component class that are involved in the initialization.

The Constructor

Strictly speaking, you only need an empty constructor for everything to work

export class MyNotificationComponent extends extend_as("MyNotificationcomponent").mix(Component).with() {
  constructor() {
    super();
  }
}

This will invoke Component's constructor which will will do all the underlying work of initializing the component. Just don't forget that the super() call must always be there.

Component.constructor() takes an optional argument which is called options. It is an object and the only option currently supported is _template_name. Refer to the Templates section for more information. In your own components, you may employ this argument for other things (like setting attribute values programmatically), while the _template_name option will still be passed to the base Component.constructor().

However, the constructor is where a lot of important declarations are put, specifically, you will find you want the following things declared in a constructor:

  • attribute names, default values and other settings for attributes
  • handlers for events and other event settings
  • other component-specific settings that are not necessarily present in the base Component class

Due to the current limitations of Javascript, these things cannot be declared at a class-level (like you would, do in Ruby, for example). So this is what these declarations may look like:

export class MyNotificationComponent extends extend_as("MyNotificationcomponent").mix(Component).with() {
  constructor() {
    super();

    /********************** everything attribute-related ***************************************/
    this.attribute_names = ["message", "autohide_delay", "permanent", "message_id", "visible"];
    this.default_attribute_values = { "permanent": false, "autohide_delay": 5000 };
    this.attribute_callbacks = {
      this.attribute_callbacks["visible"] = (name, self) => {
        // do something when notification changes from visible to hidden;
      }
    };

    /********************** everything events related ******************************************/
    this.native_events   = ["close.click", "message.click"];
    this.event_handlers.add({
      event: this.click_event, role: "self.close", handler: (self, event) => self.hide()
    });

   }
 }

You can learn more about those declarations and how they're used in the relevant sections on Attributes and Events. The point of this section is to just get you familiar with the components initialization process.

afterInitialize()

a(name="afterInitialize")

Unfortunately, most of the useful code you might want to run on component initialization cannot be put into the constructor, because at this point the dom_element is not assigned, parent is not assigned and children are not initialized. Therefore, the afterInitialize() method is introduced - just override it in your class and it will be run after a component is initialized.

By default, the only things afterInitialize() does are:

  • initialize Behaviors
  • sets default attributes
  • initializes i18n validation error messages
You can redefine this method in the descendant classes you create, but don't forget to call super()!

Here's a typical example where afterInitialize() becomes useful - we create a component and then we automatically read its property values from the corresponding DOM elements and attributes:

export class MyComponent extends extend_as("Mycomponent").mix(Component).with() {
  afterInitialize() {
    super();
    this.updatePropertiesFromNodes({ invoke_callbacks: true });
  }
}

For more info on updatePropertiesFromNodes() method see the Properties: updating from DOM section.