0012932: file picker dialog in fileuploadgrid
authorMichael Spahn <m.spahn@metaways.de>
Wed, 22 Mar 2017 18:34:57 +0000 (19:34 +0100)
committerPhilipp Schüle <p.schuele@metaways.de>
Wed, 5 Apr 2017 12:19:43 +0000 (14:19 +0200)
https://forge.tine20.org/view.php?id=12932

Change-Id: I7c59a135e75b3e14b68f3bf1f6651eac40b69fd7
Reviewed-on: http://gerrit.tine20.com/customers/4407
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
tine20/Filemanager/js/FilePicker.js
tine20/Filemanager/js/FilePickerDialog.js
tine20/Filemanager/js/NodeGridPanel.js
tine20/Filemanager/js/NodeTreePanel.js
tine20/Tinebase/FileSystem.php
tine20/Tinebase/FileSystem/RecordAttachments.php
tine20/Tinebase/js/widgets/container/TreePanel.js
tine20/Tinebase/js/widgets/grid/FileUploadGrid.js
tine20/Tinebase/js/widgets/grid/GridPanel.js

index 71a0dbf..b3c2563 100644 (file)
@@ -16,25 +16,50 @@ Ext.ns('Tine.Filemanager');
  * The filepicker offers two events:
  *  - nodeSelected
  *  - invalidNodeSelected
