mixing search + table in plain HTML

Hi Community.

I managed to cobble together a combination of search + table inputs using standard HTML (no notebook; no framework). It’s pretty concise, but I wonder whether I could do better to correctly leverage the Observable runtime and to avoid having to dispose of and re-render my table with event listeners. Any suggestions?

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ObservableHQ Table with Search</title>
    <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@observablehq/inputs@0.12/dist/index.css">
    <script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>

    <h2>Observable Search + Table</h2>
    <div id="search-container"></div> 
    <div id="table-container"></div>

    <script type="module">
        import * as Inputs from "https://cdn.jsdelivr.net/npm/@observablehq/inputs@0.12/+esm";

        const csv_source = `Name,Age,City
        Alice,25,New York
        Bob,30,Los Angeles
        Charlie,22,Chicago
        David,35,Houston
        Eva,28,San Francisco`;

        let data = d3.csvParse(csv_source);

        const search = Inputs.search(data, { placeholder: "Search table..." });
        document.getElementById("search-container").append(search);

        let table = Inputs.table(search.value, { format: { Age: d3.format("d") } });
        document.getElementById("table-container").append(table);

        search.addEventListener("input", () => {
            const filtered_table = Inputs.table(search.value, { format: { Age: d3.format("d") } });
            document.getElementById("table-container").replaceChildren(filtered_table);
        });

    </script>

</body>
</html>

this is what Observable runtime does (destruction and recreation of a table) so I would not worry about it.

Thank you, Tom. I guess my challenge is that I haven’t figured out how to appropriately use the runtime in a vanilla HTML and JS setup?

Ahh ok. So if you want to do it with Observable Runtime, the best resources are

Inspect the embedded snippet you get when you export with javascript:-

The actual user code comes from the observable API, which is bundled as a UMD module format.

You can inspect this with curl

https://api.observablehq.com/@tomlarkworthy/module-selection.js?v=4

A lod of JS comes out of that but the important bit is the exported define, which is passed to the runtime:

export default function define(runtime, observer) {
  const main = runtime.module();
  main.define("module 1", async () => runtime.module((await import("/@tomlarkworthy/exporter.js?v=4&resolutions=de3abeb05c4b090e@351")).default));
  main.define("module 2", async () => runtime.module((await import("/@tomlarkworthy/import-notebook.js?v=4&resolutions=de3abeb05c4b090e@351")).default));
  main.define("module 3", async () => runtime.module((await import("/@tomlarkworthy/fileattachments.js?v=4&resolutions=de3abeb05c4b090e@351")).default));
  main.define("module 4", async () => runtime.module((await import("/@tomlarkworthy/lopepage-urls.js?v=4&resolutions=de3abeb05c4b090e@351")).default));
  main.define("module 5", async () => runtime.module((await import("/d/57d79353bac56631@44.js?v=4&resolutions=de3abeb05c4b090e@351")).default));
  main.define("module 6", async () => runtime.module((await import("/@tomlarkworthy/module-map.js?v=4&resolutions=de3abeb05c4b090e@351")).default));
  main.define("module 7", async () => runtime.module((await import("/d/e1c39d41e8e944b0@939.js?v=4&resolutions=de3abeb05c4b090e@351")).default));
  main.define("module 8", async () => runtime.module((await import("/@tomlarkworthy/runtime-sdk.js?v=4&resolutions=de3abeb05c4b090e@351")).default));
  main.variable(observer()).define(["md"], _1);
  main.variable(observer("viewof selected_modules")).define("viewof selected_modules", ["Inputs","modules","hashModules"], _selected_modules);
  main.variable(observer("selected_modules")).define("selected_modules", ["Generators", "viewof selected_modules"], (G, _) => G.input(_));
  main.variable(observer("viewof additional_module")).define("viewof additional_module", ["Inputs"], _additional_module);
  main.variable(observer("additional_module")).define("additional_module", ["Generators", "viewof additional_module"], (G, _) => G.input(_));
  main.define("exporter", ["module 1", "@variable"], (_, v) => v.import("exporter", _));
  main.variable(observer()).define(["exporter"], _5);

So you can kinda see how the define is really just scripting the process of setting up the runtime with variables and registering cell definitions. The real source of truth on what is possible is the runtime docs GitHub - observablehq/runtime: The reactive dataflow runtime that powers Observable Framework and Observable notebooks

You could do that manually in your HTML page or the easiest way is creating a notebook and calling the API. Note when using the runtime low level each cell has to announce its dependancies, the Observable website does this for you in a process of compilation. If its very simple you can do it by hand, but there are also third party tools to do it automatically.

If its just a search box connected to a table, then manually wiring the event emitter is the simplest approach with the least Javascript and highest overall performance.

1 Like

Thanks for helping me through this. So yeah, it sounds like if I am trying to use the runtime for table creation and disposal rather than writing my own functions for these in plain JS, I have to manually script the define function? I’ve seen ‘main.define’ in action in notebook exports, as you point out, but it wasn’t clear to me that how to implement in on my own.

The basic issue that I was trying to get around is that Observable notebook exports and generated Framework code can’t render without an HTTP server, whereas a plain HTML page that pulls in the inputs library from NPM will work. While I am working on a proper solution at my work where I can share generated pages from a cloud storage bucket (that hence will render), in the mean time I am trying to mock-up some tools (like a simple search and table) as a proof of concept. I’ve used your notebook exporter for some of this (thank you!), but I am disallowed from using the notebook platform for work.

… I’ll have a look through the docs you reference to learn how to write out the define function for the case I shared in raising this question. Thank you for your time and guidance! :smiling_face_with_heart_eyes:

1 Like

… While I haven’t managed to write this out myself, I’m marking Tom’s explanation as the solution. Will update this post if I ever manage to make it work in a simple HTML page…

I’m not sure how much of a help it’s going to be (if at all), but I had this notebook lying around unpublished:

1 Like

Nice, thank you! It’s always a informative to read through your work. :smiling_face_with_heart_eyes: … I’m hoping to get past this specific hurdle with the adoption of Framework in my organization. Still working through all the administrative checks…