Widgets definined using this framework will follow the lifecycle documented below. Any step shown below can be left out if your component has no need of it.

Lifecycle

onCreate() Lifecycle

The onCreate method is called as part of creating a component. Its typically used to:

  • Setup event handlers
  • Add custom nodes
  • Setup internal variables/properties

onCreate() State

The following list should help you understand the state of your widget at the time onCreate is called:

  • If you have a template, it will have been loaded into your widget before onCreate, adding all of your child nodes
  • The nodes property will be setup and point to any nodes in your template that specify a layer-id.
  • If your widget was created with any attributes, they may be available in this.properties but you should not depend upon them being set yet
  • No property setters will have been called yet
  • Your widget will not have a parentNode, but WILL have a parentComponent and mainComponent already set.

onCreate() Examples

layerUI.registerComponent('my-sample-widget', {
  template: "<div layer-id='node1'></div><button layer-id='button'></button>",
  methods: {
    onCreate() {
      // 1. DOM Manipulation that does not depend upon any properties
      // other than the ones known to be set prior to this lifecycle event
      if (this.parentComponent.tagName === 'MY-PARENT-COMPONENT') {
        this.nodes.button.innerHTML = "Do X";
      } else {
        this.nodes.button.innerHTML = "Do Y";
      }

      // 2. Setup event listeners on nodes defined in the template
      this.nodes.button.addEventListener('click', this.onClick.bind(this));

      // 3. Setup internal properties
      this.properties.selectedItems = [];
    }
  }
});

Property Setter Lifecycle

The Property Setters are called after onCreate, and after a setImmediate delay, but before onAfterCreate.

When defining a Component, we define properties, and where needed, custom property setters. These setters are called any time these property values change, including the initial value of the property.

layerUI.registerComponent('my-sample-widget', {
  template: "<div layer-id='node1'></div><button layer-id='button'></button>",
  properties: {
    buttonTitle: {
      // Default Value
      value: "Click Me",
      // Property Setter
      set: function(newValue, oldValue) {
        this.nodes.button.innerHTML = newValue;
      }
    }
  }
});

Given the component definition shown above, the property setter will be called under the following conditions:

Applying the default value:

<my-sample-widget></<my-sample-widget>

The above HTML will cause the buttonTitle setter to be called with ("Click Me", null) since the value property definition above specifies that its default value is “Click Me”.

Applying an attribute value:

<my-sample-widget button-title="Do it"></<my-sample-widget>

The above HTML will cause the buttonTitle setter to be called with ("Do it", null) since the initial value passed in is “Do it”. The default value is ignored.

Applying a property value:

var widget = document.createElement("my-sample-widget");
widget.buttonTitle = "Click";

The above javascript will cause the buttonTitle setter to be called with ("Click", null). Its important to understand the life cycle of this to see why the default value of “Click Me” was never applied, and why the DOM may not update at the time we set this property:

The life cycle is:

  1. onCreate is called
  2. setImmediate is used to allow time for properties to be set
  3. Execute widget.buttonTitle = "Click" but property setter is not called
  4. setImmediate delay completes
  5. The buttonTitle setter is called; value of “Click” is used
  6. Any properties set after this phase completes will immediately result in a property setter calling

This means that the following should be expected:

var widget = document.createElement("my-sample-widget");
widget.buttonTitle = "Click";
console.log(widget.nodes.button.innerHTML); // Logs ""
setTimeout(function() {
  console.log(widget.nodes.button.innerHTML); // Logs "Click"
}, 1);

No value set:

If there is no default value, nor an initial value for a property, then no setter is called.

onAfterCreate() Lifecycle

The onAfterCreate method is called after all other initialization on the widget has completed.

This method is typically used for

  • Setup and DOM manipulation that depends upon property values (else it would go in onCreate)
  • One time DOM manipulation based on property values that never change. Any DOM manipulation based on values that change would typically go in onRender which can be called repeatedly.

