ExtJs Grid has a lot of features built in. Some examples are the Grouped Grid and the Checkbox Selection.
However, grouping these two together poses a bit of a challenge.
After searching on the forums, this post guided me into a nice, working example.
The Grouping Check column has been created as a feature.
Ext.define('App.GroupedCheckboxFeature', { extend: 'Ext.grid.feature.Grouping', alias: 'feature.groupedcheckbox', /** @property */ targetCls: 'group-checkbox', /** @property */ checkDataIndex: 'isChecked', startCollapsed: true, constructor: function (config) { config.groupHeaderTpl = ['<div class="group-header"><input class="' + this.targetCls + '" {[values.record.get("' + this.checkDataIndex + '") ? "checked" : ""]} type="checkbox"/><span class="group-title">{name}</span></div>']; this.callParent(arguments); }, init: function (grid) { var store = grid.getStore(); if (store) { store.on('update', this.onStoreUpdate, this); } this.callParent(arguments); }, setupRowData: function (record, idx, rowValues) { this.callParent(arguments); // Ext JS 6 vs Ext JS 5.1.1 vs Ext JS 5.1.0- var groupInfo = this.groupRenderInfo || this.metaGroupCache || this.groupInfo; groupInfo.record = this.getParentRecord(record.get(this.getGroupField())); }, /** * This method will only run once... on the initial load of the view... this * is so we can check the store for the grouped item's children... if they're * all checked, then we need to set the private variable to checked */ checkAllGroups: function (groupName) { var store = this.view.getStore(); var groupField = this.getGroupField(); if (store) { var groups = store.getGroups(); if (groups) { groups.each(function (groupRec) { var allChecked = true; var groupKey = groupRec.getGroupKey(); var checkGroup = true; if (groupName) { if (groupKey !== groupName) { checkGroup = false; } } if (checkGroup) { groupRec.each(function (rec) { allChecked = rec.get(this.checkDataIndex); groupName = rec.get(groupField); if (allChecked === false) { return false; } }, this); this.updateParentRecord(groupName, allChecked); } }, this); } } }, updateParentRecord: function (groupName, checked) { var parentRecord = this.getParentRecord(groupName); if (parentRecord) { parentRecord.set(this.checkDataIndex, checked); this.refreshView(); } }, getParentRecord: function (groupName) { var parentRecord; var metaGroup; // For Ext JS 6 and 5.1.1 if (this.getMetaGroup) { metaGroup = this.getMetaGroup(groupName); } // For Ext JS 5.1- else { metaGroup = this.groupCache[groupName]; } if (metaGroup) { parentRecord = metaGroup.placeholder; } return parentRecord; }, /** * TODO: This might break... we're using a private variable here... but this * is the only way we can refresh the view without breaking any sort of * scrolling... I'm not sure how to only refresh the group header itself, so * I'm keeping the groupName as a param passing in... might be able to figure * this out later * @param {String} groupName */ refreshView: function (groupName) { var view = this.view; if (view) { view.refreshView(); } }, onStoreUpdate: function (store, record, operation, modifiedFieldNames, details, eOpts) { var grid = this.grid; if (!this.updatingRecords && grid && record) { var groupName = record.get(this.getGroupField()); this.checkAllGroups(groupName); grid.setSelection(record); this.refreshView(groupName); } }, onGroupClick: function (grid, node, group, event, eOpts) { if (event && grid) { var target = event.getTarget('.' + this.targetCls); var store = grid.getStore(); var groupRecord = this.getRecordGroup(event.record); if (target && store && groupRecord) { var checked = target.checked; this.updatingRecords = true; groupRecord.each(function (rec, index) { rec.beginEdit(); rec.set(this.checkDataIndex, checked); rec.endEdit(true); }, this); this.updatingRecords = false; this.updateParentRecord(group, checked); } else { this.callParent(arguments); } } } });
The Grid itself is a regular grid.
Ext.define('App.view.Main', { extend: 'Ext.container.Viewport' , items: [{ xtype: 'grid' , title: 'ExtJs - Grouped Grid Checkbox Column' , titleAlign: 'center' , viewConfig: { markDirty: false } , features: [{ ftype: 'groupedcheckbox', enableGroupingMenu: false, hideGroupHeader: true, //startCollapsed: true // produces strange bug }] , store: { fields: ['name', 'department', 'isChecked'] , groupField: 'department' , proxy: { type: 'rest' , url: 'data/store.json' , reader: { type: 'json' , rootProperty: 'values' } } , autoLoad: true } , columns: { defaults: { menuDisabled: true , scrollable: true , hideable: false , draggable: false } , items: [{ xtype: 'checkcolumn' , headerCheckbox: true , text: '' , dataIndex: 'isChecked' , width: 50 }, { text: 'Name' , dataIndex: 'name' , flex: 1 }, { text: 'Department' , dataIndex: 'department' , flex: 1 }] } }] });
But although it worked perfectly, the styling looked a bit off and that’s because it uses the standard HTML checkbox.
I had to hunt around for trying to customize it to look like an ExtJs checkbox.
Finally, with the help of this Stackoverflow solution, I was able to solve it!
I also did a bit of customization to the GroupHeaderTpl from the original post to make the group header texts align correctly (as seen above in the GroupedCheckboxFeature ).
.group-header { display: flex; align-items: center; vertical-align: middle; } .group-title { font-weight: bold; } input[type="checkbox"] { width: 15px; height: 15px; -webkit-appearance: none; -moz-appearance: none; appearance: none; margin-right: 10px; background-color: #ffffff; outline: 0; border: 1px solid #919191; border-radius: 3px; display: inline-block; -webkit-box-shadow: none !important; -moz-box-shadow: none !important; box-shadow: none !important; } input[type="checkbox"]:focus { outline: none; border: none !important; -webkit-box-shadow: none !important; -moz-box-shadow: none !important; box-shadow: none !important; } input[type="checkbox"]:checked { outline: none; border: none !important; text-align: center; line-height: 15px; } input[type="checkbox"]:checked::after { content: "\e613"; font: 18px/1 "ExtJS"; color: #919191; }