tags:

views:

270

answers:

6

Hi, 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?

A: 

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

Bob Fincheimer
A: 

Well the point of using AJAX is to avoid a visible reload of the page. If you want a download, you want the opposite,- a brand new request from the browser. I'd say, just create a simple button pointing to your php page.

Nicolas78
Ok, if I do that how would I integrate a loading animation and a UI lock into it? Wouldn't that be a reason to use AJAX? Also, I'm pretty new to this and I'm not sure the best way to do that while integrating the GET parameter into the page request.
The_Denominater
@The_Denominator use an iframe
+2  A: 

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.

Ast Derek
Thanks a lot for the tips. I can get this to work very easily, but I'm wondering how I could use this while still implementing a screen lock and progress animation. I'm just worried that users might get confused since depending on the query this process can take up to 3 or 4 minutes.
The_Denominater
If it's going to take that long to create the CSV file, you may need to change directions slightly and instead use AJAX to kick off a PHP script that creates a temporary disk file; once the PHP script returns (with a filename) the AJAX callback function can do the redirect as Ast Derek shows.
godheadatomic
So your edit seems like it should work, but it seems like the statements are being run asynchronously. The third statement doesn't wait for the download to finish, so the wait screen appears then disappears instantaneously. Is there any way to force it to run synchronously?
The_Denominater
@godheadatomic I have artificial limitations in regards to that. The information I am relaying is confidential, and I'm not allowed to save it, even temporarily, on the web server(which is separate from the database). That limitation has caused me many problems so far..
The_Denominater
The progress dialog is tricky. I had a similar problem here: http://stackoverflow.com/questions/1106377/detect-when-browser-receives-file-download but never really solved it.
JW
The 'wait' animation/dialog is meant for the user to wait for the download to start, not for the download to finish. You won't be able to say if the download is finished or not, wihtout a more complicated approach
Ast Derek
Thanks for all the help, at this point I might just use the simple solution without progress notification, I'll just have to adequately warn the users in advance that the wait time is long.
The_Denominater
A: 

To echo and expand on what others have said, you can't really send the file using AJAX. One of the reasons for this is (and someone correct me if I'm wrong on this, please) that the page you're currently on already has sent its content headers; you can't send them again to the same window, even with an AJAX request (which is what your PHP file is attempting to do).

What I've done before in projects is to simply provide a link (with target="_blank" or javascript redirect) to a separate download PHP page. If you're using Apache, check out mod_xsendfile as well.

godheadatomic
A: 

This seems pretty easy from here, as I'm no php expert:

http://www.sebastiansulinski.co.uk/tutorials/show_item/53/download_file_with_php

Also it adds to a well validated download.

loxxy
He is asking how to show a wait message while the download is in progress
Ast Derek
my bad....should never have tried to help someone in something where I have least knowledge of :p....sry again.
loxxy
A: 

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'];
}
?>
godheadatomic