onAfterCreate() State

The following list should help you understand the state of your widget at the time onAfterCreate is called:

  • onCreate has been called
  • Property setters have all fired
  • onRender has not been called

onAfterCreate() Examples

layerUI.registerComponent('my-message-item-menu', {
  template: '<div class="title" layer-id="title"></div><div class="menu-items" layer-id="menu"></div>',
  properties: {
    message: {},
    menuItems: {
      set: function() {
        this.onRender();
      }
    },
  },
  methods: {
    onAfterCreate: () {

      // If the message never changes, then this title will never change.
      // menu items may change, so those are handled on onRender().
      if (this.message.sender.sessionOwner) {
        this.nodes.title.innerHTML = "Message Actions on Your Message";
      } else {
        this.nodes.title.innerHTML = "Message Actions on Their Message";
      }
    },

    // This will be called during initialization, and each time the menuItems property changes
    onRender() {
      this.nodes.menu.innerHTML = "";
      menuItems.forEach(function(menuItem) {
        var div = document.createElement('div');
        div.innerHTML = menuItem;
        this.nodes.menu.appendChild(div);
      }, this);
    }
  }
});

onRender() Lifecycle

There are many places you can put rendering code; if your rendering does not depend upon any state or values, it could all exist within your template. The onRender method is solely for rendering that is based on property values, and can be called and re-called any time a property value has changed.

Note that your property setter must explicitly call onRender. When does it make sense for a property setter to directly update the DOM rather than calling onRender? onRender takes the sum of all property values to determine how to render; an individual property setter should care only about its own value. If two properties affect rendering, then either you must replicate code in each of those property’s setters, or move all code to onRender.

onRender() State

The following list should help you understand the state of your widget at the time onRender is called:

  • onCreate, all property setters and onAfterCreate have already been called
  • This is automatically called immediately after onAfterCreate; there is no delay between these calls
  • onAttach has not yet been called when this is first called; subsequent calls may be before or after onAttach

Its worth noting that any calls to onRender from a property setter will be ignored if its called prior to onAfterCreate; onRender is automatically called right after onAfterCreate.

onRender() Examples

layerUI.registerComponent('my-message-item-menu', {
  template: '<div class="title" layer-id="title"></div><div class="menu-items" layer-id="menu"></div>',
  properties: {
    message: {},
    menuItems: {
      set: function() {
        this.onRender();
      }
    },
    disabledItems: {
      set: function() {
        this.onRender();
      }
    }
  },
  methods: {
    onRender() {
      this.nodes.menu.innerHTML = "";
      var menuItems = this.menuItems;
      var disabledItems = this.disabledItems;
      for (var i = 0; i < menuItems.length; i++) {
        var div = document.createElement('div');
        div.innerHTML = menuItem;
        if (disabledItems[i]) div.classList.add('disabled');
        this.nodes.menu.appendChild(div);
      }
    }
  }
});

onRerender() Lifecycle

Widgets that render a Layer Web SDK Object should listen for changes to that object and call onRerender to update rendering of that object.

Unlike onRender which would let you render an entirely new Message or Conversation, onRerender would handle changes within the existing Message, Conversation, Identity, etc. onRerender is also used when listening for events rather than changes to properties.

A simple rule of thumb: If a property of this widget changes, you may want onRender. If the property is unchanged, but something has changed within an object refered to by that property, then a smaller onRerender should instead be called.

onRerender() State

The following list should help you understand the state of your widget at the time onRerender is called:

  • onAfterCreate must have been called prior to calling onRerender
  • onRerender, if called during the intial Property Setter phase, will be ignored
  • onRerender is not automatically called as part of any built-in life cycle; it is simply a recommended practice. That means if you want to insure its called, call it from your onRender method.

onRerender() Example

