PDA

View Full Version : Combobox functionality: optgroups



jd_porter
11 May 2007, 8:28 AM
I'm looking for a way to mimic optgroups with Ext.form.Combobox. Is this possible?

In short, I need items in an combobox to be unselectable and to have a different appearance than selectable items. Here's a very terse example (http://marcusaureli.us/optgroups.html) of an html select element with optgroups for reference.

Any help would be greatly, greatly appreciated.

jd_porter
15 May 2007, 7:02 PM
bump.

waterlowa
15 May 2007, 10:20 PM
Yes something similar to OPTGROUP will be great.

I've been playing with ComboBox and would like to know how to
- generate OPTGROUP
- add a separator
- enable/disable options
- create one that's similar to the Extjs's "Search Forums" (see image)

Some of these functions may already exist but I haven't had much luck finding the right solution apart from autoCreate and transform from existing html.

Any suggestion will be greatly appreciated :D

jd_porter
16 May 2007, 7:51 AM
I'm thinking about an extended class that would run something like

new Ext.form.GroupComboBox ({
store: store,
displayField: 'disp',
valueField: 'val',
groupField: 'grouping'
mode: 'remote',
triggerAction: 'all'
});

where the groupField would determine how things get grouped together. Thoughts?

waterlowa
17 May 2007, 12:52 AM
Ok here's my first attempt, not perfect (actually very crude) but it's a start:



Ext.form.GroupComboBox = function(config)
{
var cls = 'x-combo-list';
this.tpl = '<div class="'+cls+'-item x-combo-list-group">{' + config.groupField + '}</div><div class="'+cls+'-item x-combo-list-groupitem">{' + config.displayField + '}</div>';

Ext.form.GroupComboBox.superclass.constructor.call(this, config);
};

Ext.extend(Ext.form.GroupComboBox, Ext.form.ComboBox,
{
groupField: undefined,

initEvents: function()
{
Ext.form.GroupComboBox.superclass.initEvents.call(this);

this.keyNav.down = function(e)
{
if(!this.isExpanded()){
this.onTriggerClick();
}else{
this.inKeyMode = true;
}
this.selectNext();
};
},

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.GroupComboBox.superclass.expand.call(this);
},

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-group'))
{
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;

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

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

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

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();
}
}
});


Usage:


var store = new Ext.data.SimpleStore({
fields: ['optgroup', 'value', 'text'],
data : [
['Ext', '', ''],
['', 'A', 'a'],
['', 'B', 'b'],
['YUI', '', ''],
['', 'C', 'c'],
['', 'D', 'd']
]
});


combo = new Ext.form.GroupComboBox({
fieldLabel: 'Test Combo',
width:175,
id: 'test',
displayField: 'text',
valueField: 'value',
groupField: 'optgroup',
store: store,
mode: 'local',
lazyRender: true,
typeAhead: true
});


The CSS:


.x-combo-list-group
{
font-weight: bold;
}

.x-combo-list-groupitem
{
padding-left: 10px;
}


I've made it such that pressing up and down scroll through the list continuously. Also haven't tested on IE.

jd_porter
17 May 2007, 6:54 AM
Wow, that's impressive effort! I'll have some testing time later this afternoon & let you know how it works. :D

waterlowa
21 May 2007, 9:09 PM
Also works with JSON data which I much prefer:



var myData = {"rows":[
{"optgroup": "Ext"},
{"value": "e", "text": "EE"},
{"value": "f", "text": "FF"},
{"optgroup": "YUI"},
{"value": "g", "text": "GG"},
{"value": "h", "text": "HH"}
]};

var record = Ext.data.Record.create([{name: 'optgroup'}, {name: 'value'}, {name: 'text'}]);

var reader = new Ext.data.JsonReader({
root: "rows"
},
record);

var store2 = new Ext.data.Store({
proxy: new Ext.data.MemoryProxy(myData),
reader: reader
});
store2.load();

this.elUsername = new Ext.form.GroupComboBox({
fieldLabel: 'Username',
width:175,
id: 'username',
displayField: 'text',
valueField: 'value',
groupField: 'optgroup',
store: store2,
mode: 'local',
lazyRender: true,
typeAhead: false
});


