PDA

View Full Version : Ext.ux.DownloadIframe -> opening downloads in an iframe



eztam
14 Jul 2010, 12:29 AM
Hi,

I needed to open downloads in an iframe that a download will not be blocked by a popup blocker.
So I wrote a little class to load my download into an iframe:



Ext.ux.DownloadIframe = Ext.extend(Ext.util.Observable, function ()
{
var BEFOREDOWNLOADSTART = "beforedownloadstart",
DOWNLOADSTART = "downloadstart",
iframe = null,
loadMask = null;

return {
constructor: function (config)
{
if (!Ext.isEmpty(config))
{
Ext.apply(this, config, {
"name": "Ext_ux_DownloadIframe"
});
}

this.superclass().constructor.apply(this, arguments);
},
initComponent: function ()
{
this.superclass().initComponent.call(this);

this.addEvents(
BEFOREDOWNLOADSTART, DOWNLOADSTART);
},
start: function (download, loadMaskCfg)
{
if (iframe !== null) iframe.destroy();

var on =
{
};
on =
{
"fn": this.onBeforeDownloadStart,
"single": true,
"scope": this
};
on[DOWNLOADSTART] =
{
"fn": this.onDownloadStart,
"single": true,
"scope": this
};
this.on(on);


if (!Ext.isEmpty(loadMaskCfg))
{
if (loadMaskCfg instanceof Ext.Element)
{
loadMaskCfg =
{
"el": loadMaskCfg
};
}

var lmCfg =
{
"msg": "Loading..."
},
el = loadMaskCfg.el || Ext.getBody();

Ext.apply(lmCfg, loadMaskCfg);

loadMask = new Ext.LoadMask(el, lmCfg);
}

iframe = new Ext.BoxComponent(
{
"width": 0,
"height": 0,
"autoEl": {
"tag": "iframe",
"name": this.name,
"frameBorder": 0,
"src": download,
"style": {
"border": "0 none"
}
}
});

this.fireEvent(BEFOREDOWNLOADSTART, this, iframe, download);
iframe.render(this.renderTo || Ext.getBody());

iframe.el.dom.onload = this.onload.createDelegate(this, [this, iframe, download]);
},
onload: function (o, iframe, download)
{
o.fireEvent(DOWNLOADSTART, o, iframe, download);
},
onBeforeDownloadStart: function (o, iframe, download)
{
if (loadMask !== null) loadMask.show();
},
onDownloadStart: function (o, iframe, download)
{
if (loadMask !== null) loadMask.hide();
}
}
}());
Sample usage:


[B]var download = new Ext.ux.DownloadIframe();
var downloadBtn = new Ext.Button({
"text": "download",
"handler": function() {
download.start("url/to/downloadfile.zip", true);
}
});
btn.render("content");
-> On button click the download will open and a loadMask (on body) will appear till the download starts.


Greets eztam!

Condor
14 Jul 2010, 6:06 AM
I assume you are downloading files with Content-disposition:attachment? In that case you will never get a load event on the IFRAME, so you would never hide the loadmask.

eztam
14 Jul 2010, 11:06 PM
I assume you are downloading files with Content-disposition:attachment? In that case you will never get a load event on the IFRAME, so you would never hide the loadmask.
I was testing in Firefox and the loadMask correctly disappeared when the download was ready. But I think you're right, because in IE the loadMask will not be hidden.

Is there anything I can do to fix this?

Condor
14 Jul 2010, 11:28 PM
No, I've tried everything, but you just can't get notified when the download finishes.

eztam
14 Jul 2010, 11:44 PM
No, I've tried everything, but you just can't get notified when the download finishes.
My downloads are generated in a php script, so if I want to show a loadMask while download is generated, I have to generate it asynchronously (showing the loadMask till request is done), temporarily save the download, return the link and load the returned link into the iframe (without loadMask). Then I could delete the temporary file on download.
This is the only solution!?

Condor
15 Jul 2010, 12:36 AM
Looks ok, but I wouldn't remove the temporary file on download - the user might want to retry. Instead I would use a batch job to delete old temporary files.

chaos
15 Jul 2010, 2:02 AM
My downloads are generated in a php script, so if I want to show a loadMask while download is generated, I have to generate it asynchronously (showing the loadMask till request is done), temporarily save the download, return the link and load the returned link into the iframe (without loadMask). Then I could delete the temporary file on download.
This is the only solution!?

As Condor said there's no way to get notification when the download finishes, so I solved using the browser defaults.
Pratically when the form is submited (see code below) the browser open a new blank window during the server processing (e.g. report creation). When the processing is done the window is automatically closed by the browser and the 'normal' browser file download process begin ('save dialog' appears...etc..).

advantage:
- User can continue to work while the server is processing the request and while the browser is downloading the file.
- The progress of the file download is managed by the browser so it is visibible (in the status bar or in the download window...depends on the browser)
- Multiple file download at the same time (not possible with the iframe solution if the download iframe is always the same)

disadvantage:
- blank window during the long server process.
- no custom load mask


this is my code:



exportReport : function(url, params) {

// cannot add fields directly to the form due an IE bugs
// that doesn't permit to change the form's action (url)
// if inside there is a field named 'action'
//
// workaround: create the form, set the form's action and finally add the fields
var form = new Ext.form.FormPanel({
url: url,
hidden : true,
hideMode: 'offsets',
method: 'POST',
standardSubmit : true,
items: [{xtype: 'hidden', name : 'dummy', value: ''}] // extJs required at least on field
});

form.render(document.body);

// set the url and the target of the form
form.getForm().getEl().dom.action = url;
form.getForm().getEl().dom.target = "_blank"; // this is the trick!!!!

// create hidden fields and add them to the form
if(params) {
for (p in params) {
form.add({
xtype: 'hidden',
name : p,
value: params[p]
});
}
}

// render the fields
form.doLayout();

// form submit
form.getForm().submit();

// destroy form and garbage stuff
form.destroy();
delete form;
}


ciao
-Mario