PDA

View Full Version : Ext.form.SelectBox (Make a ComboBox work behave like an HTML SELECT)



corey.gilmore
17 May 2007, 2:27 PM
This started out as an extra function for Ext.form.ComboBox (http://extjs.com/forum/showthread.php?t=5039), and Jeff pointed out that you couldn't use the keyboard to navigate.

I've added basic searching to Ext.data.SimpleStore and extended Ext.form.ComboBox to create Ext.form.SelectBox. Type a letter to jump to and select the first matching result. Press that same key to find the next match. Home/End jump to the top/bottom of the list, PgUp/PgDn move the selection one 'page' at a time. There's only one new config option, searchResetDelay which is the amount of time before a search is reset. With a little extra effort this could be modified to support a deeper search so that typing 'Ver' fast would match Vermont instead of searching for matches to V, E and then R.

See the example page for more details:
http://crepitus.com/misc/ext/combo.html

May 29, 2007 - Fixed a bug with the way superclass functions were called that broke when using an Ext.form.Form

Edit: I didn't test it in Safari, fixed a few bugs there and with page up/down scrolling the page.

lgerndt
25 Jun 2007, 1:57 PM
Excellent code, thank you so much for writing and posting it. One problem: when the user clicks once to open and then clicks again to select (as oppposed to just clicking once and releasing on the item they want), "onselect" gets called twice. I need to hook into that and do something and I don't want it done twice, do you know an easy fix for this?

lgerndt
25 Jun 2007, 10:16 PM
This code is marriage of GroupComboBox and SelectBox, both third party contributions to this forum. I started with SelectBox, which is a very clean extension to Ext.form.ComboBox to make it behave like a normal HTTP select box, and then extracted the excellent parts of GroupComboBox that provide the support for optgroups.

Usage: input records should be as specified by the GroupComboBox example. I've added some code to stylize the optgroup headers nicely, as you can see in the picture below.



/**
* Searches through records and returns the index of the first match.
* @param {String} field A field in the records being searched
* @param {String/RegExp} value The string to compare to the field's value
* @param {Number} startIndex Index to begin searching at (defaults to 0). (optional)
* @param {Object} config (optional)
*/
Ext.data.Store.prototype.searchByString = function(field, value, startIndex) {
var record = false, rc = this.getCount(), range;
startIndex = startIndex || 0;
startIndex = startIndex >= rc ? 0 : startIndex;

if(!(value instanceof RegExp)) { // not a regex
value = String(value);
if(value.length === 0){
return false;
}
value = new RegExp("^" + Ext.escapeRe(value), "i");
}
if(rc > 0) {
range = this.getRange(startIndex);
Ext.each(range, function(r) {
if( value.test(r.data[field]) ) {
record = r;
return false;
}
});
if( !record && startIndex > 0 ) {
// if a startIndex was provided and we have no match, restart the search from the beginning
return this.searchByString(field, value, 0);
}
}
return record;
};

/*
This code is marriage of GroupComboBox and SelectBox,
both third party contributions to this forum. I started with
SelectBox, which is a very clean extension to
Ext.form.ComboBox to make it behave like a normal HTTP select
box, and then extracted the excellent parts of GroupComboBox that
provide the support for optgroups.

Usage: input records should be as specified by the GroupComboBox
example. I've added some code to stylize the optgroup headers nicely,
as you can see in the picture below.
*/

/**
* Original quote from Ext.form.SelectBox:
* Makes a ComboBox more closely mimic an HTML SELECT. Supports clicking and dragging
* through the list, with item selection occurring when the mouse button is released.
* When used will automatically set {@link #editable} to false and call {@link Ext.Element#unselectable}
* on inner elements. Re-enabling editable after calling this will NOT work.
*/
Ext.form.SelectGroupBox = function(config){
this.searchResetDelay = 1000;
config = config || {};
config = Ext.apply(config || {}, {
editable: false,
forceSelection: true,
rowHeight: false,
lastSearchTerm: false
});

var cls = 'x-combo-list';
this.tpl = '<div class="'+cls+'-item x-combo-list-hd">{' + config.groupField + '}</div><div class="'+cls+'-item x-combo-list-groupitem">{' + config.displayField + '}</div>';
Ext.form.SelectGroupBox.superclass.constructor.apply(this, arguments);

this.lastSelectedIndex = this.selectedIndex || 0;
};

Ext.extend(Ext.form.SelectGroupBox, Ext.form.ComboBox, {

groupField: undefined,

initEvents : function(){
Ext.form.SelectGroupBox.superclass.initEvents.apply(this, arguments);
// you need to use keypress to capture upper/lower case and shift+key, but it doesn't work in IE
this.el.on('keydown', this.keySearch, this, true);
this.on('beforeselect', this.beforeSelect, this, true);
this.cshTask = new Ext.util.DelayedTask(this.clearSearchHistory, this);
},

expand: function()
{
var l = this.innerList.dom.childNodes.length - 1;

for (var i=l; i>=0; i--)
{
var e = this.innerList.dom.childNodes[i];

if(Ext.util.Format.trim(e.innerHTML).length === 0)
{
Ext.get(e).remove();
}

}

this.view.updateIndexes();

Ext.form.SelectGroupBox.superclass.expand.call(this);
},

keySearch : function(e, target, options) {
var raw = e.getKey();
var key = String.fromCharCode(raw);
var startIndex = 0;

if( !this.store.getCount() ) {
return;
}

switch(raw) {
case Ext.EventObject.HOME:
e.stopEvent();
this.selectFirst();
return;

case Ext.EventObject.END:
e.stopEvent();
this.selectLast();
return;

case Ext.EventObject.PAGEDOWN:
this.selectNextPage();
e.stopEvent();
return;

case Ext.EventObject.PAGEUP:
this.selectPrevPage();
e.stopEvent();
return;
}

// skip special keys other than the shift key
if( (e.hasModifier() && !e.shiftKey) || e.isNavKeyPress() || e.isSpecialKey() ) {
return;
}
if( this.lastSearchTerm == key ) {
startIndex = this.lastSelectedIndex;
}
this.search(this.displayField, key, startIndex);
this.cshTask.delay(this.searchResetDelay);
},

onRender : function(ct, position) {
this.store.on('load', this.calcRowsPerPage, this);
Ext.form.SelectGroupBox.superclass.onRender.apply(this, arguments);
if( this.mode == 'local' ) {
this.calcRowsPerPage();
}
},

onSelect : function(record, index, skipCollapse){
if(this.fireEvent('beforeselect', this, record, index) !== false){
this.setValue(record.data[this.valueField || this.displayField]);
if( !skipCollapse ) {
this.collapse();
}
this.lastSelectedIndex = index + 1;
this.fireEvent('select', this, record, index);
}
},

render : function(ct) {
Ext.form.SelectGroupBox.superclass.render.apply(this, arguments);
if( Ext.isSafari ) {
this.el.swallowEvent('mousedown', true);
}
this.el.unselectable();
this.innerList.unselectable();
this.trigger.unselectable();
this.innerList.on('mouseup', function(e, target, options) {
if( target.id && target.id == this.innerList.id ) {
return;
}
this.onViewClick();
}, this);

this.innerList.on('mouseover', function(e, target, options) {
if( target.id && target.id == this.innerList.id ) {
return;
}
this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1;
this.cshTask.delay(this.searchResetDelay);
}, this);

this.trigger.un('click', this.onTriggerClick, this);
this.trigger.on('mousedown', function(e, target, options) {
e.preventDefault();
this.onTriggerClick();
}, this);

this.on('collapse', function(e, target, options) {
Ext.get(document).un('mouseup', this.collapseIf, this);
}, this, true);

this.on('expand', function(e, target, options) {
Ext.get(document).on('mouseup', this.collapseIf, this);
}, this, true);
},

clearSearchHistory : function() {
this.lastSelectedIndex = 0;
this.lastSearchTerm = false;
},

selectFirst : function() {
this.focusAndSelect(this.store.data.first());
},

selectLast : function() {
this.focusAndSelect(this.store.data.last());
},

selectPrevPage : function() {
if( !this.rowHeight ) {
return;
}
var index = Math.max(this.selectedIndex-this.rowsPerPage, 0);
this.focusAndSelect(this.store.getAt(index));
},

selectNextPage : function() {
if( !this.rowHeight ) {
return;
}
var index = Math.min(this.selectedIndex+this.rowsPerPage, this.store.getCount() - 1);
this.focusAndSelect(this.store.getAt(index));
},

search : function(field, value, startIndex) {
field = field || this.displayField;
this.lastSearchTerm = value;
var record = this.store.searchByString.apply(this.store, arguments);
if( record !== false ) {
this.focusAndSelect(record);
}
},

focusAndSelect : function(record) {
var index = this.store.indexOf(record);
this.select(index, this.isExpanded());
this.onSelect(record, index, this.isExpanded());
},

calcRowsPerPage : function() {
if( this.store.getCount() ) {
this.rowHeight = Ext.fly(this.view.getNode(0)).getHeight();
this.rowsPerPage = this.maxHeight / this.rowHeight;
} else {
this.rowHeight = false;
}
},

onViewClick : function(doFocus)
{
var index = this.view.getSelectedIndexes()[0];

var r = this.store.getAt(index);

if(r)
{

if(r.data.optgroup.length)
{
this.selectNext();
}
else
{
this.onSelect(r, index);
}
}
if(doFocus !== false){
this.el.focus();
}
},


onViewOver : function(e, t)
{
if(this.inKeyMode){ // prevent key nav and mouse over conflicts
return;
}
var item = this.view.findItemFromChild(t);

if(item){
var index = this.view.indexOf(item);

if(Ext.get(item).hasClass('x-combo-list-hd'))
{
//this.selectNext();
}
else
{
this.select(index, false);
}
}
},

selectNext : function(){
var ct = this.store.getCount();

if(ct > 0)
{
var index = this.selectedIndex;

if(index < ct-1)
{
var r = this.store.getAt(index+1);


if(r.data.optgroup.length)
{
this.selectedIndex += 1;

this.selectNext();
}
else
{
this.select(index+1);
}
}
else
{
this.selectedIndex = -1;

this.selectNext();
}
}
},

selectPrev : function()
{
var ct = this.store.getCount();

if(ct > 0)
{
var index = this.selectedIndex;
var r;
if(index === 0)
{
r = this.store.getAt(ct-1);

if(r.data.optgroup.length)
{
this.selectedIndex = ct;
this.selectPrev();
}
else
{
this.select(ct-1);
}
}
else
{
r = this.store.getAt(index-1);

if(r.data.optgroup.length)
{
this.selectedIndex -= 1;
this.selectPrev();
}
else
{
this.select(index-1);
}
}
}
},

beforeSelect : function(combo, record, index){
if (record.data.text == "Add more...")
{
// Becuase the author implemented detection of mouseup, he introduced a sort of bug:
// we get called here twice, once while expanded, and again after closed. We only want
// to do Add more... once, so we check for expanded, but we want to return false
// both times, so that this item never stays selected.
if (combo.isExpanded())
{
this.collapse();
window.alert("Add more... will be implemented soon!");
}
return false; // i.e. cancel the selection of this item
}
return true;
},

onLoad : function(){
if(!this.hasFocus){
return;
}
if(this.store.getCount() > 0){
this.expand();
this.restrictHeight();
if(this.lastQuery == this.allQuery){
if(this.editable){
this.el.dom.select();
}
if(!this.selectByValue(this.value, true)){
this.selectNext(); // changed from this.select(0, true);
}
}else{
this.selectNext();
if(this.typeAhead && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){
this.taTask.delay(this.typeAheadDelay);
}
}
}else{
this.onEmptyResults();
}
}
});

rajesha
28 Jun 2007, 10:58 PM
very good code, but i want use the select box code in the html,not an input field

http://extjs.com/forum/images/misc/progress.gif

HarryC
5 Jul 2007, 8:18 AM
The SelectGroupBox looks like just what I'm currently looking for, but I can't find the GroupComboBox mentioned above when searching the forum to see the correct syntax for using this. Can someone please elaborate or provide the correct link. Thanks.

lgerndt
5 Jul 2007, 8:23 AM
The SelectGroupBox looks like just what I'm currently looking for, but I can't find the GroupComboBox mentioned above when searching the forum to see the correct syntax for using this. Can someone please elaborate or provide the correct link. Thanks.
Sorry about that, I posted this in two different threads. Here's the thread link: http://extjs.com/forum/showthread.php?p=31645

skyey
5 Jul 2007, 8:32 AM
can i make a tree or grid inside the combobox? tks

johnnycannuk
6 Jul 2007, 7:03 PM
This looks really great...well done.

Now, I have not had the opportunity to test it out, but does this eliminate the IE bug where html selects are always on top, even when layout regions slide over them? If so, sold!

ChrisR
24 Jul 2007, 11:57 PM
Excellent code! Would be great if this would make it into the main Ext build!

Troy Wolf
19 Sep 2007, 8:48 AM
corey.gilmore or others,

Looking at your Ext.form.SelectBox demo and code, it looks and works very nicely. I found it because I want a Select (or ComboBox) that works more like a normal HTML Select. Mainly, when an item is selected, I do not want the list of options to change to only those that match the selected option text. This default functionality is nice for when a person is typing into the combobox looking for an option, but it is very unnatural when the user wants to later click the arrow to select a different option from the list----and nothing shows but the one selected option.

Wanting the list of options to remain static upon typing and selection seems like such a natural choice, that I would be very surprised to find that the Ext ComboBox cannot be naturally tweaked to behave this way.

corey.gilmore, since you obviously had to learn a lot about the ComboBox object, I figure you can easily answer my question. Do I need to use your SelectBox just to get this "basic" functionality?

Thank you!

Troy Wolf
20 Sep 2007, 6:16 AM
Btw, I am using SelectBox now. My ComboBoxes use JsonStore as opposed to SimpleStore. I was able to use your searchByString prototype simply by changing:



Ext.data.SimpleStore.prototype.searchByString = ....


To:



Ext.data.JsonStore.prototype.searchByString = ....


So far, it seems to work fine. Firebug is not reporting any errors.

Tefen
30 Nov 2007, 2:33 PM
corey.gilmore or others,

Looking at your Ext.form.SelectBox demo and code, it looks and works very nicely. I found it because I want a Select (or ComboBox) that works more like a normal HTML Select. Mainly, when an item is selected, I do not want the list of options to change to only those that match the selected option text. This default functionality is nice for when a person is typing into the combobox looking for an option, but it is very unnatural when the user wants to later click the arrow to select a different option from the list----and nothing shows but the one selected option.

Wanting the list of options to remain static upon typing and selection seems like such a natural choice, that I would be very surprised to find that the Ext ComboBox cannot be naturally tweaked to behave this way.

corey.gilmore, since you obviously had to learn a lot about the ComboBox object, I figure you can easily answer my question. Do I need to use your SelectBox just to get this "basic" functionality?

Thank you!



I think the answer is

triggerAction: 'all'


Took me a while to figure that one out myself.

Troy Wolf
30 Nov 2007, 2:43 PM
I think the answer is

triggerAction: 'all'


Took me a while to figure that one out myself.
I think you are correct. In fact, I'm no longer using the custom SelectBox object. I'm back to using the standard ComboBox with triggerAction:'all'. It gives me what I need for my app. ~o)

