Hierarchy / Children & Parents

Children/parent relationship between components in Webface.js usually depends on the DOM hierarchy, but technically it doesn't have to. Every component has a .parent property, which will be null if there is no parent (as is the case with the RootComponent or any other temporarily orphaned component). Every component also has a .children property which is an Array of all the children components.

Let's see what happens when we initialize a Webface.js app. Here's a typical initializer that you would write (see Installation & Usage) section for mode detail):

export var Webface = {
  "init": (root_el=document.querySelector("body")) => {
    var root = new RootComponent();
    window.webface["root_component"] = root;
    root.dom_element = root_el;

When you call Webface.init() it finds the <body> element in the DOM, creates an instance of RootComponent for it and assigns that <body> element to RootComponent.dom_element property. It then calls .initChildComponents() method on this newly created RootComponent instance, which, as you can imagine, reads and parses its dom_element.innerHtml (which was set <body>, remember?) until it identifies descendants which have a data-component-class attribute on them.

For each of the dom elements found to have the data-component-class a new component instance is instantiated and, of course, that dom element is assigned as dom_element to each respective Component instance.

When this process is completed, the newly intialized components are added into the .children array of their parent component and their own .parent property is set accordingly. And finally, once again, initChildComponents() is called on each of those components.

Let's illustrate the above with some code. Create component classes first:

export class MyButtonComponent extends extend_as("MyButtonComponent").mix(Component).with() {
  constructor() { this.attribute_names = ["caption", "disabled"]; }
export class UserFormComponent extends extend_as("UserFormComponent").mix(Component).with() {
  constructor() {}

Here we can see that the <form> element has two child nodes: <h1> and <div>, however those don't have data-component-class attributes, so no new components based on those nodes are created. However, the <button> element does have such attribute and a new MyButtonComponent is created with this node assigned to its .dom_element property. Even though the <button> node isn't the direct descendant of the <form> node, the UserFormComponent instance would still have the MyButtonComponent instance as a child.

Component class provides a number of methods to manipulate children. For example, .applyToChildren() method will call a specific method on each child component (recursively too), while .findChildrenByRole() will will find all child components with a specific role. Refer to the documentation/code for more insight.

Adding components dynamically

Sometimes you might want to add a manually created component into the DOM and also into the components hierarchy. This can be easily done by invoking the .addChild() method on any component. The method accepts another component instance as an argument. Apart from adding the component into the .children array, it will also append it to the end of the parent's dom_element - a default behavior which can be changed by overwriting the _appendChildDomElement() method. For example:

export class UserFormComponent extends extend_as("UserFormComponent").mix(Component).with() {
  constructor() {}
  _appendChildDomElement(el) { this.dom_element.querySelector('.formFields').append(el); }

Now calling UserFormComponent.addChild() will result in appending children's dom elements to <div class="formFields"> instead of the <form> element itself.