/**
* A selection model that renders a column of checkboxes that can be toggled to
* select or deselect rows. The default mode for this selection model is MULTI.
*
* The selection model will inject a header for the checkboxes in the first view
* and according to the {@link #injectCheckbox} configuration.
*/
Ext.define('Ext.selection.CheckboxModel', {
alias: 'selection.checkboxmodel',
extend: 'Ext.selection.RowModel',
/**
* @cfg {"SINGLE"/"SIMPLE"/"MULTI"} mode
* Modes of selection.
* Valid values are `"SINGLE"`, `"SIMPLE"`, and `"MULTI"`.
*/
mode: 'MULTI',
/**
* @cfg {Number/String} [injectCheckbox=0]
* The index at which to insert the checkbox column.
* Supported values are a numeric index, and the strings 'first' and 'last'.
*/
injectCheckbox: 0,
/**
* @cfg {Boolean} checkOnly
* True if rows can only be selected by clicking on the checkbox column.
*/
checkOnly: false,
* @cfg {Boolean} showHeaderCheckbox
* Configure as `false` to not display the header checkbox at the top of the column.
* When {@link Ext.data.Store#buffered} is set to `true`, this configuration will
* not be available because the buffered data set does not always contain all data.
*/
showHeaderCheckbox: undefined,
/**
* @cfg {String} [checkSelector="x-grid-row-checker"]
* The selector for determining whether the checkbox element is clicked. This may be changed to
* allow for a wider area to be clicked, for example, the whole cell for the selector.
*/
checkSelector: '.' + Ext.baseCSSPrefix + 'grid-row-checker',
// private
checkerOnCls: Ext.baseCSSPrefix + 'grid-hd-checker-on',
constructor: function(){
var me = this;
me.callParent(arguments);
// If mode is single and showHeaderCheck isn't explicity set to
// true, hide it.
if (me.mode === 'SINGLE' && me.showHeaderCheckbox !== true) {
me.showHeaderCheckbox = false;
}
},
beforeViewRender: function(view) {
var me = this,
owner;
me.callParent(arguments);
// if we have a locked header, only hook up to the first
if (!me.hasLockedHeader() || view.headerCt.lockedCt) {
if (me.showHeaderCheckbox !== false) {
view.headerCt.on('headerclick', me.onHeaderClick, me);
}
me.addCheckbox(view, true);
owner = view.ownerCt;
// Listen to the outermost reconfigure event
if (view.headerCt.lockedCt) {
owner = owner.ownerCt;
}
me.mon(owner, 'reconfigure', me.onReconfigure, me);
}
},
bindComponent: function(view) {
var me = this;
me.sortable = false;
me.callParent(arguments);
},
var views = this.views,
vLen = views.length,
v;
for (v = 0; v < vLen; v++) {
if (views[v].headerCt.lockedCt) {
return true;
}
}
return false;
},
/**
* Add the header checkbox to the header row
* @private
* @param {Boolean} initial True if we're binding for the first time.
*/
addCheckbox: function(view, initial){
var me = this,
checkbox = me.injectCheckbox,
headerCt = view.headerCt;
// Preserve behaviour of false, but not clear why that would ever be done.
if (checkbox !== false) {
if (checkbox == 'first') {
checkbox = 0;
} else if (checkbox == 'last') {
checkbox = headerCt.getColumnCount();
}
Ext.suspendLayouts();
if (view.getStore().buffered) {
me.showHeaderCheckbox = false;
}
headerCt.add(checkbox, me.getHeaderConfig());
Ext.resumeLayouts();
}
if (initial !== true) {
view.refresh();
}
},
/**
* Handles the grid's reconfigure event. Adds the checkbox header if the columns have been reconfigured.
* @private
* @param {Ext.panel.Table} grid
* @param {Ext.data.Store} store
* @param {Object[]} columns
*/
onReconfigure: function(grid, store, columns) {
if(columns) {
this.addCheckbox(this.views[0]);
}
},
* Toggle the ui header between checked and unchecked state.
* @param {Boolean} isChecked
* @private
*/
toggleUiHeader: function(isChecked) {
var view = this.views[0],
headerCt = view.headerCt,
checkHd = headerCt.child('gridcolumn[isCheckerHd]'),
cls = this.checkerOnCls;
if (checkHd) {
if (isChecked) {
checkHd.addCls(cls);
} else {
checkHd.removeCls(cls);
}
}
},
* Toggle between selecting all and deselecting all when clicking on
* a checkbox header.
*/
onHeaderClick: function(headerCt, header, e) {
if (header.isCheckerHd) {
e.stopEvent();
var me = this,
isChecked = header.el.hasCls(Ext.baseCSSPrefix + 'grid-hd-checker-on');
// Prevent focus changes on the view, since we're selecting/deselecting all records
me.preventFocus = true;
if (isChecked) {
me.deselectAll();
} else {
me.selectAll();
}
delete me.preventFocus;
}
},
* Retrieve a configuration to be used in a HeaderContainer.
* This should be used when injectCheckbox is set to false.
*/
getHeaderConfig: function() {
var me = this,
showCheck = me.showHeaderCheckbox !== false;
return {
isCheckerHd: showCheck,
text : ' ',
clickTargetName: 'el',
width: me.headerWidth,
sortable: false,
draggable: false,
resizable: false,
hideable: false,
menuDisabled: true,
dataIndex: '',
cls: showCheck ? Ext.baseCSSPrefix + 'column-header-checkbox ' : '',
renderer: Ext.Function.bind(me.renderer, me),
editRenderer: me.editRenderer || me.renderEmpty,
locked: me.hasLockedHeader()
};
},
renderEmpty: function() {
return ' ';
},
// After refresh, ensure that the header checkbox state matches
refresh: function() {
this.callParent(arguments);
this.updateHeaderState();
},
/**
* Generates the HTML to be rendered in the injected checkbox column for each row.
* Creates the standard checkbox markup by default; can be overridden to provide custom rendering.
* See {@link Ext.grid.column.Column#renderer} for description of allowed parameters.
*/
renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
var baseCSSPrefix = Ext.baseCSSPrefix;
metaData.tdCls = baseCSSPrefix + 'grid-cell-special ' + baseCSSPrefix + 'grid-cell-row-checker';
return '<div class="' + baseCSSPrefix + 'grid-row-checker"> </div>';
},
processSelection: function(view, record, item, index, e){
var me = this,
checker = e.getTarget(me.checkSelector),
mode;
// checkOnly set, but we didn't click on a checker.
if (me.checkOnly && !checker) {
return;
}
if (checker) {
mode = me.getSelectionMode();
// dont change the mode if its single otherwise
// we would get multiple selection
if (mode !== 'SINGLE') {
me.setSelectionMode('SIMPLE');
}
me.selectWithEvent(record, e);
me.setSelectionMode(mode);
} else {
me.selectWithEvent(record, e);
}
},
/**
* Synchronize header checker value as selection changes.
* @private
*/
onSelectChange: function() {
this.callParent(arguments);
if (!this.suspendChange) {
this.updateHeaderState();
}
},
/**
* @private
*/
onStoreLoad: function() {
this.callParent(arguments);
this.updateHeaderState();
},
onStoreAdd: function() {
this.callParent(arguments);
this.updateHeaderState();
},
onStoreRemove: function() {
this.callParent(arguments);
this.updateHeaderState();
},
onStoreRefresh: function(){
this.callParent(arguments);
this.updateHeaderState();
},
maybeFireSelectionChange: function(fireEvent) {
if (fireEvent && !this.suspendChange) {
this.updateHeaderState();
}
this.callParent(arguments);
},
resumeChanges: function(){
this.callParent();
if (!this.suspendChange) {
this.updateHeaderState();
}
},
* @private
*/
updateHeaderState: function() {
// check to see if all records are selected
var me = this,
store = me.store,
storeCount = store.getCount(),
views = me.views,
hdSelectStatus = false,
selectedCount = 0,
selected, len, i;
if (!store.buffered && storeCount > 0) {
selected = me.selected;
hdSelectStatus = true;
for (i = 0, len = selected.getCount(); i < len; ++i) {
if (!me.storeHasSelected(selected.getAt(i))) {
break;
}
++selectedCount;
}
hdSelectStatus = storeCount === selectedCount;
}
if (views && views.length) {
me.toggleUiHeader(hdSelectStatus);
}
}
});