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?
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.
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'];
}
?>
The best way to accomplish this is to use a Data URI as follows:
Make the AJAX call to the server as per normal
Generate the CSV on the server-side
Return the data (either bare or inside a JSON structure)
Create a Data URI in Javascript using the returned data
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.
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
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.
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.
Related
Ive got a php function that packages all files of a type in a directory into a zip file and downloads it.
Ive set the index.php to shows me all the files in the directory so i can download individual files as needed.
What ive done is put the zip script into a function called 'create_zip'. What i want to be able to do is call the 'create_zip' on click of an <a> tag.
How can i do this ? Becuase if i include the function it will just run as soon as i load the page..
the html im using for the tag is really simple like this
Download all files
You would need to do this via using ajax, i.e
First create a file, which you can call once the link has been clicked, for this example, well call it download.php
Then in the file add a call to your function....
<?
// execute function to get files
$zip_file = create_zip();
// get the full path to zip file
$full_zip_path = '/path/to/zip/file'.$zip_file;
// You may need to do more here depending on how far your create zip function goes
// if you want it to force the download you can then do this
header("Pragma: public"); // required
header("Expires: 0");
header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
header("Cache-Control: private",false); // required for certain browsers
header("Content-Type: octet/stream");
header("Content-Disposition: attachment; filename=".$zip_file.";" );
header("Content-Transfer-Encoding: binary");
header("Content-Length: ".filesize($full_zip_path));
readfile($full_zip_path);
exit();
?>
Then add a class to your a tag, as such...
Download all files
Then what you want is some javascript to call load the php file in the background, basically add this somewhere in your footer/header (depending on where your scripts are).
<script>
$(function() {
$("a.download-zip").click(function() {
window.location.href = "/path/to/php/file/download.php";
return false;
});
});
</script>
That will then load the file in the background, which will in turn force a download of the created zip file. Some of the above may be slightly out due to not seeing how far your create_zip function goes, but it should set you on the right path.
That's very simple:
Download all files
then just add you zip function to myphpfile.php
myphpfile.php
<?php create_zip(); ?>
Just found my script
So you create a hidden iframe
<iframe id="upload_target" name="upload_target" style="display: none;" src="#"></iframe>
Then you create a form with some button
<form action="path/to/downloadzip.php" method="POST" enctype="multipart/form-data" target="upload_target"> //target will tell the browser to run it in the iFrame with name="upload_target"
then in downloadzip.php:
<?php create_zip(); ?> //and output the file here
This will download the zip without having to load a new page
You need to call it like an url:
Download all files
And then it'll do the job for you.
Problem: it'll open a new page, the user will have to return to the previous one manually.
You might want to include the code in the actual page and, call the link with a parameter:
Download all files
On your function you can check the parameter (something close to this):
if ($['GET'] == "download-all") {
download all
}
An AJAX call, as Steve suggested is also a good idea, though I'm not sure on how to handle the incoming zip file.
You need to send the request back to the server in order to do this.
For example, you would use a link that pointed to a PHP page on your server that would run the function and give you the result:
Download all files
You could also submit this in the background using and AJAX request, but this is an optional extra.
better way you can use ajax post
You can try calling ajax on click on hyperlink (give it identity like class).
$('.classHyper').click(function() {
//do ajax post
});
This question already has an answer here:
Closed 10 years ago.
Possible Duplicate:
Downloading Via JQuery AJAX Post not working
filedownload.php has below snippent.
$file = 'cut.png';
header("Content-Type: image/png");
header('Content-Disposition: attachment; filename="'.$file.'"');
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
readfile($file);
exit();
AJAX Call
jQuery.post('filedownload.php',{
'file' : result // not used for the time being
});
I make an ajax call to the filedownload.php file. It does not allow user to download the file. But If I run the php directly it allows user to download the file. What could be the issue ?
I would like to use core functions rather than using jQuery Plugins. If it's not possible a plugin would be fine.
Given that I use ajax because the page can not be refreshed.
The Problem
Let's take the example of a productivity web app such as a spreadsheet
editor, which has the ability to open, save, import and export. The
open and save options would involve loading a spreadsheet from the
database, whereas import and export deal with local files on the
user's machine. To implement the export behavior, you might decide
that the user should have to save their spreadsheet first, allowing
you to export the data from the backend to file. But let's assume
instead you'd like to allow users to export their data without saving,
perhaps to afford them the option of working locally without ever
storing data on the server. In order to do this, you'd need to send
the current spreadsheet data to the backend and receive a file to
download. Unfortunately, this can not be handled using Ajax, since
Ajax can only receive responses in the form of text. In cases where
the data to be saved is rather lengthy, this poses a considerable
problem.
The Workaround
In order to make the request, you'd need to make a regular (not Ajax)
HTTP request using GET or POST. If the data is reasonably short, you
might get away with a GET request (perhaps by simply setting
Window.location to your export url), but due to varying browser
limitations on GET request length, a POST will most likely be needed.
The following plugin allows you to make a request that returns a file
in a similar syntax to jQuery's native Ajax functions.
jQuery Code Which fixes the problem
jQuery.download = function(url, data, method){
//url and data options required
if( url && data ){
//data can be string of parameters or array/object
data = typeof data == 'string' ? data : jQuery.param(data);
//split params into form inputs
var inputs = '';
jQuery.each(data.split('&'), function(){
var pair = this.split('=');
inputs+='<input type="hidden" name="'+ pair[0] +'" value="'+ pair[1] +'" />';
});
//send request
jQuery('<form action="'+ url +'" method="'+ (method||'post') +'">'+inputs+'</form>')
.appendTo('body').submit().remove();
};
};
How to call
$.download('filedownload.php','filename='+filename );
Read more
I have a php script that gets called via an ajax call. Values are sent to this script to build a pdf. I want to send the pdf to the browser, but since the script that builds the pdf returns to the page with the javascript I can't see how to do this. Any ideas?
I would recommend something a bit different. Instead of AJAX call make a redirect to an URL like this:
./path_to_pdf_script/script.php?param1=val1¶m2=val2
This script would be the one which generated the pdf. Place somewhere on top of the script this header:
header('Content-type: application/pdf');
And simply echo the string the pdf content is in. If you want the user to download this pdf instead of viewing you could do the AJAX call with the example found HERE:
from php.net
If you want the user to be prompted to save the data you are sending,
such as a generated PDF file, you can use the ยป Content-Disposition
header to supply a recommended filename and force the browser to
display the save dialog.
<?php
// We'll be outputting a PDF
header('Content-type: application/pdf');
// It will be called downloaded.pdf
header('Content-Disposition: attachment; filename="downloaded.pdf"');
// The PDF source is in original.pdf
readfile('original.pdf');
?>
You could use an iframe instead of an ajax request and force-download the pdf file.
As you noticed, your AJAX call can't directly output the PDF to the browser. One workaround is to remove AJAX and send the user directly to the page that generates the PDF. This approach is very common and well documented. But there is a way to use AJAX to generate the PDF, so that the user will stay on the web page until the file is ready.
Your AJAX call could answer with a JSON object with 2 exclusive fields:
"pdfurl" if the pdf file was successfully created and written to the disk,
"errormsg" if there was an error.
Something like (in PHP):
<?php
//...
if (writepdf($filename, ...)) {
$result = array('pdfurl' => '/files/' . $filename);
} else {
$result = array('errormsg' => 'Error!');
}
echo json_encode($result);
Then the page's javascript could contain (jQuery example):
$.ajax({
type: "GET",
url: "ajaxcreatepdf.php",
data: {userid: 1},
dataType: "json",
success: function(data, textStatus) {
if (data.pdfurl) {
window.location.href = data.pdfurl;
}
else {
$("#messagebox").html(data.errormsg);
}
}
});
The Ajax request is not direct visible to the user, so a redirect make no sense
You need to load this PDF into an existing or new browser window after the ajax has returned.
I've got a large form where the user is allowed to input many different fields, and when they're done I need to send the contents of the form to the server, process it, and then spit out a .txt file containing the results of the processing for them to download. Now, I'm all set except for the download part. Setting the headers on the response to the jQuery .post() doesn't seem to work. Is there any other way than doing some sort of iframe trick to make this work (a la JavaScript/jQuery to download file via POST with JSON data)?
Again, I'm sending data to the server, processing it, and then would like to just echo out the result with headers to prompt a download dialog. I don't want to write the result to disk, offer that for download, and then delete the file from the server.
Don't use AJAX. There is no cross-browser way to force the browser to show a save-as dialog in JavaScript for some arbitrary blob of data received from the server via AJAX. If you want the browser to interpret the results of a HTTP POST request (in this case, offering a download dialog) then don't issue the request via AJAX.
If you need to perform some kind of validation via AJAX, you'll have to do a two step process where your validation occurs via AJAX, and then the download is started by redirecting the browser to the URL where the .txt file can be found.
Found this thread while struggling with similar issue. Here's the workaround I ended up using:
$.post('genFile.php', {data : data}, function(url) {
$("body").append("<iframe src='download.php?url="+url+"' style='display: none;'></iframe>");
});
genFile.php creates the file in staging location using a randomly generated string for filename.
download.php reads the generated file, sets the MIME type and disposition (allowing to prompt using a predefined name instead of the random string in the actual filename), returns the file content and cleans up by deleting the source file.
[edit] might as well share the PHP code...
download.php:
<?php
$fname = "/tmp/".$_GET['url'];
header('Content-Type: text/xml');
header('Content-Disposition: attachment; filename="plan.xml"');
echo file_get_contents($fname);
unlink ($fname);
?>
genFile.php:
<?php
$length = 12;
$chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
$str = substr( str_shuffle( $chars ), 0, $length ).'.xml';
$fh = fopen(('tmp/'.$str), 'w') or die("can't open file");
fwrite($fh,$_POST["data"]);
fclose($fh);
echo $str;
?>
Rather than using jQuery's .post(), you should just do a normal POST by submitting the form, and have the server respond with appropriate Content-Encoding and MIME-type headers. You can't trigger a download through post() because jQuery encapsulates the returned data.
One thing I see in use rather frequently, though, is this:
$.post('generateFile.php', function(data) {
// generateFile builds data and stores it in a
// temporary location on the server, and returns
// the URL to the requester.
// For example, http://mysite.com/getFile.php?id=12345
// Open a new window to the returned URL which
// should prompt a download, assuming the server
// is sending the correct headers:
window.open(data);
});
I'm trying to export some tables generated with PHP from MySQL data. I'm trying to send the info via AJAX to a file with this code:
<?php
header("Content-type: application/vnd.ms-excel; name='excel'");
header("Content-Disposition: filename=excel.xls");
header("Pragma: no-cache");
header("Expires: 0");
echo $_POST['table'];
?>
The data comes from this function
function export_excel (id_table) {
var table = $("#" + id_table).html();
$.ajax({
type: 'POST',
url: 'toexcel.php',
data: 'table='+table
});
}
Through Firebug I can see that the table is echoed correctly but it doesn't start any download. Which could be the problem?
It is not possible to start a file download as a response to an Ajax request. You have to send the browser to fetch the resource like if you were navigating to a page.
If you need to use the POST method, I think the ideal way to do this would be:
Have a real <form> element into which you write the POST data
Have an invisible or small iframe. Give it a name
Give the form the iframe name as the target property
submit() the form
if all headers are set correctly (You may have to add some more, like Content-disposition: attachment), this should trigger a file download without affecting your current page.
If you can use GET, a simple
location.href="toexcel.php?param1=value1¶m2=value2"
should do already.