Surprises with `display` and `view`

(Sorry for so many posts. Genuinely trying to be helpful. And I find it better to post each topic in a distinct thread instead of a single long list of issues for purposes of discovery and clarity.)

I think many users will find it very surprising that when display or view are called from inside a function, they present their output in the cell where that function is defined and not where that function is called.

This is confounding enough that I think it would be appropriate to put a brief mention of this where display and view are first introduced in the documentation, with a link to more “advanced” documentation that explains why and provides the solution. The solution being to pass display or view from the calling cell, so that when they are called they have the “correct” scope.

Here is a notebook that walks through my experience with this issue:

https://observablehq.com/d/8596d725dd5d8d16

+1 for a short note in the docs. I hit the same thing while splitting a chart helper out into its own cell: the helper called display() and the output showed up next to the helper definition, not in the cell I was actually running, and it took me a while to realise it was a scoping thing rather than a bug.

Passing display in as an argument is what fixed it for me too, something like function renderChart(data, {display}) { … } and then calling renderChart(rows, {display}) from the cell that should show the result. Having that pattern next to where display and view are first explained would save people some head scratching, especially anyone coming from plain JavaScript where a function call has no idea which “cell” it lives in.

I would not pass display and view; I would instead have helper/component functions return the element (without displaying them).

So instead of:

function sizer(size, display) {
  const myHTML = html`<div style="border: 1px solid black; font-size: ${size}px;">The font size is ${size}</div>`;
  display(myHTML);
}

and

sizer(20, display)

I would say

function sizer(size) {
  return html`<div style="border: 1px solid black; font-size: ${size}px;">The font size is ${size}</div>`;
}

and then

sizer(20)

or if you want to be more explicit

display(sizer(20))

In other words, push the display logic down to the cells where you want to display something, and leave any reusable logic as agnostic about how (and where) the content is displayed.