-
Notifications
You must be signed in to change notification settings - Fork 0
Component
Components are used to create HTML and manage changes to the DOM.
The jackle.component()
method accepts a single Handler or an array or Handlers.
jackel.component(my_component);
// or
jackel.handler([ my_component, my_other_component ]);
The Handler interface and it's types are available under the Jagwah.handler
namespace.
export module Jackle {
...
export module component {
export type html = hyperhtml.BoundTemplateFunction<HTMLElement>;
export type template = (state: Jackle.state, node: Jackle.component.html, controller?: { [name: string]: any }) => void;
export type controller = (state: Jackle.state) => { [name: string]: any };
}
export interface component {
selector: string;
template: Jackle.component.template;
controller?: Jackle.component.controller;
}
}
The selector property should be a reference to an element on the DOM which is accessible via the document.querySelector(selector)
method.
The template
property will be called every time the state changes, the template
property can be async
and will be given three arguments, state
, html
and controller
.
The state
argument is a frozen (Object.freeze()
) copy of the state, this is because all changes to the state should be performed by calling the change
method.
The html
argument is a hyperhtml.BoundTemplateFunction<HTMLElement>
tagged template function, which, more or less takes a string
and turns it into html
, learn more here.
The controller
argument will only be provided if there was a controller
defined for the component.
The controller
property will also be called every time the state changes and accepts a single state
argument, it should return a keyed object.
The example below will find an element matching #form
and then output the tagged template literal result to that element.
const form_component: Jackle.component = {
selector: '#form',
template: async (state, html) => {
html`
<form>
<input type="text" value=${state.temp.text}>
</form>
`;
}
}
<body>
<div id="form">
<!-- <form> element will be placed here -->
</div>
</body>
That example is pretty straight forward, to take it further we can bind the input
element's value with the state, to do this we'll call the change()
method when the input
element value changes.
To implement this we can set an event handler for the input
elements onchange
or oninput
events.
const form_component: Jackle.component = {
selector: '#form',
template: async (state, html) => {
html`
<form>
<input type="text" oninput=${(event: Event) => state.change('update:temp', e.target)} value=${state.temp.text}>
</form>
`;
}
}
However, writing long lambdas within element attributes can be hard to read, to resolve this we'll use a simple controller
for handling interactions.
const form_component: Jackle.component = {
selector: '#form',
controller: (state) => {
return {
oninput: (event: Event) => {
state.change('update:temp', event.target);
}
}
},
template: async (state, html, controller) => {
html`
<form>
<input type="text" oninput=${controller.oninput} value=${state.temp.text}>
</form>
`;
}
}
Personally I'm a fan of keeping file counts to a minimum but since Components are simple objects you can move the controller
and template
properties into separate files and change the structure to suit your needs.
components/form/form.component.ts
import { controller } from './form.controller';
import { template } from './form.template';
const form_component: Jackle.component = {
selector: '#form',
controller: controller,
template: template,
}
components/form/form.controller.ts
export const controller: Jackle.component.controller = (state) => {
return {
oninput: (event: Event) => {
state.change('update:temp', event.target);
}
}
}
components/form/form.template.ts
export const template: Jackle.component.template = async (state, html, controller) => {
html`
<form>
<input type="text" oninput=${controller.oninput} value=${state.temp.text}>
</form>
`;
}
Doing this does mean you'll lose the easily inferred types, so you'll need to wire them out separately.