4

I have seen questions slightly related to this, but none that answer my problem. I have set up an Ext.Ajax.request as follows:

var paramsStringVar = 'param1=1&param2=two&param3=something&param4=etc';

Ext.Ajax.request({
  url: '/cgi-bin/url.pl',
  method:'POST',
  params:paramsStringVar,
  timeout:120000,
  success: function(response, opts){
    var objhtml = response.responseText; //content returned from server side
    console.log(objhtml);
  }

});

This request retrieves the appropriate content from the backend. One parameter is outputType, which can take values {html, excel, csv}. When returning html to display I am able to handle and display it correctly. Now on to the problem...

When I set the outputType parameter to csv or excel, I get back the appropriate content as csv or tsv(excel) as requested. BUT, I don't want the content, I want a prompt to download the file(csv or excel). How can I have the browser auto prompt the user to download the file instead of just retrieving the text content within extjs?

Version 4.07 so I can't use any 4.1 only features

luca76
  • 813
  • 1
  • 10
  • 20
Bbb
  • 271
  • 2
  • 8
  • 22

4 Answers4

7

There seems to be no bulletproof solution but there are several approaches I would try:

1) Use an iframe instead of real XHR to POST data to the server, e.g. <form action="/something" target="myiframe"> where myiframe is the name of your hidden iframe. That way your form would use the iframe (not your main window) to submit data to the configured URL. Your server should set response header as application/octet-stream (or some ither MIME type for binary data) so the browser triggers download. Otherwise (if html returned in your case) you can just retrieve iframe's body innerHTML and display it to the user in UI. While using an iframe (or a new window) instead of XHR doesn't sound like the best idea, this solution seems to be the most reliable so far (and with best browser support).

Here is a slightly modified example from Ext.form.Basic docs page:

Ext.create('Ext.form.Panel', {
    title: 'Basic Form',
    renderTo: Ext.getBody(),
    width: 350,

    // Any configuration items here will be automatically passed along to
    // the Ext.form.Basic instance when it gets created.

    // *THIS* makes the form use a standard submit mechanism, not XHR
/**/standardSubmit: true,

    // URL to submit to
    url: 'save-form.php',

    items: [{
        fieldLabel: 'Field',
        xtype: 'textfield',
        name: 'theField'
    }],

    buttons: [{
        text: 'Submit',
        handler: function() {
            // The getForm() method returns the Ext.form.Basic instance:
            var form = this.up('form').getForm();
            if (form.isValid()) {
                // Submit the Ajax request and handle the response
                form.submit({
                    success: function(form, action) {
                       Ext.Msg.alert('Success', action.result.msg);
                    },
                    failure: function(form, action) {
                        Ext.Msg.alert('Failed', action.result.msg);
                    },

                    // You can put the name of your iframe here instead of _blank
                    // this parameter makes its way to Ext.form.Basic.doAction() 
                    // and further leads to creation of StandardSubmit action instance
/**/                target: '_blank'                        
                });
            }
        }
    }]
});

There are two key parameters here (lines marked with /**/):

  1. standardSubmit: true config that you pass to your form will make it do a standard submit instead of XHR.
  2. Passing a target parameter to the form's submit action. This feature is not documented but you can see it being used in Ext.form.action.Submit source code (all options that you pass to Ext.form.Basic.submit() method end up as parameters of Ext.form.action.* instance.

In the example code I put target: '_blank' to demonstrate that it works right away (will create a new browser window). You can replace it with the name of your iframe later but I suggest that you first test how your form submits data to a regular new window and then develop logic that creates and processes an iframe. You will have to process the result inside iframe yourself, thought. It's not that difficult, see Ext.data.Connection.upload() implementation as an example of iframe processing.

ExtJS actually already uses the iframe technique for file uploads. See Ext.data.Connection and Ext.form.field.Field.isFileUpload() for an idea of how it can work.