akannu
12 Dec 2007, 2:39 PM
Based on the documentation (see below) I would not have guessed that this config option makes the combo box work like a HTML select. What am I missing?
----------------
triggerAction
triggerAction : String
The action to execute when the trigger field is activated. Use 'all' to run the query specified by the allQuery config option (defaults to 'query')
This config option is defined by ComboBox.
----------------

corey.gilmore
13 Dec 2007, 10:37 AM
I never really noticed the requirement of triggerAction:'all' before, but from Combo.js you've got:


onTriggerClick : function(){
if(this.disabled){
return;
}
if(this.isExpanded()){
this.collapse();
this.el.focus();
}else {
this.onFocus({});
if(this.triggerAction == 'all') {
this.doQuery(this.allQuery, true);
} else {
this.doQuery(this.getRawValue());
}
this.el.focus();
}
}

Here's an updated version with some changes by Jack for Ext 2.0 that forces triggerAction etc.



/**
* Makes a ComboBox more closely mimic an HTML SELECT. Supports clicking and dragging
* through the list, with item selection occurring when the mouse button is released.
* When used will automatically set {@link #editable} to false and call {@link Ext.Element#unselectable}
* on inner elements. Re-enabling editable after calling this will NOT work.
*
* @author Corey Gilmore
* http://extjs.com/forum/showthread.php?t=6392
*
* @history 2007-07-08 jvs
* Slight mods for Ext 2.0
*/
Ext.ux.SelectBox = function(config){
this.searchResetDelay = 1000;
config = config || {};
config = Ext.apply(config || {}, {
editable: false,
forceSelection: true,
rowHeight: false,
lastSearchTerm: false,
triggerAction: 'all',
mode: 'local'
});

Ext.ux.SelectBox.superclass.constructor.apply(this, arguments);

this.lastSelectedIndex = this.selectedIndex || 0;
};