- *
- *  @todo: remove border
  */
 Tine.Filemanager.FilePicker = Ext.extend(Ext.Container, {
+    /**
+     * Filemanager app
+     * @private
+     */
     app: null,
 
+    /**
+     * Layout.
+     * @private
+     */
     layout: 'fit',
 
+    /**
+     * NodeTreePanel instance
+     * @private
+     */
     treePanel: null,
+
+    /**
+     * NodeGridPanel instance
+     * @private
+     */
     gridPanel: null,
 
     /**
-     * Selected node
+     * Selected nodes
+     * @private
      */
-    selection: null,
+    selection: [],
 
+    /**
+     * Last clicked node
+     * @private
+     */
     lastClickedNode: null,
 
     /**
+     * Allow to select one or more node
+     */
+    singleSelect: true,
+
+    /**
      * A constraint allows to alter the selection behaviour of the picker, for example only allow to select files.
      *
      * By default, file and folder are allowed to be selected, the concrete implementation needs to define it's purpose
@@ -103,16 +128,22 @@ Tine.Filemanager.FilePicker = Ext.extend(Ext.Container, {
     /**
      * Updates selected element and triggers an event
      */
-    updateSelection: function (node) {
+    updateSelection: function (nodes) {
         // If selection doesn't fullfil constraint, we don't throw a selectionChange event
-        if (!this.checkConstraint(node)) {
+        if (!this.checkConstraint(nodes)) {
             this.fireEvent('invalidNodeSelected');
             return false;
         }
 
-        this.selection = node.data || node;
+        //  Clear previous selection
+        this.selection = [];
 
-        this.fireEvent('nodeSelected', this, this.selection);
+        var me = this;
+        Ext.each(nodes, function (node) {
+            me.selection.push(node.data || node);
+        });
+
+        this.fireEvent('nodeSelected', this.selection);
     },
 
     /**
@@ -131,9 +162,11 @@ Tine.Filemanager.FilePicker = Ext.extend(Ext.Container, {
             filterMode: 'filterToolbar'
         });
 
-        treePanel.getSelectionModel().on('selectionchange', function (selectionModel, treeNode) {
+        treePanel.getSelectionModel().on('selectionchange', function (selectionModel) {
             var treeNode = selectionModel.getSelectedNode();
-            me.updateSelection(treeNode.attributes);
+            me.updateSelection([
+                treeNode.attributes
+            ]);
         });
 
         return treePanel;
@@ -154,14 +187,19 @@ Tine.Filemanager.FilePicker = Ext.extend(Ext.Container, {
             height: 200,
             width: 200,
             readOnly: true,
+            enableDD: false,
+            enableDrag: false,
+            treePanel: this.getTreePanel(),
             hasQuickSearchFilterToolbarPlugin: false,
             stateIdPrefix: '-FilePicker',
             plugins: [this.getTreePanel().getFilterPlugin()]
         });
+
         gridPanel.getGrid().reconfigure(gridPanel.getStore(), this.getColumnModel());
+        gridPanel.getGrid().getSelectionModel().singleSelect = this.singleSelect;
         gridPanel.getGrid().getSelectionModel().on('rowselect', function (selModel) {
-            var record = selModel.getSelected();
-            me.updateSelection(record.data);
+            var record = selModel.getSelections();
+            me.updateSelection(record);
         });
 
         return gridPanel;
@@ -169,8 +207,29 @@ Tine.Filemanager.FilePicker = Ext.extend(Ext.Container, {
 
     /**
      * Check if selection fits current constraint
+     * @returns {boolean}
+     */
+    checkConstraint: function (nodes) {
+        var me = this;
+        var valid = true;
+
+        Ext.each(nodes, function (node) {
+            if (!me.checkNodeConstraint(node.data || node)) {
+                valid = false;
+                return false;
+            }
+        });
+
+        return valid;
+    },
+
+    /**
+     * Checks if a single node matches the constraints
+     *
+     * @param node
+     * @returns {boolean}
      */
-    checkConstraint: function (node) {
+    checkNodeConstraint: function (node) {
         // Minimum information to proceed here
         if (!node.path || !node.id) {
             return false;
index f38a63d..9907686 100644 (file)
@@ -19,12 +19,58 @@ Ext.ns('Tine.Filemanager');
 Tine.Filemanager.FilePickerDialog = Ext.extend(Ext.Panel, {
     layout: 'fit',
     border: false,
+    frame: false,
+
+    /**
+     * ok button action held here
+     */
     okAction: null,
 
-    node: null,
+    /**
+     * Dialog window
+     */
+    window: null,
+
+    /**
+     * Window title
+     */
+    title: null,
+
+    /**
+     * Hide panel header by default
+     */
+    header: false,
+
+    /**
+     * The validated and choosen node
+     */
+    nodes: null,
 
+    /**
+     * @todo: maybe remove
+     */
     windowNamePrefix: 'test',
 
+    /**
+     * Allow to select one or more node
+     */
+    singleSelect: true,
+
+    /**
+     * A constraint allows to alter the selection behaviour of the picker, for example only allow to select files.
+     *
+     * By default, file and folder are allowed to be selected, the concrete implementation needs to define it's purpose
+     *
+     * Valids constraints:
+     *  - file
+     *  - folder
+     *  - null (take all)
+     */
+    constraint: null,
+
+    /**
+     * Constructor.
+     */
     initComponent: function () {
         this.addEvents(
             /**
@@ -52,6 +98,7 @@ Tine.Filemanager.FilePickerDialog = Ext.extend(Ext.Panel, {
         });
 
         this.bbar = [
+            '->',
             this.okAction
         ];
 
@@ -62,7 +109,7 @@ Tine.Filemanager.FilePickerDialog = Ext.extend(Ext.Panel, {
      * button handler
      */
     onOk: function () {
-        this.fireEvent('selected', this.node);
+        this.fireEvent('selected', this.nodes);
         this.window.close();
     },
 
@@ -72,11 +119,12 @@ Tine.Filemanager.FilePickerDialog = Ext.extend(Ext.Panel, {
      */
     getFilePicker: function () {
         var picker = new Tine.Filemanager.FilePicker({
-            constraint: 'file'
+            constraint: this.constraint,
+            singleSelect: this.singleSelect
         });
 
-        picker.on('nodeSelected', this.onNodeSelected.createDelegate(this));
-        picker.on('invalidNodeSelected', this.onInvalidNodeSelected.createDelegate(this));
+        picker.on('nodeSelected', this.onNodesSelected.createDelegate(this));
+        picker.on('invalidNodeSelected', this.onInvalidNodesSelected.createDelegate(this));
 
         return picker;
     },
@@ -85,26 +133,39 @@ Tine.Filemanager.FilePickerDialog = Ext.extend(Ext.Panel, {
      * If a node was selected
      * @param node
      */
-    onNodeSelected: function (node) {
-        this.node = node;
+    onNodesSelected: function (nodes) {
+        this.nodes = nodes;
         this.okAction.setDisabled(false);
     },
 
     /**
      * If an invalid node was selected
      */
-    onInvalidNodeSelected: function () {
+    onInvalidNodesSelected: function () {
         this.okAction.setDisabled(true);
+    },
+
+    /**
+     * Creates a new pop up dialog/window (acc. configuration)
+     *
+     * @returns {null}
+     */
+    openWindow: function () {
+        this.window = Tine.WindowFactory.getWindow({
+            title: this.title,
+            closeAction: 'close',
+            modal: true,
+            width: 550,
+            height: 500,
+            layout: 'fit',
+            plain: true,
+            bodyStyle: 'padding:5px;',
+
+            items: [
+                this
+            ]
+        });
+
+        return this.window;
     }
 });
-
-Tine.Filemanager.FilePickerDialog.openWindow = function (config) {
-    return Tine.WindowFactory.getWindow({
-        width: 480,
-        height: 400,
-        modal: true,
-        name: Tine.Filemanager.FilePickerDialog.windowNamePrefix + Ext.id(),
-        contentPanelConstructor: 'Tine.Filemanager.FilePickerDialog',
-        contentPanelConstructorConfig: config
-    });
-};
index ea2676e..3e83061 100644 (file)
@@ -9,30 +9,30 @@ Ext.ns('Tine.Filemanager');
 
 /**
  * File grid panel
- * 
+ *
  * @namespace   Tine.Filemanager
  * @class       Tine.Filemanager.NodeGridPanel
  * @extends     Tine.widgets.grid.GridPanel
- * 
+ *
  * <p>Node Grid Panel</p>
  * <p><pre>
  * </pre></p>
- * 
+ *
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Martin Jatho <m.jatho@metaways.de>
  * @copyright   Copyright (c) 2007-2011 Metaways Infosystems GmbH (http://www.metaways.de)
- * 
+ *
  * @param       {Object} config
  * @constructor
  * Create a new Tine.Filemanager.FileGridPanel
  */
-Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {   
+Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
     /**
      * @cfg filesProperty
      * @type String
      */
     filesProperty: 'files',
-    
+
     /**
      * config values
      * @private
@@ -42,26 +42,18 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
     deferredRender: false,
     autoExpandColumn: 'name',
     showProgress: true,
-    
+
     recordClass: Tine.Filemanager.Model.Node,
     hasDetailsPanel: false,
     evalGrants: true,
-    
+
     /**
      * grid specific
      * @private
      */
-    defaultSortInfo: {field: 'name', direction: 'DESC'},
-    gridConfig: {
-        autoExpandColumn: 'name',
-        enableFileDialog: false,
-        enableDragDrop: true,
-        ddGroup: 'fileDDGroup'
-    },
-     
-    ddGroup : 'fileDDGroup',  
-    currentFolderNode : '/',
-    
+    ddGroup: 'fileDDGroup',
+    currentFolderNode: '/',
+
     /**
      * Prevent download and edit of nodes/files and so on. Only allow the selection of items
      */
@@ -72,10 +64,22 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
      * @private
      */
     initComponent: function() {
+        Ext.applyIf(this.defaultSortInfo, {field: 'name', direction: 'DESC'});
+        Ext.applyIf(this.gridConfig, {
+            autoExpandColumn: 'name',
+            enableFileDialog: false,
+            enableDragDrop: true,
+            ddGroup: 'fileDDGroup'
+        });
+
+        if (this.readOnly) {
+            this.gridConfig.enableDragDrop = false;
+        }
+
         this.recordProxy = Tine.Filemanager.fileRecordBackend;
-        
+
         this.gridConfig.cm = this.getColumnModel();
-        
+
         this.defaultFilters = [
             {field: 'query', operator: 'contains', value: ''},
             {field: 'path', operator: 'equals', value: '/'}
@@ -102,7 +106,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
         }
 
         Tine.Filemanager.NodeGridPanel.superclass.initComponent.call(this);
-        this.getStore().on('load', this.onLoad);
+        this.getStore().on('load', this.onLoad.createDelegate(this));
         Tine.Tinebase.uploadManager.on('update', this.onUpdate);
     },
 
@@ -119,19 +123,22 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
         if (!this.readOnly) {
             this.initDropTarget();
         }
-        this.currentFolderNode = this.app.getMainScreen().getWestPanel().getContainerTreePanel().getRootNode();
+
+        var treePanel = this.treePanel || this.app.getMainScreen().getWestPanel().getContainerTreePanel();
+
+        this.currentFolderNode = treePanel.getRootNode();
     },
-    
+
     /**
      * returns cm
-     * 
+     *
      * @return Ext.grid.ColumnModel
      * @private
-     * 
+     *
      * TODO    add more columns
      */
     getColumnModel: function(){
-        var columns = [{ 
+        var columns = [{
                 id: 'tags',
                 header: this.app.i18n._('Tags'),
                 dataIndex: 'tags',
@@ -160,7 +167,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
                 sortable: true,
                 dataIndex: 'contenttype',
                 renderer: function(value, metadata, record) {
-                    
+
                     var app = Tine.Tinebase.appMgr.get('Filemanager');
                     if(record.data.type == 'folder') {
                         return app.i18n._("Folder");
@@ -196,7 +203,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
                 width: 50,
                 sortable: true,
                 dataIndex: 'last_modified_by',
-                renderer: Tine.Tinebase.common.usernameRenderer 
+                renderer: Tine.Tinebase.common.usernameRenderer
             }
         ];
 
@@ -212,7 +219,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
                 renderer: Tine.Tinebase.common.byteRenderer.createDelegate(this, [2, true], 3)
             });
         }
-        
+
         return new Ext.grid.ColumnModel({
             defaults: {
                 sortable: true,
@@ -230,19 +237,19 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
     statusRenderer: function(value) {
         return this.app.i18n._hidden(value);
     },
-    
+
     /**
      * init ext grid panel
      * @private
      */
     initGrid: function() {
         Tine.Filemanager.NodeGridPanel.superclass.initGrid.call(this);
-        
+
         if (this.usePagingToolbar) {
            this.initPagingToolbar();
         }
     },
-    
+
     /**
      * inserts a quota Message when using old Browsers with html4upload
      */
@@ -259,7 +266,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             this.pagingToolbar.doLayout();
         }
     },
-    
+
     /**
      * returns filter toolbar -> supress OR filters
      * @private
@@ -272,7 +279,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             this.quickSearchFilterToolbarPlugin = new Tine.widgets.grid.FilterToolbarQuickFilterPlugin();
             plugins.push(this.quickSearchFilterToolbarPlugin);
         }
-        
+
         return new Tine.widgets.grid.FilterToolbar(Ext.apply(config, {
             app: this.app,
             recordClass: this.recordClass,
@@ -282,10 +289,10 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             plugins: plugins
         }));
     },
-    
+
     /**
      * returns add action / test
-     * 
+     *
      * @return {Object} add action config
      */
     getAddAction: function () {
@@ -305,7 +312,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             iconCls: this.app.appName + 'IconCls'
         };
     },
-    
+
     /**
      * init actions with actionToolbar, contextMenu and actionUpdater
      * @private
@@ -313,7 +320,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
     initActions: function() {
 
         this.action_upload = new Ext.Action(this.getAddAction());
-        
+
         this.action_editFile = new Ext.Action({
             requiredGrant: 'editGrant',
             allowMultiple: false,
@@ -335,7 +342,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             disabled: true,
             scope: this
         });
-        
+
         this.action_goUpFolder = new Ext.Action({
 //            requiredGrant: 'readGrant',
             allowMultiple: true,
@@ -346,7 +353,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             disabled: true,
             scope: this
         });
-        
+
         this.action_download = new Ext.Action({
             requiredGrant: 'readGrant',
             allowMultiple: false,
@@ -357,7 +364,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             disabled: true,
             scope: this
         });
-        
+
         this.action_deleteRecord = new Ext.Action({
             requiredGrant: 'deleteGrant',
             allowMultiple: true,
@@ -397,7 +404,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
                 }
             }
         });
-        
+
         this.contextMenu = Tine.Filemanager.GridContextMenu.getMenu({
             nodeName: Tine.Filemanager.Model.Node.getRecordName(),
             actions: ['delete', 'rename', this.action_moveRecord, 'download', 'resume', 'pause', this.action_editFile, 'publish'],
@@ -405,18 +412,20 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             backend: 'Filemanager',
             backendModel: 'Node'
         });
-        
+
+        var treePanel = this.treepPanel || this.app.getMainScreen().getWestPanel().getContainerTreePanel();
+
         this.folderContextMenu = Tine.Filemanager.GridContextMenu.getMenu({
-            nodeName: this.app.i18n._(this.app.getMainScreen().getWestPanel().getContainerTreePanel().containerName),
+            nodeName: this.app.i18n._(treePanel.containerName),
             actions: ['delete', 'rename', this.action_moveRecord, this.action_editFile, 'publish'],
             scope: this,
             backend: 'Filemanager',
             backendModel: 'Node'
         });
-        
+
         this.actionUpdater.addActions(this.contextMenu.items);
         this.actionUpdater.addActions(this.folderContextMenu.items);
-        
+
         this.actionUpdater.addActions([
             this.action_createFolder,
             this.action_goUpFolder,
@@ -426,17 +435,17 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             this.action_publish
         ]);
     },
-    
+
     onPublishFile: function() {
         var selections = this.selectionModel.getSelections();
-        
+
         if (selections.length != 1) {
             return;
         }
-        
+
         var date = new Date();
         date.setDate(date.getDate() + 30);
-        
+
         var record = new Tine.Filemanager.Model.DownloadLink({node_id: selections[0].id, expiry_time: date});
         Tine.Filemanager.downloadLinkRecordBackend.saveRecord(record, {
             success: function (record) {
@@ -453,20 +462,20 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             }, failure: Tine.Tinebase.ExceptionHandler.handleRequestException, scope: this
         });
     },
-    
+
     /**
      * get the right contextMenu
      */
     getContextMenu: function(grid, row, e) {
         var r = this.store.getAt(row),
             type = r ? r.get('type') : null;
-            
+
         return type === 'folder' ? this.folderContextMenu : this.contextMenu;
     },
-    
+
     /**
      * get action toolbar
-     * 
+     *
      * @return {Ext.Toolbar}
      */
     getActionToolbar: function() {
@@ -480,7 +489,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
                     columns: 8,
                     defaults: {minWidth: 60},
                     items: [
-                        this.splitAddButton ? 
+                        this.splitAddButton ?
                         Ext.apply(new Ext.SplitButton(this.action_upload), {
                             scale: 'medium',
                             rowspan: 2,
@@ -496,13 +505,13 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
                                     key:   'Tinebase-MainContextMenu'
                                 }]
                             })
-                        }) : 
+                        }) :
                         Ext.apply(new Ext.Button(this.action_upload), {
                             scale: 'medium',
                             rowspan: 2,
                             iconAlign: 'top'
                         }),
-                        
+
                         Ext.apply(new Ext.Button(this.action_editFile), {
                             scale: 'medium',
                             rowspan: 2,
@@ -543,10 +552,10 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
                 this.actionToolbar.add('->', this.filterToolbar.getQuickFilterField());
             }
         }
-        
+
         return this.actionToolbar;
     },
-    
+
     /**
      * opens the edit dialog
      */
@@ -557,53 +566,54 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             var record = new Tine.Filemanager.Model.Node(sel[0].data);
             var window = Tine.Filemanager.NodeEditDialog.openWindow({record: record});
         }
-        
+
         window.on('saveAndClose', function() {
             this.getGrid().store.reload();
         }, this);
     },
-    
+
     /**
      * create folder in current position
-     * 
+     *
      * @param {Ext.Component} button
      * @param {Ext.EventObject} event
      */
     onCreateFolder: function(button, event) {
         var app = this.app,
+            me = this,
             nodeName = Tine.Filemanager.Model.Node.getContainerName();
-        
+
         Ext.MessageBox.prompt(this.app.i18n._('New Folder'), this.app.i18n._('Please enter the name of the new folder:'), function(_btn, _text) {
-            var currentFolderNode = app.getMainScreen().getCenterPanel().currentFolderNode;
+            var currentFolderNode = me.currentFolderNode;
             if(currentFolderNode && _btn == 'ok') {
                 if (! _text) {
-                    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));
+                    Ext.Msg.alert(String.format(app.i18n._('No {0} added'), nodeName), String.format(app.i18n._('You have to supply a {0} name!'), nodeName));
                     return;
                 }
-                
+
                 var filename = currentFolderNode.attributes.path + '/' + _text;
                 Tine.Filemanager.fileRecordBackend.createFolder(filename);
-                
+
             }
-        }, this);  
+        }, this);
     },
-    
+
     /**
      * delete selected files / folders
-     * 
+     *
      * @param {Ext.Component} button
      * @param {Ext.EventObject} event
      */
     onDeleteRecords: function(button, event) {
         var app = this.app,
             nodeName = '',
-            sm = app.getMainScreen().getCenterPanel().selectionModel,
+            sm = this.selectionModel,
             nodes = sm.getSelections();
-        
+
         if(nodes && nodes.length) {
             for(var i=0; i<nodes.length; i++) {
                 var currNodeData = nodes[i].data;
-                
+
                 if(typeof currNodeData.name == 'object') {
                     nodeName += currNodeData.name.name + '<br />';
                 }
@@ -612,7 +622,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
                 }
             }
         }
-        
+
         this.conflictConfirmWin = Tine.widgets.dialog.FileListDialog.openWindow({
             modal: true,
             allowCancel: false,
@@ -627,40 +637,39 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
                     this.pagingToolbar.refresh.disable();
                     Tine.Filemanager.fileRecordBackend.deleteItems(nodes);
                 }
-                
+
                 for(var i=0; i<nodes.length; i++) {
                     var node = nodes[i];
-                    
+
                     if(node.fileRecord) {
                         var upload = Tine.Tinebase.uploadManager.getUpload(node.fileRecord.get('uploadKey'));
                         upload.setPaused(true);
                         Tine.Tinebase.uploadManager.unregisterUpload(upload.id);
                     }
-                    
+
                 }
             }
         }, this);
     },
-    
+
     /**
      * go up one folder
-     * 
+     *
      * @param {Ext.Component} button
      * @param {Ext.EventObject} event
      */
     onLoadParentFolder: function(button, event) {
-        var app = this.app,
-            currentFolderNode = app.getMainScreen().getCenterPanel().currentFolderNode;
-        
+        var currentFolderNode = this.currentFolderNode;
+
         if(currentFolderNode && currentFolderNode.parentNode) {
-            app.getMainScreen().getCenterPanel().currentFolderNode = currentFolderNode.parentNode;
+            this.currentFolderNode = currentFolderNode.parentNode;
             currentFolderNode.parentNode.select();
         }
     },
-    
+
     /**
      * grid row doubleclick handler
-     * 
+     *
      * @param {Tine.Filemanager.NodeGridPanel} grid
      * @param {} row record
      * @param {Ext.EventObjet} e
@@ -668,15 +677,16 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
     onRowDblClick: function(grid, row, e) {
         var app = this.app;
         var rowRecord = grid.getStore().getAt(row);
-        
+
         if(rowRecord.data.type == 'file' && !this.readOnly) {
             Tine.Filemanager.downloadFile(rowRecord);
         }
-        
+
         else if (rowRecord.data.type == 'folder'){
-            var treePanel = app.getMainScreen().getWestPanel().getContainerTreePanel();
-            
+            var treePanel = this.treePanel || app.getMainScreen().getWestPanel().getContainerTreePanel();
+
             var currentFolderNode;
+
             if(rowRecord.data.path == '/personal/system') {
                 currentFolderNode = treePanel.getNodeById('personal');
             }
@@ -692,7 +702,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             if(currentFolderNode) {
                 currentFolderNode.select();
                 currentFolderNode.expand();
-                app.getMainScreen().getCenterPanel().currentFolderNode = currentFolderNode;
+                this.currentFolderNode = currentFolderNode;
             } else {
                 // get ftb path filter
                 this.filterToolbar.filterStore.each(function(filter) {
@@ -701,18 +711,18 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
                         filter.set('value', '');
                         filter.set('value', rowRecord.data);
                         filter.formFields.value.setValue(rowRecord.get('path'));
-                        
+
                         this.filterToolbar.onFiltertrigger();
                         return false;
                     }
                 }, this);
             }
         }
-    }, 
-        
+    },
+
     /**
      * on upload failure
-     * 
+     *
      * @private
      */
     onUploadFail: function () {
@@ -720,15 +730,14 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             i18n._('Upload Failed'),
             i18n._('Could not upload file. Filesize could be too big. Please notify your Administrator.')
         ).setIcon(Ext.MessageBox.ERROR);
-        
-        var app = Tine.Tinebase.appMgr.get('Filemanager'),
-            grid = app.getMainScreen().getCenterPanel();
+
+        var grid = this;
         grid.pagingToolbar.refresh.enable();
     },
-    
+
     /**
      * on remove handler
-     * 
+     *
      * @param {} button
      * @param {} event
      */
@@ -737,13 +746,16 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
         for (var i = 0; i < selectedRows.length; i += 1) {
             this.store.remove(selectedRows[i]);
             var upload = Tine.Tinebase.uploadManager.getUpload(selectedRows[i].get('uploadKey'));
-            upload.setPaused(true);
+
+            if (upload) {
+                upload.setPaused(true);
+            }
         }
     },
-    
+
     /**
      * populate grid store
-     * 
+     *
      * @param {} record
      */
     loadRecord: function (record) {
@@ -757,20 +769,19 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             }
         }
     },