A drawback: typeAhead breaks. So for the time being typeAhead should be set to false. I'm still trying to figure it out. Sorry, I'm a javascript newbie...

jd_porter
22 May 2007, 9:02 AM
I can't get this to work smoothly with XML datasources. Consider the xml fragment below. Each of the optgroups are an element inside of an item. I know that your code would expect each optgroup to exist as a sibling of items, but I don't have sufficient control of the xml source to do that. Any ideas?


<item>
<optgroup>a</optgroup>
<id>8</id>
<title>123</title>
</savedSearches>
</item>
<item>
<optgroup>b</optgroup>
<id>2</id>
<title>456</title>
</savedSearches>
</item>

waterlowa
22 May 2007, 7:29 PM
On top of my head I can't think of a solution for this kind of data structure.

The combobox uses the selected index to look up the data in the store. So with your xml data when you select "123", the index in the view is 1 but its index in the store is 0. Hence it's not working properly...

If you can get your xml to look like this it should work:



<item>
<optgroup>a</optgroup>
<id />
<title />
</item>
<item>
<optgroup />
<id>8</id>
<title>123</title>
</item>
<item>
<optgroup>b</optgroup>
<id />
<title />
</item>
<item>
<optgroup />
<id>2</id>
<title>456</title>
</item>


Hopefully someone can come up with a solution or a different GroupComboBox class that'll cater to all kinds of data structure.

lgerndt
25 Jun 2007, 10:12 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();
}
}
});

aconran
26 Jun 2007, 10:03 AM
Looks very nice, I look forward to something like this being in the official Ext distribution.

cgi-bin
2 Jul 2007, 7:08 AM
The example given are close, but this needs to be more flexible to allow mixed options and optgroups together.

Something that could emulate this:
<select>
<option>option1</option>
<optgroup label="group1">
<option>group1 opt1</option>
<option>group1 opt2</option>
<option>group1 opt3</option>
</optgroup>
<optgroup label="group2">
<option>group2 opt1</option>
<option>group2 opt2</option>
</optgroup>
<option>option2</option>
<option>option3</option>
<optgroup label="group3">
<option>group3 opt1</option>
<option>group3 opt2</option>
<option>group3 opt3</option>
</optgroup>
</select>
Would probably need to define a field in the store as the "group", and if null, it is rendered as a normal option, otherwise the option is placed in the group. Unfortunately I think that might require it to read the whole store to generate the groups before rendering.

Another possibility would be to handle it like multi-level menus, and have the optgroups have an arrow to render the "sub-options".

akhalil
5 Nov 2007, 4:47 PM
Well done there man - looks like you did a lot of work on this ...

May I ask you a question about it though?

Does the typeAhead thing ( the filter search thing) still work here??? So is it still behaving exactly like the comboBox in every other respect?

Thankyou very much - sorry for troubling you after you've already done lots of work on it.

lgerndt
5 Nov 2007, 5:15 PM
Well done there man - looks like you did a lot of work on this ...
Does the typeAhead thing ( the filter search thing) still work here??? So is it still behaving exactly like the comboBox in every other respect?

Yes, although there is room for improvement in two areas:
1. type-ahead is implemented in a rather strange way: you type only the first letter, repeatedly, until you see the item you want. you don't type the first few letters. I'm not sure why he implemented it like that but it works.
2. I'm finding (just today) that type-ahead doesn't work if the datastore is in remote mode. The reason is simple: it doesn't have any records until the user clicks it, telling it to do the remote query.

akhalil
5 Nov 2007, 6:46 PM
Thankyou for your quick reply ...

OK,
with point 2) .... I'm ok with that - I'm happy for it work just as 'local'

with point 1) .... Do you think that I can modify so the typeAhead feature would work just like in the original Ext ComboBox??

Or do you think that thre are risks here - I just don't want to waist a lot of time only to findout that thre are some limitations,

The reason for my question is that management here love the typeAhead feature - it must work .. but they also want the grouping thing to work too.... what do you think mate?

Thankyou very much.

lgerndt
5 Nov 2007, 7:51 PM
Do you think that I can modify so the typeAhead feature would work just like in the original Ext ComboBox??

Yes. Yes I do. Seriously, it would be a valuable enhancement to this class.

