In intent-driven development, the emphasis on modularity is paramount. Similarly, in web development, the reusability of functionality is sought after, facilitating code written once to be utilized in multiple instances throughout the application.
Typically in web development, this could be attained by creating a custom element derived from HTMLElement. However, a hurdle arises when engaging with binding engines, as it necessitates the definition of a custom context and the parsing of markup for the binding engine to function effectively. This encompasses any binding expression, ranging from UI interactions to data display.
The BindableElement
class furnishes the capacity to delineate a custom element while concurrently being binding-aware. It autonomously generates its binding context and parses its markup, alleviating you from these tasks. Moreover, it manages memory cleanup during the disconnected callback, obviating the need to manually remove events. As long as the binding engine features are employed for its creation, the component autonomously oversees this cleanup, simplifying the process on your end.
It’s crucial to mention that when you override the connectedCallback
or disconnectedCallback
, you must also invoke the base implementation. Failing to do so can lead to unexpected behavior. Specifically, if you overlook the connectedCallback
, loading operations might not work properly, so it’s recommended to use the load method instead. Neglecting to call the base of disconnectedCallback
can result in memory leaks.
export class MyComponent extends crs.classes.BindableElement {
get shadowDom() {
return true;
}
get html() {
return import.meta.url.replace(".js", ".html");
}
get mobi() {
return import.meta.url.replace(".js", ".mobi.html");
}
async connectedCallback() {
await super.connectedCallback();
// ... do your stuff
}
async preLoad() {
// ... load data before parsing is done
}
async load() {
requestAnimationFrame(async () => {
// ... do your stuff
})
}
async disconnectedCallback() {
// ... do your clean up.
await super.disconnectedCallback();
}
}
customElements.define("my-component", MyComponent);
The standard practice while working with web components entails having three distinct files, each dedicated to a different type: JavaScript, HTML, and CSS. Although the file names are identical, their extensions differ to indicate the type. The naming convention dictates that the file names should match the custom element name, and all these files should be housed in a folder bearing the same name as the custom element.
my-component (folder)
my-component.js
my-component.html
my-component.css
It’s possible to have a component devoid of HTML, but in such cases, you need to return null for the html property. Refer to the details at the bottom for more information.
The BindableElement also employs the utility function disposeProperties during disconnection. This action constitutes an aggressive cleanup, where properties are meticulously inspected and nullified recursively on the component. This thorough cleanup is designed to address any overlooked cleanup by the developer. Although this cleanup is executed on your behalf, it’s still advisable to manually tidy up your elements prior to this aggressive cleanup, ensuring any special cases are adequately handled.
Property | Mutability | Type | Description |
---|---|---|---|
bid | read only | number | The context id for this element |
html | read only | string | What is the file location where the markup should be loaded from. |
hasStyle | read only | bool | Does this element have it’s own stylesheet. Stylesheets are loaded as part of the HTML using a link tag. |
allowNotifications | read write | bool | True if event dispatching is allowed. False if dispatching is suppressed |
shadowDom | read only | bool | True if the component must be created as a shadowdom component with a shadow root. |
mobi | read only | string | In scenarios where a specialized UI is desired for mobile devices, specify the HTML path here, similar to how it might be done with standard HTML. If the application identifies a mobile device, it will load this specified HTML instead of the main HTML. |
Several methods are noteworthy as they are frequently utilized in bindable elements.
Method | Parameters | Description |
---|---|---|
getProperty | property | Retrieve the value of a property from the current binding context. |
setProperty | property value | Assign the given value to the property on the current context. |
updateProperty | property callback | Similar to setProperty, but in this scenario, a callback is passed. The callback must be asynchronous. Upon activation, the callback provides the current value of that property as a parameter. You can then return the desired value, and the property on the context will be updated accordingly. |
notify | event name detail | This method encapsulates the dispatchEvent method, streamlining the event dispatch process and ensuring the correct options are utilized during the dispatch. |
In addition to the above methods, the events mix in is also added.
crs.binding.dom.enableEvents(this);
This introduces two more methods:
- registerEvent
- unregisterEvent
The registerEvent method tracks the events you’ve added, eliminating the need to call unregisterEvent, as these events will be automatically cleaned up upon the element’s disconnection from the DOM.
On the other hand, unregisterEvent is beneficial for temporary events that are no longer needed and should be removed accordingly.
Behind the scenes, registerEvent
and unregisterEvent
leverage the standard addEventListener
and removeEventListener
methods on elements, with the primary aim being tracking and automatic cleanup. As previously mentioned, when employing the binding engine to monitor events, particularly through the use of registerEvent, there’s no need to be concerned about memory leaks from events that were overlooked and not cleaned up.
registerEvent takes four parameters
Parameter | Description |
---|---|
element | The HTMLElement on which the event needs to be registered |
event | The event you are monitoring |
callback | The asynchronous function to invoke upon the event’s trigger |
eventOptions | Additional event listener options you wish to include. This is an optional parameter, defaulting to null. |
unregisterEvent utilizes the first three parameters mentioned above.
this.registerEvent(buttonElement, "click", this.#click);
this.unregisterEvent(buttonElement, "click", this.#click);
There are two distinct timing methods:
- preLoad
- load
These can be overridden in your instance to accommodate timing-sensitive operations.
The preLoad method is invoked prior to parsing, allowing for the loading of data necessary for one-time bindings within the asynchronous preLoad method.
On the other hand, the load method is triggered as the final step of initialization, post the loading of all components.