11

Greetings,

I'm working on a JS-based application that does some complex work and logs some information (actually, up to hundreds of lines) on a <div>.

My goal is to have a "Save log" button that triggers the browser's download dialog to save the contents of my logging <div>.

More concisely, these are the requirements for this feature:

  • The final user must have full control over the file. S/he should be able to save/archive it for future reference, mail it to the support department to get help addressing some issue, load it back into the app, etc. So HTML5's Web Storage API doesn't help here (data would get saved to a browser-defined location and wouldn't be easily retrievable except from the JS that creates it).
  • The application must be able to work offline (at least under some circumstances). That, and efficiency, is why I discarded the idea of POSTing the data to the server to get it back with a "Content-disposition" header.
  • The file should be properly marked as text/plain, so the browser can suggest default actions (like "Open on notepad") just like with a normal file download. This can be seen as a specific aspect of the first requirement.
  • Telling the user to copy-paste the content of the <div> into a text editor and saving it from there is definitely horrendous, and is exactly why I'm trying to avoid.

I have been searching on this site, on the WHATWG and W3C sites and on the web in general with no success. Is this doable at all?

The closest I have got is by using a data: url. My first attempt, performing a POST action, couldn't get a content type through, so it'd fall-back to the UA's heuristics. I got it slightly better by styling a <a> link to look like a button and giving it a type attribute, but then the UA will play too smart and render the content instead of saving (and asking the user to save the file from the browser at that step becomes even worse than taking the copy-paste approach, since page saving varies wildly between browsers).

If there were just some way to combine a data: url with a "content-disposition"-like hint, things could go really smooth.

Regards, Herenvardo

Edurne Pascual
  • 5,560
  • 1
  • 26
  • 31
  • 1
    Is using a plugin like Flash out of the question? I don't know whether it's possible to do with standard HTML, but it would be pretty easy if Flash were an option... – Dean Harding Jul 28 '10 at 23:02
  • I had a similar question http://stackoverflow.com/questions/2832392/can-you-do-file-io-using-javascript-without-activex but only got one answer. I don't think it is possible though. – Fredrick Pennachi Jul 28 '10 at 23:26
  • Did the "copying to the clipboard with code and telling the user to paste it in notepad (or favorite text editor)" idea come up? Clipboard can handle megabytes, notepad could choke on a few megabytes though. On the other hand: see http://stackoverflow.com/questions/17836273/export-javascript-data-to-csv-file-without-server-interaction – TWiStErRob Feb 18 '14 at 09:35

4 Answers4

2

Unfortunately this is not something you can do with normal browser capabilities. Something like flash or a browser-specific plugin will get you what you need, but security limitations within javascript will not let you download arbitrary data created within the browser.

Also the 'data' url is not supported across all browser/version combinations. I am not sure if your users are constrained on what browser they are using or not but that may limit what you can do with that solution.

Mike Clark
  • 11,769
  • 6
  • 39
  • 43
  • Thanks! It seems that the plug-in (or Flash)- based approach is the only way all requirements may be met. On a side note, the support for 'data:' is not much of an issue: the app as a whole relies on a lot of HTML5 features, so there is already a heavy browser requirement, requiring support for 'data:' won't be that much of an issue. – Edurne Pascual Jul 30 '10 at 18:08
  • 1
    Also, Silverlight may be a viable option here. – Josh Stodola Jul 30 '10 at 18:27
2

This solution is slightly convoluted but would work for your needs, perhaps it's an option.

Create a custom web server that simply returns the contents of any GET or POST as text/plain.

Host that web server on the same box as the web application but running on a different port. When you click the Save log button, the request is sent to the custom server and the text file is returned.

Here's a proof of concept server in Java that will do just that:

// After running this program, access with your browser at 127.0.0.1:8080

import java.net.*;
import java.io.*;

public class ReturnTextFile
{

    public static void main(String[] args)
    {

    try
    {
        ServerSocket server = new ServerSocket(8080);
        Socket connection = null;

        while (true)
        {

        try
        {
            connection = server.accept();

            BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));

            String input = in.readLine();

            // TODO: Clean up input

            Writer out = new OutputStreamWriter(connection.getOutputStream());

            String output = "HTTP/1.1 200 OK\n" +
                    "Connection: close\n" +
                    "Content-Disposition: attachment; filename=log.txt\n" +
                    "Content-Type: text/plain; charset=utf-8\n" +
                    "\n";

            output += input;

            out.write(output);

            out.flush();

            connection.close();
        }
        catch (IOException ex)
        {
            ex.printStackTrace();
        }
        finally
        {
            if (connection != null)
            {
            connection.close();
            }
        }

        }

    }
    catch (IOException ex)
    {
        ex.printStackTrace();
    }

    }
}
Fredrick Pennachi
  • 832
  • 1
  • 8
  • 17
  • +1 for such an interesting approach. I'll definitely keep the idea of a local server in mind for future projects, but Mike's answer seems to solve the issue much more easily. – Edurne Pascual Jul 30 '10 at 18:10
2

You can set the content-type in the data: URL, Something like this:

data:text/plain,this is some text

However, that still has the problem of the browser automatically rendering it as text. You really have two options that i can see. One is that you set the type to some kind of binary type so that the browser doesn't try and render it, or that you have the type as text/plain and get the user to right-click and save as. Maybe something here can help?

crazy2be
  • 2,134
  • 1
  • 21
  • 17
1

I have been down this road, and I also wanted to do it purely on the client-side. But, it is impossible. The only way you can trigger the save dialog without any fuss is by making a HTTP POST request, and having the server respond with a content-disposition header.

I have your desired functionality in the code snippets on my dusty old blog. I have a form with one hidden field pointed to a custom HTTP handler. Javascript grabs the inner text of the code block, puts it in the hidden field, and submits the form. Server responds with the entire body of the request, along with the required headers. No refresh, you click it and you get a save dialog. Works great!

Josh Stodola
  • 81,538
  • 47
  • 180
  • 227
  • What about using an `http-equiv` header in junction with a `data:` URI to simulate the header? Would that work? – haxxerz Aug 02 '12 at 03:26