🏠 back to Observable

overcoming `r is not iterable` with new runtime? (or another mistake?)

I have created a HTML file that imports a cell from my notebook following a pattern to call in specific cells that I believe I picked up from @fil somewhere along the way:

  for (let i in renders)
    renders[i] = document.querySelector(renders[i]);

This patterns works well when I view the HTML page locally on my computer and reference the notebook and runtime .js files from Observable, like this:

<script type="module">
      import {Inspector, Runtime} from "https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js";
      import notebook from "https://api.observablehq.com/d/9aef18a2b092b886.js";

	  const renders = {
        "map": "#output",
      };

  for (let i in renders)
    renders[i] = document.querySelector(renders[i]);

  Runtime.load(notebook, (variable) => {
    if (renders[variable.name])
      return new Inspector(renders[variable.name]);
  });
    </script>

After downloading the tarball code and uploading it to GitHub, I want to change the code to reference the local copy of the notebook .js file, like this:

import notebook from "./9aef18a2b092b886@429.js";

But doing so returns a console error:

Uncaught TypeError: r is not iterable
    at Function.value (runtime.js:2)
    at index_mod.html:28

I note that the approach I am using for calling in multiple cells doesn’t work when I add ?v=3 to the end of my notebook js code when pointing to the observable api, which leads me to expect that the code is only good for older versions of the runtime. Other forum discussions (referenced below) further lead me to believe this might have to do with changes to the runtime. Is that the case?

And what is the new pattern for calling in several specific cells? Would I just repeat this code block from Jeremy’s primer on embedding:

new Runtime().module(notebook, name => {
  if (name === "chart") {
    return new Inspector(document.querySelector("#chart"));
  }
});

… Or is there a way that we can still, as before, define a list of target cells without repeating the code block several times?

Thanks in advance for your help and guidance!


Related reading:

This thread indicates that there may be no options but to repeat the new pattern. Copying in Jeremy’s response:

As this code is written for a React app, it’s a bit hard for me to ascertain how to convert it back to Vanila js.

Here’s what I have working so far:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
    <title>Embedded Observable notebook</title>
    <link href="https://unpkg.com/leaflet@1.2.0/dist/leaflet.css" rel="stylesheet">

  </head>
  <body>
  <div id="title"></div>
  <div id="map"></div>
<script type="module">
import {Runtime, Inspector} from "https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js";
import define from "https://api.observablehq.com/d/9aef18a2b092b886.js?v=3";

const inspect1 = Inspector.into("#title");
(new Runtime).module(define, name => (name === "title") && inspect1());

const inspect2 = Inspector.into("#map");
(new Runtime).module(define, name => (name === "map") && inspect2());
  
</script>

… This approach still seems fairly labor intensive as compared to just defining a list of cells that one wishes to render out. Is there really no more elegant solution?

Apparently this is it:

const inspect = Inspector.into("#title", "#map");
(new Runtime).module(define, name => (name === "title", "map") && inspect());

… Sorry, I am mostly just guessing and hacking away :frowning: Inspiration to try this from @j-f1 's answer to : DRY Leaflet layers & the HTML template literal

It’s interesting to note that both Chrome and Firefox were unable to prettify the minified source of runtime.js:

  • Chrome didn’t display any different formatting in the first place.
  • Firefox did even worse, it produced invalid code where e.g. for (const e of r) became for (const eofr) (took me forever to figure out that’s not a thing).
1 Like

I’ll try to explain a bit better … but if there’s anything that’s currently too confusing about https://observablehq.com/@observablehq/downloading-and-embedding-notebooks, please let me know!

Creating a runtime:

new Runtime();

Loading a notebook into a new runtime:

new Runtime().module(notebook);

Loading a notebook into a new runtime, and attaching an observer to a single cell, named “chart”:

new Runtime().module(notebook, name => {
  if (name === "chart") {
    return new Inspector("#chart");
  }
});

The callback argument to module is invoked once for every named cell in the notebook definition, giving you a chance to set up an observer that renders to the DOM. In the previous example, we’re only rendering the cell named “chart” into an existing HTML element with the ID of “chart” … but in the following example, we’ll render “title” and “map” into "#title" and "#map":

new Runtime().module(notebook, name => {
  if (name === "title") {
    return new Inspector("#title");
  } else if (name === "map") {
    return new Inspector("#map");
  }
});

Or more generically:

function renderNotebook(notebook, cellNames) {
  new Runtime().module(notebook, name => {
    if (cellNames.includes(name)) {
      return new Inspector(`#${name}`);
    }
  });
}

And then, to use it:

renderNotebook(notebook, ["title", "map"])
3 Likes

i like and use the strategy proposed by @bgchen
it’s work well !

const runtime = new Runtime();
const renders = {"charttitr": "#charttitr","chart": "#chart","aide":"#aide"};
   for (let i in renders)
      renders[i] = document.querySelector(renders[i]);
   const main = runtime.module(define, name => {
   if (renders[name])
      return new Inspector(renders[name]);
   else return true;
   })

Thanks @jashkenas . This is helpful. I don’t suppose your notebook on embedding is confusing; rather - I remain confused about Javascript fundamentals (in this case, not intuiting that ‘else if’ would render both statements, rather than just stopping at the first truthy statement).

To close out this thread for others such as myself who are new and don’t immediately see such things, a minor revision to your solution: the addition of document.querySelector, without which the code you supplied will continue to return console errors.

Here’s the final code:

new Runtime().module(notebook, name => {
  if (name === "title") {
    return new Inspector(document.querySelector("#title"));
  }
  else if (name === "map") {
    return new Inspector(document.querySelector("#map"));
  }
});

-or-

function renderNotebook(notebook, cellNames) {
  new Runtime().module(notebook, name => {
    if (cellNames.includes(name)) {
      return new Inspector(document.querySelector(`#${name}`));
    }
  });
}

renderNotebook(notebook, ["title", "map"])

You’re right! I meant Inspector.into() instead of new Inspector(). Sorry!

The key here is that the callback is being called multiple times: once for every named cell defined in your notebook. You can add a console.log(name) to observe this happening. The if ... else .. if does indeed stop at the first truthy statement.

1 Like