Ext.extend(Ext.ux.SelectBox, Ext.form.ComboBox, {
lazyInit: false,
initEvents : function(){
Ext.ux.SelectBox.superclass.initEvents.apply(this, arguments);
// you need to use keypress to capture upper/lower case and shift+key, but it doesn't work in IE
this.el.on('keydown', this.keySearch, this, true);
this.cshTask = new Ext.util.DelayedTask(this.clearSearchHistory, this);
},

keySearch : function(e, target, options) {
var raw = e.getKey();
var key = String.fromCharCode(raw);
var startIndex = 0;

if( !this.store.getCount() ) {
return;
}

switch(raw) {
case Ext.EventObject.HOME:
e.stopEvent();
this.selectFirst();
return;

case Ext.EventObject.END:
e.stopEvent();
this.selectLast();
return;

case Ext.EventObject.PAGEDOWN:
this.selectNextPage();
e.stopEvent();
return;

case Ext.EventObject.PAGEUP:
this.selectPrevPage();
e.stopEvent();
return;
}

// skip special keys other than the shift key
if( (e.hasModifier() && !e.shiftKey) || e.isNavKeyPress() || e.isSpecialKey() ) {
return;
}
if( this.lastSearchTerm == key ) {
startIndex = this.lastSelectedIndex;
}
this.search(this.displayField, key, startIndex);
this.cshTask.delay(this.searchResetDelay);
},

onRender : function(ct, position) {
this.store.on('load', this.calcRowsPerPage, this);
Ext.ux.SelectBox.superclass.onRender.apply(this, arguments);
if( this.mode == 'local' ) {
this.calcRowsPerPage();
}
},

onSelect : function(record, index, skipCollapse){
if(this.fireEvent('beforeselect', this, record, index) !== false){
this.setValue(record.data[this.valueField || this.displayField]);
if( !skipCollapse ) {
this.collapse();
}
this.lastSelectedIndex = index + 1;
this.fireEvent('select', this, record, index);
}
},

render : function(ct) {
Ext.ux.SelectBox.superclass.render.apply(this, arguments);
if( Ext.isSafari ) {
this.el.swallowEvent('mousedown', true);
}
this.el.unselectable();
this.innerList.unselectable();
this.trigger.unselectable();
this.innerList.on('mouseup', function(e, target, options) {
if( target.id && target.id == this.innerList.id ) {
return;
}
this.onViewClick();
}, this);

this.innerList.on('mouseover', function(e, target, options) {
if( target.id && target.id == this.innerList.id ) {
return;
}
this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1;
this.cshTask.delay(this.searchResetDelay);
}, this);

this.trigger.un('click', this.onTriggerClick, this);
this.trigger.on('mousedown', function(e, target, options) {
e.preventDefault();
this.onTriggerClick();
}, this);

this.on('collapse', function(e, target, options) {
Ext.getDoc().un('mouseup', this.collapseIf, this);
}, this, true);

this.on('expand', function(e, target, options) {
Ext.getDoc().on('mouseup', this.collapseIf, this);
}, this, true);
},

clearSearchHistory : function() {
this.lastSelectedIndex = 0;
this.lastSearchTerm = false;
},

selectFirst : function() {
this.focusAndSelect(this.store.data.first());
},

selectLast : function() {
this.focusAndSelect(this.store.data.last());
},

selectPrevPage : function() {
if( !this.rowHeight ) {
return;
}
var index = Math.max(this.selectedIndex-this.rowsPerPage, 0);
this.focusAndSelect(this.store.getAt(index));
},

selectNextPage : function() {
if( !this.rowHeight ) {
return;
}
var index = Math.min(this.selectedIndex+this.rowsPerPage, this.store.getCount() - 1);
this.focusAndSelect(this.store.getAt(index));
},

search : function(field, value, startIndex) {
field = field || this.displayField;
this.lastSearchTerm = value;
var index = this.store.find.apply(this.store, arguments);
if( index !== -1 ) {
this.focusAndSelect(index);
}
},

focusAndSelect : function(record) {
var index = typeof record === 'number' ? record : this.store.indexOf(record);
this.select(index, this.isExpanded());
this.onSelect(this.store.getAt(record), index, this.isExpanded());
},

calcRowsPerPage : function() {
if( this.store.getCount() ) {
this.rowHeight = Ext.fly(this.view.getNode(0)).getHeight();
this.rowsPerPage = this.maxHeight / this.rowHeight;
} else {
this.rowHeight = false;
}
}

});

