Initial value for Inputs.file
Feature Request:
It would be great to set a default file (e.g. a FileAttachment) like:
viewof csvfile = Inputs.file({..., value: FileAttachment("defaultData.csv")})
PoC:
function defaultFileInput({value, ...options} = {}) {
const toFile = async attachment => new File(
[await attachment.blob()],
attachment.name,
{type: attachment.mimeType}
);
const setValue = async (input, value) => {
const files = await Promise.all(
(input.multiple ? Array.from(value) : [value])
.map(f => f instanceof FileAttachment ? toFile(f) : f)
);
const transfer = new DataTransfer();
for(const f of files) transfer.items.add(f);
input.files = transfer.files;
input.dispatchEvent(new Event('input', {bubbles: true}));
};
const input = Inputs.file(options);
if(value != null) setValue(input.querySelector('input[type="file"]'), value);
return input;
}
viewof file = defaultFileInput({value: FileAttachment("example.csv")})
viewof files = defaultFileInput({value: multiple: true, [
FileAttachment("example.csv"),
FileAttachment("example.json"),
]})
Would love to see this included 👏🏽
A notebook with @mootari’s PoC: https://observablehq.com/d/2babcaffa59f03c1
The challenge here seems to be that we can’t construct a FileList from a FileAttachment synchronously because FileList requires a File which requires a Blob which requires loading the FileAttachment which is asynchronous. I don’t think we want to set the value asynchronously as in the above PoC, since in extreme cases that could override the value set by the user. I suppose it might be okay to check if input.files has been set before setting it, and if so ignore the initial value, since if the input’s value is undefined, then downstream cells won’t have run anyway.
There’s the same problem with setting the value of this input programmatically:
https://github.com/observablehq/inputs/blob/3281a4acf1e948284deeeacd179cd7f911e56649/src/file.js#L55
There’s no way to do this synchronously using the FileList API.
And also it’s undesirable to load the contents of the file attachment unless you’re actually using it…
So, I think maybe what we do is we create a “fake” File in this case, with empty contents and the matching name. But the exposed value of the file input is still the given array of values. That way we can trick the native file input into displaying the correct state, but the exposed value of the input is the expected array of files. The trick is revealed if someone peeks at input.file.files directly, but people should be using at input.value instead (via viewof).
Not sure how applicable it is here, but in other cases I've used thenables as lazy promises that only evaluate when they are being resolved.
I suppose it might be okay to check if input.files has been set before setting it, and if so ignore the initial value
Yes, a dirty flag should suffice I think? But I do agree that the async nature is rather ugly (which is also one of the reasons I didn't turn this into a PR yet).
And also it’s undesirable to load the contents of the file attachment unless you’re actually using it…
This sounds like an edge case. If an author presets the value of Inputs.file() it's probably safe to assume that the files are referenced downstream.
This sounds like an edge case. If an author presets the value of Inputs.file() it's probably safe to assume that the files are referenced downstream.
Disagree. For example, you could use Input.file to select multiple files, and then choose between them in another Input.select. Moreover, I think it’s feasible to solve both problems at once using the technique I described, so I see little reason to choose the slower solution.