You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

519 lines
14 KiB

  1. (function($K)
  2. {
  3. $K.add('module', 'upload', {
  4. init: function(app, context)
  5. {
  6. this.app = app;
  7. this.utils = app.utils;
  8. this.animate = app.animate;
  9. this.response = app.response;
  10. this.progress = app.progress;
  11. // defaults
  12. var defaults = {
  13. size: 120, // pixels
  14. url: false,
  15. urlRemove: false,
  16. param: false,
  17. type: false, // image, file
  18. multiple: false,
  19. placeholder: 'Drop files here or click to upload',
  20. progress: false,
  21. target: false,
  22. append: false
  23. };
  24. // context
  25. this.context = context;
  26. this.params = context.getParams(defaults);
  27. this.$element = context.getElement();
  28. this.$target = context.getTarget();
  29. // local
  30. this.statusMap = ['hover', 'error', 'success', 'drop'];
  31. },
  32. // public
  33. start: function()
  34. {
  35. this._buildBox();
  36. this._buildInput();
  37. this._buildCount();
  38. this._buildType();
  39. this._buildPlaceholder();
  40. this._buildSize();
  41. this._buildMultiple();
  42. this._buildItems();
  43. this._buildEvents();
  44. },
  45. stop: function()
  46. {
  47. this.$box.remove();
  48. this.$element.off('.kube.upload');
  49. },
  50. // private
  51. _buildBox: function()
  52. {
  53. if (this.params.type === 'image')
  54. {
  55. this.$box = this.$element.find('.upload-item');
  56. }
  57. else
  58. {
  59. this.$box = this.$element;
  60. }
  61. },
  62. _buildInput: function()
  63. {
  64. this.$input = $K.dom('<input>');
  65. this.$input.attr('type', 'file');
  66. this.$input.attr('name', this._getParamName());
  67. this.$input.hide();
  68. this.$element.before(this.$input);
  69. },
  70. _buildCount: function()
  71. {
  72. this.$inputCount = $K.dom('<input>');
  73. this.$inputCount.attr('type', 'hidden');
  74. this.$inputCount.attr('name', this._getParamName() + '-count');
  75. this.$inputCount.val(0);
  76. this.$element.before(this.$inputCount);
  77. },
  78. _buildType: function()
  79. {
  80. this.isBox = this.$element.hasClass('upload');
  81. },
  82. _buildPlaceholder: function()
  83. {
  84. if (this.isBox)
  85. {
  86. var $placeholder = $K.dom('<span>');
  87. $placeholder.addClass('upload-placeholder');
  88. $placeholder.html(this.params.placeholder);
  89. this.$element.append($placeholder);
  90. }
  91. },
  92. _buildSize: function()
  93. {
  94. if (this.isBox)
  95. {
  96. this.$box.css({
  97. height: this.params.size + 'px'
  98. });
  99. }
  100. else if (this.params.type === 'image')
  101. {
  102. this.$box.css({
  103. width: this.params.size + 'px',
  104. height: this.params.size + 'px'
  105. });
  106. }
  107. },
  108. _buildMultiple: function()
  109. {
  110. this.isMultiple = this.params.multiple;
  111. if (this.isMultiple)
  112. {
  113. this.$input.attr('multiple', 'true');
  114. }
  115. },
  116. _buildItems: function()
  117. {
  118. if (!this.params.type) return;
  119. var isFile = (this.params.type === 'file');
  120. var $target = (isFile) ? this.$target : this.$element;
  121. var fn = (isFile) ? '_removeFile' : '_removeImage';
  122. var $closes = $target.find('.close');
  123. $closes.on('click', this[fn].bind(this));
  124. if (!isFile)
  125. {
  126. $closes.closest('.upload-item').addClass('is-uploaded');
  127. }
  128. this.$inputCount.val($closes.length);
  129. },
  130. _buildEvents: function()
  131. {
  132. this.$input.on('change.redactor.upload', this._change.bind(this));
  133. this.$box.on('click.redactor.upload', this._click.bind(this));
  134. this.$box.on('drop.redactor.upload', this._drop.bind(this));
  135. this.$box.on('dragover.redactor.upload', this._dragover.bind(this));
  136. this.$box.on('dragleave.redactor.upload', this._dragleave.bind(this));
  137. },
  138. // Events
  139. _click: function(e)
  140. {
  141. e.preventDefault();
  142. var $el = $K.dom(e.target);
  143. if ($el.hasClass('close')) return;
  144. this.$input.click();
  145. },
  146. _change: function(e)
  147. {
  148. this.app.broadcast('upload.start', this);
  149. this._send(e, this.$input.get().files);
  150. },
  151. _drop: function(e)
  152. {
  153. e.preventDefault();
  154. this._clearStatuses();
  155. this._setStatus('drop');
  156. this.app.broadcast('upload.start', this);
  157. this._send(e);
  158. },
  159. _dragover: function(e)
  160. {
  161. e.preventDefault();
  162. this._setStatus('hover');
  163. return false;
  164. },
  165. _dragleave: function(e)
  166. {
  167. e.preventDefault();
  168. this._removeStatus('hover');
  169. return false;
  170. },
  171. // Count
  172. _upCount: function()
  173. {
  174. var val = this.$inputCount.val();
  175. val++;
  176. this.$inputCount.val(val);
  177. },
  178. _downCount: function()
  179. {
  180. var val = this.$inputCount.val();
  181. val--;
  182. val = (val < 0) ? 0 : val;
  183. this.$inputCount.val(val);
  184. },
  185. _clearCount: function()
  186. {
  187. this.$inputCount.val(0);
  188. },
  189. // Name
  190. _getParamName: function()
  191. {
  192. return (this.params.param) ? this.params.param : 'file';
  193. },
  194. _getHiddenName: function()
  195. {
  196. var name = this._getParamName();
  197. return (this.isMultiple) ? name + '-uploaded[]' : name + '-uploaded';
  198. },
  199. // Status
  200. _clearStatuses: function()
  201. {
  202. this.$box.removeClass('is-upload-' + this.statusMap.join(' is-upload-'));
  203. },
  204. _setStatus: function(status)
  205. {
  206. this.$box.addClass('is-upload-' + status);
  207. },
  208. _removeStatus: function(status)
  209. {
  210. this.$box.removeClass('is-upload-' + status);
  211. },
  212. // Target
  213. _clearTarget: function()
  214. {
  215. var $items = this.$target.find('.upload-item');
  216. $items.each(function(node)
  217. {
  218. var $node = $K.dom(node);
  219. this._removeFileRequest($node.attr('data-id'));
  220. }.bind(this));
  221. this._clearCount();
  222. this.$target.html('');
  223. },
  224. _clearBox: function()
  225. {
  226. var $items = this.$target.find('.upload-item');
  227. $items.each(function(node)
  228. {
  229. var $node = $K.dom(node);
  230. this._removeFileRequest($node.attr('data-id'));
  231. }.bind(this));
  232. this._clearCount();
  233. this.$target.html('');
  234. },
  235. // Remove
  236. _removeFile: function(e)
  237. {
  238. e.preventDefault();
  239. var $el = $K.dom(e.target);
  240. var $item = $el.closest('.upload-item');
  241. var id = $item.attr('data-id');
  242. this.animate.run($item, 'fadeOut', function()
  243. {
  244. $item.remove();
  245. this._downCount();
  246. this._removeFileRequest(id);
  247. // clear target
  248. if (this.$target.find('.upload-item').length === 0)
  249. {
  250. this.$target.html('');
  251. }
  252. }.bind(this))
  253. },
  254. _removeImage: function(e)
  255. {
  256. e.preventDefault();
  257. var $el = $K.dom(e.target);
  258. var $item = $el.closest('.upload-item');
  259. var id = $item.attr('data-id');
  260. if (this.isMultiple)
  261. {
  262. this.animate.run($item, 'fadeOut', function()
  263. {
  264. $item.remove();
  265. this._downCount();
  266. this._removeFileRequest(id);
  267. }.bind(this))
  268. }
  269. else
  270. {
  271. var $img = $item.find('img');
  272. $el.hide();
  273. this.animate.run($img, 'fadeOut', function()
  274. {
  275. this.$box.html('');
  276. this.$box.removeClass('is-uploaded');
  277. this._clearCount();
  278. this._removeFileRequest(id);
  279. }.bind(this))
  280. }
  281. },
  282. _removeFileRequest: function(id)
  283. {
  284. if (this.params.urlRemove)
  285. {
  286. $K.ajax.post({
  287. url: this.params.urlRemove,
  288. data: { id: id }
  289. });
  290. }
  291. },
  292. // Send
  293. _send: function(e, files)
  294. {
  295. e = e.originalEvent || e;
  296. files = (files) ? files : e.dataTransfer.files;
  297. var data = new FormData();
  298. var name = this._getParamName();
  299. data = this._buildData(name, files, data);
  300. if (this.params.append)
  301. {
  302. data = this.utils.extendData(data, this.params.append);
  303. }
  304. this._sendData(data, files, e);
  305. },
  306. _sendData: function(data, files, e)
  307. {
  308. if (this.params.progress) this.progress.show();
  309. $K.ajax.post({
  310. url: this.params.url,
  311. data: data,
  312. before: function(xhr)
  313. {
  314. return this.app.broadcast('upload.beforeSend', this, xhr);
  315. }.bind(this),
  316. success: function(response)
  317. {
  318. this._complete(response, e);
  319. }.bind(this)
  320. });
  321. },
  322. _buildData: function(name, files, data)
  323. {
  324. for (var i = 0; i < files.length; i++)
  325. {
  326. data.append(name + '[]', files[i]);
  327. }
  328. return data;
  329. },
  330. _complete: function (response, e)
  331. {
  332. this._clearStatuses();
  333. if (this.params.progress) this.progress.hide();
  334. // error
  335. var json = (Array.isArray(response)) ? response[0] : response;
  336. if (typeof json.type !== 'undefined' && json.type === 'error')
  337. {
  338. this._setStatus('error');
  339. this.response.parse(response);
  340. this.app.broadcast('upload.error', this, response);
  341. }
  342. // complete
  343. else
  344. {
  345. this._setStatus('success');
  346. switch (this.params.type)
  347. {
  348. case 'image':
  349. this._completeBoxImage(response);
  350. break;
  351. case 'file':
  352. this._completeBoxFile(response);
  353. break;
  354. default:
  355. this._completeBoxUpload(response);
  356. }
  357. this.app.broadcast('upload.complete', this, response);
  358. setTimeout(this._clearStatuses.bind(this), 500);
  359. }
  360. },
  361. _completeBoxUpload: function(response)
  362. {
  363. this.response.parse(response);
  364. },
  365. _completeBoxImage: function(response)
  366. {
  367. for (var key in response)
  368. {
  369. // img
  370. var $img = $K.dom('<img>');
  371. $img.attr('src', response[key].url);
  372. // close
  373. var $close = $K.dom('<span>');
  374. $close.addClass('close');
  375. $close.on('click', this._removeImage.bind(this));
  376. // hidden
  377. var $hidden = $K.dom('<input>');
  378. $hidden.attr('type', 'hidden');
  379. $hidden.attr('name', this._getHiddenName());
  380. $hidden.val(response[key].id);
  381. // item
  382. var $item = $K.dom('<div>');
  383. $item.addClass('upload-item is-uploaded');
  384. $item.attr('data-id', response[key].id);
  385. if (this.isMultiple)
  386. {
  387. // append
  388. $item.append($close);
  389. $item.append($img);
  390. $item.append($hidden);
  391. this.$box.last().before($item);
  392. }
  393. // single
  394. else
  395. {
  396. var $lastImg = this.$box.find('img');
  397. if ($lastImg.length !== 0)
  398. {
  399. this._removeFileRequest(this.$box.attr('data-id'));
  400. }
  401. this.$box.html('');
  402. this.$box.attr('data-id', response[key].id);
  403. this.$box.append($close);
  404. this.$box.append($img);
  405. this.$box.append($hidden);
  406. return;
  407. }
  408. }
  409. },
  410. _completeBoxFile: function(response)
  411. {
  412. if (!this.isMultiple) this._clearTarget();
  413. for (var key in response)
  414. {
  415. // item
  416. var $item = $K.dom('<div>');
  417. $item.addClass('upload-item');
  418. $item.attr('data-id', response[key].id);
  419. // file
  420. var $file = $K.dom('<span>');
  421. $file.html(response[key].name);
  422. // close
  423. var $close = $K.dom('<span>');
  424. $close.addClass('close');
  425. $close.on('click', this._removeFile.bind(this));
  426. // hidden
  427. var $hidden = $K.dom('<input>');
  428. $hidden.attr('type', 'hidden');
  429. $hidden.attr('name', this._getHiddenName());
  430. $hidden.val(response[key].id);
  431. // size
  432. if (typeof response[key].size !== 'undefined')
  433. {
  434. var $size = $K.dom('<em>');
  435. $size.html(response[key].size);
  436. $file.append($size);
  437. }
  438. // append
  439. $item.append($close);
  440. $item.append($file);
  441. $item.append($hidden);
  442. // target
  443. this.$target.append($item);
  444. this._upCount();
  445. }
  446. }
  447. });
  448. })(Kube);