2-Stage Ajax File Download with YUI

Consider the following scenario:
  • You create a page with a Download PDF link.
  • When the user clicks the link, this invokes a back-end process that first generates the pdf then streams it back to the client.
  • The PDF generation step can take 10 seconds or more, during which time your page will appear slow and unresponsive to the user.
A better solution would be:
  • User clicks Download PDF link.
  • User is shown a 'Please wait, generating PDF' modal dialog while your back-end process is busy.
  • Once the PDF is available, the modal dialog disappears and the browser Download File dialog appears.
This can be achieved quite easily with some simple Ajax. The basic process is to split the download task into two discrete operations. The first will generate the actual PDF file and store it on the back-end server, returning to the user a filename, file hash or some other ticket id. The second operation takes this ticket id and streams the file content to the user's browser. The file is downloaded using an invisible iframe.

The following source-code demonstrates the process using YUI, although almost any library could be used.

/**
* This simply shows a Busy modal dialog with
* given message prompt. Function returns a handle
* to the dialog so it can be later hidden.
*/
function yuiBusy(message) {
modalDialog = new YAHOO.widget.SimpleDialog("Please wait...",
{ width: "300px",
fixedcenter: true,
modal:true,
visible: false,
draggable: true,
close: false,
text: message,
icon: null,
constraintoviewport: true,
buttons: []
}
);
modalDialog.setHeader("Please wait...");
modalDialog.render("doc3");
modalDialog.show();
return modalDialog;
}

/**
* An indirect way of downloading files via Ajax. The given URL is
* first queried asynchronously to get a file download name/hash.
* This may take a while, especially when downloading PDFs that need to
* be generated by the system. During this time, a busy dialog is shown
* with the given message. Once the actual file link is returned, the
* busy dialog is hidden and the file is downloaded via an invisible iframe.
* The user never leaves the original page.
*/
function ajaxDownload(url, message) {
var callback = {
success: function(o) {
busyDialog.hide(); //got the file url, hide the busy box
xml = xml2array(o.responseXML);

if(xml['response']['success'] == 'true') {
//generate a hidden iframe and make it download the returned URL
var iframe = document.createElement("iframe");
iframe.src = '/file/download/file/' + xml['response']['data'] + '/fname/uos.pdf';
iframe.style.display = "none";
document.body.appendChild(iframe);
} else {
yuiErrorAlert(
'Service Error',
'Server responded with an error: ' + xml['response']['prompt']
);
}
},
failure: function(o) {
yuiErrorAlert(
'Service Error',
'Failed retrieving data from server. Please refresh the page and try again.'
);
}
};

busyDialog = yuiBusy(message); //show a busy dialog
YAHOO.util.Connect.asyncRequest('GET',url, callback); //make an ajax call to get the file url
}

/**
* A yui themed error alert dialog box.
*/
function yuiErrorAlert(title, message) {
return yuiAlert(title, message, YAHOO.widget.SimpleDialog.ICON_ERROR);
}


/**
* A yui themed alert box with title and message.
* Same functionality as a standard alert().
*/
function yuiAlert(title, message, icon) {
modalDialog = new YAHOO.widget.SimpleDialog(title,
{ width: "300px",
fixedcenter: true,
modal:true,
visible: false,
draggable: true,
close: true,
text: message,
icon: icon,
constraintoviewport: true,
buttons: [ { text:"Ok", handler:function(){this.hide();}, isDefault:true }]
}
);
modalDialog.setHeader(title);
modalDialog.render("doc3");
modalDialog.show();
return false;
}

Note that the code above will NOT work out-of-box for you. You'll need to modify the iframe.src url link as appropriate for your application (the files are not downloaded directly, but rather go through a downloader script that interprets the id and uses a 'Content-Disposition: inline' header to stream the file back).

You'll also need to modify the logic to extract the file ticket from the success response. I've used xml2array to parse my REST service response, you'll need to adjust for your own message structure.

Comments

  1. A working source code of this example would be greatly apreciated. I have tried to decifer and implement this for my own purposes and have difficulty translating it.

    ReplyDelete

Post a Comment

Popular posts from this blog

Wkhtmltopdf font and sizing issues

Import Google Contacts to Nokia PC Suite

Can't delete last blank page from Word