Skip to main content

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.

A React deck uses useActions(), which calls this. :::
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.

OptionRead from
targetKeyel.dataset.slideKey. On a template the attribute starts with template-: remove it first.
slidePathel.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​

ArgumentTypeDescription
editableObjectWhat defineEditableSource() returned. Must declare actions.
argsObjectThe usage's arguments.
options.targetKeyStringRequired. Fills {targetKey}.
options.slidePathStringRequired. The slide the field is rendered on.
options.defaultValueAnyThe copy written in the source.
options.fieldStringWhich 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
Release when the field leaves the screen. Otherwise a query measures an element that is no longer shown. :::

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.