API reference
This page provides a reference to the Javascript API for wecco
.
Apps
wecco
apps are a combination of view functions, data models and mutations on
those models in combination with a well-defined lifecycle that causes the
views to be invoked any time the data changes. Those apps follow the
model-view-update architecture pattern which is often described as the
elm architecture.
Contexts
wecco
apps use an idiom called contexts which means, that all functions
that make up an app and which are called by the framework
receive a context parameter which contains data and callbacks for each app
part.
In wecco
, by convention, a context parameter is usually handled via object
deconstruction, cherry-picking only those properties needed.
The following sections describe the types of context and their usage.
View
A view defines how the UI looks (i.e. which HTML elements to render) given some data (the model).
A view is just a function that conforms to the following type definition:
type View<MODEL, MESSAGE> = (ctx: ViewContext<MODEL, MESSAGE>) => ElementUpdate
interface ViewContext<MODEL, MESSAGE> {
emit: MessageEmitter<MESSAGE>
get model(): MODEL
}
The view function produces an ElementUpdate
, which is how wecco
updates
element's content. This can either be a string, a value returned from the
wecco.html
string tag, and HTMLElement
, null
or an array of those values.
The context passed to the view contains the model
to render as well as
a callback emit
which is used to emit a message to be handled.
Update
An update function mutates the data (the model) based on some input, which is called a message. Think of a message as something like a command pattern object which describes what operation to perform and which arguments to use for this operation.
The result of such an update cycle is a (possibly new) version of the data to render.
An update function must conform to the following type definition:
type Updater<MODEL, MESSAGE> = (ctx: UpdaterContext<MODEL, MESSAGE>) => ModelResult<MODEL>
export interface UpdaterContext<MODEL, MESSAGE> extends ViewContext<MODEL, MESSAGE> {
get message(): MESSAGE
}
Notice the return type ModelResult<MODEL>
. This type union includes instances
of MODEL
, Promises
that resolve to a MODEL
as well as the sentinel
NoModelChange
value (which signals, that nothing needs to be updated).
wecco.createApp
Creates a new app. The arguments define the app's model, view and update handler.
modelInitializer
- initializer to create the initial model - either synchronously or asynchronouslyupdater
- applies update messages to the model - either synchronously or asynchronouslyview
- renders the model - always synchronously
The function returns an App
(see below) that can be used to control
the app by emitting messages.
The following "sketch" shows the "wireframe" for each app:
type Model = // ...
type Message = // ...
function update({model}: wecco.UpdaterContext<Model, Message>): Model {
// ...
}
function view ({ emit, model }: wecco.ViewContext<Model, Message>): wecco.ElementUpdate {
return wecco.html`
...
`
}
document.addEventListener("DOMContentLoaded", () => {
wecco.createApp(() => /* some model value */, update, view)
.mount("#app")
})
wecco.App
An App
is returned from wecco.createApp
. It provides two methods:
mount
- which "mounts" the app onto some HTML elementemit
- which can be used to send messages to the app used to control the app from the outside.
wecco.NoModelChange
A sentinel value representing the updater's decision not to update the model
and skip the re-rendering cycle. May be returned from an update
function to
indicate that side effects happend (such as updating the storage) but no
re-rendering is needed.
Custom Elements
wecco.define
define
is used to define a new web component which is also a custom web element.
The function accepts the following arguments:
name
- the name of the custom element. This name is used as the custom element's tag name, so it must follow the custom element specs (i.e. the name must contain a dash)renderCallback
- the render callback will be called whenever the elements content needs to be updated. The callback receives a
RenderContext` parameter (see below) which, by convention, is handled by object deconstruction.options
- an optional options object. See below for additional information
The value returned from define
is an instance of a ComponentFactory
which
can produce instances of the defined component by passing in initial data.
const CountClicks = wecco.define<number>("count-clicks", ...)
// ...
const countClicksElement = CountClicks(3)
someHTMLElement.appendChild(countClicksElement)
Render Callback
The context passed to each invocation of the render callback, follows the following interface:
interface RenderContext<T> {
get data(): T
requestUpdate: () => void
emit: (event: string, payload?: any) => void
addEventListener: (event: string, listener: (payload?: any) => void) => void
once: (id: string, callback: OnceCallback) => void
}
data
- getter to get the data to be rendered. Almost all custom elements use thisrequestUpdate
- a callback to be bound to some trigger from within the rendered HTML. Invoking this callback signals to the framework thatdata
has been updated and rendering should be triggered again for the element's rendered content to reflect this change. As withdata
, almost all custom elements use this.emit
- can be used to emit aCustomEvent
from the element. Pass in the event's name and an (optional payload). This method is especially usefull when using custom events.addEventListener
- used to subscribe forCustomEvent
s.once
- register some callback to be called exactly once for each instance of the element. Use this to register a callback that, loads inital data (i.e. viafetch
) or register timeouts, intervals, ...
Options
You can pass an optional options
parameter to define
which supports a
two-way binding of data
fields to either HTML element attributes or
JavaScript properties of the element. The options type is defined as
interface DefineOptions<T> {
observedAttributes?: Array<keyof T>
observedProperties?: Array<keyof T>
}
Full Example
The following full example shows a simple click-counting button:
import * as wecco from "@weccoframework/core"
interface CountClicks {
count?: number
}
const CountClicks = wecco.define("count-clicks", ({ data, requestUpdate }: wecco.RenderContext<CountClicks>) => {
data.count = data.count ?? 0
return wecco.html`<p>
<button
class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
@click=${() => { data.count++; requestUpdate() }}>
You clicked me ${data.count} times
</button>
</p>`
}, {
observedAttributes: ["count"],
})
wecco.component
Creates an instance of a define
d element which will use the given data
and
will be added to the given host
element. This is an alternative approach to
using the ComponentFactory
returned by define
which does not require a
handle to the component factory but uses the browser's built-in registry for
custom web components.
The following two snippets as essentially the same:
vs.
wecco.define("some-component" // ...
const componentData = // ...
wecco.component("some-component", componentData).mount("#body")
The arguments passed to component
are:
componentName
- the component's namedata
- the bound datahost
- the host to add the component to. This is optional; you can leave it out and add the element to the DOM by any other means.
Rendering HTML
wecco.updateElement
updateElement
applies the given update request to the given target.
target
- the target to update. This can be a DOMElement
or a CSS selector string.request
- the update "request". May be an update function or anElementUpdater
. The following things can be used:null
to remove any content from the targetstring
which is used as inner text (note that this is notinnerHTML
)- a DOM
Element
which gets appended as a child of target - a
wecco.ElementUpdateFunction
which is callback function that receives the target to update - a
wecco.ElementUpdater
an interface for types used internally by wecco (but may be used externally as well). See the documentation onwecco.html
andwecco.shadow
below for the most common uses of this type - an array of any of the above
notifyUpdated
whether to send an update event after the element update
updateElement
emits custom events to notify any subscribers that an update happens/happend. The following
events are emitted:
updatestart
- emitted for thetarget
before update startsupdateend
- emitted for thetarget
adter update endsupdate
- emitted for every child nested belowtarget
that was visited during the update
Subscribe to those event in case you need to handle custom processing such as managing animations.
wecco.html
Used as a tag for a template string to create an ElementUpdater
that updates
a DOM node. See HTML tag template string reference for details.
wecco.shadow
Used to create a HTML ShadowDocument
to isolate an element from the surrounding
CSS and JavaScript.