-    
+
     /**
      * copies uploaded temporary file to target location
-     * 
+     *
      * @param upload  {Ext.ux.file.Upload}
-     * @param file  {Ext.ux.file.Upload.file} 
+     * @param file  {Ext.ux.file.Upload.file}
      */
     onUploadComplete: function(upload, file) {
-        var app = Tine.Tinebase.appMgr.get('Filemanager'),
-            grid = app.getMainScreen().getCenterPanel();
-        
+        var grid = this;
+
         // check if we are responsible for the upload
         if (upload.fmDirector != grid) return;
-        
+
         // $filename, $type, $tempFileId, $forceOverwrite
         Ext.Ajax.request({
             timeout: 10*60*1000, // Overriding Ajax timeout - important!
@@ -781,23 +792,23 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
                 tempFileId: file.get('id'),
                 forceOverwrite: true
             },
-            success: grid.onNodeCreated.createDelegate(this, [upload], true), 
+            success: grid.onNodeCreated.createDelegate(this, [upload], true),
             failure: grid.onNodeCreated.createDelegate(this, [upload], true)
         });
-        
+
     },
-    
+
     /**
      * TODO: move to Upload class or elsewhere??
      * updating fileRecord after creating node
-     * 
+     *
      * @param response
      * @param request
      * @param upload
      */
     onNodeCreated: function(response, request, upload) {
         var record = Ext.util.JSON.decode(response.responseText);
-                
+
         var fileRecord = upload.fileRecord;
         fileRecord.beginEdit();
         fileRecord.set('contenttype', record.contenttype);
@@ -811,12 +822,11 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
         fileRecord.set('status', 'complete');
         fileRecord.set('progress', 100);
         fileRecord.commit(false);
-       
+
         upload.fireEvent('update', 'uploadfinished', upload, fileRecord);
-        
-        var app = Tine.Tinebase.appMgr.get('Filemanager'),
-            grid = app.getMainScreen().getCenterPanel();
-        
+
+        var grid = this;
+
         var allRecordsComplete = true;
         var storeItems = grid.getStore().getRange();
         for(var i=0; i<storeItems.length; i++) {
@@ -825,21 +835,21 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
                 break;
             }
         }
-        
+
         if(allRecordsComplete) {
             grid.pagingToolbar.refresh.enable();
         }
     },
-    
+
     /**
      * upload new file and add to store
-     * 
+     *
      * @param {ux.BrowsePlugin} fileSelector
      * @param {} e
      */
     onFilesSelect: function (fileSelector, event) {
         var app = Tine.Tinebase.appMgr.get('Filemanager'),
-            grid = app.getMainScreen().getCenterPanel(),
+            grid = this,
             targetNode = grid.currentFolderNode,
             gridStore = grid.store,
             rowIndex = false,
@@ -847,16 +857,16 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             addToGrid = true,
             dropAllowed = false,
             nodeRecord = null;
-        
+
         if(event && event.getTarget()) {
             rowIndex = grid.getView().findRowIndex(event.getTarget());
         }
-        
-        
+
+
         if(targetNode.attributes) {
             nodeRecord = targetNode.attributes.nodeRecord;
         }
-        
+
         if(rowIndex !== false && rowIndex > -1) {
             var newTargetNode = gridStore.getAt(rowIndex);
             if(newTargetNode && newTargetNode.data.type == 'folder') {
@@ -865,48 +875,48 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
                 nodeRecord = new Tine.Filemanager.Model.Node(newTargetNode.data);
             }
         }
-        
+
         if(!nodeRecord.isDropFilesAllowed()) {
             Ext.MessageBox.alert(
                     i18n._('Upload Failed'),
                     app.i18n._('Putting files in this folder is not allowed!')
             ).setIcon(Ext.MessageBox.ERROR);
-            
+
             return;
-        }    
-        
+        }
+
         var files = fileSelector.getFileList();
-        
+
         if(files.length > 0) {
             grid.pagingToolbar.refresh.disable();
         }
-        
+
         var filePathsArray = [], uploadKeyArray = [];
-        
+
         Ext.each(files, function (file) {
             var fileRecord = Tine.Filemanager.Model.Node.createFromFile(file),
                 filePath = targetFolderPath + '/' + fileRecord.get('name');
-            
+
             fileRecord.set('path', filePath);
             var existingRecordIdx = gridStore.find('name', fileRecord.get('name'));
             if(existingRecordIdx < 0) {
                 gridStore.add(fileRecord);
             }
-            
+
             var upload = new Ext.ux.file.Upload({
                 fmDirector: grid,
                 file: file,
                 fileSelector: fileSelector,
                 id: filePath
             });
-            
+
             var uploadKey = Tine.Tinebase.uploadManager.queueUpload(upload);
-            
+
             filePathsArray.push(filePath);
             uploadKeyArray.push(uploadKey);
-            
+
         }, this);
-        
+
         var params = {
                 filenames: filePathsArray,
                 type: "file",
@@ -915,38 +925,35 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
         };
         Tine.Filemanager.fileRecordBackend.createNodes(params, uploadKeyArray, true);
     },
-    
+
     /**
      * download file
-     * 
+     *
      * @param {} button
      * @param {} event
      */
     onDownload: function(button, event) {
-        
-        var app = Tine.Tinebase.appMgr.get('Filemanager'),
-            grid = app.getMainScreen().getCenterPanel(),
+        var grid = this,
             selectedRows = grid.selectionModel.getSelections();
 
         Tine.Filemanager.downloadFile(selectedRows[0]);
     },
-    
+
     /**
      * grid on load handler
-     * 
+     *
      * @param grid
      * @param records
      * @param options
      */
     onLoad: function(store, records, options){
-        var app = Tine.Tinebase.appMgr.get('Filemanager'),
-            grid = app.getMainScreen().getCenterPanel();
-        
+        var grid = this;
+
         for(var i=records.length-1; i>=0; i--) {
             var record = records[i];
             if(record.get('type') == 'file' && (!record.get('size') || record.get('size') == 0)) {
                 var upload = Tine.Tinebase.uploadManager.getUpload(record.get('path'));
-                
+
                 if(upload) {
                       if(upload.fileRecord && record.get('name') == upload.fileRecord.get('name')) {
                           grid.updateNodeRecord(record, upload.fileRecord);
@@ -956,10 +963,10 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             }
         }
     },
-    
+
     /**
      * update grid nodeRecord with fileRecord data
-     * 
+     *
      * @param nodeRecord
      * @param fileRecord
      */
@@ -969,27 +976,27 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
         }
         nodeRecord.fileRecord = fileRecord;
     },