akannu
13 Dec 2007, 11:17 AM
The User Extension SelectBox does solve what we are looking for (i.e. matching user input with 1st character and auto selecting)

However, I think in addition to COMBO BOX, Ext should provide the following controls:

1) DROP DOWN (Ext Widget for SELECT HTML element)
2) LIST (Ext Widget for SELECT HTML element with Multiple attribute)

with each having their own config options.

This would allow for flexibility and let everyone use what is best for them.

scottw
31 Jan 2008, 3:02 PM
One problem: when the user clicks once to open and then clicks again to select (as oppposed to just clicking once and releasing on the item they want), "onselect" gets called twice. I need to hook into that and do something and I don't want it done twice, do you know an easy fix for this?

I encountered the same problem. I stole your idea from another post to come up with this solution. The 'onSelect' method was updated to:


onSelect : function(record, index, skipCollapse){

// THIS ISEXPANDED CHECK IS NEW, THE REST IS UNCHANGED
// The authors implementation of this causes this method to be called twice,
// once while expanded, and again after closed.
// We only want this called once, so we check for expanded.
if (this.isExpanded()) {

if(this.fireEvent('beforeselect', this, record, index) !== false){
this.setValue(record.data[this.valueField || this.displayField]);
if( !skipCollapse ) {
this.collapse();
}
this.lastSelectedIndex = index + 1;
this.fireEvent('select', this, record, index);
}
}
},

