GroupTabPanel.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. /**
  2. * @author Nicolas Ferrero
  3. * @class Ext.ux.GroupTabPanel
  4. * @extends Ext.Container
  5. * A TabPanel with grouping support.
  6. */
  7. Ext.define('Ext.ux.GroupTabPanel', {
  8. extend: 'Ext.Container',
  9. alias: 'widget.grouptabpanel',
  10. requires:[
  11. 'Ext.data.*',
  12. 'Ext.tree.*',
  13. 'Ext.layout.*'
  14. ],
  15. baseCls : Ext.baseCSSPrefix + 'grouptabpanel',
  16. initComponent: function(config) {
  17. var me = this;
  18. Ext.apply(me, config);
  19. // Processes items to create the TreeStore and also set up
  20. // "this.cards" containing the actual card items.
  21. me.store = me.createTreeStore();
  22. me.layout = {
  23. type: 'hbox',
  24. align: 'stretch'
  25. };
  26. me.defaults = {
  27. border: false
  28. };
  29. me.items = [{
  30. xtype: 'treepanel',
  31. cls: 'x-tree-panel x-grouptabbar',
  32. width: 150,
  33. rootVisible: false,
  34. store: me.store,
  35. hideHeaders: true,
  36. animate: false,
  37. processEvent: Ext.emptyFn,
  38. viewConfig: {
  39. overItemCls: '',
  40. getRowClass: me.getRowClass,
  41. itemSelector: 'div.' + Ext.baseCSSPrefix + 'grouptab-row',
  42. cellSelector: 'div.' + Ext.baseCSSPrefix + 'grouptab',
  43. getTableChunker: function() {
  44. return Ext.ux.GroupTreeChunker;
  45. },
  46. onHeaderResize: function(header, w, suppressFocus) {
  47. var me = this,
  48. el = me.el;
  49. if (el) {
  50. el.select('div.' + Ext.baseCSSPrefix + 'grid-table-resizer').setWidth(me.headerCt.getFullWidth());
  51. if (!me.ignoreTemplate) {
  52. me.setNewTemplate();
  53. }
  54. if (!suppressFocus) {
  55. me.el.focus();
  56. }
  57. me.forceReflow();
  58. }
  59. }
  60. },
  61. columns: [{
  62. xtype: 'treecolumn',
  63. sortable: false,
  64. dataIndex: 'text',
  65. flex: 1,
  66. renderer: function (value, cell, node, idx1, idx2, store, tree) {
  67. var cls = '';
  68. if (node.parentNode && node.parentNode.parentNode === null) {
  69. cls += ' x-grouptab-first';
  70. if (node.previousSibling) {
  71. cls += ' x-grouptab-prev';
  72. }
  73. if (!node.get('expanded')) {
  74. cls += ' x-grouptab-last';
  75. }
  76. } else if (node.nextSibling === null) {
  77. cls += ' x-grouptab-last';
  78. } else {
  79. cls += ' x-grouptab-center';
  80. }
  81. if (node.data.activeTab) {
  82. cls += ' x-active-tab';
  83. }
  84. cell.tdCls= 'x-grouptab'+ cls;
  85. return value;
  86. }
  87. }]
  88. },{
  89. xtype: 'container',
  90. flex: 1,
  91. layout: 'card',
  92. activeItem: me.mainItem,
  93. baseCls: Ext.baseCSSPrefix + 'grouptabcontainer',
  94. items: me.cards
  95. }];
  96. me.addEvents(
  97. /**
  98. * @event beforetabchange
  99. * Fires before a tab change (activated by {@link #setActiveTab}). Return false in any listener to cancel
  100. * the tabchange
  101. * @param {Ext.ux.GroupTabPanel} grouptabPanel The GroupTabPanel
  102. * @param {Ext.Component} newCard The card that is about to be activated
  103. * @param {Ext.Component} oldCard The card that is currently active
  104. */
  105. 'beforetabchange',
  106. /**
  107. * @event tabchange
  108. * Fires when a new tab has been activated (activated by {@link #setActiveTab}).
  109. * @param {Ext.ux.GroupTabPanel} grouptabPanel The GroupTabPanel
  110. * @param {Ext.Component} newCard The newly activated item
  111. * @param {Ext.Component} oldCard The previously active item
  112. */
  113. 'tabchange',
  114. /**
  115. * @event beforegroupchange
  116. * Fires before a group change (activated by {@link #setActiveGroup}). Return false in any listener to cancel
  117. * the groupchange
  118. * @param {Ext.ux.GroupTabPanel} grouptabPanel The GroupTabPanel
  119. * @param {Ext.Component} newGroup The root group card that is about to be activated
  120. * @param {Ext.Component} oldGroup The root group card that is currently active
  121. */
  122. 'beforegroupchange',
  123. /**
  124. * @event groupchange
  125. * Fires when a new group has been activated (activated by {@link #setActiveGroup}).
  126. * @param {Ext.ux.GroupTabPanel} grouptabPanel The GroupTabPanel
  127. * @param {Ext.Component} newGroup The newly activated root group item
  128. * @param {Ext.Component} oldGroup The previously active root group item
  129. */
  130. 'groupchange'
  131. );
  132. me.callParent(arguments);
  133. me.setActiveTab(me.activeTab);
  134. me.setActiveGroup(me.activeGroup);
  135. me.mon(me.down('treepanel').getSelectionModel(), 'select', me.onNodeSelect, me);
  136. },
  137. getRowClass: function(node, rowIndex, rowParams, store) {
  138. var cls = '';
  139. if (node.data.activeGroup) {
  140. cls += ' x-active-group';
  141. }
  142. return cls;
  143. },
  144. /**
  145. * @private
  146. * Node selection listener.
  147. */
  148. onNodeSelect: function (selModel, node) {
  149. var me = this,
  150. currentNode = me.store.getRootNode(),
  151. parent;
  152. if (node.parentNode && node.parentNode.parentNode === null) {
  153. parent = node;
  154. } else {
  155. parent = node.parentNode;
  156. }
  157. if (me.setActiveGroup(parent.get('id')) === false || me.setActiveTab(node.get('id')) === false) {
  158. return false;
  159. }
  160. while (currentNode) {
  161. currentNode.set('activeTab', false);
  162. currentNode.set('activeGroup', false);
  163. currentNode = currentNode.firstChild || currentNode.nextSibling || currentNode.parentNode.nextSibling;
  164. }
  165. parent.set('activeGroup', true);
  166. parent.eachChild(function(child) {
  167. child.set('activeGroup', true);
  168. });
  169. node.set('activeTab', true);
  170. selModel.view.refresh();
  171. },
  172. /**
  173. * Makes the given component active (makes it the visible card in the GroupTabPanel's CardLayout)
  174. * @param {Ext.Component} cmp The component to make active
  175. */
  176. setActiveTab: function(cmp) {
  177. var me = this,
  178. newTab = cmp,
  179. oldTab;
  180. if(Ext.isString(cmp)) {
  181. newTab = Ext.getCmp(newTab);
  182. }
  183. if (newTab === me.activeTab) {
  184. return false;
  185. }
  186. oldTab = me.activeTab;
  187. if (me.fireEvent('beforetabchange', me, newTab, oldTab) !== false) {
  188. me.activeTab = newTab;
  189. if (me.rendered) {
  190. me.down('container[baseCls=' + Ext.baseCSSPrefix + 'grouptabcontainer' + ']').getLayout().setActiveItem(newTab);
  191. }
  192. me.fireEvent('tabchange', me, newTab, oldTab);
  193. }
  194. return true;
  195. },
  196. /**
  197. * Makes the given group active
  198. * @param {Ext.Component} cmp The root component to make active.
  199. */
  200. setActiveGroup: function(cmp) {
  201. var me = this,
  202. newGroup = cmp,
  203. oldGroup;
  204. if(Ext.isString(cmp)) {
  205. newGroup = Ext.getCmp(newGroup);
  206. }
  207. if (newGroup === me.activeGroup) {
  208. return true;
  209. }
  210. oldGroup = me.activeGroup;
  211. if (me.fireEvent('beforegroupchange', me, newGroup, oldGroup) !== false) {
  212. me.activeGroup = newGroup;
  213. me.fireEvent('groupchange', me, newGroup, oldGroup);
  214. } else {
  215. return false;
  216. }
  217. return true;
  218. },
  219. /**
  220. * @private
  221. * Creates the TreeStore used by the GroupTabBar.
  222. */
  223. createTreeStore: function() {
  224. var me = this,
  225. groups = me.prepareItems(me.items),
  226. data = {
  227. text: '.',
  228. children: []
  229. },
  230. cards = me.cards = [];
  231. me.activeGroup = me.activeGroup || 0;
  232. Ext.each(groups, function(groupItem, idx) {
  233. var leafItems = groupItem.items.items,
  234. rootItem = (leafItems[groupItem.mainItem] || leafItems[0]),
  235. groupRoot = {
  236. children: []
  237. };
  238. // Create the root node of the group
  239. groupRoot.id = rootItem.id;
  240. groupRoot.text = rootItem.title;
  241. groupRoot.iconCls = rootItem.iconCls;
  242. groupRoot.expanded = true;
  243. groupRoot.activeGroup = (me.activeGroup === idx);
  244. groupRoot.activeTab = groupRoot.activeGroup ? true : false;
  245. if (groupRoot.activeTab) {
  246. me.activeTab = groupRoot.id;
  247. }
  248. if (groupRoot.activeGroup) {
  249. me.mainItem = groupItem.mainItem || 0;
  250. me.activeGroup = groupRoot.id;
  251. }
  252. Ext.each(leafItems, function(leafItem) {
  253. // First node has been done
  254. if (leafItem.id !== groupRoot.id) {
  255. var child = {
  256. id: leafItem.id,
  257. leaf: true,
  258. text: leafItem.title,
  259. iconCls: leafItem.iconCls,
  260. activeGroup: groupRoot.activeGroup,
  261. activeTab: false
  262. };
  263. groupRoot.children.push(child);
  264. }
  265. // Ensure the items do not get headers
  266. delete leafItem.title;
  267. delete leafItem.iconCls;
  268. cards.push(leafItem);
  269. });
  270. data.children.push(groupRoot);
  271. });
  272. return Ext.create('Ext.data.TreeStore', {
  273. fields: ['id', 'text', 'activeGroup', 'activeTab'],
  274. root: {
  275. expanded: true
  276. },
  277. proxy: {
  278. type: 'memory',
  279. data: data
  280. }
  281. });
  282. },
  283. /**
  284. * Returns the item that is currently active inside this GroupTabPanel.
  285. * @return {Ext.Component/Number} The currently active item
  286. */
  287. getActiveTab: function() {
  288. return this.activeTab;
  289. },
  290. /**
  291. * Returns the root group item that is currently active inside this GroupTabPanel.
  292. * @return {Ext.Component/Number} The currently active root group item
  293. */
  294. getActiveGroup: function() {
  295. return this.activeGroup;
  296. }
  297. });
  298. /**
  299. * Allows GroupTab to render a table structure.
  300. */
  301. Ext.define('Ext.ux.GroupTreeChunker', {
  302. singleton: true,
  303. requires: ['Ext.XTemplate'],
  304. metaTableTpl: [
  305. '{%if (this.openTableWrap)out.push(this.openTableWrap())%}',
  306. '<div class="' + Ext.baseCSSPrefix + 'grid-table-resizer" border="0" cellspacing="0" cellpadding="0" {[this.embedFullWidth(values)]}>',
  307. '{[this.openRows()]}',
  308. '{row}',
  309. '{[this.closeRows()]}',
  310. '</div>',
  311. '{%if (this.closeTableWrap)out.push(this.closeTableWrap())%}'
  312. ],
  313. constructor: function() {
  314. Ext.XTemplate.prototype.recurse = function(values, reference) {
  315. return this.apply(reference ? values[reference] : values);
  316. };
  317. },
  318. embedFullWidth: function(values) {
  319. var result = 'style="width:{fullWidth}px;';
  320. // If there are no records, we need to give the table a height so that it
  321. // is displayed and causes q scrollbar if the width exceeds the View's width.
  322. if (!values.rowCount) {
  323. result += 'height:1px;';
  324. }
  325. return result + '"';
  326. },
  327. openRows: function() {
  328. return '<tpl for="rows">';
  329. },
  330. closeRows: function() {
  331. return '</tpl>';
  332. },
  333. metaRowTpl: [
  334. '<div class="' + Ext.baseCSSPrefix + 'grouptab-row {[this.embedRowCls()]}" {[this.embedRowAttr()]}>',
  335. '<tpl for="columns">',
  336. '<div class="{cls} ' + Ext.baseCSSPrefix + 'grouptab-cell ' + Ext.baseCSSPrefix + 'grid-cell-{columnId} {{id}-modified} {{id}-tdCls} {[this.firstOrLastCls(xindex, xcount)]}" {{id}-tdAttr}>',
  337. '<div {unselectableAttr} class="' + Ext.baseCSSPrefix + 'grid-cell-inner {unselectableCls}" style="text-align: {align}; {{id}-style};">{{id}}</div>',
  338. '<div class="x-grouptabs-corner x-grouptabs-corner-top-left" id="ext-gen25"></div>',
  339. '<div class="x-grouptabs-corner x-grouptabs-corner-bottom-left" id="ext-gen26"></div>',
  340. '</div>',
  341. '</tpl>',
  342. '</div>'
  343. ],
  344. firstOrLastCls: function(xindex, xcount) {
  345. if (xindex === 1) {
  346. return Ext.view.Table.prototype.firstCls;
  347. } else if (xindex === xcount) {
  348. return Ext.view.Table.prototype.lastCls;
  349. }
  350. },
  351. embedRowCls: function() {
  352. return '{rowCls}';
  353. },
  354. embedRowAttr: function() {
  355. return '{rowAttr}';
  356. },
  357. getTableTpl: function(cfg, textOnly) {
  358. var tpl,
  359. tableTplMemberFns = {
  360. openRows: this.openRows,
  361. closeRows: this.closeRows,
  362. embedFullWidth: this.embedFullWidth
  363. },
  364. tplMemberFns = {},
  365. features = cfg.features || [],
  366. ln = features.length,
  367. i = 0,
  368. memberFns = {
  369. embedRowCls: this.embedRowCls,
  370. embedRowAttr: this.embedRowAttr,
  371. firstOrLastCls: this.firstOrLastCls,
  372. unselectableAttr: cfg.enableTextSelection ? '' : 'unselectable="on"',
  373. unselectableCls: cfg.enableTextSelection ? '' : Ext.baseCSSPrefix + 'unselectable'
  374. },
  375. // copy the default
  376. metaRowTpl = Array.prototype.slice.call(this.metaRowTpl, 0),
  377. metaTableTpl;
  378. for (; i < ln; i++) {
  379. if (!features[i].disabled) {
  380. features[i].mutateMetaRowTpl(metaRowTpl);
  381. Ext.apply(memberFns, features[i].getMetaRowTplFragments());
  382. Ext.apply(tplMemberFns, features[i].getFragmentTpl());
  383. Ext.apply(tableTplMemberFns, features[i].getTableFragments());
  384. }
  385. }
  386. metaRowTpl = new Ext.XTemplate(metaRowTpl.join(''), memberFns);
  387. cfg.row = metaRowTpl.applyTemplate(cfg);
  388. metaTableTpl = new Ext.XTemplate(this.metaTableTpl.join(''), tableTplMemberFns);
  389. tpl = metaTableTpl.applyTemplate(cfg);
  390. // TODO: Investigate eliminating.
  391. if (!textOnly) {
  392. tpl = new Ext.XTemplate(tpl, tplMemberFns);
  393. }
  394. return tpl;
  395. }
  396. });