layerUI.registerComponent('my-message-item-menu', {
  template: '<div class="title" layer-id="title">Menu Items</div><div class="menu-items" layer-id="menu"></div>',
  properties: {
    message: {
      set: function(value) {
        value.on('change', this.onRerender, this);
      }
    },
    menuItems: {
      set: function() {
        this.onRender();
      }
    },
    disabledItems: {
      set: function() {
        this.onRender();
      }
    }
  },
  methods: {
    onRender() {
      this.nodes.menu.innerHTML = "";
      var menuItems = this.menuItems;
      var disabledItems = this.disabledItems;
      for (var i = 0; i < menuItems.length; i++) {
        var div = document.createElement('div');
        div.innerHTML = menuItem;
        if (disabledItems[i]) div.classList.add('disabled');
        this.nodes.menu.appendChild(div);
      }

      this.onRerender();
    },
    onRerender() {
      this.nodes.title.innerHTML = this.message.isRead ? 'Read Menu Items' : 'Unread Menu Items';
    }
  }
});

onAttach() Lifecycle

onAttach is called once your widget has been added to the document.body. This callback is useful primarily for examining parentNode and determining how much width/height is available.

onAttach() State

  • onRender has already been called; this means that at least your first rendering must be done without size information.
  • onAttach may be called multiple times in the lifespan of the widget

onAttach() Examples

layerUI.registerComponent('my-calendar-widget', {
  properties: {
    mode: {
      value: 'compact',
      set: function(value) {
        this.onRender();
      }
    }
  },
  methods: {
    onAttach() {
      this.mode = (this.parentNode.clientWidth > 500) ? 'full' : 'compact';
    },
    onRender() {
      this.innerHTML = "";
      if (this.mode === 'full') {
        this.onRenderFullCalendar();
      } else {
        this.onRenderAgenda();
      }
    },
    onRenderFullCalendar() {
      // ...
    },
    onRenderAgenda() {
      // ...
    }
  }
});

onDetach() Lifecycle

The onDetach method is called when your widget is removed from document.body. Its useful primarily for disabling or silencing your widget. It may be the case that your widget will be added back into the DOM at a later time.

onDetach() State

  • onRender and onAttach must have been called at least once for this to trigger

onDetach Examples

layerUI.registerComponent('my-message-item-menu', {
  template: '<div class="title" layer-id="title">Menu Items</div><div class="menu-items" layer-id="menu"></div>',
  properties: {
    message: {
      set: function(value) {
        value.on('change', this.onRerender, this);
      }
    },
    enabled: {},
  },
  methods: {
    onAttach() {
      this.enabled = true;
    },
    onDetach() {
      this.enabled = false;
    },
    onRerender() {
      if (this.enabled) {
        this.nodes.title.innerHTML = this.message.isRead ? 'Read Menu Items' : 'Unread Menu Items';
      }
    }
  }
});

onDestroy() Lifecycle

After your widget has been removed from document.body, it is given a grace period of a few seconds, and then destroyed.

This behavior insures that all of the resources used by these Components are automatically cleaned up.

You can block this behavior; you may also take advantage of this behavior to insure that your widgets are properly cleaned up.

onDestroy() State

If called as part of the lifecycle, then onDetach must be called prior to this method. You may also call this method directly to cleanup resources.

onDestroy() Examples

You can prevent onDestroy from being called after onDetatch by calling evt.preventDefault on the layer-widget-destroyed event:

document.body.addEventListener('layer-widget-destroyed', function(evt) {
  evt.preventDefault();
});

You can use the onDestroy method to cleanup your own event handlers:

layerUI.registerComponent('my-widget', {
  methods: {
    onCreate() {
      this.properties.handleKeyPress = this.handleKeypress.bind(this);
      document.body.addEventListener('keypress', this.properties.handleKeyPress);
    },
    onDestroy() {
      document.body.removeEventListener('keypress', this.properties.handleKeyPress);
    }
  }
});
Theming Defining a Component