bwenneker
4 Mar 2008, 2:12 AM
There's a bug in the focusAndSelect method:


focusAndSelect : function(record) {
var index = typeof record === 'number' ? record : this.store.indexOf(record);
this.select(index, this.isExpanded());
// => bug: this.store.getAt(record) returns undefined, because record is not always numeric
this.onSelect(this.store.getAt(record), index, this.isExpanded());
},So focusAndSelect should be:


focusAndSelect : function(record) {
var index = typeof record === 'number' ? record : this.store.indexOf(record);
this.select(index, this.isExpanded());
// => fixed: this.store.getAt(index) returns a record
this.onSelect(this.store.getAt(index), index, this.isExpanded());
},Great code by the way, thanks!

surfyogi
31 Mar 2008, 2:41 PM
I would have to agree; ExtJS does some really amazing things, and then leaves me a bit stranded when I simply try to replace the select tags in my forms... which should be the first goal of the API considering the bugs in IE that make the native select tags such a nightmare to work with/around.

That said, is there an up to date (2.0) version of this simple example??
http://crepitus.com/misc/ext/combo.html


I'm pretty lost here, just trying to 2.0-ify this example; not working; I know I need to change out all the libs, and the SelectBox.js with current 2.0 versions, and I think I need to use .render() instead of appy to; anything else?

