0012934: new file picker component
authorMichael Spahn <m.spahn@metaways.de>
Thu, 16 Mar 2017 12:57:34 +0000 (13:57 +0100)
committerPhilipp Schüle <p.schuele@metaways.de>
Tue, 4 Apr 2017 17:52:35 +0000 (19:52 +0200)
https://forge.tine20.org/view.php?id=12934

Change-Id: I0daf20f2ce954ffb0555321086ae6b7f0fc79ff6
Reviewed-on: http://gerrit.tine20.com/customers/4357
Reviewed-by: Philipp Schüle <p.schuele@metaways.de>
Tested-by: Philipp Schüle <p.schuele@metaways.de>
tine20/Filemanager/Filemanager.jsb2
tine20/Filemanager/js/FilePicker.js [new file with mode: 0644]
tine20/Filemanager/js/FilePickerDialog.js [new file with mode: 0644]
tine20/Filemanager/js/NodeGridPanel.js
tine20/Tinebase/js/ux/WindowFactory.js
tine20/Tinebase/js/widgets/grid/FilterToolbarQuickFilterPlugin.js
tine20/Tinebase/js/widgets/grid/GridPanel.js

index 97bef63..4924cf3 100644 (file)
         {
           "text": "Filemanager.js",
           "path": "js/"
+        },
+        {
+          "text": "FilePickerDialog.js",
+          "path": "js/"
+        },
+        {
+          "text": "FilePicker.js",
+          "path": "js/"
         }
       ]
     },