2) Suggested here: Using HTML5/Javascript to generate and save a file.

If you don't want to go the iframe way, you can try generate data URI from response data and navigate to that URI triggering download:

content = "Hello world!";
uriContent = "data:application/octet-stream," + encodeURIComponent(content);
window.location.href = uriContent;

Again, mimetype is essential here. This worked for me, you should note, however, that browsers impose a size limit to data URIs (256Kb is a safe bet).

3) Another answer in the mentioned thread links to FileSaver.js library the implements the (abandoned?) w3 spec. Usage and demo here. It uses [BlobBuilder] to generate a blob of binary data that is further used to initialize downloads using one of several methods. While this solution seems to work, it uses deprecated APIs and may not be future-proof.

Community
  • 1
  • 1
Dmitry Pashkevich
  • 13,431
  • 9
  • 55
  • 73
  • When you say, 'Use an iframe' do you mean insert an iframe(hidden) into my panel and then what? – Bbb Aug 03 '12 at 17:06
  • DO I want to embed HTML for hidden form fields in the Iframe? If so, then how in extjs do I set the values of the fields then post the form? – Bbb Aug 03 '12 at 17:31
  • I updated point 1) in my answer. You don't put your fields inside the iframe, you leave them where they are right now in your app, you just configure your form to submit data into an iframe and using a regular synchronous request, not XHR. See [Ext.form.Basic.standardSubmit](http://docs.sencha.com/ext-js/4-0/#!/api/Ext.form.Basic-cfg-standardSubmit). – Dmitry Pashkevich Aug 04 '12 at 11:09
  • How to distinguish between XHR versus regular synchronus request? As opposed to Ext.Ajax.Request, what would I use to make the request? – Bbb Aug 06 '12 at 13:47
  • Could you provide some sample code? I haven't done this before, and I must be missing a step. I am posting to the Iframe, but how? – Bbb Aug 06 '12 at 14:02
  • Also, how do I set a form's target in extjs? Ext.form.Panel has no 'target' config. Should I insert the form as html within a panel instead of a true extjs 'form' xtype? – Bbb Aug 06 '12 at 15:29
  • Updated my answer. Hope that helps but feel free to ask further questions. This situation is uncommon in ExtJS so it requires a bit of hacking around, although the framework's flexibility still gives you some neat configuration options out-of-the-box. – Dmitry Pashkevich Aug 06 '12 at 19:00
  • I'm a little unsure on where to put the iframe? I suppose in the view inside of my panel's html. But, if I do that how do I access it will the 'target' config know where to look?? – Bbb Aug 06 '12 at 19:20
  • You can put your iframe wherever you want in the document, it's hidden after all. Take a look at [Ext.form.field.Field.isFileUpload()](http://docs.sencha.com/ext-js/4-0/source/Connection.html#Ext-data-Connection-method-upload) source, it's simple. You should give your iframe an `id` and set it as `target` for your form. – Dmitry Pashkevich Aug 06 '12 at 20:30
  • But as I said above in my answer, if iframes confuse you, try without them first, just submit your form to a new window (`target="_blank"`). And make sure you get the expected response from server. – Dmitry Pashkevich Aug 06 '12 at 20:32
  • If I have a variable number of fields, can I build the json object for items as a string then insert the variable? I'm asking because initially I thought I would have an html form, now I realize I should have an actual Extjs form – Bbb Aug 07 '12 at 12:50
  • Yes. See [FormPanel.getValues()](http://docs.sencha.com/ext-js/4-0/#!/api/Ext.form.Panel-method-getValues), then you can use [Ext.JSON.encode()](http://docs.sencha.com/ext-js/4-0/#!/api/Ext.JSON-method-encode) to stringify it – Dmitry Pashkevich Aug 07 '12 at 14:12
  • I need to set the values in the form first. Maybe I am confused. I have all the values in various variables, so this will really be hidden form fields. I already have the parameters, but how should they be encoded, and should they become the items config or params???? – Bbb Aug 07 '12 at 14:30
  • If you already have an URL-encoded string of params then you can pass them as `params` config to Ext.form.Basic.submit(). Read [Ext.form.Basic.doAction()](http://docs.sencha.com/ext-js/4-0/#!/api/Ext.form.Basic-method-doAction) for a list of parameters that can be passed to submit() method! – Dmitry Pashkevich Aug 07 '12 at 15:52
  • Target iframe is not neccesary – Bbb Aug 07 '12 at 19:48
6

Below is my solution. This is how I have it currently working. The response generates a download/open prompt, based on a response type of text/csv. Note that no iFrame or reference to an iframe are needed. I spent a lot of time hung up on the need for an iFrame, which actually broke my solution. An iFrame is not needed to generate a download prompt. What is needed is a request(submittal) similar to this one, along with a backend generating the appropriate csv with text/csv response header.

var hiddenForm = Ext.create('Ext.form.Panel', {
  title:'hiddenForm',
  standardSubmit: true,
  url: /cgi-bin/url.pl
  timeout: 120000,
  height:0,
  width: 0,
  hidden:true,
  items:[
    {xtype:'hiddenField', name:'field1', value:'field1Value'},
    // additional fields
  ]
})

hiddenForm.getForm().submit()

The standardSubmit line is vital

Bbb
  • 271
  • 2
  • 8
  • 22
  • Looks like we misunderstood each other a little bit. Yes, you don't need an iframe to trigger download. But you mentioned that you also have an html response that you wish to process and display somewhere in your interface, as I understood (`When returning html to display I am able to handle and display it correctly.`). You would need an iframe for processing non-downloaded response. But anyway, I suggested you to first try the code without the iframe (with target _blank) before you "waste time". – Dmitry Pashkevich Aug 07 '12 at 20:52
  • 1
    The target: _blank is not required in my case. I mentioned in the initial post, "When returning html to display I am able to handle and display it correctly." My issue was strictly with triggering the download. – Bbb Aug 07 '12 at 21:00
  • I use this approach in a success callback of an ajax-form. This way, if the submit of the ajax form succeeds I will execute a standardSubmit to offer the resource stored in the session for download. If the ajax submit fails I show an error message in a panel. – mwhs Oct 22 '13 at 15:39
  • I cant attach a listener to my submit method, as I need to cleanup after this executes, did you have any luck? – Patrick Nov 03 '14 at 16:25
0

You don't need to create a form panel and make it hidden in your extjs file. We can add a html form and on click of button in extjs file we can submit the form using the url. This will work both in IE as well as chrome browsers. Below is my code i tried and its working fine,

<form action="<%=fullURL%>/DownloadServlet.do" method="get" id="downloadForm"   name="downloadForm" target="_self">
</form>

click: 
  {
     fn: function()
    { 
       document.getElementById('downloadForm').submit();    
    }
  }   
0

To get it working on ExtJS 3.4:

        var hiddenForm = new Ext.FormPanel({
            id:'hiddenForm',
            region: 'south',
            method: 'POST',
            url: "/cgi/test.wsgi",
            height: 0,
            standardSubmit: true,
            hidden:true,
            items:[
                {xtype:'hidden', name:'p', value:p},
                {xtype:'hidden', name:'g', value:g},
                // ...
            ],
        });

        linkThis = new Ext.Button({
            text: 'Download this CSV',
            handler: function() {
                hiddenForm.getForm().submit();
            },
            maxHeight: 30,
        });

Remember that in order to make it working, you should put the hiddenForm in any container (i.e. in the same Ext.Window of the button), for example:

  risultatiWindow = new Ext.Window({
            title: 'CSV Export',
            height: 400,
            width: 500,
            ....
            items: [...., hiddenForm]
 });
luca76
  • 813
  • 1
  • 10
  • 20