6ac8ceb8caa665d92273bfb36a80d6cc10fdb906
[tine20] / tine20 / Tinebase / js / widgets / grid / FileUploadGrid.js
1 /*
2  * Tine 2.0
3  * 
4  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
5  * @author      Philipp Schüle <p.schuele@metaways.de>
6  * @copyright   Copyright (c) 2010-2011 Metaways Infosystems GmbH (http://www.metaways.de)
7  * @version     $Id
8  */
9
10 /*global Ext, Tine*/
11  
12 Ext.ns('Tine.widgets.grid');
13
14 /**
15  * @namespace   Tine.widgets.grid
16  * @class       Tine.widgets.grid.FileUploadGrid
17  * @extends     Ext.grid.GridPanel
18  * 
19  * <p>FileUpload grid for dialogs</p>
20  * <p>
21  * </p>
22  * 
23  * @author      Philipp Schüle <p.schuele@metaways.de>
24  * @copyright   Copyright (c) 2010-2011 Metaways Infosystems GmbH (http://www.metaways.de)
25  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
26  * 
27  * @param       {Object} config
28  * 
29  * @constructor Create a new  Tine.widgets.grid.FileUploadGrid
30  */
31 Tine.widgets.grid.FileUploadGrid = Ext.extend(Ext.grid.GridPanel, {
32
33     /**
34      * @cfg filesProperty
35      * @type String
36      */
37     filesProperty: 'files',
38     
39     /**
40      * @cfg showTopToolbar
41      * @type Boolean
42      * TODO     think about that -> when we deactivate the top toolbar, we lose the dropzone for files!
43      */
44     //showTopToolbar: null,
45
46     /**
47      * @cfg {Bool} readOnly
48      */
49     readOnly: false,
50
51     /**
52      * config values
53      * @private
54      */
55     header: false,
56     border: false,
57     deferredRender: false,
58     autoExpandColumn: 'name',
59     showProgress: true,
60     
61     i18nFileString: null,
62     
63     /**
64      * init
65      * @private
66      */
67     initComponent: function () {
68         this.i18nFileString = this.i18nFileString ? this.i18nFileString : i18n._('File');
69         
70         this.record = this.record || null;
71
72         // init actions
73         this.actionUpdater = new Tine.widgets.ActionUpdater({
74             containerProperty: 'container_id', 
75             evalGrants: false
76         });
77         
78         this.initToolbarAndContextMenu();
79         this.initStore();
80         this.initColumnModel();
81         this.initSelectionModel();
82         
83         this.plugins = [ new Ext.ux.grid.GridViewMenuPlugin({}) ];
84         this.enableHdMenu = false;
85       
86         Tine.widgets.grid.FileUploadGrid.superclass.initComponent.call(this);
87
88         this.on('rowcontextmenu', function (grid, row, e) {
89             e.stopEvent();
90             var selModel = grid.getSelectionModel();
91             if (! selModel.isSelected(row)) {
92                 selModel.selectRow(row);
93             }
94             this.contextMenu.showAt(e.getXY());
95         }, this);
96
97         if (! this.record || this.record.id == 0) {
98             this.on('rowdblclick', function (grid, row, e) {
99                 e.stopEvent();
100                 this.onDownload()
101             }, this);
102         }
103     },
104
105     setReadOnly: function(readOnly) {
106         this.readOnly = readOnly;
107         this.action_add.setDisabled(readOnly);
108         this.action_remove.setDisabled(readOnly);
109         this.action_add_from_filemanager.setDisabled(readOnly);
110     },
111
112     /**
113      * on upload failure
114      * @private
115      */
116     onUploadFail: function (uploader, fileRecord) {
117         
118         var dataSize;
119         if (fileRecord.html5upload) {
120             dataSize = Tine.Tinebase.registry.get('maxPostSize');
121         }
122         else {
123             dataSize = Tine.Tinebase.registry.get('maxFileUploadSize');
124         }
125         
126         Ext.MessageBox.alert(
127             i18n._('Upload Failed'),
128             i18n._('Could not upload file. Filesize could be too big. Please notify your Administrator. Max upload size:') + ' ' + parseInt(dataSize, 10) / 1048576 + ' MB'
129         ).setIcon(Ext.MessageBox.ERROR);
130         
131         this.getStore().remove(fileRecord);
132         if(this.loadMask) this.loadMask.hide();
133     },
134     
135     /**
136      * on remove
137      * @param {} button
138      * @param {} event
139      */
140     onRemove: function (button, event) {
141         
142         var selectedRows = this.getSelectionModel().getSelections();
143         for (var i = 0; i < selectedRows.length; i += 1) {
144             this.store.remove(selectedRows[i]);
145             var upload = Tine.Tinebase.uploadManager.getUpload(selectedRows[i].get('uploadKey'));
146             if (upload) {
147                 upload.setPaused(true);
148             }
149         }
150     },
151     
152     
153     /**
154      * on pause
155      * @param {} button
156      * @param {} event
157      */
158     onPause: function (button, event) {
159  
160         var selectedRows = this.getSelectionModel().getSelections();
161         for(var i=0; i < selectedRows.length; i++) {
162             var upload = Tine.Tinebase.uploadManager.getUpload(selectedRows[i].get('uploadKey'));
163             upload.setPaused(true);
164         }       
165         this.getSelectionModel().deselectRange(0, this.getSelectionModel().getCount());
166     },
167
168     
169     /**
170      * on resume
171      * @param {} button
172      * @param {} event
173      */
174     onResume: function (button, event) {
175
176         var selectedRows = this.getSelectionModel().getSelections();
177         for(var i=0; i < selectedRows.length; i++) {
178             var upload = Tine.Tinebase.uploadManager.getUpload(selectedRows[i].get('uploadKey'));
179             upload.resumeUpload();
180         }
181         this.getSelectionModel().deselectRange(0, this.getSelectionModel().getCount());
182     },
183
184     
185     /**
186      * init toolbar and context menu
187      * @private
188      */
189     initToolbarAndContextMenu: function () {
190         var me = this;
191
192         this.action_add = new Ext.Action(this.getAddAction());
193
194         if (Tine.Tinebase.appMgr.isEnabled('Filemanager')) {
195             var filemanager = Tine.Tinebase.appMgr.get('Filemanager');
196
197             this.action_add_from_filemanager = new Ext.Action({
198                 text: String.format(filemanager.i18n._('Add {0} from Filemanager'), this.i18nFileString),
199                 iconCls: 'action_add',
200                 scope: this,
201                 handler: function () {
202                     var filePickerDialog = new Tine.Filemanager.FilePickerDialog({
203                         title: filemanager.i18n._('Select a file'),
204                         singleSelect: true,
205                         constraint: 'file'
206                     });
207
208                     filePickerDialog.openWindow();
209
210                     filePickerDialog.on('selected', function(node) {
211                         me.onFileSelectFromFilemanager.call(me, node);
212                     });
213                 }
214             });
215         }
216
217         this.action_remove = new Ext.Action({
218             text: String.format(i18n._('Remove {0}'), this.i18nFileString),
219             iconCls: 'action_remove',
220             scope: this,
221             disabled: true,
222             handler: this.onRemove
223         });
224         
225         this.action_pause = new Ext.Action({
226             text: i18n._('Pause upload'),
227             iconCls: 'action_pause',
228             scope: this,
229             handler: this.onPause,
230             actionUpdater: this.isPauseEnabled
231         });
232         
233         this.action_resume = new Ext.Action({
234             text: i18n._('Resume upload'),
235             iconCls: 'action_resume',
236             scope: this,
237             handler: this.onResume,
238             actionUpdater: this.isResumeEnabled
239         });
240         
241         this.tbar = [
242             this.action_add
243         ];
244
245         if (Tine.Tinebase.appMgr.isEnabled('Filemanager')) {
246             this.tbar.push(this.action_add_from_filemanager)
247         }
248
249         this.tbar.push(this.action_remove);
250         
251         this.contextMenu = new Ext.menu.Menu({
252             plugins: [{
253                 ptype: 'ux.itemregistry',
254                 key:   'Tinebase-MainContextMenu'
255             }],
256             items:  [
257                  this.action_remove,
258                  this.action_pause,
259                  this.action_resume
260             ]
261         });
262         
263         this.actionUpdater.addActions([
264             this.action_pause,
265             this.action_resume
266         ]);
267
268     },
269     
270     /**
271      * init store
272      * @private
273      */
274     initStore: function () {
275         this.store = new Ext.data.SimpleStore({
276             fields: Ext.ux.file.Upload.file
277         });
278
279         this.store.on('add', this.onStoreAdd, this);
280
281         this.loadRecord(this.record);
282     },
283
284     onStoreAdd: function(store, records, idx) {
285         Ext.each(records, function (attachment) {
286             if (attachment.get('url')) {
287                 // we can't use Ext.data.connection here as we can't control xhr obj. directly :-(
288                 var me = this,
289                     url = attachment.get('url'),
290                     name = url.split('/').pop(),
291                     xhr = new XMLHttpRequest();
292
293                 xhr.open('GET', url, true);
294                 xhr.responseType = 'blob';
295
296                 store.suspendEvents();
297                 attachment.set('name', name);
298                 attachment.set('type', name.split('.').pop());
299                 store.resumeEvents();
300
301                 xhr.onprogress = function (e) {
302                     var progress = Math.floor(100 * e.loaded / e.total) + '% loaded';
303                     console.log(e);
304                 }
305
306
307                 xhr.onload = function (e) {
308 //                    attachment.set('type', xhr.response.type);
309 //                    attachment.set('size', xhr.response.size);
310
311                     var upload = new Ext.ux.file.Upload({
312                         file: new File([xhr.response], name),
313                         type: xhr.response.type,
314                         size: xhr.response.size
315                     });
316                     // work around chrome bug which dosn't take type from blob
317                     upload.file.fileType = xhr.response.type;
318
319                     var uploadKey = Tine.Tinebase.uploadManager.queueUpload(upload);
320                     var fileRecord = Tine.Tinebase.uploadManager.upload(uploadKey);
321
322                     upload.on('uploadfailure', me.onUploadFail, me);
323                     upload.on('uploadcomplete', me.onUploadComplete, fileRecord);
324                     upload.on('uploadstart', Tine.Tinebase.uploadManager.onUploadStart, me);
325
326                     store.remove(attachment);
327                     store.add(fileRecord);
328
329                 };
330
331                 xhr.send();
332
333             }
334         }, this);
335     },
336
337     /**
338      * On download attached file from panel
339      */
340     onDownload: function() {
341         var selectedRows = this.getSelectionModel().getSelections();
342
343         selectedRows.forEach(function (attachement) {
344             new Ext.ux.file.Download({
345                 params: {
346                     requestType: 'HTTP',
347                     method: 'Tinebase.downloadTempfile',
348                     tmpFileId: attachement.get('id')
349                 }
350             }).start();
351         });
352     },
353
354     /**
355      * returns add action
356      * 
357      * @return {Object} add action config
358      */
359     getAddAction: function () {
360         return {
361             text: String.format(i18n._('Add {0}'), this.i18nFileString),
362             iconCls: 'action_add',
363             scope: this,
364             plugins: [{
365                 ptype: 'ux.browseplugin',
366                 multiple: true,
367                 dropElSelector: 'div[id=' + this.id + ']'
368             }],
369             handler: this.onFilesSelect
370         };
371     },
372     
373     /**
374      * populate grid store
375      *
376      * @param {} record
377      */
378     loadRecord: function (record) {
379         if (record && record.get(this.filesProperty)) {
380             var files = record.get(this.filesProperty);
381             for (var i = 0; i < files.length; i += 1) {
382                 var file = new Ext.ux.file.Upload.file(files[i]);
383                 file.data.status = 'complete';
384                 this.store.add(file);
385             }
386         }
387     },
388     
389     /**
390      * init cm
391      * @private
392      */
393     initColumnModel: function () {
394         this.cm = new Ext.grid.ColumnModel(this.getColumns());
395     },
396     
397     getColumns: function() {
398         var columns = [{
399             resizable: true,
400             id: 'name',
401             dataIndex: 'name',
402             width: 300,
403             header: i18n._('name'),
404             renderer: Ext.ux.PercentRendererWithName
405         }, {
406             resizable: true,
407             id: 'size',
408             dataIndex: 'size',
409             width: 70,
410             header: i18n._('size'),
411             renderer: Ext.util.Format.fileSize
412         }, {
413             resizable: true,
414             id: 'type',
415             dataIndex: 'type',
416             width: 70,
417             header: i18n._('type')
418             // TODO show type icon?
419             //renderer: Ext.util.Format.fileSize
420         }];
421         
422         return columns;
423     },
424
425     /**
426      * init sel model
427      * @private
428      */
429     initSelectionModel: function () {
430         this.selModel = new Ext.grid.RowSelectionModel({multiSelect: true});
431         
432         this.selModel.on('selectionchange', function (selModel) {
433             var rowCount = selModel.getCount();
434             this.action_remove.setDisabled(this.readOnly || rowCount === 0);
435             this.actionUpdater.updateActions(selModel);
436
437         }, this);
438     },
439     
440     /**
441      * upload new file and add to store
442      * 
443      * @param {} btn
444      * @param {} e
445      */
446     onFilesSelect: function (fileSelector, e) {
447         var files = fileSelector.getFileList();
448         Ext.each(files, function (file) {
449
450             var upload = new Ext.ux.file.Upload({
451                 file: file,
452                 fileSelector: fileSelector
453             });
454
455             var uploadKey = Tine.Tinebase.uploadManager.queueUpload(upload);
456             var fileRecord = Tine.Tinebase.uploadManager.upload(uploadKey);
457             
458             upload.on('uploadfailure', this.onUploadFail, this);
459             upload.on('uploadcomplete', this.onUploadComplete, fileRecord);
460             upload.on('uploadstart', Tine.Tinebase.uploadManager.onUploadStart, this);
461
462             if(fileRecord.get('status') !== 'failure' ) {
463                 this.store.add(fileRecord);
464             }
465
466             
467         }, this);
468         
469     },
470
471     /**
472      * Add one or more files from filemanager
473      *
474      * @param nodes
475      */
476     onFileSelectFromFilemanager: function (nodes) {
477         var me = this;
478
479         Ext.each(nodes, function(node) {
480             var record = new Tine.Filemanager.Model.Node(node);
481
482             if (me.store.find('name', record.get('name')) === -1) {
483                 me.store.add(record);
484             } else {
485                 Ext.MessageBox.show({
486                     title: i18n._('Failure'),
487                     msg: i18n._('This file is already attached to this record.'),
488                     buttons: Ext.MessageBox.OK,
489                     icon: Ext.MessageBox.ERROR
490                 });
491             }
492         });
493     },
494     
495     onUploadComplete: function(upload, fileRecord) {
496         fileRecord.beginEdit();
497         fileRecord.set('status', 'complete');
498         fileRecord.set('progress', 100);
499         fileRecord.commit(false);
500         Tine.Tinebase.uploadManager.onUploadComplete();
501     },
502     
503     /**
504      * returns true if files are uploading atm
505      * 
506      * @return {Boolean}
507      */
508     isUploading: function () {
509         var uploadingFiles = this.store.query('status', 'uploading');
510         return (uploadingFiles.getCount() > 0);
511     },
512     
513     isPauseEnabled: function(action, grants, records) {
514          
515         for(var i=0; i<records.length; i++) {
516             if(records[i].get('type') === 'folder') {
517                 action.hide();
518                 return;
519             }
520         }
521         
522         for(var i=0; i < records.length; i++) {
523             if(!records[i].get('status') || (records[i].get('type ') !== 'folder' && records[i].get('status') !== 'paused'
524                     &&  records[i].get('status') !== 'uploading' && records[i].get('status') !== 'pending')) {
525                 action.hide();
526                 return;
527             }
528         }
529         
530         action.show();
531         
532         for(var i=0; i < records.length; i++) {
533             if(records[i].get('status')) {
534                 action.setDisabled(false);
535             }
536             else {
537                 action.setDisabled(true);
538             }
539             if(records[i].get('status') && records[i].get('status') !== 'uploading'){
540                 action.setDisabled(true);
541             }
542             
543         }             
544     },
545
546     isResumeEnabled: function(action, grants, records) {
547         for(var i=0; i<records.length; i++) {
548             if(records[i].get('type') === 'folder') {
549                 action.hide();
550                 return;
551             }
552         }
553        
554         for(var i=0; i < records.length; i++) {
555             if(!records[i].get('status') || (records[i].get('type ') !== 'folder' &&  records[i].get('status') !== 'uploading' 
556                     &&  records[i].get('status') !== 'paused' && records[i].get('status') !== 'pending')) {
557                 action.hide();
558                 return;
559             }
560         }
561         
562         action.show();
563         
564         for(var i=0; i < records.length; i++) {
565             if(records[i].get('status')) {
566                 action.setDisabled(false);
567             }
568             else {
569                 action.setDisabled(true);
570             }
571             if(records[i].get('status') && records[i].get('status') !== 'paused'){
572                 action.setDisabled(true);
573             }
574             
575         }   
576     }
577 });