/** * @author Nicolas Ferrero * @class Ext.ux.GroupTabPanel * @extends Ext.Container * A TabPanel with grouping support. */ Ext.define('Ext.ux.GroupTabPanel', { extend: 'Ext.Container', alias: 'widget.grouptabpanel', requires:[ 'Ext.data.*', 'Ext.tree.*', 'Ext.layout.*' ], baseCls : Ext.baseCSSPrefix + 'grouptabpanel', initComponent: function(config) { var me = this; Ext.apply(me, config); // Processes items to create the TreeStore and also set up // "this.cards" containing the actual card items. me.store = me.createTreeStore(); me.layout = { type: 'hbox', align: 'stretch' }; me.defaults = { border: false }; me.items = [{ xtype: 'treepanel', cls: 'x-tree-panel x-grouptabbar', width: 150, rootVisible: false, store: me.store, hideHeaders: true, animate: false, processEvent: Ext.emptyFn, viewConfig: { overItemCls: '', getRowClass: me.getRowClass, itemSelector: 'div.' + Ext.baseCSSPrefix + 'grouptab-row', cellSelector: 'div.' + Ext.baseCSSPrefix + 'grouptab', getTableChunker: function() { return Ext.ux.GroupTreeChunker; }, onHeaderResize: function(header, w, suppressFocus) { var me = this, el = me.el; if (el) { el.select('div.' + Ext.baseCSSPrefix + 'grid-table-resizer').setWidth(me.headerCt.getFullWidth()); if (!me.ignoreTemplate) { me.setNewTemplate(); } if (!suppressFocus) { me.el.focus(); } me.forceReflow(); } } }, columns: [{ xtype: 'treecolumn', sortable: false, dataIndex: 'text', flex: 1, renderer: function (value, cell, node, idx1, idx2, store, tree) { var cls = ''; if (node.parentNode && node.parentNode.parentNode === null) { cls += ' x-grouptab-first'; if (node.previousSibling) { cls += ' x-grouptab-prev'; } if (!node.get('expanded') || node.firstChild == null) { cls += ' x-grouptab-last'; } } else if (node.nextSibling === null) { cls += ' x-grouptab-last'; } else { cls += ' x-grouptab-center'; } if (node.data.activeTab) { cls += ' x-active-tab'; } cell.tdCls= 'x-grouptab'+ cls; return value; } }] },{ xtype: 'container', flex: 1, layout: 'card', activeItem: me.mainItem, baseCls: Ext.baseCSSPrefix + 'grouptabcontainer', items: me.cards }]; me.addEvents( /** * @event beforetabchange * Fires before a tab change (activated by {@link #setActiveTab}). Return false in any listener to cancel * the tabchange * @param {Ext.ux.GroupTabPanel} grouptabPanel The GroupTabPanel * @param {Ext.Component} newCard The card that is about to be activated * @param {Ext.Component} oldCard The card that is currently active */ 'beforetabchange', /** * @event tabchange * Fires when a new tab has been activated (activated by {@link #setActiveTab}). * @param {Ext.ux.GroupTabPanel} grouptabPanel The GroupTabPanel * @param {Ext.Component} newCard The newly activated item * @param {Ext.Component} oldCard The previously active item */ 'tabchange', /** * @event beforegroupchange * Fires before a group change (activated by {@link #setActiveGroup}). Return false in any listener to cancel * the groupchange * @param {Ext.ux.GroupTabPanel} grouptabPanel The GroupTabPanel * @param {Ext.Component} newGroup The root group card that is about to be activated * @param {Ext.Component} oldGroup The root group card that is currently active */ 'beforegroupchange', /** * @event groupchange * Fires when a new group has been activated (activated by {@link #setActiveGroup}). * @param {Ext.ux.GroupTabPanel} grouptabPanel The GroupTabPanel * @param {Ext.Component} newGroup The newly activated root group item * @param {Ext.Component} oldGroup The previously active root group item */ 'groupchange' ); me.callParent(arguments); me.setActiveTab(me.activeTab); me.setActiveGroup(me.activeGroup); me.mon(me.down('treepanel').getSelectionModel(), 'select', me.onNodeSelect, me); }, getRowClass: function(node, rowIndex, rowParams, store) { var cls = ''; if (node.data.activeGroup) { cls += ' x-active-group'; } return cls; }, /** * @private * Node selection listener. */ onNodeSelect: function (selModel, node) { var me = this, currentNode = me.store.getRootNode(), parent; if (node.parentNode && node.parentNode.parentNode === null) { parent = node; } else { parent = node.parentNode; } if (me.setActiveGroup(parent.get('id')) === false || me.setActiveTab(node.get('id')) === false) { return false; } while (currentNode) { currentNode.set('activeTab', false); currentNode.set('activeGroup', false); currentNode = currentNode.firstChild || currentNode.nextSibling || currentNode.parentNode.nextSibling; } parent.set('activeGroup', true); parent.eachChild(function(child) { child.set('activeGroup', true); }); node.set('activeTab', true); selModel.view.refresh(); }, /** * Makes the given component active (makes it the visible card in the GroupTabPanel's CardLayout) * @param {Ext.Component} cmp The component to make active */ setActiveTab: function(cmp) { var me = this, newTab = cmp, oldTab; if(Ext.isString(cmp)) { newTab = Ext.getCmp(newTab); } if (newTab === me.activeTab) { return false; } oldTab = me.activeTab; if (me.fireEvent('beforetabchange', me, newTab, oldTab) !== false) { me.activeTab = newTab; if (me.rendered) { me.down('container[baseCls=' + Ext.baseCSSPrefix + 'grouptabcontainer' + ']').getLayout().setActiveItem(newTab); } me.fireEvent('tabchange', me, newTab, oldTab); } return true; }, /** * Makes the given group active * @param {Ext.Component} cmp The root component to make active. */ setActiveGroup: function(cmp) { var me = this, newGroup = cmp, oldGroup; if(Ext.isString(cmp)) { newGroup = Ext.getCmp(newGroup); } if (newGroup === me.activeGroup) { return true; } oldGroup = me.activeGroup; if (me.fireEvent('beforegroupchange', me, newGroup, oldGroup) !== false) { me.activeGroup = newGroup; me.fireEvent('groupchange', me, newGroup, oldGroup); } else { return false; } return true; }, /** * @private * Creates the TreeStore used by the GroupTabBar. */ createTreeStore: function() { var me = this, groups = me.prepareItems(me.items), data = { text: '.', children: [] }, cards = me.cards = []; me.activeGroup = me.activeGroup || 0; Ext.each(groups, function(groupItem, idx) { var leafItems = groupItem.items.items, rootItem = (leafItems[groupItem.mainItem] || leafItems[0]), groupRoot = { children: [] }; // Create the root node of the group groupRoot.id = rootItem.id; groupRoot.text = rootItem.title; groupRoot.iconCls = rootItem.iconCls; groupRoot.expanded = true; groupRoot.activeGroup = (me.activeGroup === idx); groupRoot.activeTab = groupRoot.activeGroup ? true : false; if (groupRoot.activeTab) { me.activeTab = groupRoot.id; } if (groupRoot.activeGroup) { me.mainItem = groupItem.mainItem || 0; me.activeGroup = groupRoot.id; } Ext.each(leafItems, function(leafItem) { // First node has been done if (leafItem.id !== groupRoot.id) { var child = { id: leafItem.id, leaf: true, text: leafItem.title, iconCls: leafItem.iconCls, activeGroup: groupRoot.activeGroup, activeTab: false }; groupRoot.children.push(child); } // Ensure the items do not get headers delete leafItem.title; delete leafItem.iconCls; cards.push(leafItem); }); data.children.push(groupRoot); }); return Ext.create('Ext.data.TreeStore', { fields: ['id', 'text', 'activeGroup', 'activeTab'], root: { expanded: true }, proxy: { type: 'memory', data: data } }); }, /** * Returns the item that is currently active inside this GroupTabPanel. * @return {Ext.Component/Number} The currently active item */ getActiveTab: function() { return this.activeTab; }, /** * Returns the root group item that is currently active inside this GroupTabPanel. * @return {Ext.Component/Number} The currently active root group item */ getActiveGroup: function() { return this.activeGroup; } }); /** * Allows GroupTab to render a table structure. */ Ext.define('Ext.ux.GroupTreeChunker', { singleton: true, requires: ['Ext.XTemplate'], metaTableTpl: [ '{%if (this.openTableWrap)out.push(this.openTableWrap())%}', '
', '{[this.openRows()]}', '{row}', '{[this.closeRows()]}', '
', '{%if (this.closeTableWrap)out.push(this.closeTableWrap())%}' ], constructor: function() { Ext.XTemplate.prototype.recurse = function(values, reference) { return this.apply(reference ? values[reference] : values); }; }, embedFullWidth: function(values) { var result = 'style="width:{fullWidth}px;'; // If there are no records, we need to give the table a height so that it // is displayed and causes q scrollbar if the width exceeds the View's width. if (!values.rowCount) { result += 'height:1px;'; } return result + '"'; }, openRows: function() { return ''; }, closeRows: function() { return ''; }, metaRowTpl: [ '
', '', '
', '
{{id}}
', '
', '
', '
', '
', '
' ], firstOrLastCls: function(xindex, xcount) { if (xindex === 1) { return Ext.view.Table.prototype.firstCls; } else if (xindex === xcount) { return Ext.view.Table.prototype.lastCls; } }, embedRowCls: function() { return '{rowCls}'; }, embedRowAttr: function() { return '{rowAttr}'; }, getTableTpl: function(cfg, textOnly) { var tpl, tableTplMemberFns = { openRows: this.openRows, closeRows: this.closeRows, embedFullWidth: this.embedFullWidth }, tplMemberFns = {}, features = cfg.features || [], ln = features.length, i = 0, memberFns = { embedRowCls: this.embedRowCls, embedRowAttr: this.embedRowAttr, firstOrLastCls: this.firstOrLastCls, unselectableAttr: cfg.enableTextSelection ? '' : 'unselectable="on"', unselectableCls: cfg.enableTextSelection ? '' : Ext.baseCSSPrefix + 'unselectable' }, // copy the default metaRowTpl = Array.prototype.slice.call(this.metaRowTpl, 0), metaTableTpl; for (; i < ln; i++) { if (!features[i].disabled) { features[i].mutateMetaRowTpl(metaRowTpl); Ext.apply(memberFns, features[i].getMetaRowTplFragments()); Ext.apply(tplMemberFns, features[i].getFragmentTpl()); Ext.apply(tableTplMemberFns, features[i].getTableFragments()); } } metaRowTpl = new Ext.XTemplate(metaRowTpl.join(''), memberFns); cfg.row = metaRowTpl.applyTemplate(cfg); metaTableTpl = new Ext.XTemplate(this.metaTableTpl.join(''), tableTplMemberFns); tpl = metaTableTpl.applyTemplate(cfg); // TODO: Investigate eliminating. if (!textOnly) { tpl = new Ext.XTemplate(tpl, tplMemberFns); } return tpl; } });