// Note: add the styles shown in the comment at the end of this code block to your .css Ext.ns('Ext.ux.form'); // create namespace Ext.define('Ext.ux.form.CheckboxListCombo', { extend: 'Ext.form.field.ComboBox', alias: 'widget.checkboxlistcombo', /** * The following options were added to extend the ComboBox to provide additional functionality. * The are now 4 possible display values in the text box of the combo: * (1) .emptyText * (2) .displayField - if only one item is selected * (3) optional .briefDisplayField - if 2 or more items are selected * (4) optional {nn} briefSummaryTitle - if more than a certain number of items are selected (and all won't fit) * These features are only used if the values below are not falsy */ briefDisplayField: false, // store field to use if there are multiple items selected (if not false) briefDisplayLimit: false, // max # of "briefDisplayField" items to display, after which "## {briefSummaryTitle}" is displayed briefSummaryTitle: false, // string to display if there are too many selected items for the display box displayList: "", // will hold the delimited list of all selections (may not be in the combo if too many, but can be used as desired) firstItemChecksAll: false, // if true then the first item is ignored other than for this purpose (value must be falsy) allSelectedTitle: false, // if not set, it will be set to the displayValue of the first item constructor: function(config) { Ext.ux.form.CheckboxListCombo.superclass.constructor.call(this, config); }, initComponent: function () { if (this.briefDisplayField) { this.briefDisplayTpl = Ext.create('Ext.XTemplate', '{[typeof values === "string" ? values : values.' + this.briefDisplayField + ']}' + this.delimiter + ''); } Ext.ux.form.CheckboxListCombo.superclass.initComponent.apply(this, arguments); this.listConfig.checkboxComboId = this.id; // for firstItem checking (see below) }, getDisplayValue: function () { this.displayList = this.displayTplData && this.displayTplData.length ? (this.briefDisplayTpl || this.displayTpl).apply(this.displayTplData) : ""; var ttl = this.allSelected ? this.allSelectedTitle || "[" + this.emptyText + "]" : ( (this.briefDisplayLimit && this.briefSummaryTitle && this.displayTplData && this.displayTplData.length > this.briefDisplayLimit) ? (this.displayTplData.length) + " " + this.briefSummaryTitle : this.displayList ); return ttl || this.emptyText; }, lastQuery: '', // prevents clearing of the list after initial setValue listConfig: { getInnerTpl: function (displayField) { return '
{' + (displayField || 'text') + ':htmlEncode}
'; }, // from here down it's all about the first item checking/unchecking all others previousAllChecked: false, previousCheckCount: 0, listeners: { beforeselect: function ( me, node, selections, options ) { // since selectionChange does not provide info on which node changed, // we need to determine whether the all item was selected... var combo = Ext.getCmp(me.view.checkboxComboId); me.view.somethingChecked = true; me.view.allChecked = combo && combo.firstItemChecksAll && !node.data[combo.valueField]; return true; }, selectionchange: function (dataViewModel, selections, options) { var me = dataViewModel,combo = Ext.getCmp(me.view.checkboxComboId), storeRecs, recs = [], d, i, j, nChecked, vField, dField, allState, allNodes, allItem, grayCls = "ux-checkboxcombolist-tri", somethingChecked = me.view.somethingChecked, allChecked = me.view.allChecked; me.view.somethingChecked = false; me.view.allChecked = false; // beforeselect doesn't fire on deselect if (combo && combo.firstItemChecksAll) { allNodes = me.view.getNodes(); if (allNodes.length) { allItem = Ext.get(allNodes[0]); vField = combo.valueField; dField = combo.displayField; storeRecs = Ext.clone(me.store.getRange(0)); for (i=nChecked=0;i0) { nChecked++; } else if (!combo.allSelectedTitle) { combo.allSelectedTitle = d[dField]; } } } recs.push(d); } allState = ( ( recs[0].checked && allChecked ) || (nChecked == recs.length-1 && somethingChecked)) ? 1 : ( 0 < nChecked && nChecked < recs.length-1 ? 2 : 0); me.view.suspendEvents();// suspend events, though selectAll & deselectAll send them anyway me.suspendEvents(); switch (allState) { case 0: // None //me.deselectAll(true); // Nope, doesn't suspend events combo.allSelected = false; allItem.removeCls(grayCls); setTimeout(function () { me.deselectAll(false); },1); // suspendEvent is ignored, so we hack using a timer break; case 1: // All //me.selectAll(true); // Nope, doesn't suspend events combo.allSelected = true; allItem.removeCls(grayCls); setTimeout(function () { me.selectAll(false); },1); // suspendEvent is ignored, so we hack using a timer break; case 2: // Some (gray out the ALL item) allItem.addCls(grayCls); combo.allSelected = false; me.deselect(0,true); // in this case the suspendEvent flag works break; } me.resumeEvents();// resume events, though selectAll & deselectAll send them anyway me.view.resumeEvents(); console.log("CheckboxComboList.changed: " + allState + " / " + nChecked); } } } } }, });