Skip to content

Commit f9486e6

Browse files
committed
Merge branch 'form-data'
Conflicts: README.md
2 parents 5590329 + 6aff57b commit f9486e6

File tree

5 files changed

+198
-150
lines changed

5 files changed

+198
-150
lines changed

README.md

+5-7
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,18 @@
22

33
A simple application written in Ruby, using the Sinatra framework. Hopefully it should be easy to read even to those who don't know Ruby.
44

5-
To run it, just do:
5+
To run it, do:
66

7-
rackup
7+
rackup -I.
88

99
And the application should be accessible at `http://localhost:9292`.
1010

11-
If you want to know more, have a look at our [blog post covering Ajax file uploads](http://blog.new-bamboo.co.uk/2010/7/30/html5-powered-ajax-file-uploads)
11+
# What to look at?
1212

13-
# Rack::RawUpload
14-
15-
This example includes and uses a piece of middleware called [Rack::RawUpload](http://github.com/newbamboo/rack-raw-upload), also by us. It makes handling this kind of uploads much easier.
13+
The important bit is the client code, found at `/public/index.html`
1614

1715
# Not really HTML5
1816

1917
Although initially I (Pablo Brasero) referred to this technique as HTML5 uploads, this is actually correct. The interfaces used are described in two separate specifications, different from HTML5. These are *XMLHttpRequest level 2* and the *File API*.
2018

21-
Also, remember that these specifications are, at the time of writing, working drafts. They could change in the future, before they become full-fledged W3C recommended standards.
19+
Also, remember that these specifications are, at the time of writing, working drafts. They could change in the future, before they become full-fledged W3C recommended standards.

config.ru

-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
require 'rubygems'
2-
require 'rack'
32
require 'sinatra'
43
require 'example_ajax_upload'
54

6-
use Rack::RawUpload
75
run Sinatra::Application

example_ajax_upload.rb

+21-76
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,32 @@
11
require 'sinatra'
2-
require 'json'
3-
require 'lib/rack/raw_upload'
2+
require 'pp'
43

54
get '/' do
6-
HTML_CODE
5+
send_file File.join(settings.public_folder, 'index.html')
76
end
87

98
post '/' do
10-
JSON.generate(params[:file])
9+
our_file = params.delete('our-file')
10+
"Received form fields:\n\n" + pretty_str(params) +
11+
"\nAnd uploaded file:\n\n" + file_info(our_file)
1112
end
1213

14+
post '/upload' do
15+
file_info params['our-file']
16+
end
1317

14-
#
15-
# Javascript code
16-
#
17-
18-
JS_CODE =<<-JS
19-
document.getElementById('the-file').onchange = function () {
20-
var fileInput = document.getElementById('the-file');
21-
var file = fileInput.files[0];
22-
23-
var xhr = new XMLHttpRequest();
24-
xhr.upload.addEventListener('loadstart', onloadstartHandler, false);
25-
xhr.upload.addEventListener('progress', onprogressHandler, false);
26-
xhr.upload.addEventListener('load', onloadHandler, false);
27-
xhr.addEventListener('readystatechange', onreadystatechangeHandler, false);
28-
xhr.open('POST', '/', true);
29-
xhr.setRequestHeader("Content-Type", "application/octet-stream");
30-
xhr.setRequestHeader("X-File-Name", file.name);
31-
xhr.send(file); // Simple!
32-
33-
function onloadstartHandler(evt) {
34-
$('#upload-status').html('Upload started!');
35-
}
36-
37-
function onloadHandler(evt) {
38-
$('#upload-status').html('Upload successful!');
39-
}
40-
41-
function onprogressHandler(evt) {
42-
var percent = evt.loaded/evt.total*100;
43-
$('#progress').html('Progress: ' + percent + '%');
44-
}
45-
46-
function onreadystatechangeHandler(evt) {
47-
var status = null;
48-
49-
try {
50-
status = evt.target.status;
51-
}
52-
catch(e) {
53-
return;
54-
}
55-
56-
if (status == '200' && evt.target.responseText) {
57-
$('#result').html('<p>The server saw it as:</p><pre>' + evt.target.responseText + '</pre>');
58-
}
18+
def file_info(file)
19+
details = {
20+
:filename => file[:filename],
21+
:type => file[:type],
22+
:head => file[:head],
23+
:name => file[:name],
24+
:tempfile_path => file[:tempfile].path,
25+
:tempfile_size => file[:tempfile].size,
5926
}
60-
}
61-
JS
62-
63-
64-
#
65-
# HTML code
66-
#
67-
68-
HTML_CODE =<<-HTML
69-
<!DOCTYPE html>
70-
<html lang="en">
71-
<head>
72-
<meta charset="utf-8" />
73-
<title>Ajax upload form</title>
74-
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
75-
</head>
76-
<body>
27+
pretty_str details
28+
end
7729

78-
<input id="the-file" type=file name=file />
79-
<script>
80-
#{JS_CODE}
81-
</script>
82-
<p id="upload-status"></p>
83-
<p id="progress"></p>
84-
<div id="result"></div>
85-
</body>
86-
</html>
87-
HTML
30+
def pretty_str(obj)
31+
''.tap{|output| PP.pp(obj, output) }
32+
end

lib/rack/raw_upload.rb

-65
This file was deleted.

public/index.html

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>Ajax upload form</title>
6+
</head>
7+
<body>
8+
9+
<!--
10+
By default, we assume Ajax uploads are not supported.
11+
Later we'll detect support and change this message if found.
12+
-->
13+
<p id="support-notice">Your browser does not support Ajax uploads :-(<br/>The form will be submitted as normal.</p>
14+
15+
<!-- The form starts -->
16+
<form action="/" method="post" enctype="multipart/form-data" id="form-id">
17+
18+
<!-- The file to upload -->
19+
<p><input id="file-id" type="file" name="our-file" />
20+
21+
<!--
22+
Also by default, we disable the upload button.
23+
If Ajax uploads are supported we'll enable it.
24+
-->
25+
<input type="button" value="Upload" id="upload-button-id" disabled="disabled" /></p>
26+
27+
<!-- A different field, just for the sake of the example -->
28+
<p><label>Some other field: <input name="other-field" type="text" id="other-field-id" /></label></p>
29+
30+
<!-- And finally a submit button -->
31+
<p><input type="submit" value="Submit" /></p>
32+
33+
<script>
34+
// Function that will allow us to know if Ajax uploads are supported
35+
function supportAjaxUploadWithProgress() {
36+
return supportFileAPI() && supportAjaxUploadProgressEvents() && supportFormData();
37+
38+
// Is the File API supported?
39+
function supportFileAPI() {
40+
var fi = document.createElement('INPUT');
41+
fi.type = 'file';
42+
return 'files' in fi;
43+
};
44+
45+
// Are progress events supported?
46+
function supportAjaxUploadProgressEvents() {
47+
var xhr = new XMLHttpRequest();
48+
return !! (xhr && ('upload' in xhr) && ('onprogress' in xhr.upload));
49+
};
50+
51+
// Is FormData supported?
52+
function supportFormData() {
53+
return !! window.FormData;
54+
}
55+
}
56+
57+
// Actually confirm support
58+
if (supportAjaxUploadWithProgress()) {
59+
// Ajax uploads are supported!
60+
// Change the support message and enable the upload button
61+
var notice = document.getElementById('support-notice');
62+
var uploadBtn = document.getElementById('upload-button-id');
63+
notice.innerHTML = "Your browser supports HTML uploads. Go try me! :-)";
64+
uploadBtn.removeAttribute('disabled');
65+
66+
// Init the Ajax form submission
67+
initFullFormAjaxUpload();
68+
69+
// Init the single-field file upload
70+
initFileOnlyAjaxUpload();
71+
}
72+
73+
function initFullFormAjaxUpload() {
74+
var form = document.getElementById('form-id');
75+
form.onsubmit = function() {
76+
// FormData receives the whole form
77+
var formData = new FormData(form);
78+
79+
// We send the data where the form wanted
80+
var action = form.getAttribute('action');
81+
82+
// Code common to both variants
83+
sendXHRequest(formData, action);
84+
85+
// Avoid normal form submission
86+
return false;
87+
}
88+
}
89+
90+
function initFileOnlyAjaxUpload() {
91+
var uploadBtn = document.getElementById('upload-button-id');
92+
uploadBtn.onclick = function (evt) {
93+
var formData = new FormData();
94+
95+
// Since this is the file only, we send it to a specific location
96+
var action = '/upload';
97+
98+
// FormData only has the file
99+
var fileInput = document.getElementById('file-id');
100+
var file = fileInput.files[0];
101+
formData.append('our-file', file);
102+
103+
// Code common to both variants
104+
sendXHRequest(formData, action);
105+
}
106+
}
107+
108+
// Once the FormData instance is ready and we know
109+
// where to send the data, the code is the same
110+
// for both variants of this technique
111+
function sendXHRequest(formData, uri) {
112+
// Get an XMLHttpRequest instance
113+
var xhr = new XMLHttpRequest();
114+
115+
// Set up events
116+
xhr.upload.addEventListener('loadstart', onloadstartHandler, false);
117+
xhr.upload.addEventListener('progress', onprogressHandler, false);
118+
xhr.upload.addEventListener('load', onloadHandler, false);
119+
xhr.addEventListener('readystatechange', onreadystatechangeHandler, false);
120+
121+
// Set up request
122+
xhr.open('POST', uri, true);
123+
124+
// Fire!
125+
xhr.send(formData);
126+
}
127+
128+
// Handle the start of the transmission
129+
function onloadstartHandler(evt) {
130+
var div = document.getElementById('upload-status');
131+
div.innerHTML = 'Upload started!';
132+
}
133+
134+
// Handle the end of the transmission
135+
function onloadHandler(evt) {
136+
var div = document.getElementById('upload-status');
137+
div.innerHTML = 'Upload successful!';
138+
}
139+
140+
// Handle the progress
141+
function onprogressHandler(evt) {
142+
var div = document.getElementById('progress');
143+
var percent = evt.loaded/evt.total*100;
144+
div.innerHTML = 'Progress: ' + percent + '%';
145+
}
146+
147+
// Handle the response from the server
148+
function onreadystatechangeHandler(evt) {
149+
var status = null;
150+
151+
try {
152+
status = evt.target.status;
153+
}
154+
catch(e) {
155+
return;
156+
}
157+
158+
if (status == '200' && evt.target.responseText) {
159+
var result = document.getElementById('result');
160+
result.innerHTML = '<p>The server saw it as:</p><pre>' + evt.target.responseText + '</pre>';
161+
}
162+
}
163+
</script>
164+
165+
<!-- Placeholders for messages set by event handlers -->
166+
<p id="upload-status"></p>
167+
<p id="progress"></p>
168+
<pre id="result"></pre>
169+
170+
</form>
171+
</body>
172+
</html>

0 commit comments

Comments
 (0)