-    
+
     /**
      * upload update handler
-     * 
+     *
      * @param change {String} kind of change
      * @param upload {Ext.ux.file.Upload} upload
      * @param fileRecord {file} fileRecord
-     * 
+     *
      */
     onUpdate: function(change, upload, fileRecord) {
         var app = Tine.Tinebase.appMgr.get('Filemanager'),
             grid = app.getMainScreen().getCenterPanel(),
             rowsToUpdate = grid.getStore().query('name', fileRecord.get('name'));
-        
+
         if(change == 'uploadstart') {
             Tine.Tinebase.uploadManager.onUploadStart();
         }
         else if(change == 'uploadfailure') {
             grid.onUploadFail();
         }
-        
+
         if(rowsToUpdate.get(0)) {
             if(change == 'uploadcomplete') {
                 grid.onUploadComplete(upload, fileRecord);
@@ -1002,83 +1009,84 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             rowsToUpdate.get(0).commit(false);
         }
     },
-    
+
     /**
      * init grid drop target
-     * 
+     *
      * @TODO DRY cleanup
      */
     initDropTarget: function(){
+        var me = this;
         var ddrow = new Ext.dd.DropTarget(this.getEl(), {
-            ddGroup : 'fileDDGroup',  
-            
+            ddGroup : 'fileDDGroup',
+
             notifyDrop : function(dragSource, e, data){
-                
+
                 if(data.node && data.node.attributes && !data.node.attributes.nodeRecord.isDragable()) {
                     return false;
                 }
-                
+
                 var app = Tine.Tinebase.appMgr.get(Tine.Filemanager.fileRecordBackend.appName),
-                    grid = app.getMainScreen().getCenterPanel(),
-                    treePanel = app.getMainScreen().getWestPanel().getContainerTreePanel(),
+                    grid = dragSource.grid,
+                    treePanel = me.treePanel || app.getMainScreen().getWestPanel().getContainerTreePanel(),
                     dropIndex = grid.getView().findRowIndex(e.target),
                     target = grid.getStore().getAt(dropIndex),
                     nodes = data.selections ? data.selections : [data.node];
-                
+
                 if((!target || target.data.type === 'file') && grid.currentFolderNode) {
                     target = grid.currentFolderNode;
                 }
-                
+
                 if(!target) {
                     return false;
                 }
-                
+
                 for(var i=0; i<nodes.length; i++) {
                     if(nodes[i].id == target.id) {
                         return false;
                     }
                 }
-                
+
                 var targetNode = treePanel.getNodeById(target.id);
                 if(targetNode && targetNode.isAncestor(nodes[0])) {
                     return false;
                 }
-                
+
                 Tine.Filemanager.fileRecordBackend.copyNodes(nodes, target, !e.ctrlKey);
                 return true;
             },
-            
+
             notifyOver : function( dragSource, e, data ) {
                 if(data.node && data.node.attributes && !data.node.attributes.nodeRecord.isDragable()) {
                     return false;
                 }
-                
+
                 var app = Tine.Tinebase.appMgr.get(Tine.Filemanager.fileRecordBackend.appName),
-                    grid = app.getMainScreen().getCenterPanel(),
-                    dropIndex = grid.getView().findRowIndex(e.target),
-                    treePanel = app.getMainScreen().getWestPanel().getContainerTreePanel(),
+                    grid = dragSource.grid,
+                    dropIndex = me.getView().findRowIndex(e.target),
+                    treePanel = me.treePanel || app.getMainScreen().getWestPanel().getContainerTreePanel(),
                     target= grid.getStore().getAt(dropIndex),
                     nodes = data.selections ? data.selections : [data.node];
-                
+
                 if((!target || (target.data && target.data.type === 'file')) && grid.currentFolderNode) {
                     target = grid.currentFolderNode;
                 }
-                
+
                 if(!target) {
                     return false;
                 }
-                
+
                 for(var i=0; i<nodes.length; i++) {
                     if(nodes[i].id == target.id) {
                         return false;
                     }
                 }
-                
+
                 var targetNode = treePanel.getNodeById(target.id);
                 if(targetNode && targetNode.isAncestor(nodes[0])) {
                     return false;
                 }
-                
+
                 return this.dropAllowed;
             }
         });
@@ -1088,13 +1096,18 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
         var sm = this.grid.getSelectionModel(),
             records = sm.getSelections();
 
-        var containerSelectDialog = new Tine.widgets.container.SelectionDialog({
+        var filePickerDialog = new Tine.Filemanager.FilePickerDialog({
             title: this.app.i18n._('Move Items'),
-            recordClass: this.recordClass
+            singleSelect: true,
+            constraint: 'folder'
         });
-        containerSelectDialog.on('select', function(dlg, node) {
+
+        filePickerDialog.on('selected', function(nodes) {
+            var node = nodes[0];
             this.pagingToolbar.refresh.disable();
-            Tine.Filemanager.fileRecordBackend.copyNodes(records, node.attributes.path, true);
+            Tine.Filemanager.fileRecordBackend.copyNodes(records, node.path, true);
         }, this);
+
+        filePickerDialog.openWindow();
     }
 });
index fbbd454..3297ad3 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Tine 2.0
- * 
+ *
  * @package     Tinebase
  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
  * @author      Philipp Schüle <p.schuele@metaways.de>
@@ -13,13 +13,13 @@ Ext.ns('Tine.Filemanager');
  * @namespace Tine.Filemanager
  * @class Tine.Filemanager.NodeTreePanel
  * @extends Tine.widgets.container.TreePanel
- * 
+ *
  * @author Martin Jatho <m.jatho@metaways.de>
  */
 
 Tine.Filemanager.NodeTreePanel = function(config) {
     Ext.apply(this, config);
-    
+
     this.addEvents(
         /**
          * @event containeradd
@@ -40,39 +40,63 @@ Tine.Filemanager.NodeTreePanel = function(config) {
          */
         'containerrename'
     );
-    
+
     Tine.Filemanager.NodeTreePanel.superclass.constructor.call(this);
 };
 
 Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
-    
+
     filterMode : 'filterToolbar',
-    
+
     recordClass : Tine.Filemanager.Model.Node,
-    
-    allowMultiSelection : false, 
-    
+
+    allowMultiSelection : false,
+
     defaultContainerPath: '/personal',
-    
+
     ddGroup: 'fileDDGroup',
-    
+
     enableDD: true,
-       
+
     initComponent: function() {
-        
-        this.on('containeradd', this.onFolderAdd, this);
-        this.on('containerrename', this.onFolderRename, this);
-        this.on('containerdelete', this.onFolderDelete, this);
-        this.on('nodedragover', this.onNodeDragOver, this);
+        if (!this.readOnly) {
+            this.on('containeradd', this.onFolderAdd, this);
+            this.on('containerrename', this.onFolderRename, this);
+            this.on('containerdelete', this.onFolderDelete, this);
+            this.on('nodedragover', this.onNodeDragOver, this);
+        }
 
         if (! this.app) {
             this.app = Tine.Tinebase.appMgr.get('Filemanager');
         }
-        
-        Tine.Tinebase.uploadManager.on('update', this.onUpdate);
-        
+
+        if (!this.readOnly) {
+            Tine.Tinebase.uploadManager.on('update', this.onUpdate);
+        }
+
+        if (this.readOnly) {
+            this.hasContextMenu = false;
+            this.enableDD = false;
+        }
+
         Tine.Filemanager.NodeTreePanel.superclass.initComponent.call(this);
 
+        this.plugins = this.plugins || [];
+
+        if (!this.readOnly) {
+            this.plugins.push({
+                ptype : 'ux.browseplugin',
+                enableFileDialog: false,
+                multiple : true,
+                handler : this.dropIntoTree
+            });
+        }
+    },
+
+    /**
+     * Setups drop zone
+     */
+    initDrop: function() {
         // init drop zone
         this.dropConfig = {
             ddGroup: this.ddGroup || 'fileDDGroup',
@@ -84,7 +108,7 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
             onNodeOver : function(n, dd, e, data) {
                 var preventDrop = false,
                     selectionContainsFiles = false;
-                
+
                 if (dd.dragData.selections) {
                     for (var i=0; i<dd.dragData.selections.length; i++) {
                         if (n.node.id == dd.dragData.selections[i].id) {
@@ -97,36 +121,36 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
                 }
                 else if(dd.dragData.node && dd.dragData.node.id == n.node.id) {
                     preventDrop = true;
-                } 
-                
+                }
+
                 if(selectionContainsFiles && !n.node.attributes.account_grants) {
                     preventDrop = true;
                 }
-                
+
                 if(n.node.isAncestor(dd.dragData.node)) {
                     preventDrop = true;
                 }
-                
-                return n.node.attributes.nodeRecord.isCreateFolderAllowed() 
+
+                return n.node.attributes.nodeRecord.isCreateFolderAllowed()
                     && (!dd.dragData.node || dd.dragData.node.attributes.nodeRecord.isDragable())
                     && !preventDrop ? 'x-dd-drop-ok' : false;
             },
-            
+
             /**
              * this is called on drop
-             * 
+             *
              * @TODO: combine with repeated code from onNodeOver. DRY!
              */
             isValidDropPoint: function(n, op, dd, e){
                 var preventDrop = false,
                     selectionContainsFiles = false;
-                    
+
                 if (dd.dragData.selections) {
                     for(var i=0; i<dd.dragData.selections.length; i++) {
                         if (n.node.id == dd.dragData.selections[i].id) {
                             preventDrop = true;
                         }
-    
+
                         if(dd.dragData.selections[i].data.type == 'file') {
                             selectionContainsFiles = true;
                         }
@@ -134,21 +158,21 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
                 }
                 else if(dd.dragData.node && dd.dragData.node.id == n.node.id) {
                     preventDrop = true;
-                } 
-                
+                }
+
                 if(selectionContainsFiles && !n.node.attributes.account_grants) {
                     preventDrop = true;
                 }
-                
+
                 if(n.node.isAncestor(dd.dragData.node)) {
                     preventDrop = true;
                 }
-                
+
                 return n.node.attributes.nodeRecord.isCreateFolderAllowed()
                         && (!dd.dragData.node || dd.dragData.node.attributes.nodeRecord.isDragable())
                         && !preventDrop;
             },
-            
+
             completeDrop: function(de) {
                 var ns = de.dropNode, p = de.point, t = de.target;
                 t.ui.endDrop();
@@ -161,7 +185,7 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
             scroll: this.ddScroll,
             /**
              * tree node dragzone modified, dragged node doesn't get selected
-             * 
+             *
              * @param e
              */
             onInitDrag: function(e) {
@@ -172,7 +196,7 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
                 this.tree.fireEvent("startdrag", this.tree, data.node, e);
             }
         };
-        
+
         this.plugins = this.plugins || [];
         this.plugins.push({
             ptype : 'ux.browseplugin',
@@ -201,7 +225,7 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
             }]
         };
     },
-    
+
     /**
      * Tine.widgets.tree.FilterPlugin
      * returns a filter plugin to be used in a grid
@@ -216,7 +240,7 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
                 nodeAttributeField: 'path'
             });
         }
-        
+
         return this.filterPlugin;
     },
 
@@ -230,23 +254,23 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
 
     /**
      * returns params for async request
-     * 
+     *
      * @param {Ext.tree.TreeNode} node
      * @return {Object}
      */
     onBeforeLoad: function(node) {
-        
+
         var path = node.attributes.path;
         var type = Tine.Tinebase.container.path2type(path);
         var owner = Tine.Tinebase.container.pathIsPersonalNode(path);
         var loginName = Tine.Tinebase.registry.get('currentAccount').accountLoginName;
-        
+
         if (type === 'personal' && owner != loginName) {
             type = 'otherUsers';
         }
-        
+
         var newPath = path;
-        
+
         if (type === 'personal' && owner) {
             var pathParts = path.toString().split('/');
             newPath = '/' + pathParts[1] + '/' + loginName;
@@ -254,7 +278,7 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
                 newPath += '/' + pathParts[3];
             }
         }
-        
+
         var params = {
             method: 'Filemanager.searchNodes',
             application: this.app.appName,
@@ -265,22 +289,22 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
                      ],
             paging: {dir: 'ASC', limit: 50, sort: 'name', start: 0}
         };
-        
+
         return params;
     },
-    
+
     onBeforeCreateNode: function(attr) {
         Tine.Filemanager.NodeTreePanel.superclass.onBeforeCreateNode.apply(this, arguments);
-        
+
         attr.leaf = false;
-        
+
         if(attr.name && typeof attr.name == 'object') {
             Ext.apply(attr, {
                 text: Ext.util.Format.htmlEncode(attr.name.name),
                 qtip: Tine.Tinebase.common.doubleEncode(attr.name.name)
             });
         }
-        
+
         // copy 'real' data to a node record NOTE: not a full record as we have no record reader here
         var nodeData = Ext.copyTo({}, attr, Tine.Filemanager.Model.Node.getFieldNames());
         attr.nodeRecord = new Tine.Filemanager.Model.Node(nodeData);
@@ -288,11 +312,11 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
 
     /**
      * initiates tree context menues
-     * 
+     *
      * @private
      */
     initContextMenu: function() {
-        
+
         this.contextMenuUserFolder = Tine.widgets.tree.ContextMenu.getMenu({
             nodeName: this.app.i18n._(this.containerName),
             actions: ['add', 'reload', 'delete', 'rename', 'properties'],
@@ -300,7 +324,7 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
             backend: 'Filemanager',
             backendModel: 'Node'
         });
-            
+
         this.contextMenuRootFolder = Tine.widgets.tree.ContextMenu.getMenu({
             nodeName: this.app.i18n._(this.containerName),
             actions: ['add', 'reload'],
@@ -308,7 +332,7 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
             backend: 'Filemanager',
             backendModel: 'Node'
         });
-        
+
         this.contextMenuOtherUserFolder = Tine.widgets.tree.ContextMenu.getMenu({
             nodeName: this.app.i18n._(this.containerName),
             actions: ['reload'],
@@ -316,7 +340,7 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
             backend: 'Filemanager',
             backendModel: 'Node'
         });
-        
+
         this.contextMenuContainerFolder = Tine.widgets.tree.ContextMenu.getMenu({
             nodeName: this.app.i18n._(this.containerName),
             actions: ['add', 'reload', 'delete', 'rename', 'grants', 'properties'],
@@ -324,7 +348,7 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
             backend: 'Filemanager',
             backendModel: 'Node'
         });
-        
+
         this.contextMenuReloadFolder = Tine.widgets.tree.ContextMenu.getMenu({
             nodeName: this.app.i18n._(this.containerName),
             actions: ['reload', 'properties'],
@@ -333,7 +357,7 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
             backendModel: 'Node'
         });
     },
-    
+
     /**
      * @private
      * - select default path
@@ -341,34 +365,34 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
     afterRender: function() {
         Tine.Filemanager.NodeTreePanel.superclass.afterRender.call(this);
     },
-    
+
     /**
      * show context menu
-     * 
+     *
      * @param {Ext.tree.TreeNode} node
      * @param {Ext.EventObject} event
      */
     onContextMenu: function(node, event) {
-        
+
         var currentAccount = Tine.Tinebase.registry.get('currentAccount');
-        
+
         this.ctxNode = node;
         var container = node.attributes.nodeRecord.data,
             path = container.path;
-        
+
         if (! Ext.isString(path) || node.isRoot) {
             return;
         }
-        
+
         Tine.log.debug('Tine.Filemanager.NodeTreePanel::onContextMenu - context node:');
         Tine.log.debug(node);
-        
+
         if (node.id == 'otherUsers' || (node.parentNode && node.parentNode.id == 'otherUsers')) {
             this.contextMenuOtherUserFolder.showAt(event.getXY());
         } else if (node.id == 'personal' || node.id == 'shared') {
             this.contextMenuRootFolder.showAt(event.getXY());
-        } else if (path.match(/^\/shared/) 
-                && (Tine.Tinebase.common.hasRight('admin', this.app.appName) 
+        } else if (path.match(/^\/shared/)
+                && (Tine.Tinebase.common.hasRight('admin', this.app.appName)
                     || Tine.Tinebase.common.hasRight('manage_shared_folders', this.app.appName))
                     || container.account_grants && container.account_grants.adminGrant
                 )
@@ -390,57 +414,54 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
             this.contextMenuUserFolder.showAt(event.getXY());
         }
     },
-    
+
     /**
      * updates grid actions
      * @todo move to grid / actionUpdater
-     * 
+     *
      * @param {} sm     SelectionModel
      * @param {Ext.tree.TreeNode} node
      */
     updateActions: function(sm, node) {
         var grid = this.app.getMainScreen().getCenterPanel();
-        
+
         grid.action_deleteRecord.disable();
         grid.action_upload.disable();
-        
-        if(!!node && !!node.isRoot) {
+
+        if (!!node && !!node.isRoot) {
             grid.action_goUpFolder.disable();
-        }
-        else {
+        } else {
             grid.action_goUpFolder.enable();
         }
-                
-        if(node && node.attributes && node.attributes.nodeRecord.isCreateFolderAllowed()) {
+
+        if (node && node.attributes && node.attributes.nodeRecord.isCreateFolderAllowed()) {
             grid.action_createFolder.enable();
-        }
-        else {
+        } else {
             grid.action_createFolder.disable();
         }
-        
-        if(node && node.attributes && node.attributes.nodeRecord.isDropFilesAllowed()) {
+
+        if (node && node.attributes && node.attributes.nodeRecord.isDropFilesAllowed()) {
             grid.action_upload.enable();
-        }
-        else {
+        } else {
             grid.action_upload.disable();
         }
     },
-    
+
     /**
      * called when tree selection changes
-     * 
+     *
      * @param {} sm     SelectionModel
      * @param {Ext.tree.TreeNode} node
      */
     onSelectionChange: function(sm, node) {
         this.updateActions(sm, node);
         var grid = this.app.getMainScreen().getCenterPanel();
-        
+
         grid.currentFolderNode = node;
         Tine.Filemanager.NodeTreePanel.superclass.onSelectionChange.call(this, sm, node);
-    
+
     },
-    
+
     /**
      * convert filesystem path to treePath
      *
@@ -465,12 +486,12 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
 
         return treePath;
     },
-    
+
     /**
      * Expands a specified path in this TreePanel. A path can be retrieved from a node with {@link Ext.data.Node#getPath}
-     * 
+     *
      * NOTE: path does not consist of id's starting from the second depth
-     * 
+     *
      * @param {String} path
      * @param {String} attr (optional) The attribute used in the path (see {@link Ext.data.Node#getPath} for more info)
      * @param {Function} callback (optional) The callback to call when the expand is complete. The callback will be called with
@@ -488,7 +509,7 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
                 }
                 return;
             }
-            
+
             if (index > 2) {
                 var c = curNode.findChild('path', curPath + '/' + keys[index]);
             } else {
@@ -506,45 +527,45 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
         };
         curNode.expand(false, false, f);
     },
-    
+
     /**
      * files/folder got dropped on node
-     * 
+     *
      * @param {Object} dropEvent
      * @private
      */
     onBeforeNodeDrop: function(dropEvent) {
         var nodes, target = dropEvent.target;
-        
+
         if(dropEvent.data.selections) {
             nodes = dropEvent.data.grid.selModel.selections.items;
-        }    
-            
+        }
+
         if(!nodes && dropEvent.data.node) {
             nodes = [dropEvent.data.node];
         }
-        
+
         Tine.Filemanager.fileRecordBackend.copyNodes(nodes, target, !dropEvent.rawEvent.ctrlKey);
-        
+
         dropEvent.dropStatus = true;
         return true;
     },
-    
+
     /**
      * folder delete handler
      */
     onFolderDelete: function(node) {
         var grid = this.app.getMainScreen().getCenterPanel();
-        if(grid.currentFolderNode.isAncestor && typeof grid.currentFolderNode.isAncestor == 'function' 
+        if(grid.currentFolderNode.isAncestor && typeof grid.currentFolderNode.isAncestor == 'function'
             && grid.currentFolderNode.isAncestor(node)) {
             node.parentNode.select();
         }
         grid.getStore().reload();
     },
-    
+
     /**
      * clone a tree node / create a node from grid node
-     * 
+     *
      * @param node
      * @returns {Ext.tree.AsyncTreeNode}
      */
@@ -552,14 +573,14 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
         var targetPath = target.attributes.path,
             newPath = '',
             copy;
-        
+
         if(node.attributes) {
             var nodeName = node.attributes.name;
             if(typeof nodeName == 'object') {
                 nodeName = nodeName.name;
             }
             newPath = targetPath + '/' + nodeName;
-            
+
             copy = new Ext.tree.AsyncTreeNode({text: node.text, path: newPath, name: node.attributes.name
                 , nodeRecord: node.attributes.nodeRecord, account_grants: node.attributes.account_grants});
         }
@@ -568,26 +589,26 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
             if(typeof nodeName == 'object') {
                 nodeName = nodeName.name;
             }
-            
+
             var nodeData = Ext.copyTo({}, node.data, Tine.Filemanager.Model.Node.getFieldNames());
             var newNodeRecord = new Tine.Filemanager.Model.Node(nodeData);
-             
+
             newPath = targetPath + '/' + nodeName;
             copy = new Ext.tree.AsyncTreeNode({text: nodeName, path: newPath, name: node.data.name
                 , nodeRecord: newNodeRecord, account_grants: node.data.account_grants});
         }
-                
+
         copy.attributes.nodeRecord.beginEdit();
         copy.attributes.nodeRecord.set('path', newPath);
         copy.attributes.nodeRecord.endEdit();
-        
+
         copy.parentNode = target;
         return copy;
     },
-    
+
     /**
      * create Tree node by given node data
-     * 
+     *
      * @param nodeData
      * @param target
      * @returns {Ext.tree.AsyncTreeNode}
@@ -597,9 +618,9 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
         if(typeof nodeName == 'object') {
             nodeName = nodeName.name;
         }
-        
+
         var newNodeRecord = new Tine.Filemanager.Model.Node(nodeData);
-        
+
         var newNode = new Ext.tree.AsyncTreeNode({
             text: nodeName,
             path: nodeData.path,
@@ -608,31 +629,31 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
             account_grants: nodeData.account_grants,
             id: nodeData.id
         })
-        
+
         newNode.attributes.nodeRecord.beginEdit();
         newNode.attributes.nodeRecord.set('path', nodeData.path);
         newNode.attributes.nodeRecord.endEdit();
-        
+
         newNode.parentNode = target;
         return newNode;
-        
+
     },
-    
+
     /**
      * TODO: move to Upload class or elsewhere??
      * updating fileRecord after creating node
-     * 
+     *
      * @param response
      * @param request
      * @param upload
      */
     onNodeCreated: function(response, request, upload) {
-       
+
         var app = Tine.Tinebase.appMgr.get('Filemanager'),
         grid = app.getMainScreen().getCenterPanel();
-        
+
         var record = Ext.util.JSON.decode(response.responseText);
-        
+
         var fileRecord = upload.fileRecord;
         fileRecord.beginEdit();
         fileRecord.set('contenttype', record.contenttype);
@@ -646,26 +667,30 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
         fileRecord.set('name', record.name);
         fileRecord.set('path', record.path);
         fileRecord.commit(false);
-        
+
         upload.fireEvent('update', 'uploadfinished', upload, fileRecord);
-        
+
         grid.pagingToolbar.refresh.enable();
-        
+
     },
-    
+
     /**
      * copies uploaded temporary file to target location
-     * 
+     *
      * @param upload    {Ext.ux.file.Upload}
-     * @param file  {Ext.ux.file.Upload.file} 
+     * @param file  {Ext.ux.file.Upload.file}
      */
     onUploadComplete: function(upload, file) {
+        if (this.readOnly) {
+            return;
+        }
+
         var app = Tine.Tinebase.appMgr.get('Filemanager'),
             treePanel = app.getMainScreen().getWestPanel().getContainerTreePanel();
-        
+
      // check if we are responsible for the upload
         if (upload.fmDirector != treePanel) return;
-        
+
         // $filename, $type, $tempFileId, $forceOverwrite
         Ext.Ajax.request({
             timeout: 10*60*1000, // Overriding Ajax timeout - important!
@@ -676,15 +701,15 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
                 tempFileId: file.get('id'),
                 forceOverwrite: true
             },
-            success: treePanel.onNodeCreated.createDelegate(this, [upload], true), 
+            success: treePanel.onNodeCreated.createDelegate(this, [upload], true),
             failure: treePanel.onNodeCreated.createDelegate(this, [upload], true)
         });
-        
+
     },
-    
+
     /**
      * on upload failure
-     * 
+     *
      * @private
      */
     onUploadFail: function () {
@@ -693,32 +718,32 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
             i18n._('Could not upload file. Filesize could be too big. Please notify your Administrator.')
         ).setIcon(Ext.MessageBox.ERROR);
     },
-    
+
     /**
      * add folder handler
      */
     onFolderAdd: function(nodeData) {
-        
+
         var app = Tine.Tinebase.appMgr.get('Filemanager'),
             grid = app.getMainScreen().getCenterPanel();
-        
+
         grid.getStore().reload();
         if(nodeData.error) {
             Tine.log.debug(nodeData);
         }
     },
-    
+
     /**
      * handles renaming of a tree node / aka folder
      */
     onFolderRename: function(nodeData, node, newName) {
         var app = Tine.Tinebase.appMgr.get('Filemanager'),
             grid = app.getMainScreen().getCenterPanel();
-        
+
         if(nodeData[0]) {
             nodeData = nodeData[0];
-        };
-        
+        }
+
         node.attributes.nodeRecord.beginEdit();
         if (typeof node.attributes.name == 'object') {
             node.attributes.name.name = newName;
@@ -732,33 +757,33 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
         node.attributes.nodeRecord.commit(false);
 
         grid.currenFolderNode = node;
-        
+
         Tine.Filemanager.NodeTreePanel.superclass.onSelectionChange.call(this, this.getSelectionModel(), node);
-        
+
     },
-    
+
     /**
      * upload update handler
-     * 
+     *
      * @param change {String} kind of change
      * @param upload {Ext.ux.file.Upload} upload
      * @param fileRecord {file} fileRecord
-     * 
+     *
      */
     onUpdate: function(change, upload, fileRecord) {
-        
+
         var app = Tine.Tinebase.appMgr.get('Filemanager'),
             grid = app.getMainScreen().getCenterPanel(),
             treePanel = app.getMainScreen().getWestPanel().getContainerTreePanel(),
             rowsToUpdate = grid.getStore().query('name', fileRecord.get('name'));
-        
+
         if(change == 'uploadstart') {
             Tine.Tinebase.uploadManager.onUploadStart();
         }
         else if(change == 'uploadfailure') {
             treePanel.onUploadFail();
         }
-        
+
         if(rowsToUpdate.get(0)) {
             if(change == 'uploadcomplete') {
                 treePanel.onUploadComplete(upload, fileRecord);
@@ -769,68 +794,68 @@ Ext.extend(Tine.Filemanager.NodeTreePanel, Tine.widgets.container.TreePanel, {
             }
             rowsToUpdate.get(0).afterEdit();
             rowsToUpdate.get(0).commit(false);
-        }       
+        }
     },
-    
+
     /**
      * handels tree drop of object from outside the browser
-     * 
+     *
      * @param fileSelector
      * @param targetNodeId
      */
     dropIntoTree: function(fileSelector, event) {
-              
+
         var treePanel = fileSelector.component,
             app = treePanel.app,
             grid = app.getMainScreen().getCenterPanel(),
             targetNode,
             targetNodePath;
-            
-        
+
+
         var targetNodeId;
         var treeNodeAttribute = event.getTarget('div').attributes['ext:tree-node-id'];
         if(treeNodeAttribute) {
             targetNodeId = treeNodeAttribute.nodeValue;
             targetNode = treePanel.getNodeById(targetNodeId);
             targetNodePath = targetNode.attributes.path;
-            
-        };
-        
+
+        }
+
         if(!targetNode.attributes.nodeRecord.isDropFilesAllowed()) {
             Ext.MessageBox.alert(
                     i18n._('Upload Failed'),
                     app.i18n._('Putting files in this folder is not allowed!')
                 ).setIcon(Ext.MessageBox.ERROR);
-            
+
             return;
-        };
-      
+        }
+
         var files = fileSelector.getFileList(),
             filePathsArray = [],
             uploadKeyArray = [],
             addToGridStore = false;
-        
+
         Ext.each(files, function (file) {
-            
+
             var fileName = file.name || file.fileName,
                 filePath = targetNodePath + '/' + fileName;
-            
+
             var upload = new Ext.ux.file.Upload({
                 fmDirector: treePanel,
                 file: file,
                 fileSelector: fileSelector,
                 id: filePath
             });
-            
+
             var uploadKey = Tine.Tinebase.uploadManager.queueUpload(upload);
-            
+
             filePathsArray.push(filePath);
             uploadKeyArray.push(uploadKey);
-            
+
             addToGridStore = grid.currentFolderNode.id === targetNodeId;
-            
+
         }, this);
-        
+
         var params = {
                 filenames: filePathsArray,
                 type: "file",
index a923353..e2319c6 100644 (file)
@@ -48,7 +48,7 @@ class Tinebase_FileSystem implements Tinebase_Controller_Interface
     protected $_modLogActive = false;
 
     protected $_indexingActive = false;
-    
+
     /**
      * stat cache
      * 
@@ -109,7 +109,7 @@ class Tinebase_FileSystem implements Tinebase_Controller_Interface
             Tinebase_Config::FILESYSTEM_MODLOGACTIVE => $this->_modLogActive
         ));
     }
-    
+
     /**
      * init application base paths
      * 
@@ -776,7 +776,7 @@ class Tinebase_FileSystem implements Tinebase_Controller_Interface
         $parentNode  = null;
         $pathParts   = $this->_splitPath($path);
         $node = null;
-        
+
         foreach ($pathParts as $pathPart) {
             $pathPart = trim($pathPart);
             $currentPath[]= $pathPart;
@@ -924,7 +924,7 @@ class Tinebase_FileSystem implements Tinebase_Controller_Interface
                 $this->_getTreeNodeBackend()->setRevision(null);
             }
         }
-        
+
         return $node;
     }
 
@@ -1242,7 +1242,7 @@ class Tinebase_FileSystem implements Tinebase_Controller_Interface
     {
         $pathParts = is_array($path) ? $path : $this->_splitPath($path);
         array_unshift($pathParts, '@' . $revision);
-        
+
         return sha1(implode(null, $pathParts));
     }
     
@@ -1449,7 +1449,7 @@ class Tinebase_FileSystem implements Tinebase_Controller_Interface
         if (count($toDeleteIds) === 0) {
             return 0;
         }
-        
+
         $nodeIdsToDelete =$this->_getTreeNodeBackend()->search(new Tinebase_Model_Tree_Node_Filter(array(array(
             'field'     => 'object_id',
             'operator'  => 'in',
@@ -1465,7 +1465,7 @@ class Tinebase_FileSystem implements Tinebase_Controller_Interface
         if (false === $this->_modLogActive && true === $this->_indexingActive) {
             Tinebase_Fulltext_Indexer::getInstance()->removeFileContentsFromIndex($toDeleteIds);
         }
-        
+
         return $deleteCount;
     }
 
index 61ac606..56d52df 100644 (file)
@@ -210,10 +210,15 @@ class Tinebase_FileSystem_RecordAttachments
             $name = $attachment->name;
         }
 
+        // If there is no tempfile, the attachment was added from the filemanager
+        if ($attachment instanceof Tinebase_Model_Tree_Node && !isset($attachment->tempFile) && isset($attachment->path)) {
+            return $this->addRecordAttachmentFromFilemanager($record, $attachment);
+        }
+
         if ($attachment instanceof Tinebase_Model_Tree_Node && empty($name)) {
             $name = $attachment->name;
         }
-        
+
         if (empty($name)) {
             if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ .
                 ' Could not evaluate attachment name.');
@@ -234,7 +239,28 @@ class Tinebase_FileSystem_RecordAttachments
         $node = $this->_fsController->stat($attachmentPath);
         return $node;
     }
-    
+
+    /**
+     * Add a filemanager attachment to a given record
+     *
+     * @param Tinebase_Record_Abstract $record
+     * @param Tinebase_Model_Tree_Node $attachment
+     * @return null|Tinebase_Model_Tree_Node
+     */
+    public function addRecordAttachmentFromFilemanager(Tinebase_Record_Abstract $record, Tinebase_Model_Tree_Node $attachment) {
+        if (!$attachment->path || !$attachment->name) {
+            return null;
+        }
+
+        $attachmentsDir = $this->getRecordAttachmentPath($record, true);
+        $attachmentPath = $attachmentsDir . '/' . $attachment->name;
+
+        $nodeController = Filemanager_Controller_Node::getInstance();
+        $path = Tinebase_Model_Tree_Node_Path::createFromPath($nodeController->addBasePath($attachment->path));
+        $attachment = $this->_fsController->copy($path->statpath, $attachmentPath);
+
+        return $attachment;
+    }
     
     /**
      * delete attachments of record
index ff60a85..140e59c 100644 (file)
@@ -26,7 +26,7 @@ var taskPanel =  new Tine.containerTreePanel({
  */
 Tine.widgets.container.TreePanel = function(config) {
     Ext.apply(this, config);
-    
+
     this.addEvents(
         /**
          * @event containeradded
@@ -59,7 +59,7 @@ Tine.widgets.container.TreePanel = function(config) {
          */
         'containercolorset'
     );
-        
+
     Tine.widgets.container.TreePanel.superclass.constructor.call(this);
 };
 
@@ -95,7 +95,7 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
      * grants which are required to select leaf node(s)
      */
     requiredGrants: null,
-    
+
     /**
      * @cfg {Boolean} useContainerColor
      * use container colors
@@ -119,6 +119,11 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
      */
     canonicalName: 'ContainerTree',
 
+    /**
+     * Referenced grid panel
+     */
+    gridPanel: null,
+
     useArrows: true,
     border: false,
     autoScroll: true,
@@ -129,12 +134,17 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
 
     /**
      * @fixme not needed => all events hand their events over!!!
-     * 
+     *
      * @property ctxNode holds treenode which got a contextmenu
      * @type Ext.tree.TreeNode
      */
     ctxNode: null,
-    
+
+    /**
+     * No user interactions, menus etc. allowed except for browsing
+     */
+    readOnly: false,
+
     /**
      * init this treePanel
      */
@@ -145,7 +155,7 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
         if (! this.app) {
             this.app = Tine.Tinebase.appMgr.get(this.appName);
         }
-        
+
         if (this.allowMultiSelection) {
             this.selModel = new Ext.tree.MultiSelectionModel({});
         }
@@ -157,14 +167,14 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
         if (this.modelConfiguration) {
             this.hasPersonalContainer = this.modelConfiguration.hasPersonalContainer !== false;
         }
-        
+
         var containerName = this.recordClass ? this.recordClass.getContainerName() : 'container';
         var containersName = this.recordClass ? this.recordClass.getContainersName() : 'containers';
 
         //ngettext('container', 'containers', n);
         this.containerName = this.containerName || this.app.i18n.n_hidden(containerName, containersName, 1);
         this.containersName = this.containersName || this.app.i18n._hidden(containersName);
-        
+
         this.loader = this.loader || new Tine.widgets.tree.Loader({
             getParams: this.onBeforeLoad.createDelegate(this),
             inspectCreateNode: this.onBeforeCreateNode.createDelegate(this)
@@ -176,44 +186,48 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
             this.rootVisible = false;
         }
 
-        // init drop zone
-        this.dropConfig = {
-            ddGroup: this.ddGroup || 'TreeDD',
-            appendOnly: this.ddAppendOnly === true,
-            /**
-             * @todo check acl!
-             */
-            onNodeOver : function(n, dd, e, data) {
-                var node = n.node;
-                
-                // auto node expand check
-                if(node.hasChildNodes() && !node.isExpanded()){
-                    this.queueExpand(node);
-                }
-                return node.attributes.allowDrop ? 'tinebase-tree-drop-move' : false;
-            },
-            isValidDropPoint: function(n, dd, e, data){
-                return n.node.attributes.allowDrop;
-            },
-            completeDrop: Ext.emptyFn
-        };
+        if (!this.readOnly) {
+            // init drop zone
+            this.dropConfig = {
+                ddGroup: this.ddGroup || 'TreeDD',
+                appendOnly: this.ddAppendOnly === true,
+                /**
+                 * @todo check acl!
+                 */
+                onNodeOver: function (n, dd, e, data) {
+                    var node = n.node;
+
+                    // auto node expand check
+                    if (node.hasChildNodes() && !node.isExpanded()) {
+                        this.queueExpand(node);
+                    }
+                    return node.attributes.allowDrop ? 'tinebase-tree-drop-move' : false;
+                },
+                isValidDropPoint: function (n, dd, e, data) {
+                    return n.node.attributes.allowDrop;
+                },
+                completeDrop: Ext.emptyFn
+            };
+        }
 
         if (this.hasContextMenu) {
             this.initContextMenu();
         }
-              
+
         this.getSelectionModel().on('beforeselect', this.onBeforeSelect, this);
         this.getSelectionModel().on('selectionchange', this.onSelectionChange, this);
         this.on('click', this.onClick, this);
         if (this.hasContextMenu) {
             this.on('contextmenu', this.onContextMenu, this);
         }
-        this.on('beforenodedrop', this.onBeforeNodeDrop, this);
-        this.on('append', this.onAppendNode, this);
-        this.on('beforecollapsenode', this.onBeforeCollapse, this);
-        
+
+        if (!this.readOnly) {
+            this.on('beforenodedrop', this.onBeforeNodeDrop, this);
+            this.on('append', this.onAppendNode, this);
+            this.on('beforecollapsenode', this.onBeforeCollapse, this);
+        }
+
         Tine.widgets.container.TreePanel.superclass.initComponent.call(this);
-        return;
     },
 
     /**
@@ -252,22 +266,22 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
 
     /**
      * template fn for subclasses to set default path
-     * 
+     *
      * @return {String}
      */
     getDefaultContainerPath: function() {
         return this.defaultContainerPath || '/';
     },
-    
+
     /**
      * template fn for subclasses to append extra items
-     * 
+     *
      * @return {Array}
      */
     getExtraItems: function() {
         return this.extraItems || [];
     },
-    
+
     /**
      * returns a filter plugin to be used in a grid
      */
@@ -277,13 +291,13 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
                 treePanel: this
             });
         }
