PageRenderTime 47ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/js/backbone.upload-manager.js

https://github.com/migaber/backbone-upload-manager
JavaScript | 463 lines | 251 code | 53 blank | 159 comment | 28 complexity | c3b5d4a38faf92ea3735412656796d36 MD5 | raw file
  1. /**
  2. * Backbone Upload Manager v1.0.0
  3. *
  4. * Copyright (c) 2013 Samuel ROZE
  5. *
  6. * License and more information at:
  7. * http://github.com/sroze/backbone-upload-manager
  8. */
  9. (function(Backbone){
  10. Backbone.UploadManager = Backbone.DeferedView.extend({
  11. /**
  12. * Default options, that will be merged with the passed.
  13. *
  14. */
  15. defaults: {
  16. templates: {
  17. main: '/templates/upload-manager.main.default',
  18. file: '/templates/upload-manager.file.default'
  19. },
  20. uploadUrl: '/upload',
  21. autoUpload: false,
  22. dataType: 'json'
  23. },
  24. /**
  25. * An integer used to track the files by a unique
  26. * identifier.
  27. *
  28. */
  29. file_id: 0,
  30. /**
  31. * View container class.
  32. *
  33. */
  34. className: 'upload-manager',
  35. /**
  36. * Initialize upload manager options
  37. *
  38. */
  39. initialize: function (options)
  40. {
  41. // Merge options
  42. this.options = $.extend(this.defaults, options);
  43. // Update template name
  44. this.templateName = this.options.templates.main;
  45. // Create the file list
  46. this.files = new Backbone.UploadManager.FileCollection();
  47. // Create the file-upload wrapper
  48. this.uploadProcess = $('<input id="fileupload" type="file" name="files[]" multiple="multiple">').fileupload({
  49. dataType: this.options.dataType,
  50. url: this.options.uploadUrl,
  51. formData: this.options.formData,
  52. autoUpload: this.options.autoUpload,
  53. singleFileUploads: true
  54. });
  55. // Add upload process events handlers
  56. this.bindProcessEvents();
  57. // Add local events handlers
  58. this.bindLocal();
  59. },
  60. /**
  61. * Bind local events.
  62. *
  63. */
  64. bindLocal: function ()
  65. {
  66. var self = this;
  67. this.on('fileadd', function (file) {
  68. // Add it to current list
  69. self.files.add(file);
  70. // Create the view
  71. self.renderFile(file);
  72. }).on('fileprogress', function (file, progress) {
  73. file.progress(progress);
  74. }).on('filefail', function (file, error) {
  75. file.fail(error);
  76. }).on('filedone', function (file, data) {
  77. file.done(data.result);
  78. });
  79. // When collection changes
  80. this.files.on('all', this.update, this);
  81. },
  82. /**
  83. * Render a file.
  84. *
  85. */
  86. renderFile: function (file)
  87. {
  88. var file_view = new Backbone.UploadManager.FileView($.extend(this.options, {model: file}));
  89. $('#file-list', self.el).append(file_view.deferedRender().el);
  90. },
  91. /**
  92. * Update the view without full rendering.
  93. *
  94. */
  95. update: function ()
  96. {
  97. var with_files_elements = $('button#cancel-uploads-button, button#start-uploads-button', this.el);
  98. var without_files_elements = $('#file-list .no-data', this.el);
  99. if (this.files.length > 0) {
  100. with_files_elements.removeClass('hidden');
  101. without_files_elements.addClass('hidden');
  102. } else {
  103. with_files_elements.addClass('hidden');
  104. without_files_elements.removeClass('hidden');
  105. }
  106. },
  107. /**
  108. * Bind events on the upload processor.
  109. *
  110. */
  111. bindProcessEvents: function ()
  112. {
  113. var self = this;
  114. this.uploadProcess.on('fileuploadadd', function (e, data) {
  115. // Create an array in which the file objects
  116. // will be stored.
  117. data.uploadManagerFiles = [];
  118. // A file is added, process for each file.
  119. // Note: every times, the data.files array length is 1 because
  120. // of "singleFileUploads" option.
  121. $.each(data.files, function (index, file_data) {
  122. // Create the file object
  123. file_data.id = self.file_id++;
  124. var file = new Backbone.UploadManager.File({
  125. data: file_data,
  126. processor: data
  127. });
  128. // Add file in data
  129. data.uploadManagerFiles.push(file);
  130. // Trigger event
  131. self.trigger('fileadd', file);
  132. });
  133. }).on('fileuploadprogress', function (e, data) {
  134. $.each(data.uploadManagerFiles, function (index, file) {
  135. self.trigger('fileprogress', file, data);
  136. });
  137. }).on('fileuploadfail', function (e, data) {
  138. $.each(data.uploadManagerFiles, function (index, file) {
  139. var error = "Unknown error";
  140. if (typeof data.errorThrown == "string") {
  141. error = data.errorThrown;
  142. } else if (typeof data.errorThrown == "object") {
  143. error = data.errorThrown.message;
  144. } else if (data.result) {
  145. if (data.result.error) {
  146. error = data.result.error;
  147. } else if (data.result.files && data.result.files[index] && data.result.files[index].error) {
  148. error = data.result.files[index].error;
  149. } else {
  150. error = "Unknown remote error";
  151. }
  152. }
  153. self.trigger('filefail', file, error);
  154. });
  155. }).on('fileuploaddone', function (e, data) {
  156. $.each(data.uploadManagerFiles, function (index, file) {
  157. self.trigger('filedone', file, data);
  158. });
  159. });
  160. },
  161. /**
  162. * Render the main part of upload manager.
  163. *
  164. */
  165. render: function ()
  166. {
  167. $(this.el).html(this.template());
  168. // Update view
  169. this.update();
  170. // Add add files handler
  171. var input = $('input#fileupload', this.el), self = this;
  172. input.on('change', function (){
  173. self.uploadProcess.fileupload('add', {
  174. fileInput: $(this)
  175. });
  176. });
  177. // Add cancel all handler
  178. $('button#cancel-uploads-button', this.el).click(function(){
  179. self.files.each(function(file){
  180. file.cancel();
  181. });
  182. });
  183. // Add start uploads handler
  184. $('button#start-uploads-button', this.el).click(function(){
  185. self.files.each(function(file){
  186. file.start();
  187. });
  188. });
  189. // Render current files
  190. $.each(this.files, function (i, file) {
  191. self.renderFile(file);
  192. });
  193. }
  194. }, {
  195. /**
  196. * This model represents a file.
  197. *
  198. */
  199. File: Backbone.Model.extend({
  200. state: "pending",
  201. /**
  202. * Start upload.
  203. *
  204. */
  205. start: function ()
  206. {
  207. if (this.isPending()) {
  208. this.get('processor').submit();
  209. this.state = "running";
  210. // Dispatch event
  211. this.trigger('filestarted', this);
  212. }
  213. },
  214. /**
  215. * Cancel a file upload.
  216. *
  217. */
  218. cancel: function ()
  219. {
  220. this.get('processor').abort();
  221. this.destroy();
  222. // Dispatch event
  223. this.state = "canceled";
  224. this.trigger('filecanceled', this);
  225. },
  226. /**
  227. * Notify file that progress updated.
  228. *
  229. */
  230. progress: function (data)
  231. {
  232. // Dispatch event
  233. this.trigger('fileprogress', this.get('processor').progress());
  234. },
  235. /**
  236. * Notify file that upload failed.
  237. *
  238. */
  239. fail: function (error)
  240. {
  241. // Dispatch event
  242. this.state = "error";
  243. this.trigger('filefailed', error);
  244. },
  245. /**
  246. * Notify file that upload is done.
  247. *
  248. */
  249. done: function (result)
  250. {
  251. // Dispatch event
  252. this.state = "error";
  253. this.trigger('filedone', result);
  254. },
  255. /**
  256. * Is this file pending to be uploaded ?
  257. *
  258. */
  259. isPending: function ()
  260. {
  261. return this.getState() == "pending";
  262. },
  263. /**
  264. * Is this file currently uploading ?
  265. *
  266. */
  267. isRunning: function ()
  268. {
  269. return this.getState() == "running";
  270. },
  271. /**
  272. * Is this file uploaded ?
  273. *
  274. */
  275. isDone: function ()
  276. {
  277. return this.getState() == "done";
  278. },
  279. /**
  280. * Is this upload in error ?
  281. *
  282. */
  283. isError: function ()
  284. {
  285. return this.getState() == "error" || this.getState == "canceled";
  286. },
  287. /**
  288. * Get the file state.
  289. *
  290. */
  291. getState: function ()
  292. {
  293. return this.state;
  294. }
  295. }),
  296. /**
  297. * This is a file collection, used to manage the selected
  298. * and processing files.
  299. *
  300. */
  301. FileCollection: Backbone.Collection.extend({
  302. model: this.File
  303. }),
  304. /**
  305. * A file view, which is the view that manage a single file
  306. * process in the upload manager.
  307. *
  308. */
  309. FileView: Backbone.DeferedView.extend({
  310. className: 'upload-manager-file row-fluid',
  311. initialize: function (options) {
  312. this.templateName = options.templates.file;
  313. // Bind model events
  314. this.model.on('destroy', this.close, this);
  315. this.model.on('fileprogress', this.updateProgress, this);
  316. this.model.on('filefailed', this.hasFailed, this);
  317. this.model.on('filedone', this.hasDone, this);
  318. // In each case, update view
  319. this.model.on('all', this.update, this);
  320. },
  321. /**
  322. * Render the file item view.
  323. *
  324. */
  325. render: function ()
  326. {
  327. $(this.el).html(this.template(this.computeData()));
  328. // Bind events
  329. this.bindEvents();
  330. // Update elements
  331. this.update();
  332. },
  333. /**
  334. * Update upload progress.
  335. *
  336. */
  337. updateProgress: function (progress)
  338. {
  339. var percent = parseInt(progress.loaded / progress.total * 100, 10);
  340. $('div.progress', this.el)
  341. .find('.bar')
  342. .css('width', percent+'%')
  343. .parent()
  344. .find('.progress-label')
  345. .html(this.getHelpers().displaySize(progress.loaded)+' of '+this.getHelpers().displaySize(progress.total));
  346. },
  347. /**
  348. * File upload has failed.
  349. *
  350. */
  351. hasFailed: function (error)
  352. {
  353. $('span.message', this.el).html('<i class="icon-error"></i> '+error);
  354. },
  355. /**
  356. * File upload is done.
  357. *
  358. */
  359. hasDone: function (result)
  360. {
  361. $('span.message', this.el).html('<i class="icon-success"></i> Uploaded');
  362. },
  363. /**
  364. * Update view without complete rendering.
  365. *
  366. */
  367. update: function ()
  368. {
  369. var when_pending = $('span.size, button#btn-cancel', this.el),
  370. when_running = $('div.progress, button#btn-cancel', this.el),
  371. when_done = $('span.message, button#btn-clear', this.el);
  372. if (this.model.isPending()) {
  373. when_running.add(when_done).addClass('hidden');
  374. when_pending.removeClass('hidden');
  375. } else if (this.model.isRunning()) {
  376. when_pending.add(when_done).addClass('hidden');
  377. when_running.removeClass('hidden');
  378. } else if (this.model.isDone() || this.model.isError()) {
  379. when_pending.add(when_running).addClass('hidden');
  380. when_done.removeClass('hidden');
  381. }
  382. },
  383. /**
  384. * Bind local elements events.
  385. *
  386. */
  387. bindEvents: function ()
  388. {
  389. var self = this;
  390. // DOM events
  391. $('button#btn-cancel', this.el).click(function(){
  392. self.model.cancel();
  393. self.collection.remove(self.model);
  394. });
  395. $('button#btn-clear', this.el).click(function(){
  396. self.model.destroy();
  397. self.collection.remove(self.model);
  398. })
  399. },
  400. /**
  401. * Compute data to be passed to the view.
  402. *
  403. */
  404. computeData: function ()
  405. {
  406. return $.extend(this.getHelpers(), this.model.get('data'));
  407. }
  408. })
  409. });
  410. })(Backbone);