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