ElemX is a proof-of-concept front-end javascript library for connecting MobX to native Web Components with a Vue-like template binding syntax.
# import
$ npm install elemx
# use in project
import { ReactiveElement } from 'elemx';
Yes, this is another front-end javascript framework, so why write it?
- I like the potential of native Web Components
- I prefer using MobX for state management
- I like the template binding syntax of Vue.js and Knockout.js
class HelloWorldElement extends ReactiveElement {
@observable name = "World";
templateHTML() {
return `
<p>
Enter your name:
<wl-textfield outlined placeholder="Your name" @sync="this.name" @sync-event="keyup"></wl-textfield>
</p>
<p>
👋 Hello, {{ this.name }}!
</p>
`;
}
templateCSS() {
// These styles are scoped to this component using the shadow dom
return `
p { ... }
`;
}
}
customElements.define('hello-world', HelloWorldElement);
-
Hello World - A must have
-
Todo List - Also incorporates weightless elements
-
Todo List Full App - Todo List demo app with full code
ElemX binds expressions using observables and computeds to ReactiveElement attributes using direct attribute bindings and directive bindings.
Attribute bindings use :
to denote an attribute of the element is bound to a reactive expression.
// This will update the name attribute when `this.name` changes.
<div :name="this.name"/>
You can also use ::
to denote a two-way attribute binding.
// This will update the name attribute when `this.name` changes,
// *AND* `this.name` will be updated if the name attribute changes.
<div ::name="this.name"/>
Attribute bindings will also bind to element properties if they are defined.
Directive bindings use the @
symbol. They are definable to perform specific customizable reactive actions to the element.
For example, the sync
bindings synchronizes a form element with an observable. There are several pre-defined bindings (see below).
// This will update the value of the input when `this.name` changes,
// *AND* `this.name` will be updated when the input value changes
<input type="text" @sync="this.name"/>
<template @if="this.isShown">
<div>Conditionally shown</div>
</template>
<template @each="this.todos" @as="todo">
// `this` is still available here, and references the ReactiveElement
// that defines this template
<todo-item :item="context.todo"></todo-item>
</template>
You can handle events from elements using the @on-<event-name>
syntax.
<div @on-click="this.handleClick"></div>
Elements can also trigger custom events using emitEvent
in a ReactiveElement.
class MyElement extends ReactiveElement {
emitCustomEvent() {
this.emitEvent('customevent', {test: true})
}
}
<input @sync="this.message" placeholder="edit me"/>
<p>Message is: {{ this.message }}</p>
Several other bindings are already pre-defined. See pre-defined bindings
Bindings are very easy to add. See below:
import { bindings } from 'elemx';
let newBinding = {
// how to access binding on element with '@'
name: "my-binding",
// whether to pass the evaluated binding express to the binding handlers
evaluateValue: true
// initialization
init: ({element, rawValue, evalValue, customElement, context})=> {
// init code here
}
// code that runs when attribute expression changes
update: ({element, rawValue, evalValue, customElement, context})=> {
// binding reaction here
}
}
// register the binding with ElemX
bindings.registerBinding(newBinding);
// now use the binding in template HTML
class MyElement extends ReactiveElement {
templateHTML() {
return `
<div @my-binding="this.value"></div>
`;
}
}