-        
+
         return this.filterPlugin;
     },
-    
+
     /**
      * returns object of selected container/filter or null/default
-     * 
+     *
      * @param {Array} [requiredGrants]
      * @param {Tine.Tinebase.Model.Container} [defaultContainer]
      * @param {Boolean} onlySingle use default if more than one container in selection
@@ -293,21 +307,21 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
         var container = defaultContainer,
             sm = this.getSelectionModel(),
             selection = typeof sm.getSelectedNodes == 'function' ? sm.getSelectedNodes() : [sm.getSelectedNode()];
-        
+
         if (Ext.isArray(selection) && selection.length > 0 && (! onlySingle || selection.length === 1 || ! container)) {
             container = this.getContainerFromSelection(selection, requiredGrants) || container;
-        } 
+        }
         // postpone this as we don't get the whole container record here
 //        else if (this.filterMode == 'filterToolbar' && this.filterPlugin) {
 //            container = this.getContainerFromFilter() || container;
 //        }
-        
+
         return container;
     },
-    
+
     /**
      * get container from selection
-     * 
+     *
      * @param {Array} selection
      * @param {Array} requiredGrants
      * @return {Tine.Tinebase.Model.Container}
@@ -324,21 +338,21 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
                 }
             }
         }, this);
-        
+
         return result;
     },
 
     /**
      * get container from filter toolbar
-     * 
+     *
      * @param {Array} requiredGrants
      * @return {Tine.Tinebase.Model.Container}
-     * 
+     *
      * TODO make this work -> atm we don't get the account grants here (why?)
      */
     getContainerFromFilter: function(requiredGrants) {
         var result = null;
-        
+
         // check if single container is selected in filter toolbar 
         var ftb = this.filterPlugin.getGridPanel().filterToolbar,
             filterValue = null;
@@ -355,13 +369,13 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
                 return false;
             }
         }, this);
