Maybe use FileAttachment to load a template from a file and pass to the template compiler, and then use DomParser to turn that into a DOM Document and pass that to display() ?
Yeah I’ve tried this approach but it falls apart when I’m trying to do something more complex like a dynamic layout where the number of row depends on what is in the data. Think base object but then N number of child objects, and the markup has to change based on N.
The html literal template doesn’t work for dynamically generating that markup as well as the stuff between the tags.
It feels more and more like I need to dynamically construct a DOM document node by node…
Using Handlebars to construct a template-based system doesn’t work because I can’t dynamically construct a path to pass to FileAttachment, which I assume is because of the static site generation constraints.
So I have to create a component for every template.
Even when I do that, and I return a compiled template as a string, I try this:
The createContextualFragment() method should return a DocumentFragment so I’m confused by this now.
Even if I remove the display() wrapper it’s the same thing.
If just do
cardTemplate({ cardTitle: "Card Title" })
It will properly output a string that contains all my escaped markup as a string literal, but of course that’s not what I want. So the template seems to be properly generated as a string but parsing it into a DOM document fragment doesn’t work.
(Sticking “await” in front of the display call also doesn’t do anything)
So in the spirt of sharing, for anyone else looking to use something like Handlebars to manage templates in Observable Framework, here’s how I’ve netted out.
I have a components/templates.js file that looks like this:
import Handlebars from "npm:handlebars"
import { FileAttachment } from "npm:@observablehq/stdlib"
function renderTemplate(contents, data) {
const template = Handlebars.compile(contents)
return document.createRange().createContextualFragment(template(data)).firstChild;
}
export async function cardTemplate(data) {
const templateContents = await FileAttachment("../data/templates/app_card.hbs").text();
return renderTemplate(templateContents, data);
}
export async function boxTemplate(data) {
const templateContents = await FileAttachment("../data/templates/app_box.hbs").text();
return renderTemplate(templateContents, data);
}
// an exported method for each of your other templates ....
As mentioned earlier in the thread, because of how FileAttachment works you can’t dynamically build the path to the template file (it will throw an error about the method needing a Literal parameter), so you do need a function for each template you have, as far as I can tell.
The renderTemplate method then compiles and returns the Handlebar template as a string, which is then turned into a DOM fragement and returned.
Your templates can probably be anywhere but I put them in a folder under data.
The app_card.hbs template in this case is super simple: