I have a JavaScript Pivot table that displays data to the user. The user can select the columns/rows etc. I've added an export to Excel button, that performs an ajax post with the current data/view of the pivot table (json data). It posts the data to my server that converts that to an Excel file.
Creating the file all works well, but my problem is getting the file to the user. From what I've read I can't send a file to a user after an ajax post.
I'm happy to use a plain html post (in fact it is what I want since I can redirect the user to the file), but I don't know how to include the pivot table data as part of the post since it is not a form.
I know I can save the file locally and send a url back but this complicates things and I would like to avoid it.
Is it possible to do this without saving the file locally and sending a url where the file is located?
Browsers don't (yet) support JSON serialization of forms, so AFAIK it's not really possible to send JSON to backend using pure forms.
I have two solutions that will not require saving file on the server:
1) Simple solution would be to generate an invisible form with JavaScript, create hidden input of name json and populate it with JSON content to send to the server. On the server side, you would read the form data and parse JSON that is stored in the data. Then you just generate the file and send the file in response. The browser should trigger download dialog.
var form = document.createElement('form');
form.method = 'post';
form.action = 'url';
var input = document.createElement('input');
input.type = 'hidden';
textarea.name = 'json';
textarea.value = JSON.stringify(your_json);
form.appendChild(input);
document.body.appendChild(form);
form.submit();
form.parentNode.removeChild(form);
2) Second option uses ajax to send data to the server. The browsers need to support several APIs, though.
You do your JSON request as usual and the server should respond with header Content-Type: here-the-MIME-type-of-your-file and with the file contents in response body.
The code on client side should look like:
var json = JSON.stringify({here: ['your', 'json', 'to', 'send', 'to', 'server']});
var xhr = new XMLHttpRequest();
xhr.open('POST', '/your/api/url', true);
xhr.setRequestHeader('Content-type', 'application/json');
xhr.responseType = 'arraybuffer';
xhr.addEventListener('load', function () {
var blob = new Blob([this.response], { // reading response, not responseText
type: this.getResponseHeader('Content-Type')
});
var url = URL.createObjectURL(blob);
var a = document.createElement('a');
a.href = url;
a.download = 'file_name.txt'; // set proper extension
document.body.appendChild(a); // it needs to be added to the document in order to work
a.click();
a.parentNode.removeChild(a);
});
xhr.send(json);
Also, the created URL objects should be revoked at some point, otherwise they will exist until the page reloads. But I think they cannot be revoked before the user downloads the file. So it's one task still to do with the above code.
Related
Having an HTML Form that is submittet via POST (user clicking the
submit button).
Furthermore having an image that is read via Javascript of a canvas
object (getImageData()).
Question:
How to "inject" this image data into the HTML Form, so that it gets uploaded as Content-Type:multipart/form-data and can be processed via the existing PHP Frameworks Data Extraction Logic?
Example from a <input type="file" upload captured with CHrome in a POST request => it should look like this
------WebKitFormBoundaryBBAQ5B4Ax1NgxFmD
Content-Disposition: form-data; name="images"; filename="fooimage.png"
Content-Type: image/png
Problem:
I know how to uploed it in a seperate request (via ajax, seperate from the form). I know how to upload it as base64 Data an process it manually in the form.
But I do not know how to send the Image Data along the exiting Form so that it looks for the PHP Serverside Scripts exactly the same as an image that is send via <input type="file"...
Reason: Symphony (FileUpload Object) checks if a file is uploaded via the POST Form and fails if I manulally instanciate the object with the data.
So the best solution would be (in regards to a lot of other things, like testing, avoiding unnecessary logik), if the data would be passed the same as a regular form upload. How to do this?
Thanks!
You can use a FormData object to get the values of your form, and then append a blob version of your canvas into the FormData.
This blob will be seen as a file by the server.
Unfortunately, all browsers still don't support the native canvas.toBlob() method, and even worth, all implementations are not the same.
All major browsers now support the toBlob method, and you can find a polyfill on mdn for older browsers.
// the function to create and send our FormData
var send = function(form, url, canvas, filename, type, quality, callback) {
canvas.toBlob(function(blob){
var formData = form ? new FormData(form) : new FormData();
formData.append('file', blob, filename);
var xhr = new XMLHttpRequest();
xhr.onload = callback;
xhr.open('POST', url);
xhr.send(formData);
}, type, quality);
};
// How to use it //
var form = document.querySelector('form'), // the form to construct the FormData, can be null or undefined to send only the image
url = 'http://example.com/upload.php', // required, the url where we'll send it
canvas = document.querySelector('canvas'), // required, the canvas to send
filename = (new Date()).getTime() + '.jpg',// required, a filename
type = 'image/jpeg', // optional, the algorithm to encode the canvas. If omitted defaults to 'image/png'
quality = .5, // optional, if the type is set to jpeg, 0-1 parameter that sets the quality of the encoding
callback = function(e) {console.log(this.response);}; // optional, a callback once the xhr finished
send(form, url, canvas, filename, type, quality, callback);
PHP side would then be :
if ( isset( $_FILES["file"] ) ){
$dir = 'some/dir/';
$blob = file_get_contents($_FILES["file"]['tmp_name']);
file_put_contents($dir.$_FILES["file"]["name"], $blob);
}
I have a PDF on my .NET Core server, which I need to somehow send across the wire as a BLOB, so that my JS AJAX request can convert it back to a PDF so it can be downloaded.
The reason for the indirection is because the file comes from my API, which is only accessed through AJAX. Due to the need for a Bearer token, I can't just use a form behind the scenes, as there's no cookie for the site created. (Weird, I know, but that's how it is presently, and I'm not looking to change that part)
So, on my C# side, I've tried several variations, shown below. ApiResponse is just a container I use that holds a bool and a string (named message) so I can tell if the request was good or not.
These are what I've been trying
return new ApiResponse(true, File.ReadAllText(path));
return new ApiResponse(true, Convert.ToBase64String(File.ReadAllBytes(path)));
return new ApiResponse(true, JsonConvert.SerializeObject(File.ReadAllBytes(path)));
And on the JS side, in the same order to parse it back out, I have:
// Get the response in object form, since it's sent as an ApiResponse
const response = JSON.parse(xmlhttp.response);
const text = response.message;
const text = atob(response.message)
const text = JSON.parse(response.message)
I've also tried things like
const text = atob(JSON.parse(response.message))
Then, with the text I'm doing this:
const blob = new Blob([text], {type: "application/pdf"});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "file.pdf";
document.body.appendChild(a);
a.click();
And this does correctly generate a file that's downloaded. However, that file is not valid: it's corrupted.
I'm pretty much stuck at this point, and I haven't been able to find something that goes from start to finish using this method to download files with Javascript. It's either the back side, or the front side, but never tied together.
So, how can I successfully send a PDF BLOB across the wire, and recreate it on the front end so it can be downloaded?
The easy answer to how to do the convert is don't.
Every modern browser supports base64 encoding natively, so there's no need to convert the data back to a BLOB before putting it into download.
Thus, the end code is:
const a = document.createElement("a");
a.href = "data:application/pdf;base64," + response.message;
a.download = "file.pdf";
document.body.appendChild(a);
a.click();
I need to force download of file using JavaScript. I am using Angular and restangular to communicate with API. I am now working on file download action from API... API returns me raw bytes of that file and these headers:
Content-Disposition:attachment; filename="thefile"
Content-Length:2753
So I have raw bytes, but I do not know how to handle it to download this file to client...Can you provide me some solution of this issue? How can I handle returns response from server to open in client browser Save As dialog?
EDITED:
Server does not send me content-type of the file...Also in call's headers need to be auth token so I cannot use direct open window with url..
Code is:
vm.downloadFile = function(fileId){
var action = baseHelpers.one('files/' + fileId + '/content').get();
action.then(function(result){});
}
My first guess would be: Just request that API URL directly and not with an asynchronous request. You should be able to do something like this in your code
$window.location = "http://example.org/api/download"
For a solution using RESTangular I found this snipped, maybe you can try it:
Restangular.one('attachments', idAtt).withHttpConfig({responseType: 'blob'}}.get({}, {"X-Auth-Token": 'token'}).then(function(response) {
var url = (window.URL || window.webkitURL).createObjectURL(response);
window.open(url);
});
I had an endpoint on .Net server
[HttpPost]
[Route("api/tagExportSelectedToExcel")]
and a React frontend with axios. The task was to add a button which downloads a file from this API. I spent several hours before found this solution. I hope it will be helpful for someone else.
This is what I did:
axios('/api/tagExportSelectedToExcel', {
data: exportFilter,
method: 'POST',
responseType: 'blob'
}).then(res => resolveAndDownloadBlob(res));
Where resolveAndDownloadBlob:
/**
* Resolved and downloads blob response as a file.
* FOR BROWSERS ONLY
* #param response
*/
function resolveAndDownloadBlob(response: any) {
let filename = 'tags.xlsx';
filename = decodeURI(filename);
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute('download', filename);
document.body.appendChild(link);
link.click();
window.URL.revokeObjectURL(url);
link.remove();
}
It's difficult to answer without seeing your code calling the API, but in general the way you do this is to send a form rather than using ajax. Typically you'd have a hidden iframe with a name="downloadframe" or similar, and then use a form like this:
<form id="downloadform" target="downloadframe" method="POST" action="/the/api/endpoint">
<input type="hidden" name="apiParameter" value="parameter-value">
</form>
Then you'd fill in the fields of the form and submit it programmatically. Here's an example not using Angular, but adapting it would be simple (though not necessary):
var form = document.getElementById("downloadform");
form.apiParameter.value = "appropriate value";
form.submit();
When the browser gets the response, it sees the Content-Disposition header and asks the user where to save the file.
You can even build the form dynamically rather than using markup if you prefer.
I apologize in the advance as I am a total beginner.
I have a pre-existing html form with text fields. I need to have a button that will allow me to upload a txt file (since when trying to look for answer about this, I learned javascript can't just access a file from my PC without me actively uploading it). Then I need the values from this txt file inserted into the text fields (for example, the form has: name, last name, phone etc - and the file will fill out this info).
I am going crazy trying to collect bits and pieces from other people's questions. any help would be greatly appreciated!
it depends on how you would like to have this handled, there are basically two options:
File Upload and page redirect
you could provide a file upload form to upload your textfile, redirect to the same page via form submission, handle the data on serverside (e.g. parse the file and get the values out of it) and let the server inject the values as default properties for the form file which is returned to the browser
XMLHttpRequest File Upload
in modern browsers, the native xhr object supports an upload property, so you could send the file via that upload property. it has to be sent to a serverside script that parses the file and returns the values in a fitting format, e.g. json (which would look like this: {'name':'somename', 'lastName':'someothername'}). then you have to register an eventlistener on completion of this upload (e.g. onload) and set these values on javascript side.
check this out for XMLHttpRequest upload (better solution in my opinion): https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Submitting_forms_and_uploading_files
edit:
well, the easiest solution would be just to provide a textfield and paste the content of the file into this field, hit a button and the content is parsed. then you wouldn't rely on network traffic or even a serverside handling, but could do everything with javascript, e.g. like this:
dom:
<textarea id="tf"></textarea>
<button id="parse">fill form!</button>
js:
var tf = document.getElementById("tf");
document.getElementById("parse").addEventListener("click", function(){
var formData = JSON.parse(tf.value);
//if your textfile is in json format, the formData object now has all values
});
edit: from the link i posted in the comments:
<!-- The HTML -->
<input id="the-file" name="file" type="file">
<button id="sendfile">send</button>
and
document.getElementById('sendfile').addEventListener("click", function(){
var fileInput = document.getElementById('the-file');
var file = fileInput.files[0];
var formData = new FormData();
formData.append('file', file);
var xhr = new XMLHttpRequest();
// Add any event handlers here...
xhr.open('POST', '/upload/path', true);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
var values = JSON.parse(xhr.responseText);
//these are your input elements you want to fill!
formNameInput.setAttribute('value', values.name);
formFirstNameInput.setAttribute('value', values.firstName);
}
}
xhr.send(formData);
});
as already said, your serverside has to parse the file and respond with json
When an HTML form contains <input type="file"> I need to specify the enctype="multipart/form-data" attribute on the form. However, when I send a binary file over XMLHttpRequest I don't need to specify that type anywhere:
var builder = new BlobBuilder();
builder.append("Hello world!");
var blob = builder.getBlob("text/plain");
var oReq = new XMLHttpRequest();
oReq.open("POST", url, true);
oReq.send(blob);
Why so?
With a form, you are telling the browser how to format the data to send it to the server. (The default value for enctype doesn't support files).
With XHR, you are formatting the data yourself. (That said, you should still use addHeader to specify a suitable content-type for your POST body).