Browser requests file, server downloads pdf, browser displays pdf in iframe - javascript

this is what I want to do:
1) a browser initiates an ajax request to the server, asking for a pdf.
2) the server downloads the pdf, and returns the pdf for display.
3) the browser displays the downloaded pdf in a pre-existing iframe.
Below is my code. It appears to stop at the iframe part, but I suspect that its not sending the pdf properly.
Browser index.html file:
var uri = '/viewer/loaddrawing/';
$.getJSON(uri, {key:value}, function(data, jqXHR){
document.getElementById("iframetitle").src = uri;
});
Django server views.py file:
import requests
def loaddrawing(request):
value = request.GET.get('key')
#the key is used to generate a unique url, but for test purposes lets use the url shown below
url = "http://cbmeturkey.com/media/109/test.pdf"
response = urllib2.urlopen(url)
some_data = response.read()
return HttpResponse(some_data, mimetype='application/pdf')
EDIT:
I have one issue remaining: I don't want to use the below code in my index.html file anyway, because I want to actually download the pdf and use it again. The reason for this is that my index.html page uses javascript to display and hide the iframe, and with the below code the pdf is redownloaded each time the iframe is shown.
var uri = '/viewer/loaddrawing/';
document.getElementById('iframetitle').src = uri + '?key=' + value;
SOLVED:
The above issue was solved after directions from Augusto, and the following index.html code, which loads the pdf only once, although notice that now I am now modifying a div (named "divtitle") and not an iframe:
var uri = '/viewer/loaddrawing/' + '?key=' + value;
var htm = '\<iframe src="' + uri +'" onload="downloadComplete()">\</iframe>';
document.getElementById('divtitle').innerHTML = htm;

Probably what you meant was simply:
var uri = '/viewer/loaddrawing/';
document.getElementById('iframetitle').src = uri + '?key=' + value;
There is no JSON involved, so the iframe will get a PDF directly. However, notice that the client must have a PDF-viewer plugin, otherwise the browser will ask for a download prompt.

Related

How to encode binary file inside JavaScript in HTML

I want to use a HTML webpage that uses a .bin file with JavaScript.
I have found out that browsers don't support opening files from JS with anything BUT a protocol.
Console in Google Chrome throws the "origin 'null' has been blocked by CORS policy"
HOWEVER, could I paste the binary file's base64 value (e.g. data URI or JSON) inside the .HTML instead of asking it to open the file?
...
var filename = "recipes_0_" + cut_cost + "_" + stack_cost + (singleLayerIsRaw?"r":"") + ".bin";
load_binary_resource( filename );
}
function onPageLoad() {
document.getElementById("code").disabled = true;
//load_binary_resource("[base64 value]");
instead of
document.getElementById("code").disabled = true;
//load_binary_resource("myfile.bin");

<a> element does not recognize the file extension automatically when downloading a file

I'm currently developing an application where the user clicks on an element, that element calls a JS function and the function handles a file download.
The files are reports generated dynamically by Devexpress XtraReports module, converted to Base64 and then sent back to the client side. When the client receives the Base64 string, the JS function creates an <a> element, sets the href attribute to data:application/pdf;base64,JVBERi0xLjQNCiWio[...] and simulates a click with the click() event.
Here's the piece of JS code that handles the file download:
let downloadLink;
try {
downloadLink = executionId ? await getLinkPdfBase64(executionId) : false;
} catch (error) {
downloadLink = false;
console.log(error);
}
if (downloadLink) {
const aElement = document.createElement("a");
downloadLink = "data:application/pdf;base64," + downloadLink;
aElement.setAttribute("download", currentReportData.LayoutName);
aElement.setAttribute("href", downloadLink);
aElement.click();
aElement.remove();
} else {
DevExpress.ui.dialog.alert( //Ignore this, it's a Devexpress component
"Your report could not be generated",
"Alert"
);
}
The problem is:
When I generate a report with custom parameter types, Devexpress generates it correctly (the Base64, if converted to string, is visibly correctly formed) but the browser (Google Chrome) downloads the file with the extension ".0".
If the report has normal Devexpress parameters (like Strings, Int32, Guids, etc)) the file is downloaded with the correct ".pdf" extension.
Here's a picture of a correctly downloaded PDF and a ".0" extension file:
Could it be the JS function the cause or the solution to the problem? If not, almost for sure there will be something wrong with the report generator (Devexpress).
NB: If I manually change the ".0" extension to ".pdf" the file opens and it is displayed / formed correctly.
Turns out I ended up solving it just by adding the file extension ".pdf" in the download attribute, so when the browser can't recognize it, you are already specifying which one it is:
aElement.setAttribute("download", currentReportData.LayoutName + ".pdf");

Is there a way to download an encoded file with window.open?

