Uploader.js 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. /**
  2. * Uploader
  3. *
  4. * @version 1.0
  5. *
  6. * @license MIT License
  7. *
  8. * @author Harald Kirschner <mail [at] digitarald [dot] de>
  9. * @copyright Authors
  10. */
  11. var Uploader = new Class({
  12. Implements: [Options, Events],
  13. options: {
  14. container: null,
  15. queued: true,
  16. verbose: false,
  17. url: null,
  18. method: null,
  19. data: null,
  20. mergeData: true,
  21. fieldName: null,
  22. allowDuplicates: false,
  23. fileListMax: 0,
  24. instantStart: false,
  25. appendCookieData: false,
  26. fileClass: null
  27. },
  28. initialize: function(options) {
  29. this.setOptions(options);
  30. this.target = $(this.options.target);
  31. this.box = new Element('div').setStyles({
  32. position: 'absolute',
  33. opacity: 0.01,
  34. zIndex: 9999,
  35. overflow: 'hidden'
  36. });
  37. this.reposition();
  38. window.addEvent('resize', this.reposition.bind(this));
  39. this.box.inject(this.options.container || document.body);
  40. this.addEvents({
  41. buttonEnter: this.targetRelay.bind(this, ['mouseenter']),
  42. buttonLeave: this.targetRelay.bind(this, ['mouseleave']),
  43. buttonDown: this.targetRelay.bind(this, ['mousedown']),
  44. buttonDisable: this.targetRelay.bind(this, ['disable'])
  45. });
  46. this.uploading = 0;
  47. this.fileList = [];
  48. this.createIFrame();
  49. return this;
  50. },
  51. targetRelay: function(name) {
  52. if (this.target) this.target.fireEvent(name);
  53. },
  54. createIFrame: function() {
  55. this.iframe = new Element('iframe', {
  56. src: "javascript:'<html></html>'",
  57. frameborder: 'no',
  58. border: 0,
  59. styles: {
  60. width: '100%',
  61. height: '100%'
  62. }
  63. }).inject(this.box);
  64. this.runner = this.createIBody.periodical(50, this);
  65. },
  66. createIBody: function() {
  67. var doc = this.iframe.contentWindow.document;
  68. if (!doc || !doc.body) return;
  69. $clear(this.runner);
  70. var align = (Browser.Engine.trident) ? 'left' : 'right';
  71. doc.body.innerHTML = '<form method="post" enctype="multipart/form-data" id="form">' +
  72. '<input type="file" id="file" style="position:absolute;' + align + ':0;top:0" />' +
  73. '<input type="submit" /><div id="data"></div></form>' +
  74. '<style type="text/css">*{margin:0;padding:0;border:0;overflow:hidden;cursor:pointer;}</style>';
  75. this.doc = doc;
  76. this.processIBody.delay(50, this);
  77. },
  78. processIBody: function() {
  79. this.doc;
  80. if (!(this.file = this.doc.getElementById('file')) || !this.file.offsetHeight) {
  81. this.createIBody(); // WTF: FF forgot to update the HTML?!
  82. return;
  83. }
  84. $extend(this.file, {
  85. onmousedown: function() {
  86. if (Browser.Engine.presto) return true;
  87. (function() {
  88. this.file.click();
  89. this.fireEvent('buttonDown');
  90. }).delay(10, this);
  91. return false;
  92. }.bind(this),
  93. onfocus: function() {
  94. return false;
  95. },
  96. onchange: this.select.bind(this),
  97. onmouseover: this.fireEvent.bind(this, 'buttonEnter'),
  98. onmouseout: this.fireEvent.bind(this, 'buttonLeave')
  99. });
  100. },
  101. select: function() {
  102. this.file.onchange = this.file.onmousedown = this.file.onfocus = null;
  103. var name = this.file.value.replace(/^.*[\\\/]/, '');
  104. var cls = this.options.fileClass || Uploader.File;
  105. var ret = new cls(this, name, this.iframe.setStyle('display', 'none'));
  106. if (!ret.validate()) {
  107. ret.invalidate().render();
  108. this.fireEvent('onSelectFailed', [[ret]]);
  109. return;
  110. }
  111. this.fileList.push(ret);
  112. ret.render();
  113. this.fireEvent('onSelectSuccess', [[ret]]);
  114. if (this.options.instantStart) this.start();
  115. this.file = null;
  116. this.createIFrame();
  117. },
  118. reposition: function() {
  119. var pos = this.target.getCoordinates(this.box.getOffsetParent());
  120. this.box.setStyles(pos);
  121. },
  122. start: function() {
  123. this.fireEvent('beforeStart');
  124. var queued = this.options.queued;
  125. queued = (queued) ? ((queued > 1) ? queued : 1) : 0;
  126. for (var i = 0, file; file = this.fileList[i]; i++) {
  127. if (this.fileList[i].status != Uploader.STATUS_QUEUED) continue;
  128. this.fileList[i].start();
  129. if (queued && this.uploading >= queued) break;
  130. }
  131. return this;
  132. },
  133. stop: function() {
  134. this.fireEvent('beforeStop');
  135. for (var i = this.fileList.length; i--;) this.fileList[i].stop();
  136. },
  137. remove: function() {
  138. this.fireEvent('beforeRemove');
  139. for (var i = this.fileList.length; i--;) this.fileList[i].remove();
  140. },
  141. setEnabled: function(status) {
  142. this.file.disabled = !!(status);
  143. if (status) this.fireEvent('buttonDisable');
  144. }
  145. });
  146. $extend(Uploader, {
  147. STATUS_QUEUED: 0,
  148. STATUS_RUNNING: 1,
  149. STATUS_ERROR: 2,
  150. STATUS_COMPLETE: 3,
  151. STATUS_STOPPED: 4,
  152. id: 0,
  153. log: function() {
  154. if (window.console && console.info) console.info.apply(console, arguments);
  155. }
  156. });
  157. Uploader.File = new Class({
  158. Extends: Events,
  159. Implements: Options,
  160. options: {
  161. url: null,
  162. method: null,
  163. data: null,
  164. mergeData: true,
  165. fieldName: null
  166. },
  167. initialize: function(base, name, iframe) {
  168. this.base = base;
  169. this.id = Uploader.id++;
  170. this.name = name;
  171. this.extension = name.replace(/^.*\./, '').toLowerCase();
  172. this.status = Uploader.STATUS_QUEUED;
  173. this.dates = {};
  174. this.dates.add = new Date();
  175. this.iframe = iframe.addEvents({
  176. abort: this.stop.bind(this),
  177. load: this.onLoad.bind(this)
  178. });
  179. },
  180. fireEvent: function(name) {
  181. this.base.fireEvent('file' + name.capitalize(), [this]);
  182. Uploader.log('File::' + name, this);
  183. return this.parent(name, [this]);
  184. },
  185. validate: function() {
  186. var base = this.base.options;
  187. if (!base.allowDuplicates) {
  188. var name = this.name;
  189. var dup = this.base.fileList.some(function(file) {
  190. return (file.name == name);
  191. });
  192. if (dup) {
  193. this.validationError = 'duplicate';
  194. return false;
  195. }
  196. }
  197. if (base.fileListMax && this.base.fileList.length >= base.fileListMax) {
  198. this.validationError = 'fileListMax';
  199. return false;
  200. }
  201. return true;
  202. },
  203. invalidate: function() {
  204. this.invalid = true;
  205. return this.fireEvent('invalid');
  206. },
  207. render: function() {
  208. return this;
  209. },
  210. onLoad: function() {
  211. if (this.status != Uploader.STATUS_RUNNING) return;
  212. this.status = Uploader.STATUS_COMPLETE;
  213. var win = new Window(this.iframe.contentWindow);
  214. var doc = new Document(win.document);
  215. this.response = {
  216. window: win,
  217. document: doc,
  218. text: doc.innerHTML || ''
  219. };
  220. this.base.uploading--;
  221. this.dates.complete = new Date();
  222. this.fireEvent('complete');
  223. this.base.start();
  224. },
  225. start: function() {
  226. if (this.status != Uploader.STATUS_QUEUED) return this;
  227. var base = this.base.options, options = this.options;
  228. var merged = {};
  229. for (var key in base) {
  230. merged[key] = (this.options[key] != null) ? this.options[key] : base[key];
  231. }
  232. merged.url = merged.url || location.href;
  233. merged.method = (merged.method) ? (merged.method.toLowerCase()) : 'post';
  234. var doc = this.iframe.contentWindow.document;
  235. var more = doc.getElementById('data');
  236. more.innerHTML = '';
  237. if (merged.data) {
  238. if (merged.mergeData && base.data && options.data) {
  239. if ($type(base.data) == 'string') merged.data = base.data + '&' + options.data;
  240. else merged.data = $merge(base.data, options.data);
  241. }
  242. var query = ($type(merged.data) == 'string') ? merged.data : Hash.toQueryString(merged.data);
  243. if (query.length) {
  244. if (merged.method == 'get') {
  245. if (data.length) merged.url += ((merged.url.contains('?')) ? '&' : '?') + query;
  246. } else {
  247. query.split('&').map(function(value) {
  248. value = value.split('=');
  249. var input = doc.createElement('input');
  250. input.type = 'hidden';
  251. input.name = decodeURIComponent(value[0]);
  252. input.value = decodeURIComponent(value[1] || '');
  253. more.appendChild(input);
  254. }).join('');
  255. }
  256. }
  257. }
  258. var form = doc.forms[0];
  259. form.action = merged.url;
  260. var input = form.elements[0];
  261. input.name = merged.fieldName || 'Filedata';
  262. this.status = Uploader.STATUS_RUNNING;
  263. this.base.uploading++;
  264. form.submit();
  265. this.dates.start = new Date();
  266. this.fireEvent('start');
  267. return this;
  268. },
  269. requeue: function() {
  270. this.stop();
  271. this.status = Uploader.STATUS_QUEUED;
  272. this.fireEvent('requeue');
  273. },
  274. stop: function(soft) {
  275. if (this.status == Uploader.STATUS_RUNNING) {
  276. this.status = Uploader.STATUS_STOPPED;
  277. this.base.uploading--;
  278. this.base.start();
  279. if (!soft) {
  280. this.iframe.contentWindow.history.back();
  281. this.fireEvent('stop');
  282. }
  283. }
  284. return this;
  285. },
  286. remove: function() {
  287. this.stop(true);
  288. this.iframe = this.iframe.destroy();
  289. this.base.fileList.erase(this);
  290. this.fireEvent('remove');
  291. return this;
  292. }
  293. });