why html`<button onclick="displayDate()">The time is?</button>` not work?

Cell 1 doesn’t work because when you embed JavaScript into the onclick attribute, it runs in the global context. Named cells in Observable aren’t globals—they are only accessible to other cells in the notebook—and so assuming that displayDate is a cell in your notebook, it’s not a global.

Cell 2 doesn’t work because you’re immediately invoking the displayDate function as part of the expression, and then interpolating the returned value into the HTML. Since displayDate returns undefined, the result is:

html`<button onclick="undefined">The time is?</button>`

Here are two ways to add an event listener to a DOM element. First, you can use Object.assign to set the onclick property of the element:

Object.assign(html`<button>The time is?</button>`, {onclick: displayDate})

Second, you can explicitly call addEventListener:

{
  const button = html`<button>The time is?</button>`;
  button.addEventListener("click", displayDate);
  return button;
}

By coincidence, I am working on a new tagged template literal of Observable which will allow you to assign event listeners inside embedded expressions. When this is released, you will be able to say simply:

html`<button onclick=${displayDate}>The time is?</button>`

I’m giving a preview of this work at tonight’s notebook and will share the notebook soon.

One other note: selecting global elements from the DOM is an antipattern in Observable because cells run in topological order. You can fix this by giving your demo cell a name instead of an id attribute:

demo = html`<p></p>`
function displayDate() {
  return demo.innerHTML = Date();
}

Or consider using a view. For example, if you want a button that causes another cell to run:

viewof update = html`<button>Update the time`
date = {
  update; // Referencing update causes this cell to run when viewof update is clicked.
  return new Date;
}
5 Likes