multiuploaddialog.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. (function(){
  2. var maxFileSize = parseInt(MODx.config['upload_maxsize'], 10);
  3. var permittedFileTypes = MODx.config['upload_files'].toLowerCase().split(',');
  4. FileAPI.debug = false;
  5. FileAPI.staticPath = MODx.config['manager_url'] + 'assets/fileapi/';
  6. var api = {
  7. humanFileSize: function(bytes, si) {
  8. var thresh = si ? 1000 : 1024;
  9. if(bytes < thresh) return bytes + ' B';
  10. var units = si ? ['kB','MB','GB','TB','PB','EB','ZB','YB'] : ['KiB','MiB','GiB','TiB','PiB','EiB','ZiB','YiB'];
  11. var u = -1;
  12. do {
  13. bytes /= thresh;
  14. ++u;
  15. } while(bytes >= thresh);
  16. return bytes.toFixed(1)+' '+units[u];
  17. },
  18. getFileExtension: function(filename)
  19. {
  20. var result = '';
  21. var parts = filename.split('.');
  22. if (parts.length > 1) {
  23. result = parts.pop();
  24. }
  25. return result;
  26. },
  27. isFileSizePermitted: function(size){
  28. return (size <= maxFileSize);
  29. },
  30. formatBytes: function(size, unit){
  31. unit = unit || FileAPI.MB;
  32. return Math.round(((size / unit) + 0.00001) * 100) / 100;
  33. }
  34. };
  35. Ext.namespace('MODx.util.MultiUploadDialog');
  36. /**
  37. * File upload browse button.
  38. *
  39. * @class MODx.util.MultiUploadDialog.BrowseButton
  40. */
  41. MODx.util.MultiUploadDialog.BrowseButton = Ext.extend(Ext.Button,{
  42. input_name : 'file',
  43. input_file : null,
  44. original_handler : null,
  45. original_scope : null,
  46. /**
  47. * @access private
  48. */
  49. initComponent : function()
  50. {
  51. MODx.util.MultiUploadDialog.BrowseButton.superclass.initComponent.call(this);
  52. this.original_handler = this.handler || null;
  53. this.original_scope = this.scope || window;
  54. this.handler = null;
  55. this.scope = null;
  56. },
  57. /**
  58. * @access private
  59. */
  60. onRender : function(ct, position)
  61. {
  62. MODx.util.MultiUploadDialog.BrowseButton.superclass.onRender.call(this, ct, position);
  63. this.createInputFile();
  64. },
  65. /**
  66. * @access private
  67. */
  68. createInputFile : function()
  69. {
  70. var button_container = this.el.child('button').wrap();
  71. // button_container.position('relative');
  72. this.input_file = button_container.createChild({
  73. tag: 'input',
  74. type: 'file',
  75. size: 1,
  76. name: this.input_name || Ext.id(this.el),
  77. style: "cursor: pointer; display: inline-block; opacity: 0; position: absolute; top: 0; left: 0; width: 100%; height: 100%;",
  78. multiple: true
  79. });
  80. // this can all be done via the inline css above
  81. // doing it like this prevents a too big hidden file field creating weird hover behavoir on the buttons
  82. // this.input_file.setOpacity(0.0);
  83. // var button_box = this.el.getBox();
  84. // this.input_file.setStyle('font-size', (button_box.height * 1) + 'px');
  85. // var adj = {x: -3, y: -3};
  86. // this.input_file.setLeft(adj.x + 'px');
  87. // this.input_file.setTop(adj.y + 'px');
  88. // this.input_file.setOpacity(0.0);
  89. if (this.handleMouseEvents) {
  90. this.input_file.on('mouseover', this.onMouseOver, this);
  91. this.input_file.on('mousedown', this.onMouseDown, this);
  92. }
  93. if(this.tooltip){
  94. if(typeof this.tooltip == 'object'){
  95. Ext.QuickTips.register(Ext.apply({target: this.input_file}, this.tooltip));
  96. }
  97. else {
  98. this.input_file.dom[this.tooltipType] = this.tooltip;
  99. }
  100. }
  101. this.input_file.on('change', this.onInputFileChange, this);
  102. this.input_file.on('click', function(e) { e.stopPropagation(); });
  103. },
  104. /**
  105. * @access public
  106. */
  107. detachInputFile : function(no_create)
  108. {
  109. var result = this.input_file;
  110. if (typeof this.tooltip == 'object') {
  111. Ext.QuickTips.unregister(this.input_file);
  112. }
  113. else {
  114. this.input_file.dom[this.tooltipType] = null;
  115. }
  116. this.input_file.removeAllListeners();
  117. this.input_file = null;
  118. return result;
  119. },
  120. /**
  121. * @access public
  122. */
  123. getInputFile : function()
  124. {
  125. return this.input_file;
  126. },
  127. /**
  128. * @access public
  129. */
  130. disable : function()
  131. {
  132. MODx.util.MultiUploadDialog.BrowseButton.superclass.disable.call(this);
  133. this.input_file.dom.disabled = true;
  134. },
  135. /**
  136. * @access public
  137. */
  138. enable : function()
  139. {
  140. MODx.util.MultiUploadDialog.BrowseButton.superclass.enable.call(this);
  141. this.input_file.dom.disabled = false;
  142. },
  143. /**
  144. * @access public
  145. */
  146. destroy : function()
  147. {
  148. var input_file = this.detachInputFile(true);
  149. input_file.remove();
  150. input_file = null;
  151. MODx.util.MultiUploadDialog.BrowseButton.superclass.destroy.call(this);
  152. },
  153. /**
  154. * @access public
  155. */
  156. reset : function()
  157. {
  158. var form = new Ext.Element(document.createElement('form'));
  159. var buttonParent = this.input_file.parent();
  160. form.appendChild(this.input_file);
  161. form.dom.reset();
  162. buttonParent.appendChild(this.input_file);
  163. },
  164. /**
  165. * @access private
  166. */
  167. onInputFileChange : function(ev)
  168. {
  169. if (this.original_handler) {
  170. this.original_handler.call(this.original_scope, this, ev);
  171. }
  172. this.fireEvent('click', this, ev);
  173. }
  174. });
  175. Ext.reg('multiupload-browse-btn', MODx.util.MultiUploadDialog.BrowseButton);
  176. MODx.util.MultiUploadDialog.FilesGrid = function(config) {
  177. config = config || {};
  178. Ext.applyIf(config,{
  179. height: 300
  180. ,autoScroll: true
  181. ,border: false
  182. ,fields: ['name', 'size', 'file', 'permitted', 'message', 'uploaded']
  183. ,paging: false
  184. ,remoteSort: false
  185. ,viewConfig: {
  186. forceFit: true
  187. ,getRowClass: function(record, index, rowParams){
  188. if(!record.get('permitted')){
  189. return 'upload-error';
  190. }
  191. else if(record.get('uploaded')){
  192. return 'upload-success';
  193. }
  194. }
  195. }
  196. ,sortInfo: {
  197. field: 'name'
  198. ,direction: 'ASC'
  199. }
  200. ,deferRowRender: true
  201. ,anchor: '100%'
  202. ,autoExpandColumn: 'state'
  203. ,columns: [{
  204. header: _('upload.columns.file')
  205. ,dataIndex: 'name'
  206. ,sortable: true
  207. ,width: 200
  208. ,renderer: function(value, meta, record){
  209. var id = Ext.id();
  210. FileAPI.Image(record.get('file'))
  211. .resize(100, 50, 'max')
  212. .get(function (err, img){
  213. if(!err){
  214. img = new Ext.Element(img).addClass('upload-thumb');
  215. Ext.get(id).insertFirst(img);
  216. }
  217. });
  218. return '<div id="' + id + '"><p>' + value + '</p></div>';
  219. }
  220. }
  221. ,{
  222. header: _('upload.columns.state')
  223. ,id: 'state'
  224. ,width: 100
  225. ,renderer: function(value, meta, record) {
  226. if(!record.get('permitted') || record.get('uploaded')){
  227. return '<p class="upload-status-text">' + record.get('message') + '</p>';
  228. }
  229. else{
  230. var id = Ext.id();
  231. (function() {
  232. record.progressbar = new Ext.ProgressBar({
  233. renderTo: id
  234. ,value: 0
  235. ,text : '0 / ' + record.get('size')
  236. });
  237. }).defer(25);
  238. return '<div id="' + id + '"></div>';
  239. }
  240. }
  241. }]
  242. ,getMenu: function() {
  243. return [{
  244. text: _('upload.contextmenu.remove_entry')
  245. ,handler: this.removeFile
  246. }];
  247. }
  248. });
  249. MODx.util.MultiUploadDialog.FilesGrid.superclass.constructor.call(this,config);
  250. };
  251. Ext.extend(MODx.util.MultiUploadDialog.FilesGrid,MODx.grid.LocalGrid,{
  252. removeFile: function() {
  253. var selected = this.getSelectionModel().getSelections();
  254. this.getStore().remove(selected);
  255. }
  256. });
  257. Ext.reg('multiupload-grid-files',MODx.util.MultiUploadDialog.FilesGrid);
  258. MODx.util.MultiUploadDialog.Dialog = function(config) {
  259. this.filesGridId = Ext.id();
  260. config = config || {};
  261. Ext.applyIf(config,{
  262. permitted_extensions: permittedFileTypes
  263. ,autoHeight: true
  264. ,width: 600
  265. ,closeAction: 'hide'
  266. ,layout: 'anchor'
  267. ,listeners: {
  268. 'show': {fn: this.onShow}
  269. ,'hide': {fn: this.onHide}
  270. }
  271. ,items: [{
  272. xtype: 'multiupload-grid-files'
  273. ,id: this.filesGridId
  274. ,anchor: '100%'
  275. }
  276. ]
  277. ,buttons: [
  278. {
  279. xtype: 'multiupload-browse-btn'
  280. ,text: _('upload.buttons.choose')
  281. ,cls: 'primary-button'
  282. ,listeners: {
  283. 'click': {
  284. scope: this,
  285. fn: function(btn, ev){
  286. var files = FileAPI.getFiles(ev);
  287. this.addFiles(files);
  288. btn.reset();
  289. }
  290. }
  291. }
  292. }
  293. ,{
  294. xtype: 'splitbutton'
  295. ,text: _('upload.buttons.clear')
  296. ,listeners: {
  297. 'click': {
  298. scope: this,
  299. fn: this.clearStore
  300. }
  301. }
  302. ,menu: new Ext.menu.Menu({
  303. items: [
  304. {
  305. text: _('upload.clear_list.all')
  306. ,listeners: {
  307. 'click': {
  308. scope: this,
  309. fn: this.clearStore
  310. }
  311. }
  312. }
  313. ,{
  314. text: _('upload.clear_list.notpermitted')
  315. ,listeners: {
  316. 'click': {
  317. scope: this,
  318. fn: this.clearNotPermittedItems
  319. }
  320. }
  321. }
  322. ]
  323. })
  324. }
  325. ,{
  326. xtype: 'button'
  327. ,text: _('upload.buttons.upload')
  328. ,cls: 'primary-button'
  329. ,listeners: {
  330. 'click': {
  331. scope: this
  332. ,fn: this.startUpload
  333. }
  334. }
  335. }
  336. ,{
  337. xtype: 'button'
  338. ,text: _('upload.buttons.close')
  339. ,listeners: {
  340. 'click': {
  341. scope: this,
  342. fn: this.hideDialog
  343. }
  344. }
  345. }
  346. ]
  347. });
  348. MODx.util.MultiUploadDialog.Dialog.superclass.constructor.call(this,config);
  349. };
  350. var originalWindowOnShow = Ext.Window.prototype.onShow;
  351. var originalWindowOnHide = Ext.Window.prototype.onHide;
  352. Ext.extend(MODx.util.MultiUploadDialog.Dialog, Ext.Window, {
  353. addFiles: function(files){
  354. var store = Ext.getCmp(this.filesGridId).getStore();
  355. var dialog = this;
  356. FileAPI.each(files, function(file){
  357. var permitted = true;
  358. var message = '';
  359. if(!api.isFileSizePermitted(file.size)){
  360. message = _('upload.notpermitted.filesize', {
  361. 'size': api.humanFileSize(file.size),
  362. 'max': api.humanFileSize(maxFileSize)
  363. });
  364. permitted = false;
  365. }
  366. if(!dialog.isFileTypePermitted(file.name)){
  367. message = _('upload.notpermitted.extension', {
  368. 'ext': api.getFileExtension(file.name)
  369. });
  370. permitted = false;
  371. }
  372. var data = {
  373. 'name': file.name,
  374. 'size': api.humanFileSize(file.size),
  375. 'file': file,
  376. 'permitted': permitted,
  377. 'message': message,
  378. 'uploaded': false
  379. };
  380. var p = new store.recordType(data);
  381. store.insert(0, p);
  382. });
  383. },
  384. startUpload: function(){
  385. var dialog = this;
  386. var files = [];
  387. var params = Ext.apply(this.base_params, {
  388. 'HTTP_MODAUTH': MODx.siteId
  389. });
  390. var store = Ext.getCmp(this.filesGridId).getStore();
  391. store.each(function(){
  392. var file = this.get('file');
  393. if(this.get('permitted') && !this.get('uploaded')){
  394. file.record = this;
  395. files.push(file);
  396. }
  397. });
  398. var xhr = FileAPI.upload({
  399. url: this.url
  400. ,data: params
  401. ,files: { file: files }
  402. ,fileprogress: function(evt, file){
  403. file.record.progressbar.updateProgress(
  404. evt.loaded/evt.total,
  405. _('upload.upload_progress', {
  406. 'loaded': api.humanFileSize(evt.loaded),
  407. 'total': file.record.get('size')
  408. }),
  409. true);
  410. }
  411. ,filecomplete: function (err, xhr, file, options){
  412. if( !err ){
  413. var resp = Ext.util.JSON.decode(xhr.response);
  414. if(resp.success){
  415. file.record.set('uploaded', true);
  416. file.record.set('message', _('upload.upload.success'))
  417. }
  418. else{
  419. file.record.set('permitted', false);
  420. file.record.set('message', resp.message);
  421. }
  422. }
  423. else{
  424. if(xhr.status !== 401){
  425. MODx.msg.alert(_('upload.msg.title.error'), err);
  426. }
  427. }
  428. }
  429. ,complete: function(err, xhr){
  430. dialog.fireEvent('uploadsuccess');
  431. }
  432. });
  433. },
  434. removeEntry: function(record){
  435. Ext.getCmp(this.filesGridId).getStore().remove(record);
  436. },
  437. clearStore: function(){
  438. Ext.getCmp(this.filesGridId).getStore().removeAll();
  439. },
  440. clearNotPermittedItems: function(){
  441. var store = Ext.getCmp(this.filesGridId).getStore();
  442. var notPermitted = store.query('permitted', false);
  443. store.remove(notPermitted.getRange());
  444. },
  445. hideDialog: function(){
  446. this.hide();
  447. },
  448. onDDrag: function(ev){
  449. ev && ev.preventDefault();
  450. },
  451. onDDrop: function(ev){
  452. ev && ev.preventDefault();
  453. var dialog = this;
  454. FileAPI.getDropFiles(ev.browserEvent, function(files){
  455. if( files.length ){
  456. dialog.addFiles(files);
  457. }
  458. });
  459. },
  460. onShow: function(){
  461. // make sure the window is shown with the reworked windows
  462. var ret = originalWindowOnShow.apply(this,arguments);
  463. var store = Ext.getCmp(this.filesGridId).getStore();
  464. store.removeAll();
  465. if(!this.isDDSet){
  466. this.el.on('dragenter', this.onDDrag, this);
  467. this.el.on('dragover', this.onDDrag, this);
  468. this.el.on('dragleave', this.onDDrag, this);
  469. this.el.on('drop', this.onDDrop, this);
  470. this.isDDSet = true;
  471. }
  472. return ret;
  473. },
  474. onHide: function(){
  475. // make sure the window is hidden with the reworked windows
  476. var ret = originalWindowOnHide.apply(this,arguments);
  477. this.el.un('dragenter', this.onDDrag, this);
  478. this.el.un('dragover', this.onDDrag, this);
  479. this.el.un('dragleave', this.onDDrag, this);
  480. this.el.un('drop', this.onDDrop, this);
  481. this.isDDSet = false;
  482. return ret;
  483. },
  484. setBaseParams: function(params){
  485. this.base_params = params;
  486. this.setTitle(_('upload.title.destination_path', {'path': this.base_params['path']}));
  487. },
  488. isFileTypePermitted: function(filename){
  489. var ext = api.getFileExtension(filename);
  490. return (this.permitted_extensions.indexOf(ext.toLowerCase()) > -1);
  491. }
  492. });
  493. Ext.reg('multiupload-window-dialog', MODx.util.MultiUploadDialog.Dialog);
  494. })();