Embedding all cells but one in the right order

I’d like to embed every cell in one of my notebooks but one in my blog. Based on the guidance here, I thought I could try something like the code below. It does work, but it made all my cells to be rendered out of order. :frowning:

Runtime.load(notebook, (cell) => {
  if (cell.name === "excludeThis") {
    return {
      fulfilled: (value) => {
        document.getElementById("chart").appendChild(value);
      }
    };
  }
});

I did find a couple of solutions, but I just wondered if there was a better way of tackling this. Both of my solutions involved modifying notebook before I pass it on to Runtime.load.

1st solution: In my case, the cell I wanted to get rid of was the first cell. So, simply:

// Remove the title
notebook.modules[0].variables.shift()

2nd solution: If I know what the name of the cell I want to exclude is, then I can filter the array for everything but that cell.

// Code to remove a cell with a certain name
var variables = notebook.modules[0].variables
var filtered = variables.filter((object, index, arr) => {
    return object.name != 'title';
});
notebook.modules[0].variables = filtered

I think your first code snippet has a typo (presumably you actually have:

  if (cell.name !== "excludeThis") {

instead.)

In the first code snippet, the elements are appended as children of the chart element when the cells become “fulfilled” – not in the order that they occur in the notebook JS file. This means that the ordering of the cells can even change if cells are executed multiple times! For that reason, that code should probably only be used for displaying a single cell, as in its original form.

(That code also won’t properly display cells whose output is a value rather than an HTML element.)

For displaying multiple cells it’s probably best to use something like Inspector.into, as I think you might be in your two solutions (?). Something that could go wrong with those solutions though is that you are actually removing those cells from the notebook entirely, so cells in your notebook that depend on them will break.

Here’s some import code which uses a modification of Inspector.into that allows you to filter cells from being displayed while still letting them execute in the runtime:

<script type="module">

import {Runtime, Inspector} from "https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js";
import notebook from "./YOUR-NOTEBOOK.js";

// modified from https://github.com/observablehq/inspector/blob/master/src/index.js#L51
const intoFilter = function(container, filterFunc) {
  if (typeof container === "string") {
    container = document.querySelector(container);
    if (container == null) throw new Error("container not found");
  }
  return function(variable) {
    if (filterFunc(variable.name))
      return new Inspector(container.appendChild(document.createElement("div")));
  };
};

// you can enter an arbitrary filtering function as the second argument to intoFilter.
// it should take the variable name (a string) as an input and return a boolean
Runtime.load(notebook, intoFilter(document.body, (name) => name !== "excludeThis"));

</script>
1 Like

Yep - that was a typo. Sorry!

Thank you for this solution :):slightly_smiling_face: This makes sense!

@bgchen I have a question though. I had looked at that exact same code to try and do something similar to what you did.

How did you know that the function would get the variable as an input?

return function(variable) {

Good question! The relevant code that calls that function in Runtime.load is this line.

 else if (module === main) module.variable(observer(v, i, m.variables)).define(v.name, v.inputs, v.value);

Here, observer is the function returned by intoFilter (or Inspector.into) and you can see that it’s passed v (the variable), i (an integer giving its order in the module) and m.variables (the array of variables).

As an aside: I suspect that Runtime.load and the v1 runtime modules currently served by Observable’s API will be deprecated at some point (Runtime.load was removed from the official docs a few months ago…). In the v3 runtime modules (try appending ?v=3 to a notebook’s JS file URL), it appears that the observer function is passed the cell name as a string. This is incompatible with the behavior I’m using above (though intoFilter is not hard to fix).

1 Like

Ah interesting. Thank you for explaining! :pray:

Just trying this in a new simple html page to test embedding, attempting to embed just the two cells I want - the main visualization, and a dropdown (select) control.

it seems I should be able to use the above code to achieve this, but the page throws an error ‘r is not iterable’.

32

<html>
        

        <script type="module">

            import {Runtime, Inspector} from "https://cdn.jsdelivr.net/npm/@observablehq/runtime@4/dist/runtime.js";
            import notebook from "https://api.observablehq.com/@kchalas/calendar-heatmap-too.js?v=3";

            // modified from https://github.com/observablehq/inspector/blob/master/src/index.js#L51
            const intoFilter = function(container, filterFunc) {
                if (typeof container === "string") {
                container = document.querySelector(container);
                if (container == null) throw new Error("container not found");
                }
                return function(variable) {
                if (filterFunc(variable.name))
                    return new Inspector(container.appendChild(document.createElement("div")));
                };
            };
            
            // you can enter an arbitrary filtering function as the second argument to intoFilter.
            // it should take the variable name (a string) as an input and return a boolean
            Runtime.load(notebook, intoFilter(document.body, (name) => {
                console.log(name);
                name !== "excludeThis"
            }));
            
        </script>

</html>

I think the recommended way is now https://observablehq.com/@observablehq/downloading-and-embedding-notebooks.

thanks, you’re right and i did try that option first. i will open a different topic on the issues i am facing with that approach. thanks