]> git.proxmox.com Git - extjs.git/blob - extjs/classic/classic/src/panel/Table.js
add extjs 6.0.1 sources
[extjs.git] / extjs / classic / classic / src / panel / Table.js
1 /**
2 * This class is the base class for both {@link Ext.tree.Panel TreePanel} and
3 * {@link Ext.grid.Panel GridPanel}.
4 *
5 * TablePanel aggregates:
6 *
7 * - a Selection Model
8 * - a View
9 * - a Store
10 * - Ext.grid.header.Container
11 *
12 * @mixins Ext.grid.locking.Lockable
13 */
14 Ext.define('Ext.panel.Table', {
15 extend: 'Ext.panel.Panel',
16
17 alias: 'widget.tablepanel',
18
19 requires: [
20 'Ext.layout.container.Fit'
21 ],
22
23 uses: [
24 'Ext.selection.RowModel',
25 'Ext.selection.CellModel',
26 'Ext.selection.CheckboxModel',
27 'Ext.grid.plugin.BufferedRenderer',
28 'Ext.grid.header.Container',
29 'Ext.grid.locking.Lockable',
30 'Ext.grid.NavigationModel'
31 ],
32
33 extraBaseCls: Ext.baseCSSPrefix + 'grid',
34 extraBodyCls: Ext.baseCSSPrefix + 'grid-body',
35 actionableModeCls: Ext.baseCSSPrefix + 'grid-actionable',
36 noHeaderBordersCls: Ext.baseCSSPrefix + 'no-header-borders',
37
38 defaultBindProperty: 'store',
39
40 layout: 'fit',
41
42 ariaRole: 'grid',
43
44 config: {
45 /**
46 * @cfg {Ext.data.Model} selection
47 * The selected model. Typically used with {@link #bind binding}.
48 */
49 selection: null,
50
51 /**
52 * @cfg {Boolean} [headerBorders=`true`]
53 * To show no borders around grid headers, configure this as `false`.
54 */
55 headerBorders: true
56 },
57
58 publishes: ['selection'],
59 twoWayBindable: ['selection'],
60
61 /**
62 * @cfg {Boolean} [autoLoad=false]
63 * Use `true` to load the store as soon as this component is fully constructed. It is
64 * best to initiate the store load this way to allow this component and potentially
65 * its plugins (such as `{@link Ext.grid.filters.Filters}`) to be ready to load.
66 */
67 autoLoad: false,
68
69 /**
70 * @cfg {Boolean} [variableRowHeight=false]
71 * @deprecated 5.0.0 Use {@link Ext.grid.column.Column#variableRowHeight} instead.
72 * Configure as `true` if the row heights are not all the same height as the first row.
73 */
74 variableRowHeight: false,
75
76 /**
77 * @cfg {Number} [numFromEdge]
78 * This configures the zone which causes new rows to be appended to the view. As soon as the edge
79 * of the rendered grid is this number of rows from the edge of the viewport, the view is moved.
80 */
81 numFromEdge: 2,
82
83 /**
84 * @cfg {Number} [trailingBufferZone]
85 * TableViews are buffer rendered in 5.x and above which means that only the visible subset of data rows
86 * are rendered into the DOM. These are removed and added as scrolling demands.
87 *
88 * This configures the number of extra rows to render on the trailing side of scrolling
89 * **outside the {@link #numFromEdge}** buffer as scrolling proceeds.
90 */
91 trailingBufferZone: 10,
92
93 /**
94 * @cfg {Number} [leadingBufferZone]
95 * TableViews are buffer rendered in 5.x and above which means that only the visible subset of data rows
96 * are rendered into the DOM. These are removed and added as scrolling demands.
97 *
98 * This configures the number of extra rows to render on the leading side of scrolling
99 * **outside the {@link #numFromEdge}** buffer as scrolling proceeds.
100 */
101 leadingBufferZone: 20,
102
103 /**
104 * @property {Boolean} hasView
105 * True to indicate that a view has been injected into the panel.
106 */
107 hasView: false,
108
109 /**
110 * @property items
111 * @hide
112 */
113
114 /**
115 * @cfg {String} viewType
116 * An xtype of view to use. This is automatically set to 'tableview' by {@link Ext.grid.Panel Grid}
117 * and to 'treeview' by {@link Ext.tree.Panel Tree}.
118 * @protected
119 */
120 viewType: null,
121
122 /**
123 * @cfg {Object} viewConfig
124 * A config object that will be applied to the grid's UI view. Any of the config options available for
125 * {@link Ext.view.Table} can be specified here. This option is ignored if {@link #view} is specified.
126 */
127
128 /**
129 * @cfg {Ext.view.Table} view
130 * The {@link Ext.view.Table} used by the grid. Use {@link #viewConfig} to just supply some config options to
131 * view (instead of creating an entire View instance).
132 */
133
134 /**
135 * @cfg {String} [selType]
136 * An xtype of selection model to use. This is used to create selection model if just
137 * a config object or nothing at all given in {@link #selModel} config.
138 *
139 * @deprecated 5.1.0 Use the {@link #selModel}'s `type` property. Or, if no other
140 * configs are required, use the string form of selModel.
141 */
142
143 /**
144 * @cfg {Ext.selection.Model/Object/String} [selModel=rowmodel]
145 * A {@link Ext.selection.Model selection model} instance or config object, or the selection model class's alias string.
146 *
147 * In latter case its `type` property determines to which type of selection model this config is applied.
148 */
149
150 /**
151 * @cfg {Boolean} [multiSelect=false]
152 * True to enable 'MULTI' selection mode on selection model.
153 * @deprecated 4.1.1 Use {@link Ext.selection.Model#mode} 'MULTI' instead.
154 */
155
156 /**
157 * @cfg {Boolean} [simpleSelect=false]
158 * True to enable 'SIMPLE' selection mode on selection model.
159 * @deprecated 4.1.1 Use {@link Ext.selection.Model#mode} 'SIMPLE' instead.
160 */
161
162 /**
163 * @cfg {Ext.data.Store/String/Object} store (required)
164 * The data source to which the grid / tree is bound. Acceptable values for this
165 * property are:
166 *
167 * - **any {@link Ext.data.Store Store} class / subclass**
168 * - **an {@link Ext.data.Store#storeId ID of a store}**
169 * - **a {@link Ext.data.Store Store} config object**. When passing a config you can
170 * specify the store type by alias. Passing a config object with a store type will
171 * dynamically create a new store of that type when the grid / tree is instantiated.
172 *
173 * For example:
174 *
175 * Ext.define('MyApp.store.Customers', {
176 * extend: 'Ext.data.Store',
177 * alias: 'store.customerstore',
178 * fields: ['name']
179 * });
180 *
181 * Ext.create({
182 * xtype: 'gridpanel',
183 * renderTo: document.body,
184 * store: {
185 * type: 'customerstore',
186 * data: [{
187 * name: 'Foo'
188 * }]
189 * },
190 * columns: [{
191 * text: 'Name',
192 * dataIndex: 'name'
193 * }]
194 * });
195 */
196
197 /**
198 * @cfg {String/Boolean} scroll
199 * Scrollers configuration. Valid values are 'both', 'horizontal' or 'vertical'.
200 * True implies 'both'. False implies 'none'.
201 * @deprecated 5.1.0 Use {@link #scrollable} instead
202 */
203
204 /**
205 * @cfg {Boolean} [reserveScrollbar=false]
206 * Set this to true to **always** leave a scrollbar sized space at the end of the grid content when
207 * fitting content into the width of the grid.
208 *
209 * If the grid's record count fluctuates enough to hide and show the scrollbar regularly, this setting
210 * avoids the multiple layouts associated with switching from scrollbar present to scrollbar not present.
211 */
212
213 /**
214 * @cfg {Ext.grid.column.Column[]/Object} columns
215 * An array of {@link Ext.grid.column.Column column} definition objects which define all columns that appear in this
216 * grid. Each column definition provides the header text for the column, and a definition of where the data for that
217 * column comes from.
218 *
219 * This can also be a configuration object for a {@link Ext.grid.header.Container HeaderContainer} which may override
220 * certain default configurations if necessary. For example, the special layout may be overridden to use a simpler
221 * layout, or one can set default values shared by all columns:
222 *
223 * columns: {
224 * items: [
225 * {
226 * text: "Column A",
227 * dataIndex: "field_A"
228 * },{
229 * text: "Column B",
230 * dataIndex: "field_B"
231 * },
232 * ...
233 * ],
234 * defaults: {
235 * flex: 1
236 * }
237 * }
238 */
239
240 /**
241 * @cfg {Boolean} forceFit
242 * True to force the columns to fit into the available width. Headers are first sized according to configuration,
243 * whether that be a specific width, or flex. Then they are all proportionally changed in width so that the entire
244 * content width is used. For more accurate control, it is more optimal to specify a flex setting on the columns
245 * that are to be stretched & explicit widths on columns that are not.
246 */
247
248 /**
249 * @cfg {Ext.grid.feature.Feature[]/Object[]/Ext.enums.Feature[]} features
250 * An array of grid Features to be added to this grid. Can also be just a single feature instead of array.
251 *
252 * Features config behaves much like {@link #plugins}.
253 * A feature can be added by either directly referencing the instance:
254 *
255 * features: [Ext.create('Ext.grid.feature.GroupingSummary', {groupHeaderTpl: 'Subject: {name}'})],
256 *
257 * By using config object with ftype:
258 *
259 * features: [{ftype: 'groupingsummary', groupHeaderTpl: 'Subject: {name}'}],
260 *
261 * Or with just a ftype:
262 *
263 * features: ['grouping', 'groupingsummary'],
264 *
265 * See {@link Ext.enums.Feature} for list of all ftypes.
266 */
267
268 /**
269 * @cfg {Boolean} [hideHeaders=false]
270 * True to hide column headers.
271 */
272
273 /**
274 * @cfg {Boolean} [deferRowRender=false]
275 * Configure as `true` to enable deferred row rendering.
276 *
277 * This allows the View to execute a refresh quickly, with the update of the row structure deferred so
278 * that layouts with GridPanels appear, and lay out more quickly.
279 */
280 deferRowRender: false,
281
282 /**
283 * @cfg {Boolean} [sortableColumns=true]
284 * False to disable column sorting via clicking the header and via the Sorting menu items.
285 */
286 sortableColumns: true,
287
288 /**
289 * @cfg {Boolean} [multiColumnSort=false]
290 * Configure as `true` to have columns remember their sorted state after other columns have been clicked upon to sort.
291 *
292 * As subsequent columns are clicked upon, they become the new primary sort key.
293 *
294 * The maximum number of sorters allowed in a Store is configurable via its underlying data collection. See {@link Ext.util.Collection#multiSortLimit}
295 */
296 multiColumnSort: false,
297
298 /**
299 * @cfg {Boolean} [enableLocking=false]
300 * Configure as `true` to enable locking support for this grid. Alternatively, locking will also be automatically
301 * enabled if any of the columns in the {@link #columns columns} configuration contain a {@link Ext.grid.column.Column#locked locked} config option.
302 *
303 * A locking grid is processed in a special way. The configuration options are cloned and *two* grids are created to be the locked (left) side
304 * and the normal (right) side. This Panel becomes merely a {@link Ext.container.Container container} which arranges both in an {@link Ext.layout.container.HBox HBox} layout.
305 *
306 * {@link #plugins Plugins} may be targeted at either locked, or unlocked grid, or, both, in which case the plugin is cloned and used on both sides.
307 *
308 * Plugins may also be targeted at the containing locking Panel.
309 *
310 * This is configured by specifying a `lockableScope` property in your plugin which may have the following values:
311 *
312 * * `"both"` (the default) - The plugin is added to both grids
313 * * `"top"` - The plugin is added to the containing Panel
314 * * `"locked"` - The plugin is added to the locked (left) grid
315 * * `"normal"` - The plugin is added to the normal (right) grid
316 *
317 * If `both` is specified, then each copy of the plugin gains a property `lockingPartner` which references its sibling on the other side so that they
318 * can synchronize operations is necessary.
319 *
320 * {@link #features Features} may also be configured with `lockableScope` and may target the locked grid, the normal grid or both grids. Features
321 * also get a `lockingPartner` reference injected.
322 */
323 enableLocking: false,
324
325 /**
326 * @private
327 * Used to determine where to go down to find views
328 * this is here to support locking.
329 */
330 scrollerOwner: true,
331
332 /**
333 * @cfg {Boolean} [enableColumnMove=true]
334 * False to disable column dragging within this grid.
335 */
336 enableColumnMove: true,
337
338 /**
339 * @cfg {Boolean} [sealedColumns=false]
340 * True to constrain column dragging so that a column cannot be dragged in or out of it's
341 * current group. Only relevant while {@link #enableColumnMove} is enabled.
342 */
343 sealedColumns: false,
344
345 /**
346 * @cfg {Boolean} [enableColumnResize=true]
347 * False to disable column resizing within this grid.
348 */
349 enableColumnResize: true,
350
351 /**
352 * @cfg {Boolean} [enableColumnHide=true]
353 * False to disable column hiding within this grid.
354 */
355
356 /**
357 * @cfg {Boolean} columnLines Adds column line styling
358 */
359
360 /**
361 * @cfg {Boolean} [rowLines=true] Adds row line styling
362 */
363 rowLines: true,
364
365 /**
366 * @cfg {Boolean} [disableSelection=false]
367 * True to disable selection model.
368 */
369
370 /**
371 * @cfg {String} emptyText Default text (HTML tags are accepted) to display in the
372 * Panel body when the Store is empty. When specified, and the Store is empty, the
373 * text will be rendered inside a DIV with the CSS class "x-grid-empty". The emptyText
374 * will not display until the first load of the associated store by default. If you
375 * want the text to be displayed prior to the first store load use the
376 * {@link Ext.view.Table#deferEmptyText deferEmptyText} config in the {@link #viewConfig} config.
377 */
378
379 /**
380 * @cfg {Boolean} [allowDeselect=false]
381 * True to allow deselecting a record. This config is forwarded to {@link Ext.selection.Model#allowDeselect}.
382 */
383
384 /**
385 * @cfg {Boolean} [bufferedRenderer=true]
386 * Buffered rendering is enabled by default.
387 *
388 * Configure as `false` to disable buffered rendering. See {@link Ext.grid.plugin.BufferedRenderer}.
389 *
390 * @since 5.0.0
391 */
392 bufferedRenderer: true,
393
394 /**
395 * @cfg stateEvents
396 * @inheritdoc Ext.state.Stateful#cfg-stateEvents
397 * @localdoc By default the following stateEvents are added:
398 *
399 * - {@link #event-resize} - _(added by Ext.Component)_
400 * - {@link #event-collapse} - _(added by Ext.panel.Panel)_
401 * - {@link #event-expand} - _(added by Ext.panel.Panel)_
402 * - {@link #event-columnresize}
403 * - {@link #event-columnmove}
404 * - {@link #event-columnhide}
405 * - {@link #event-columnshow}
406 * - {@link #event-sortchange}
407 * - {@link #event-filterchange}
408 * - {@link #event-groupchange}
409 */
410
411 /**
412 * @property {Boolean} optimizedColumnMove
413 * If you are writing a grid plugin or a {Ext.grid.feature.Feature Feature} which creates a column-based structure which
414 * needs a view refresh when columns are moved, then set this property in the grid.
415 *
416 * An example is the built in {@link Ext.grid.feature.AbstractSummary Summary} Feature. This creates summary rows, and the
417 * summary columns must be in the same order as the data columns. This plugin sets the `optimizedColumnMove` to `false.
418 */
419
420 /**
421 * @property {Ext.panel.Table} ownerGrid
422 * A reference to the top-level owning grid component.
423 *
424 * This is a reference to this GridPanel if this GridPanel is not part of a locked grid arrangement.
425 * @readonly
426 * @private
427 * @since 5.0.0
428 */
429 ownerGrid: null,
430
431 colLinesCls: Ext.baseCSSPrefix + 'grid-with-col-lines',
432 rowLinesCls: Ext.baseCSSPrefix + 'grid-with-row-lines',
433 noRowLinesCls: Ext.baseCSSPrefix + 'grid-no-row-lines',
434 hiddenHeaderCtCls: Ext.baseCSSPrefix + 'grid-header-ct-hidden',
435 hiddenHeaderCls: Ext.baseCSSPrefix + 'grid-header-hidden',
436 resizeMarkerCls: Ext.baseCSSPrefix + 'grid-resize-marker',
437 emptyCls: Ext.baseCSSPrefix + 'grid-empty',
438
439 // The TablePanel claims to be focusable, but it does not place a tabIndex
440 // on any of its elements.
441 // Its focus implementation delegates to its view. TableViews are focusable.
442 focusable: true,
443
444 /**
445 * @event viewready
446 * Fires when the grid view is available (use this for selecting a default row).
447 * @param {Ext.panel.Table} this
448 */
449
450 constructor: function (config) {
451 var me = this,
452 topGrid = config && config.ownerGrid,
453 store;
454
455 me.ownerGrid = topGrid || me;
456
457 /**
458 * @property {Array} actionables An array of objects which register themselves with a grid panel using
459 * {@link #registerActionable} which are consulted upon entry into actionable mode.
460 *
461 * These must implement the following methods:
462 *
463 * - activateCell Called when actionable mode is requested upon a cell. A {@link Ext.grid.CellContext CellContext}
464 * object is passed. If that cell is actionable by the terms of the callee, the callee should return `true` if it
465 * ascertains that the cell is actionable, and that it now contains focusable elements which may be tabbed to.
466 * - activateRow Called when the user enters actionable mode in a row. The row DOM is passed. Actionables
467 * should take any action they need to prime the row for cell activation which happens as users TAB from cell to cell.
468 * @readonly
469 */
470 me.actionables = topGrid ? topGrid.actionables : []; // One shared array when there's a lockable at the top
471
472 me.callParent([config]);
473
474 store = me.store;
475
476 // Any further changes become stateful.
477 store.trackStateChanges = true;
478
479 if (me.autoLoad) {
480 // Note: if there is a store bound by a VM, we (might) do the load in #setStore.
481 if (!store.isEmptyStore) {
482 store.load();
483 }
484 }
485 },
486
487 /**
488 *
489 * @param {Object} actionable An object which has an interest in the implementation of actionable mode in
490 * this grid.
491 *
492 * An actionable object may be a Plugin which upon activation injects tabbable elements or Components into
493 * a grid row.
494 */
495 registerActionable: function(actionable) {
496 // If a lockableScope: 'both' plugin/feature registers on each side, only include it in the actionables once.
497 Ext.Array.include(this.actionables, actionable);
498 },
499
500 initComponent: function() {
501 //<debug>
502 if (this.verticalScroller) {
503 Ext.raise("The verticalScroller config is not supported.");
504 }
505 if (!this.viewType) {
506 Ext.raise("You must specify a viewType config.");
507 }
508 if (this.headers) {
509 Ext.raise("The headers config is not supported. Please specify columns instead.");
510 }
511 //</debug>
512
513 var me = this,
514 headerCtCfg = me.columns || me.colModel || [],
515 store, view, i, len, bufferedRenderer, columns, viewScroller, headerCt;
516
517 // Look up the configured Store. If none configured, use the fieldless, empty Store
518 // defined in Ext.data.Store.
519 store = me.store = Ext.data.StoreManager.lookup(me.store || 'ext-empty-store');
520
521 me.enableLocking = me.enableLocking || me.hasLockedColumns(headerCtCfg);
522
523 // Construct the plugins now rather than in the constructor of AbstractComponent because the component may have a subclass
524 // that has overridden initComponent and defined plugins in it. For plugins like RowExpander that rely upon a grid feature,
525 // this is a problem because the view needs to know about all its features before it's constructed. Constructing the plugins
526 // now ensures that plugins defined in the instance config or in initComponent are all constructed before the view.
527 // See EXTJSIV-11927.
528 //
529 // Note that any components that do not inherit from this class will still have their plugins constructed in
530 // AbstractComponent:initComponent.
531 if (me.plugins) {
532 me.plugins = me.constructPlugins();
533 }
534
535 // Add the row/column line classes to the body element so that the settings are not inherited by docked grids (https://sencha.jira.com/browse/EXTJSIV-9263).
536 if (me.columnLines) {
537 me.addBodyCls(me.colLinesCls);
538 }
539
540 me.addBodyCls(me.rowLines ? me.rowLinesCls : me.noRowLinesCls);
541 me.addBodyCls(me.extraBodyCls);
542
543
544 // If any of the Column objects contain a locked property, and are not processed, this is a lockable TablePanel, a
545 // special view will be injected by the Ext.grid.locking.Lockable mixin, so no processing of .
546 if (me.enableLocking) {
547 me.self.mixin('lockable', Ext.grid.locking.Lockable);
548 me.injectLockable();
549 headerCt = me.headerCt;
550 }
551 // Not lockable - create the HeaderContainer
552 else {
553 // It's a fully instantiated HeaderContainer
554 if (headerCtCfg.isRootHeader) {
555 if (me.hideHeaders) {
556 headerCtCfg.setHeight(0);
557 // don't set the hidden property, we still need these to layout
558 headerCtCfg.hiddenHeaders = true;
559 } else {
560 // the header container is not user scrollable, but it has a scroller instance
561 // so that we can sync its scroll position with that of the grid view
562 headerCtCfg.setScrollable({
563 x: false,
564 y: false
565 });
566 }
567
568 me.headerCt = headerCt = headerCtCfg;
569
570 headerCt.grid = me;
571 headerCt.forceFit = !!me.forceFit;
572 headerCt.$initParent = me;
573
574 // If it's an instance then the column managers were already created and bound to the headerCt.
575 me.columnManager = headerCtCfg.columnManager;
576 me.visibleColumnManager = headerCtCfg.visibleColumnManager;
577 }
578 // It's an array of Column definitions, or a config object of a HeaderContainer
579 else {
580 if (Ext.isArray(headerCtCfg)) {
581 headerCtCfg = {
582 items: headerCtCfg
583 };
584 }
585 Ext.apply(headerCtCfg, {
586 grid: me,
587 $initParent: me,
588 forceFit: me.forceFit,
589 sortable: me.sortableColumns,
590 enableColumnMove: me.enableColumnMove,
591 enableColumnResize: me.enableColumnResize,
592 columnLines: me.columnLines,
593 sealed: me.sealedColumns,
594 // the header container is not user scrollable, but if it is visible,
595 // it has a scroller instance so that we can sync its scroll position with that of the grid view
596 scrollable: me.hideHeaders ? undefined : {
597 x: false,
598 y: false
599 }
600 });
601 if (me.hideHeaders) {
602 headerCtCfg.height = 0;
603 // don't set the hidden property, we still need these to layout
604 headerCtCfg.hiddenHeaders = true;
605 }
606
607 if (Ext.isDefined(me.enableColumnHide)) {
608 headerCtCfg.enableColumnHide = me.enableColumnHide;
609 }
610 me.headerCt = headerCt = new Ext.grid.header.Container(headerCtCfg);
611 }
612 }
613
614 // Maintain backward compatibiliy by providing the initial leaf column set as a property.
615 me.columns = columns = headerCt.getGridColumns();
616
617 me.scrollTask = new Ext.util.DelayedTask(me.syncHorizontalScroll, me);
618
619 me.cls = (me.cls || '') + (' ' + me.extraBaseCls);
620
621 // autoScroll is not a valid configuration
622 delete me.autoScroll;
623
624 bufferedRenderer = me.plugins && Ext.Array.findBy(me.plugins, function(p) {
625 return p.isBufferedRenderer;
626 });
627
628 // If we find one in the plugins, just use that.
629 if (bufferedRenderer) {
630 me.bufferedRenderer = bufferedRenderer;
631 }
632
633 // If this TablePanel is lockable (Either configured lockable, or any of the defined columns has a 'locked' property)
634 // then a special lockable view containing 2 side-by-side grids will have been injected so we do not need to set up any UI.
635 if (!me.hasView) {
636
637 // If the Store is paging blocks of the dataset in, then it can only be sorted remotely.
638 if (store.isBufferedStore && !store.getRemoteSort()) {
639 for (i = 0, len = columns.length; i < len; i++) {
640 columns[i].sortable = false;
641 }
642 }
643
644 if (me.hideHeaders) {
645 me.headerCt.addCls(me.hiddenHeaderCtCls);
646 me.addCls(me.hiddenHeaderCls);
647 }
648
649 me.relayHeaderCtEvents(headerCt);
650 me.features = me.features || [];
651 if (!Ext.isArray(me.features)) {
652 me.features = [me.features];
653 }
654 me.dockedItems = [].concat(me.dockedItems || []);
655 me.dockedItems.unshift(headerCt);
656 me.viewConfig = me.viewConfig || {};
657
658 // AbstractDataView will look up a Store configured as an object
659 // getView converts viewConfig into a View instance
660 view = me.getView();
661
662 me.items = [view];
663 me.hasView = true;
664
665 // Add a listener to synchronize the horizontal scroll position of the headers
666 // with the table view's element... Unless we are not showing headers!
667 if (!me.hideHeaders) {
668 // sync the horizontal scroll position of the headerCt as the view is scrolled.
669 viewScroller = view.getScrollable();
670 if (viewScroller) {
671 headerCt.getScrollable().addPartner(viewScroller, 'x');
672 }
673 }
674
675 // Attach this Panel to the Store
676 me.bindStore(store, true);
677
678 me.mon(view, {
679 viewready: me.onViewReady,
680 refresh: me.onRestoreHorzScroll,
681 scope: me
682 });
683 }
684
685 // Whatever kind of View we have, be it a TableView, or a LockingView, we are interested in the selection model
686 me.selModel = me.view.getSelectionModel();
687 if (me.selModel.isRowModel) {
688 me.selModel.on({
689 scope: me,
690 lastselectedchanged: me.updateBindSelection,
691 selectionchange: me.updateBindSelection
692 });
693 }
694
695 // Relay events from the View whether it be a LockingView, or a regular GridView
696 me.relayEvents(me.view, [
697 /**
698 * @event beforeitemmousedown
699 * @inheritdoc Ext.view.View#beforeitemmousedown
700 */
701 'beforeitemmousedown',
702 /**
703 * @event beforeitemmouseup
704 * @inheritdoc Ext.view.View#beforeitemmouseup
705 */
706 'beforeitemmouseup',
707 /**
708 * @event beforeitemmouseenter
709 * @inheritdoc Ext.view.View#beforeitemmouseenter
710 */
711 'beforeitemmouseenter',
712 /**
713 * @event beforeitemmouseleave
714 * @inheritdoc Ext.view.View#beforeitemmouseleave
715 */
716 'beforeitemmouseleave',
717 /**
718 * @event beforeitemclick
719 * @inheritdoc Ext.view.View#beforeitemclick
720 */
721 'beforeitemclick',
722 /**
723 * @event beforeitemdblclick
724 * @inheritdoc Ext.view.View#beforeitemdblclick
725 */
726 'beforeitemdblclick',
727 /**
728 * @event beforeitemcontextmenu
729 * @inheritdoc Ext.view.View#beforeitemcontextmenu
730 */
731 'beforeitemcontextmenu',
732 /**
733 * @event itemmousedown
734 * @inheritdoc Ext.view.View#itemmousedown
735 */
736 'itemmousedown',
737 /**
738 * @event itemmouseup
739 * @inheritdoc Ext.view.View#itemmouseup
740 */
741 'itemmouseup',
742 /**
743 * @event itemmouseenter
744 * @inheritdoc Ext.view.View#itemmouseenter
745 */
746 'itemmouseenter',
747 /**
748 * @event itemmouseleave
749 * @inheritdoc Ext.view.View#itemmouseleave
750 */
751 'itemmouseleave',
752 /**
753 * @event itemclick
754 * @inheritdoc Ext.view.View#itemclick
755 */
756 'itemclick',
757 /**
758 * @event itemdblclick
759 * @inheritdoc Ext.view.View#itemdblclick
760 */
761 'itemdblclick',
762 /**
763 * @event itemcontextmenu
764 * @inheritdoc Ext.view.View#itemcontextmenu
765 */
766 'itemcontextmenu',
767 /**
768 * @event beforecellclick
769 * @inheritdoc Ext.view.Table#beforecellclick
770 */
771 'beforecellclick',
772 /**
773 * @event cellclick
774 * @inheritdoc Ext.view.Table#cellclick
775 */
776 'cellclick',
777 /**
778 * @event beforecelldblclick
779 * @inheritdoc Ext.view.Table#beforecelldblclick
780 */
781 'beforecelldblclick',
782 /**
783 * @event celldblclick
784 * @inheritdoc Ext.view.Table#celldblclick
785 */
786 'celldblclick',
787 /**
788 * @event beforecellcontextmenu
789 * @inheritdoc Ext.view.Table#beforecellcontextmenu
790 */
791 'beforecellcontextmenu',
792 /**
793 * @event cellcontextmenu
794 * @inheritdoc Ext.view.Table#cellcontextmenu
795 */
796 'cellcontextmenu',
797 /**
798 * @event beforecellmousedown
799 * @inheritdoc Ext.view.Table#beforecellmousedown
800 */
801 'beforecellmousedown',
802 /**
803 * @event cellmousedown
804 * @inheritdoc Ext.view.Table#cellmousedown
805 */
806 'cellmousedown',
807 /**
808 * @event beforecellmouseup
809 * @inheritdoc Ext.view.Table#beforecellmouseup
810 */
811 'beforecellmouseup',
812 /**
813 * @event cellmouseup
814 * @inheritdoc Ext.view.Table#cellmouseup
815 */
816 'cellmouseup',
817 /**
818 * @event beforecellkeydown
819 * @inheritdoc Ext.view.Table#beforecellkeydown
820 */
821 'beforecellkeydown',
822 /**
823 * @event cellkeydown
824 * @inheritdoc Ext.view.Table#cellkeydown
825 */
826 'cellkeydown',
827 /**
828 * @event rowclick
829 * @inheritdoc Ext.view.Table#rowclick
830 */
831 'rowclick',
832 /**
833 * @event rowdblclick
834 * @inheritdoc Ext.view.Table#rowdblclick
835 */
836 'rowdblclick',
837 /**
838 * @event rowcontextmenu
839 * @inheritdoc Ext.view.Table#rowcontextmenu
840 */
841 'rowcontextmenu',
842 /**
843 * @event rowmousedown
844 * @inheritdoc Ext.view.Table#rowmousedown
845 */
846 'rowmousedown',
847 /**
848 * @event rowmouseup
849 * @inheritdoc Ext.view.Table#rowmouseup
850 */
851 'rowmouseup',
852 /**
853 * @event rowkeydown
854 * @inheritdoc Ext.view.Table#rowkeydown
855 */
856 'rowkeydown',
857 /**
858 * @event beforeitemkeydown
859 * @inheritdoc Ext.view.Table#beforeitemkeydown
860 */
861 'beforeitemkeydown',
862 /**
863 * @event itemkeydown
864 * @inheritdoc Ext.view.Table#itemkeydown
865 */
866 'itemkeydown',
867 /**
868 * @event beforeitemkeyup
869 * @inheritdoc Ext.view.Table#beforeitemkeyup
870 */
871 'beforeitemkeyup',
872 /**
873 * @event itemkeyup
874 * @inheritdoc Ext.view.Table#itemkeyup
875 */
876 'itemkeyup',
877 /**
878 * @event beforeitemkeypress
879 * @inheritdoc Ext.view.Table#beforeitemkeypress
880 */
881 'beforeitemkeypress',
882 /**
883 * @event itemkeypress
884 * @inheritdoc Ext.view.Table#itemkeypress
885 */
886 'itemkeypress',
887 /**
888 * @event beforecontainermousedown
889 * @inheritdoc Ext.view.View#beforecontainermousedown
890 */
891 'beforecontainermousedown',
892 /**
893 * @event beforecontainermouseup
894 * @inheritdoc Ext.view.View#beforecontainermouseup
895 */
896 'beforecontainermouseup',
897 /**
898 * @event beforecontainermouseover
899 * @inheritdoc Ext.view.View#beforecontainermouseover
900 */
901 'beforecontainermouseover',
902 /**
903 * @event beforecontainermouseout
904 * @inheritdoc Ext.view.View#beforecontainermouseout
905 */
906 'beforecontainermouseout',
907 /**
908 * @event beforecontainerclick
909 * @inheritdoc Ext.view.View#beforecontainerclick
910 */
911 'beforecontainerclick',
912 /**
913 * @event beforecontainerdblclick
914 * @inheritdoc Ext.view.View#beforecontainerdblclick
915 */
916 'beforecontainerdblclick',
917 /**
918 * @event beforecontainercontextmenu
919 * @inheritdoc Ext.view.View#beforecontainercontextmenu
920 */
921 'beforecontainercontextmenu',
922 /**
923 * @event beforecontainerkeydown
924 * @inheritdoc Ext.view.View#beforecontainerkeydown
925 */
926 'beforecontainerkeydown',
927 /**
928 * @event beforecontainerkeyup
929 * @inheritdoc Ext.view.View#beforecontainerkeyup
930 */
931 'beforecontainerkeyup',
932 /**
933 * @event beforecontainerkeypress
934 * @inheritdoc Ext.view.View#beforecontainerkeypress
935 */
936 'beforecontainerkeypress',
937 /**
938 * @event containermouseup
939 * @inheritdoc Ext.view.View#containermouseup
940 */
941 'containermouseup',
942 /**
943 * @event containermousedown
944 * @inheritdoc Ext.view.View#containermousedown
945 */
946 'containermousedown',
947 /**
948 * @event containermouseover
949 * @inheritdoc Ext.view.View#containermouseover
950 */
951 'containermouseover',
952 /**
953 * @event containermouseout
954 * @inheritdoc Ext.view.View#containermouseout
955 */
956 'containermouseout',
957 /**
958 * @event containerclick
959 * @inheritdoc Ext.view.View#containerclick
960 */
961 'containerclick',
962 /**
963 * @event containerdblclick
964 * @inheritdoc Ext.view.View#containerdblclick
965 */
966 'containerdblclick',
967 /**
968 * @event containercontextmenu
969 * @inheritdoc Ext.view.View#containercontextmenu
970 */
971 'containercontextmenu',
972 /**
973 * @event containerkeydown
974 * @inheritdoc Ext.view.View#containerkeydown
975 */
976 'containerkeydown',
977 /**
978 * @event containerkeyup
979 * @inheritdoc Ext.view.View#containerkeyup
980 */
981 'containerkeyup',
982 /**
983 * @event containerkeypress
984 * @inheritdoc Ext.view.View#containerkeypress
985 */
986 'containerkeypress',
987 /**
988 * @event selectionchange
989 * @inheritdoc Ext.selection.Model#selectionchange
990 */
991 'selectionchange',
992 /**
993 * @event beforeselect
994 * @inheritdoc Ext.selection.RowModel#beforeselect
995 */
996 'beforeselect',
997 /**
998 * @event select
999 * @inheritdoc Ext.selection.RowModel#select
1000 */
1001 'select',
1002 /**
1003 * @event beforedeselect
1004 * @inheritdoc Ext.selection.RowModel#beforedeselect
1005 */
1006 'beforedeselect',
1007 /**
1008 * @event deselect
1009 * @inheritdoc Ext.selection.RowModel#deselect
1010 */
1011 'deselect'
1012 ]);
1013
1014 me.callParent();
1015 if (me.enableLocking) {
1016 me.afterInjectLockable();
1017 } else {
1018 delete headerCt.$initParent;
1019 }
1020 me.addStateEvents(['columnresize', 'columnmove', 'columnhide', 'columnshow', 'sortchange', 'filterchange', 'groupchange']);
1021
1022 // rowBody feature events
1023 /**
1024 * @event beforerowbodymousedown
1025 * @preventable
1026 * @inheritdoc Ext.view.Table#event-beforerowbodymousedown
1027 */
1028
1029 /**
1030 * @event beforerowbodymouseup
1031 * @preventable
1032 * @inheritdoc Ext.view.Table#event-beforerowbodymouseup
1033 */
1034
1035 /**
1036 * @event beforerowbodyclick
1037 * @preventable
1038 * @inheritdoc Ext.view.Table#event-beforerowbodyclick
1039 */
1040
1041 /**
1042 * @event beforerowbodydblclick
1043 * @preventable
1044 * @inheritdoc Ext.view.Table#event-beforerowbodydblclick
1045 */
1046
1047 /**
1048 * @event beforerowbodycontextmenu
1049 * @preventable
1050 * @inheritdoc Ext.view.Table#event-beforerowbodycontextmenu
1051 */
1052
1053 /**
1054 * @event beforerowbodylongpress
1055 * @preventable
1056 * @inheritdoc Ext.view.Table#event-beforerowbodylongpress
1057 */
1058
1059 /**
1060 * @event beforerowbodykeydown
1061 * @preventable
1062 * @inheritdoc Ext.view.Table#event-beforerowbodykeydown
1063 */
1064
1065 /**
1066 * @event beforerowbodykeyup
1067 * @preventable
1068 * @inheritdoc Ext.view.Table#event-beforerowbodykeyup
1069 */
1070
1071 /**
1072 * @event beforerowbodykeypress
1073 * @preventable
1074 * @inheritdoc Ext.view.Table#event-beforerowbodykeypress
1075 */
1076
1077 /**
1078 * @event rowbodymousedown
1079 * @inheritdoc Ext.view.Table#event-rowbodymousedown
1080 */
1081
1082 /**
1083 * @event rowbodymouseup
1084 * @inheritdoc Ext.view.Table#event-rowbodymouseup
1085 */
1086
1087 /**
1088 * @event rowbodyclick
1089 * @inheritdoc Ext.view.Table#event-rowbodyclick
1090 */
1091
1092 /**
1093 * @event rowbodydblclick
1094 * @inheritdoc Ext.view.Table#event-rowbodydblclick
1095 */
1096
1097 /**
1098 * @event rowbodycontextmenu
1099 * @inheritdoc Ext.view.Table#event-rowbodycontextmenu
1100 */
1101
1102 /**
1103 * @event rowbodylongpress
1104 * @inheritdoc Ext.view.Table#event-rowbodylongpress
1105 */
1106
1107 /**
1108 * @event rowbodykeydown
1109 * @inheritdoc Ext.view.Table#event-rowbodykeydown
1110 */
1111
1112 /**
1113 * @event rowbodykeyup
1114 * @inheritdoc Ext.view.Table#event-rowbodykeyup
1115 */
1116
1117 /**
1118 * @event rowbodykeypress
1119 * @inheritdoc Ext.view.Table#event-rowbodykeypress
1120 */
1121 },
1122
1123 beforeRender: function() {
1124 var me = this,
1125 bufferedRenderer = me.bufferedRenderer,
1126 ariaAttr;
1127
1128 // If this is the topmost container of a lockable assembly, add the special class body
1129 if (me.lockable) {
1130 me.getProtoBody().addCls(me.lockingBodyCls);
1131 }
1132
1133 // Don't create a buffered renderer for a locked grid.
1134 else {
1135 // If we're auto heighting, we can't buffered render, so don't create it
1136 if (bufferedRenderer && me.getSizeModel().height.auto) {
1137 //<debug>
1138 if (bufferedRenderer.isBufferedRenderer) {
1139 Ext.raise('Cannot use buffered rendering with auto height');
1140 }
1141 //</debug>
1142 me.bufferedRenderer = bufferedRenderer = false;
1143 }
1144
1145 if (bufferedRenderer && !bufferedRenderer.isBufferedRenderer) {
1146 // Create a BufferedRenderer as a plugin if we have not already configured with one.
1147 bufferedRenderer = {
1148 xclass: 'Ext.grid.plugin.BufferedRenderer'
1149 };
1150 Ext.copy(bufferedRenderer, me, 'variableRowHeight,numFromEdge,trailingBufferZone,leadingBufferZone,scrollToLoadBuffer');
1151 me.bufferedRenderer = me.addPlugin(bufferedRenderer);
1152 }
1153
1154 ariaAttr = me.ariaRenderAttributes || (me.ariaRenderAttributes = {});
1155 ariaAttr['aria-readonly'] = !me.isEditable;
1156 ariaAttr['aria-multiselectable'] = me.selModel.selectionMode !== 'SINGLE';
1157 }
1158
1159 me.callParent(arguments);
1160 },
1161
1162 onRender: function() {
1163 var me = this,
1164 gridPanelBorderWidth,
1165 totalColumnWidth;
1166
1167 // If this is the locked side, include border width in calculated locked grid width.
1168 // TODO: Use shrinkWrapDock on the locked grid's headerCt when it works.
1169 if (me.isLocked && me.getSizeModel().width.shrinkWrap) {
1170 me.shrinkWrapColumns = true;
1171 totalColumnWidth = me.headerCt.getTableWidth();
1172 //<debug>
1173 if (isNaN(totalColumnWidth)) {
1174 Ext.raise("Locked columns in an unsized locked side do NOT support a flex width.");
1175 }
1176 //</debug>
1177 gridPanelBorderWidth = me.gridPanelBorderWidth || (me.gridPanelBorderWidth = me.el.getBorderWidth('lr'));
1178 me.width = totalColumnWidth + gridPanelBorderWidth;
1179 }
1180 me.callParent();
1181 },
1182
1183 /**
1184 * Gets the {@link Ext.grid.header.Container headercontainer} for this grid / tree.
1185 * @return {Ext.grid.header.Container} headercontainer
1186 *
1187 * **Note:** While a locked grid / tree will return an instance of
1188 * {@link Ext.grid.locking.HeaderContainer} you will code to the
1189 * {@link Ext.grid.header.Container} API.
1190 */
1191 getHeaderContainer: function () {
1192 return this.getView().getHeaderCt();
1193 },
1194
1195 /**
1196 * @inheritdoc Ext.grid.header.Container#getGridColumns
1197 */
1198 getColumns: function () {
1199 return this.getColumnManager().getColumns();
1200 },
1201
1202 /**
1203 * @inheritdoc Ext.grid.header.Container#getVisibleGridColumns
1204 */
1205 getVisibleColumns: function () {
1206 return this.getVisibleColumnManager().getColumns();
1207 },
1208
1209 focus: function() {
1210 // TablePanel is not focusable, but allow a call to delegate into the view
1211 this.getView().focus();
1212 },
1213
1214 /**
1215 * Disables interaction with, and masks this grid's column headers.
1216 */
1217 disableColumnHeaders: function() {
1218 this.headerCt.disable();
1219 },
1220
1221 /**
1222 * Enables interaction with, and unmasks this grid's column headers after a call to {#disableColumnHeaders}.
1223 */
1224 enableColumnHeaders: function() {
1225 this.headerCt.enable();
1226 },
1227
1228 /**
1229 * @private
1230 * Determine if there are any columns with a locked configuration option.
1231 */
1232 hasLockedColumns: function(columns) {
1233 var i,
1234 len,
1235 column;
1236
1237 // Fully instantiated HeaderContainer
1238 if (columns.isRootHeader) {
1239 columns = columns.items.items;
1240 }
1241 // Config object with items
1242 else if (Ext.isObject(columns)) {
1243 columns = columns.items;
1244 }
1245 for (i = 0, len = columns.length; i < len; i++) {
1246 column = columns[i];
1247 if (!column.processed && column.locked) {
1248 return true;
1249 }
1250 }
1251 },
1252
1253 relayHeaderCtEvents: function (headerCt) {
1254 this.relayEvents(headerCt, [
1255 /**
1256 * @event columnresize
1257 * @inheritdoc Ext.grid.header.Container#columnresize
1258 */
1259 'columnresize',
1260 /**
1261 * @event columnmove
1262 * @inheritdoc Ext.grid.header.Container#columnmove
1263 */
1264 'columnmove',
1265 /**
1266 * @event columnhide
1267 * @inheritdoc Ext.grid.header.Container#columnhide
1268 */
1269 'columnhide',
1270 /**
1271 * @event columnshow
1272 * @inheritdoc Ext.grid.header.Container#columnshow
1273 */
1274 'columnshow',
1275 /**
1276 * @event columnschanged
1277 * @inheritdoc Ext.grid.header.Container#columnschanged
1278 */
1279 'columnschanged',
1280 /**
1281 * @event sortchange
1282 * @inheritdoc Ext.grid.header.Container#sortchange
1283 */
1284 'sortchange',
1285 /**
1286 * @event headerclick
1287 * @inheritdoc Ext.grid.header.Container#headerclick
1288 */
1289 'headerclick',
1290 /**
1291 * @event headercontextmenu
1292 * @inheritdoc Ext.grid.header.Container#headercontextmenu
1293 */
1294 'headercontextmenu',
1295 /**
1296 * @event headertriggerclick
1297 * @inheritdoc Ext.grid.header.Container#headertriggerclick
1298 */
1299 'headertriggerclick'
1300 ]);
1301 },
1302
1303 getState: function(){
1304 var me = this,
1305 state = me.callParent(),
1306 storeState = me.store.getState();
1307
1308 state = me.addPropertyToState(state, 'columns', me.headerCt.getColumnsState());
1309
1310 if (storeState) {
1311 state.storeState = storeState;
1312 }
1313 return state;
1314 },
1315
1316 applyState: function (state) {
1317 var me = this,
1318 sorter = state.sort,
1319 storeState = state.storeState,
1320 store = me.store,
1321 columns = state.columns;
1322
1323 delete state.columns;
1324
1325 // Ensure superclass has applied *its* state.
1326 // Component saves dimensions (and anchor/flex) plus collapsed state.
1327 me.callParent(arguments);
1328
1329 if (columns) {
1330 // Column state restoration needs to examine store state
1331 me.headerCt.applyColumnsState(columns, storeState);
1332 }
1333
1334 // Old stored sort state. Deprecated and will die out.
1335 if (sorter) {
1336 if (store.getRemoteSort()) {
1337 // Pass false to prevent a sort from occurring.
1338 store.sort({
1339 property: sorter.property,
1340 direction: sorter.direction,
1341 root: sorter.root
1342 }, null, false);
1343 } else {
1344 store.sort(sorter.property, sorter.direction);
1345 }
1346 }
1347 // New storeState which encapsulates groupers, sorters and filters.
1348 else if (storeState) {
1349 store.applyState(storeState);
1350 }
1351 },
1352
1353 /**
1354 * Returns the store associated with this Panel.
1355 * @return {Ext.data.Store} The store
1356 */
1357 getStore: function(){
1358 return this.store;
1359 },
1360
1361 /**
1362 * Gets the view for this panel.
1363 * @return {Ext.view.Table}
1364 */
1365 getView: function() {
1366 var me = this,
1367 scroll, scrollable, viewConfig;
1368
1369 if (!me.view) {
1370 viewConfig = me.viewConfig;
1371 scroll = viewConfig.scroll || me.scroll;
1372 scrollable = me.scrollable;
1373
1374 if (scrollable == null && viewConfig.scrollable == null && scroll !== null) {
1375 // transform deprecated scroll config into scrollable config
1376 if (scroll === true || scroll === 'both') {
1377 scrollable = true;
1378 } else if (scroll === false || scroll === 'none') {
1379 scrollable = false;
1380 } else if (scroll === 'vertical') {
1381 scrollable = {
1382 x: false,
1383 y: true
1384 };
1385 } else if (scroll === 'horizontal') {
1386 scrollable = {
1387 x: true,
1388 y: false
1389 };
1390 }
1391 }
1392
1393 viewConfig = Ext.apply({
1394 // TableView injects the view reference into this grid so that we have a reference as early as possible
1395 // and Features need a reference to the grid.
1396 // For these reasons, we configure a reference to this grid into the View
1397 grid: me,
1398 ownerGrid: me.ownerGrid,
1399 deferInitialRefresh: me.deferRowRender,
1400 variableRowHeight: me.variableRowHeight,
1401 preserveScrollOnRefresh: true,
1402 trackOver: me.trackMouseOver !== false,
1403 throttledUpdate: me.throttledUpdate === true,
1404 xtype: me.viewType,
1405 store: me.store,
1406 headerCt: me.headerCt,
1407 columnLines: me.columnLines,
1408 rowLines: me.rowLines,
1409 navigationModel: 'grid',
1410 features: me.features,
1411 panel: me,
1412 emptyText: me.emptyText || ''
1413 }, me.viewConfig);
1414
1415 if (scrollable != null) {
1416 viewConfig.scrollable = scrollable;
1417 me.scrollable = null;
1418 }
1419
1420 Ext.create(viewConfig);
1421
1422 // Normalize the application of the markup wrapping the emptyText config.
1423 // `emptyText` can now be defined on the grid as well as on its viewConfig, and this led to the emptyText not
1424 // having the wrapping markup when it was defined in the viewConfig. It should be backwards compatible.
1425 // Note that in the unlikely event that emptyText is defined on both the grid config and the viewConfig that the viewConfig wins.
1426 if (me.view.emptyText) {
1427 me.view.emptyText = '<div class="' + me.emptyCls + '">' + me.view.emptyText + '</div>';
1428 }
1429
1430 // TableView's custom component layout, Ext.view.TableLayout requires a reference to the headerCt because it depends on the headerCt doing its work.
1431 me.view.getComponentLayout().headerCt = me.headerCt;
1432
1433 me.mon(me.view, {
1434 uievent: me.processEvent,
1435 scope: me
1436 });
1437 me.headerCt.view = me.view;
1438
1439 // Plugins and features may need to access the view as soon as it is created.
1440 if (me.hasListeners.viewcreated) {
1441 me.fireEvent('viewcreated', me, me.view);
1442 }
1443 }
1444 return me.view;
1445 },
1446
1447 getColumnManager: function() {
1448 return this.columnManager;
1449 },
1450
1451 getVisibleColumnManager: function() {
1452 return this.visibleColumnManager;
1453 },
1454
1455 getTopLevelColumnManager: function() {
1456 return this.ownerGrid.getColumnManager();
1457 },
1458
1459 getTopLevelVisibleColumnManager: function() {
1460 return this.ownerGrid.getVisibleColumnManager();
1461 },
1462
1463 /**
1464 * @private
1465 * autoScroll is never valid for all classes which extend TablePanel.
1466 */
1467 setAutoScroll: Ext.emptyFn,
1468
1469 applyScrollable: function(scrollable) {
1470 if (this.view) {
1471 this.view.setScrollable(scrollable);
1472 }
1473
1474 return scrollable;
1475 },
1476
1477 getScrollable: function() {
1478 return null;
1479 },
1480
1481 /**
1482 * @private
1483 * Processes UI events from the view. Propagates them to whatever internal Components need to process them.
1484 * @param {String} type Event type, eg 'click'
1485 * @param {Ext.view.Table} view TableView Component
1486 * @param {HTMLElement} cell Cell HTMLElement the event took place within
1487 * @param {Number} recordIndex Index of the associated Store Model (-1 if none)
1488 * @param {Number} cellIndex Cell index within the row
1489 * @param {Ext.event.Event} e Original event
1490 */
1491 processEvent: function(type, view, cell, recordIndex, cellIndex, e, record, row) {
1492 var header = e.position.column;
1493
1494 if (header) {
1495 return header.processEvent.apply(header, arguments);
1496 }
1497 },
1498
1499 /**
1500 * Scrolls the specified record into view.
1501 * @param {Number/String/Ext.data.Model} record The record, record id, or the zero-based position in the dataset to scroll to.
1502 * @param {Object} [options] An object containing options to modify the operation.
1503 * @param {Boolean} [options.animate] Pass `true` to animate the row into view.
1504 * @param {Boolean} [options.highlight] Pass `true` to highlight the row with a glow animation when it is in view.
1505 * @param {Boolean} [options.select] Pass as `true` to select the specified row.
1506 * @param {Boolean} [options.focus] Pass as `true` to focus the specified row.
1507 * @param {Function} [options.callback] A function to execute when the record is in view. This may be necessary if the
1508 * first parameter is a record index and the view is backed by a {@link Ext.data.BufferedStore buffered store}
1509 * which does not contain that record.
1510 * @param {Boolean} options.callback.success `true` if acquiring the record's view node was successful.
1511 * @param {Ext.data.Model} options.callback.record If successful, the target record.
1512 * @param {HTMLElement} options.callback.node If successful, the record's view node.
1513 * @param {Object} [options.scope] The scope (`this` reference) in which the callback function is executed.
1514 */
1515 ensureVisible: function(record, options) {
1516 this.doEnsureVisible(record, options);
1517 },
1518
1519 scrollByDeltaY: function(yDelta, animate) {
1520 this.getView().scrollBy(0, yDelta, animate);
1521 },
1522
1523 scrollByDeltaX: function(xDelta, animate) {
1524 this.getView().scrollBy(xDelta, 0, animate);
1525 },
1526
1527 afterCollapse: function() {
1528 this.saveScrollPos();
1529 this.callParent(arguments);
1530 },
1531
1532 afterExpand: function() {
1533 this.callParent(arguments);
1534 this.restoreScrollPos();
1535 },
1536
1537 saveScrollPos: Ext.emptyFn,
1538
1539 restoreScrollPos: Ext.emptyFn,
1540
1541 onHeaderResize: function() {
1542 var scroller = this.view.getScrollable(),
1543 size;
1544
1545 if (scroller && scroller.isTouchScroller) {
1546 size = scroller.getSize();
1547 if (size) {
1548 scroller.setSize({
1549 x: this.headerCt.getTableWidth(),
1550 y: size.y
1551 });
1552 }
1553 }
1554 },
1555
1556 // Update the view when a header moves
1557 onHeaderMove: function(headerCt, header, colsToMove, fromIdx, toIdx) {
1558 var me = this;
1559
1560 // If there are Features or Plugins which create DOM which must match column order, they set the optimizedColumnMove flag to false.
1561 // In this case we must refresh the view on column move.
1562 if (me.optimizedColumnMove === false) {
1563 me.view.refreshView();
1564 }
1565
1566 // Simplest case for default DOM structure is just to swap the columns round in the view.
1567 else {
1568 me.view.moveColumn(fromIdx, toIdx, colsToMove);
1569 }
1570 me.delayScroll();
1571 },
1572
1573 // Section onHeaderHide is invoked after view.
1574 onHeaderHide: function(headerCt, header) {
1575 var view = this.view;
1576 // The headerCt may be hiding multiple children if a leaf level column
1577 // causes a parent (and possibly other parents) to be hidden. Only run the refresh
1578 // once we're done
1579 if (!headerCt.childHideCount && view.refreshCounter) {
1580 view.refreshView();
1581 }
1582 },
1583
1584 onHeaderShow: function(headerCt, header) {
1585 var view = this.view;
1586 if (view.refreshCounter) {
1587 view.refreshView();
1588 }
1589 },
1590
1591 // To be triggered on add/remove/move for a leaf header
1592 onHeadersChanged: function(headerCt, header) {
1593 var me = this;
1594 if (me.rendered && !me.reconfiguring) {
1595 me.view.refreshView();
1596 me.delayScroll();
1597 }
1598 },
1599
1600 delayScroll: function(){
1601 var target = this.view;
1602 if (target) {
1603 // Do not cause a layout by reading scrollX now.
1604 // It must be read from the target when the task finally executes.
1605 this.scrollTask.delay(10, null, null, [target]);
1606 }
1607 },
1608
1609 /**
1610 * @private
1611 * Fires the TablePanel's viewready event when the view declares that its internal DOM is ready
1612 */
1613 onViewReady: function() {
1614 this.fireEvent('viewready', this);
1615 },
1616
1617 /**
1618 * @private
1619 * Tracks when things happen to the view and preserves the horizontal scroll position.
1620 */
1621 onRestoreHorzScroll: function() {
1622 var me = this,
1623 x = me.scrollXPos;
1624
1625 if (x) {
1626 // We need to restore the body scroll position here
1627 me.syncHorizontalScroll(me, true);
1628 }
1629 },
1630
1631 getScrollerOwner: function() {
1632 var rootCmp = this;
1633 if (!this.scrollerOwner) {
1634 rootCmp = this.up('[scrollerOwner]');
1635 }
1636 return rootCmp;
1637 },
1638
1639 /**
1640 * Gets left hand side marker for header resizing.
1641 * @private
1642 */
1643 getLhsMarker: function() {
1644 var me = this;
1645 return me.lhsMarker || (me.lhsMarker = Ext.DomHelper.append(me.el, {
1646 role: 'presentation',
1647 cls: me.resizeMarkerCls
1648 }, true));
1649 },
1650
1651 /**
1652 * Gets right hand side marker for header resizing.
1653 * @private
1654 */
1655 getRhsMarker: function() {
1656 var me = this;
1657
1658 return me.rhsMarker || (me.rhsMarker = Ext.DomHelper.append(me.el, {
1659 role: 'presentation',
1660 cls: me.resizeMarkerCls
1661 }, true));
1662 },
1663
1664 /**
1665 * Returns the grid's selection. See `{@link Ext.selection.Model#getSelection}`.
1666 * @inheritdoc Ext.selection.Model#getSelection
1667 */
1668 getSelection: function () {
1669 return this.getSelectionModel().getSelection();
1670 },
1671
1672 updateSelection: function(selection) {
1673 var me = this,
1674 sm;
1675
1676 if (!me.ignoreNextSelection) {
1677 me.ignoreNextSelection = true;
1678 sm = me.getSelectionModel();
1679 if (selection) {
1680 sm.select(selection);
1681 } else {
1682 sm.deselectAll();
1683 }
1684 me.ignoreNextSelection = false;
1685 }
1686 },
1687
1688 updateBindSelection: function(selModel, selection) {
1689 var me = this,
1690 selected = null;
1691
1692 if (!me.ignoreNextSelection) {
1693 me.ignoreNextSelection = true;
1694 if (selection.length) {
1695 selected = selModel.getLastSelected();
1696 me.hasHadSelection = true;
1697 }
1698 if (me.hasHadSelection) {
1699 me.setSelection(selected);
1700 }
1701 me.ignoreNextSelection = false;
1702 }
1703 },
1704
1705 updateHeaderBorders: function(headerBorders) {
1706 this[headerBorders ? 'removeCls' : 'addCls'](this.noHeaderBordersCls);
1707 },
1708
1709 getNavigationModel: function() {
1710 return this.getView().getNavigationModel();
1711 },
1712
1713 /**
1714 * Returns the selection model being used by this grid's {@link Ext.view.Table view}.
1715 * @return {Ext.selection.Model} The selection model being used by this grid's {@link Ext.view.Table view}.
1716 */
1717 getSelectionModel: function() {
1718 return this.getView().getSelectionModel();
1719 },
1720
1721 getScrollTarget: function(){
1722 var items = this.getScrollerOwner().query('tableview');
1723
1724 // Last view has the scroller
1725 return items[items.length - 1];
1726 },
1727
1728 syncHorizontalScroll: function(target, setBody) {
1729 var me = this,
1730 x = me.view.getScrollX(),
1731 scrollTarget;
1732
1733 setBody = setBody === true;
1734 // Only set the horizontal scroll if we've changed position,
1735 // so that we don't set this on vertical scrolls
1736 if (me.rendered && (setBody || x !== me.scrollXPos)) {
1737 // Only set the body position if we're reacting to a refresh, otherwise
1738 // we just need to set the header.
1739 if (setBody) {
1740 scrollTarget = me.getScrollTarget();
1741 scrollTarget.setScrollX(x);
1742 }
1743 me.headerCt.setScrollX(x);
1744 me.scrollXPos = x;
1745 }
1746 },
1747
1748 // template method meant to be overriden
1749 onStoreLoad: Ext.emptyFn,
1750
1751 getEditorParent: function() {
1752 return this.body;
1753 },
1754
1755 bindStore: function(store, initial) {
1756 var me = this,
1757 view = me.getView();
1758
1759 // Normally, this method will always be called with a valid store (because there is a symmetric
1760 // .unbindStore method), but there are cases where this method will be called and passed a null
1761 // value, i.e., a panel is used as a pickerfield. See EXTJS-13089.
1762 if (store) {
1763 // Bind to store immediately because subsequent processing looks for grid's store property
1764 me.store = store;
1765
1766 if (view.store !== store) {
1767 // If coming from a reconfigure, we need to set the actual store property on the view. Setting the
1768 // store will then also set the dataSource.
1769 //
1770 // Note that if it's a grid feature then this is sorted out in view.bindStore(), and it's own
1771 // implementation of .bindStore() will be called.
1772 view.bindStore(store, false);
1773 }
1774
1775 me.mon(store, {
1776 load: me.onStoreLoad,
1777 scope: me
1778 });
1779 me.storeRelayers = me.relayEvents(store, [
1780 /**
1781 * @event filterchange
1782 * @inheritdoc Ext.data.Store#filterchange
1783 */
1784 'filterchange',
1785 /**
1786 * @event groupchange
1787 * @inheritdoc Ext.data.Store#groupchange
1788 */
1789 'groupchange'
1790 ]);
1791 } else {
1792 me.unbindStore();
1793 }
1794 },
1795
1796 unbindStore: function() {
1797 var me = this,
1798 store = me.store,
1799 view;
1800
1801 if (store) {
1802 store.trackStateChanges = false;
1803
1804 me.store = null;
1805
1806 me.mun(store, {
1807 load: me.onStoreLoad,
1808 scope: me
1809 });
1810
1811 Ext.destroy(me.storeRelayers);
1812
1813 view = me.view;
1814 if (view.store) {
1815 view.bindStore(null);
1816 }
1817 }
1818 },
1819
1820 setColumns: function(columns) {
1821 // If being reconfigured from zero columns to zero columns, skip operation.
1822 // This can happen if columns are being set from a binding and the initial value
1823 // of the bound data in the ViewModel is []
1824 if (columns.length || this.getColumnManager().getColumns().length) {
1825 this.reconfigure(undefined, columns);
1826 }
1827 },
1828
1829 /**
1830 * A convenience method that fires {@link #reconfigure} with the store param. To set the store AND change columns,
1831 * use the {@link #reconfigure reconfigure method}.
1832 *
1833 * @param {Ext.data.Store} [store] The new store.
1834 */
1835 setStore: function (store) {
1836 this.reconfigure(store);
1837
1838 if (this.autoLoad && !store.isEmptyStore && !(store.loading || store.isLoaded())) {
1839 store.load();
1840 }
1841 },
1842
1843 /**
1844 * Reconfigures the grid or tree with a new store and/or columns. Stores and columns
1845 * may also be passed as params.
1846 *
1847 * grid.reconfigure(store, columns);
1848 *
1849 * Additionally, you can pass just a store or columns.
1850 *
1851 * tree.reconfigure(store);
1852 * // or
1853 * grid.reconfigure(columns);
1854 * // or
1855 * tree.reconfigure(null, columns);
1856 *
1857 * If you're using locked columns, the {@link #enableLocking} config should be set
1858 * to `true` before the reconfigure method is executed.
1859 *
1860 * @param {Ext.data.Store/Object} [store] The new store instance or store config. You can
1861 * pass `null` if no new store.
1862 * @param {Object[]} [columns] An array of column configs
1863 */
1864 reconfigure: function(store, columns) {
1865 var me = this,
1866 oldStore = me.store,
1867 headerCt = me.headerCt,
1868 lockable = me.lockable,
1869 oldColumns = headerCt ? headerCt.items.getRange() : me.columns,
1870 view = me.getView(),
1871 block, refreshCounter;
1872
1873 // Allow optional store argument to be fully omitted, and the columns argument to be solo
1874 if (arguments.length === 1 && Ext.isArray(store)) {
1875 columns = store;
1876 store = null;
1877 }
1878
1879 // Make copy in case the beforereconfigure listener mutates it.
1880 if (columns) {
1881 columns = Ext.Array.slice(columns);
1882 }
1883
1884 me.reconfiguring = true;
1885 if (store) {
1886 store = Ext.StoreManager.lookup(store);
1887 }
1888 me.fireEvent('beforereconfigure', me, store, columns, oldStore, oldColumns);
1889
1890 Ext.suspendLayouts();
1891
1892 if (lockable) {
1893 me.reconfigureLockable(store, columns);
1894 } else {
1895 // Prevent the view from refreshing until we have resumed layouts and any columns are rendered
1896 block = view.blockRefresh;
1897 view.blockRefresh = true;
1898
1899 // The following test compares the result of an assignment of the store var with the oldStore var.
1900 // This saves a large amount of code.
1901 //
1902 // Note that we need to process the store first in case one or more passed columns (if there are any)
1903 // have active gridfilters with values which would filter the currently-bound store.
1904 if (store && store !== oldStore) {
1905 me.unbindStore();
1906 me.bindStore(store);
1907 }
1908
1909 if (columns) {
1910 // new columns, delete scroll pos
1911 delete me.scrollXPos;
1912 headerCt.removeAll();
1913 headerCt.add(columns);
1914 }
1915
1916 view.blockRefresh = block;
1917 refreshCounter = view.refreshCounter;
1918 }
1919
1920 Ext.resumeLayouts(true);
1921 if (lockable) {
1922 me.afterReconfigureLockable();
1923 } else if (view.refreshCounter === refreshCounter) {
1924 // If the layout resumption didn't trigger the view to refresh, do it here
1925 view.refreshView();
1926 }
1927
1928 me.fireEvent('reconfigure', me, store, columns, oldStore, oldColumns);
1929 delete me.reconfiguring;
1930 },
1931
1932 beforeDestroy: function(){
1933 var me = this,
1934 task = me.scrollTask;
1935
1936 if (task) {
1937 task.cancel();
1938 me.scrollTask = null;
1939 }
1940 Ext.destroy(me.focusEnterLeaveListeners);
1941 me.callParent();
1942 },
1943
1944 onDestroy: function(){
1945 var me = this;
1946 if (me.lockable) {
1947 me.destroyLockable();
1948 }
1949 me.unbindStore();
1950 me.callParent();
1951 me.columns = me.storeRelayers = me.columnManager = me.visibleColumnManager = null;
1952 },
1953
1954 destroy: function() {
1955 // Clear out references here because other things (plugins/features) may need to know about them during destruction
1956 var me = this;
1957 me.callParent();
1958 if (me.destroyed) {
1959 me.view = me.selModel = me.headerCt = null;
1960 }
1961 },
1962
1963 privates: {
1964 // The focusable flag is set, but there is no focusable element.
1965 // Focus is delegated to the view by the focus implementation.
1966 initFocusableElement: function() {},
1967
1968 doEnsureVisible: function(record, options) {
1969 // Handle the case where this is a lockable assembly
1970 if (this.lockable) {
1971 return this.ensureLockedVisible(record, options);
1972 }
1973
1974 // Allow them to pass the record id.
1975 if (typeof record !== 'number' && !record.isEntity) {
1976 record = this.store.getById(record);
1977 }
1978 var me = this,
1979 view = me.getView(),
1980 domNode = view.getNode(record),
1981 callback, scope, animate,
1982 highlight, select, doFocus, scrollable, column, cell;
1983
1984 if (options) {
1985 callback = options.callback;
1986 scope = options.scope;
1987 animate = options.animate;
1988 highlight = options.highlight;
1989 select = options.select;
1990 doFocus = options.focus;
1991 column = options.column;
1992 }
1993
1994 // Always supercede any prior deferred request
1995 if (me.deferredEnsureVisible) {
1996 me.deferredEnsureVisible.destroy();
1997 }
1998
1999 // We have not yet run the layout.
2000 // Add this to the end of the first sizing process.
2001 // By using the resize event, we will come in AFTER any Component's onResize and onBoxReady handling.
2002 if (!view.componentLayoutCounter) {
2003 me.deferredEnsureVisible = view.on({
2004 resize: me.doEnsureVisible,
2005 args: Ext.Array.slice(arguments),
2006 scope: me,
2007 single: true,
2008 destroyable: true
2009 });
2010 return;
2011 }
2012
2013 if (typeof column === 'number') {
2014 column = me.ownerGrid.getVisibleColumnManager().getColumns()[column];
2015 }
2016
2017 // We found the DOM node associated with the record
2018 if (domNode) {
2019 scrollable = view.getScrollable();
2020 if (column) {
2021 cell = Ext.fly(domNode).selectNode(column.getCellSelector());
2022 }
2023 if (scrollable) {
2024 scrollable.scrollIntoView(cell || domNode, !!column, animate, highlight);
2025 }
2026 if (!record.isEntity) {
2027 record = view.getRecord(domNode);
2028 }
2029 if (select) {
2030 view.getSelectionModel().select(record);
2031 }
2032 if (doFocus) {
2033 view.getNavigationModel().setPosition(record, 0);
2034 }
2035 Ext.callback(callback, scope || me, [true, record, domNode]);
2036 }
2037 // If we didn't find it, it's probably because of buffered rendering
2038 else if (view.bufferedRenderer) {
2039 view.bufferedRenderer.scrollTo(record, {
2040 animate: animate,
2041 highlight: highlight,
2042 select: select,
2043 focus: doFocus,
2044 column: column,
2045 callback: function(recordIdx, record, domNode) {
2046 Ext.callback(callback, scope || me, [true, record, domNode]);
2047 }
2048 });
2049 } else {
2050 Ext.callback(callback, scope || me, [false, null]);
2051 }
2052 },
2053
2054 getFocusEl: function() {
2055 return this.getView().getFocusEl();
2056 },
2057
2058 /**
2059 * Toggles ARIA actionable mode on/off
2060 * @param {Boolean} enabled
2061 * @return {Boolean} `true` if actionable mode was entered
2062 * @private
2063 */
2064 setActionableMode: function(enabled, position) {
2065 // Always set the topmost grid in a lockable assembly
2066 var me = this.ownerGrid;
2067
2068 // Can be called to exit actionable mode upon a focusLeave caused by destruction
2069 if (!me.destroying && me.view.setActionableMode(enabled, position) !== false) {
2070 me.fireEvent('actionablemodechange', enabled);
2071 me[enabled ? 'addCls' : 'removeCls'](me.actionableModeCls);
2072 return true;
2073 }
2074 }
2075 }
2076 });