bindActions()
bindActions(editable, args, { targetKey, slidePath, [defaultValue], [field] })
Binds a usage's queries and mutations to where the field is stored, on the slide it is rendered on.
import { bindActions } from "@livepreso/webpack-deck";
export function bindPrice(element, { id, currency, targetKey, slidePath }) {
const price = bindActions(editable, { id, currency }, { targetKey, slidePath });
const label = element.querySelector(".price-label");
const input = element.querySelector("input");
const draw = () => {
label.textContent = price.queries.formatted();
};
draw();
price.on("changed", draw);
input.addEventListener("change", () => price.mutations.set({ amount: Number(input.value) }));
}
A salesperson typing a price and an assistant calling set run the same mutation.
Declaring the field
Use bindActions() where a slide's code works on the DOM directly: markup it writes as a string, or markup already in
its HTML. Declare the function that writes the field's markup. The build finds each place a slide calls it, and reads
the field's id there. Binding is a separate step, run once that markup is on the page.
import { bindActions, defineEditableSource } from "@livepreso/webpack-deck";
import { price } from "./price-actions.js";
export function Price({ id, currency }) {
return `<label data-price="${id}"><span class="price-label"></span><input type="number"></label>`;
}
const editable = defineEditableSource(Price, {
actions: price,
personal: { key: "{targetKey}-{id}" },
});
// The slide module, handed the slide's article.
export default (el) => {
el.innerHTML = Price({ id: "monthly", currency: "AUD" });
bindPrice(el.querySelector('[data-price="monthly"]'), {
id: "monthly",
currency: "AUD",
targetKey: readTargetKey(el),
slidePath: el.dataset.slidePath,
});
};
A slide with static markup
When the field's markup is already in the slide's HTML, there is no function writing it. Declare the function that binds
the field instead. The build reads the field's id from the first object written out in the call, so the element can
come before it.
export function bindDial(element, { id, targetKey, slidePath }) {
const dial = bindActions(editable, { id }, { targetKey, slidePath });
dial.on("changed", () => {
element.textContent = dial.queries.level();
});
return dial;
}
const editable = defineEditableSource(bindDial, {
actions: dial,
everyone: { key: "{targetKey}-{id}" },
});
bindDial(el.querySelector('[data-dial="volume"]'), {
id: "volume",
targetKey: readTargetKey(el),
slidePath: el.dataset.slidePath,
});
Where targetKey and slidePath come from
Both are on the slide's <article>, the element the slide module is handed.
| Option | Read from |
|---|---|
| targetKey | el.dataset.slideKey. On a template the attribute starts with template-: remove it first. |
| slidePath | el.dataset.slidePath |
function readTargetKey(el) {
return el.dataset.slideKey.replace(/^template-/, "");
}
a slide data-slide-key="plans" -> targetKey "plans"
a template data-slide-key="template-plans" -> targetKey "plans"
Stop listening when the slide closes, so a redraw never runs against a slide that has left the screen. The application
fires slideclosed on the article through jQuery, which addEventListener does not see. In bindPrice, with the
article passed in as article:
$(article).on("slideclosed", () => price.off("changed", draw));
Arguments
| Argument | Type | Description |
|---|---|---|
| editable | Object | What defineEditableSource() returned. Must declare actions. |
| args | Object | The usage's arguments. |
| options.targetKey | String | Required. Fills {targetKey}. |
| options.slidePath | String | Required. The slide the field is rendered on. |
| options.defaultValue | Any | The copy written in the source. |
| options.field | String | Which field, where the declaration names several. |
queries
price.queries.formatted(); // "$49 AUD"
await price.queries.formattedAsync(); // "$49 AUD"
Every query has an Async twin that returns a promise. Call it where a query returns a promise itself - the plain call
would return the promise itself, not what it resolves to.
mutations
const result = await price.mutations.set({ amount: 39 });
{ stored: true, value: { amount: 39 }, errors: {} }
The slide shows the new state as soon as the mutation runs, before it is stored.
errors
The refusals from each mutation's latest call, keyed by mutation, then by code. Each one carries the rule's message, ready to show beside the field.
await price.mutations.set({ amount: -5 });
price.errors;
// { set: { "price-below-zero": { message: "A price cannot be below zero." } } }
channel
The channel this page edits the field on.
price.channel; // "everyone" in PresoManager, "personal" in Prep
// null until the interface loads
key
The field's key, for provideRendered().
price.key; // "plans-monthly"
Events
price.on(event, listener);
price.off(event, listener);
changed
Fires when the stored value or the errors change.
price.on("changed", draw);
validation-error
Fires when a mutation is refused.
price.on("validation-error", ({ operation, input, errors }) => {
// operation "set"
// input { amount: -5 }
// errors { "price-below-zero": { message: "A price cannot be below zero." } }
});
provideRendered()
provideRendered(fieldKey, values);
Hands a query or mutation that names rendered what the deck is holding.
import { provideRendered } from "@livepreso/webpack-deck";
const release = provideRendered(price.key, { box: label });
// When the field leaves the screen:
release();
before provideRendered fits is refused with no-render
after fits({ rendered }) is handed { box: label }
after release() fits is refused with no-render
Older apps
An app without this interface shows each field's starting copy, read-only. A mutation stores nothing and logs a warning naming the field.