diff --git a/tine20/Filemanager/js/FilePicker.js b/tine20/Filemanager/js/FilePicker.js
new file mode 100644 (file)
index 0000000..71a0dbf
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * Tine 2.0
+ *
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Michael Spahn <m.spahn@metaways.de>
+ * @copyright   Copyright (c) 2017 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+Ext.ns('Tine.Filemanager');
+
+/**
+ * FilePicker component.
+ *
+ * Standalone file picker for selecting a node or a folder from filemanager.
+ * The filepicker does require the filemanager to be enabled!
+ *
+ * The filepicker offers two events:
+ *  - nodeSelected
+ *  - invalidNodeSelected
+ *
+ *  @todo: remove border
+ */
+Tine.Filemanager.FilePicker = Ext.extend(Ext.Container, {
+    app: null,
+
+    layout: 'fit',
+
+    treePanel: null,
+    gridPanel: null,
+
+    /**
+     * Selected node
+     */
+    selection: null,
+
+    lastClickedNode: null,
+
+    /**
+     * 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 () {
+        var model = Tine.Filemanager.Model.Node;
+        this.app = Tine.Tinebase.appMgr.get(model.getMeta('appName'));
+
+        this.treePanel = this.getTreePanel();
+        this.gridPanel = this.getGridPanel();
+
+        this.addEvents(
+            /**
+             * Fires when a file was selected which fulfills all constraints
+             *
+             * @param nodeData selected node data
+             */
+            'nodeSelected',
+            /**
+             * Fires if a node is selected which does not fulfill the provided constraints
+             */
+            'invalidNodeSelected'
+        );
+
+        this.items = [{
+            layout: 'border',
+            border: false,
+            frame: false,
+            items: [{
+                layout: 'fit',
+                region: 'west',
+                frame: false,
+                width: 200,
+                border: false,
+                split: true,
+                collapsible: true,
+                collapseMode: 'mini',
+                items: [
+                    this.treePanel
+                ]
+            }, {
+                layout: 'fit',
+                split: true,
+                frame: false,
+                region: 'center',
+                width: 300,
+                items: [
+                    this.gridPanel
+                ]
+            }]
+        }];
+
+        Tine.Filemanager.FilePicker.superclass.initComponent.call(this);
+    },
+
+    /**
+     * Updates selected element and triggers an event
+     */
+    updateSelection: function (node) {
+        // If selection doesn't fullfil constraint, we don't throw a selectionChange event
+        if (!this.checkConstraint(node)) {
+            this.fireEvent('invalidNodeSelected');
+            return false;
+        }
+
+        this.selection = node.data || node;
+
+        this.fireEvent('nodeSelected', this, this.selection);
+    },
+
+    /**
+     * @returns {Tine.Filemanager.NodeTreePanel}
+     */
+    getTreePanel: function () {
+        if (this.treePanel) {
+            return this.treePanel;
+        }
+
+        var me = this;
+        var treePanel = new Tine.Filemanager.NodeTreePanel({
+            height: 200,
+            width: 200,
+            readOnly: true,
+            filterMode: 'filterToolbar'
+        });
+
+        treePanel.getSelectionModel().on('selectionchange', function (selectionModel, treeNode) {
+            var treeNode = selectionModel.getSelectedNode();
+            me.updateSelection(treeNode.attributes);
+        });
+
+        return treePanel;
+    },
+
+    /**
+     * @returns {*}
+     */
+    getGridPanel: function () {
+        if (this.gridPanel) {
+            return this.gridPanel;
+        }
+
+        var me = this;
+
+        var gridPanel = new Tine.Filemanager.NodeGridPanel({
+            app: me.app,
+            height: 200,
+            width: 200,
+            readOnly: true,
+            hasQuickSearchFilterToolbarPlugin: false,
+            stateIdPrefix: '-FilePicker',
+            plugins: [this.getTreePanel().getFilterPlugin()]
+        });
+        gridPanel.getGrid().reconfigure(gridPanel.getStore(), this.getColumnModel());
+        gridPanel.getGrid().getSelectionModel().on('rowselect', function (selModel) {
+            var record = selModel.getSelected();
+            me.updateSelection(record.data);
+        });
+
+        return gridPanel;
+    },
+
+    /**
+     * Check if selection fits current constraint
+     */
+    checkConstraint: function (node) {
+        // Minimum information to proceed here
+        if (!node.path || !node.id) {
+            return false;
+        }
+
+        // If no constraints apply, skip here
+        if (this.constraint === null) {
+            return true;
+        }
+
+        switch (this.constraint) {
+            case 'file':
+                if (node.type !== 'file') {
+                    return false;
+                }
+                break;
+            case 'folder':
+                if (node.type !== 'folder') {
+                    return false;
+                }
+                break;
+        }
+
+        return true;
+    },
+
+    /**
+     * Customized column model for the grid
+     * @returns {*}
+     */
+    getColumnModel: function () {
+        var columns = [{
+            id: 'name',
+            header: this.app.i18n._("Name"),
+            width: 70,
+            sortable: true,
+            dataIndex: 'name',
+            renderer: Ext.ux.PercentRendererWithName
+        }, {
+            id: 'size',
+            header: this.app.i18n._("Size"),
+            width: 40,
+            sortable: true,
+            dataIndex: 'size',
+            renderer: Tine.Tinebase.common.byteRenderer.createDelegate(this, [2, true], 3)
+        }, {
+            id: 'contenttype',
+            header: this.app.i18n._("Contenttype"),
+            width: 50,
+            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");
+                }
+                else {
+                    return value;
+                }
+            }
+        }];
+
+        return new Ext.grid.ColumnModel({
+            defaults: {
+                sortable: true,
+                resizable: true
+            },
+            columns: columns
+        });
+    }
+});
diff --git a/tine20/Filemanager/js/FilePickerDialog.js b/tine20/Filemanager/js/FilePickerDialog.js
new file mode 100644 (file)
index 0000000..f38a63d
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Tine 2.0
+ *
+ * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
+ * @author      Michael Spahn <m.spahn@metaways.de>
+ * @copyright   Copyright (c) 2017 Metaways Infosystems GmbH (http://www.metaways.de)
+ */
+Ext.ns('Tine.Filemanager');
+
+/**
+ * File picker dialog
+ *
+ * @namespace   Tine.Filemanager
+ * @class       Tine.Filemanager.FilePickerDialog
+ * @extends     Ext.Panel
+ * @constructor
+ * @param       {Object} config The configuration options.
+ */
+Tine.Filemanager.FilePickerDialog = Ext.extend(Ext.Panel, {
+    layout: 'fit',
+    border: false,
+    okAction: null,
+
+    node: null,
+
+    windowNamePrefix: 'test',
+
+    initComponent: function () {
+        this.addEvents(
+            /**
+             * If the dialog will close and an valid node was selected
+             * @param node
+             */
+            'selected'
+        );
+
+        this.items = [{
+            layout: 'fit',
+            items: [
+                this.getFilePicker()
+            ]
+        }];
+
+        var me = this;
+        this.okAction = new Ext.Action({
+            disabled: true,
+            text: 'Ok',
+            iconCls: 'action_saveAndClose',
+            minWidth: 70,
+            handler: this.onOk.createDelegate(me),
+            scope: this
+        });
+
+        this.bbar = [
+            this.okAction
+        ];
+
+        Tine.Filemanager.FilePickerDialog.superclass.initComponent.call(this);
+    },
+
+    /**
+     * button handler
+     */
+    onOk: function () {
+        this.fireEvent('selected', this.node);
+        this.window.close();
+    },
+
+    /**
+     * Create a new filepicker and register listener
+     * @returns {*}
+     */
+    getFilePicker: function () {
+        var picker = new Tine.Filemanager.FilePicker({
+            constraint: 'file'
+        });
+
+        picker.on('nodeSelected', this.onNodeSelected.createDelegate(this));
+        picker.on('invalidNodeSelected', this.onInvalidNodeSelected.createDelegate(this));
+
+        return picker;
+    },
+
+    /**
+     * If a node was selected
+     * @param node
+     */
+    onNodeSelected: function (node) {
+        this.node = node;
+        this.okAction.setDisabled(false);
+    },
+
+    /**
+     * If an invalid node was selected
+     */
+    onInvalidNodeSelected: function () {
+        this.okAction.setDisabled(true);
+    }
+});
+
+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 2ef97ec..ea2676e 100644 (file)
@@ -63,6 +63,11 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
     currentFolderNode : '/',
     
     /**
+     * Prevent download and edit of nodes/files and so on. Only allow the selection of items
+     */
+    selectOnly: false,
+
+    /**
      * inits this cmp
      * @private
      */
@@ -75,34 +80,45 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
             {field: 'query', operator: 'contains', value: ''},
             {field: 'path', operator: 'equals', value: '/'}
         ];