-        
+
         return result;
     },
-    
+
     /**
      * convert containerPath to treePath
-     * 
+     *
      * @param {String}  containerPath
      * @return {String} treePath
      */
@@ -377,13 +391,13 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
                 treePath = treePath.replace('personal/'  + Tine.Tinebase.registry.get('currentAccount').accountId, 'personal');
             }
         }
-        
+
         return treePath;
     },
-    
+
     /**
-     * checkes if user has requested grant for given container represented by a tree node 
-     * 
+     * checkes if user has requested grant for given container represented by a tree node
+     *
      * @param {Ext.tree.TreeNode} node
      * @param {Array} grant
      * @return {}
@@ -401,34 +415,34 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
 
         return condition;
     },
-    
+
     /**
      * @private
      * - select default path
      */
     afterRender: function() {
         Tine.widgets.container.TreePanel.superclass.afterRender.call(this);
-        
+
         var defaultContainerPath = this.getDefaultContainerPath();
-        
+
         if (defaultContainerPath && defaultContainerPath != '/') {
             var root = '/' + this.getRootNode().id;
-            
+
             this.expand();
             // @TODO use getTreePath() when filemanager is fixed
             this.selectPath.defer(100, this, [root + defaultContainerPath]);
         }
-        
+
         if (this.filterMode == 'filterToolbar' && this.filterPlugin && this.filterPlugin.getGridPanel()) {
             this.filterPlugin.getGridPanel().filterToolbar.on('change', this.onFilterChange, this);
         }
     },
