Receiving files in Javascript from Flask using the send_file() function is a common task that many developers

I am in the process of creating a web application that utilizes HTML along with plain Javascript for the frontend, and Flask as the backend. The main function of the application is to send an ID to the server, where it will then generate a PDF report and send it back to the client.

In order to achieve this, I have set up an endpoint using Flask:

    @app.route("/download_pdf", methods=['POST'])
    def download_pdf():
        if request.method == 'POST':
            report_id = request.form['reportid']
            print(report_id) //For testing purposes.
            // Perform necessary operations with report_id to generate a PDF file.
            return send_file('/home/user/report.pdf', mimetype='application/pdf', as_attachment=True)
// Various attempts were made with different values for mimetype and as_attachment=False

Testing the endpoint from the command line results in obtaining the correct file, with the server console displaying the expected report ID (123):

curl --form reportid=123 http://localhost:5000/download_pdf >> output.pdf

On the frontend side, I have created a button that triggers a JavaScript function:

<button id=pdf_button onclick="generatePdf()"> PDF </button>

The JavaScript function is structured as follows:

function generatePdf(){
    var report_list = document.getElementById("report_list")

    if (report_list.selectedIndex < 0){
        alert("Please select a report.");
    }else{
        var req = new XMLHttpRequest();

        req.open("POST", "/download_pdf", true);
        req.responseType = "document";
        req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
        req.onreadystatechange = function(){
            console.log(req.readyState)
            console.log(req.status)
            console.log(req.response)
            var link = document.createElement('a')
            link.href = req.response;
            link.download="report.pdf"
            link.click()

        }
        var selected_value = report_list.options[report_list.selectedIndex].value;
        var params="reportid="+selected_value;
        req.send(params);
    }

};

In this case, req.response returns null. Nevertheless, the call to the endpoint is executed correctly, as evidenced by the backend console displaying the report ID as intended.

Attempts made so far include:

  • Setting responseType to "blob" and "arraybuffer" based on this resource.
  • Verifying the HTTP return code, which consistently shows 0.
  • Replacing req.onreadystatechange with req.onload, but no output is observed in the console.

Furthermore, upon clicking the button, the Firefox console displays 6 messages (make sure to observe the console.log() calls in the preceding code):

2
0
null
4
0
null

It appears that the JavaScript function is being called twice when the button is pressed.

My objective is to successfully download the PDF. Any assistance or guidance would be greatly appreciated as I attempt to resolve this issue.

Answer №1

After some thorough investigation, I have finally identified the root cause of the issue and wanted to document it here for future reference. Initially, I dismissed it as unrelated, but upon closer inspection, I discovered that the <button> triggering the Javascript function was nested within a <form>. This led to a situation where the form was being updated before the endpoint call could complete, resulting in premature termination.

For anyone facing a similar scenario, below is a snippet of the revised code:

HTML (note that both the select dropdown and button are now outside the <form>):

<select id="report_list" size=20> ... </select>
...
<button id="pdf_button" onclick="generatePdf()"> PDF </button>

Javascript:

function generatePdf(){
    var report_list = document.getElementById("report_list");
    var req = XMLHttpRequest();
    var selected_value = report_list.options[report_list.selectedIndex].value;

    req.open("POST", "/reports/"+selected_value+"/pdf", true);
    req.responseType = "blob";
    req.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    req.onreadystatechange = function(){
    if (this.readyState == 4 && this.status == 200){
        var blob = new Blob([this.response], {type: "application/pdf"});
        var url = window.URL.createObjectURL(blob);
        var link = document.createElement('a');
        document.body.appendChild(link);
        link.style = "display: none";
        link.href = url;
        link.download = "report.pdf";
        link.click();

        setTimeout(() => {
        window.URL.revokeObjectURL(url);
        link.remove(); } , 100);
    }
    };

    req.send();
}

Flask:

@app.route("/reports/<id>/pdf", methods=['POST'])
def get_pdf(id):
    if request.method == 'POST':
        return send_file(get_pdf_path(id), mimetype='application/pdf')

While I cannot guarantee that this is the optimal or most elegant solution, it has proven effective for my needs thus far.

Answer №2

Make sure to double check your ajax settings for better performance:

req.open("POST", "/download_pdf", true);
req.responseType = "blob";

req.onreadystatechange = function() {
  console.log(req.readyState)
  console.log(req.status)
  const blob = new Blob([req.response]);
  const url = window.URL.createObjectURL(blob);

  const link = document.createElement('a')
  link.href = url
  link.download = "report.pdf"
  link.click()
}

It's crucial that the response type is set to a blob, and always remember to parse it as such upon receiving. Don't forget to remove the link after some time.

setTimeout(() => {
    window.URL.revokeObjectURL(url);
    link.remove();
}, 100);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Mastering TypeScript in Router Configuration

I am currently working with a standard router setup. type Routes = '/' | '/achievements' | ... ; This helps in identifying the routers present in the project. However, I am faced with a new challenge of creating an array that includes ...

