[pve-devel] [PATCH manager 01/10] use a treelist instead of tabs in configpanel

Dominik Csapak d.csapak at proxmox.com
Mon Aug 22 17:13:33 CEST 2016


this patch changes configpanel class,
so that instead of creating a tabpanel,
we now are a card panel which uses a treelist
to choose the active card

this changes how the panel looks:
instead of having countless tabs on the top
we now can have a nice tree structure on the left

how the items will be interpreted by the tree
is commented in www/manager6/panel/ConfigPanel.js

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 www/css/ext6-pve.css              |  24 ++++
 www/manager6/Workspace.js         |   1 -
 www/manager6/panel/ConfigPanel.js | 294 ++++++++++++++++++++++++++++----------
 3 files changed, 239 insertions(+), 80 deletions(-)

diff --git a/www/css/ext6-pve.css b/www/css/ext6-pve.css
index 8ab383c..ddef394 100644
--- a/www/css/ext6-pve.css
+++ b/www/css/ext6-pve.css
@@ -392,3 +392,27 @@ div.right-aligned {
 .x-progress.warning .x-progress-bar{
     background-color: #FFCC00;
 }
+
+.x-treelist-nav {
+    background-color: #f5f5f5;
+}
+
+.x-treelist-row {
+    margin-top: 5px;
+}
+
+.x-treelist-item-icon {
+    color: #000;
+    margin-left: 2px;
+}
+
+.x-treelist-item-text {
+    color: #000;
+    padding-left: 5px;
+    padding-right: 5px;
+}
+
+.x-treelist-row-over > * > .x-treelist-item-icon,
+.x-treelist-row-over > * > .x-treelist-item-text{
+    color: #000;
+}
diff --git a/www/manager6/Workspace.js b/www/manager6/Workspace.js
index c23ea57..83a2c62 100644
--- a/www/manager6/Workspace.js
+++ b/www/manager6/Workspace.js
@@ -306,7 +306,6 @@ Ext.define('PVE.StdWorkspace', {
 			    comp = {
 				xtype: tlckup[n.data.type || 'root'] || 
 				    'pvePanelConfig',
-				layout: { type: 'fit' },
 				showSearch: (n.data.id === 'root') ||
 				    Ext.isDefined(n.data.groupbyid),
 				pveSelNode: n,
diff --git a/www/manager6/panel/ConfigPanel.js b/www/manager6/panel/ConfigPanel.js
index 51bdf1f..a5279d8 100644
--- a/www/manager6/panel/ConfigPanel.js
+++ b/www/manager6/panel/ConfigPanel.js
@@ -1,5 +1,43 @@
 /*
  * Base class for all the multitab config panels
+ *
+ * How to use this:
+ *
+ * You create a subclass of this, and then define your wanted tabs
+ * as items like this:
+ *
+ * items: [{
+ *  title: "myTitle",
+ *  xytpe: "somextype",
+ *  iconCls: 'fa fa-icon',
+ *  groups: ['somegroup'],
+ *  expandedOnInit: true,
+ *  itemId: 'someId'
+ * }]
+ *
+ * this has to be in the declarative syntax, else we
+ * cannot save them for later
+ * (so no Ext.create or Ext.apply of an item in the subclass)
+ *
+ * the groups array expects the itemids of the items
+ * which are the parents, which have to come before they
+ * are used
+ *
+ * if you want following the tree:
+ *
+ * Option1
+ * Option2
+ *   -> SubOption1
+ *	-> SubSubOption1
+ *
+ * the suboption1 group array has to look like this:
+ * groups: ['itemid-of-option2']
+ *
+ * and of subsuboption1:
+ * groups: ['itemid-of-option2', 'itemid-of-suboption1']
+ *
+ * setting the expandedOnInit determines if the item/group is expanded
+ * initially (false by default)
  */
 Ext.define('PVE.panel.Config', {
     extend: 'Ext.panel.Panel',
@@ -8,42 +46,117 @@ Ext.define('PVE.panel.Config', {
     showSearch: true, // add a ressource grid with a search button as first tab
     viewFilter: undefined, // a filter to pass to that ressource grid
 
+    dockedItems: [{
+	// this is needed for the overflow handler
+	xtype: 'toolbar',
+	overflowHandler: 'scroller',
+	dock: 'left',
+	style: {
+	    backgroundColor: '#f5f5f5',
+	    padding: 0,
+	    margin: 0
+	},
+	items: {
+	    xtype: 'treelist',
+	    itemId: 'menu',
+	    ui: 'nav',
+	    expanderOnly: true,
+	    expanderFirst: false,
+	    animation: false,
+	    singleExpand: false,
+	    listeners: {
+		selectionchange: function(treeList, selection) {
+		    var me = this.up('panel');
+		    me.suspendLayout = true;
+		    me.activateCard(selection.data.id);
+		    me.suspendLayout = false;
+		    me.updateLayout();
+		},
+		itemclick: function(treelist, info) {
+		    var olditem = treelist.getSelection();
+		    var newitem = info.node;
+
+		    // when clicking on the expand arrow,
+		    // we dont select items, but still want
+		    // the original behaviour
+		    if (info.select === false) {
+			return;
+		    }
+
+		    // if you click on a different item which is open,
+		    // leave it open
+		    // else toggle the clicked item
+		    if (olditem.data.id !== newitem.data.id &&
+			newitem.data.expanded === true) {
+			info.toggle = false;
+		    } else {
+			info.toggle = true;
+		    }
+		}
+	    }
+	}
+    },
+    {
+	xtype: 'toolbar',
+	itemId: 'toolbar',
+	dock: 'top',
+	height: 36,
+	overflowHandler: 'menu'
+    }],
+
+    firstItem: '',
+    layout: 'card',
+    border: 0,
+
+    activateCard: function(cardid) {
+	var me = this;
+	if (me.savedItems[cardid]) {
+	    var curcard = me.getLayout().getActiveItem();
+	    var newcard = me.add(me.savedItems[cardid]);
+	    if (curcard) {
+		me.setActiveItem(cardid);
+		me.remove(curcard, true);
+
+		// trigger state change
+
+		var ncard = cardid;
+		// Note: '' is alias for first tab.
+		// First tab can be 'search' or something else
+		if (cardid === me.firstItem) {
+		    ncard = '';
+		}
+		if (me.hstateid) {
+		   me.sp.set(me.hstateid, { value: ncard });
+		}
+	    }
+	}
+    },
+
     initComponent: function() {
         var me = this;
 
 	var stateid = me.hstateid;
 
-	var sp = Ext.state.Manager.getProvider();
+	me.sp = Ext.state.Manager.getProvider();
 
 	var activeTab; // leaving this undefined means items[0] will be the default tab
 
-	var hsregex =  /^([^\-\s]+)(-\S+)?$/;
-
 	if (stateid) {
-	    var state = sp.get(stateid);
+	    var state = me.sp.get(stateid);
 	    if (state && state.value) {
-		var res = hsregex.exec(state.value);
-		if (res && res[1] && Ext.isArray(me.items)) {
-		    me.items.forEach(function(item) {
-			if (item.itemId === res[1]) {
-			    activeTab = res[1];
-			}
-		    });
-		} else if (res && res[1] && me.items && me.items.itemId === res[1]) {
-		    activeTab = res[1];
-		}
+		// if this tab does not exists, it chooses the first
+		activeTab = state.value;
 	    }
 	}
 
-	var items = me.items || [];
-	me.items = undefined;
+	// get title
+	var title = me.title || me.pveSelNode.data.text;
+	me.title = undefined;
 
+	// create toolbar
 	var tbar = me.tbar || [];
 	me.tbar = undefined;
 
-	var title = me.title || me.pveSelNode.data.text;
-	me.title = undefined;
-
 	tbar.unshift('->');
 	tbar.unshift({
 	    xtype: 'tbtext',
@@ -51,92 +164,115 @@ Ext.define('PVE.panel.Config', {
 	    baseCls: 'x-panel-header-text'
 	});
 
+	me.dockedItems[1].items = tbar;
 
+	// include search tab
+	me.items = me.items || [];
 	if (me.showSearch) {
-	    items.unshift({
+	    me.items.unshift({
 		itemId: 'search',
 		title: gettext('Search'),
-		layout: { type:'fit' },
-		plugins: [{
-		    ptype: 'lazyitems',
-		    items: [{
-			xtype: 'pveResourceGrid',
-			pveSelNode: me.pveSelNode
-		    }]
-		}]
+		iconCls: 'fa fa-search',
+		xtype: 'pveResourceGrid',
+		pveSelNode: me.pveSelNode
 	    });
 	}
 
-	var toolbar = Ext.create('Ext.toolbar.Toolbar', {
-	    items: tbar,
-	    border: false,
-	    height: 36
-	});
+	me.savedItems = {};
+	/*jslint confusion:true*/
+	if (me.items[0]) {
+	    me.firstItem = me.items[0].itemId;
+	}
+	/*jslint confusion:false*/
 
-	var tab = Ext.create('Ext.tab.Panel', {
-	    flex: 1,
-	    border: true,
-	    activeTab: activeTab,
-	    defaults: Ext.apply(me.defaults ||  {}, {
-		pveSelNode: me.pveSelNode,
-		viewFilter: me.viewFilter,
-		workspace: me.workspace,
-		border: false
-	    }),
-	    items: items,
-	    listeners: {
-		tabchange: function(tp, newcard, oldcard) {
-		    var ntab = newcard.itemId;
+	me.store = Ext.create('Ext.data.TreeStore', {
+	    root: {
+		expanded: true
+	    }
+	});
+	var root = me.store.getRoot();
+	me.items.forEach(function(item){
+	    var treeitem = Ext.create('Ext.data.TreeModel',{
+		id: item.itemId,
+		text: item.title,
+		iconCls: item.iconCls,
+		leaf: true,
+		expanded: item.expandedOnInit
+	    });
+	    item.header = false;
+	    if (me.savedItems[item.itemId] !== undefined) {
+		throw "itemId already exists, please use another";
+	    }
+	    me.savedItems[item.itemId] = item;
 
-		    // Note: '' is alias for first tab.
-		    // First tab can be 'search' or something else
-		    if (newcard.itemId === items[0].itemId) {
-			ntab = '';
-		    }
-		    if (stateid) {
-			if (newcard.phstateid) {
-			    sp.set(newcard.phstateid, newcard.getHState());
-			} else {
-			    sp.set(stateid, { value: ntab });
-			}
-		    }
+	    var group;
+	    var curnode = root;
 
-		    // if we have a tabpanel which we declared lazy (with ptype: lazyitems)
-		    // then we have the actual item in items.items[0]
-		    // and there we need to fire the event hide
-		    // because some tabs use this event (which is not fired in this case)
-		    if (oldcard.plugins && oldcard.plugins[0] && oldcard.plugins[0].ptype == 'lazyitems') {
-			oldcard.items.items[0].fireEvent('hide');
-		    }
+	    // get/create the group items
+	    while (Ext.isArray(item.groups) && item.groups.length > 0) {
+		group = item.groups.shift();
 
-		    // same for activating
-		    if (newcard.plugins && newcard.plugins[0] && newcard.plugins[0].ptype == 'lazyitems') {
-			newcard.items.items[0].fireEvent('activate');
-		    }
+		var child = curnode.findChild('id', group);
+		if (child === null) {
+		    // did not find the group item
+		    // so add it where we are
+		    break;
 		}
+		curnode = child;
+	    }
+
+	    // insert the item
+
+	    // lets see if it already exists
+	    var node = curnode.findChild('id', item.itemId);
+
+	    if (node === null) {
+		curnode.appendChild(treeitem);
+	    } else {
+		// should not happen!
+		throw "id already exists";
 	    }
 	});
 
-	Ext.apply(me, {
-	    layout: { type: 'vbox', align: 'stretch' },
-	    items: [ toolbar, tab]
+	delete me.items;
+	me.defaults = me.defaults || {};
+	Ext.apply(me.defaults, {
+	    pveSelNode: me.pveSelNode,
+	    viewFilter: me.viewFilter,
+	    workspace: me.workspace,
+	    border: 0
 	});
 
 	me.callParent();
 
+	var menu = me.down('#menu');
+	var selection = root.findChild('id', activeTab, true) || root.firstChild;
+	var node = selection;
+	while (node !== root) {
+	    node.expand();
+	    node = node.parentNode;
+	}
+	menu.setStore(me.store);
+	menu.setSelection(selection);
+
+	// on a state change,
+	// select the new item
 	var statechange = function(sp, key, state) {
+	    // it the state change is for this panel
 	    if (stateid && (key === stateid) && state) {
-		var atab = tab.getActiveTab().itemId;
-		var res = hsregex.exec(state.value);
-		var ntab = (res && res[1]) ? res[1] : items[0].itemId;
-		if (ntab && (atab != ntab)) {
-		    tab.setActiveTab(ntab);
+		// get active item
+		var acard = me.getLayout().getActiveItem().itemId;
+		// get the itemid of the new value
+		var ncard = state.value || me.firstItem;
+		if (ncard && (acard != ncard)) {
+		    // select the chosen item
+		    menu.setSelection(root.findChild('id', ncard, true) || root.firstChild);
 		}
 	    }
 	};
 
 	if (stateid) {
-	    me.mon(sp, 'statechange', statechange);
+	    me.mon(me.sp, 'statechange', statechange);
 	}
     }
 });
-- 
2.1.4





More information about the pve-devel mailing list