-    
+
     /**
      * @private
      */
     initContextMenu: function() {
-        
+
         this.contextMenuUserFolder = Tine.widgets.tree.ContextMenu.getMenu({
             nodeName: this.containerName,
             actions: ['add'],
@@ -436,7 +450,7 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
             backend: 'Tinebase_Container',
             backendModel: 'Container'
         });
-        
+
         this.contextMenuSingleContainer = Tine.widgets.tree.ContextMenu.getMenu({
             nodeName: this.containerName,
             actions: ['delete', 'rename', 'grants'].concat(
@@ -448,7 +462,7 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
             backend: 'Tinebase_Container',
             backendModel: 'Container'
         });
-        
+
         this.contextMenuSingleContainerProperties = Tine.widgets.tree.ContextMenu.getMenu({
             nodeName: this.containerName,
             actions: ['properties'],
@@ -457,42 +471,46 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
             backendModel: 'Container'
         });
     },
-    
+
     /**
      * called when node is appended to this tree
      */
     onAppendNode: function(tree, parent, appendedNode, idx) {
         if (appendedNode.leaf && this.hasGrant(appendedNode, this.requiredGrants)) {
             if (this.useContainerColor) {
-                appendedNode.ui.render = appendedNode.ui.render.createSequence(function() {
-                    this.colorNode = Ext.DomHelper.insertAfter(this.iconNode, {tag: 'span', html: '&nbsp;&#9673;&nbsp', style: {color: appendedNode.attributes.container.color || '#808080'}}, true);
+                appendedNode.ui.render = appendedNode.ui.render.createSequence(function () {
+                    this.colorNode = Ext.DomHelper.insertAfter(this.iconNode, {
+                        tag: 'span',
+                        html: '&nbsp;&#9673;&nbsp',
+                        style: {color: appendedNode.attributes.container.color || '#808080'}
+                    }, true);
                 }, appendedNode.ui);
             }
         }
     },
-    
+
     /**
      * expand automatically on node click
-     * 
+     *
      * @param {} node
      * @param {} e
      */
     onClick: function(node, e) {
         var sm = this.getSelectionModel(),
             selectedNode = sm.getSelectedNode();
-    
+
         // NOTE: in single select mode, a node click on a selected node does not trigger 
         //       a selection change. We need to do this by hand here
         if (! this.allowMultiSelection && node == selectedNode) {
             this.onSelectionChange(sm, node);
         }
-        
+
         node.expand();
     },
-    
+
     /**
      * show context menu
-     * 
+     *
      * @param {} node
      * @param {} event
      */
@@ -501,14 +519,14 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
         var container = node.attributes.container,
             path = container.path,
             owner;
-        
+
         if (! Ext.isString(path)) {
             return;
         }
 
         event.stopPropagation();
         event.preventDefault();
-        
+
         if (Tine.Tinebase.container.pathIsContainer(path)) {
             if (container.account_grants && container.account_grants.adminGrant) {
                 this.contextMenuSingleContainer.showAt(event.getXY());
@@ -524,7 +542,7 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
 
     /**
      * adopt attr
-     * 
+     *
      * @param {Object} attr
      */
     onBeforeCreateNode: function(attr) {
@@ -533,25 +551,25 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
             attr.path = '/personal/' + attr.accountId;
             attr.id = attr.accountId;
         }
-        
+
         if (! attr.name && attr.path) {
             attr.name = Tine.Tinebase.container.path2name(attr.path, this.containerName, this.containersName);
         }
-        
+
         Ext.applyIf(attr, {
             text: Ext.util.Format.htmlEncode(attr.name),
             qtip: Tine.Tinebase.common.doubleEncode(attr.name),
             leaf: !!attr.account_grants,
             allowDrop: !!attr.account_grants && attr.account_grants.addGrant
         });
-        
+
         // copy 'real' data to container space
         attr.container = Ext.copyTo({}, attr, Tine.Tinebase.Model.Container.getFieldNames());
     },
-    
+
     /**
      * returns params for async request
-     * 
+     *
      * @param {Ext.tree.TreeNode} node
      * @return {Object}
      */
@@ -559,11 +577,11 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
         var path = node.attributes.path;
         var type = Tine.Tinebase.container.path2type(path);
         var owner = Tine.Tinebase.container.pathIsPersonalNode(path);
-        
+
         if (type === 'personal' && ! owner) {
             type = 'otherUsers';
         }
-        
+
         var params = {
             method: 'Tinebase_Container.getContainer',
             application: this.app.appName,
@@ -571,13 +589,13 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
             requiredGrants: this.requiredGrants,
             owner: owner
         };
-        
+
         return params;
     },
-    
+
     /**
      * permit selection of nodes with missing required grant
-     * 
+     *
      * @param {} sm
      * @param {} newSelection
      * @param {} oldSelection
@@ -595,25 +613,25 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
             }
         }
     },
-    
+
     /**
      * record got dropped on container node
-     * 
+     *
      * @param {Object} dropEvent
      * @private
-     * 
+     *
      * TODO use Ext.Direct
      */
     onBeforeNodeDrop: function(dropEvent) {
         var targetContainerId = dropEvent.target.id;
-        
+
         // get selection filter from grid
         var sm = this.app.getMainScreen().getCenterPanel().getGrid().getSelectionModel();
         if (sm.getCount() === 0) {
             return false;
         }
         var filter = sm.getSelectionFilter();
-        
+
         // move messages to folder
         Ext.Ajax.request({
             params: {
@@ -629,12 +647,12 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
                 this.app.getMainScreen().getCenterPanel().loadGridData();
             }
         });
-        
+
         // prevent repair actions
         dropEvent.dropStatus = true;
         return true;
     },
-    
+
     /**
      * called on filtertrigger of filter toolbar
      * clears selection silently
@@ -643,13 +661,13 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
         /* 2012-01-30 cweiss: i have no idea what this was for.
          * if its needed please document here!
         var sm = this.getSelectionModel();
-        
+
         sm.suspendEvents();
         sm.clearSelections();
         sm.resumeEvents();
         */
     },
-    
+
     /**
      * If first node is collapsed, reload all data
      */
@@ -662,7 +680,7 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
 
     /**
      * called when tree selection changes
-     * 
+     *
      * @param {} sm
      * @param {} node
      */
@@ -688,13 +706,13 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
                 }
             }, this);
             ftb.supressEvents = false;
