Prompt file download - javascript

I have a link on my page on click of which I am trying to generate a PDF document and then show the "Open - Save" prompt on the browser.
My HTML (reactjs component) has the below code where onclick calls the _getMyDocument function which then calls a Webapi method.
<div className="row">
<a href="#" onClick={this._getMyDocument.bind(this)}>Test Link</a>
</div>
_getMyDocument(e) {
GetMyDocument(this.props.mydata).then(()=> {
}).catch(error=> {
});
My Controller has the below code
[HttpPost]
[Route("Generate/Report")]
public IHttpActionResult GetMyReport(MyData myData)
{
byte[] myDoc = MyBusinessObject.GenerateMyReport(myData);
var result = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new ByteArrayContent(myDoc)
};
result.Content.Headers.ContentDisposition =
new ContentDispositionHeaderValue("attachment")
{
FileName = "MyDocument.pdf"
};
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
var response = ResponseMessage(result);
return response;
}
Currently all the code executes but I don't get the file PDF download prompt. What am I doing wrong here?
Response object on success from the ajax call lokks like below

Your response from the server looks good.
The missing part is that you are not handling this response from the client side in the correct way.
Lets assume that your resource url object looks like below in js. (i.e. you already know the resource url, if you don't know it yet, then you will need a separate call to the server to know the download url)
response.downloadUrl = app/media/fileId/document.pdf
All you need to do is set,
window.location = item.downloadUrl;
This will cause the browser to request a resource from the server, the response from the server must include Content-Disposition:attachment;. It will cause the browser to show the download dialog.
P.S. I have recently worked on a similar functionality. If you have questions please ask.
When you want to force the browser to show the download prompt for some files (or resources), you must include Content-Disposition:attachment; in the response header (which you already did).

In our app(angular) we had to create an object url for that with a code like:
WebApi:
result = Request.CreateResponse(HttpStatusCode.OK);
result.Content = new ByteArrayContent(data);
result.Content.Headers.Add("Content-Type", "application/pdf");
result.Content.Headers.ContentDisposition =
new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
{
FileName = "FileNameHere"
};
return result;
javascript:
// HttpCall in here
// On SuccessResponse
var file = new Blob([data], {
type: 'application/pdf'
});
var fileURL = URL.createObjectURL(file);
// create an anchor and click on it.
var ancorTag = document.createElement('a');
ancorTag.href = fileURL;ancorTag.target = '_blank';
ancorTag.download = 'CouponOrder.pdf';
document.body.appendChild(ancorTag);
ancorTag.click();
document.body.removeChild(ancorTag);

A simple way to do this is on your server to use GET method, and receive your data by query parameters.
Then in your client app you just have to create a classic link:
Test Link
Or use that simple js script if your want to manage it from your script logic:
window.open("http://your-server.com/your-resource?param1=123&param2=456");

Create Content Disposition and add it to your response header
var cd = new System.Net.Mime.ContentDisposition
{
// for example foo.bak
FileName = System.IO.Path.GetFileName(fileName),
// always prompt the user for downloading, set to true if you want
// the browser to try to show the file inline
Inline = false,
};
Response.AppendHeader("Content-Disposition", cd.ToString());

Your issue is that the GetMyDocument function will receive the PDF file as a server response, and currently you are doing nothing with that response. You need to handle the response in the then callback. Saving files from javascript is not entirely simple, so I would recommend using an external library such as filesaver.js to help you.
It will end up looking something like...
_getMyDocument(e) {
GetMyDocument(this.props.mydata)
.then((response)=> {
const returnedFile = new Blob([response], { type: 'application/pdf'});
saveAs(returnedFile);
}).catch(error=> {
});

Related

javascript - make browser send PDF file to download folder

How do I send a PDF file I am downloading from a server to the browser's download area instead of opening it in a browser window?
I am working with C# in Blazor. This is my current code, which I need to modify, but don't know how to (ofc I googled before asking here):
async void DownloadDocument(string apiURL, Guid ID)
{
JSRuntime.InvokeAsync<string>("open", $"{apiURL}/GetPDF/{ID}", "_blank");
}
The server returns a FileStreamResult here and the browser shows the file in a new tab. I want it to send it to its downloads folder instead.
You are invoking a JS Function called "open" with two params. (https://developer.mozilla.org/en-US/docs/Web/API/Window/open)
Try creating your own JS download function inside of a script file / tag and invoking it.
The crucial part to save a file in the downloads folder is to set the download attribute of the a tag.
It could look something like this.
inside wwwroot/index.html:
<script>
window.downloadFile = (fileName, pdfData) => {
const linkSource = `data:application/pdf;base64,${pdfData}`;
const downloadLink = document.createElement("a");
downloadLink.href = linkSource;
downloadLink.download = fileName;
downloadLink.click();
downloadLink.remove();
}
</script>
and in your blazor component:
async void DownloadDocument(string apiURL, Guid ID)
{
// call your api to download the file you want to download
var response = await Http.GetAsync($"{apiURL}/GetPDF/{ID}"));
// convert to base64
var pdfExportBytes = await response.Content.ReadAsByteArrayAsync();
var pdfExportB64 = Convert.ToBase64String(pdfExportBytes);
// invoke js download
await JSRuntime.InvokeVoidAsync("downloadFile", "FileName", pdfExportB64);
}
The file will still be opened if configured so in the users browser, but it will also be stored in the download folder.

How to rename .docx blob file after getting response from server in Angular automatically without creating anchor tag?

I want to rename a blob file downloaded from a server. I looked for other solutions but they mainly deal with the created anchor link and setting name to anchor. I tried this approach:
getRenamed(title): void {
this.docTitle = title;
this.requestService.download(title).subscribe((response) => {
let thefile = new Blob([response], {
type:
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
});
let url = window.URL.createObjectURL(thefile);
window.open(url);
});
}
But the problem with this approach is that it creates a random filename. And I want to give it a specific filename.

In node/js, how can I display a text file that I fetched in the browser window?

I have a protected API that fetches the contents of a file. By protected I mean, I need to send in Authorization headers before the API will allow me to fetch the file contents.
How do I display this in a browser window?
Currently, my nodejs backend is returning the contents with Content-Type:text/html
On the frontend, my current code looks like this
$http.get(downloadUrl)
.then(function(resp) {
var data = resp.data;
var blob = new Blob([data], { type: "text/html" });
let objectUrl = (window.URL || window.webkitURL).createObjectURL( blob );
let anchor = document.createElement("a");
anchor.href = objectUrl;
anchor.download = 'source.html';
anchor.click();
window.URL.revokeObjectURL(objectUrl);
This just download the file though. I just want to display it, preferably in a new window.
Edit I believe this is a duplicate of this question and there is no front-end-only solution to it. It requires the backend to play a part, such as implementing a "Holder-Of-Key Authentication Scheme"

send file from server to client on bokeh

I have made a user interface to fetch data from a MySQL table and visualize it. It is running on a bokeh server. My users connect remotely to the server using their browser (firefox). This works perfectly fine: I simply import the table into a pandas dataframe.
My users also need to download the table as excel. This means I cannot use the export_csv example which is pure javascript.
I have no experience with JavaScript. All I want is to transfer a file from the directory where my main.py is to the client side.
The technique I have tried so far is to join a normal on_click callback to a button, export the information I need to 'output.xls', then change a parameter from a dummy glyph which in turn runs a Javascript code. I got the idea from Bokeh widgets call CustomJS and Python callback for single event? . Note I haven't set the alpha to 0, so that I can see if the circle is really growing upon clicking the download button.
At the bottom of my message you can find my code. You can see I have tried with both XMLHttpRequest and with Fetch directly. In the former case, nothing happens. In the latter case I obtain a file named "mydata.xlsx" as expected, however it contains only this raw text: <html><title>404: Not Found</title><body>404: Not Found</body></html>.
Code:
p = figure(title='mydata')
#download button
download_b = Button(label="Download", button_type="success")
download_b.on_click(download)
#dummy idea from https://stackoverflow.com/questions/44212250/bokeh-widgets-call-customjs-and-python-callback-for-single-event
dummy = p.circle([1], [1],name='dummy')
JScode_xhr = """
var filename = p.title.text;
filename = filename.concat('.xlsx');
alert(filename);
var xhr = new XMLHttpRequest();
xhr.open('GET', '/output.xlsx', true);
xhr.responseType = 'blob';
xhr.onload = function(e) {
if (this.status == 200) {
var blob = this.response;
alert('seems to work...');
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, filename);
}
else {
var link = document.createElement("a");
link = document.createElement('a');
link.href = URL.createObjectURL(blob);
window.open(link.href, '_blank');
link.download = filename;
link.target = "_blank";
link.style.visibility = 'hidden';
link.dispatchEvent(new MouseEvent('click'));
URL.revokeObjectURL(url);
}
}
else {
alert('Ain't working!');
}
};
"""
JScode_fetch = """
var filename = p.title.text;
filename = filename.concat('.xlsx');
alert(filename);
fetch('/output.xlsx').then(response => response.blob())
.then(blob => {
alert(filename);
//addresses IE
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, filename);
}
else {
var link = document.createElement("a");
link = document.createElement('a')
link.href = URL.createObjectURL(blob);
window.open(link.href, '_blank');
link.download = filename
link.target = "_blank";
link.style.visibility = 'hidden';
link.dispatchEvent(new MouseEvent('click'))
URL.revokeObjectURL(url);
}
return response.text();
});
"""
dummy.glyph.js_on_change('size', CustomJS(args=dict(p=p),
code=JScode_fetch))
plot_tab = Panel(child=row(download_b,p),
title="Plot",
closable=True,
name=str(self.test))
def download():
writer = pd.ExcelWriter('output.xlsx')
data.to_excel(writer,'data')
infos.to_excel(writer,'info')
dummy = p.select(name='dummy')[0]
dummy.glyph.size = dummy.glyph.size +1
Trying out Eugene Pakhomov's answer, I found what was the issue.
The javascript code I named JScode_fetch is almost correct, however I get a 404 because it is not pointing correctly to the right path.
I made my application in the directory format: I changed my .py file to main.py, placed it into a folder called app, and changed this one line of code in JScode_fetch:
fetch('/app/static/output.xlsx', {cache: "no-store"}).then(response => response.blob())
[...]
You can see the problem was that it was trying to access localhost:5006/output.xlsx, instead of localhost:5006/app/output.xlsx. As it is in directory format, the right link is now localhost:5006/app/static/output.xlsx to count for the static directory.
I also changed a few lines in the download function:
def download():
dirpath = os.path.join(os.path.dirname(__file__),'static')
writer = pd.ExcelWriter(os.path.join(dirpath,'output.xlsx'))
writer = pd.ExcelWriter('output.xlsx')
data.to_excel(writer,'data')
infos.to_excel(writer,'info')
dummy = p.select(name='dummy')[0]
dummy.glyph.size = dummy.glyph.size +1
Now it is working flawlessly!
edit: I have added , {cache: "no-store"} within the fetch() function. Otherwise the browser thinks the file is the same if you have to download a different dataframe excel while using the same output.xlsx filename. More info here.
bokeh serve creates just a few predefined handlers to serve some static files and a WebSocket connection - by default, it doesn't have anything to serve files from the root of the project.
Instead of using the one-file format, you can try using the directory format, save your files to static directory and download them from /static/.
One downside of this approach is that you still have to write that convoluted code to just make your backend create the file before a user downloads it.
The best solution would be to go one step further and embed Bokeh Server as a library into your main application. Since you don't have any non-Bokeh code, the simplest way would be to go with Tornado (an example).
bokeh.server.server.Server accepts extra_patterns argument - you can add a handler there to dynamically create Excel files and serve them from, say, /data/. After all that, the only thing that you need in your front-end is a single link to the Excel file.

How to create a Blob object from image url?

I am using Winjs(javascript for windows 8 app).
what I want is to create a simple blob object from a specific url of my static image by giving the path.
What is the solution?
Any help will be appreciated.
'MSApp.CreateFileFromStorageFile()` as used below will work. if you need to send the file using WinJS.xhr() you can set as data in xhrOptions.
var uri = new Windows.Foundation.Uri('ms-appx:///images/IMG_0550.jpg');
var self = this;
Windows.Storage.StorageFile.getFileFromApplicationUriAsync(uri).then(function ongetfilecomplete(storageFile)
{
var file = MSApp.createFileFromStorageFile(storageFile);
var url = URL.createObjectURL(file, { oneTimeOnly: true });
// assume that this.imageElement points to the image tag
self.imageElement.setAttribute('src', url);
}).then(null, function onerror(error)
{
});
refer the link in case you are looking for upload the blob to azure. For send the blob to your webservice also, code will be on these lines.
URL.createObjectURL("") should work. I use it all the time. Test it with some other URLs. You could do it in debug mode in the JS console to make it easier.

Categories

Resources