0011666: Some fixes for Expressodriver
[tine20] / tine20 / Expressodriver / js / NodeGridPanel.js
1 /*
2  * Tine 2.0
3  *
4  * @package     Expressodriver
5  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
6  * @copyright   Copyright (c) 2007-2014 Metaways Infosystems GmbH (http://www.metaways.de)
7  * @copyright   Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
8  * @author      Marcelo Teixeira <marcelo.teixeira@serpro.gov.br>
9  * @author      Edgar de Lucca <edgar.lucca@serpro.gov.br>
10  */
11 Ext.ns('Tine.Expressodriver');
12
13 /**
14  * File grid panel
15  *
16  * @namespace   Tine.Expressodriver
17  * @class       Tine.Expressodriver.NodeGridPanel
18  * @extends     Tine.widgets.grid.GridPanel
19  *
20  * <p>Node Grid Panel</p>
21  * <p><pre>
22  * </pre></p>
23  *
24  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
25  * @copyright   Copyright (c) 2007-2014 Metaways Infosystems GmbH (http://www.metaways.de)
26  * @copyright   Copyright (c) 2014 Serpro (http://www.serpro.gov.br)
27  * @author      Marcelo Teixeira <marcelo.teixeira@serpro.gov.br>
28  * @author      Edgar de Lucca <edgar.lucca@serpro.gov.br>
29  *
30  * @param       {Object} config
31  * @constructor
32  * Create a new Tine.Expressodriver.FileGridPanel
33  */
34 Tine.Expressodriver.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
35     /**
36      * @cfg filesProperty
37      * @type String
38      */
39     filesProperty: 'files',
40
41
42     /**
43      * config values
44      * @private
45      */
46     header: false,
47     border: false,
48     deferredRender: false,
49     autoExpandColumn: 'name',
50     showProgress: true,
51
52     recordClass: Tine.Expressodriver.Model.Node,
53     hasDetailsPanel: false,
54     evalGrants: true,
55
56     /**
57      * grid specific
58      * @private
59      */
60     defaultSortInfo: {field: 'name', direction: 'DESC'},
61     gridConfig: {
62         autoExpandColumn: 'name',
63         enableFileDialog: false,
64         enableDragDrop: true,
65         ddGroup: 'fileDDGroup'
66     },
67
68     ddGroup : 'fileDDGroup',
69     currentFolderNode : '/',
70
71     /**
72      * inits this cmp
73      * @private
74      */
75     initComponent: function() {
76         this.recordProxy = Tine.Expressodriver.fileRecordBackend;
77
78         this.gridConfig.cm = this.getColumnModel();
79
80         this.defaultFilters = [
81             {field: 'query', operator: 'contains', value: ''},
82             {field: 'path', operator: 'equals', value: this.app.getMainScreen().getWestPanel().getContainerTreePanel().getRootPath()}
83         ];
84
85         this.filterToolbar = this.filterToolbar || this.getFilterToolbar();
86         this.filterToolbar.getQuickFilterPlugin().criteriaIgnores.push({field: 'path'});
87
88         this.plugins = this.plugins || [];
89         this.plugins.push(this.filterToolbar);
90         this.plugins.push({
91             ptype: 'ux.browseplugin',
92             multiple: true,
93             scope: this,
94             enableFileDialog: false,
95             handler: this.onFilesSelect
96         });
97
98         Tine.Expressodriver.NodeGridPanel.superclass.initComponent.call(this);
99         this.getStore().on('load', this.onLoad);
100         Tine.Tinebase.uploadManager.on('update', this.onUpdate);
101     },
102
103     initFilterPanel: function() {},
104
105     /**
106      * after render handler
107      */
108     afterRender: function() {
109         Tine.Expressodriver.NodeGridPanel.superclass.afterRender.call(this);
110         this.action_upload.setDisabled(true);
111         this.initDropTarget();
112         this.currentFolderNode = this.app.getMainScreen().getWestPanel().getContainerTreePanel().getRootNode();
113     },
114
115     /**
116      * returns cm
117      *
118      * @return Ext.grid.ColumnModel
119      * @private
120      *
121      * TODO    add more columns
122      */
123     getColumnModel: function(){
124         var columns = [{
125                 id: 'tags',
126                 header: this.app.i18n._('Tags'),
127                 dataIndex: 'tags',
128                 width: 50,
129                 renderer: Tine.Tinebase.common.tagsRenderer,
130                 sortable: false,
131                 hidden: true
132             }, {
133                 id: 'name',
134                 header: this.app.i18n._("Name"),
135                 width: 70,
136                 sortable: true,
137                 dataIndex: 'name',
138                 renderer: Ext.ux.PercentRendererWithName
139             },{
140                 id: 'size',
141                 header: this.app.i18n._("Size"),
142                 width: 40,
143                 sortable: true,
144                 dataIndex: 'size',
145                 renderer: Tine.Tinebase.common.byteRenderer.createDelegate(this, [2, true], 3)
146             },{
147                 id: 'contenttype',
148                 header: this.app.i18n._("Contenttype"),
149                 width: 50,
150                 sortable: true,
151                 dataIndex: 'contenttype',
152                 renderer: function(value, metadata, record) {
153
154                     var app = Tine.Tinebase.appMgr.get('Expressodriver');
155                     if(record.data.type == 'folder') {
156                         return app.i18n._("Folder");
157                     }
158                     else {
159                         return value;
160                     }
161                 }
162             },{
163                 id: 'creation_time',
164                 header: this.app.i18n._("Creation Time"),
165                 width: 50,
166                 sortable: true,
167                 dataIndex: 'creation_time',
168                 renderer: Tine.Tinebase.common.dateTimeRenderer,
169                 hidden: true
170
171             },{
172                 id: 'created_by',
173                 header: this.app.i18n._("Created By"),
174                 width: 50,
175                 sortable: true,
176                 dataIndex: 'created_by',
177                 renderer: Tine.Tinebase.common.usernameRenderer,
178                 hidden: true
179
180             },{
181                 id: 'last_modified_time',
182                 header: this.app.i18n._("Last Modified Time"),
183                 width: 80,
184                 sortable: true,
185                 dataIndex: 'last_modified_time',
186                 renderer: Tine.Tinebase.common.dateTimeRenderer
187             },{
188                 id: 'last_modified_by',
189                 header: this.app.i18n._("Last Modified By"),
190                 width: 50,
191                 sortable: true,
192                 dataIndex: 'last_modified_by',
193                 renderer: Tine.Tinebase.common.usernameRenderer,
194                 hidden: true
195             }
196         ];
197
198         return new Ext.grid.ColumnModel({
199             defaults: {
200                 sortable: true,
201                 resizable: true
202             },
203             columns: columns
204         });
205     },
206
207     /**
208      * status column renderer
209      * @param {string} value
210      * @return {string}
211      */
212     statusRenderer: function(value) {
213         return this.app.i18n._hidden(value);
214     },
215
216     /**
217      * init ext grid panel
218      * @private
219      */
220     initGrid: function() {
221         Tine.Expressodriver.NodeGridPanel.superclass.initGrid.call(this);
222
223         if (this.usePagingToolbar) {
224            this.initPagingToolbar();
225         }
226     },
227
228     /**
229      * inserts a quota Message when using old Browsers with html4upload
230      */
231     initPagingToolbar: function() {
232         if(!this.pagingToolbar || !this.pagingToolbar.rendered) {
233             this.initPagingToolbar.defer(50, this);
234             return;
235         }
236         // old browsers
237         if (!((! Ext.isGecko && window.XMLHttpRequest && window.File && window.FileList) || (Ext.isGecko && window.FileReader))) {
238             var text = new Ext.Panel({padding: 2, html: String.format(this.app.i18n._('The max. Upload Filesize is {0} MB'), Tine.Tinebase.registry.get('maxFileUploadSize') / 1048576 )});
239             this.pagingToolbar.insert(12, new Ext.Toolbar.Separator());
240             this.pagingToolbar.insert(12, text);
241             this.pagingToolbar.doLayout();
242         }
243     },
244
245     /**
246      * returns filter toolbar -> supress OR filters
247      * @private
248      */
249     getFilterToolbar: function(config) {
250         config = config || {};
251         var plugins = [];
252         if (! Ext.isDefined(this.hasQuickSearchFilterToolbarPlugin) || this.hasQuickSearchFilterToolbarPlugin) {
253             this.quickSearchFilterToolbarPlugin = new Tine.widgets.grid.FilterToolbarQuickFilterPlugin();
254             plugins.push(this.quickSearchFilterToolbarPlugin);
255         }
256
257         return new Tine.widgets.grid.FilterToolbar(Ext.apply(config, {
258             app: this.app,
259             recordClass: this.recordClass,
260             filterModels: this.recordClass.getFilterModel().concat(this.getCustomfieldFilters()),
261             defaultFilter: 'query',
262             filters: this.defaultFilters || [],
263             plugins: plugins
264         }));
265     },
266
267     /**
268      * returns add action / test
269      *
270      * @return {Object} add action config
271      */
272     getAddAction: function () {
273         return {
274             requiredGrant: 'addGrant',
275             actionType: 'add',
276             text: this.app.i18n._('Upload'),
277             handler: this.onFilesSelect,
278             disabled: true,
279             scope: this,
280             plugins: [{
281                 ptype: 'ux.browseplugin',
282                 multiple: true,
283                 enableFileDrop: false,
284                 disable: true
285             }],
286             iconCls: this.app.appName + 'IconCls'
287         };
288     },
289
290     /**
291      * init actions with actionToolbar, contextMenu and actionUpdater
292      * @private
293      */
294     initActions: function() {
295         this.action_upload = new Ext.Action(this.getAddAction());
296
297         this.action_editFile = new Ext.Action({
298             requiredGrant: 'editGrant',
299             allowMultiple: false,
300             text: this.app.i18n._('Properties'),
301             handler: this.onEditFile,
302             iconCls: 'action_edit_file',
303             disabled: false,
304             actionType: 'edit',
305             scope: this
306         });
307         this.action_createFolder = new Ext.Action({
308             requiredGrant: 'addGrant',
309             actionType: 'reply',
310             allowMultiple: true,
311             text: this.app.i18n._('Create Folder'),
312             handler: this.onCreateFolder,
313             iconCls: 'action_create_folder',
314             disabled: true,
315             scope: this
316         });
317
318         this.action_goUpFolder = new Ext.Action({
319 //            requiredGrant: 'readGrant',
320             allowMultiple: true,
321             actionType: 'goUpFolder',
322             text: this.app.i18n._('Folder Up'),
323             handler: this.onLoadParentFolder,
324             iconCls: 'action_expressodriver_folder_up',
325             disabled: true,
326             scope: this
327         });
328
329         this.action_download = new Ext.Action({
330             requiredGrant: 'readGrant',
331             allowMultiple: false,
332             actionType: 'download',
333             text: this.app.i18n._('Save locally'),
334             handler: this.onDownload,
335             iconCls: 'action_expressodriver_save_all',
336             disabled: true,
337             scope: this
338         });
339
340         this.action_deleteRecord = new Ext.Action({
341             requiredGrant: 'deleteGrant',
342             allowMultiple: true,
343             singularText: this.app.i18n._('Delete'),
344             pluralText: this.app.i18n._('Delete'),
345             translationObject: this.i18nDeleteActionText ? this.app.i18n : Tine.Tinebase.translation,
346             text: this.app.i18n._('Delete'),
347             handler: this.onDeleteRecords,
348             disabled: true,
349             iconCls: 'action_delete',
350             scope: this
351         });
352
353         this.contextMenu = Tine.Expressodriver.GridContextMenu.getMenu({
354             nodeName: Tine.Expressodriver.Model.Node.getRecordName(),
355             actions: ['delete', 'rename', 'download', 'resume', 'pause', 'edit'],
356             scope: this,
357             backend: 'Expressodriver',
358             backendModel: 'Node'
359         });
360
361         this.folderContextMenu = Tine.Expressodriver.GridContextMenu.getMenu({
362             nodeName: this.app.i18n._(this.app.getMainScreen().getWestPanel().getContainerTreePanel().containerName),
363             actions: ['delete', 'rename'],
364             scope: this,
365             backend: 'Expressodriver',
366             backendModel: 'Node'
367         });
368
369         this.actionUpdater.addActions(this.contextMenu.items);
370         this.actionUpdater.addActions(this.folderContextMenu.items);
371
372         this.actionUpdater.addActions([
373            this.action_createFolder,
374            this.action_goUpFolder,
375            this.action_download,
376            this.action_deleteRecord,
377            this.action_editFile
378        ]);
379     },
380
381     /**
382      * get the right contextMenu
383      */
384     getContextMenu: function(grid, row, e) {
385         var r = this.store.getAt(row),
386             type = r ? r.get('type') : null,
387             pathParts = r ? r.get('path') : null;
388
389         var isRootFolder = pathParts ? pathParts.split('/').length < 3 : false;
390         if (isRootFolder) {
391             return;
392         } else if (type === 'folder') {
393             return this.folderContextMenu;
394         } else {
395             return this.contextMenu;
396         }
397     },
398
399     /**
400      * called on row context click
401      *
402      * @param {Ext.grid.GridPanel} grid
403      * @param {Number} row
404      * @param {Ext.EventObject} e
405      */
406     onRowContextMenu: function(grid, row, e) {
407         e.stopEvent();
408         var selModel = grid.getSelectionModel();
409         if (!selModel.isSelected(row)) {
410             // disable preview update if config option is set to false
411             this.updateOnSelectionChange = this.updateDetailsPanelOnCtxMenu;
412             selModel.selectRow(row);
413         }
414
415         var contextMenu = this.getContextMenu(grid, row, e);
416         if (contextMenu) {
417             contextMenu.showAt(e.getXY())
418         }
419         // reset preview update
420         this.updateOnSelectionChange = true;
421     },
422
423     /**
424      * get action toolbar
425      *
426      * @return {Ext.Toolbar}
427      */
428     getActionToolbar: function() {
429         if (! this.actionToolbar) {
430             this.actionToolbar = new Ext.Toolbar({
431                 defaults: {height: 55},
432                 items: [{
433                     xtype: 'buttongroup',
434                     columns: 8,
435                     defaults: {minWidth: 60},
436                     items: [
437                         this.splitAddButton ?
438                         Ext.apply(new Ext.SplitButton(this.action_upload), {
439                             scale: 'medium',
440                             rowspan: 2,
441                             iconAlign: 'top',
442                             arrowAlign:'right',
443                             menu: new Ext.menu.Menu({
444                                 items: [],
445                                 plugins: [{
446                                     ptype: 'ux.itemregistry',
447                                     key:   'Tine.widgets.grid.GridPanel.addButton'
448                                 }]
449                             })
450                         }) :
451                         Ext.apply(new Ext.Button(this.action_upload), {
452                             scale: 'medium',
453                             rowspan: 2,
454                             iconAlign: 'top'
455                         }),
456
457                         Ext.apply(new Ext.Button(this.action_editFile), {
458                             scale: 'medium',
459                             rowspan: 2,
460                             iconAlign: 'top'
461                         }),
462                         Ext.apply(new Ext.Button(this.action_deleteRecord), {
463                             scale: 'medium',
464                             rowspan: 2,
465                             iconAlign: 'top'
466                         }),
467                         Ext.apply(new Ext.Button(this.action_createFolder), {
468                             scale: 'medium',
469                             rowspan: 2,
470                             iconAlign: 'top'
471                         }),
472                         Ext.apply(new Ext.Button(this.action_goUpFolder), {
473                             scale: 'medium',
474                             rowspan: 2,
475                             iconAlign: 'top'
476                         }),
477                         Ext.apply(new Ext.Button(this.action_download), {
478                             scale: 'medium',
479                             rowspan: 2,
480                             iconAlign: 'top'
481                         })
482                  ]
483                 }, this.getActionToolbarItems()]
484             });
485
486             if (this.filterToolbar && typeof this.filterToolbar.getQuickFilterField == 'function') {
487                 this.actionToolbar.add('->', this.filterToolbar.getQuickFilterField());
488             }
489         }
490
491         return this.actionToolbar;
492     },
493
494     /**
495      * opens the edit dialog
496      */
497     onEditFile: function() {
498         var sel = this.getGrid().getSelectionModel().getSelections();
499
500         if(sel.length == 1) {
501             var record = new Tine.Expressodriver.Model.Node(sel[0].data);
502             var window = Tine.Expressodriver.NodeEditDialog.openWindow({record: record});
503         }
504
505         window.on('saveAndClose', function() {
506             this.getGrid().store.reload();
507         }, this);
508     },
509
510     /**
511      * create folder in current position
512      *
513      * @param {Ext.Component} button
514      * @param {Ext.EventObject} event
515      */
516     onCreateFolder: function(button, event) {
517         var app = this.app,
518             nodeName = Tine.Expressodriver.Model.Node.getContainerName();
519
520         Ext.MessageBox.prompt(this.app.i18n._('New Folder'), this.app.i18n._('Please enter the name of the new folder:'), function(_btn, _text) {
521             var currentFolderNode = app.getMainScreen().getCenterPanel().currentFolderNode;
522             if(currentFolderNode && _btn == 'ok') {
523                 if (! _text) {
524                     Ext.Msg.alert(String.format(this.app.i18n._('No {0} added'), nodeName), String.format(this.app.i18n._('You have to supply a {0} name!'), nodeName));
525                     return;
526                 }
527                 Ext.MessageBox.wait(_('Please wait'), String.format(_('Creating {0}...' ), nodeName));
528                 var filename = currentFolderNode.attributes.path + '/' + _text;
529                 Tine.Expressodriver.fileRecordBackend.createFolder(filename);
530
531             }
532         }, this);
533     },
534
535     /**
536      * delete selected files / folders
537      *
538      * @param {Ext.Component} button
539      * @param {Ext.EventObject} event
540      */
541     onDeleteRecords: function(button, event) {
542         var app = this.app,
543             nodeName = '',
544             sm = app.getMainScreen().getCenterPanel().selectionModel,
545             nodes = sm.getSelections();
546
547         if(nodes && nodes.length) {
548             for(var i=0; i<nodes.length; i++) {
549                 var currNodeData = nodes[i].data;
550
551                 if(typeof currNodeData.name == 'object') {
552                     nodeName += currNodeData.name.name + '<br />';
553                 }
554                 else {
555                     nodeName += currNodeData.name + '<br />';
556                 }
557             }
558         }
559
560         this.conflictConfirmWin = Tine.widgets.dialog.FileListDialog.openWindow({
561             modal: true,
562             allowCancel: false,
563             height: 180,
564             width: 300,
565             title: app.i18n._('Do you really want to delete the following files?'),
566             text: nodeName,
567             scope: this,
568             handler: function(button){
569                 if (nodes && button == 'yes') {
570                     this.store.remove(nodes);
571                     this.pagingToolbar.refresh.disable();
572                     Tine.Expressodriver.fileRecordBackend.deleteItems(nodes);
573                 }
574
575                 for(var i=0; i<nodes.length; i++) {
576                     var node = nodes[i];
577
578                     if(node.fileRecord) {
579                         var upload = Tine.Tinebase.uploadManager.getUpload(node.fileRecord.get('uploadKey'));
580                         upload.setPaused(true);
581                         Tine.Tinebase.uploadManager.unregisterUpload(upload.id);
582                     }
583
584                 }
585             }
586         }, this);
587     },
588
589     /**
590      * go up one folder
591      *
592      * @param {Ext.Component} button
593      * @param {Ext.EventObject} event
594      */
595     onLoadParentFolder: function(button, event) {
596         var app = this.app,
597             currentFolderNode = app.getMainScreen().getCenterPanel().currentFolderNode;
598
599         if(currentFolderNode && currentFolderNode.parentNode) {
600             app.getMainScreen().getCenterPanel().currentFolderNode = currentFolderNode.parentNode;
601             currentFolderNode.parentNode.select();
602         }
603     },
604
605     /**
606      * grid row doubleclick handler
607      *
608      * @param {Tine.Expressodriver.NodeGridPanel} grid
609      * @param {} row record
610      * @param {Ext.EventObjet} e
611      */
612     onRowDblClick: function(grid, row, e) {
613         //ver
614         var app = this.app;
615         var rowRecord = grid.getStore().getAt(row);
616
617         if(rowRecord.data.type == 'file') {
618             var downloadPath = rowRecord.data.path;
619             var downloader = new Ext.ux.file.Download({
620                 params: {
621                     method: 'Expressodriver.downloadFile',
622                     requestType: 'HTTP',
623                     id: '',
624                     path: downloadPath
625                 }
626             }).start();
627         }
628
629         else if (rowRecord.data.type == 'folder'){
630             var treePanel = app.getMainScreen().getWestPanel().getContainerTreePanel();
631
632             var currentFolderNode = treePanel.getNodeById(rowRecord.id);
633
634             if(currentFolderNode) {
635                 currentFolderNode.select();
636                 currentFolderNode.expand();
637                 app.getMainScreen().getCenterPanel().currentFolderNode = currentFolderNode;
638             } else {
639                 // get ftb path filter
640                 this.filterToolbar.filterStore.each(function(filter) {
641                     var field = filter.get('field');
642                     if (field === 'path') {
643                         filter.set('value', '');
644                         filter.set('value', rowRecord.data);
645                         filter.formFields.value.setValue(rowRecord.get('path'));
646
647                         this.filterToolbar.onFiltertrigger();
648                         return false;
649                     }
650                 }, this);
651             }
652         }
653     },
654
655     /**
656      * on upload failure
657      *
658      * @private
659      */
660     onUploadFail: function () {
661         Ext.MessageBox.alert(
662             _('Upload Failed'),
663             _('Could not upload file. Filesize could be too big. Please notify your Administrator. Max upload size:') + ' '
664             + Tine.Tinebase.common.byteRenderer(Tine.Tinebase.registry.get('maxFileUploadSize'))
665         ).setIcon(Ext.MessageBox.ERROR);
666
667         var app = Tine.Tinebase.appMgr.get('Expressodriver'),
668             grid = app.getMainScreen().getCenterPanel();
669         grid.pagingToolbar.refresh.enable();
670     },
671
672     /**
673      * on remove handler
674      *
675      * @param {} button
676      * @param {} event
677      */
678     onRemove: function (button, event) {
679         var selectedRows = this.selectionModel.getSelections();
680         for (var i = 0; i < selectedRows.length; i += 1) {
681             this.store.remove(selectedRows[i]);
682             var upload = Tine.Tinebase.uploadManager.getUpload(selectedRows[i].get('uploadKey'));
683             upload.setPaused(true);
684         }
685     },
686
687     /**
688      * populate grid store
689      *
690      * @param {} record
691      */
692     loadRecord: function (record) {
693         if (record && record.get(this.filesProperty)) {
694             var files = record.get(this.filesProperty);
695             for (var i = 0; i < files.length; i += 1) {
696                 var file = new Ext.ux.file.Upload.file(files[i]);
697                 file.set('status', 'complete');
698                 file.set('nodeRecord', new Tine.Expressodriver.Model.Node(file.data));
699                 this.store.add(file);
700             }
701         }
702     },
703
704     /**
705      * copies uploaded temporary file to target location
706      *
707      * @param upload  {Ext.ux.file.Upload}
708      * @param file  {Ext.ux.file.Upload.file}
709      */
710     onUploadComplete: function(upload, file) {
711         var app = Tine.Tinebase.appMgr.get('Expressodriver'),
712             grid = app.getMainScreen().getCenterPanel();
713
714         // check if we are responsible for the upload
715         if (upload.fmDirector != grid) return;
716
717         // $filename, $type, $tempFileId, $forceOverwrite
718         Ext.Ajax.request({
719             timeout: 10*60*1000, // Overriding Ajax timeout - important!
720             params: {
721                 method: 'Expressodriver.createNode',
722                 filename: upload.id,
723                 type: 'file',
724                 tempFileId: file.get('id'),
725                 forceOverwrite: true
726             },
727             success: grid.onNodeCreated.createDelegate(this, [upload], true),
728             failure: grid.onNodeCreated.createDelegate(this, [upload], true)
729         });
730
731     },
732
733     /**
734      * TODO: move to Upload class or elsewhere??
735      * updating fileRecord after creating node
736      *
737      * @param response
738      * @param request
739      * @param upload
740      */
741     onNodeCreated: function(response, request, upload) {
742         var record = Ext.util.JSON.decode(response.responseText);
743
744         var fileRecord = upload.fileRecord;
745         fileRecord.beginEdit();
746         fileRecord.set('contenttype', record.contenttype);
747         fileRecord.set('created_by', Tine.Tinebase.registry.get('currentAccount'));
748         fileRecord.set('creation_time', record.creation_time);
749         fileRecord.set('revision', record.revision);
750         fileRecord.set('last_modified_by', record.last_modified_by);
751         fileRecord.set('last_modified_time', record.last_modified_time);
752         fileRecord.set('name', record.name);
753         fileRecord.set('path', record.path);
754         fileRecord.set('status', 'complete');
755         fileRecord.set('progress', 100);
756         fileRecord.commit(false);
757
758         upload.fireEvent('update', 'uploadfinished', upload, fileRecord);
759
760         var app = Tine.Tinebase.appMgr.get('Expressodriver'),
761             grid = app.getMainScreen().getCenterPanel();
762
763         var allRecordsComplete = true;
764         var storeItems = grid.getStore().getRange();
765         for(var i=0; i<storeItems.length; i++) {
766             if(storeItems[i].get('status') && storeItems[i].get('status') !== 'complete') {
767                 allRecordsComplete = false;
768                 break;
769             }
770         }
771
772         if(allRecordsComplete) {
773             grid.pagingToolbar.refresh.enable();
774         }
775     },
776
777     /**
778      * upload new file and add to store
779      *
780      * @param {ux.BrowsePlugin} fileSelector
781      * @param {} e
782      */
783     onFilesSelect: function (fileSelector, event) {
784         var app = Tine.Tinebase.appMgr.get('Expressodriver'),
785             grid = app.getMainScreen().getCenterPanel(),
786             targetNode = grid.currentFolderNode,
787             gridStore = grid.store,
788             rowIndex = false,
789             targetFolderPath = grid.currentFolderNode.attributes.path,
790             addToGrid = true,
791             dropAllowed = false,
792             nodeRecord = null;
793
794         if(event && event.getTarget()) {
795             rowIndex = grid.getView().findRowIndex(event.getTarget());
796         }
797
798
799         if(targetNode.attributes) {
800             nodeRecord = targetNode.attributes.nodeRecord;
801         }
802
803         if(rowIndex !== false && rowIndex > -1) {
804             var newTargetNode = gridStore.getAt(rowIndex);
805             if(newTargetNode && newTargetNode.data.type == 'folder') {
806                 targetFolderPath = newTargetNode.data.path;
807                 addToGrid = false;
808                 nodeRecord = new Tine.Expressodriver.Model.Node(newTargetNode.data);
809             }
810         }
811
812         if(!nodeRecord.isDropFilesAllowed()) {
813             Ext.MessageBox.alert(
814                     _('Upload Failed'),
815                     app.i18n._('Putting files in this folder is not allowed!')
816             ).setIcon(Ext.MessageBox.ERROR);
817
818             return;
819         }
820
821         var files = fileSelector.getFileList();
822
823         if(files.length > 0) {
824             grid.pagingToolbar.refresh.disable();
825         }
826
827         var filePathsArray = [], uploadKeyArray = [];
828
829         Ext.each(files, function (file) {
830             var fileRecord = Tine.Expressodriver.Model.Node.createFromFile(file),
831                 filePath = targetFolderPath + '/' + fileRecord.get('name');
832
833             fileRecord.set('path', filePath);
834             var existingRecordIdx = gridStore.find('name', fileRecord.get('name'));
835             if(existingRecordIdx < 0) {
836                 gridStore.add(fileRecord);
837             }
838
839             var upload = new Ext.ux.file.Upload({
840                 fmDirector: grid,
841                 file: file,
842                 fileSelector: fileSelector,
843                 id: filePath
844             });
845
846             var uploadKey = Tine.Tinebase.uploadManager.queueUpload(upload);
847
848             filePathsArray.push(filePath);
849             uploadKeyArray.push(uploadKey);
850
851         }, this);
852
853         var params = {
854                 filenames: filePathsArray,
855                 type: "file",
856                 tempFileIds: [],
857                 forceOverwrite: false
858         };
859         Tine.Expressodriver.fileRecordBackend.createNodes(params, uploadKeyArray, true);
860     },
861
862     /**
863      * download file
864      *
865      * @param {} button
866      * @param {} event
867      */
868     onDownload: function(button, event) {
869
870         var app = Tine.Tinebase.appMgr.get('Expressodriver'),
871             grid = app.getMainScreen().getCenterPanel(),
872             selectedRows = grid.selectionModel.getSelections();
873
874         var fileRow = selectedRows[0];
875
876         var downloadPath = fileRow.data.path;
877         var downloader = new Ext.ux.file.Download({
878             params: {
879                 method: 'Expressodriver.downloadFile',
880                 requestType: 'HTTP',
881                 id: '',
882                 path: downloadPath
883             }
884         }).start();
885     },
886
887     /**
888      * grid on load handler
889      *
890      * @param grid
891      * @param records
892      * @param options
893      */
894     onLoad: function(store, records, options){
895         var app = Tine.Tinebase.appMgr.get('Expressodriver'),
896             grid = app.getMainScreen().getCenterPanel();
897
898         for(var i=records.length-1; i>=0; i--) {
899             var record = records[i];
900             if(record.get('type') == 'file' && (!record.get('size') || record.get('size') == 0)) {
901                 var upload = Tine.Tinebase.uploadManager.getUpload(record.get('path'));
902
903                 if(upload) {
904                       if(upload.fileRecord && record.get('name') == upload.fileRecord.get('name')) {
905                           grid.updateNodeRecord(record, upload.fileRecord);
906                           record.afterEdit();
907                     }
908                 }
909             }
910         }
911     },
912
913     /**
914      * update grid nodeRecord with fileRecord data
915      *
916      * @param nodeRecord
917      * @param fileRecord
918      */
919     updateNodeRecord: function(nodeRecord, fileRecord) {
920         for(var field in fileRecord.fields) {
921             nodeRecord.set(field, fileRecord.get(field));
922         }
923         nodeRecord.fileRecord = fileRecord;
924     },
925
926     /**
927      * upload update handler
928      *
929      * @param change {String} kind of change
930      * @param upload {Ext.ux.file.Upload} upload
931      * @param fileRecord {file} fileRecord
932      *
933      */
934     onUpdate: function(change, upload, fileRecord) {
935         var app = Tine.Tinebase.appMgr.get('Expressodriver'),
936             grid = app.getMainScreen().getCenterPanel(),
937             rowsToUpdate = grid.getStore().query('name', fileRecord.get('name'));
938
939         if(change == 'uploadstart') {
940             Tine.Tinebase.uploadManager.onUploadStart();
941         }
942         else if(change == 'uploadfailure') {
943             grid.onUploadFail();
944         }
945
946         if(rowsToUpdate.get(0)) {
947             if(change == 'uploadcomplete') {
948                 grid.onUploadComplete(upload, fileRecord);
949             }
950             else if(change == 'uploadfinished') {
951                 rowsToUpdate.get(0).set('size', fileRecord.get('size'));
952                 rowsToUpdate.get(0).set('contenttype', fileRecord.get('contenttype'));
953             }
954             rowsToUpdate.get(0).afterEdit();
955             rowsToUpdate.get(0).commit(false);
956         }
957     },
958
959     /**
960      * init grid drop target
961      *
962      * @TODO DRY cleanup
963      */
964     initDropTarget: function(){
965         var ddrow = new Ext.dd.DropTarget(this.getEl(), {
966             ddGroup : 'fileDDGroup',
967
968             notifyDrop : function(dragSource, e, data){
969
970                 if(data.node && data.node.attributes && !data.node.attributes.nodeRecord.isDragable()) {
971                     return false;
972                 }
973
974                 var app = Tine.Tinebase.appMgr.get(Tine.Expressodriver.fileRecordBackend.appName),
975                     grid = app.getMainScreen().getCenterPanel(),
976                     treePanel = app.getMainScreen().getWestPanel().getContainerTreePanel(),
977                     dropIndex = grid.getView().findRowIndex(e.target),
978                     target = grid.getStore().getAt(dropIndex),
979                     nodes = data.selections ? data.selections : [data.node];
980
981                 if(!target || (target.data && target.data.type === 'file')) {
982                     return false;
983                 }
984
985                 for(var i=0; i<nodes.length; i++) {
986                     if(nodes[i].id == target.id) {
987                         return false;
988                     }
989                 }
990
991                 var targetNode = treePanel.getNodeById(target.id);
992                 if(targetNode && targetNode.isAncestor(nodes[0])) {
993                     return false;
994                 }
995
996                 Tine.Expressodriver.fileRecordBackend.copyNodes(nodes, target, !e.ctrlKey);
997                 return true;
998             },
999
1000             notifyOver : function( dragSource, e, data ) {
1001                 if(data.node && data.node.attributes && !data.node.attributes.nodeRecord.isDragable()) {
1002                     return false;
1003                 }
1004
1005                 var app = Tine.Tinebase.appMgr.get(Tine.Expressodriver.fileRecordBackend.appName),
1006                     grid = app.getMainScreen().getCenterPanel(),
1007                     dropIndex = grid.getView().findRowIndex(e.target),
1008                     treePanel = app.getMainScreen().getWestPanel().getContainerTreePanel(),
1009                     target= grid.getStore().getAt(dropIndex),
1010                     nodes = data.selections ? data.selections : [data.node];
1011
1012                 if(!target || (target.data && target.data.type === 'file')) {
1013                     return false;
1014                 }
1015
1016                 for(var i=0; i<nodes.length; i++) {
1017                     if(nodes[i].id == target.id) {
1018                         return false;
1019                     }
1020                 }
1021
1022                 var targetNode = treePanel.getNodeById(target.id);
1023                 if(targetNode && targetNode.isAncestor(nodes[0])) {
1024                     return false;
1025                 }
1026
1027                 return this.dropAllowed;
1028             }
1029         });
1030     },
1031
1032     /**
1033      * returns view row class
1034      */
1035     getViewRowClass: function(record, index, rowParams, store) {
1036         return '';
1037     },
1038 });