Lockable.html 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  5. <title>The source code</title>
  6. <link href="../resources/prettify/prettify.css" type="text/css" rel="stylesheet" />
  7. <script type="text/javascript" src="../resources/prettify/prettify.js"></script>
  8. <style type="text/css">
  9. .highlight { display: block; background-color: #ddd; }
  10. </style>
  11. <script type="text/javascript">
  12. function highlight() {
  13. document.getElementById(location.hash.replace(/#/, "")).className = "highlight";
  14. }
  15. </script>
  16. </head>
  17. <body onload="prettyPrint(); highlight();">
  18. <pre class="prettyprint lang-js"><span id='Ext-grid-Lockable'>/**
  19. </span> * @private
  20. *
  21. * Lockable is a private mixin which injects lockable behavior into any
  22. * TablePanel subclass such as GridPanel or TreePanel. TablePanel will
  23. * automatically inject the Ext.grid.Lockable mixin in when one of the
  24. * these conditions are met:
  25. *
  26. * - The TablePanel has the lockable configuration set to true
  27. * - One of the columns in the TablePanel has locked set to true/false
  28. *
  29. * Each TablePanel subclass must register an alias. It should have an array
  30. * of configurations to copy to the 2 separate tablepanel's that will be generated
  31. * to note what configurations should be copied. These are named normalCfgCopy and
  32. * lockedCfgCopy respectively.
  33. *
  34. * Columns which are locked must specify a fixed width. They do NOT support a
  35. * flex width.
  36. *
  37. * Configurations which are specified in this class will be available on any grid or
  38. * tree which is using the lockable functionality.
  39. */
  40. Ext.define('Ext.grid.Lockable', {
  41. requires: [
  42. 'Ext.grid.LockingView',
  43. 'Ext.view.Table'
  44. ],
  45. <span id='Ext-grid-Lockable-cfg-syncRowHeight'> /**
  46. </span> * @cfg {Boolean} syncRowHeight Synchronize rowHeight between the normal and
  47. * locked grid view. This is turned on by default. If your grid is guaranteed
  48. * to have rows of all the same height, you should set this to false to
  49. * optimize performance.
  50. */
  51. syncRowHeight: true,
  52. <span id='Ext-grid-Lockable-cfg-subGridXType'> /**
  53. </span> * @cfg {String} subGridXType The xtype of the subgrid to specify. If this is
  54. * not specified lockable will determine the subgrid xtype to create by the
  55. * following rule. Use the superclasses xtype if the superclass is NOT
  56. * tablepanel, otherwise use the xtype itself.
  57. */
  58. <span id='Ext-grid-Lockable-cfg-lockedViewConfig'> /**
  59. </span> * @cfg {Object} lockedViewConfig A view configuration to be applied to the
  60. * locked side of the grid. Any conflicting configurations between lockedViewConfig
  61. * and viewConfig will be overwritten by the lockedViewConfig.
  62. */
  63. <span id='Ext-grid-Lockable-cfg-normalViewConfig'> /**
  64. </span> * @cfg {Object} normalViewConfig A view configuration to be applied to the
  65. * normal/unlocked side of the grid. Any conflicting configurations between normalViewConfig
  66. * and viewConfig will be overwritten by the normalViewConfig.
  67. */
  68. headerCounter: 0,
  69. <span id='Ext-grid-Lockable-cfg-scrollDelta'> /**
  70. </span> * @cfg {Number} scrollDelta
  71. * Number of pixels to scroll when scrolling the locked section with mousewheel.
  72. */
  73. scrollDelta: 40,
  74. <span id='Ext-grid-Lockable-cfg-lockedGridConfig'> /**
  75. </span> * @cfg {Object} lockedGridConfig
  76. * Any special configuration options for the locked part of the grid
  77. */
  78. <span id='Ext-grid-Lockable-cfg-normalGridConfig'> /**
  79. </span> * @cfg {Object} normalGridConfig
  80. * Any special configuration options for the normal part of the grid
  81. */
  82. // i8n text
  83. //&lt;locale&gt;
  84. unlockText: 'Unlock',
  85. //&lt;/locale&gt;
  86. //&lt;locale&gt;
  87. lockText: 'Lock',
  88. //&lt;/locale&gt;
  89. determineXTypeToCreate: function() {
  90. var me = this,
  91. typeToCreate,
  92. xtypes, xtypesLn, xtype, superxtype;
  93. if (me.subGridXType) {
  94. typeToCreate = me.subGridXType;
  95. } else {
  96. xtypes = this.getXTypes().split('/');
  97. xtypesLn = xtypes.length;
  98. xtype = xtypes[xtypesLn - 1];
  99. superxtype = xtypes[xtypesLn - 2];
  100. if (superxtype !== 'tablepanel') {
  101. typeToCreate = superxtype;
  102. } else {
  103. typeToCreate = xtype;
  104. }
  105. }
  106. return typeToCreate;
  107. },
  108. // injectLockable will be invoked before initComponent's parent class implementation
  109. // is called, so throughout this method this. are configurations
  110. injectLockable: function() {
  111. // ensure lockable is set to true in the TablePanel
  112. this.lockable = true;
  113. // Instruct the TablePanel it already has a view and not to create one.
  114. // We are going to aggregate 2 copies of whatever TablePanel we are using
  115. this.hasView = true;
  116. var me = this,
  117. // If the OS does not show a space-taking scrollbar, the locked view can be overflow:auto
  118. scrollLocked = Ext.getScrollbarSize().width === 0,
  119. store = me.store = Ext.StoreManager.lookup(me.store),
  120. // xtype of this class, 'treepanel' or 'gridpanel'
  121. // (Note: this makes it a requirement that any subclass that wants to use lockable functionality needs to register an
  122. // alias.)
  123. xtype = me.determineXTypeToCreate(),
  124. // share the selection model
  125. selModel = me.getSelectionModel(),
  126. lockedFeatures,
  127. normalFeatures,
  128. lockedPlugins,
  129. normalPlugins,
  130. lockedGrid,
  131. normalGrid,
  132. i, len,
  133. columns,
  134. lockedHeaderCt,
  135. normalHeaderCt,
  136. lockedView,
  137. normalView,
  138. listeners;
  139. lockedFeatures = me.constructFeatures();
  140. // Clone any Features in the Array which are already instantiated
  141. me.cloneFeatures();
  142. normalFeatures = me.constructFeatures();
  143. lockedPlugins = me.constructPlugins();
  144. // Clone any Plugins in the Array which are already instantiated
  145. me.clonePlugins();
  146. normalPlugins = me.constructPlugins();
  147. // The &quot;shell&quot; Panel which just acts as a Container for the two grids must not use the features and plugins
  148. delete me.features;
  149. delete me.plugins;
  150. // Each Feature must have a reference to its counterpart on the opposite side of the locking view
  151. for (i = 0, len = (lockedFeatures ? lockedFeatures.length : 0); i &lt; len; i++) {
  152. lockedFeatures[i].lockingPartner = normalFeatures[i];
  153. normalFeatures[i].lockingPartner = lockedFeatures[i];
  154. }
  155. lockedGrid = Ext.apply({
  156. xtype: xtype,
  157. store: store,
  158. scrollerOwner: false,
  159. // Lockable does NOT support animations for Tree
  160. enableAnimations: false,
  161. scroll: scrollLocked ? 'vertical' : false,
  162. selModel: selModel,
  163. border: false,
  164. cls: Ext.baseCSSPrefix + 'grid-inner-locked',
  165. isLayoutRoot: function() {
  166. return false;
  167. },
  168. features: lockedFeatures,
  169. plugins: lockedPlugins
  170. }, me.lockedGridConfig);
  171. normalGrid = Ext.apply({
  172. xtype: xtype,
  173. store: store,
  174. scrollerOwner: false,
  175. enableAnimations: false,
  176. selModel: selModel,
  177. border: false,
  178. isLayoutRoot: function() {
  179. return false;
  180. },
  181. features: normalFeatures,
  182. plugins: normalPlugins
  183. }, me.normalGridConfig);
  184. me.addCls(Ext.baseCSSPrefix + 'grid-locked');
  185. // copy appropriate configurations to the respective
  186. // aggregated tablepanel instances and then delete them
  187. // from the master tablepanel.
  188. Ext.copyTo(normalGrid, me, me.bothCfgCopy);
  189. Ext.copyTo(lockedGrid, me, me.bothCfgCopy);
  190. Ext.copyTo(normalGrid, me, me.normalCfgCopy);
  191. Ext.copyTo(lockedGrid, me, me.lockedCfgCopy);
  192. for (i = 0; i &lt; me.normalCfgCopy.length; i++) {
  193. delete me[me.normalCfgCopy[i]];
  194. }
  195. for (i = 0; i &lt; me.lockedCfgCopy.length; i++) {
  196. delete me[me.lockedCfgCopy[i]];
  197. }
  198. me.addEvents(
  199. <span id='Ext-grid-Lockable-event-lockcolumn'> /**
  200. </span> * @event lockcolumn
  201. * Fires when a column is locked.
  202. * @param {Ext.grid.Panel} this The gridpanel.
  203. * @param {Ext.grid.column.Column} column The column being locked.
  204. */
  205. 'lockcolumn',
  206. <span id='Ext-grid-Lockable-event-unlockcolumn'> /**
  207. </span> * @event unlockcolumn
  208. * Fires when a column is unlocked.
  209. * @param {Ext.grid.Panel} this The gridpanel.
  210. * @param {Ext.grid.column.Column} column The column being unlocked.
  211. */
  212. 'unlockcolumn'
  213. );
  214. me.addStateEvents(['lockcolumn', 'unlockcolumn']);
  215. me.lockedHeights = [];
  216. me.normalHeights = [];
  217. columns = me.processColumns(me.columns);
  218. lockedGrid.width = columns.lockedWidth + Ext.num(selModel.headerWidth, 0);
  219. lockedGrid.columns = columns.locked;
  220. normalGrid.columns = columns.normal;
  221. // normal grid should flex the rest of the width
  222. normalGrid.flex = 1;
  223. lockedGrid.viewConfig = me.lockedViewConfig || {};
  224. lockedGrid.viewConfig.loadingUseMsg = false;
  225. normalGrid.viewConfig = me.normalViewConfig || {};
  226. Ext.applyIf(lockedGrid.viewConfig, me.viewConfig);
  227. Ext.applyIf(normalGrid.viewConfig, me.viewConfig);
  228. me.lockedGrid = Ext.ComponentManager.create(lockedGrid);
  229. lockedView = me.lockedGrid.getView();
  230. normalGrid.viewConfig.lockingPartner = lockedView;
  231. me.normalGrid = Ext.ComponentManager.create(normalGrid);
  232. normalView = me.normalGrid.getView();
  233. me.view = new Ext.grid.LockingView({
  234. locked: me.lockedGrid,
  235. normal: me.normalGrid,
  236. panel: me
  237. });
  238. // Set up listeners for the locked view. If its SelModel ever scrolls it, the normal view must sync
  239. listeners = {
  240. scroll: {
  241. fn: me.onLockedViewScroll,
  242. element: 'el',
  243. scope: me
  244. }
  245. };
  246. // If there are system scrollbars, we have to monitor the mousewheel and fake a scroll
  247. if (!scrollLocked) {
  248. listeners.mousewheel = {
  249. fn: me.onLockedViewMouseWheel,
  250. element: 'el',
  251. scope: me
  252. };
  253. }
  254. if (me.syncRowHeight) {
  255. listeners.refresh = me.onLockedViewRefresh;
  256. listeners.itemupdate = me.onLockedViewItemUpdate;
  257. listeners.scope = me;
  258. }
  259. lockedView.on(listeners);
  260. // Set up listeners for the normal view
  261. listeners = {
  262. scroll: {
  263. fn: me.onNormalViewScroll,
  264. element: 'el',
  265. scope: me
  266. },
  267. refresh: me.syncRowHeight ? me.onNormalViewRefresh : me.updateSpacer,
  268. scope: me
  269. };
  270. normalView.on(listeners);
  271. lockedHeaderCt = me.lockedGrid.headerCt;
  272. normalHeaderCt = me.normalGrid.headerCt;
  273. lockedHeaderCt.lockedCt = true;
  274. lockedHeaderCt.lockableInjected = true;
  275. normalHeaderCt.lockableInjected = true;
  276. lockedHeaderCt.on({
  277. columnshow: me.onLockedHeaderShow,
  278. columnhide: me.onLockedHeaderHide,
  279. columnmove: me.onLockedHeaderMove,
  280. sortchange: me.onLockedHeaderSortChange,
  281. columnresize: me.onLockedHeaderResize,
  282. scope: me
  283. });
  284. normalHeaderCt.on({
  285. columnmove: me.onNormalHeaderMove,
  286. sortchange: me.onNormalHeaderSortChange,
  287. scope: me
  288. });
  289. me.modifyHeaderCt();
  290. me.items = [me.lockedGrid, me.normalGrid];
  291. me.relayHeaderCtEvents(lockedHeaderCt);
  292. me.relayHeaderCtEvents(normalHeaderCt);
  293. me.layout = {
  294. type: 'hbox',
  295. align: 'stretch'
  296. };
  297. },
  298. processColumns: function(columns){
  299. // split apart normal and lockedWidths
  300. var i = 0,
  301. len = columns.length,
  302. lockedWidth = 0,
  303. lockedHeaders = [],
  304. normalHeaders = [],
  305. column;
  306. for (; i &lt; len; ++i) {
  307. column = columns[i];
  308. // MUST clone the column config because we mutate it, and we must not mutate passed in config objects in case they are re-used
  309. // eg, in an extend-to-configure scenario.
  310. if (!column.isComponent) {
  311. column = Ext.apply({}, columns[i]);
  312. }
  313. // mark the column as processed so that the locked attribute does not
  314. // trigger trying to aggregate the columns again.
  315. column.processed = true;
  316. if (column.locked) {
  317. // &lt;debug&gt;
  318. if (column.flex) {
  319. Ext.Error.raise(&quot;Columns which are locked do NOT support a flex width. You must set a width on the &quot; + columns[i].text + &quot;column.&quot;);
  320. }
  321. // &lt;/debug&gt;
  322. if (!column.hidden) {
  323. lockedWidth += column.width || Ext.grid.header.Container.prototype.defaultWidth;
  324. }
  325. lockedHeaders.push(column);
  326. } else {
  327. normalHeaders.push(column);
  328. }
  329. if (!column.headerId) {
  330. column.headerId = (column.initialConfig || column).id || ('L' + (++this.headerCounter));
  331. }
  332. }
  333. return {
  334. lockedWidth: lockedWidth,
  335. locked: {
  336. items: lockedHeaders,
  337. itemId: 'lockedHeaderCt',
  338. stretchMaxPartner: '^^&gt;&gt;#normalHeaderCt'
  339. },
  340. normal: {
  341. items: normalHeaders,
  342. itemId: 'normalHeaderCt',
  343. stretchMaxPartner: '^^&gt;&gt;#lockedHeaderCt'
  344. }
  345. };
  346. },
  347. <span id='Ext-grid-Lockable-method-onLockedViewMouseWheel'> /**
  348. </span> * @private
  349. * Listen for mousewheel events on the locked section which does not scroll.
  350. * Scroll it in response, and the other section will automatically sync.
  351. */
  352. onLockedViewMouseWheel: function(e) {
  353. var me = this,
  354. scrollDelta = -me.scrollDelta,
  355. deltaY = scrollDelta * e.getWheelDeltas().y,
  356. vertScrollerEl = me.lockedGrid.getView().el.dom,
  357. verticalCanScrollDown, verticalCanScrollUp;
  358. if (vertScrollerEl) {
  359. verticalCanScrollDown = vertScrollerEl.scrollTop !== vertScrollerEl.scrollHeight - vertScrollerEl.clientHeight;
  360. verticalCanScrollUp = vertScrollerEl.scrollTop !== 0;
  361. }
  362. if ((deltaY &lt; 0 &amp;&amp; verticalCanScrollUp) || (deltaY &gt; 0 &amp;&amp; verticalCanScrollDown)) {
  363. e.stopEvent();
  364. // Inhibit processing of any scroll events we *may* cause here.
  365. // Some OSs do not fire a scroll event when we set the scrollTop of an overflow:hidden element,
  366. // so we invoke the scroll handler programatically below.
  367. me.scrolling = true;
  368. vertScrollerEl.scrollTop += deltaY;
  369. me.normalGrid.getView().el.dom.scrollTop = vertScrollerEl.scrollTop;
  370. me.scrolling = false;
  371. // Invoke the scroll event handler programatically to sync everything.
  372. me.onNormalViewScroll();
  373. }
  374. },
  375. onLockedViewScroll: function() {
  376. var me = this,
  377. lockedView = me.lockedGrid.getView(),
  378. normalView = me.normalGrid.getView(),
  379. normalTable,
  380. lockedTable;
  381. // Set a flag so that the scroll even doesn't bounce back when we set the normal view's scroll position
  382. if (!me.scrolling) {
  383. me.scrolling = true;
  384. normalView.el.dom.scrollTop = lockedView.el.dom.scrollTop;
  385. // For buffered views, the absolute position is important as well as scrollTop
  386. if (me.store.buffered) {
  387. lockedTable = lockedView.el.child('table', true);
  388. normalTable = normalView.el.child('table', true);
  389. lockedTable.style.position = 'absolute';
  390. }
  391. me.scrolling = false;
  392. }
  393. },
  394. onNormalViewScroll: function() {
  395. var me = this,
  396. lockedView = me.lockedGrid.getView(),
  397. normalView = me.normalGrid.getView(),
  398. normalTable,
  399. lockedTable;
  400. // Set a flag so that the scroll even doesn't bounce back when we set the locked view's scroll position
  401. if (!me.scrolling) {
  402. me.scrolling = true;
  403. lockedView.el.dom.scrollTop = normalView.el.dom.scrollTop;
  404. // For buffered views, the absolute position is important as well as scrollTop
  405. if (me.store.buffered) {
  406. lockedTable = lockedView.el.child('table', true);
  407. normalTable = normalView.el.child('table', true);
  408. lockedTable.style.position = 'absolute';
  409. lockedTable.style.top = normalTable.style.top;
  410. }
  411. me.scrolling = false;
  412. }
  413. },
  414. // trigger a pseudo refresh on the normal side
  415. onLockedHeaderMove: function() {
  416. if (this.syncRowHeight) {
  417. this.onNormalViewRefresh();
  418. }
  419. },
  420. // trigger a pseudo refresh on the locked side
  421. onNormalHeaderMove: function() {
  422. if (this.syncRowHeight) {
  423. this.onLockedViewRefresh();
  424. }
  425. },
  426. // Create a spacer in lockedsection and store a reference.
  427. // This is to allow the locked section to scroll past the bottom to
  428. // take the mormal section's horizontal scrollbar into account
  429. // TODO: Should destroy before refreshing content
  430. updateSpacer: function() {
  431. var me = this,
  432. // This affects scrolling all the way to the bottom of a locked grid
  433. // additional test, sort a column and make sure it synchronizes
  434. lockedViewEl = me.lockedGrid.getView().el,
  435. normalViewEl = me.normalGrid.getView().el.dom,
  436. spacerId = lockedViewEl.dom.id + '-spacer',
  437. spacerHeight = (normalViewEl.offsetHeight - normalViewEl.clientHeight) + 'px';
  438. me.spacerEl = Ext.getDom(spacerId);
  439. if (me.spacerEl) {
  440. me.spacerEl.style.height = spacerHeight;
  441. } else {
  442. Ext.core.DomHelper.append(lockedViewEl, {
  443. id: spacerId,
  444. style: 'height: ' + spacerHeight
  445. });
  446. }
  447. },
  448. // cache the heights of all locked rows and sync rowheights
  449. onLockedViewRefresh: function() {
  450. // Only bother if there are some columns in the normal grid to sync
  451. if (this.normalGrid.headerCt.getGridColumns().length) {
  452. var me = this,
  453. view = me.lockedGrid.getView(),
  454. el = view.el,
  455. rowEls = el.query(view.getItemSelector()),
  456. ln = rowEls.length,
  457. i = 0;
  458. // reset heights each time.
  459. me.lockedHeights = [];
  460. for (; i &lt; ln; i++) {
  461. me.lockedHeights[i] = rowEls[i].offsetHeight;
  462. }
  463. me.syncRowHeights();
  464. me.updateSpacer();
  465. }
  466. },
  467. // cache the heights of all normal rows and sync rowheights
  468. onNormalViewRefresh: function() {
  469. // Only bother if there are some columns in the locked grid to sync
  470. if (this.lockedGrid.headerCt.getGridColumns().length) {
  471. var me = this,
  472. view = me.normalGrid.getView(),
  473. el = view.el,
  474. rowEls = el.query(view.getItemSelector()),
  475. ln = rowEls.length,
  476. i = 0;
  477. // reset heights each time.
  478. me.normalHeights = [];
  479. for (; i &lt; ln; i++) {
  480. me.normalHeights[i] = rowEls[i].offsetHeight;
  481. }
  482. me.syncRowHeights();
  483. me.updateSpacer();
  484. }
  485. },
  486. // rows can get bigger/smaller
  487. onLockedViewItemUpdate: function(record, index, node) {
  488. // Only bother if there are some columns in the normal grid to sync
  489. if (this.normalGrid.headerCt.getGridColumns().length) {
  490. this.lockedHeights[index] = node.offsetHeight;
  491. this.syncRowHeights();
  492. }
  493. },
  494. // rows can get bigger/smaller
  495. onNormalViewItemUpdate: function(record, index, node) {
  496. // Only bother if there are some columns in the locked grid to sync
  497. if (this.lockedGrid.headerCt.getGridColumns().length) {
  498. this.normalHeights[index] = node.offsetHeight;
  499. this.syncRowHeights();
  500. }
  501. },
  502. <span id='Ext-grid-Lockable-method-syncRowHeights'> /**
  503. </span> * Synchronizes the row heights between the locked and non locked portion of the grid for each
  504. * row. If one row is smaller than the other, the height will be increased to match the larger one.
  505. */
  506. syncRowHeights: function() {
  507. var me = this,
  508. lockedHeights = me.lockedHeights,
  509. normalHeights = me.normalHeights,
  510. ln = lockedHeights.length,
  511. i = 0,
  512. lockedView, normalView,
  513. lockedRowEls, normalRowEls,
  514. scrollTop;
  515. // ensure there are an equal num of locked and normal
  516. // rows before synchronization
  517. if (lockedHeights.length &amp;&amp; normalHeights.length) {
  518. lockedView = me.lockedGrid.getView();
  519. normalView = me.normalGrid.getView();
  520. lockedRowEls = lockedView.el.query(lockedView.getItemSelector());
  521. normalRowEls = normalView.el.query(normalView.getItemSelector());
  522. // loop thru all of the heights and sync to the other side
  523. for (; i &lt; ln; i++) {
  524. // ensure both are numbers
  525. if (!isNaN(lockedHeights[i]) &amp;&amp; !isNaN(normalHeights[i])) {
  526. if (lockedHeights[i] &gt; normalHeights[i]) {
  527. Ext.fly(normalRowEls[i]).setHeight(lockedHeights[i]);
  528. } else if (lockedHeights[i] &lt; normalHeights[i]) {
  529. Ext.fly(lockedRowEls[i]).setHeight(normalHeights[i]);
  530. }
  531. }
  532. }
  533. // Synchronize the scrollTop positions of the two views
  534. scrollTop = normalView.el.dom.scrollTop;
  535. normalView.el.dom.scrollTop = scrollTop;
  536. lockedView.el.dom.scrollTop = scrollTop;
  537. // reset the heights
  538. me.lockedHeights = [];
  539. me.normalHeights = [];
  540. }
  541. },
  542. // inject Lock and Unlock text
  543. modifyHeaderCt: function() {
  544. var me = this;
  545. me.lockedGrid.headerCt.getMenuItems = me.getMenuItems(me.lockedGrid.headerCt.getMenuItems, true);
  546. me.normalGrid.headerCt.getMenuItems = me.getMenuItems(me.normalGrid.headerCt.getMenuItems, false);
  547. },
  548. onUnlockMenuClick: function() {
  549. this.unlock();
  550. },
  551. onLockMenuClick: function() {
  552. this.lock();
  553. },
  554. getMenuItems: function(getMenuItems, locked) {
  555. var me = this,
  556. unlockText = me.unlockText,
  557. lockText = me.lockText,
  558. unlockCls = Ext.baseCSSPrefix + 'hmenu-unlock',
  559. lockCls = Ext.baseCSSPrefix + 'hmenu-lock',
  560. unlockHandler = Ext.Function.bind(me.onUnlockMenuClick, me),
  561. lockHandler = Ext.Function.bind(me.onLockMenuClick, me);
  562. // runs in the scope of headerCt
  563. return function() {
  564. // We cannot use the method from HeaderContainer's prototype here
  565. // because other plugins or features may already have injected an implementation
  566. var o = getMenuItems.call(this);
  567. o.push('-', {
  568. cls: unlockCls,
  569. text: unlockText,
  570. handler: unlockHandler,
  571. disabled: !locked
  572. });
  573. o.push({
  574. cls: lockCls,
  575. text: lockText,
  576. handler: lockHandler,
  577. disabled: locked
  578. });
  579. return o;
  580. };
  581. },
  582. // going from unlocked section to locked
  583. <span id='Ext-grid-Lockable-method-lock'> /**
  584. </span> * Locks the activeHeader as determined by which menu is open OR a header
  585. * as specified.
  586. * @param {Ext.grid.column.Column} [header] Header to unlock from the locked section.
  587. * Defaults to the header which has the menu open currently.
  588. * @param {Number} [toIdx] The index to move the unlocked header to.
  589. * Defaults to appending as the last item.
  590. * @private
  591. */
  592. lock: function(activeHd, toIdx) {
  593. var me = this,
  594. normalGrid = me.normalGrid,
  595. lockedGrid = me.lockedGrid,
  596. normalHCt = normalGrid.headerCt,
  597. lockedHCt = lockedGrid.headerCt;
  598. activeHd = activeHd || normalHCt.getMenu().activeHeader;
  599. // if column was previously flexed, get/set current width
  600. // and remove the flex
  601. if (activeHd.flex) {
  602. activeHd.width = activeHd.getWidth();
  603. delete activeHd.flex;
  604. }
  605. Ext.suspendLayouts();
  606. activeHd.ownerCt.remove(activeHd, false);
  607. activeHd.locked = true;
  608. if (Ext.isDefined(toIdx)) {
  609. lockedHCt.insert(toIdx, activeHd);
  610. } else {
  611. lockedHCt.add(activeHd);
  612. }
  613. me.syncLockedSection();
  614. Ext.resumeLayouts(true);
  615. me.updateSpacer();
  616. me.fireEvent('lockcolumn', me, activeHd);
  617. },
  618. syncLockedSection: function() {
  619. var me = this;
  620. me.syncLockedWidth();
  621. me.lockedGrid.getView().refresh();
  622. me.normalGrid.getView().refresh();
  623. },
  624. // adjust the locked section to the width of its respective
  625. // headerCt
  626. syncLockedWidth: function() {
  627. var me = this,
  628. locked = me.lockedGrid,
  629. width = locked.headerCt.getFullWidth(true);
  630. Ext.suspendLayouts();
  631. if (width &gt; 0) {
  632. locked.setWidth(width);
  633. locked.show();
  634. } else {
  635. locked.hide();
  636. }
  637. Ext.resumeLayouts(true);
  638. return width &gt; 0;
  639. },
  640. onLockedHeaderResize: function() {
  641. this.syncLockedWidth();
  642. },
  643. onLockedHeaderHide: function() {
  644. this.syncLockedWidth();
  645. },
  646. onLockedHeaderShow: function() {
  647. this.syncLockedWidth();
  648. },
  649. onLockedHeaderSortChange: function(headerCt, header, sortState) {
  650. if (sortState) {
  651. // no real header, and silence the event so we dont get into an
  652. // infinite loop
  653. this.normalGrid.headerCt.clearOtherSortStates(null, true);
  654. }
  655. },
  656. onNormalHeaderSortChange: function(headerCt, header, sortState) {
  657. if (sortState) {
  658. // no real header, and silence the event so we dont get into an
  659. // infinite loop
  660. this.lockedGrid.headerCt.clearOtherSortStates(null, true);
  661. }
  662. },
  663. // going from locked section to unlocked
  664. <span id='Ext-grid-Lockable-method-unlock'> /**
  665. </span> * Unlocks the activeHeader as determined by which menu is open OR a header
  666. * as specified.
  667. * @param {Ext.grid.column.Column} [header] Header to unlock from the locked section.
  668. * Defaults to the header which has the menu open currently.
  669. * @param {Number} [toIdx=0] The index to move the unlocked header to.
  670. * @private
  671. */
  672. unlock: function(activeHd, toIdx) {
  673. var me = this,
  674. normalGrid = me.normalGrid,
  675. lockedGrid = me.lockedGrid,
  676. normalHCt = normalGrid.headerCt,
  677. lockedHCt = lockedGrid.headerCt,
  678. refreshLocked = false;
  679. if (!Ext.isDefined(toIdx)) {
  680. toIdx = 0;
  681. }
  682. activeHd = activeHd || lockedHCt.getMenu().activeHeader;
  683. Ext.suspendLayouts();
  684. activeHd.ownerCt.remove(activeHd, false);
  685. if (me.syncLockedWidth()) {
  686. refreshLocked = true;
  687. }
  688. activeHd.locked = false;
  689. // Refresh the locked section first in case it was empty
  690. normalHCt.insert(toIdx, activeHd);
  691. me.normalGrid.getView().refresh();
  692. if (refreshLocked) {
  693. me.lockedGrid.getView().refresh();
  694. }
  695. Ext.resumeLayouts(true);
  696. me.fireEvent('unlockcolumn', me, activeHd);
  697. },
  698. applyColumnsState: function (columns) {
  699. var me = this,
  700. lockedGrid = me.lockedGrid,
  701. lockedHeaderCt = lockedGrid.headerCt,
  702. normalHeaderCt = me.normalGrid.headerCt,
  703. lockedCols = Ext.Array.toMap(lockedHeaderCt.items, 'headerId'),
  704. normalCols = Ext.Array.toMap(normalHeaderCt.items, 'headerId'),
  705. locked = [],
  706. normal = [],
  707. lockedWidth = 1,
  708. length = columns.length,
  709. i, existing,
  710. lockedDefault,
  711. col;
  712. for (i = 0; i &lt; length; i++) {
  713. col = columns[i];
  714. lockedDefault = lockedCols[col.id];
  715. existing = lockedDefault || normalCols[col.id];
  716. if (existing) {
  717. if (existing.applyColumnState) {
  718. existing.applyColumnState(col);
  719. }
  720. if (existing.locked === undefined) {
  721. existing.locked = !!lockedDefault;
  722. }
  723. if (existing.locked) {
  724. locked.push(existing);
  725. if (!existing.hidden &amp;&amp; typeof existing.width == 'number') {
  726. lockedWidth += existing.width;
  727. }
  728. } else {
  729. normal.push(existing);
  730. }
  731. }
  732. }
  733. // state and config must have the same columns (compare counts for now):
  734. if (locked.length + normal.length == lockedHeaderCt.items.getCount() + normalHeaderCt.items.getCount()) {
  735. lockedHeaderCt.removeAll(false);
  736. normalHeaderCt.removeAll(false);
  737. lockedHeaderCt.add(locked);
  738. normalHeaderCt.add(normal);
  739. lockedGrid.setWidth(lockedWidth);
  740. }
  741. },
  742. getColumnsState: function () {
  743. var me = this,
  744. locked = me.lockedGrid.headerCt.getColumnsState(),
  745. normal = me.normalGrid.headerCt.getColumnsState();
  746. return locked.concat(normal);
  747. },
  748. // we want to totally override the reconfigure behaviour here, since we're creating 2 sub-grids
  749. reconfigureLockable: function(store, columns) {
  750. var me = this,
  751. lockedGrid = me.lockedGrid,
  752. normalGrid = me.normalGrid;
  753. if (columns) {
  754. Ext.suspendLayouts();
  755. lockedGrid.headerCt.removeAll();
  756. normalGrid.headerCt.removeAll();
  757. columns = me.processColumns(columns);
  758. lockedGrid.setWidth(columns.lockedWidth);
  759. lockedGrid.headerCt.add(columns.locked.items);
  760. normalGrid.headerCt.add(columns.normal.items);
  761. Ext.resumeLayouts(true);
  762. }
  763. if (store) {
  764. store = Ext.data.StoreManager.lookup(store);
  765. me.store = store;
  766. lockedGrid.bindStore(store);
  767. normalGrid.bindStore(store);
  768. } else {
  769. lockedGrid.getView().refresh();
  770. normalGrid.getView().refresh();
  771. }
  772. },
  773. <span id='Ext-grid-Lockable-method-cloneFeatures'> /**
  774. </span> * Clones items in the features array if they are instantiated Features. If an item
  775. * is just a feature config, it leaves it alone.
  776. *
  777. * This is so that features can be replicated on both sides of the LockingView
  778. *
  779. */
  780. cloneFeatures: function() {
  781. var me = this,
  782. features = me.features,
  783. feature,
  784. i = 0, len;
  785. if (features) {
  786. len = features.length;
  787. for (; i &lt; len; i++) {
  788. feature = features[i];
  789. if (feature.isFeature) {
  790. features[i] = feature.clone();
  791. }
  792. }
  793. }
  794. },
  795. <span id='Ext-grid-Lockable-method-clonePlugins'> /**
  796. </span> * Clones items in the plugins array if they are instantiated Plugins. If an item
  797. * is just a plugin config, it leaves it alone.
  798. *
  799. * This is so that plugins can be replicated on both sides of the LockingView
  800. *
  801. */
  802. clonePlugins: function() {
  803. var me = this,
  804. plugins = me.plugins,
  805. plugin,
  806. i = 0, len;
  807. if (plugins) {
  808. len = plugins.length;
  809. for (; i &lt; len; i++) {
  810. plugin = plugins[i];
  811. if (typeof plugin.init === 'function') {
  812. plugins[i] = plugin.clone();
  813. }
  814. }
  815. }
  816. }
  817. }, function() {
  818. this.borrow(Ext.view.Table, ['constructFeatures']);
  819. this.borrow(Ext.AbstractComponent, ['constructPlugins', 'constructPlugin']);
  820. });
  821. </pre>
  822. </body>
  823. </html>