fileuploader-rails 2.1.2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +11 -4
- data/lib/fileuploader-rails/version.rb +1 -1
- data/vendor/assets/javascripts/fineuploader/button.js +102 -0
- data/vendor/assets/javascripts/fineuploader/handler.base.js +120 -0
- data/vendor/assets/javascripts/fineuploader/handler.form.js +197 -0
- data/vendor/assets/javascripts/fineuploader/handler.xhr.js +168 -0
- data/vendor/assets/javascripts/fineuploader/header.js +12 -0
- data/vendor/assets/javascripts/fineuploader/jquery-plugin.js +148 -0
- data/vendor/assets/javascripts/fineuploader/uploader.basic.js +483 -0
- data/vendor/assets/javascripts/fineuploader/uploader.js +575 -0
- data/vendor/assets/javascripts/fineuploader/util.js +319 -0
- data/vendor/assets/stylesheets/fineuploader.css +16 -3
- metadata +17 -3
- data/vendor/assets/javascripts/fineuploader.js +0 -1642
data/README.md
CHANGED
@@ -1,18 +1,25 @@
|
|
1
|
-
# Fine Uploader
|
1
|
+
# Fine Uploader 3.0.0 for Rails
|
2
2
|
|
3
3
|
[Fineuploader](http://fineuploader.com/) is a Javascript plugin written by [Andrew Valums](http://github.com/valums/), and actively developed by [Ray Nicholus](http://lnkd.in/Nkhx2C). This plugin uses an XMLHttpRequest (AJAX) for uploading multiple files with a progress-bar in FF3.6+, Safari4+, Chrome and falls back to hidden-iframe-based upload in other browsers (namely IE), providing good user experience everywhere. It does not use Flash, jQuery, or any external libraries.
|
4
4
|
|
5
|
-
This gem integrates this fantastic plugin with Rails 3.1 Asset Pipeline.
|
5
|
+
This gem integrates this fantastic plugin with Rails 3.1+ Asset Pipeline.
|
6
6
|
|
7
7
|
## Installing Gem
|
8
8
|
|
9
|
-
gem 'fileuploader-rails', '~>
|
9
|
+
gem 'fileuploader-rails', '~> 3.0.0'
|
10
10
|
|
11
11
|
## Using the javascripts
|
12
12
|
|
13
13
|
Require fineuploader in your app/assets/application.js file.
|
14
14
|
|
15
|
-
|
15
|
+
# Basic version
|
16
|
+
//= require fineuploader/uploader.basic
|
17
|
+
|
18
|
+
# Full version
|
19
|
+
//= require fineuploader/uploader
|
20
|
+
|
21
|
+
# Jquery wrapper
|
22
|
+
//= require fineuploader/jquery-plugin
|
16
23
|
|
17
24
|
## Using the stylesheet
|
18
25
|
|
@@ -0,0 +1,102 @@
|
|
1
|
+
qq.UploadButton = function(o){
|
2
|
+
this._options = {
|
3
|
+
element: null,
|
4
|
+
// if set to true adds multiple attribute to file input
|
5
|
+
multiple: false,
|
6
|
+
acceptFiles: null,
|
7
|
+
// name attribute of file input
|
8
|
+
name: 'file',
|
9
|
+
onChange: function(input){},
|
10
|
+
hoverClass: 'qq-upload-button-hover',
|
11
|
+
focusClass: 'qq-upload-button-focus'
|
12
|
+
};
|
13
|
+
|
14
|
+
qq.extend(this._options, o);
|
15
|
+
qq.extend(this, qq.DisposeSupport);
|
16
|
+
|
17
|
+
this._element = this._options.element;
|
18
|
+
|
19
|
+
// make button suitable container for input
|
20
|
+
qq(this._element).css({
|
21
|
+
position: 'relative',
|
22
|
+
overflow: 'hidden',
|
23
|
+
// Make sure browse button is in the right side
|
24
|
+
// in Internet Explorer
|
25
|
+
direction: 'ltr'
|
26
|
+
});
|
27
|
+
|
28
|
+
this._input = this._createInput();
|
29
|
+
};
|
30
|
+
|
31
|
+
qq.UploadButton.prototype = {
|
32
|
+
/* returns file input element */
|
33
|
+
getInput: function(){
|
34
|
+
return this._input;
|
35
|
+
},
|
36
|
+
/* cleans/recreates the file input */
|
37
|
+
reset: function(){
|
38
|
+
if (this._input.parentNode){
|
39
|
+
qq(this._input).remove();
|
40
|
+
}
|
41
|
+
|
42
|
+
qq(this._element).removeClass(this._options.focusClass);
|
43
|
+
this._input = this._createInput();
|
44
|
+
},
|
45
|
+
_createInput: function(){
|
46
|
+
var input = document.createElement("input");
|
47
|
+
|
48
|
+
if (this._options.multiple){
|
49
|
+
input.setAttribute("multiple", "multiple");
|
50
|
+
}
|
51
|
+
|
52
|
+
if (this._options.acceptFiles) input.setAttribute("accept", this._options.acceptFiles);
|
53
|
+
|
54
|
+
input.setAttribute("type", "file");
|
55
|
+
input.setAttribute("name", this._options.name);
|
56
|
+
|
57
|
+
qq(input).css({
|
58
|
+
position: 'absolute',
|
59
|
+
// in Opera only 'browse' button
|
60
|
+
// is clickable and it is located at
|
61
|
+
// the right side of the input
|
62
|
+
right: 0,
|
63
|
+
top: 0,
|
64
|
+
fontFamily: 'Arial',
|
65
|
+
// 4 persons reported this, the max values that worked for them were 243, 236, 236, 118
|
66
|
+
fontSize: '118px',
|
67
|
+
margin: 0,
|
68
|
+
padding: 0,
|
69
|
+
cursor: 'pointer',
|
70
|
+
opacity: 0
|
71
|
+
});
|
72
|
+
|
73
|
+
this._element.appendChild(input);
|
74
|
+
|
75
|
+
var self = this;
|
76
|
+
this._attach(input, 'change', function(){
|
77
|
+
self._options.onChange(input);
|
78
|
+
});
|
79
|
+
|
80
|
+
this._attach(input, 'mouseover', function(){
|
81
|
+
qq(self._element).addClass(self._options.hoverClass);
|
82
|
+
});
|
83
|
+
this._attach(input, 'mouseout', function(){
|
84
|
+
qq(self._element).removeClass(self._options.hoverClass);
|
85
|
+
});
|
86
|
+
this._attach(input, 'focus', function(){
|
87
|
+
qq(self._element).addClass(self._options.focusClass);
|
88
|
+
});
|
89
|
+
this._attach(input, 'blur', function(){
|
90
|
+
qq(self._element).removeClass(self._options.focusClass);
|
91
|
+
});
|
92
|
+
|
93
|
+
// IE and Opera, unfortunately have 2 tab stops on file input
|
94
|
+
// which is unacceptable in our case, disable keyboard access
|
95
|
+
if (window.attachEvent){
|
96
|
+
// it is IE or Opera
|
97
|
+
input.setAttribute('tabIndex', "-1");
|
98
|
+
}
|
99
|
+
|
100
|
+
return input;
|
101
|
+
}
|
102
|
+
};
|
@@ -0,0 +1,120 @@
|
|
1
|
+
/**
|
2
|
+
* Class for uploading files, uploading itself is handled by child classes
|
3
|
+
*/
|
4
|
+
qq.UploadHandlerAbstract = function(o){
|
5
|
+
// Default options, can be overridden by the user
|
6
|
+
this._options = {
|
7
|
+
debug: false,
|
8
|
+
endpoint: '/upload.php',
|
9
|
+
// maximum number of concurrent uploads
|
10
|
+
maxConnections: 999,
|
11
|
+
log: function(str, level) {},
|
12
|
+
onProgress: function(id, fileName, loaded, total){},
|
13
|
+
onComplete: function(id, fileName, response, xhr){},
|
14
|
+
onCancel: function(id, fileName){},
|
15
|
+
onUpload: function(id, fileName, xhr){},
|
16
|
+
onAutoRetry: function(id, fileName, response, xhr){}
|
17
|
+
|
18
|
+
};
|
19
|
+
qq.extend(this._options, o);
|
20
|
+
|
21
|
+
this._queue = [];
|
22
|
+
// params for files in queue
|
23
|
+
this._params = [];
|
24
|
+
|
25
|
+
this.log = this._options.log;
|
26
|
+
};
|
27
|
+
qq.UploadHandlerAbstract.prototype = {
|
28
|
+
/**
|
29
|
+
* Adds file or file input to the queue
|
30
|
+
* @returns id
|
31
|
+
**/
|
32
|
+
add: function(file){},
|
33
|
+
/**
|
34
|
+
* Sends the file identified by id and additional query params to the server
|
35
|
+
*/
|
36
|
+
upload: function(id, params){
|
37
|
+
var len = this._queue.push(id);
|
38
|
+
|
39
|
+
var copy = {};
|
40
|
+
qq.extend(copy, params);
|
41
|
+
this._params[id] = copy;
|
42
|
+
|
43
|
+
// if too many active uploads, wait...
|
44
|
+
if (len <= this._options.maxConnections){
|
45
|
+
this._upload(id, this._params[id]);
|
46
|
+
}
|
47
|
+
},
|
48
|
+
retry: function(id) {
|
49
|
+
var i = qq.indexOf(this._queue, id);
|
50
|
+
if (i >= 0) {
|
51
|
+
this._upload(id, this._params[id]);
|
52
|
+
}
|
53
|
+
else {
|
54
|
+
this.upload(id, this._params[id]);
|
55
|
+
}
|
56
|
+
},
|
57
|
+
/**
|
58
|
+
* Cancels file upload by id
|
59
|
+
*/
|
60
|
+
cancel: function(id){
|
61
|
+
this.log('Cancelling ' + id);
|
62
|
+
this._cancel(id);
|
63
|
+
this._dequeue(id);
|
64
|
+
},
|
65
|
+
/**
|
66
|
+
* Cancells all uploads
|
67
|
+
*/
|
68
|
+
cancelAll: function(){
|
69
|
+
for (var i=0; i<this._queue.length; i++){
|
70
|
+
this._cancel(this._queue[i]);
|
71
|
+
}
|
72
|
+
this._queue = [];
|
73
|
+
},
|
74
|
+
/**
|
75
|
+
* Returns name of the file identified by id
|
76
|
+
*/
|
77
|
+
getName: function(id){},
|
78
|
+
/**
|
79
|
+
* Returns size of the file identified by id
|
80
|
+
*/
|
81
|
+
getSize: function(id){},
|
82
|
+
/**
|
83
|
+
* Returns id of files being uploaded or
|
84
|
+
* waiting for their turn
|
85
|
+
*/
|
86
|
+
getQueue: function(){
|
87
|
+
return this._queue;
|
88
|
+
},
|
89
|
+
reset: function() {
|
90
|
+
this.log('Resetting upload handler');
|
91
|
+
this._queue = [];
|
92
|
+
this._params = [];
|
93
|
+
},
|
94
|
+
/**
|
95
|
+
* Actual upload method
|
96
|
+
*/
|
97
|
+
_upload: function(id){},
|
98
|
+
/**
|
99
|
+
* Actual cancel method
|
100
|
+
*/
|
101
|
+
_cancel: function(id){},
|
102
|
+
/**
|
103
|
+
* Removes element from queue, starts upload of next
|
104
|
+
*/
|
105
|
+
_dequeue: function(id){
|
106
|
+
var i = qq.indexOf(this._queue, id);
|
107
|
+
this._queue.splice(i, 1);
|
108
|
+
|
109
|
+
var max = this._options.maxConnections;
|
110
|
+
|
111
|
+
if (this._queue.length >= max && i < max){
|
112
|
+
var nextId = this._queue[max-1];
|
113
|
+
this._upload(nextId, this._params[nextId]);
|
114
|
+
}
|
115
|
+
},
|
116
|
+
/**
|
117
|
+
* Determine if the file exists.
|
118
|
+
*/
|
119
|
+
isValid: function(id) {}
|
120
|
+
};
|
@@ -0,0 +1,197 @@
|
|
1
|
+
/**
|
2
|
+
* Class for uploading files using form and iframe
|
3
|
+
* @inherits qq.UploadHandlerAbstract
|
4
|
+
*/
|
5
|
+
qq.UploadHandlerForm = function(o){
|
6
|
+
qq.UploadHandlerAbstract.apply(this, arguments);
|
7
|
+
|
8
|
+
this._inputs = {};
|
9
|
+
this._detach_load_events = {};
|
10
|
+
};
|
11
|
+
// @inherits qq.UploadHandlerAbstract
|
12
|
+
qq.extend(qq.UploadHandlerForm.prototype, qq.UploadHandlerAbstract.prototype);
|
13
|
+
|
14
|
+
qq.extend(qq.UploadHandlerForm.prototype, {
|
15
|
+
add: function(fileInput){
|
16
|
+
fileInput.setAttribute('name', this._options.inputName);
|
17
|
+
var id = 'qq-upload-handler-iframe' + qq.getUniqueId();
|
18
|
+
|
19
|
+
this._inputs[id] = fileInput;
|
20
|
+
|
21
|
+
// remove file input from DOM
|
22
|
+
if (fileInput.parentNode){
|
23
|
+
qq(fileInput).remove();
|
24
|
+
}
|
25
|
+
|
26
|
+
return id;
|
27
|
+
},
|
28
|
+
getName: function(id){
|
29
|
+
// get input value and remove path to normalize
|
30
|
+
return this._inputs[id].value.replace(/.*(\/|\\)/, "");
|
31
|
+
},
|
32
|
+
isValid: function(id) {
|
33
|
+
return this._inputs[id] !== undefined;
|
34
|
+
},
|
35
|
+
reset: function() {
|
36
|
+
qq.UploadHandlerAbstract.prototype.reset.apply(this, arguments);
|
37
|
+
this._inputs = {};
|
38
|
+
this._detach_load_events = {};
|
39
|
+
},
|
40
|
+
_cancel: function(id){
|
41
|
+
this._options.onCancel(id, this.getName(id));
|
42
|
+
|
43
|
+
delete this._inputs[id];
|
44
|
+
delete this._detach_load_events[id];
|
45
|
+
|
46
|
+
var iframe = document.getElementById(id);
|
47
|
+
if (iframe){
|
48
|
+
// to cancel request set src to something else
|
49
|
+
// we use src="javascript:false;" because it doesn't
|
50
|
+
// trigger ie6 prompt on https
|
51
|
+
iframe.setAttribute('src', 'javascript:false;');
|
52
|
+
|
53
|
+
qq(iframe).remove();
|
54
|
+
}
|
55
|
+
},
|
56
|
+
_upload: function(id, params){
|
57
|
+
this._options.onUpload(id, this.getName(id), false);
|
58
|
+
var input = this._inputs[id];
|
59
|
+
|
60
|
+
if (!input){
|
61
|
+
throw new Error('file with passed id was not added, or already uploaded or cancelled');
|
62
|
+
}
|
63
|
+
|
64
|
+
var fileName = this.getName(id);
|
65
|
+
params[this._options.inputName] = fileName;
|
66
|
+
|
67
|
+
var iframe = this._createIframe(id);
|
68
|
+
var form = this._createForm(iframe, params);
|
69
|
+
form.appendChild(input);
|
70
|
+
|
71
|
+
var self = this;
|
72
|
+
this._attachLoadEvent(iframe, function(){
|
73
|
+
self.log('iframe loaded');
|
74
|
+
|
75
|
+
var response = self._getIframeContentJSON(iframe);
|
76
|
+
|
77
|
+
// timeout added to fix busy state in FF3.6
|
78
|
+
setTimeout(function(){
|
79
|
+
self._detach_load_events[id]();
|
80
|
+
delete self._detach_load_events[id];
|
81
|
+
qq(iframe).remove();
|
82
|
+
}, 1);
|
83
|
+
|
84
|
+
if (!response.success) {
|
85
|
+
if (self._options.onAutoRetry(id, fileName, response)) {
|
86
|
+
return;
|
87
|
+
}
|
88
|
+
}
|
89
|
+
self._options.onComplete(id, fileName, response);
|
90
|
+
self._dequeue(id);
|
91
|
+
});
|
92
|
+
|
93
|
+
this.log('Sending upload request for ' + id);
|
94
|
+
form.submit();
|
95
|
+
qq(form).remove();
|
96
|
+
|
97
|
+
return id;
|
98
|
+
},
|
99
|
+
_attachLoadEvent: function(iframe, callback){
|
100
|
+
var self = this;
|
101
|
+
this._detach_load_events[iframe.id] = qq(iframe).attach('load', function(){
|
102
|
+
self.log('Received response for ' + iframe.id);
|
103
|
+
|
104
|
+
// when we remove iframe from dom
|
105
|
+
// the request stops, but in IE load
|
106
|
+
// event fires
|
107
|
+
if (!iframe.parentNode){
|
108
|
+
return;
|
109
|
+
}
|
110
|
+
|
111
|
+
try {
|
112
|
+
// fixing Opera 10.53
|
113
|
+
if (iframe.contentDocument &&
|
114
|
+
iframe.contentDocument.body &&
|
115
|
+
iframe.contentDocument.body.innerHTML == "false"){
|
116
|
+
// In Opera event is fired second time
|
117
|
+
// when body.innerHTML changed from false
|
118
|
+
// to server response approx. after 1 sec
|
119
|
+
// when we upload file with iframe
|
120
|
+
return;
|
121
|
+
}
|
122
|
+
}
|
123
|
+
catch (error) {
|
124
|
+
//IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
|
125
|
+
self.log('Error when attempting to access iframe during handling of upload response (' + error + ")", 'error');
|
126
|
+
}
|
127
|
+
|
128
|
+
callback();
|
129
|
+
});
|
130
|
+
},
|
131
|
+
/**
|
132
|
+
* Returns json object received by iframe from server.
|
133
|
+
*/
|
134
|
+
_getIframeContentJSON: function(iframe){
|
135
|
+
//IE may throw an "access is denied" error when attempting to access contentDocument on the iframe in some cases
|
136
|
+
try {
|
137
|
+
// iframe.contentWindow.document - for IE<7
|
138
|
+
var doc = iframe.contentDocument ? iframe.contentDocument: iframe.contentWindow.document,
|
139
|
+
response;
|
140
|
+
|
141
|
+
var innerHTML = doc.body.innerHTML;
|
142
|
+
this.log("converting iframe's innerHTML to JSON");
|
143
|
+
this.log("innerHTML = " + innerHTML);
|
144
|
+
//plain text response may be wrapped in <pre> tag
|
145
|
+
if (innerHTML && innerHTML.match(/^<pre/i)) {
|
146
|
+
innerHTML = doc.body.firstChild.firstChild.nodeValue;
|
147
|
+
}
|
148
|
+
response = eval("(" + innerHTML + ")");
|
149
|
+
} catch(error){
|
150
|
+
this.log('Error when attempting to parse form upload response (' + error + ")", 'error');
|
151
|
+
response = {success: false};
|
152
|
+
}
|
153
|
+
|
154
|
+
return response;
|
155
|
+
},
|
156
|
+
/**
|
157
|
+
* Creates iframe with unique name
|
158
|
+
*/
|
159
|
+
_createIframe: function(id){
|
160
|
+
// We can't use following code as the name attribute
|
161
|
+
// won't be properly registered in IE6, and new window
|
162
|
+
// on form submit will open
|
163
|
+
// var iframe = document.createElement('iframe');
|
164
|
+
// iframe.setAttribute('name', id);
|
165
|
+
|
166
|
+
var iframe = qq.toElement('<iframe src="javascript:false;" name="' + id + '" />');
|
167
|
+
// src="javascript:false;" removes ie6 prompt on https
|
168
|
+
|
169
|
+
iframe.setAttribute('id', id);
|
170
|
+
|
171
|
+
iframe.style.display = 'none';
|
172
|
+
document.body.appendChild(iframe);
|
173
|
+
|
174
|
+
return iframe;
|
175
|
+
},
|
176
|
+
/**
|
177
|
+
* Creates form, that will be submitted to iframe
|
178
|
+
*/
|
179
|
+
_createForm: function(iframe, params){
|
180
|
+
// We can't use the following code in IE6
|
181
|
+
// var form = document.createElement('form');
|
182
|
+
// form.setAttribute('method', 'post');
|
183
|
+
// form.setAttribute('enctype', 'multipart/form-data');
|
184
|
+
// Because in this case file won't be attached to request
|
185
|
+
var protocol = this._options.demoMode ? "GET" : "POST"
|
186
|
+
var form = qq.toElement('<form method="' + protocol + '" enctype="multipart/form-data"></form>');
|
187
|
+
|
188
|
+
var queryString = qq.obj2url(params, this._options.endpoint);
|
189
|
+
|
190
|
+
form.setAttribute('action', queryString);
|
191
|
+
form.setAttribute('target', iframe.name);
|
192
|
+
form.style.display = 'none';
|
193
|
+
document.body.appendChild(form);
|
194
|
+
|
195
|
+
return form;
|
196
|
+
}
|
197
|
+
});
|