Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Download CSV file using "AJAX"

Tags:

ajax

php

csv

I'm trying to accomplish a fairly simple task for my website, but I"m not sure exactly how to go about it. I want the user to be viewing a table, then click a button, at which point the user can save the contents of that table as a csv file. This request can sometimes be quite complicated so I generate a progress page to alert the user.

I have most things figured out except actually generating the csv file. (I use jQuery and PHP)

the jQuery code run on click:

hmis_query_csv_export: function(query_name) {
    $.uiLock('<p>Query Loading.</p><img src="/images/loading.gif" />')
    $.get({
        url: '/php_scripts/utils/csv_export.php',
        data: {query_name: query_name},
        success: function(data) {
            $.uiUnlock();
        }
    });}

the relevant PHP:

header("Content-type: text/x-csv");
header("Content-Disposition: attachment; filename=search_results.csv");
//
//Generate csv
//
echo $csvOutput
exit();

What this does is sends the text as the PHP file, but it's doesn't generate a download. What am I doing wrong?

like image 358
The_Denominater Avatar asked Jul 27 '10 17:07

The_Denominater


4 Answers

If you are forcing a download, you can redirect the current page to the download link. Since the link will generate a download dialog, the current page (and its state) will be kept in place.

Basic approach:

$('a#query_name').click(function(){
    $('#wait-animation').show();
    document.location.href = '/php_scripts/utils/csv_export.php?query_name='+query_name;
    $('#wait-animation').hide();
});

More complicated:

$('a#query_name').click(function(){
    MyTimestamp = new Date().getTime(); // Meant to be global var
    $('#wait-animation').show();
    $.get('/php_scripts/utils/csv_export.php','timestamp='+MyTimestamp+'&query_name='query_name,function(){
        document.location.href = '/php_scripts/utils/csv_export.php?timestamp='+MyTimestamp+'&query_name='+query_name;
        $('#wait-animation').hide();
    });
});

At PHP script:

@header("Last-Modified: " . @gmdate("D, d M Y H:i:s",$_GET['timestamp']) . " GMT");
@header("Content-type: text/x-csv");
// If the file is NOT requested via AJAX, force-download
if(!isset($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest') {
    header("Content-Disposition: attachment; filename=search_results.csv");
}
//
//Generate csv
//
echo $csvOutput
exit();

The URL for both requests must be the same to trick the browser not to start a new download at document.location.href, but to save the copy at the cache. I'm not totally sure about it, but seems pretty promising.

like image 173
Ast Derek Avatar answered Oct 05 '22 18:10

Ast Derek


EDIT I just tried this with a 10MB file and it seems that val() is too slow to insert the data. Hurrumph.


Okay, so I gave this one another go. This may or may not be completely insane! The idea is to make an AJAX request to create the data, then use the callback to insert the data into a hidden form on the current page which has an action of a third "download" page; after the insertion, the form is automatically submitted, the download page sends headers and echoes the POST, and et voila, download.

All the while, on the original page you've got an indication that the file is being prepared, and when it finishes the indicator is updated.

NOTE: this test code isn't tested extensively, and has no real security checks (or any at all) put in place. I tested it with a 1.5MB CSV file I had laying about and it was reasonably snappy.

Index.html

<a id="downloadlink" href="#">Click Me</a>
<div id="wait"></div>
<form id="hiddenform" method="POST" action="download.php">
    <input type="hidden" id="filedata" name="data" value="">
</form>

test.js

$(document).ready(function(){
  $("#downloadlink").click(function(){       // click the link to download
      lock();                                // start indicator
      $.get("create.php",function(filedata){ // AJAX call returns with CSV file data
          $("#filedata").val(filedata);      // insert into the hidden form
          unlock();                          // update indicator
          $("#hiddenform").submit();         // submit the form data to the download page
      });
  });

  function lock(){
      $("#wait").text("Creating File...");
  }

  function unlock(){
      $("#wait").text("Done");
  }
});

create.php

<?php
//create $data
print $data;
?>

download.php

<?php
header("Pragma: public");
header("Expires: 0"); 
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Content-Type: text/x-csv");
header("Content-Disposition: attachment;filename=\"search_results.csv\""); 

if($_POST['data']){
    print $_POST['data'];
}
?>
like image 32
godheadatomic Avatar answered Oct 05 '22 19:10

godheadatomic


The best way to accomplish this is to use a Data URI as follows:

  1. Make the AJAX call to the server as per normal
  2. Generate the CSV on the server-side
  3. Return the data (either bare or inside a JSON structure)
  4. Create a Data URI in Javascript using the returned data
  5. Set window.location.href to the Data URI

See this link for instructions (paragraph #3, specifically): http://en.wikipedia.org/wiki/Data_URI_scheme

This way, you don't need to save any files on the server, and you also don't need to use iframes or hidden form elements or any such hacks.

like image 22
Aquarelle Avatar answered Oct 05 '22 18:10

Aquarelle


I don't think you can make the browser download using a AJAX/JS request. Try using a hidden iframe that navigates to the page which generates the CSV

like image 31
Bob Fincheimer Avatar answered Oct 05 '22 18:10

Bob Fincheimer