Here's my code that's not working (from above SelectBox Demo.htm):

Ext.onReady(function() {
// simple array store
var store = new Ext.data.SimpleStore({
fields: ['abbr', 'state'],
data : Ext.exampledata.states // from states.js
});
var combo = new Ext.ux.SelectBox({
store: store,
displayField:'state',
mode: 'local',
triggerAction: 'all',
emptyText:'Select a state...',
selectOnFocus:true
});

combo.render('local-states');

});

Everything seems cool, until combo.render gets called..

This is the line that I can get back to on a call stack:

Ext.ux.SelectBox.superclass.onRender.apply(this, arguments);

in the onRender method of SelectBox.js.

Anybody recognize why this example blows up on me?

corey.gilmore
31 Mar 2008, 2:44 PM
Take a look at the updated code here (post #15 in this thread) (http://extjs.com/forum/showthread.php?p=98733#post98733)

lgerndt
31 Mar 2008, 2:51 PM
I did indeed update SelectGroupBox for Ext 2.0. The new code is posted here (http://extjs.com/forum/showthread.php?p=139053#post139053).

surfyogi
31 Mar 2008, 3:41 PM
I appreciate the quick replies; unfortunately they don't help me here.
I just want a simple example that works; unfortunately, the example I posted that I've updated to use the current versions of ext-all, base, SelectBox and states.js does not work.

Still looking at why... if I replace the ux.SelectBox with form.ComboBox, it works..
if I move .render to applyTo within the combobox declaration as a parameter.

Any idea why this example doesn't work?

Ext.onReady(function() {
// simple array store
var store = new Ext.data.SimpleStore({
fields: ['abbr', 'state'],
data : Ext.exampledata.states // from states.js
});
var combo = new Ext.ux.SelectBox({
store: store,
displayField:'state',
mode: 'local',
triggerAction: 'all',
selectOnFocus:true,
applyTo: 'local-states'
});
});

But this one does?

Ext.onReady(function() {
// simple array store
var store = new Ext.data.SimpleStore({
fields: ['abbr', 'state'],
data : Ext.exampledata.states // from states.js
});
var combo = new Ext.form.ComboBox({
store: store,
displayField:'state',
mode: 'local',
triggerAction: 'all',
selectOnFocus:true,
applyTo: 'local-states'
});
});

Also, with a combobox or selectbox, I thought I could set a param for:

setValue: 'AL'

and that would make the first item in the states.js come up as default selection, but that does not work under any circumstances I can find. Any clues on how to get a default selection from the store data set?

thanks!

surfyogi
31 Mar 2008, 5:08 PM
Did a little debug session here; looks like SelectBox.js may be set up to work with MulitCombo but assumes there is an "innerList" of some kind.

In the render method, if I comment out references to innerList, the thing just starts working...

thanks for the code, nice stuff.

Jeff-

render : function(ct) {
Ext.ux.SelectBox.superclass.render.apply(this, arguments);
if( Ext.isSafari ) {
this.el.swallowEvent('mousedown', true);
}
this.el.unselectable();
//this.innerList.unselectable();
this.trigger.unselectable();
/*
this.innerList.on('mouseup', function(e, target, options) {
if( target.id && target.id == this.innerList.id ) {
return;
}
this.onViewClick();
}, this);
this.innerList.on('mouseover', function(e, target, options) {
if( target.id && target.id == this.innerList.id ) {
return;
}
this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1;
this.cshTask.delay(this.searchResetDelay);
}, this);
*/
----------------------------------------------------

So therefore, this SIMPLE example now works:

Ext.onReady(function(){
var combo = new Ext.ux.SelectBox({
store: new Ext.data.SimpleStore({
fields: ['abbr', 'state'],
data : [['AL', 'Alabama'],
['AK', 'Alaska'],
['AZ', 'Arizona']]
}),
displayField:'state',
typeAhead: false,
mode: 'local',
editable: false,
triggerAction: 'all',
value:'Alabama',
applyTo: 'local-states'
});
});

Arthur.Blake
4 Apr 2008, 9:47 AM
SelectBox is great stuff! Thanks for working on it!

Given that SelectBox is really even more basic than ComboBox...

What are the chances this can be built in to the next version of ExtJS with its own xtype or possibly add all of this functionality into ComboBox, controlled with new config options?

ntulip
10 Apr 2008, 1:34 PM
Has anyone had issues with the selection model on the dropdown?

I ask because on a Mac both under Safari and Firefox, once you select an item from the dropdown, if you decide to go back and select a different option, the only option available is the one you last selected.

Any ideas here?

THanks,
nick

extjsF4n
19 May 2008, 3:42 PM
Same problem. Happens for me on latest Firefox.

akannu
23 May 2008, 12:36 PM
SelectBox is great stuff! Thanks for working on it!

Given that SelectBox is really even more basic than ComboBox...

What are the chances this can be built in to the next version of ExtJS with its own xtype or possibly add all of this functionality into ComboBox, controlled with new config options?

I agree with this. I know I have expressed my opinion on every thread I got a chance. This is just too close to my heart.

akannu
29 May 2008, 5:50 AM
I know several people including me have been asking for the behavior of 'matching on initials' to mimic the HTML SELECT control.

We just noticed that HTML SELECT behaves differently in latest browsers. Assume a combo for US states. In IE6, if I type "AL" the combo first shows Alabama and then switches to Lousiana. Try the same SELECT in IE7 and FF2. If you tpe "AL", the combo will continue to show ALABAMA. Our observation is that the latest browsers seem to work off on a delay factor and if you type reasonably fast it is trying to match for the entire string instead of treating each character as an initial you are trying to match for. If you allow a substantial delay between A & L, then it will try to treat it as initials and give you the IE6 behavior.

If this is the trend in latest browsers, it seems that we should think about the requests that some of us are making because we dont want to be inconsistent with SELECT in recent browsers.

Has anyone else noticed what we see? and any thoughts/recommendations?

scottw
31 Oct 2008, 8:48 AM
This extension did not initially work for me on Ext 2.2. I was getting the error "this.view has no properties". The solution that I found was to set 'lazyInit' to false on the SelectBox.

You will find that there are two versions of SelectBox.js on the Ext 2.2 download. One of these (the one in the 'air\samples' directory) has 'lazyInit:false' hard-coded in it, but the other does not. Be careful which you use or just pass in 'lazyInit:false' yourself.

It is unfortunate that this has to be done because I assume there are some performance implications, particulary for a SelectBox that is never expanded. Anyone have a better fix?

scottw
31 Oct 2008, 9:12 AM
As described previously, the SelectBox does not function unless you set lazyInit to false. I came up with another solution. I overloaded the ComboBox method 'initList' and move anything dealing with 'innerList' and 'view' into it as follows:


initList : function(){

Ext.ux.SelectBox.superclass.initList.apply(this, arguments);

this.calcRowsPerPage();

this.innerList.unselectable();
this.innerList.on('mouseup', function(e, target, options) {
if( target.id && target.id == this.innerList.id ) {
return;
}
this.onViewClick();
}, this);

this.innerList.on('mouseover', function(e, target, options) {
if( target.id && target.id == this.innerList.id ) {
return;
}
this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1;
this.cshTask.delay(this.searchResetDelay);
}, this);
},

I also removed the 'lazyInit:false'. Here is the complete code:


Ext.ux.SelectBox = function(config){
this.searchResetDelay = 1000;
config = config || {};
config = Ext.apply(config || {}, {
editable: false,
forceSelection: true,
rowHeight: false,
lastSearchTerm: false,
triggerAction: 'all',
mode: 'local'
});

Ext.ux.SelectBox.superclass.constructor.apply(this, arguments);

this.lastSelectedIndex = this.selectedIndex || 0;
};

Ext.extend(Ext.ux.SelectBox, Ext.form.ComboBox, {
// REMOVED - lazyInit: false,
initEvents : function(){
Ext.ux.SelectBox.superclass.initEvents.apply(this, arguments);
// you need to use keypress to capture upper/lower case and shift+key, but it doesn't work in IE
this.el.on('keydown', this.keySearch, this, true);
this.cshTask = new Ext.util.DelayedTask(this.clearSearchHistory, this);
},

keySearch : function(e, target, options) {
var raw = e.getKey();
var key = String.fromCharCode(raw);
var startIndex = 0;

if( !this.store.getCount() ) {
return;
}

switch(raw) {
case Ext.EventObject.HOME:
e.stopEvent();
this.selectFirst();
return;

case Ext.EventObject.END:
e.stopEvent();
this.selectLast();
return;

case Ext.EventObject.PAGEDOWN:
this.selectNextPage();
e.stopEvent();
return;

case Ext.EventObject.PAGEUP:
this.selectPrevPage();
e.stopEvent();
return;
}

// skip special keys other than the shift key
if( (e.hasModifier() && !e.shiftKey) || e.isNavKeyPress() || e.isSpecialKey() ) {
return;
}
if( this.lastSearchTerm == key ) {
startIndex = this.lastSelectedIndex;
}
this.search(this.displayField, key, startIndex);
this.cshTask.delay(this.searchResetDelay);
},

onRender : function(ct, position) {
this.store.on('load', this.calcRowsPerPage, this);
Ext.ux.SelectBox.superclass.onRender.apply(this, arguments);
/* REMOVED
if( this.mode == 'local' ) {
this.calcRowsPerPage();
}*/
},

onSelect : function(record, index, skipCollapse){
if(this.fireEvent('beforeselect', this, record, index) !== false){
this.setValue(record.data[this.valueField || this.displayField]);
if( !skipCollapse ) {
this.collapse();
}
this.lastSelectedIndex = index + 1;
this.fireEvent('select', this, record, index);
}
},

render : function(ct) {
Ext.ux.SelectBox.superclass.render.apply(this, arguments);
if( Ext.isSafari ) {
this.el.swallowEvent('mousedown', true);
}
this.el.unselectable();
// REMOVED - this.innerList.unselectable();
this.trigger.unselectable();
/* REMOVED
this.innerList.on('mouseup', function(e, target, options) {
if( target.id && target.id == this.innerList.id ) {
return;
}
this.onViewClick();
}, this);

this.innerList.on('mouseover', function(e, target, options) {
if( target.id && target.id == this.innerList.id ) {
return;
}
this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1;
this.cshTask.delay(this.searchResetDelay);
}, this);*/

this.trigger.un('click', this.onTriggerClick, this);
this.trigger.on('mousedown', function(e, target, options) {
e.preventDefault();
this.onTriggerClick();
}, this);

this.on('collapse', function(e, target, options) {
Ext.getDoc().un('mouseup', this.collapseIf, this);
}, this, true);

this.on('expand', function(e, target, options) {
Ext.getDoc().on('mouseup', this.collapseIf, this);
}, this, true);
},

initList : function(){

Ext.ux.SelectBox.superclass.initList.apply(this, arguments);

this.calcRowsPerPage();

this.innerList.unselectable();
this.innerList.on('mouseup', function(e, target, options) {
if( target.id && target.id == this.innerList.id ) {
return;
}
this.onViewClick();
}, this);

this.innerList.on('mouseover', function(e, target, options) {
if( target.id && target.id == this.innerList.id ) {
return;
}
this.lastSelectedIndex = this.view.getSelectedIndexes()[0] + 1;
this.cshTask.delay(this.searchResetDelay);
}, this);
},

clearSearchHistory : function() {
this.lastSelectedIndex = 0;
this.lastSearchTerm = false;
},

selectFirst : function() {
this.focusAndSelect(this.store.data.first());
},

selectLast : function() {
this.focusAndSelect(this.store.data.last());
},

selectPrevPage : function() {
if( !this.rowHeight ) {
return;
}
var index = Math.max(this.selectedIndex-this.rowsPerPage, 0);
this.focusAndSelect(this.store.getAt(index));
},

selectNextPage : function() {
if( !this.rowHeight ) {
return;
}
var index = Math.min(this.selectedIndex+this.rowsPerPage, this.store.getCount() - 1);
this.focusAndSelect(this.store.getAt(index));
},

search : function(field, value, startIndex) {
field = field || this.displayField;
this.lastSearchTerm = value;
var index = this.store.find.apply(this.store, arguments);
if( index !== -1 ) {
this.focusAndSelect(index);
}
},

focusAndSelect : function(record) {
var index = typeof record === 'number' ? record : this.store.indexOf(record);
this.select(index, this.isExpanded());
//this.onSelect(this.store.getAt(record), index, this.isExpanded());
// => fixed: this.store.getAt(index) returns a record
this.onSelect(this.store.getAt(index), index, this.isExpanded());
},

calcRowsPerPage : function() {
if( this.store.getCount() ) {
this.rowHeight = Ext.fly(this.view.getNode(0)).getHeight();
this.rowsPerPage = this.maxHeight / this.rowHeight;
} else {
this.rowHeight = false;
}
}
});

prophet
4 Jan 2010, 12:34 PM
scottw's onSelect fix (http://www.extjs.com/forum/showthread.php?p=117889#post117889) helped me quite a bit! Thanks scottw!

j0452
1 Jun 2010, 12:28 PM
I'm using a SelectBox for an optional field. When first rendered, the SelectBox has no selection. If you select something, there's no way to unset the SelectBox, all you can do is select a different option. What is the correct way to allow the user to unset the SelectBox?

Adding an empty record to the SelectBox's store feels like a kludge. The SelectBox for the optional field in question shares a store with another SelectBox which is not optional. It would be wrong to add an empty record to the store since it would be invalid in the case of the required field. It also feels wrong to create a whole separate store just for the optional field. Is this use case supported? If not, what would be the correct way to support it? Perhaps SelectBox could accept a config parameter "allowUnset", which if true adds a blank option to the top of the list.

Thanks in advance!

Phil.Strong
16 May 2012, 10:07 AM
I wonder if we can get a gist or git repository for this plugin ...

This would make it easier to see what the latest was