I need to create a way for a user to open a web page, choose from a list of checkboxes in a form, and once the form is submitted, download all of those files together.
Here are the restrictions imposed on me by the client:
- Platform is primarily mobile devices
- No zip files (since we cannot assume that the mobile device can handle zips)
- Files MUST be downloaded, not streamed
So I created a web app using XHTML/CSS w/ jQuery Mobile 1.0a3 on the front-end and Apache w/ Python 2.6 on the back-end. The target files that will be downloaded are .mp3 files.
I have successfully managed to perform the desired effect on the desktop using hidden iframes passed from the server and loaded by jQuery via AJAX... but it doesn't work on the default Android browser or Dolphin browser.
I made sure my Apache config will force download behavior:
<Files *.mp3>
ForceType application/octet-stream
Header set Content-Disposition attachment
</Files>
Also, Apache's "headers" module is enabled (needed for the "Header set" config parameter), so that's not an issue.
I make an AJAX call to the server with all of the selected items as parameters, and when the server reads the array of items, it will query the database for information about each item (such as the URL of each mp3 file). Then the iframe code is created on the backend for each mp3 file and then sent back to jQuery's $.load()
function to load the new iframes (which downloads the mp3s).
Without pasting too much code, here's a very short test case of what I'm doing:
Server Side
def download(req):
resultDiv = """<div id="downloads">"""
queryIds = []
for element in req.form:
# "element" contains the id number that matches database record
trackId = re.match('^track(\d+)', element).group(1)
queryIds.append(trackId)
conn = MySQLdb.connect(host='localhost', user='fake', passwd='fake', db='fake')
cursor = conn.cursor()
buildQuery = """\
SELECT filePath FROM tracks
WHERE trackNum in ("""
buildQuery += ','.join(queryIds)
buildQuery += ')'
cursor.execute(buildQuery)
downloadRows = cursor.fetchall()
for track in downloadRows:
resultDiv += """
<iframe src="%s"></iframe>
""" % track[0]
return resultDiv
Client Side
<!DOCTYPE html>
<html>
<head>
<!-- INCLUDES FOR JQUERY MOBILE AND JQUERY -->
<style type="text/css">
.invisible {
display: none;
}
</style>
<script type="text/javascript">
$(document).ready(function() {
$('#albumForm').submit(function(e) {
e.preventDefault();
// this will hold the selected items on the form
selTracks = {};
$('#trackList').find(':checked').each(function() {
selTracks[this.id] = 'on';
});
// load the iframes into a 'div' set aside for that purpose
$('#results').load('control.py/download #tracks', selTracks);
});
});
</script>
</head>
<body>
<div data-role='page' id='page'>
<div data-role='header' id='header'>
</div>
<div data-role='content' id='content'>
<div id='container'>
<form id='albumForm'>
<div data-role='controlgroup' data-role='fieldcontain'>
<input type='checkbox' name='track1' id='track1' />
<label for='track1' id='track1label'>Track 1</label>
<input type='checkbox' name='track2' id='track2' />
<label for='track2' id='track2label'>Track 2</label>
<input type='checkbox' name='track3' id='track3' />
<label for='track3' id='track3label'>Track 3</label>
<input type='submit' id='downloadButton' value='Download' />
</div>
</form>
</div>
<div id='results' class='invisible'>
</div>
</div>
<div data-role='footer' id='footer'>
</div>
</div>
</body>
</html>
Sorry the code is so generic (and significantly shortened), but I'm not authorized to post the actual code (you know how it is). But this is basically the gist of it; I believe the problem lies somewhere either in the mobile browser's interpretation, or maybe in the HTTP headers somewhere? This WORKS in Chrome and Firefox on the desktop, and it actually works exactly as expected in Fennec for Android (it downloads all the files with no further interaction and just displays them in the notification bar). I just can't assume everyone's using Fennec (which they're not, lol).
In addition to the above, I've tried the following (which have all worked on desktop but not on mobile):
- JSON returned from the server, and iframes created on the client by jQuery
- JSON returned from the server, and a for-loop to call
window.open()
for each URL - JSON returned from the server, and
<a>
tags created by jQuery and aclick()
triggered - Use different DOCTYPEs
Here's what I tried that didn't work on either desktop or mobile:
- Modifying
location.href
orwindow.location
(can only do it once, obviously) - Calling
req.sendfile()
on the server (maybe I'm doing it wrong?) - Returning multipart/form-data and dumping binary data with a set boundary from the server (VERY messy, and maybe I'm also doing this one wrong?)
Still no joy; What could I be missing?
P.S. Please don't flame me for using hidden iframes...
EDIT: I'll even be okay going with another native browser protocol that I can setup on the server, such as FTP. All ideas are welcome.
UPDATE: Am trying to initiate an FTP connection from the client to the server and run a "mget". I know net2ftp can do this... now to figure it out ;) Still up for new ideas.