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 CHANGED
@@ -1,18 +1,25 @@
1
- # Fine Uploader 2.1.2 for Rails
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', '~> 2.1.2'
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
- //= require fineuploader
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
 
@@ -1,5 +1,5 @@
1
1
  module Fileuploader
2
2
  module Rails
3
- VERSION = "2.1.2"
3
+ VERSION = "3.0.0"
4
4
  end
5
5
  end
@@ -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
+ });