Table of Contents

AJAX-like jQuery File Download

jQuery File Download is a file downloader plugin that make the downlaod look like ajax. The way it does the download is to use a hidden html iframe + server edit cookie to signal the front end the download it completed. It is developed by johnculviner.com. Here we only covered what I would like to be covered. For more details, always go to the offical site https://github.com/johnculviner/jquery.fileDownload.

Install

Go to https://github.com/johnculviner/jquery.fileDownload to download the javascript file for the plugin. This plugin need Jquery version 1.6+.

Client Side

Assume we have a <button type='button' id='btn-save'>Download<button> within a HTML form. and here is what the callback for this button looks like. Let's say

showLoadingModal(); //SHOW YOUR LOADING SCREEN
var form = $(this).closest('form').get(0);
$.fileDownload($(form).prop('action'), {
	httpMethod: "POST",
	data: $(form).serialize(),
	successCallback: function (url) {
		hideLoadingModal(); //HIDE YOUR LOADING SCREEN
		//YOUR SUCCESS CALLBACK, URL IS YOUR REQUSET URL
	},
	failCallback: function (html, url) {
		hideLoadingModal(); //HIDE YOUR LOADING SCREEN
		//YOUR FAIL CALLBACK, html IS THE RESPONSE HTML FROM SERVER, URL IS YOUR REQUSET URL
	},
	abortCallback: function (url) {
		hideLoadingModal(); //HIDE YOUR LOADING SCREEN
		//YOUR ABORT CALLBACK, URL IS YOUR REQUSET URL
	}
});

Server Side (In Grails)

The url from the client side should be calling one of your controller's method. Assume the client pass an 'id' field to the server. DomainObject can be any of your domain object that mapped to your data source, myService supposed to be any grails server that handle your business logic; MyException could be any of your own exception thrown by your business logic, and DateTimeTools is my own creation to handle date time related function call. Also assume the file we are going to download is a docx.

Basically we need to do:

def myFunction(DomainObject domainObject) {
	try {
            byte[] yourBytes = //get the byte of array in your own way, may be to do something with your domainObject or database.
            String filename = "YOUR_FILE_NAME.docx"
            
            //Setup cookie to alert the client side.
            Cookie cookie = new Cookie( 'fileDownload', 'true' )
            cookie.path = '/'
            cookie.maxAge = 5
            response.addCookie(cookie)

            response.setHeader("Content-Disposition", "attachment; filename=\"" + filename+"\"")
            response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document")
            
            response.outputStream.write(yourBytes)
            response.outputStream.flush() //Flushing is important! because the server
	}catch (MyException e) {
		response.sendError(503, "Fail to download. Please try again later.  ${e.getMessage()}")
	}catch (Exception e) {
		log.error("Time: ${DateTimeTools.getSystemCurrentTime()} myFunction: ${e.getMessage()}", e)
		response.sendError(503, "Fail to download. Please try again later.")
	}
}

Limitation

Not support well on mutiple tabs for the same site.

Since there is only one cookie to tell the download is completed. If there is multiple tabs in the same web browser window download the files from the same site with this plugin, the plugin would not know what tab really finished the download, and one of the tab will call its successCallback, or failCallback wrongly. The actual download action will not be affected though.