Delite and Custom Elements

The heart of delite is its support for custom elements. In a nutshell, this means that an app has the ability to define new tags (often called widgets), for example <my-combobox>, which can be used interchangeably with the native HTML tags like <input>.

Delite's support of custom elements is mainly split across four components:

Defining Custom Elements

Custom Elements are defined using register(). The register() method is a combination of a class declaration system (internally it uses dcl), and a shim of document.registerElement() from the new custom elements proposed standard.

When defining a custom element you generally:

There are other optional steps, like defining methods and emitting custom events, but those are the most basic.

Example:

define([
    "delite/register",
    "delite/Widget",
    "delite/handlebars!./MyWidget/MyWidget.html",               // the template
    "delite/theme!./MyWidget/themes/{{theme}}/MyWidget.css"     // the CSS
], function (register, Widget, template) {
    register(
        "my-widget",                // the custom tag name
        [HTMLElement, Widget],      // the superclasses
        {
            // my template
            template: template

            // my public properties
            stringProp: "",
            numProp: 123,
            boolProp: true,
            objProp: null
        }
    );
});

The template (MyWidget.html) will be something like:

<template>
    Hello {{stringProp}}!  You have {{numProp}} messages and
    {{this.boolProp ? "no" : "some"}} calendar events.
</template>

It is discussed in detail in handlebars!.

Instantiating Custom Elements Declaratively

Like native elements, custom elements can be instantiated declaratively. Given a custom element definition like above, you would declare an instance like:

<my-widget stringProp="hi" numProp="456" boolProp="false"
    objProp="myGlobalVariable"></my-widget>

Note that delite does automatic type conversion from the attribute value (which is always a string) to the property's type.

Parsing

"Parsing" refers to scanning the document for custom element usages (ex: <my-widget></my-widget>), and upgrading those plain HTML elements to be proper custom elements (i.e. setting up the prototype chain, and calling createdCallback() and attachedCallback()).

When the document has finished loading, delite will do an initial parse. Afterwards, if new custom elements are defined, delite will scan the document for any additional nodes that need to be upgraded.

So, custom elements will be instantiated without any guaranteed order, and without any guaranteed timing relative to other javascript code running. Therefore, if your custom elements depend on a global variable, like in the example above, you should make sure it is available before the custom element is loaded. So you may need code like this:

require(["dstore/Memory"], function (Memory) {
    myGlobalVar = new Memory();
    require(["deliteful/List"]);
});

Declarative Events

Like native elements, custom elements' main notification mechanism is emitting events. Declarative markup has a special syntax for listening to events, both natural events and synthetic ones:

<my-widget on-click="console.log(event);"
    on-element-selected="console.log(event);"></my-widget>

The example above is listening for a click event as well as a synthetic "element-selected" event.

Note how there is a convenience event parameter available to be referenced in the inlined code.

Of course, you can also use Element.addEventListener(), but this syntax is often convenient.

Creating Custom Elements Programatically

Custom elements can be created programatically the way that plain javascript objects are instantiated:

var myWidgetInstance = new MyWidget({
    stringProp: "hi",
    numProp: 456,
    boolProp: false,
    objProp: myGlobalVariable
});

Custom elements can also be created programatically just like native elements, except that [to support browsers without native custom element support] you need to call the register.createElement() shim rather than calling document.createElement():

var myWidgetInstance = register.createElement("my-widget");
myWidgetInstance.stringProp = "hi";
myWidgetInstance.numProp = 456;
myWidgetInstance.boolProp = false;
myWidgetInstance.objProp = myGlobalVariable;

The first syntax calling new MyWidget({...}) is just syntactic sugar for the second syntax.

Note that:

During creation you also typically set up event handlers, like:

myWidgetInstance.on("click", myCallback1);
myWidgetInstance.on("selection-change", myCallback2);

Finally, use placeAt() to insert the element into the DOM:

myWidget.placeAt(document.body);

Note that placeAt() will internally call attachedCallback() for the widget and any descendant widgets, which may be necessary for them to finish initializing.