Create a "Save as..." button

This seems like it should be a standard function but I’m at a loss…

I have an app that generates data, and I want to give the user the option to save the data to a file. The FileInput widget has the basic functionality that I’d like – pops up a directory, lets the user change directory and select a file – except it enforces choosing an existing file rather than providing a new name.

Is there a simple way to do this? I looked at the FileSelector widget but it’s way too fancy for this function and also wants to enforce chosing files rather than creating a new one.

Example code:

import panel as pn

file_input = pn.widgets.FileInput()

pn.Row(file_input).servable()

Does this and pops up a Windows file selector when clicked:
no_file_chosen

But what I want is this:
save_as...

Are you looking for FileDownload — Panel v1.7.1?

Thanks, no, that downloads a file. I’m looking for something where you can choose a directory and save data to a new file, like many applications. Example:

A quick Google search shows that this only works in Chromium-based browsers [ref].

I don’t think we have any support for this in Panel. I asked ChatGPT to make a JSComponent

import param
from panel.custom import JSComponent

class SaveTextFile(JSComponent):
    text = param.String(default="Hello, world!", doc="Text content to save")
    filename = param.String(default="document.txt", doc="Suggested filename")

    _esm = """
    export function render({ model }) {
        const textarea = document.createElement("textarea");
        textarea.value = model.text;
        const button = document.createElement("button");
        button.innerHTML = "Save Text File";
        button.onclick = async () => {
            const handle = await window.showSaveFilePicker({
                suggestedName: model.filename,
                types: [{ description: 'Text Files', accept: { 'text/plain': ['.txt'] } }]
            });
            const writable = await handle.createWritable();
            await writable.write(textarea.value);
            await writable.close();
        };
        const container = document.createElement("div");
        container.appendChild(textarea);
        container.appendChild(button);
        return container;
    }
    """

SaveTextFile().servable()
1 Like

Cool! Yes, that’s the functionality I’m looking for.

I’m not used to JS code, so it will take me a while to understand everything there, but good enough.

Thanks.