Hello there
I’d like to use select
from @jashkenas/inputs
to switch between 2 images:
viewof selection = select({
description: "Select image",
options: ["image1.png", "image2.png"],
value: "image1.png"
})
then use this to display an image:
FileAttachment(selection).image()
// also tried String casting:
// FileAttachment(`${selection}`).image()
But I get the following error:
SyntaxError: FileAttachment() requires a single literal string as its argument.
So, is it that FileAttachment
do not like reactive argument ?
And what alternative could I use ?
I created a notebook for this issue here: Observable
Thanks
Here’s the best I can do – at least it works [I hope…]
2 Likes
Yep, Observable prevents dynamic arguments in FileAttachments:
// Forbid all sorts of dynamic uses of FileAttachment
if (
!(
args.length === 1 &&
((args[0].type === "Literal" && /^['"]/.test(args[0].raw)) ||
(args[0].type === "TemplateLiteral" &&
args[0].expressions.length === 0))
)
) {
throw Object.assign(
new SyntaxError(
`FileAttachment() requires a single literal string as its argument.`
),
{node}
);
}
You’ll have to create an index of your attachments (either in a separate cell or within your widget):
files = [
FileAttachment("image1.png"),
FileAttachment("image2.png"),
].reduce((o, f) => (o[f.name] = f, o), {})
… then fetch the keys:
options: Object.keys(files),
… and finally use:
files[selection].image()
4 Likes
I’ve came across this problem once. As noted by @mootari , if you read on the doc about FileAttachment you can find this paragraph
References to files are parsed statically. We use static analysis to determine which files a notebook uses so that we can automatically publish referenced files when a notebook is published (and only referenced files), and similarly copy only referenced files when forking a notebook. The FileAttachment function thus accepts only literal strings; code such as
FileAttachment("my" + "file.csv")
or similar dynamic invocation is invalid syntax. For details on how this is implemented, see our parser .
If you want the selection to come from the same cell you could try this pattern
viewof selection = {
const el = DOM.element("div");
const selection = select({
description: "Select image",
options: [
{
label: "image1.png",
value: await FileAttachment("image1.png").url()
},
{
label: "image2.png",
value: await FileAttachment("image2.png").url()
}
],
value: "image1.png"
});
el.appendChild(selection);
const loadImage = url =>
new Promise(resolve => {
const image = new Image();
image.onload = () => {
el.value = image;
el.dispatchEvent(new CustomEvent("update"));
resolve(image);
};
image.src = url;
});
selection.onchange = evt => loadImage(evt.target.value); // load onselection change, passing url
el.value = await loadImage(selection.input.value); // load initial value
return el;
}
2 Likes
Here’s a compact example which will return the actual FileAttachment instance:
viewof selection = {
const files = new Map([
FileAttachment("image1.png"),
FileAttachment("image2.png")
].map(f => [f.name, f]));
const form = select({
description: "Select image",
options: Array.from(files.keys()),
value: "image2.png"
});
return Object.defineProperty(html`<div>${form}`, 'value', {get() { return files.get(form.value) }});
}
… then (e.g.):
selection.image()
7 Likes
Wow
Thanks @aaronkyle @mootari @radames for your quick and really insightful answers !
1 Like
ohhh this is so much better! using FileAttachment instance great idea!
Steve
June 4, 2020, 8:11pm
8
Great stuff !
Do you think we can get the Content-Length from headers associated with the FileAttachment response?
There is no direct method , but you can fetch the size by other means:
via .url()
:+(await fetch(
await FileAttachment('image.png').url(), {method: 'head'})
).headers.get('Content-Length')
Note: You can also access the URL directly: +(await fetch(FileAttachment('image.png')._url, {method: 'head'}))
.headers.get('Content-Length')
via .blob()
:(await FileAttachment('image.png').blob()).size
via .arrayBuffer()
:(await FileAttachment('image.png').arrayBuffer()).byteLength
4 Likes
I could be doing this wrong, but I am getting a runtime error: “select is not defined”
mootari
September 30, 2020, 5:43pm
12
You need to import the select
cell from Jeremy Ashkenas’ omnipresent input widgets into your notebook:
import {select} from '@jashkenas/inputs'