I'm using this code to download a file on click:
var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(this.data, null, "\t"));
var dlAnchorElem = document.createElement('a');
dlAnchorElem.setAttribute("href", dataStr);
dlAnchorElem.setAttribute("download", "data.json");
document.body.appendChild(dlAnchorElem);
dlAnchorElem.click();
Due to some restrictions on the browsers I'm working with (inApp for android), this code does not work. So I'm thinking of a workaround..
I could use this code:
var url = "download.html";
var windowref = window.open(url, '_blank', 'location=no,closebuttoncaption=Cerrar,toolbar=yes,enableViewportScale=yes');
But since my content is dynamically generated (a dump of a json object), I can't pass it to download.html. Is there a way where window.open would take an encoded value in the URL parameter, so that when the new window opens, it would instantly download the file?

Can I set the filename of a PDF object displayed in Chrome?

In my Vue app I receive a PDF as a blob, and want to display it using the browser's PDF viewer.
I convert it to a file, and generate an object url:
const blobFile = new File([blob], `my-file-name.pdf`, { type: 'application/pdf' })
this.invoiceUrl = window.URL.createObjectURL(blobFile)
Then I display it by setting that URL as the data attribute of an object element.
<object
:data="invoiceUrl"
type="application/pdf"
width="100%"
style="height: 100vh;">
</object>
The browser then displays the PDF using the PDF viewer. However, in Chrome, the file name that I provide (here, my-file-name.pdf) is not used: I see a hash in the title bar of the PDF viewer, and when I download the file using either 'right click -> Save as...' or the viewer's controls, it saves the file with the blob's hash (cda675a6-10af-42f3-aa68-8795aa8c377d or similar).
The viewer and file name work as I'd hoped in Firefox; it's only Chrome in which the file name is not used.
Is there any way, using native Javascript (including ES6, but no 3rd party dependencies other than Vue), to set the filename for a blob / object element in Chrome?
[edit] If it helps, the response has the following relevant headers:
Content-Type: application/pdf; charset=utf-8
Transfer-Encoding: chunked
Content-Disposition: attachment; filename*=utf-8''Invoice%2016246.pdf;
Content-Description: File Transfer
Content-Encoding: gzip
Chrome's extension seems to rely on the resource name set in the URI, i.e the file.ext in protocol://domain/path/file.ext.
So if your original URI contains that filename, the easiest might be to simply make your <object>'s data to the URI you fetched the pdf from directly, instead of going the Blob's way.
Now, there are cases it can't be done, and for these, there is a convoluted way, which might not work in future versions of Chrome, and probably not in other browsers, requiring to set up a Service Worker.
As we first said, Chrome parses the URI in search of a filename, so what we have to do, is to have an URI, with this filename, pointing to our blob:// URI.
To do so, we can use the Cache API, store our File as Request in there using our URL, and then retrieve that File from the Cache in the ServiceWorker.
Or in code,
From the main page
// register our ServiceWorker
navigator.serviceWorker.register('/sw.js')
.then(...
...
async function displayRenamedPDF(file, filename) {
// we use an hard-coded fake path
// to not interfere with legit requests
const reg_path = "/name-forcer/";
const url = reg_path + filename;
// store our File in the Cache
const store = await caches.open( "name-forcer" );
await store.put( url, new Response( file ) );
const frame = document.createElement( "iframe" );
frame.width = 400
frame.height = 500;
document.body.append( frame );
// makes the request to the File we just cached
frame.src = url;
// not needed anymore
frame.onload = (evt) => store.delete( url );
}
In the ServiceWorker sw.js
self.addEventListener('fetch', (event) => {
event.respondWith( (async () => {
const store = await caches.open("name-forcer");
const req = event.request;
const cached = await store.match( req );
return cached || fetch( req );
})() );
});
Live example (source)
Edit: This actually doesn't work in Chrome...
While it does set correctly the filename in the dialog, they seem to be unable to retrieve the file when saving it to the disk...
They don't seem to perform a Network request (and thus our SW isn't catching anything), and I don't really know where to look now.
Still this may be a good ground for future work on this.
And an other solution, I didn't took the time to check by myself, would be to run your own pdf viewer.
Mozilla has made its js based plugin pdf.js available, so from there we should be able to set the filename (even though once again I didn't dug there yet).
And as final note, Firefox is able to use the name property of a File Object a blobURI points to.
So even though it's not what OP asked for, in FF all it requires is
const file = new File([blob], filename);
const url = URL.createObjectURL(file);
object.data = url;
In Chrome, the filename is derived from the URL, so as long as you are using a blob URL, the short answer is "No, you cannot set the filename of a PDF object displayed in Chrome." You have no control over the UUID assigned to the blob URL and no way to override that as the name of the page using the object element. It is possible that inside the PDF a title is specified, and that will appear in the PDF viewer as the document name, but you still get the hash name when downloading.
This appears to be a security precaution, but I cannot say for sure.
Of course, if you have control over the URL, you can easily set the PDF filename by changing the URL.
I believe Kaiido's answer expresses, briefly, the best solution here:
"if your original URI contains that filename, the easiest might be to simply make your object's data to the URI you fetched the pdf from directly"
Especially for those coming from this similar question, it would have helped me to have more description of a specific implementation (working for pdfs) that allows the best user experience, especially when serving files that are generated on the fly.
The trick here is using a two-step process that perfectly mimics a normal link or button click. The client must (step 1) request the file be generated and stored server-side long enough for the client to (step 2) request the file itself. This requires you have some mechanism supporting unique identification of the file on disk or in a cache.
Without this process, the user will just see a blank tab while file-generation is in-progress and if it fails, then they'll just get the browser's ERR_TIMED_OUT page. Even if it succeeds, they'll have a hash in the title bar of the PDF viewer tab, and the save dialog will have the same hash as the suggested filename.
Here's the play-by-play to do better:
You can use an anchor tag or a button for the "download" or "view in browser" elements
Step 1 of 2 on the client: that element's click event can make a request for the file to be generated only (not transmitted).
Step 1 of 2 on the server: generate the file and hold on to it. Return only the filename to the client.
Step 2 of 2 on the client:
If viewing the file in the browser, use the filename returned from the generate request to then invoke window.open('view_file/<filename>?fileId=1'). That is the only way to indirectly control the name of the file as shown in the tab title and in any subsequent save dialog.
If downloading, just invoke window.open('download_file?fileId=1').
Step 2 of 2 on the server:
view_file(filename, fileId) handler just needs to serve the file using the fileId and ignore the filename parameter. In .NET, you can use a FileContentResult like File(bytes, contentType);
download_file(fileId) must set the filename via the Content-Disposition header as shown here. In .NET, that's return File(bytes, contentType, desiredFilename);
client-side download example:
download_link_clicked() {
// show spinner
ajaxGet(generate_file_url,
{},
(response) => {
// success!
// the server-side is responsible for setting the name
// of the file when it is being downloaded
window.open('download_file?fileId=1', "_blank");
// hide spinner
},
() => { // failure
// hide spinner
// proglem, notify pattern
},
null
);
client-side view example:
view_link_clicked() {
// show spinner
ajaxGet(generate_file_url,
{},
(response) => {
// success!
let filename = response.filename;
// simplest, reliable method I know of for controlling
// the filename of the PDF when viewed in the browser
window.open('view_file/'+filename+'?fileId=1')
// hide spinner
},
() => { // failure
// hide spinner
// proglem, notify pattern
},
null
);
I'm using the library pdf-lib, you can click here to learn more about the library.
I solved part of this problem by using api Document.setTitle("Some title text you want"),
Browser displayed my title correctly, but when click the download button, file name is still previous UUID. Perhaps there is other api in the library that allows you to modify download file name.

Upload file to Windows Azure with only the link of the file is provided

How can I upload a file in azure if I only have the URL of the file to upload. In this case, i 'm using Dropbox file chooser which selects file from dropbox and returns its url path.
eq
https://www.dropbox.com/s/o9myet72y19iaan/Getting%20Started.pdf
Now we need the file to be stored in Windows Azure blob. What is the easiest way to do this without downloading the file first.
I'm planning to use a asp.net web api for the uploading of file to azure blob.
At first, I thought it should be quite straight forward as Azure Blob Storage support copying blobs from external URL however I don't think this would work in case of Dropbox files. I just tried it and got an error even though.
The link you mentioned above is not the direct link to the file. It's a link to a page on Dropbox's website from where you can download a file. This is obviously you don't want. Here's an alternate solution which you can try:
Replace www.dropbox.com in your URL with dl.dropboxusercontent.com (based on #smarx's comments below) and use that URL in the following code:
First you would need to append dl=1 to your request URL as query string. So your Dropbox URL would be https://www.dropbox.com/s/o9myet72y19iaan/Getting%20Started.pdf?dl=1. dl query string parameter indicates the file needs to be downloaded.
Next, using HTTPWebRequest try accessing this URL. Dropbox will respond back with another link and 302 status code. This link would be something like https://dl.dropboxusercontent.com/s/o9myet72y19iaan/Getting%20Started.pdf?token_hash=<tokenhash>.
Use this link in the code below to copy file. This would work.
CloudStorageAccount acc = new CloudStorageAccount(new StorageCredentials("account", "key"), false);
var client = acc.CreateCloudBlobClient();
var container = client.GetContainerReference("container-name");
container.CreateIfNotExists();
var blob = container.GetBlockBlobReference("dropbox-file-name");
blob.StartCopyFromBlob(new Uri("dropbox URL with dl.dropboxusercontent.com"));
Console.WriteLine("Copy request accepted");
Console.WriteLine("Now checking for copy state");
bool continueLoop = true;
do
{
blob.FetchAttributes();
var copyState = blob.CopyState;
switch (copyState.Status)
{
case CopyStatus.Pending:
Console.WriteLine("Copy is still pending. Will check status again after 1 second.");
System.Threading.Thread.Sleep(1000);//Copy is still pending...check after 1 second
break;
default:
Console.WriteLine("Terminating process with copy state = " + copyState.Status);
continueLoop = false;
break;
}
}
while (continueLoop);
Console.WriteLine("Press any key to continue.");

Categories