-            
+
             // set ftb filters according to tree selection
             var containerFilter = this.getFilterPlugin().getFilter();
             ftb.addFilter(new ftb.record(containerFilter));
-        
+
             ftb.onFiltertrigger();
-            
+
             // finally select the selected node, as filtertrigger clears all selections
             sm.suspendEvents();
             Ext.each(nodes, function(node) {
@@ -703,10 +721,10 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
             sm.resumeEvents();
         }
     },
-    
+
     /**
      * selects path by container Path
-     * 
+     *
      * @param {String} containerPath
      * @param {String} [attr]
      * @param {Function} [callback]
@@ -714,10 +732,10 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
     selectContainerPath: function(containerPath, attr, callback) {
         return this.selectPath(this.getTreePath(containerPath), attr, callback);
     },
-    
+
     /**
      * get default container for new records
-     * 
+     *
      * @param {String} default container registry key
      * @return {Tine.Tinebase.Model.Container}
      */
@@ -725,9 +743,9 @@ Ext.extend(Tine.widgets.container.TreePanel, Ext.tree.TreePanel, {
         if (! registryKey) {
             registryKey = 'defaultContainer';
         }
-        
+
         var container = Tine[this.appName].registry.get(registryKey);
-        
+
         return this.getSelectedContainer('addGrant', container, true);
     }
 });
index 82697b2..69bd051 100644 (file)
@@ -106,6 +106,7 @@ Tine.widgets.grid.FileUploadGrid = Ext.extend(Ext.grid.GridPanel, {
         this.readOnly = readOnly;
         this.action_add.setDisabled(readOnly);
         this.action_remove.setDisabled(readOnly);
+        this.action_add_from_filemanager.setDisabled(readOnly);
     },
 
     /**
@@ -116,7 +117,7 @@ Tine.widgets.grid.FileUploadGrid = Ext.extend(Ext.grid.GridPanel, {
         
         var dataSize;
         if (fileRecord.html5upload) {
-            dataSize = dataSize = Tine.Tinebase.registry.get('maxPostSize');
+            dataSize = Tine.Tinebase.registry.get('maxPostSize');
         }
         else {
             dataSize = Tine.Tinebase.registry.get('maxFileUploadSize');
@@ -186,8 +187,27 @@ Tine.widgets.grid.FileUploadGrid = Ext.extend(Ext.grid.GridPanel, {
      * @private
      */
     initToolbarAndContextMenu: function () {
-        
+        var me = this;
+
         this.action_add = new Ext.Action(this.getAddAction());
+        this.action_add_from_filemanager = new Ext.Action({
+            text: String.format(i18n._('Add {0} from Filemanager'), this.i18nFileString),
+            iconCls: 'action_add',
+            scope: this,
+            handler: function () {
+                var filePickerDialog = new Tine.Filemanager.FilePickerDialog({
+                    title: this.app.i18n._('Select a file'),
+                    singleSelect: true,
+                    constraint: 'file'
+                });
+
+                filePickerDialog.openWindow();
+
+                filePickerDialog.on('selected', function(node) {
+                    me.onFileSelectFromFilemanager.call(me, node);
+                });
+            }
+        });
 
         this.action_remove = new Ext.Action({
             text: String.format(i18n._('Remove {0}'), this.i18nFileString),
@@ -215,6 +235,7 @@ Tine.widgets.grid.FileUploadGrid = Ext.extend(Ext.grid.GridPanel, {
         
         this.tbar = [
             this.action_add,
+            this.action_add_from_filemanager,
             this.action_remove
         ];
         
@@ -437,9 +458,32 @@ Tine.widgets.grid.FileUploadGrid = Ext.extend(Ext.grid.GridPanel, {
         }, this);
         
     },
+
+    /**
+     * Add one or more files from filemanager
+     *
+     * @param nodes
+     */
+    onFileSelectFromFilemanager: function (nodes) {
+        var me = this;
+
+        Ext.each(nodes, function(node) {
+            var record = new Tine.Filemanager.Model.Node(node);
+
+            if (me.store.find('name', record.get('name')) === -1) {
+                me.store.add(record);
+            } else {
+                Ext.MessageBox.show({
+                    title: i18n._('Failure'),
+                    msg: i18n._('This file is already attached to this record.'),
+                    buttons: Ext.MessageBox.OK,
+                    icon: Ext.MessageBox.ERROR
+                });
+            }
+        });
+    },
     
     onUploadComplete: function(upload, fileRecord) {
-    
         fileRecord.beginEdit();
         fileRecord.set('status', 'complete');
         fileRecord.set('progress', 100);
index a744e02..191c705 100644 (file)
@@ -138,6 +138,10 @@ Ext.extend(Tine.widgets.grid.GridPanel, Ext.Panel, {
      * specialised strings for delete action button
      */
     i18nDeleteActionText: null,
+    /**
+     * Tree panel referenced to this gridpanel
+     */
+    treePanel: null,
 
     /**
      * if this resides in a editDialog, this property holds it
@@ -345,6 +349,8 @@ Ext.extend(Tine.widgets.grid.GridPanel, Ext.Panel, {
     border: false,
     stateful: true,
 
+    stateIdPrefix: null,
+
     /**
      * Makes the grid readonly, this means, no dialogs, no actions, nothing else than selection, no dbclick
      */
@@ -408,7 +414,9 @@ Ext.extend(Tine.widgets.grid.GridPanel, Ext.Panel, {
             }, this);
         }
 
-        this.on('resize', this.onContentResize, this, {buffer: 100});
+        if (this.detailsPanel) {
+            this.on('resize', this.onContentResize, this, {buffer: 100});
+        }
 
         Tine.widgets.grid.GridPanel.superclass.initComponent.call(this);
     },
@@ -813,10 +821,11 @@ Ext.extend(Tine.widgets.grid.GridPanel, Ext.Panel, {
      * @param {Button} btn
      */
     onImport: function(btn) {
+        var treePanel = this.treePanel || this.app.getMainScreen().getWestPanel().getContainerTreePanel();
         var popupWindow = Tine.widgets.dialog.ImportDialog.openWindow({
             appName: this.app.name,
             modelName: this.recordClass.getMeta('modelName'),
-            defaultImportContainer: this.app.getMainScreen().getWestPanel().getContainerTreePanel().getDefaultContainer(this.modelConfig['import'].defaultImportContainerRegistryKey),
+            defaultImportContainer: treePanel.getDefaultContainer(this.modelConfig['import'].defaultImportContainerRegistryKey),
             listeners: {
                 scope: this,
                 'finish': function() {
@@ -1220,12 +1229,13 @@ Ext.extend(Tine.widgets.grid.GridPanel, Ext.Panel, {
      * @param {Button} btn
      */
     onImport: function(btn) {
+        var treePanel = this.treePanel || this.app.getMainScreen().getWestPanel().getContainerTreePanel();
         Tine.widgets.dialog.ImportDialog.openWindow({
             appName: this.app.appName,
             modelName: this.recordClass.getMeta('modelName'),
             defaultImportContainer: Ext.isFunction(this.getDefaultContainer)
                 ? this.getDefaultContainer()
-                : this.app.getMainScreen().getWestPanel().getContainerTreePanel().getDefaultContainer(),
+                : treePanel.getDefaultContainer(),
             // update grid after import
             listeners: {
                 scope: this,
@@ -1627,7 +1637,7 @@ Ext.extend(Tine.widgets.grid.GridPanel, Ext.Panel, {
                 items: [],
                 plugins: [{
                     ptype: 'ux.itemregistry',
-                    key:   this.app.appName + '-' + this.recordClass.prototype.modelName + '-GridPanel-ContextMenu-New'
+                    key:   this.app.appName + '-' + this.recordClass.prototype.modelName + '-GridPanel-ContextMenu-New' + this.stateIdPrefix
                 }]
             });
 
@@ -1646,7 +1656,7 @@ Ext.extend(Tine.widgets.grid.GridPanel, Ext.Panel, {
                 items: [],
                 plugins: [{
                     ptype: 'ux.itemregistry',
-                    key:   this.app.appName + '-' + this.recordClass.prototype.modelName + '-GridPanel-ContextMenu-Add'
+                    key:   this.app.appName + '-' + this.recordClass.prototype.modelName + '-GridPanel-ContextMenu-Add' + this.stateIdPrefix
                 }]
             });
 
@@ -1664,7 +1674,7 @@ Ext.extend(Tine.widgets.grid.GridPanel, Ext.Panel, {
                 items: items,
                 plugins: [{
                     ptype: 'ux.itemregistry',
-                    key:   this.app.appName + '-' + this.recordClass.prototype.modelName + '-GridPanel-ContextMenu'
+                    key:   this.app.appName + '-' + this.recordClass.prototype.modelName + '-GridPanel-ContextMenu' + this.stateIdPrefix
                 }, {
                     ptype: 'ux.itemregistry',
                     key:   'Tinebase-MainContextMenu'
@@ -1894,7 +1904,6 @@ Ext.extend(Tine.widgets.grid.GridPanel, Ext.Panel, {
      * @param {Ext.EventObject} e
      */
     onRowContextMenu: function(grid, row, e) {
-        console.log(e);
         e.stopEvent();
         var selModel = grid.getSelectionModel();
         if (!selModel.isSelected(row)) {
@@ -1903,7 +1912,12 @@ Ext.extend(Tine.widgets.grid.GridPanel, Ext.Panel, {
             selModel.selectRow(row);
         }
 
-        this.getContextMenu(grid, row, e).showAt(e.getXY());
+        var contextMenu = this.getContextMenu(grid, row, e);
+
+        if (contextMenu) {
+            contextMenu.showAt(e.getXY());
+        }
+
         // reset preview update
         this.updateOnSelectionChange = true;
     },