j_butterfly
28 Jan 2008, 7:36 PM
hi,lgerndt
Could you put full optgroups demo on?
i copy your code for my app ,but not working..

j_butterfly
29 Jan 2008, 7:56 PM
Combobox functionality (http://extjs.com/forum/showthread.php?p=116136#post116136):optgroups can work with EXT2.0?

mw-flow
6 Mar 2008, 1:41 AM
Hi all,

thanks for your sharing your great SelectGroupBox code. With the arrival of 2.0, it seems the code no longer works which is a pity because the functionality is something that is, according to my experiences, needed quite often. I'm neither sure if you're still working on that code nor have i found an update on this code in another thread, so i'll just give you a hint on what seems to be wrong now:

There are at least two lines where the code produces errors now:

in function calcRowsPerPage(...), the line
this.rowHeight = Ext.fly(this.view.getNode(0)).getHeight();
this.view does not exist
in function render(...), the this.innerList does not exist (which will probably affect almost all other methods as well).

lgerndt
16 Mar 2008, 12:37 PM
I was hoping that in Ext 2.0, we would have something like this in the official distribution, but alas, no such luck. And unfortunately, as the previous post indicates, it no longer works in Ext 2.0. I'm in the process of debugging now, but if anyone has already fixed this, please share. If I fix, I will. Thanks!

brian.moeskau
16 Mar 2008, 1:29 PM
Actually, I don't think anyone in the core team at the time (pre-2.0) ever noticed this thread. I agree that it would make a nice addition to the combo, so we'll consider it for a future release. Meanwhile, I'll move this thread to the feature request form where I can keep an eye on it ;)

lgerndt
16 Mar 2008, 11:08 PM
Ok, here's an update which is Ext 2.0 compatible. I've performed some limited testing and it appears to work fine. By the way, at some point Jack gave us an updated version of SelectBox (http://extjs.com/forum/showthread.php?p=98733#post98733), and I've incorporated his update in this code.


/**
* This code is a marriage of GroupComboBox and SelectBox,
* both third party contributions to the EXT forum. I started with
* SelectBox, which is a very clean extension for making the combo
* behave like a normal HTTP select box, and then extracted the excellent
* parts of GroupComboBox that provide the support for optgroups.
*
* The pastor who married them: lgerndt
*/

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

// the records in our combo have an 'optgroup' field and a 'text' field. If the 'optgroup' field is not empty, the 'text' field
// will be, and vice versa. We use this fact to render the group headers with a different class than the items in the group.
var cls = 'x-combo-list';
this.tpl = new Ext.XTemplate(
'<tpl for=".">',

// if the length of the 'optgroup' field is non-zero, render an optgroup title div
'<tpl if="optgroup.length &gt; 0">',
'<div class="'+cls+'-item x-combo-list-hd">{' + config.groupField + '}</div>',
'</tpl>',

// if the length of the 'text' field is non-zero, render an item div
'<tpl if="text.length &gt; 0">',
'<div class="'+cls+'-item x-combo-list-groupitem">{' + config.displayField + '}</div>',
'</tpl>',

'</tpl>'
);
Ext.form.SelectGroupBox.superclass.constructor.apply(this, arguments);

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

Ext.extend(Ext.form.SelectGroupBox, Ext.form.ComboBox, {
lazyInit: false,

//-----------------------------------------------------------------------------------------------
// from GroupComboBox
//-----------------------------------------------------------------------------------------------

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);
},

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);
//console.log(index);
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 == MYCOMPANY.Resources.Common.Labels.editCategories)
{
// Because the author implemented a handler for mouseup, he introduced a sort of bug:
// we can get called here twice, once while expanded, and again after closed. We only want
// to do Add/Edit categories... 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.location["href"] = MYCOMPANY.Url.getBasePath() + "pages/main/edit-categories.jsf";
}
return false; // return false to 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();
}
},

//-----------------------------------------------------------------------------------------------
// from SelectBox
//-----------------------------------------------------------------------------------------------

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 && 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;
}
}

});

allampraveen
29 Apr 2008, 2:27 AM
Hi,

Can you give a link for live demo or samples to use the group combo. It would be highly appreciated