Carebun Interactive/Make a book

The guide

Chapter 3

Making an interactive

The 25-line pattern behind every slider on this shelf: state, controls, a draw function, and the arithmetic shown in the open.

10 min read · interactive

On this page
  1. The pattern
  2. The coffee calculator, line by line
  3. What the engine gives you
  4. Seven rules for a good interactive
  5. Try it yourself
  6. Common questions
  7. Carry this
  8. Check yourself

In this chapter, we will learn how to write an interactive. We will read the coffee calculator from Chapter 1 line by line, and then list the parts the engine gives you: sliders, buttons, tiles, tables and charts. If you can write a function in JavaScript, you can write one of these.

The pattern

An interactive is a function that receives an empty box and fills it. It keeps its numbers in one object, draws once, and draws again whenever a control moves.

state  -->  controls change the state  -->  draw() reads the state  -->  tiles, arithmetic, chart
  ^                                                                              |
  +------------------------  the reader moves a slider  -------------------------+

The coffee calculator, line by line

MB.widget("coffee-cost", function (el, ui) {
    var st = {};                                            # 1. the state
    var body = ui.frame("What does the coffee habit cost?", # 2. the frame: title and subtitle
                        "Move a slider. Every number below is re-derived.");
    var out = MB.h("div");                                  # 3. where results are drawn

    body.appendChild(ui.controls([                          # 4. the sliders
      { key: "cups",  label: "Cups per day",  min: 0,   max: 8,  value: 2 },
      { key: "price", label: "Price per cup", min: 0.5, max: 8,  step: 0.1, value: 3.5,
        fmt: function (v) { return "$" + v.toFixed(2); } },
      { key: "years", label: "Years",         min: 1,   max: 40, value: 10 }
    ], st, draw));
    body.appendChild(out);

    function draw() {                                       # 5. state in, page out
      var day = st.cups * st.price, year = day * 365, total = year * st.years;
      out.textContent = "";
      out.appendChild(ui.tiles([
        { label: "Per day",  value: MB.fmt.usd(day) },
        { label: "Per year", value: MB.fmt.usd(year) },
        { label: "Over " + st.years + " years", value: MB.fmt.usd(total), kind: "hot" }]));
      out.appendChild(ui.calc([                             # 6. the arithmetic, in the open
        "per day    " + st.cups + " x $" + st.price.toFixed(2) + " = " + MB.fmt.usd(day),
        "per year   " + MB.fmt.usd(day) + " x 365 = " + MB.fmt.usd(year),
        "total      " + MB.fmt.usd(year) + " x " + st.years + " = " + MB.fmt.usd(total)]));
      ui.foot("One sentence that tells the reader what to notice.");   # 7. the lesson
    }
    draw();
});

(The block is coloured as Python only so that the comments stand out. The file is JavaScript.)

Save it in books/your-book/assets/widgets-your-book.js, add the file name to scripts in book.json, and place it in a chapter:

<div class="widget" data-widget="coffee-cost"></div>

What the engine gives you

CallWhat you get
ui.frame(title, subtitle)The card with its header. Returns the body to fill.
ui.controls(specs, state, onChange)Sliders. Add log: true for ranges such as 1,000 to 100,000,000.
ui.segmented(options, value, onPick)A row of buttons where one is chosen.
ui.tiles([{label, value, note, kind}])Big numbers. kind is hot, good or bad.
ui.calc([lines])The arithmetic block.
ui.table(columns, rows, rowClass)A table that scrolls sideways on a phone.
ui.foot(html)The line under the card: what to notice.
MB.lineChart({...})Lines over one axis, with legend, end labels, markers and hover.
MB.barList({...})Horizontal bars with the value at the tip.
MB.scatter({...})Points in up to three groups, each with a tooltip.
MB.fmt.int / usd / compact / bytes / ms / pctNumbers as people write them: 1,095,000 and $0.045 and 664 GB.
MB.h(tag, attributes, children)Make any element, for everything else.

Seven rules for a good interactive

  1. One idea. If the title needs the word "and", make two.
  2. Start at the book's numbers. The default position of every slider is the example in the text, so the first thing the reader sees matches what they just read.
  3. Show the arithmetic. A number without its steps is a magic trick, and a magic trick teaches nothing.
  4. Ask for a prediction. The sentence before the interactive asks the reader to guess.
  5. Say what is real. Mark each number as measured, derived or made up for illustration.
  6. Survive the extremes. Drag every slider to both ends. No "NaN", no "Infinity", no broken layout.
  7. Do not depend on it. The text must make sense to a reader whose browser could not run the script.

Try it yourself

Copy the coffee calculator. Change it into a calculator for something from your own subject: litres of water for a garden bed, hours to read a book, the cost of running a server. Keep it under 30 lines.

Common questions

Can I use React, D3 or another library?

The engine needs none, and every book on the shelf works without one. If your interactive truly needs a library, load it inside your own script. Remember rule 7: the page must still read well if it fails to load.

Where do I put large data?

In a separate file in assets, listed in scripts before the widget files. Attach it to window.MB, for example MB.myData = [...].

Can an interactive call a server or an AI model?

The books here run entirely in the browser, so they cost nothing to host and they keep working for years. The memory book's simulator replaces the AI model with simple rules and says so. We recommend the same honesty.

How do I test it?

node tools/smoke.mjs opens every page in a real browser, drags every slider to both ends, presses every button and fails on any error or any "NaN" on screen.

Carry this

  • State, controls, draw(). Draw once, and again on every change.
  • Always print the arithmetic, and end with one line that says what to notice.
  • Sliders start at the numbers used in the text.

Check yourself

1. Your interactive shows "NaN" when a slider is at zero. What is the likely cause, and which rule did it break?

Answer

A division by that value, or by something derived from it. It breaks rule 6. Guard the division, or give the slider a smallest value above zero.