1. #1
    Sencha Premium Member
    Join Date
    Oct 2009
    Location
    Austin, TX
    Posts
    35
    Vote Rating
    0
    wiz61 is on a distinguished road

      0  

    Default ExtJs4 - Checkbox List Combo

    ExtJs4 - Checkbox List Combo


    As part of a UI redesign project, I have implemented a CheckboxListCombo control for ExtJs4, the code is shown below. I've added some explanation afterward. In addition, there's an issue that might be an ExtJs bug that I could use some help with (though it does work as is).

    Code:
    // 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', '<tpl for=".">{[typeof values === "string" ? values : values.' + this.briefDisplayField + ']}<tpl if="xindex < xcount">' + this.delimiter + '</tpl></tpl>');
    		}
    		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 '<tpl for="."><div><img src="' + Ext.BLANK_IMAGE_URL + '" ' + 'class="ux-checkboxlistcombo-icon">{' + (displayField || 'text') + ':htmlEncode}</div></tpl>';
    		}
    		// 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;i<storeRecs.length;i++) {
    							d = storeRecs[i].data;
    							d.checked = false;
    							for (j=0;selections && !d.checked && j<selections.length;j++) {
    								if (selections[j].data[vField] == d[vField]) {
    									d.checked = true;
    									if (i>0) {
    										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);
    					}
    				}
    			}
    		}
    	},
    });
    
    /* -- Add the following to your .css file --
    .ux-checkboxlistcombo-icon {
        float: left; width:16px; height:16px;
        background-position: -1px -1px ! important; background-repeat: no-repeat ! important;
    }
    .x-boundlist-item>div>.ux-checkboxlistcombo-icon {
        background: url('data:image/gif;base64,R0lGODlhEAAQAIcAAED/QI6Pj66zua+0urK3vLS5vbi7v7u+wby/wsHDxcLExsbHyMrLzMzNzcvP1c3R1s3R19TU1dTV1tDT2NDU2dLV2tTX29XY3Njb3tvb3Nrc39vd39zd3t3f4eDh4eDh4+Hi4uHi4+Lj5OPk5eTl5eXm5ubm5ubn5+jo6Onp6enp6urq6urr6+vr7Ovs7Ozs7O3t7e/v7/Dw8PLy8vT09PX19fb29gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAMAAAAALAAAAAAQABAAAAiKAAEIHEiwoMGDBQMoXMhw4cAANCJKnEgjwEMaAjJqHFDgQMWLAhyIdDDhwoYEHwVCDOngQQUMHUQsSAlgJQQKFjaEKIGiAU2IBC5o+IDiBQwYEn7SMNCBxIoYM6JyUIpgRAoYM2jUoAFCqYKrMmjYGGtCKYMIGTycYOGihQqlFClaVNmw7lyECAMCADs=');
    }
    .x-boundlist-item.x-boundlist-selected>div>.ux-checkboxlistcombo-icon {
        background: url('data:image/gif;base64,R0lGODlhEAAQAIcAAENZkkRZkkZblEdclUhdlkhelklel0pfl0tgmFZpnV1woWBypGN1pWR2pWZ3p21+qkD/QHaGq46Pj4uZu4yZupCdva6zuZ2nwKOuyK+4z7C50be/1bm/0LrB1bzD1sbIysXJzcrLzMnM0MvP1c3P0cnP3czQ1s3R1tXV1tXY3NXZ3dra29vc3Nzd3s/U4tzf5N7g4d/i5d7h6d/i6eHi4uDi5ubm5ufn6Onp6uvr7Ozt7e3u7urs8O3u8fLz9PLz9vT09PX19fb29vj4+Pn5+fj5+gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAMAABAALAAAAAAQABAAAAicACEIHEiwoMGDBSUoXMhw4UAJQCJKBDKEiBAgEh4CscCRo4gLEV4EySgQooURKE30SACAgxCSEEyiPFEjQ4AGNzBqtKAiRgofCgR4YKGzJBAQJTDs6ECAwRAURWMCgfFAgAsHBzYIaREVIgkZAwocWFAECI2uQD7wmGAAgQaLNtCGWDGDQoUfOnLgQBsxCJEhQSbCbEhYIcLDEAICADs=');
    }
    .x-boundlist-item.ux-checkboxcombolist-tri>div>.ux-checkboxlistcombo-icon {
        background: url('data:image/gif;base64,R0lGODlhEAAQAKU3AED/QI6Pj66zua+0urK3vLS5vbi7v7u+wby/wsHDxcLExsbHyMrLzMzNzcvP1c3R1s3R19TU1dTV1tDT2NDU2dLV2tTX29XY3Njb3tvb3Nrc39vd39zd3t3f4eDh4eDh4+Hi4uHi4+Lj5OPk5eTl5eXm5ubm5ubn5+jo6Onp6enp6urq6urr6+vr7Ovs7Ozs7O3t7e/v7/Dw8PLy8vT09PX19fb29v///////////////////////////////////yH5BAEAAD8ALAAAAAAQABAAAAZLwJ9wSCwaj8WAcslcDgO0qJRmq9ICT5pguzV4DTWsECr4mg028Y98/l6z5bbhPabJvfS1/Z6H3udqfnyBe3J9UjVVNVOBTY5qSEdBADs=');
    }
    */
    My implementation provides for "brief" display options to allow for more info in the displayField. For example, when dealing with US states it would make sense to use the full name in the list but a list of delimited abbreviations in the displayField. And you would want to control how many selected you would want to display before just saying something like "10 states". These options are in there.

    The implementation ended up being fairly straight-forward. I had a 3.3 version that was loosely based on Saki's lovcombo, but it turned out that the changes in ExtJs4 made moving that over more trouble than it was worth. The first 40% or so of the code implements the simple case, mainly using css styles. The getDisplayText() function determines what to display in the combo text field. It does a fair amount of stuff in order to provide some flexibility; i.e., it will build the "brief" list even if it only shows the summary, so you could access the list for use in a panel (I needed it for a grid).

    In addition to a simple checklist, I also added the ability to have the first item control the check/uncheck of all the rest, as well as provide an indicator of the all/some/none state. It uses a gray box if some are checked, unchecked if none and checked if all are checked. Getting the check/uncheck all to work was a bit trickier than I expected, as I am having some issues related to suspending events when doing a selectAll or deslectAll, as the pamater to not fire any events is ignored (just trace through and you'll see), so a little help here should help me fix that piece. As it is the combo will fire several events when you check or uncheck the first item.

    In any event, I hope you find this to be useful.

    -Gary Skiba (Austin, TX)
    Last edited by wiz61; 26 Jul 2011 at 5:11 AM. Reason: added lastQuery=''

  2. #2
    Sencha User
    Join Date
    Sep 2011
    Posts
    1
    Vote Rating
    0
    jwalters3 is on a distinguished road

      0  

    Default Using this in a grid with a bitwise store

    Using this in a grid with a bitwise store


    I have three items that I need checkboxes for, and need to put them in a drop down combo box inside a grid. But, I have a bitwise value in the store. How could I implement a checkbox list combo to read from the bitwise store value? Thanks!

  3. #3

  4. #4
    Sencha User
    Join Date
    Mar 2008
    Posts
    55
    Vote Rating
    10
    andong will become famous soon enough

      0  

    Default


    Are there any demos or screenshot?