I'm curious about the meaning of "!" in pseudo-code. I understand that it represents factorial, but I'm unsure of how to properly interpret it

Can someone explain the meaning of ! in pseudo-code to me? I understand that it represents factorial, but for some reason I am having trouble translating it. For example: I came across this code snippet: if (operation!= ’B’ OR operation != ’D’ O ...

AngularJS application: the button requires two clicks to activate

In my attempt to develop a simple CRUD app using AngularJS, I encountered an issue. The initial step involves clicking a button that fetches data from a REST API and populates an array. This array is then iterated using ng-repeat to generate Bootstrap card ...

Exporting data from AngularJS to Excel is not functioning correctly in Internet Explorer. Additionally, the exported Excel file does not display properly in Firefox and

I have been attempting to Export a table from a jsp page to Excel using AngularJs. Is Angularjs not compatible with IE? I am also encountering this error SCRIPT5009: 'Node' is undefined In Chrome and Firefox, the Excel file only opens when save ...

Is it possible to validate only the fields that are currently visible using HTML form validation

I need a solution for excluding hidden fields from HTML form validation. My situation involves having two groups of form fields, each with some required attributes. Depending on user selection, one group may be hidden, and those fields should not contribut ...

What's the most effective method for transferring data to different components?

How can I efficiently pass a user info object to all low-level components, even if they are grandchildren? Would using @input work or is there another method to achieve this? Here is the code for my root component: constructor(private _state: GlobalSta ...

Performing a `contains` operation with JQuery on this variable or object

Help! I'm completely confused about this problem. First, I am trying to use jQuery to select an li object and then check if a certain text exists within it using the "contains" function. However, for some reason, the line with the "contains" function ...

Executing system commands using Groovy is a breeze

One of the scripts I have is a sample.js script that allows me to view files located on the server myHost. It works perfectly: var exec = require('ssh-exec') var v_host = 'myHost' exec('ls -lh', { user: 'username&apo ...

Storing and retrieving form content in local storage with AngularJS

Is there a way for the data in the scope to be returned to the textbox value upon refreshing the page, using only an update button? Here's an example: Plunker ...

Modify marker location as data updates

I am currently utilizing the Google Maps API within a Vue.js project. In my project, I have a table of data that includes positions, and I am looking to update the marker positions dynamically without refreshing the entire card. Below is the code snippet ...

easy-to-use jquery autocomplete with json

After hours of scouring Stack Overflow and Google, I've tried countless solutions to get my autocomplete feature working. Initially, my code was functional, but it crashed when I input a larger list. So, I switched to a different script type, but I&ap ...

Setting up an Angular CLI project

In our Angular 2 application, we utilize native CSS components that contain some JavaScript files. These components have wrappers to convert them into Angular 2 components. Now, I am looking to create a new Angular 4 project using Angular CLI and incorpora ...

Unable to retrieve dynamically generated object property from an array in AngularJS 2+

Here is an example of an items array: this.itemList = [ { id: 1, name: 'a', address: 'as dasf a' }, { id: 2, name: 'b', address: 'as dasf a' }, { id: 3, name: 'c', address: 'as dasf a' } ]; ...

What could be causing the input event not to be triggered consistently when I select or highlight text?

I have implemented a 4-digit pin field with a specific behavior: when a field is filled, the focus automatically shifts to the next field (the cursor moves to the next input field). If text in a field is deleted, the text in that field is also removed and ...

Struggling to get Vuetify tabs to work alongside masonry.js, any solutions available?

As I work on building a photo gallery using vuetify and masonry.js, my goal is to have multiple tabs with images laid out in a masonry style. While using masonry.js for the layout and vuetify for the tabs, I encountered an issue where only the initial tab ...

Execute a self-invoking JavaScript function with dynamic code

I'm facing a challenging problem that I just can't seem to solve... There's a function on another website that I need to use, but unfortunately, I can't modify it The code in question is: Now, I am looking to add a prototype "aaa" to ...

display a different selection option depending on the previously chosen item

I'm working on displaying additional options in a select box when the user makes a selection from the main select box. I've set display:none for the select boxes, which will only appear when the user chooses an option from the main select box. C ...

Error: jQuery JSONP syntax error due to missing semicolon before statement

Currently, I have implemented the following code to access a rest service that is hosted on a different domain: $.ajax({ type: 'GET', url: url, async: false, jsonpCallback: 'jsonCallback' ...

What is the method for displaying x-axis dates below a highchart?

I'm encountering an issue with Highcharts where dates are not showing under the chart when passing series data as an array of objects. See result image The documentation mentions using an object instead of an array ([1649153340000, 45]docs I need t ...

Error: The function being called in React S3 is actually not an object

Currently, I am in the process of implementing a form that allows me to create a record in my database and upload images to S3. However, I am encountering the following errors: TypeError: Object(...) is not a function 2 New.js:161 SyntaxError: JSON.parse: ...