Swiff.Uploader.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. /**
  2. * Swiff.Uploader - Flash FileReference Control
  3. *
  4. * @version 3.0
  5. *
  6. * @license MIT License
  7. *
  8. * @author Harald Kirschner <http://digitarald.de>
  9. * @author Valerio Proietti, <http://mad4milk.net>
  10. * @copyright Authors
  11. */
  12. Swiff.Uploader = new Class({
  13. Extends: Swiff,
  14. Implements: Events,
  15. options: {
  16. path: 'Swiff.Uploader.swf',
  17. target: null,
  18. zIndex: 9999,
  19. height: 30,
  20. width: 100,
  21. callBacks: null,
  22. params: {
  23. wMode: 'opaque',
  24. menu: 'false',
  25. allowScriptAccess: 'always'
  26. },
  27. typeFilter: null,
  28. multiple: true,
  29. queued: true,
  30. verbose: false,
  31. url: null,
  32. method: null,
  33. data: null,
  34. mergeData: true,
  35. fieldName: null,
  36. fileSizeMin: 1,
  37. fileSizeMax: null, // Official limit is 100 MB for FileReference, but I tested up to 2Gb!
  38. allowDuplicates: false,
  39. timeLimit: (Browser.Platform.linux) ? 0 : 30,
  40. buttonImage: null,
  41. policyFile: null,
  42. fileListMax: 0,
  43. fileListSizeMax: 0,
  44. instantStart: false,
  45. appendCookieData: false,
  46. fileClass: null
  47. /*
  48. onLoad: $empty,
  49. onFail: $empty,
  50. onStart: $empty,
  51. onQueue: $empty,
  52. onComplete: $empty,
  53. onBrowse: $empty,
  54. onDisabledBrowse: $empty,
  55. onCancel: $empty,
  56. onSelect: $empty,
  57. onSelectSuccess: $empty,
  58. onSelectFail: $empty,
  59. onButtonEnter: $empty,
  60. onButtonLeave: $empty,
  61. onButtonDown: $empty,
  62. onButtonDisable: $empty,
  63. onFileStart: $empty,
  64. onFileStop: $empty,
  65. onFileRequeue: $empty,
  66. onFileOpen: $empty,
  67. onFileProgress: $empty,
  68. onFileComplete: $empty,
  69. onFileRemove: $empty,
  70. onBeforeStart: $empty,
  71. onBeforeStop: $empty,
  72. onBeforeRemove: $empty
  73. */
  74. },
  75. initialize: function(options) {
  76. // protected events to control the class, added
  77. // before setting options (which adds own events)
  78. this.addEvent('load', this.initializeSwiff, true)
  79. .addEvent('select', this.processFiles, true)
  80. .addEvent('complete', this.update, true)
  81. .addEvent('fileRemove', function(file) {
  82. this.fileList.erase(file);
  83. }.bind(this), true);
  84. this.setOptions(options);
  85. // callbacks are no longer in the options, every callback
  86. // is fired as event, this is just compat
  87. if (this.options.callBacks) {
  88. Hash.each(this.options.callBacks, function(fn, name) {
  89. this.addEvent(name, fn);
  90. }, this);
  91. }
  92. this.options.callBacks = {
  93. fireCallback: this.fireCallback.bind(this)
  94. };
  95. var path = this.options.path;
  96. if (!path.contains('?')) path += '?noCache=' + $time(); // cache in IE
  97. // container options for Swiff class
  98. this.options.container = this.box = new Element('span', {'class': 'swiff-uploader-box'}).inject($(this.options.container) || document.body);
  99. // target
  100. this.target = $(this.options.target);
  101. if (this.target) {
  102. var scroll = window.getScroll();
  103. this.box.setStyles({
  104. position: 'absolute',
  105. visibility: 'visible',
  106. zIndex: this.options.zIndex,
  107. overflow: 'hidden',
  108. height: 1, width: 1,
  109. top: scroll.y, left: scroll.x
  110. });
  111. // we force wMode to transparent for the overlay effect
  112. this.parent(path, {
  113. params: {
  114. wMode: 'transparent'
  115. },
  116. height: '100%',
  117. width: '100%'
  118. });
  119. this.target.addEvent('mouseenter', this.reposition.bind(this, []));
  120. // button interactions, relayed to to the target
  121. this.addEvents({
  122. buttonEnter: this.targetRelay.bind(this, ['mouseenter']),
  123. buttonLeave: this.targetRelay.bind(this, ['mouseleave']),
  124. buttonDown: this.targetRelay.bind(this, ['mousedown']),
  125. buttonDisable: this.targetRelay.bind(this, ['disable'])
  126. });
  127. this.reposition();
  128. window.addEvent('resize', this.reposition.bind(this, []));
  129. } else {
  130. this.parent(path);
  131. }
  132. this.inject(this.box);
  133. this.fileList = [];
  134. this.size = this.uploading = this.bytesLoaded = this.percentLoaded = 0;
  135. if (Browser.Plugins.Flash.version < 9) {
  136. this.fireEvent('fail', ['flash']);
  137. } else {
  138. this.verifyLoad.delay(1000, this);
  139. }
  140. },
  141. verifyLoad: function() {
  142. if (this.loaded) return;
  143. if (!this.object.parentNode) {
  144. this.fireEvent('fail', ['disabled']);
  145. } else if (this.object.style.display == 'none') {
  146. this.fireEvent('fail', ['hidden']);
  147. } else if (!this.object.offsetWidth) {
  148. this.fireEvent('fail', ['empty']);
  149. }
  150. },
  151. fireCallback: function(name, args) {
  152. // file* callbacks are relayed to the specific file
  153. if (name.substr(0, 4) == 'file') {
  154. // updated queue data is the second argument
  155. if (args.length > 1) this.update(args[1]);
  156. var data = args[0];
  157. var file = this.findFile(data.id);
  158. this.fireEvent(name, file || data, 5);
  159. if (file) {
  160. var fire = name.replace(/^file([A-Z])/, function($0, $1) {
  161. return $1.toLowerCase();
  162. });
  163. file.update(data).fireEvent(fire, [data], 10);
  164. }
  165. } else {
  166. this.fireEvent(name, args, 5);
  167. }
  168. },
  169. update: function(data) {
  170. // the data is saved right to the instance
  171. $extend(this, data);
  172. this.fireEvent('queue', [this], 10);
  173. return this;
  174. },
  175. findFile: function(id) {
  176. for (var i = 0; i < this.fileList.length; i++) {
  177. if (this.fileList[i].id == id) return this.fileList[i];
  178. }
  179. return null;
  180. },
  181. initializeSwiff: function() {
  182. // extracted options for the swf
  183. this.remote('initialize', {
  184. width: this.options.width,
  185. height: this.options.height,
  186. typeFilter: this.options.typeFilter,
  187. multiple: this.options.multiple,
  188. queued: this.options.queued,
  189. url: this.options.url,
  190. method: this.options.method,
  191. data: this.options.data,
  192. mergeData: this.options.mergeData,
  193. fieldName: this.options.fieldName,
  194. verbose: this.options.verbose,
  195. fileSizeMin: this.options.fileSizeMin,
  196. fileSizeMax: this.options.fileSizeMax,
  197. allowDuplicates: this.options.allowDuplicates,
  198. timeLimit: this.options.timeLimit,
  199. buttonImage: this.options.buttonImage,
  200. policyFile: this.options.policyFile
  201. });
  202. this.loaded = true;
  203. this.appendCookieData();
  204. },
  205. targetRelay: function(name) {
  206. if (this.target) this.target.fireEvent(name);
  207. },
  208. reposition: function(coords) {
  209. // update coordinates, manual or automatically
  210. coords = coords || (this.target && this.target.offsetHeight)
  211. ? this.target.getCoordinates(this.box.getOffsetParent())
  212. : {top: window.getScrollTop(), left: 0, width: 40, height: 40}
  213. this.box.setStyles(coords);
  214. this.fireEvent('reposition', [coords, this.box, this.target]);
  215. },
  216. setOptions: function(options) {
  217. if (options) {
  218. if (options.url) options.url = Swiff.Uploader.qualifyPath(options.url);
  219. if (options.buttonImage) options.buttonImage = Swiff.Uploader.qualifyPath(options.buttonImage);
  220. this.parent(options);
  221. if (this.loaded) this.remote('setOptions', options);
  222. }
  223. return this;
  224. },
  225. setEnabled: function(status) {
  226. this.remote('setEnabled', status);
  227. },
  228. start: function() {
  229. this.fireEvent('beforeStart');
  230. this.remote('start');
  231. },
  232. stop: function() {
  233. this.fireEvent('beforeStop');
  234. this.remote('stop');
  235. },
  236. remove: function() {
  237. this.fireEvent('beforeRemove');
  238. this.remote('remove');
  239. },
  240. fileStart: function(file) {
  241. this.remote('fileStart', file.id);
  242. },
  243. fileStop: function(file) {
  244. this.remote('fileStop', file.id);
  245. },
  246. fileRemove: function(file) {
  247. this.remote('fileRemove', file.id);
  248. },
  249. fileRequeue: function(file) {
  250. this.remote('fileRequeue', file.id);
  251. },
  252. appendCookieData: function() {
  253. var append = this.options.appendCookieData;
  254. if (!append) return;
  255. var hash = {};
  256. document.cookie.split(/;\s*/).each(function(cookie) {
  257. cookie = cookie.split('=');
  258. if (cookie.length == 2) {
  259. hash[decodeURIComponent(cookie[0])] = decodeURIComponent(cookie[1]);
  260. }
  261. });
  262. var data = this.options.data || {};
  263. if ($type(append) == 'string') data[append] = hash;
  264. else $extend(data, hash);
  265. this.setOptions({data: data});
  266. },
  267. processFiles: function(successraw, failraw, queue) {
  268. var cls = this.options.fileClass || Swiff.Uploader.File;
  269. var fail = [], success = [];
  270. if (successraw) {
  271. successraw.each(function(data) {
  272. var ret = new cls(this, data);
  273. if (!ret.validate()) {
  274. ret.remove.delay(10, ret);
  275. fail.push(ret);
  276. } else {
  277. this.size += data.size;
  278. this.fileList.push(ret);
  279. success.push(ret);
  280. ret.render();
  281. }
  282. }, this);
  283. this.fireEvent('selectSuccess', [success], 10);
  284. }
  285. if (failraw || fail.length) {
  286. fail.extend((failraw) ? failraw.map(function(data) {
  287. return new cls(this, data);
  288. }, this) : []).each(function(file) {
  289. file.invalidate().render();
  290. });
  291. this.fireEvent('selectFail', [fail], 10);
  292. }
  293. this.update(queue);
  294. if (this.options.instantStart && success.length) this.start();
  295. }
  296. });
  297. $extend(Swiff.Uploader, {
  298. STATUS_QUEUED: 0,
  299. STATUS_RUNNING: 1,
  300. STATUS_ERROR: 2,
  301. STATUS_COMPLETE: 3,
  302. STATUS_STOPPED: 4,
  303. log: function() {
  304. if (window.console && console.info) console.info.apply(console, arguments);
  305. },
  306. unitLabels: {
  307. b: [{min: 1, unit: 'B'}, {min: 1024, unit: 'kB'}, {min: 1048576, unit: 'MB'}, {min: 1073741824, unit: 'GB'}],
  308. s: [{min: 1, unit: 's'}, {min: 60, unit: 'm'}, {min: 3600, unit: 'h'}, {min: 86400, unit: 'd'}]
  309. },
  310. formatUnit: function(base, type, join) {
  311. var labels = Swiff.Uploader.unitLabels[(type == 'bps') ? 'b' : type];
  312. var append = (type == 'bps') ? '/s' : '';
  313. var i, l = labels.length, value;
  314. if (base < 1) return '0 ' + labels[0].unit + append;
  315. if (type == 's') {
  316. var units = [];
  317. for (i = l - 1; i >= 0; i--) {
  318. value = Math.floor(base / labels[i].min);
  319. if (value) {
  320. units.push(value + ' ' + labels[i].unit);
  321. base -= value * labels[i].min;
  322. if (!base) break;
  323. }
  324. }
  325. return (join === false) ? units : units.join(join || ', ');
  326. }
  327. for (i = l - 1; i >= 0; i--) {
  328. value = labels[i].min;
  329. if (base >= value) break;
  330. }
  331. return (base / value).toFixed(1) + ' ' + labels[i].unit + append;
  332. }
  333. });
  334. Swiff.Uploader.qualifyPath = (function() {
  335. var anchor;
  336. return function(path) {
  337. (anchor || (anchor = new Element('a'))).href = path;
  338. return anchor.href;
  339. };
  340. })();
  341. Swiff.Uploader.File = new Class({
  342. Implements: Events,
  343. initialize: function(base, data) {
  344. this.base = base;
  345. this.update(data);
  346. },
  347. update: function(data) {
  348. return $extend(this, data);
  349. },
  350. validate: function() {
  351. var options = this.base.options;
  352. if (options.fileListMax && this.base.fileList.length >= options.fileListMax) {
  353. this.validationError = 'fileListMax';
  354. return false;
  355. }
  356. if (options.fileListSizeMax && (this.base.size + this.size) > options.fileListSizeMax) {
  357. this.validationError = 'fileListSizeMax';
  358. return false;
  359. }
  360. return true;
  361. },
  362. invalidate: function() {
  363. this.invalid = true;
  364. this.base.fireEvent('fileInvalid', this, 10);
  365. return this.fireEvent('invalid', this, 10);
  366. },
  367. render: function() {
  368. return this;
  369. },
  370. setOptions: function(options) {
  371. if (options) {
  372. if (options.url) options.url = Swiff.Uploader.qualifyPath(options.url);
  373. this.base.remote('fileSetOptions', this.id, options);
  374. this.options = $merge(this.options, options);
  375. }
  376. return this;
  377. },
  378. start: function() {
  379. this.base.fileStart(this);
  380. return this;
  381. },
  382. stop: function() {
  383. this.base.fileStop(this);
  384. return this;
  385. },
  386. remove: function() {
  387. this.base.fileRemove(this);
  388. return this;
  389. },
  390. requeue: function() {
  391. this.base.fileRequeue(this);
  392. }
  393. });