-        
-        this.filterToolbar = this.filterToolbar || this.getFilterToolbar();
-        this.filterToolbar.getQuickFilterPlugin().criteriaIgnores.push({field: 'path'});
-        
+
         this.plugins = this.plugins || [];
+
+        this.filterToolbar = this.filterToolbar || this.getFilterToolbar();
+
+        if (this.hasQuickSearchFilterToolbarPlugin) {
+            this.filterToolbar.getQuickFilterPlugin().criteriaIgnores.push({field: 'path'});
+        }
+
         this.plugins.push(this.filterToolbar);
-        this.plugins.push({
-            ptype: 'ux.browseplugin',
-            multiple: true,
-            scope: this,
-            enableFileDialog: false,
-            handler: this.onFilesSelect
-        });
-        
+
+        if (!this.readOnly) {
+            this.plugins.push({
+                ptype: 'ux.browseplugin',
+                multiple: true,
+                scope: this,
+                enableFileDialog: false,
+                handler: this.onFilesSelect
+            });
+        }
+
         Tine.Filemanager.NodeGridPanel.superclass.initComponent.call(this);
         this.getStore().on('load', this.onLoad);
         Tine.Tinebase.uploadManager.on('update', this.onUpdate);
     },
-    
-    initFilterPanel: function() {},
-    
+
     /**
      * after render handler
      */
-    afterRender: function() {
+    afterRender: function () {
         Tine.Filemanager.NodeGridPanel.superclass.afterRender.call(this);
-        this.action_upload.setDisabled(true);
-        this.initDropTarget();
+
+        if (this.action_upload) {
+            this.action_upload.setDisabled(true);
+        }
+
+        if (!this.readOnly) {
+            this.initDropTarget();
+        }
         this.currentFolderNode = this.app.getMainScreen().getWestPanel().getContainerTreePanel().getRootNode();
     },
     
@@ -251,7 +267,8 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
     getFilterToolbar: function(config) {
         config = config || {};
         var plugins = [];
-        if (! Ext.isDefined(this.hasQuickSearchFilterToolbarPlugin) || this.hasQuickSearchFilterToolbarPlugin) {
+
+        if (this.hasQuickSearchFilterToolbarPlugin) {
             this.quickSearchFilterToolbarPlugin = new Tine.widgets.grid.FilterToolbarQuickFilterPlugin();
             plugins.push(this.quickSearchFilterToolbarPlugin);
         }
@@ -421,18 +438,20 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
         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) {
-            
-            // TODO: add mail-button
-            Ext.MessageBox.show({
-                title: selections[0].data.type == 'folder' ? this.app.i18n._('Folder has been published successfully') : this.app.i18n._('File has been published successfully'), 
-                msg: String.format(this.app.i18n._("Url: {0}") + '<br />' + this.app.i18n._("Valid Until: {1}"), record.get('url'), record.get('expiry_time')), 
-                minWidth: 900,
-                buttons: Ext.Msg.OK,
-                icon: Ext.MessageBox.INFO,
-                scope: this
-            });
-        }, failure: Tine.Tinebase.ExceptionHandler.handleRequestException, scope: this});
+        Tine.Filemanager.downloadLinkRecordBackend.saveRecord(record, {
+            success: function (record) {
+
+                // TODO: add mail-button
+                Ext.MessageBox.show({
+                    title: selections[0].data.type == 'folder' ? this.app.i18n._('Folder has been published successfully') : this.app.i18n._('File has been published successfully'),
+                    msg: String.format(this.app.i18n._("Url: {0}") + '<br />' + this.app.i18n._("Valid Until: {1}"), record.get('url'), record.get('expiry_time')),
+                    minWidth: 900,
+                    buttons: Ext.Msg.OK,
+                    icon: Ext.MessageBox.INFO,
+                    scope: this
+                });
+            }, failure: Tine.Tinebase.ExceptionHandler.handleRequestException, scope: this
+        });
     },
     
     /**
@@ -650,7 +669,7 @@ Tine.Filemanager.NodeGridPanel = Ext.extend(Tine.widgets.grid.GridPanel, {
         var app = this.app;
         var rowRecord = grid.getStore().getAt(row);
         
-        if(rowRecord.data.type == 'file') {
+        if(rowRecord.data.type == 'file' && !this.readOnly) {
             Tine.Filemanager.downloadFile(rowRecord);
         }
         
index 7febda8..fef684a 100644 (file)
@@ -89,7 +89,7 @@ Ext.ux.WindowFactory.prototype = {
             activeItem: 0,
             isWindowMainCardPanel: true,
             items: [this.getCenterPanel(c)]
-        }
+        };
         
         // we can only handle one window yet
         c.modal = true;
@@ -109,7 +109,7 @@ Ext.ux.WindowFactory.prototype = {
     /**
      * constructs window items from config properties
      */
