Published
Edited
Aug 30, 2021
1 star
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
page = html`<div class="Counter"></div>` // This is what the component would look like in the HTML file.
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
{
/* Counter component code */
const main = () => {
// This code runs the render function once to initialize the element.
// Usually, this code would be located in a driver script.
// We want to select all of the elements in the document (this page)
// that have class="Counter"
// and render a counter component into each one.
document.querySelectorAll(".Counter").forEach(element => {
// We'll initialize all values to 0.
render({element: element, state: {value: 0}}) // See the definition of render() below.
})
}
const render = ({element, state}) => {
console.debug(`Rendering`, state, `into`, element)
/* If our state looks like this:
{
value: 3
}
*/
/* Then the counter is going to look like this:
<div class="Counter">
<div id="root"> <-- This will contain the actual component--only our code touches this element.
<span id="current_value">3</span> <--- This will show the value.
<button id="increment">Increment</button> <--- When we click this button, the value increases by one.
</div>
</div>
*/
// Read further to see how this happens.
// First, we select the old root element.
// If this is the first time we're rendering, we'll need to create it.
let old_root = element.querySelector("#root")
if (!old_root) {
old_root = document.createElement('div')
element.appendChild(old_root)
}
// Next, we generate a *new* root element from scratch.
// We're going to configure this element to reflect our state.
const new_root = document.createElement('div')
new_root.id = "root"
// This sets up the new root element but does *not* attach any handlers.
const {value} = state // This uses ES6 destructuring to get `value` from state.
new_root.innerHTML = `
<span id="current_value">${value}</span>
<button id="increment">Increment</button>
<button id="reset">Reset</button>`
/* Note this uses ES6 template syntax--if value is 3,
`abc${value}def` becomes `abc3def`. */
// Now that the interface exists, we can attach event handlers.
// When we click the increment button, we want to modify part
// of the state by increasing `value` by 1.
new_root.querySelector('#increment').onclick =
() => update_state({value: value+1}) // See the definition of update_state below.
// When we click the reset button, we want to set `value` to 0.
new_root.querySelector('#reset').onclick =
() => update_state({value: 0})
// This is how we update the state.
const update_state = update_object => {
console.debug(`Updating`, state, `with a partial state`, update_object,
`and rerendering (after yielding execution).`)
// It doesn't matter here, but if we're handling "continuous" input
// (i.e. from a mouse), we have to yield execution with setTimeout so
// the browser can continue capturing events.
setTimeout(render({element: element, state: {...state, ...update_object}}))
}
// Now that the new root element has been created, we replace
// the old root element with the new root element.
old_root.parentElement.replaceChild(new_root, old_root)
}
main() // (Runs the main function.)
page // (Ignore this.) Tells Observable this cell should rerun if the `page` cell changes.
return "Read this!"
}
Insert cell
// (This is the Counter's styling. It would usually be found in a CSS file.)

html`<style>
.Counter {
display:inline-block;
border: 1px solid black;
padding: 5px;
}</style>`
Insert cell
Insert cell
Insert cell
function showable ({text, hidden_text=md`**Click to show**`, show=false}) {
const el = html`${show ? text : hidden_text}`
el.onclick = () => {
show = !show
el.parentNode.replaceChild(showable({text, show, hidden_text}), el)
}
return el
}
Insert cell
import {toc} from "@bryangingechen/toc"
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more