-    getCenterPanel: function (config) {
+     getCenterPanel: function (config) {
         var items;
         
         // (re-) create applicationstarter apps on BrowserWindows
@@ -194,18 +194,15 @@ Ext.ux.WindowFactory.prototype = {
         config.title = config.contentPanelConstructorConfig.title;
             delete config.contentPanelConstructorConfig.title;
         }
-        
-        switch (windowType) 
-        {
-        case 'Browser' :
-            return this.getBrowserWindow(config);
-        case 'Ext' :
-            return this.getExtWindow(config);
-        case 'Air' :
-            return this.getAirWindow(config);
-        default :
-            console.error('No such windowType: ' + this.windowType);
-            break;
+
+        switch (windowType) {
+            case 'Browser' :
+                return this.getBrowserWindow(config);
+            case 'Ext' :
+                return this.getExtWindow(config);
+            default :
+                console.error('No such windowType: ' + this.windowType);
+                break;
         }
     }
 };
index 91eab9a..f2e8634 100644 (file)
@@ -372,8 +372,11 @@ Tine.widgets.grid.FilterToolbarQuickFilterPlugin.prototype = {
                    '<br />' +
                    '&nbsp;' + criterias.join(', ');
         }
-            
-        this.criteriaText.update(text);
+
+        // If toolbar is hidden, there is no component to update!
+        if(this.criteriaText.getContentTarget()) {
+            this.criteriaText.update(text);
+        }
     },
     
     /**
index c2ae0cb..a744e02 100644 (file)
@@ -33,11 +33,17 @@ Tine.widgets.grid.GridPanel = function(config) {
     this.defaultPaging = this.defaultPaging || {
         start: 0,
         limit: 50
-    };      
+    };
+
+    var stateIdPrefix = '';
+
+    if (config.hasOwnProperty('stateIdPrefix')) {
+        stateIdPrefix = config.stateIdPrefix;
+    }
 
     // autogenerate stateId
     if (this.stateful !== false && ! this.stateId) {
-        this.stateId = this.recordClass.getMeta('appName') + '-' + this.recordClass.getMeta('recordName') + '-GridPanel';
+        this.stateId = this.recordClass.getMeta('appName') + '-' + this.recordClass.getMeta('recordName') + '-GridPanel' + stateIdPrefix;
     }
 
     if (this.stateId && Ext.isTouchDevice) {
@@ -340,6 +346,11 @@ Ext.extend(Tine.widgets.grid.GridPanel, Ext.Panel, {
     stateful: true,
 
     /**
+     * Makes the grid readonly, this means, no dialogs, no actions, nothing else than selection, no dbclick
+     */
+    readOnly: false,
+
+    /**
      * extend standard initComponent chain
      * 
      * @private
@@ -380,7 +391,10 @@ Ext.extend(Tine.widgets.grid.GridPanel, Ext.Panel, {
             containerProperty: this.recordClass.getMeta('containerProperty'), 
             evalGrants: this.evalGrants
         });
-        this.initActions();
+
+        if (!this.readOnly) {
+            this.initActions();
+        }
 
         this.initLayout();