PDA

View Full Version : Ext.ux.BoxSelect (like the Facebook's one)



tintin
25 Apr 2008, 1:31 PM
After having seen this (http://www.interiders.com/2008/02/18/protomultiselect-02/), read this (http://extjs.com/forum/showthread.php?t=29851&highlight=facebook), and this (http://extjs.com/forum/showthread.php?p=106219), I wrote this extension.

http://extjs.efattal.fr/examples/boxselect/screenshot.jpg (http://extjs.efattal.fr/examples/boxselect/)

Demo (http://extjs.efattal.fr/examples/boxselect/)

Usage:

Items can be deleted individually
Managed keys: backspace, delete, left/right arrows


Todo list:

Manage some combo config options like allowBlank=false


Tested in IE6/7, FF2/3, Opera, Chrome.

All advices are welcome

04.30.2008 : Corrected bug on delete key pressed

09.15.2008 : NEW VERSION

File renamed (initially it was intended to be a plugin but not anymore)
Duplicate entries avoided (it's not an option, otherwise it wouldn't work correctly)
Right rendering in different browsers (rounded corners only in Gecko)
New methods like setValue, enable, disable
Initial value handled in different ways (collection of record, array of IDs, string of IDs)

VinylFox
25 Apr 2008, 1:47 PM
Love it...you come up with some sweet stuff. Love toast as well.

Keep up the good work.

chalu
25 Apr 2008, 6:23 PM
This is awesome, you've handed me the base for my next extension :D . Keep it up

krycek
25 Apr 2008, 8:44 PM
tintin, just see your extension.

Great great work.

Congratulations!

=D>=D>

stever
26 Apr 2008, 11:27 AM
Very nice!

SteveEisner
26 Apr 2008, 12:39 PM
This rocks. Great job.

I've noticed from the demo that there are some selection issues:
* when you start to type, and then back up erasing the entire word, it selects the previous "resolved" item rather than leaving you with a caret on empty text
* cursor left and right gets confused sometimes (again, probably around empty text)

I haven't used the component yet but if when do I'll try to figure out what's breaking those
Steve

pibos
27 Apr 2008, 12:09 AM
I added initialValue functionality to Ext.ux.BoxSelect so that BoxSelect can be shown with intitial selection. Just add the value field to BoxSelect constructor config; it's value must be an array of objects having at minimum the fields specifyied in displayField and valueField

here is the code


Ext.ux.BoxSelect

Ext.namespace('Ext.ux.plugins');

Ext.ux.Box = Ext.extend(Ext.Component, {
initComponent : function(){
Ext.ux.Box.superclass.initComponent.call(this);
},

onRender: function(ct, position){
Ext.ux.Box.superclass.onRender.call(this, ct, this.maininput);

this.addEvents('remove');

this.addClass('bit-box');

this.el = ct.createChild({ tag: "li" }, this.maininput);
this.el.addClassOnOver('bit-hover');

Ext.apply(this.el, {

'focus': function(){
this.down('a.closebutton').focus();
},

'dispose': function(){
this.dispose()
}.createDelegate(this)

});

this.el.on('click', function(e){
this.focus()
}, this, {stopEvent:true});

this.el.update(this.caption);

this.lnk = this.el.createChild({
'tag': 'a',
'class': 'closebutton',
'href':'#'
});

this.lnk.on({
'click': function(e){
e.stopEvent();
this.fireEvent('remove', this);
this.dispose();
},
'focus': function(){
this.el.addClass("bit-box-focus");
},
'blur': function(){
this.el.removeClass("bit-box-focus");
},
scope: this
});

new Ext.KeyMap(this.lnk, [
{
key: [Ext.EventObject.BACKSPACE, Ext.EventObject.DELETE],
fn: function(){
this.dispose();
}.createDelegate(this)
},
{
key: Ext.EventObject.RIGHT,
fn: function(){
this.move('right');
}.createDelegate(this)
},
{
key: Ext.EventObject.LEFT,
fn: function(){
this.move('left');
}.createDelegate(this)
},
{
key: Ext.EventObject.TAB,
fn: function(){
}.createDelegate(this)
}
]).stopEvent = true;

},

move: function(direction) {
if(direction == 'left')
el = this.el.prev();
else
el = this.el.next();
if(el)
el.focus();
},

dispose: function() {
//if(el.prev() && this.retrieveData(el.prev(), 'small') ) el.prev().remove();
//if(this.current == el) this.focus(el.next());
//if(el.data['type'] == 'box') el.onBoxDispose(this);

Ext.fly(this.hidden).remove();
this.el.hide({
duration: .1,
callback: function(){
this.move('right');
this.destroy()
}.createDelegate(this)
});


return this;
}
});

Ext.ux.BoxSelect = Ext.extend(Ext.form.ComboBox, {
initComponent:function() {
Ext.apply(this, {
selectedValues: {},
boxElements: {},
current: false,
options: {
className: 'bit',
separator: ','
},
hideTrigger: true,
grow: false
});

Ext.ux.BoxSelect.superclass.initComponent.call(this);
},

onRender:function(ct, position) {
Ext.ux.BoxSelect.superclass.onRender.call(this, ct, position);

this.el.removeClass('x-form-text');
this.el.className = 'maininput';
this.el.dom.name = '';
this.el.setWidth(20);
this.el.dom.value = ''


this.holder = this.el.wrap({
'tag': 'ul',
'class':'holder x-form-text'
});

this.holder.on('click', function(e){
e.stopEvent();
if(this.maininput != this.current) this.focus(this.maininput);
}, this);

this.maininput = this.el.wrap({
'tag': 'li', 'class':'bit-input'
});

Ext.apply(this.maininput, {
'focus': function(){
this.focus();
}.createDelegate(this)
});

if(typeof this.displayFieldTpl === 'string')
this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl)

if(this.value){
if(typeof this.value === 'string')
this.value = [this.value]
}

Ext.each(this.value, function(item){

if(this.displayFieldTpl)
caption = this.displayFieldTpl.apply(item);
else
caption = item[this.displayField]

this.addBox(item[this.valueField], caption);
}, this);


},

onResize : function( w, h, rw, rh ){
this._width = w;
Ext.ux.BoxSelect.superclass.onResize.call(this, w, h, rw, rh);
this.autoSize();
},

onKeyUp : function(e) {

if(this.editable !== false && !e.isSpecialKey()){
this.lastKey = e.getKey();
if(e.getKey() == e.BACKSPACE && this.el.dom.value.length == 0){
e.stopEvent();
this.collapse();
var el = this.maininput.prev();
if(el) el.focus();
return;
}
this.dqTask.delay(this.queryDelay);
}

this.autoSize();

Ext.ux.BoxSelect.superclass.onKeyUp.call(this, e);
},

onSelect: function(record, index) {
var val = record.data[this.valueField];

this.selectedValues[val] = val;

if(typeof this.displayFieldTpl === 'string')
this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl)


if(!this.boxElements[val]){
var caption;
if(this.displayFieldTpl)
caption = this.displayFieldTpl.apply(record.data)
else if(this.displayField)
caption = record.data[this.displayField];

this.addBox(record.data[this.valueField], caption)

}
this.collapse();
this.setRawValue('');
this.lastSelectionText = '';
this.applyEmptyText();

this.autoSize();
},
addBox: function(id, caption){
var box = new Ext.ux.Box({
id: 'Box_' + id,
maininput: this.maininput,
renderTo: this.holder,
className: this.options['className'],
caption: caption,
'value': id,
listeners: {
'remove': function(box){
this.selectedValues[box.value] = null;
},
scope: this
}
});
box.render();

box.hidden = this.el.insertSibling({
'tag':'input',
'type':'hidden',
'value': id,
'name': (this.hiddenName || this.name)
},'before', true);

},
autoSize : function(){
if(!this.rendered){
return;
}
if(!this.metrics){
this.metrics = Ext.util.TextMetrics.createInstance(this.el);
}
var el = this.el;
var v = el.dom.value;
var d = document.createElement('div');
d.appendChild(document.createTextNode(v));
v = d.innerHTML;
d = null;
v += "*";
var w = Math.min(this._width, Math.max(this.metrics.getWidth(v) + 10, 10));
this.el.setWidth(w);
},

getValues: function(){
var ret = [];
for(var k in this.selectedValues){
if(this.selectedValues[k])
ret.push(this.selectedValues[k]);
}
return ret.join(this.options['separator']);
}
});

Ext.reg('boxselect', Ext.ux.BoxSelect);


Sample


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>BoxSelect</title>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
<link href="../../resources/css/ext-all.css" media="screen" rel="Stylesheet" type="text/css" />
<link href="../../examples/examples.css" media="screen" rel="Stylesheet" type="text/css" />

<script src="../../adapter/ext/ext-base.js" type="text/javascript"></script>
<script src="../../ext-all-debug.js" type="text/javascript"></script>
<script src="../states.js" type="text/javascript"></script>
<script type="text/javascript" src="Ext.ux.plugins.BoxSelect.js"></script>

<link href="boxselect.css" media="screen" rel="Stylesheet" type="text/css" />

<script type="text/javascript">
Ext.onReady(function() {

Ext.QuickTips.init();

var states = new Ext.data.SimpleStore({
fields: ['abbr', 'state', 'nick'],
data: Ext.exampledata.states
});

var select = new Ext.ux.BoxSelect({
id: 'select',
resizable: true,
fieldLabel: 'Send To',
name: 'states',
anchor:'100%',
store: states,
mode: 'local',
displayField: 'state',
displayFieldTpl: '<a href="" title="{nick}">{state} - {abbr}</a>',
valueField: 'abbr',
value: [{abbr: 'AL', state : 'Alabama'}, {abbr: 'NY', state: 'New York'}, {abbr: 'MN', state: 'Minessota'}]
});

var form = new Ext.form.FormPanel({
id: 'form',
baseCls: 'x-plain',
labelWidth: 55,
url: 'index.html',
method: 'get',
defaultType: 'textfield',
items: [
select,
{
fieldLabel: 'Subject',
name: 'subject',
anchor: '100%'
},
{
xtype: 'textarea',
hideLabel: true,
name: 'msg',
anchor: '100% -53'
}
]
});

var window = new Ext.Window({
id: 'window',
title: 'Resize Me',
width: 500,
height:300,
minWidth: 300,
minHeight: 200,
layout: 'fit',
plain:true,
bodyStyle:'padding:5px;',
buttonAlign:'center',
items: form,
maximizable: true,

buttons: [{
text: 'Send',
scope: this
,
handler: function(){
form.getForm().submit();
//alert(select.getValues());
}
},{
text: 'Cancel'
}]
});

window.show();
});
</script>

</head>
<body>

</body>
</html>

mystix
27 Apr 2008, 1:27 AM
great work! =D>

@zilionis came up with something similar some weeks back, but there's been no updates since then:
http://extjs.com/forum/showthread.php?p=151284

some tips i mentioned in that thread + some new tips:

you might want to remove records already in the textarea input area from the ComboBox's Store,
and return them to the Store when they're removed from the textarea input area (this will prevent duplicate items)
there might be use-cases where duplicates are actually desirable, so you might want to make this a config option
you might want to make Ext.ux.BoxSelect inherit from Ext.form.TextArea instead -- this will allow your component to grow with the number of items -- though you'll probably have to rethink how you'd want to implement the dropdown list you get from the ComboBox in this case, since a TextArea has no such featurescratch that. box grows just fine as it is. :)
since Ext.ux.Box is essentially a list item, you might want to change it's namespace to something which conveys this idea
(e.g. Ext.ux.BoxSelect.Item instead)
your js file is named Ext.ux.plugins.BoxSelect.js, but you're only using the Ext.ux.Box/BoxSelect namespace. you might want to rename your js file accordingly to prevent confusion when calling your plugin

zilionis
27 Apr 2008, 10:37 PM
Heh i made too few weeks ago, already posted here http://extjs.com/forum/showthread.php?t=32102&highlight=facebook

aconran
27 Apr 2008, 10:47 PM
Nice work! :D

mystix
27 Apr 2008, 10:53 PM
Heh i made too few weeks ago, already posted here http://extjs.com/forum/showthread.php?t=32102&highlight=facebook

yes i mentioned that 1 post above yours ;)

you're both using different approaches though -- you've used a combo for each input, while tintin directly extended a combo. it'd be interesting to see how both plugins develop. :)

zedisdead
29 Apr 2008, 6:39 AM
I added a way to include new tags that aren't on the data collection by overwriting onKeyUp property like this:



onKeyUp: function(e)
{
if(e.getKey() == e.ENTER)
{
var value = this.el.dom.value;
if(value.length > 0)
{
this.selectedValues[value] = value;
this.addBox(value, value);
this.el.dom.value = '';
}
}
else
{
if (this.editable !== false && !e.isSpecialKey())
{
this.lastKey = e.getKey();
if (e.getKey() == e.BACKSPACE && this.el.dom.value.length == 0)
{
e.stopEvent();
this.collapse();
var el = this.maininput.prev();
if (el)
el.focus();
return;
}
this.dqTask.delay(this.queryDelay);
}

this.autoSize();

Ext.ux.BoxSelect.superclass.onKeyUp.call(this, e);
}
},


Is there a better way to do this?

I also had to add the following to avoid errors on pibos's modifications:



Ext.each(this.value, function(item)
{
if(item)
{
if(this.displayFieldTpl)
{
caption = this.displayFieldTpl.apply(item);
}
else
{
caption = item[this.displayField]
}
this.addBox(item[this.valueField], caption);
}
}, this);


Thanks.

mankz
1 May 2008, 2:25 AM
@Pibos: I get "Invalid argument" when trying to use your code to set the initial value (happens in both IE7 and FF3b5). It breaks in this section (line 2735 of ext-all-debug.js (v2.0.2)):


setWidth : function(width, animate){
width = this.adjustWidth(width);
if(!animate || !A){
this.dom.style.width = this.addUnits(width);
}else{
this.anim({width: {to: width}}, this.preanim(arguments, 1));
}
return this;
},

Error: Width is NaN

I think this is because the value property is already being used by the combobox, by changing the name to initialValue or whatever your code runs fine! :)

another thing missing: a call to this.selectedValues[id] = id; in the addBox function. Otherwise initally loaded items will not be returned by the getValues() function...

IE7/IE6: Box-corners are not rounded

radtad
7 May 2008, 1:54 PM
Is there a way we can make this search through underlying data too perhaps? i.e. something that is not in the displayField. Also, it would be awesome to have this be a soundex search as well.

just some thoughts...

sunny_boy
20 May 2008, 9:27 PM
Tintin - great extension :D thanks

I was just testing in IE6, and noticed that if the length of the items inside the box become larger than the box itself, the items do not wrap correctly - see screenshot.

Works fine in FF.

I've had a look, but have not yet found a fix.

Any ideas?

sunny_boy
20 May 2008, 10:45 PM
Ok - I found a solution to the bug I raised earlier - though not sure if this is the best approach.


ul.holder { margin: 0; overflow: hidden; height: auto !important; height: 1%; padding: 0; }

The overflow property was causing the issue in IE. Removing it fixed IE, but broke the display in FF. Instead of putting a hack in the css, I chose remove the overflow property from the css, and make the following change to the onRender function in Ext.ux.BoxSelect


onRender:function(ct, position) {
Ext.ux.BoxSelect.superclass.onRender.call(this, ct, position);

this.el.removeClass('x-form-text');
this.el.className = 'maininput';
this.el.setWidth(20);

this.holder = this.el.wrap({
'tag': 'ul',
'class':'holder x-form-text'
});
if(!Ext.isIE) {
this.holder.dom.style.overflow='hidden';
}

this.holder.on('click', function(e){
e.stopEvent();
if(this.maininput != this.current) this.focus(this.maininput);
}, this);

this.maininput = this.el.wrap({
'tag': 'li', 'class':'bit-input'
});

Ext.apply(this.maininput, {
'focus': function(){
this.focus();
}.createDelegate(this)
});

},

Any suggestions on a better way to do this?

mystix
20 May 2008, 10:59 PM
@sunny_boy, Ext automatically adds a browser-specific CSS class to the <body> tag.

e.g. ext-gecko, or ext-ie

use some css like this instead


ul.holder { margin: 0; overflow: hidden; height: auto !important; height: 1%; padding: 0; }
.ext-gecko ul.holder { overflow: hidden;}


[edit]
i haven't dived into this plugin yet, so you might want to somehow make those css rules more specific so they only apply to this plugin.

wm003
20 May 2008, 11:05 PM
Awesome! =P~ Thank you for this wonderful extension!

aproust94
22 May 2008, 6:13 AM
Thank you Tintin for this great Extension and also to you Pibos for your improvement.
Pibos, there is something wrong if you don't set 'value' config option. Ext.ux.Boxselect throws an exception. I suggest you to replace the following code at line number (163 ?) :


if(this.value){
if(typeof this.value === 'string')
this.value = [this.value]
}

Ext.each(this.value, function(item){

if(this.displayFieldTpl)
caption = this.displayFieldTpl.apply(item);
else
caption = item[this.displayField]

this.addBox(item[this.valueField], caption);
}, this);

by this one :


if(typeof this.value !== 'undefined'){
if(typeof this.value === 'string')
this.value = [this.value]


Ext.each(this.value, function(item){

if(this.displayFieldTpl)
caption = this.displayFieldTpl.apply(item);
else
caption = item[this.displayField]

this.addBox(item[this.valueField], caption);
}, this);
}


And now, all works fine.

Al

gelleneu
12 Jun 2008, 2:11 AM
I have a question about this great extension.

I use it inside a form, and I want to save the items in a database.

When i submit my form, the items will be appended as post vars in this way:

(field name = "boxselectfield", values are Id's of the items)

boxselectfield=23&boxselectfield=25&boxselectfield=

Reading this post vars in PHP with $_REQUEST['boxselectfield'] results only the last
value, and this value is empty.

Has someone experience with that?

My next question: if an item is not in the list, i want to add it to the list automatically (a database operation is needed for that.)

Is there are solution for that?

Thanks!

mabello
12 Jun 2008, 2:23 AM
Very very nice extension, great job!
A small suggestion: why don't you also insert in your TextField a trigger button (like a combo) that on click show the list of all the possibility so that you can also see the entire list of possibilty and chose from that list? For now the list appears only digiting I think, it seems useful to me!
Anyway, keep up the great work!

Evolic
9 Jul 2008, 4:06 AM
I have a question about this great extension.

I use it inside a form, and I want to save the items in a database.

When i submit my form, the items will be appended as post vars in this way:

(field name = "boxselectfield", values are Id's of the items)

boxselectfield=23&boxselectfield=25&boxselectfield=

Reading this post vars in PHP with $_REQUEST['boxselectfield'] results only the last
value, and this value is empty.

Has someone experience with that?

My next question: if an item is not in the list, i want to add it to the list automatically (a database operation is needed for that.)

Is there are solution for that?

Thanks!

I found something working:

addBox: function(id, caption){
var box = new Ext.ux.Box({
id: 'Box_' + id,
maininput: this.maininput,
renderTo: this.holder,
className: this.options['className'],
caption: caption,
'value': id,
listeners: {
'remove': function(box){
this.selectedValues[box.value] = null;
},
scope: this
}
});
box.render();

if (this.hiddenName) {
var name = this.hiddenName;
} else {
var name = this.name + '[' + id + ']';
}

box.hidden = this.el.insertSibling({
'tag':'input',
'type':'hidden',
//'value': id,
'name': name
},'before', true);

PHP $_POST:

Array
(
[states] => Array
(
[AL] =>
[NY] =>
[MN] =>
[New obj 1] =>
[New obj 2] =>
)
[subject] =>
[msg] =>
)

Evolic
9 Jul 2008, 6:18 AM
I added support for adding unique values
(probably not work to support repeated values)


Ext.ux.Box = Ext.extend(Ext.Component, {
initComponent : function(){
Ext.ux.Box.superclass.initComponent.call(this);
},

onRender: function(ct, position){
Ext.ux.Box.superclass.onRender.call(this, ct, this.maininput);

this.addEvents('remove');

this.addClass('bit-box');

this.el = ct.createChild({ tag: "li" }, this.maininput);
this.el.addClassOnOver('bit-hover');

Ext.apply(this.el, {

'focus': function(){
this.down('a.closebutton').focus();
},

'dispose': function(){
this.dispose()
}.createDelegate(this)

});

this.el.on('click', function(e){
this.focus()
}, this, {stopEvent:true});

this.el.update(this.caption);

this.lnk = this.el.createChild({
'tag': 'a',
'class': 'closebutton',
'href':'#'
});

this.lnk.on({
'click': function(e){
e.stopEvent();
this.fireEvent('remove', this);
this.dispose();
},
'focus': function(){
this.el.addClass("bit-box-focus");
},
'blur': function(){
this.el.removeClass("bit-box-focus");
},
scope: this
});

new Ext.KeyMap(this.lnk, [
{
key: [Ext.EventObject.BACKSPACE, Ext.EventObject.DELETE],
fn: function(){
this.dispose();
}.createDelegate(this)
},
{
key: Ext.EventObject.RIGHT,
fn: function(){
this.move('right');
}.createDelegate(this)
},
{
key: Ext.EventObject.LEFT,
fn: function(){
this.move('left');
}.createDelegate(this)
},
{
key: Ext.EventObject.TAB,
fn: function(){
}.createDelegate(this)
}
]).stopEvent = true;

},

move: function(direction) {
if(direction == 'left')
el = this.el.prev();
else
el = this.el.next();
if(el)
el.focus();
},

dispose: function() {
//if(el.prev() && this.retrieveData(el.prev(), 'small') ) el.prev().remove();
//if(this.current == el) this.focus(el.next());
//if(el.data['type'] == 'box') el.onBoxDispose(this);

Ext.fly(this.hidden).remove();
this.el.hide({
duration: .1,
callback: function(){
this.move('right');
this.destroy()
}.createDelegate(this)
});


return this;
}
});

Ext.ux.BoxSelect = Ext.extend(Ext.form.ComboBox, {

/**
* @cfg {Boolean} selectOnFocus True to select any existing text in the field immediately on focus. Only applies
* when editable = true (defaults to false)
*/
addUniqueValues: true,

initComponent:function() {
Ext.apply(this, {
selectedValues: {},
//boxElements: {},
current: false,
options: {
className: 'bit',
separator: ','
},
hideTrigger: true,
grow: false
});

Ext.ux.BoxSelect.superclass.initComponent.call(this);
},

// private
onEnable: function(){
Ext.ux.BoxSelect.superclass.onEnable.apply(this, arguments);

},

// private
onDisable: function(){
Ext.ux.BoxSelect.superclass.onDisable.apply(this, arguments);

},

onRender:function(ct, position) {
Ext.ux.BoxSelect.superclass.onRender.call(this, ct, position);

this.el.removeClass('x-form-text');
this.el.className = 'maininput';
this.el.dom.name = '';
this.el.setWidth(20);
this.el.dom.value = ''


this.holder = this.el.wrap({
'tag': 'ul',
'class':'holder x-form-text'
});

if(!Ext.isIE) {
this.holder.dom.style.overflow='hidden';
}

this.holder.on('click', function(e){
e.stopEvent();
if(this.maininput != this.current) this.focus(this.maininput);
}, this);

this.maininput = this.el.wrap({
'tag': 'li', 'class':'bit-input'
});

Ext.apply(this.maininput, {
'focus': function(){
this.focus();
}.createDelegate(this)
});

if(typeof this.displayFieldTpl === 'string')
this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl)

if(typeof this.value !== 'undefined'){
if(typeof this.value === 'string')
this.value = [this.value];

Ext.each(this.value, function(item){
if (this.displayFieldTpl) {
caption = this.displayFieldTpl.apply(item);
} else {
caption = item[this.displayField];
}

if (!this.checkValue(caption)) {
this.addBox(item[this.valueField], caption);
}
}, this);
}
},

onResize : function( w, h, rw, rh ){
this._width = w;
Ext.ux.BoxSelect.superclass.onResize.call(this, w, h, rw, rh);
this.autoSize();
},

onKeyUp: function(e)
{
if(e.getKey() == e.ENTER)
{
var value = this.el.dom.value;
if(value.length > 0)
{
if (!this.checkValue(value)) {
this.addBox(value, value);
} else {
Ext.Msg.alert('Message', 'Item is already on the list');
}

this.el.dom.value = '';

}
} else {
if (this.editable !== false && !e.isSpecialKey())
{
this.lastKey = e.getKey();
if (e.getKey() == e.BACKSPACE && this.el.dom.value.length == 0)
{
e.stopEvent();
this.collapse();
var el = this.maininput.prev();
if (el)
el.focus();
return;
}
this.dqTask.delay(this.queryDelay);
}

this.autoSize();

Ext.ux.BoxSelect.superclass.onKeyUp.call(this, e);
}
},

onSelect: function(record, index) {
var val = record.data[this.valueField];

//this.selectedValues[val] = val;

if(typeof this.displayFieldTpl === 'string')
this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl)


var caption;
if(this.displayFieldTpl)
caption = this.displayFieldTpl.apply(record.data)
else if(this.displayField)
caption = record.data[this.displayField];

if (!this.checkValue(caption)) {
this.addBox(record.data[this.valueField], caption);
} else {
Ext.Msg.alert('Message', 'Item is already on the list');
}

this.collapse();
this.setRawValue('');
this.lastSelectionText = '';
this.applyEmptyText();

this.autoSize();
},

checkValue: function (caption) {
if (console) console.log(this.selectedValues);
if (this.addUniqueValues) {
return typeof this.selectedValues[caption] != 'undefined';
} else {
return true;
}
},

addBox: function(id, caption){
var box = new Ext.ux.Box({
id: 'Box_' + id,
maininput: this.maininput,
renderTo: this.holder,
className: this.options['className'],
caption: caption,
'value': id,
listeners: {
'remove': function(box){
this.selectedValues[box.caption] = null;
},
scope: this
}
});
box.render();

if (this.hiddenName) {
var name = this.hiddenName;
} else {
var name = this.name + '[' + id + ']';
}

box.hidden = this.el.insertSibling({
'tag':'input',
'type':'hidden',
//'value': id,
'name': name
},'before', true);

this.selectedValues[caption] = id;
},
autoSize : function(){
if(!this.rendered){
return;
}
if(!this.metrics){
this.metrics = Ext.util.TextMetrics.createInstance(this.el);
}
var el = this.el;
var v = el.dom.value;
var d = document.createElement('div');
d.appendChild(document.createTextNode(v));
v = d.innerHTML;
d = null;
v += "*";
var w = Math.min(this._width, Math.max(this.metrics.getWidth(v) + 10, 10));
this.el.setWidth(w);
},

getValues: function(){
var ret = [];
for(var k in this.selectedValues){
if(this.selectedValues[k])
ret.push(this.selectedValues[k]);
}
return ret.join(this.options['separator']);
}
});

Ext.reg('boxselect', Ext.ux.BoxSelect);

Evolic
11 Jul 2008, 12:27 AM
HTML:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>BoxSelect</title>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
<link href="../../resources/css/ext-all.css" media="screen" rel="Stylesheet" type="text/css" />
<link href="../../examples/examples.css" media="screen" rel="Stylesheet" type="text/css" />

<script type="text/javascript" src="../../source/core/Ext.js"></script>
<script type="text/javascript" src="../../source/adapter/ext-base.js"></script>
<!-- <script src="../../adapter/ext/ext-base.js" type="text/javascript"></script> -->
<script src="../../ext-all-debug.js" type="text/javascript"></script>
<script src="../form/states.js" type="text/javascript"></script>
<script type="text/javascript" src="Ext.ux.BoxSelect.v3.js"></script>

<link href="boxselect.css" media="screen" rel="Stylesheet" type="text/css" />

<script type="text/javascript">
Ext.onReady(function() {

Ext.QuickTips.init();

var states = new Ext.data.SimpleStore({
fields: ['abbr', 'state', 'nick'],
data: Ext.exampledata.states
});

var select = new Ext.ux.BoxSelect({
id: 'select',
resizable: true,
fieldLabel: 'Send To',
name: 'states',
anchor:'100%',
store: states,
mode: 'local',
displayField: 'state',
//displayFieldTpl: '<a href="" title="{nick}">{state} - {abbr}</a>',
//displayFieldTpl: '{state} - {abbr}',
valueField: 'abbr',
value: [{abbr: 'AL', state : 'Alabama'}, {abbr: 'NY', state: 'New York'}, {abbr: 'MN', state: 'Minnesota'}]
});

var select2 = new Ext.ux.BoxSelect({
id: 'select2',
resizable: true,
fieldLabel: 'Send To',
name: 'states2',
anchor:'100%',
store: states,
mode: 'local',
displayField: 'state',
//displayFieldTpl: '<a href="" title="{nick}">{state} - {abbr}</a>',
displayFieldTpl: '{state} - {abbr}',
valueField: 'abbr',
disabled: true,
value: [{abbr: 'AL', state : 'Alabama'}, {abbr: 'NY', state: 'New York'}, {abbr: 'MN', state: 'Minessota'}],
addUniqueValues: true
});

var form = new Ext.form.FormPanel({
id: 'form',
baseCls: 'x-plain',
labelWidth: 55,
url: 'submit.php',
//method: 'get',
defaultType: 'textfield',
items: [
select,
select2,
{
fieldLabel: 'Subject',
name: 'subject',
anchor: '100%'
},
{
xtype: 'textarea',
hideLabel: true,
name: 'msg',
anchor: '100% -53'
}
]
});

var window = new Ext.Window({
id: 'window',
title: 'Resize Me',
width: 500,
height:300,
minWidth: 300,
minHeight: 200,
layout: 'fit',
plain:true,
bodyStyle:'padding:5px;',
buttonAlign:'center',
items: form,
maximizable: true,

buttons: [{
text: 'Send',
scope: this
,
handler: function(){
form.getForm().submit();
//alert(select.getValues());
}
},{
text: 'Cancel'
}]
});

window.show();
});
</script>

</head>
<body>

</body>
</html>

JS:

Ext.ux.Box = Ext.extend(Ext.Component, {
initComponent : function(){
Ext.ux.Box.superclass.initComponent.call(this);
},

onElClick : function(e){
this.focus();
},

onLnkClick : function(e){
e.stopEvent();
this.fireEvent('remove', this);
this.dispose();
},
onLnkFocus : function(){
this.el.addClass("bit-box-focus");
},
onLnkBlur : function(){
this.el.removeClass("bit-box-focus");
},

enableElListeners : function() {
this.el.on('click', this.onElClick, this, {stopEvent:true});
},

enableLnkListeners : function() {
this.lnk.on({
'click': this.onLnkClick,
'focus': this.onLnkFocus,
'blur': this.onLnkBlur,
scope: this
});
},

enableAllListeners : function() {
this.enableElListeners();
this.enableLnkListeners();
},

disableAllListeners : function() {
this.el.un('click', this.onElClick, this);

this.lnk.un('click', this.onLnkClick, this);
this.lnk.un('focus', this.onLnkFocus, this);
this.lnk.un('blur', this.onLnkBlur, this);
},

onRender: function(ct, position){
Ext.ux.Box.superclass.onRender.call(this, ct, this.maininput);

this.addEvents('remove');

this.addClass('bit-box');

this.el = ct.createChild({ tag: "li" }, this.maininput);
this.el.addClassOnOver('bit-hover');

Ext.apply(this.el, {
'focus': function(){
this.down('a.closebutton').focus();
},
'dispose': function(){
this.dispose();
}.createDelegate(this)

});

this.enableElListeners();

this.el.update(this.caption);

this.lnk = this.el.createChild({
'tag': 'a',
'class': 'closebutton',
'href':'#'
});

this.enableLnkListeners();

this.on({
'disable': this.disableAllListeners,
'enable': this.enableAllListeners,
scope: this
});

new Ext.KeyMap(this.lnk,
[{
key: [Ext.EventObject.BACKSPACE, Ext.EventObject.DELETE],
fn: function(){
this.dispose();
}.createDelegate(this)
}, {
key: Ext.EventObject.RIGHT,
fn: function(){
this.move('right');
}.createDelegate(this)
}, {
key: Ext.EventObject.LEFT,
fn: function(){
this.move('left');
}.createDelegate(this)
}
]).stopEvent = true;

},

move: function(direction) {
if(direction == 'left')
el = this.el.prev();
else
el = this.el.next();
if(el)
el.focus();
},

dispose: function() {
//if(el.prev() && this.retrieveData(el.prev(), 'small') ) el.prev().remove();
//if(this.current == el) this.focus(el.next());
//if(el.data['type'] == 'box') el.onBoxDispose(this);

Ext.fly(this.hidden).remove();
this.el.hide({
duration: .1,
callback: function(){;
this.move('right');
this.destroy();
}.createDelegate(this)
});


return this;
}
});

Ext.ux.BoxSelect = Ext.extend(Ext.form.ComboBox, {

/**
* @cfg {Boolean} addUniqueValues True to add only unique values.
*/
addUniqueValues: true,

initComponent:function() {
Ext.apply(this, {
selectedValues: {},
boxElements: {},
current: false,
options: {
className: 'bit',
separator: ','
},
hideTrigger: true,
grow: false
});

Ext.ux.BoxSelect.superclass.initComponent.call(this);
},

// private
onEnable: function(){
Ext.ux.BoxSelect.superclass.onEnable.apply(this, arguments);

for(var k in this.boxElements){
this.boxElements[k].enable();
}
},

// private
onDisable: function(){
Ext.ux.BoxSelect.superclass.onDisable.apply(this, arguments);

for(var k in this.boxElements){
this.boxElements[k].disable();
}
},

onRender:function(ct, position) {
Ext.ux.BoxSelect.superclass.onRender.call(this, ct, position);

this.el.removeClass('x-form-text');
this.el.className = 'maininput';
this.el.dom.name = '';
this.el.setWidth(20);
this.el.dom.value = ''


this.holder = this.el.wrap({
'tag': 'ul',
'class':'holder x-form-text'
});

if(!Ext.isIE) {
this.holder.dom.style.overflow='hidden';
}

this.holder.on('click', function(e){
e.stopEvent();
if(this.maininput != this.current) this.focus(this.maininput);
}, this);

this.maininput = this.el.wrap({
'tag': 'li', 'class':'bit-input'
});

Ext.apply(this.maininput, {
'focus': function(){
this.focus();
}.createDelegate(this)
});

if(typeof this.displayFieldTpl === 'string')
this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl)

if(typeof this.value !== 'undefined'){
if(typeof this.value === 'string')
this.value = [this.value];

Ext.each(this.value, function(item){
if (this.displayFieldTpl) {
caption = this.displayFieldTpl.apply(item);
} else {
caption = item[this.displayField];
}

if (!this.checkValue(caption)) {
this.addBox(item[this.valueField], caption);
}
}, this);
}
},

onResize : function( w, h, rw, rh ){
this._width = w;
Ext.ux.BoxSelect.superclass.onResize.call(this, w, h, rw, rh);
this.autoSize();
},

onKeyUp: function(e)
{
if(e.getKey() == e.ENTER)
{
var value = this.el.dom.value;
if(value.length > 0)
{
if (!this.checkValue(value)) {
this.addBox(value, value);
} else {
Ext.Msg.alert('Message', 'Item is already on the list');
}

this.el.dom.value = '';

}
} else {
if (this.editable !== false && !e.isSpecialKey())
{
this.lastKey = e.getKey();
if (e.getKey() == e.BACKSPACE && this.el.dom.value.length == 0)
{
e.stopEvent();
this.collapse();
var el = this.maininput.prev();
if (el)
el.focus();
return;
}
this.dqTask.delay(this.queryDelay);
}

this.autoSize();

Ext.ux.BoxSelect.superclass.onKeyUp.call(this, e);
}
},

onSelect: function(record, index) {
var val = record.data[this.valueField];

//this.selectedValues[val] = val;

if(typeof this.displayFieldTpl === 'string')
this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl)


var caption;
if(this.displayFieldTpl)
caption = this.displayFieldTpl.apply(record.data)
else if(this.displayField)
caption = record.data[this.displayField];

if (!this.checkValue(caption)) {
this.addBox(record.data[this.valueField], caption);
} else {
Ext.Msg.alert('Message', 'Item is already on the list');
}

this.collapse();
this.setRawValue('');
this.lastSelectionText = '';
this.applyEmptyText();

this.autoSize();
},

checkValue: function (caption) {
if (this.addUniqueValues) {
return typeof this.selectedValues[caption] != 'undefined';
} else {
return true;
}
},

addBox: function(id, caption){
var box = new Ext.ux.Box({
id: 'Box_' + id,
maininput: this.maininput,
renderTo: this.holder,
className: this.options['className'],
caption: caption,
'value': id,
listeners: {
'remove': function(box){
delete this.selectedValues[box.caption];
delete this.boxElements[box.caption];
},
scope: this
}
});
box.render();

if (this.hiddenName) {
var name = this.hiddenName;
} else {
var name = this.name + '[' + id + ']';
}

box.hidden = this.el.insertSibling({
'tag':'input',
'type':'hidden',
//'value': id,
'name': name
},'before', true);

this.selectedValues[caption] = id;
this.boxElements[caption] = box;
},
autoSize : function(){
if(!this.rendered){
return;
}
if(!this.metrics){
this.metrics = Ext.util.TextMetrics.createInstance(this.el);
}
var el = this.el;
var v = el.dom.value;
var d = document.createElement('div');
d.appendChild(document.createTextNode(v));
v = d.innerHTML;
d = null;
v += "*";
var w = Math.min(this._width, Math.max(this.metrics.getWidth(v) + 10, 10));
this.el.setWidth(w);
},

getValues: function(){
var ret = [];
for(var k in this.selectedValues){
if(this.selectedValues[k])
ret.push(this.selectedValues[k]);
}
return ret.join(this.options['separator']);
}
});

Ext.reg('boxselect', Ext.ux.BoxSelect);

Evolic
11 Jul 2008, 8:39 AM
Today I noticed that there is problem when I want to load Form
from e.g. XML or JSON file.

There is some problem here I cannot solve.
Finally I managed to display all records, but there is also "[Object, Object]" on the ComboBox
(in case 2 values).

Any help?

razor
12 Jul 2008, 2:06 AM
Great work so far!

I would like to bring forward a change, would it be possible to press the downkey (or add an icon at the end of the textfield/combo) to see all available options?

Reason: what if the user forgot which items are in the combo box, this should provide a extra needed functionality.

Please let me know if anybody will do this, else I will change the code. ;)

franklt69
31 Jul 2008, 6:56 AM
Hi I need to get the event click or remove or delete key and never it is fire, some is wrong?


this.selectAttachment = new Ext.ux.BoxSelect({
hideLabel: true,
name: 'attachment',
anchor:'100%',
mode: 'local',
typeAhead: false,
hideTrigger:true,
listeners: {
'click': function(p1,p2,p3){
debugger;
var p = p1;
},
'remove': function(p1,p2,p3){
debugger;
var p = p1;
}
}
});


regards
Frank

mark_l_lewis
1 Aug 2008, 7:20 AM
Hi,

How can you reload saved data into the textfield area so that the displayField is displayed.

Ie the valuelist is 23, 29, 30. I want to save this and then redisplay at a later date?

Hope this makes sense.

Cheers,
Mark

mark_l_lewis
2 Aug 2008, 9:22 AM
hi,

i couldn't find a way so i wrote a setValues function;



setValues: function(store, selectedValues){

for(var i = 0; i < selectedValues.length; i++){
this.onSelect (store.getById(selectedValues[i]), 0);
}


}

i had to add the store as a parameter to the function as the this.store is left in the filtered state from the previous selection.

cheers,
mark

Andrewd2
10 Aug 2008, 4:06 AM
Today I noticed that there is problem when I want to load Form
from e.g. XML or JSON file.

There is some problem here I cannot solve.
Finally I managed to display all records, but there is also "[Object, Object]" on the ComboBox
(in case 2 values).

Any help?

override function InitValue() :


Ext.ux.BoxSelect = Ext.extend(Ext.form.ComboBox, {

...

initValue : function(){
},
...
});

ceej
11 Aug 2008, 11:42 AM
Andrewd2: I've just tried loading the data with no luck either, overriding 'InitValue()' enables you to load the data into the field but kills the hole point of using this add-on as it looses the facebook feature.

Is there any plans on making it so you can load the data into the field as well as entering it???

Many thanks!

rtconner
5 Sep 2008, 9:34 AM
Well since the thread creator is not keeping this code up to date, thought I post my version. This version uses the displayTpl, valueField, and displayField abilities. I fixed a prepopulate bug. And cleaned up a few things.

Usage might look like this:


new Ext.ux.BoxSelect({ store: store, mode: 'local',
displayFieldTpl: '<span>{first_name} {last_name}</span>',
valueField: 'username',
value: [{username: 'foobar', first_name: 'Foo', last_name: 'Bar'}]
});

The Code I'm using (though I'm sure it could use some further cleanup)


/**
* Ext.ux.Box
* Ext.ux.BoxSelect
*
* http://extjs.com/forum/showthread.php?t=33794
*/
Ext.namespace('Ext.ux');

Ext.ux.Box = Ext.extend(Ext.Component, {
initComponent : function(){
Ext.ux.Box.superclass.initComponent.call(this);
},

onRender: function(ct, position){
Ext.ux.Box.superclass.onRender.call(this, ct, this.maininput);

this.addEvents('remove');
this.addClass('bit-box');

this.el = ct.createChild({ tag: "li" }, this.maininput);
this.el.addClassOnOver('bit-hover');

Ext.apply(this.el, {
'focus': function(){ this.down('a.closebutton').focus(); },
'dispose': function(){ this.dispose() }.createDelegate(this)
});

this.el.on('click', function(e){
this.focus()
}, this, {stopEvent:true});

this.el.update(this.caption);

this.lnk = this.el.createChild({
'tag': 'a',
'class': 'closebutton',
'href':'#'
});

this.lnk.on({
'click': function(e){
e.stopEvent();
this.fireEvent('remove', this);
this.dispose();
},
'focus': function(){
this.el.addClass("bit-box-focus");
},
'blur': function(){
this.el.removeClass("bit-box-focus");
},
scope: this
});

new Ext.KeyMap(this.lnk, [
{
key: [Ext.EventObject.BACKSPACE, Ext.EventObject.DELETE],
fn: function(){
this.dispose();
}.createDelegate(this)
},
{
key: Ext.EventObject.RIGHT,
fn: function(){
this.move('right');
}.createDelegate(this)
},
{
key: Ext.EventObject.LEFT,
fn: function(){
this.move('left');
}.createDelegate(this)
},
{
key: Ext.EventObject.TAB,
fn: function(){
}.createDelegate(this)
}
]).stopEvent = true;

},

move: function(direction) {
if(direction == 'left')
el = this.el.prev();
else
el = this.el.next();
if(el)
el.focus();
},

dispose: function() {
Ext.fly(this.hidden).remove();
this.el.hide({
duration: .1,
callback: function(){
this.move('right');
this.destroy()
}.createDelegate(this)
});
return this;
}
});

Ext.ux.BoxSelect = Ext.extend(Ext.form.ComboBox, {
/**
* @cfg {Array} value default value(s). Should be an array of objects
*/
/**
* @cfg {String} valueField value attribute of the value objects
*/
/**
* @cfg {String} displayField display attribute of the value objects
*/
/**
* @cfg {String} displayFieldTpl
*/

initComponent:function() {
Ext.apply(this, {
selectedValues: {},
boxElements: {},
current: false,
options: {
className: 'bit',
separator: ','
},
hideTrigger: true,
grow: false
});

Ext.ux.BoxSelect.superclass.initComponent.call(this);
},

onRender:function(ct, position) {
Ext.ux.BoxSelect.superclass.onRender.call(this, ct, position);

this.el.removeClass('x-form-text');
this.el.className = 'maininput';
this.el.dom.name = '';
this.el.setWidth(20);
this.el.dom.value = ''

this.holder = this.el.wrap({
'tag': 'ul',
'class':'holder x-form-text'
});

this.holder.on('click', function(e){
e.stopEvent();
if(this.maininput != this.current) this.focus(this.maininput);
}, this);

this.maininput = this.el.wrap({
'tag': 'li', 'class':'bit-input'
});

Ext.apply(this.maininput, {
'focus': function(){
this.focus();
}.createDelegate(this)
});
},

// private
initValue : function() {
if(typeof this.displayFieldTpl === 'string') {
this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl)
}

if(typeof this.value !== 'undefined') {
Ext.each(this.value, function(item){
if(this.displayFieldTpl) {
caption = this.displayFieldTpl.apply(item);
} else {
caption = item[this.displayField]
}
this.addBox(item[this.valueField], caption);
}, this);
}
},

onResize : function( w, h, rw, rh ){
this._width = w;
Ext.ux.BoxSelect.superclass.onResize.call(this, w, h, rw, rh);
this.autoSize();
},

onKeyUp : function(e) {
if(this.editable !== false && !e.isSpecialKey()){
this.lastKey = e.getKey();
if(e.getKey() == e.BACKSPACE && this.el.dom.value.length == 0){
e.stopEvent();
this.collapse();
var el = this.maininput.prev();
if(el) el.focus();
return;
}
this.dqTask.delay(this.queryDelay);
}

this.autoSize();
Ext.ux.BoxSelect.superclass.onKeyUp.call(this, e);
},

onSelect: function(record, index) {
var val = record.data[this.valueField];

this.selectedValues[val] = val;

if(typeof this.displayFieldTpl === 'string') {
this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl)
}

if(!this.boxElements[val]) {
var caption;
if(this.displayFieldTpl)
caption = this.displayFieldTpl.apply(record.data)
else if(this.displayField)
caption = record.data[this.displayField];

this.addBox(record.data[this.valueField], caption)

}
this.collapse();
this.setRawValue('');
this.lastSelectionText = '';
this.applyEmptyText();

this.autoSize();
},

addBox: function(id, caption){
var box = new Ext.ux.Box({
id: 'Box_' + id,
maininput: this.maininput,
renderTo: this.holder,
className: this.options['className'],
caption: caption,
'value': id,
listeners: {
'remove': function(box){
this.selectedValues[box.value] = null;
},
scope: this
}
});
box.render();

box.hidden = this.el.insertSibling({
'tag':'input',
'type':'hidden',
'value': id,
'name': (this.hiddenName || this.name)
},'before', true);
},

autoSize : function(){
if(!this.rendered){ return; }
if(!this.metrics){
this.metrics = Ext.util.TextMetrics.createInstance(this.el);
}

var el = this.el;
var v = el.dom.value;
var d = document.createElement('div');
d.appendChild(document.createTextNode(v));
v = d.innerHTML;
d = null;
v += "*";
var w = Math.min(this._width, Math.max(this.metrics.getWidth(v) + 10, 10));
this.el.setWidth(w);
},

getValues: function(){
var ret = [];
for(var k in this.selectedValues){
if(this.selectedValues[k]) {
ret.push(this.selectedValues[k]);
}
}
return ret.join(this.options['separator']);
}
});

Ext.reg('boxselect', Ext.ux.BoxSelect);

mystix
5 Sep 2008, 9:41 AM
Well since the thread creator is not keeping this code up to date, thought I post my version. This version uses the displayTpl, valueField, and displayField abilities. I fixed a prepopulate bug. And cleaned up a few things.

Usage might look like this:


new Ext.ux.BoxSelect({ store: store, mode: 'local',
displayFieldTpl: '<span>{first_name} {last_name}</span>',
valueField: 'username',
value: [{username: 'foobar', first_name: 'Foo', last_name: 'Bar'}]
});



does your code work as an editor in a gridpanel?

skitzo
5 Sep 2008, 11:48 AM
rtconner,

I've been playing with this control since yesterday, and I'm having some trouble with IE6. In IE6 when you add enough items so that a second or third line is needed, the box doesn't grow correctly. It works fine in FF3. Do you see the same problem in IE6?

Thanks

scottpederick
9 Sep 2008, 11:20 PM
If anyone is having an issue with this component in IE7 whereby the trigger's textfield moves in mysterious ways, I've roughly traced it back to the trigger using setDisplayed(false) rather than setVisible(false) - I'm assuming there must be some relative measurement to the trigger that makes it fly off.

You'll see in the attached screenshots it's ok on view, but as soon as it gets focus it moves down the form (position is ~9997px). Combo still works and the dialog rights itself as soon as it loses focus.

To replicate I just used the sample and viewed it in IE.

The hack I used to fix it was to show the trigger in IE then manually set it's visibility: false.



initComponent:function() {
Ext.apply(this, {
...
hideTrigger: (Ext.isIE ? false : true),
...
},

onRender:function(ct, position) {
Ext.ux.BoxSelect.superclass.onRender.call(this, ct, position);

if (Ext.isIE)
this.trigger.setVisible(false);
...


No doubt there's a far cleaner way to do it (css only, even) - but time is against me.

tintin
15 Sep 2008, 1:24 PM
Hi all,

Sorry for having been silent for so long.

I've released a new version today (see first post (http://extjs.com/forum/showthread.php?p=159688#post159688)) with lots of corrections and enhancements. See demo here (http://efattal.fr/extjs/examples/boxselect/).

Thanks for all your advices.

galdaka
15 Sep 2008, 11:35 PM
Hi,

Window gray problem in IE7.

Thanks in advance,

tintin
16 Sep 2008, 12:07 AM
Window gray problem in IE7.

:"> Ouch, I forgot that one. It's OK now.

franklt69
16 Sep 2008, 7:58 AM
Using new version:


I don't using Ext.ux.BoxSelect with store, because I addItem for code, and I get error here:



this.store.on('datachanged', function(store){

this.store.each(function(rec){
if(this.checkValue(rec.data[this.valueField])){
this.removedRecords[rec.data[this.valueField]] = rec;
this.store.remove(rec);
}
}, this);
}, this);


so I modified the code:


if (this.store){
this.store.on('datachanged', function(store){

this.store.each(function(rec){
if(this.checkValue(rec.data[this.valueField])){
this.removedRecords[rec.data[this.valueField]] = rec;
this.store.remove(rec);
}
}, this);
}, this);
}


a doubt which event I can use to detect a remove item?, I mean I would like when the user close a box do something, how I can get this event?

regards
Frank

galdaka
16 Sep 2008, 8:47 AM
Hi,

Sorry for my English,

Excellent work. One question: Is posible combine free (non-defined) values with pre-defined (In combobox) values?

I think would be a good idea define a separator as parameter and when you write non-defined value enter separator and make posible expand combobox again for enter new value. This new value will have a diferent css or color (For mark as non-predefined).

Thanks in advance,

Psychokrameur
27 Sep 2008, 6:36 AM
Hi,

You're extension was really great. I'm happy that you still work on it.

However, I notice some problem. Try to add an element, remove it, add again.
The available selection grows... but with duplicate items.

The link to the demo doesn't work.

Finally, I agree with galdaka suggestion and could be good to have a reset function which remove boxes (caption and on the store) and free-element.

Even if you don't want to add this functionnality, could explain me how to get and to remove the RawValue properly.

provagino
29 Sep 2008, 8:05 AM
Replace:

onLnkClick : function(e){
e.stopEvent();
this.fireEvent('remove', this);
this.dispose();
},


Whit:


onLnkClick : function(e){
e.stopEvent();
this.dispose();
},

galdaka
29 Sep 2008, 9:03 AM
Heh i made too few weeks ago, already posted here http://extjs.com/forum/showthread.php?t=32102&highlight=facebook

Not work in IE. ;)

Thanks in advance,

P.D: Is posible enter "free values"?

irina
7 Oct 2008, 12:45 PM
Hi,
I would like to resolve an item when I type it and then move cursor to the different part of the product (lost focus). I tried to use "onBlur" method, which works fine almost in all cases. But it also executed when a list is shown and I select any item. So when I select an item, "onBlur" is first, then "onSelect". That causes two items are shown: the partially typed and the selected one. The question how can I distinguish this sort of losing focus from others.
Thanks a lot,
Irina

Eric24
9 Oct 2008, 1:36 PM
Very nice work indeed!

I think I may have found a bug in the latest release: After using addItem(), getValue() does not return it (only items added through the UI or setValues), but calling getValues() on the parent form returns everything, including the addItem() items. I'm not sure why...

(update)
Hmmm. It gets worse. After deleting items through the UI, they no longer appear in getValue() as expected, but they remain in getValues(). Still puzzled...

One other observation: The JS object returned for the BoxSelect field always contains an extra blank value.

Psychokrameur
14 Oct 2008, 10:23 AM
Thanks provagino (http://extjs.com/forum/member.php?u=6765) but it doesn't seems to work.

Anybody have an idea to combine "free-value" and pre-defined values (see galdaka post, previous page) ?

suaveant
16 Oct 2008, 7:21 AM
setValue uses store's find function, which (strangely) has no way to match an exact term except if you pass it a RegExp...
this.store.find(this.valueField, item.trim())

I had numeric values 59 and 593, and when the data was set, 59 was matching 593 because of this.

Here is a simple fix

this.store.find(this.valueField, new RegExp('^'+item.trim()+'$'));

patrosmania
16 Oct 2008, 7:44 AM
Hi
A very useful and great extension for Extjs.
I have a question related to the select event.
I want to create two instances of this class, but i want them to behave different when an item is selected and not erase the code in the Ext.ux.BoxSelect class. In other words, i want to extend the response for the select event.
How can i do this?
Thanks in advance

crxtech
16 Oct 2008, 11:46 AM
If you set allowBlank:false, as soon as you remove an item it sets the field to invalid, even if there are still others items.



var emailTo = new Ext.ux.BoxSelect({
fieldLabel: 'To',
labelStyle: 'font-weight:bold; width:30;',
resizable: true,
name: 'to[]',
anchor:'95%',
store: emailDS,
mode: 'local',
displayField: 'userName',
displayFieldTpl: '{userName} ({email})',
valueField: 'email',
addUniqueValues: false,
allowBlank:false
});

After more testing it seems that if you add allowBlank:false, it will always be invalid on blur, regardless of items selected.

crxtech
16 Oct 2008, 12:01 PM
It seems that once you add an item, it gets submitted, even if you remove it. I added 3 items, then removed 2 of them. getValue(), shows only one item now, but all three still get submitted. This happens with my code in the post above and with the included example if you add a submit button.

Any ideas?

crxtech
17 Oct 2008, 10:26 AM
I fixed the problem with removed items still being submitted by adding this line to the dispose function: Ext.fly(this.hidden).remove();


dispose: function(withoutEffect) {
this.fireEvent('remove', this);
Ext.fly(this.hidden).remove();

if(withoutEffect){
this.destroy();
}
else{
this.el.hide({
duration: .5,
callback: function(){
this.move('right');
this.destroy()
}.createDelegate(this)
});
}

return this;
}

masterchris_99
22 Oct 2008, 5:46 AM
Hello,

is it possible to find not only words with the beginning like
Input: 34
Found: 34567

but
Input: 34
Found: 1234567

?

masterchris_99
28 Oct 2008, 6:38 AM
no idea?

zilionis
28 Oct 2008, 6:43 AM
If you use remote solution it's on the client side...

masterchris_99
28 Oct 2008, 6:47 AM
no I use it that way


mode: 'local',

with simpleStore

Eric24
28 Oct 2008, 6:54 AM
A suggestion...
If it is config'ed to allow items to be typed in that don't exist in the store, as soon as a new item is "recognized", it should be encapsulated in a blue box just like items from the store (perhaps with some slight difference in the style to give a visual indication that it didn't come from the store).

galdaka
3 Nov 2008, 11:45 PM
New hotmail implementation of this feature.

As I say few days ago ;) in hotmail you can select "pre-defined" values or "free-values".

View image....

rdp
10 Nov 2008, 9:37 AM
I've found a bug.

When you first load the demo page (http://efattal.fr/extjs/examples/boxselect/) and press 'backspace' there is an error:



this.lastValue is undefined (line 79)


When you add one value and you press 'backspace' it works.

I don't know how to resolve this problem. But I hope this post can help you to improve this extension. :)

efiebba
11 Nov 2008, 1:55 AM
Hello

i hava problems using this extension. I use extjs 2.1 and if i use boxselect i get this error



ct is null
this.el = ct.createChild({ tag: 'li' }, this.maininput);
Ext.ux.B...Select.js (Linie 347)


with this pice of code



var select = new Ext.ux.BoxSelect({
fieldLabel: 'State',
resizable: true,
name: 'to[]',
anchor:'100%',
store: store_states,
mode: 'local',
displayField: 'state',
displayFieldTpl: '{state} ({abbr})',
valueField: 'abbr',
addUniqueValues: false,
value: new Ext.data.ArrayReader({
id: 0
}, Ext.data.Record.create([
{name: 'abbr'},
{name: 'state'}
])).readRecords([['AL', 'Alabama'], ['NY', 'New York'], ['MN', 'Minessota']]).records
//value: ['AL', 'NY', 'MN']
//value: 'AL, NY, MN'
});


Any hints?

Bastian

Lloyd K
11 Nov 2008, 4:36 AM
I'm having the exact same problem efiebba! :/

feanor73
12 Nov 2008, 10:40 AM
Hi,

You're extension was really great. I'm happy that you still work on it.

However, I notice some problem. Try to add an element, remove it, add again.
The available selection grows... but with duplicate items.

The link to the demo doesn't work.

Finally, I agree with galdaka suggestion and could be good to have a reset function which remove boxes (caption and on the store) and free-element.

Even if you don't want to add this functionnality, could explain me how to get and to remove the RawValue properly.



If it helps, I fixed the problem with "Try to add an element, remove it, add again.
The available selection grows... but with duplicate items." by uncommenting one line of code in the remove function of the BoxSelect.Item like so:




'remove': function(box){
delete this.selectedValues[box.value];
var rec = this.removedRecords[box.value];
if(rec){
this.store.add(rec);
this.sortStore();
this.view.render();
this.removedRecords[box.value] = null; // this is the change - prevents removed rows from duplicating in backing store
}
},



I'm not sure what side effect this change causes (there probably was a reason that the line of code was commented out to begin with), but so far so good on my end.

feanor73
12 Nov 2008, 11:56 AM
Hi,

Sorry for my English,

Excellent work. One question: Is posible combine free (non-defined) values with pre-defined (In combobox) values?

I think would be a good idea define a separator as parameter and when you write non-defined value enter separator and make posible expand combobox again for enter new value. This new value will have a diferent css or color (For mark as non-predefined).

Thanks in advance,


I need this ability as well. In my app, I decided to delimit free text with a semicolon ";". This allowed me to add the following method to the BoxSelect:



initEvents : function(){
Ext.ux.BoxSelect.superclass.initEvents.call(this);
var keyMap = new Ext.KeyMap(this.el, {

key: ";",
fn: function(e){
if(this.editable !== false) {
this.addItem(this.lastValue, this.lastValue);
this.setRawValue('');
this.lastSelectionText = '';
this.applyEmptyText();
}
},
scope : this,
forceKeyDown : true
});
},


I haven't tested this much and I am sure it is not very flexible, but it seems to work in the context of my application.

Hope that helps someone out!

mirko
14 Nov 2008, 11:13 AM
Great extension. Is it possible to set max height (max grow)?

galdaka
14 Nov 2008, 2:39 PM
I need this ability as well. In my app, I decided to delimit free text with a semicolon ";". This allowed me to add the following method to the BoxSelect:



initEvents : function(){
Ext.ux.BoxSelect.superclass.initEvents.call(this);
var keyMap = new Ext.KeyMap(this.el, {

key: ";",
fn: function(e){
if(this.editable !== false) {
this.addItem(this.lastValue, this.lastValue);
this.setRawValue('');
this.lastSelectionText = '';
this.applyEmptyText();
}
},
scope : this,
forceKeyDown : true
});
},


I haven't tested this much and I am sure it is not very flexible, but it seems to work in the context of my application.

Hope that helps someone out!

Not work!! ;)

Live example: http://www.jadacosta.es/extjs/examples/boxselect/index.html

joseadriano
15 Nov 2008, 1:07 PM
Thank you, helping to improve more and more ExtJS funcionalities.

efiebba
17 Nov 2008, 4:17 AM
Hello

Some days ago i asked about some problems with this nice extension. I think the solution is, that it only works with ext 2.2 - Is it correct?

Bastian

feanor73
19 Nov 2008, 7:57 AM
Not work!! ;)

Live example: http://www.jadacosta.es/extjs/examples/boxselect/index.html


I noticed 2 problems:
1. In IE7, it doesn't put the box around the text. It works in Firefox 2.0. I will eventually have to get it to work in IE7! To get the box to appear, you need to type your text and then a semi colon. So, without the quotes you type "Test;".

2. getValue() doesn't seem to work in either browser in your live example with my change. I'm not sure why without debugging it. I just post the contents of the field in my app and that seems to work.

Sorry.

Psychokrameur
22 Nov 2008, 1:35 AM
Thanks feanor73,

I'll test it soon.

feanor73
25 Nov 2008, 9:15 AM
I noticed 2 problems:
1. In IE7, it doesn't put the box around the text. It works in Firefox 2.0. I will eventually have to get it to work in IE7! To get the box to appear, you need to type your text and then a semi colon. So, without the quotes you type "Test;".

2. getValue() doesn't seem to work in either browser in your live example with my change. I'm not sure why without debugging it. I just post the contents of the field in my app and that seems to work.

Sorry.


I think I fixed problem 1.

IE and Firefox seem to treat character codes differently with regards to the semi-colon. (that's conjecture on my part).


So, I tried this in the KeyMap setup and it seemed to work (didn't try chrome, safari, or opera):


var key = ";";
if( Ext.isIE ) {
key = 186;
}
var keyMap = new Ext.KeyMap(this.el, {

key: key,
fn: function(e){
if(this.editable !== false) {
this.addItem(this.lastValue, this.lastValue);
this.setRawValue('');
this.lastSelectionText = '';
this.applyEmptyText();
}
},
scope : this,
forceKeyDown : true
});

hugo69
28 Nov 2008, 11:16 AM
I just find this extension... waw!!! what a great job !


I am newbie at ExtJs and I encontoured some problems with performance.

I want to use it for a search field.
So, I made a JSON Store with all the keyword in the database. (between 5.000 and 10.000 words).

The problem is:
- When i click in the field, I have to wait a few second, I think the program is loading with Javascript the data
- When i type some letters, at the second letter, it takes a long time to look into the Store with Javascript, I can see my Processor charge...

I also tried with a 'REMOTE' way, but, it's too long..

in local the problem is that Javascript is overcharging my UC and in remote, the request is too long...

I have filtered in maximum my store, by overpassing words smaller than 4 letters....etc...

Any idea?

Here is my code:


var keywordJReader = new Ext.data.JsonReader({
totalProperty: 'totalCount',
root: 'records',
},
[{name: 'name'}]
);

var keywordStore = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({
url: 'tools/ajax.php',
method: 'POST'
}),
baseParams:{action: 'keyword'},
reader: keywordJReader,
autoLoad: true
});


var myK = new Ext.ux.BoxSelect({
fieldLabel: 'Keyword',
resizable: false,
name: 'keyword[]',
forceSelection :true,
store: keywordStore,
mode: 'local',
displayField: 'name',
displayFieldTpl: '{name}',
valueField: 'name',
addUniqueValues: false
})

Here is the PHP JSON store for test:

$json = Array();
$json['success'] = 'true';
$json['totalCount'] = 5000;
$records=array();
$json['records'] = &$records;


for($i=0;$i<5000;$i++)
{
$myArray=array();
$myArray['name']="test".$i;
$records[]=$myArray;
unset($myArray);
}

echo json_encode($json);


Thank for your help!

Saeven
3 Dec 2008, 9:28 AM
+1 on this issue. It really needs to be addressed, the code below works.


I fixed the problem with removed items still being submitted by adding this line to the dispose function: Ext.fly(this.hidden).remove();


dispose: function(withoutEffect) {
this.fireEvent('remove', this);
Ext.fly(this.hidden).remove();

if(withoutEffect){
this.destroy();
}
else{
this.el.hide({
duration: .5,
callback: function(){
this.move('right');
this.destroy()
}.createDelegate(this)
});
}

return this;
}

hugo69
4 Dec 2008, 3:49 AM
Here is the soluce I find out. Not very clean, but working. If somebody as a better soluce, I would enjoy it, specially to find a way to have the autocomplete on local, with performance pb.

First, I put in REMOTE mode and I put thie queryDelay at 10, and minChars at 2.

As my SQL request is quite heavy, and I want a quick response, I made a file prepare with the result.
How do I do?

I put my SQL request in a file. Thie file generate a php file on my server. The first file with the SQL request is called every 10min by a CRON task.
The written file is PHP encoding String, putting all my result in a Tab Var.

When I beggin to use the combo box autocomplete, with remote mode, it made an ajax call. This target script include the file created above, and made the search in this PHP Tab Var. So its very quick. The only delay, is the delay of ajax sending and responding.

Here is the file called by CRON to generate my PHP file with tab:
[CODE]
$tableau = $result_query;

//Ouverture du fichier PHP
$texte = '<?php $keyword["result"] = array(';

$i=0;
if(count($tableau)>0)
{
foreach($tableau as $val => $id)
{
if($i>0)
$virgule = ", ";

$texte .= $virgule."'".protection($val)."' => ".$i."";

$i++;
}
}

$Fnm = "pages/calcul_keyword.php";

//FERMETURE DU FICHIER PHP
$texte .= '); ?>';

//Ouvrir le fichier en mode

Psychokrameur
6 Dec 2008, 10:09 AM
var key = ";";
if( Ext.isIE ) {
key = 186;
}
var keyMap = new Ext.KeyMap(this.el, {

key: key,
fn: function(e){
if(this.editable !== false) {
this.addItem(this.lastValue, this.lastValue);
this.setRawValue('');
this.lastSelectionText = '';
this.applyEmptyText();
}
},
scope : this,
forceKeyDown : true
});


Feanor, how did you get the value adding with this function ? I've try getValue and getRawValue but the return is empty. Maybe I have to deal with the RawValue...

skaue
8 Dec 2008, 2:14 AM
Pressing backspace in IE throws an error initially. Add an item and pressing backspace works.

Fixed by checking if this.lastValue is defined like this:


onKeyUp: function(e)
{
if (this.editable !== false && !e.isSpecialKey())
{
if (typeof (this.lastValue) == 'undefined')
this.lastValue = { length: 0 };

if (e.getKey() == e.BACKSPACE && this.lastValue.length == 0)
{
e.stopEvent();
this.collapse();
var el = this.maininput.prev();
if (el) el.focus();
return;
}
}

this.autoSize();

Ext.ux.BoxSelect.superclass.onKeyUp.call(this, e);

this.lastValue = this.el.dom.value;
},

tested in IE and firefox \:D/

Having the autocompleter query the entire string when in local mode is solved by extending the combobox as described here (http://extjs.com/forum/showthread.php?p=258164#post258164)

Eager to find a working solution for adding new items when pressing Enter. Choosing an item as it is now, creates a new hidden field and set its value to whatever is defined in valuefield. Maybe one could add a new option for this extenstion; a function-delegate for adding items not in store. This way I could write my own function adding an item to the store, making sure the new value fits my store setup (ie. fields).

skaue
8 Dec 2008, 4:39 AM
I'm gonna share how I solved extending the boxselect for a tagging mechanism I needed. This extension contains the store since the JsonFormat is given. I also enable settings of the values from outside passing in an array with jsonformatted selected records.

works like charm... B)



Facilit.App.Form.ReferenceArchiveTag = Ext.extend(Ext.ux.BoxSelect, {
constructor: function(config)
{
config = config || {};
config.listeners = config.listeners || {}
config.ReferenceTags = config.ReferenceTags || [];

config.store = config.store || new Ext.data.JsonStore({
fields: ['ReferenceTagId', 'Description'],
url: '/ReferenceArchive/GetTags/',
sortInfo: { field: 'Description', direction: 'ASC' }
});

config = Ext.apply({
store: config.store,
fieldLabel: config.fieldLabel || Resources.ReferenceArchiveStrings.Tags.capitalize(),
resizable: true,
name: config.name || 'ReferenceTagId',
mode: 'local',
displayField: 'Description',
valueField: 'ReferenceTagId',
addUniqueValues: false,
value: config.ReferenceTags
}, config)
Facilit.App.Form.ReferenceArchiveTag.superclass.constructor.call(this, config);
},
initValue: function()
{
this.store.load({ boxId: this.id, callback: function(response, options, success)
{
var box = Ext.getCmp(options.boxId);
if (box.value && box.value.length > 0)
{
var records = new Ext.data.JsonReader({ fields: ['ReferenceTagId', 'Description'] }).readRecords(box.value).records;
box.setValues(records);
}
}
});
}
});
Ext.reg('referencearchivetag', Facilit.App.Form.ReferenceArchiveTag)


Hope this helps someone...

feanor73
5 Jan 2009, 1:23 PM
Feanor, how did you get the value adding with this function ? I've try getValue and getRawValue but the return is empty. Maybe I have to deal with the RawValue...

I use the raw value obtained by iterating over the form object itself (ie. document.getElementById(someFormId)). This was how all our existing form submissions already worked.

efiebba
7 Jan 2009, 3:13 AM
Hi

in this thread are a lot of fixes and extensions. Is there a chance for an new version?

Bastian

BIS
11 Jan 2009, 6:02 PM
hi

i found another bug in the line


onKeyUp : function(e) {
if(this.editable !== false && !e.isSpecialKey()){//
if(e.getKey() == e.BACKSPACE && (!this.lastValue || this.lastValue.length == 0)){
e.stopEvent();
this.collapse();
var el = this.maininput.prev();
if(el) el.focus();
return;
}
this.dqTask.delay(this.queryDelay);
}
this.autoSize();
Ext.ux.BoxSelect.superclass.onKeyUp.call(this, e);
this.lastValue = this.el.dom.value;
},

the if statment should become


if(e.getKey() == e.BACKSPACE && (!this.lastValue || this.lastValue.length == 0)){

otherwise you will get an error when the user enters a backspace on an empty field


BTW there are a few problems i can't figure out

1- i haven't yet figured how to set the value in the component
2- can we make it a single select enabled??
3- control over width without anchor
4- on keyup seems to be not working for me

most important: can anyone glue all those fixes together please i am still a newbie or I'd did it myself

woomboom
11 Jan 2009, 9:18 PM
Had an issue where i was resubmitting a form with different values selected for the boxselect and the previous values were being submitted. Following fix should be added inside the remove listener:

Ext.select('input[name=' + (this.hiddenName || this.name) + '][value=' + box.value +']').each(function(){
Ext.removeNode(this.dom);
});


Like this

addItem: function(id, caption){
var box = new Ext.ux.BoxSelect.Item({
id: 'Box_' + id,
maininput: this.maininput,
renderTo: this.holder,
className: this.options['className'],
caption: caption,
disabled: this.disabled,
'value': id,
listeners: {
'remove': function(box){
delete this.selectedValues[box.value];
var rec = this.removedRecords[box.value];
if(rec){
this.store.add(rec);
this.sortStore();
this.view.render();
//this.removedRecords[box.value] = null;
}
Ext.select('input[name=' + (this.hiddenName || this.name) + '][value=' + box.value +']').each(function(){
Ext.removeNode(this.dom);
});
},
scope: this
}
});
box.render();

Scorpie
12 Jan 2009, 12:36 AM
Perhaps its a good idea to bundle all the fixes into a new release?

Anybody up for this?

dizor
31 Jan 2009, 9:03 AM
How i can load values into boxSelect on load form?

efiebba
2 Feb 2009, 2:38 AM
+1 New Release :D

radtad
27 Feb 2009, 6:12 PM
Does anyone have a good way to pattern match? For example if I type 'ea' I want it to match both "Ear" and "Bear" and not just 'Ear' like it does right now.

A point in the right area this code should go would help too.

galdaka
28 Feb 2009, 2:35 AM
+ 1 for new release ;)

radtad
2 Mar 2009, 4:54 PM
For any of those interested, I added the ability to match anywhere in the string and have it bold that part of the match. It came from the http://extjs.com/forum/showthread.php?p=253617#post253617 thread.

Ext.ux.BoxSelect code (from initial poster):



Ext.namespace('Ext.ux');

Ext.ux.BoxSelect = Ext.extend(Ext.form.ComboBox, {
anyMatch: true,
caseSensitive: false,
createValueMatcher: function(value) {
if(Ext.isEmpty(value, false)){
return new RegExp('^');
}
value = Ext.escapeRe(String(value));
return new RegExp((this.anyMatch === true ? '' : '^') + '(' + value + ')', this.caseSensitive ? '' : 'i');
},
prepareData : function(data) {
var result = Ext.apply({}, data);
result[this.displayField] = data[this.displayField].replace(this.createValueMatcher(this.getRawValue()), function(a, b){
if (typeof b != 'string') {
return '';
}
return '<span class="ext-combo-match">' + b + '</span>';
});
return result;
},
doQuery : function(q, forceAll){
if(q === undefined || q === null){
q = '';
}
var qe = {
query: q,
forceAll: forceAll,
combo: this,
cancel:false
};
if(this.fireEvent('beforequery', qe)===false || qe.cancel){
return false;
}
q = qe.query;
forceAll = qe.forceAll;
if(forceAll === true || (q.length >= this.minChars)){
if(this.lastQuery !== q){
this.lastQuery = q;
if(this.mode == 'local'){
this.selectedIndex = -1;
if(forceAll){
this.store.clearFilter();
}else{
this.store.filter(this.displayField, q, this.anyMatch);
}
this.onLoad();
}else{
this.store.baseParams[this.queryParam] = q;
this.store.load({
params: this.getParams(q)
});
this.expand();
}
}else{
this.selectedIndex = -1;
this.onLoad();
}
}
},

initList : function(){
Ext.ux.BoxSelect.superclass.initList.apply(this, arguments);
this.view.prepareData = this.prepareData.createDelegate(this);
},
initComponent:function() {
Ext.apply(this, {
selectedValues: {},
boxElements: {},
current: false,
options: {
className: 'bit',
separator: ','
},
hideTrigger: true,
grow: false
});

Ext.ux.BoxSelect.superclass.initComponent.call(this);
},

onRender:function(ct, position) {
Ext.ux.BoxSelect.superclass.onRender.call(this, ct, position);

this.el.removeClass('x-form-text');
this.el.className = 'maininput';
this.el.setWidth(20);

this.holder = this.el.wrap({
'tag': 'ul',
'class':'holder x-form-text'
});

this.holder.on('click', function(e){
e.stopEvent();
if(this.maininput != this.current) this.focus(this.maininput);
}, this);

this.maininput = this.el.wrap({
'tag': 'li', 'class':'bit-input'
});


Ext.apply(this.maininput, {
'focus': function(){
this.focus();
}.createDelegate(this)
})

this.store.on('datachanged', function(store){
this.store.each(function(rec){
if(this.checkValue(rec.data[this.valueField])){
this.removedRecords[rec.data[this.valueField]] = rec;
this.store.remove(rec);
}
}, this);
}, this);

this.on('expand', function(store){
this.store.each(function(rec){
if(this.checkValue(rec.data[this.valueField])){
this.removedRecords[rec.data[this.valueField]] = rec;
this.store.remove(rec);
}
}, this);
}, this);

this.removedRecords = {};
},

onResize : function(w, h, rw, rh){
this._width = w;
this.holder.setWidth(w-4);
Ext.ux.BoxSelect.superclass.onResize.call(this, w, h, rw, rh);
this.autoSize();
},

onKeyUp : function(e) {
if(this.editable !== false && !e.isSpecialKey()){
if(e.getKey() == e.BACKSPACE && this.lastValue.length == 0){
e.stopEvent();
this.collapse();
var el = this.maininput.prev();
if(el) el.focus();
return;
}
this.dqTask.delay(this.queryDelay);
}

this.autoSize();
Ext.ux.BoxSelect.superclass.onKeyUp.call(this, e);

this.lastValue = this.el.dom.value;
},

onSelect: function(record, index) {
var val = record.data[this.valueField];

this.selectedValues[val] = val;

if(typeof this.displayFieldTpl === 'string')
this.displayFieldTpl = new Ext.XTemplate(this.displayFieldTpl);

if(!this.boxElements[val]){
var caption;
if(this.displayFieldTpl)
caption = this.displayFieldTpl.apply(record.data)
else if(this.displayField)
caption = record.data[this.displayField];

this.addItem(record.data[this.valueField], caption)

}
this.collapse();
this.setRawValue('');
this.lastSelectionText = '';
this.applyEmptyText();

this.autoSize();
},

onEnable: function(){
Ext.ux.BoxSelect.superclass.onEnable.apply(this, arguments);
for(var k in this.boxElements){
this.boxElements[k].enable();
}
},

onDisable: function(){
Ext.ux.BoxSelect.superclass.onDisable.apply(this, arguments);
for(var k in this.boxElements){
this.boxElements[k].disable();
}
},

getValue: function(){
var ret = [];
for(var k in this.selectedValues){
if(this.selectedValues[k])
ret.push(this.selectedValues[k]);
}
return ret.join(this.options['separator']);
},

setValue: function(value){
this.removeAllItems();
this.store.clearFilter();
this.resetStore();

if(Ext.isArray(this.value) && typeof this.value[0]==='object' && this.value[0].data){
this.setValues(this.value);
}
else{
if(value && typeof value === 'string'){
value = value.split(',');
}

var values = [];

if(this.mode == 'local'){
Ext.each(value, function(item){
var index = this.store.find(this.valueField, item.trim());
if(index > -1){
values.push(this.store.getAt(index));
}
}, this);
}else{
this.store.baseParams[this.queryParam] = value;
this.store.load({
params: this.getParams(value)
});
}
this.setValues(values);
}
},

setValues: function(values){
if(values){
Ext.each(values, function(data){
this.onSelect(data);
}, this);
}

this.value = '';
},

removeAllItems: function(){
for(var k in this.boxElements){
this.boxElements[k].dispose(true);
}
},

resetStore: function(){
for(var k in this.removedRecords){
var rec = this.removedRecords[k];
this.store.add(rec);
}
this.sortStore();
},

sortStore: function(){
var si = this.store.getSortState();
if(si && si.field)
this.store.sort(si.field, si.direction);
},

addItem: function(id, caption){
var box = new Ext.ux.BoxSelect.Item({
id: 'Box_' + id,
maininput: this.maininput,
renderTo: this.holder,
className: this.options['className'],
caption: caption,
disabled: this.disabled,
'value': id,
listeners: {
'remove': function(box){
delete this.selectedValues[box.value];
var rec = this.removedRecords[box.value];
if(rec){
this.store.add(rec);
this.sortStore();
this.view.render();
//this.removedRecords[box.value] = null;
}
},
scope: this
}
});
box.render();

box.hidden = this.el.insertSibling({
'tag':'input',
'type':'hidden',
'value': id,
'name': (this.hiddenName || this.name)
},'before', true);

this.boxElements['Box_' + id] = box;
},

autoSize : function(){
if(!this.rendered){
return;
}
if(!this.metrics){
this.metrics = Ext.util.TextMetrics.createInstance(this.el);
}
var el = this.el;
var v = el.dom.value;
var d = document.createElement('div');
d.appendChild(document.createTextNode(v));
v = d.innerHTML;
d = null;
v += " ";
var w = Math.max(this.metrics.getWidth(v) + 10, 10);
if(typeof this._width != 'undefined')
w = Math.min(this._width, w);

this.el.setWidth(w);

if(Ext.isIE){
this.el.dom.style.top='0';
}
},

onEnable: function(){
Ext.ux.BoxSelect.superclass.onEnable.apply(this, arguments);

for(var k in this.boxElements){
this.boxElements[k].enable();
}
},

onDisable: function(){
Ext.ux.BoxSelect.superclass.onDisable.apply(this, arguments);

for(var k in this.boxElements){
this.boxElements[k].disable();
}
},

checkValue: function (value) {
return (typeof this.selectedValues[value] != 'undefined');
}
});

HTML code:



<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<title>BoxSelect</title>
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
<link href="../../resources/css/ext-all.css" media="screen" rel="Stylesheet" type="text/css" />
<link href="../../examples/examples.css" media="screen" rel="Stylesheet" type="text/css" />

<script src="../../adapter/ext/ext-base.js" type="text/javascript"></script>
<script src="../../ext-all-debug.js" type="text/javascript"></script>
<script src="../states.js" type="text/javascript"></script>
<script type="text/javascript" src="Ext.ux.BoxSelect.js"></script>

<style>
.ext-combo-match {
font-weight: bold;
}
</style>

<link href="boxselect.css" media="screen" rel="Stylesheet" type="text/css" />

<script type="text/javascript">
Ext.onReady(function() {

Ext.QuickTips.init();

var states = new Ext.data.SimpleStore({
fields: ['abbr', 'state', 'nick'],
data: Ext.exampledata.states,
sortInfo: {field: 'state', direction: 'ASC'}
});


var select = new Ext.ux.BoxSelect({
fieldLabel: 'State',
resizable: true,
name: 'to[]',
anchor:'100%',
store: states,
mode: 'local',
displayField: 'state',
displayFieldTpl: '{state} ({abbr})',
valueField: 'abbr',
addUniqueValues: false,
anyMatch: true,
queryIgnoreCase: true,
// createValueMatcher only necessary to override default regex
createValueMatcher: function(value) {
value = String(value).replace(/\s*/g, '');
if(Ext.isEmpty(value, false)){
return new RegExp('^');
}
value = Ext.escapeRe(value.split('').join('\\s*')).replace(/\\\\s\\\*/g, '\\s*');
return new RegExp('(' + value + ')', 'i');
},
value: new Ext.data.ArrayReader({
id: 0
}, Ext.data.Record.create([
{name: 'abbr'},
{name: 'state'}
])).readRecords([['AL', 'Alabama'], ['NY', 'New York'], ['MN', 'Minessota']]).records
//value: ['AL', 'NY', 'MN']
//value: 'AL, NY, MN'
});

var form = new Ext.form.FormPanel({
baseCls: 'x-plain',
labelWidth: 55,
defaultType: 'textfield',
autoHeight: true,
items: [
select,
{
fieldLabel: 'Subject',
name: 'subject',
anchor: '100%'
},
{
xtype: 'textarea',
hideLabel: true,
name: 'msg',
height: 150,
anchor: '100%'
}
]
});

var window = new Ext.Window({
title: 'Resize Me',
width: 500,
minWidth: 300,
layout: 'fit',
plain:true,
bodyStyle:'padding:5px;',
buttonAlign:'center',
items: form,
maximizable: true,
buttons: [{
text: "setValue('AK,CA,CO')",
scope: this,
handler: function(){
select.setValue('AK,CA,CO');
}
},{
text: 'enable()',
scope: this,
handler: function(){
select.enable();
}
}, {
text: 'disable()',
scope: this,
handler: function(){
select.disable();
}
}, {
text: 'getValue()',
scope: this,
handler: function(){
alert(select.getValue());
}
}]
});

window.show();

select.focus();
window.focus();
});
</script>

</head>
<body>

</body>
</html>

djul57
10 Mar 2009, 5:52 AM
Hi,

I'm a newbee with extjs. I adapt this script with my values, but I have a problem: only the first letter of the possible values appears in the input

Here is my code:



var criteriaImps = [
{criteriaId: 'carBrand', shortTitle: 'brand', criteriaType: OptionsCriteriaType, createCriteriaWidget: {className: 'Ext.ux.CheckBoxTableCriteria', config: {columns: 10, emptyStateText: 'Show all brands', options: ["AUDI","BMW","CHEVROLET","CHRYSLER","CITROEN","DAEWOO","DAIHATSU","FERRARI","FIAT","FORD","HONDA","HYUNDAI","JAGUAR","JEEP","KIA","LANCIA","LAND ROVER","LEXUS","LOTUS","MAZDA","MERCEDES-BENZ","MG","MINI","MITSUBISHI","NISSAN","OPEL","PEUGEOT","PONTIAC","PORSCHE","RENAULT","ROVER","SAAB","SEAT","SKODA","SMART","SUBARU","SUZUKI","TOYOTA","VOLVO","VW"]}}}];



var states = new Ext.data.SimpleStore({
//fields: ['abbr', 'state', 'nick'],
fields: ['state'],
//data: Ext.exampledata.states,
data:this.criteriaImps[0].createCriteriaWidget.config.options,
sortInfo: {field: 'state', direction: 'ASC'}
});


var select = new Ext.ux.BoxSelect({
fieldLabel: 'State',
resizable: false,
name: 'to[]',
anchor:'100%',
store: states,
mode: 'local',
displayField: 'state',
//displayFieldTpl: '{state} ({abbr})',
//valueField: 'abbr',
displayFieldTpl: '{state}',
valueField: 'state',
addUniqueValues: false,
/*value: new Ext.data.ArrayReader({
id: 0
}, Ext.data.Record.create([
{name: 'abbr'},
{name: 'state'}
])).readRecords([['AL', 'Alabama'], ['NY', 'New York'], ['MN', 'Minessota']]).records
*/
//value: ['AL', 'NY', 'MN']
//value: 'AL, NY, MN'
});

anolsi
13 Mar 2009, 10:51 AM
Is the property resizable working?

djul57
19 Mar 2009, 6:23 AM
it's possible to render a Ext.ux.BoxSelect in a <input type="text" id="myinput" /> ?

if I write:


myBoxSelect.render('myinput');

nothing append, but if I replace

<input type="text" id="myinput" /> by
<div id="myinput"></div>, the boxselect appears in the div.

akannu
24 Mar 2009, 6:48 AM
I was wondering if I could extend this to make it an expression builder. I will insert the following:
1) ( or )
2) Relational Expression (via a popup)
3) AND, OR, NOT

Once I construct a logical expression using the above, when I hover over any of the above to delete. However, if the relation expression only needs a minor tweaking, I don't want to delete and create again. I'd like to be able to edit. I was wondering if each element in this BoxSelect can be somehow mapped back to the popup which will allow me to edit the relational expression at least.

I understand this will need to be done by us. But, I wondered if the BoxSelect's basic behavior will allow for this extension.

Thanks

mdissel
25 Mar 2009, 3:55 AM
Expression builder.. Nice idea or has anyone else already developer something alike? (for example see http://ajax.easyquerydemo.com/)

akannu
25 Mar 2009, 4:56 AM
I like that.

crink
1 Apr 2009, 12:28 PM
I noticed that "ul" tag doesn't render correctly in FF 3.0.8. You can fix this by modifying the css


ul.holder { margin: 0; overflow: hidden; height: auto ! important; display:block;}

iamakimmer
1 Apr 2009, 12:56 PM
Does anyone know why upon replacing the anchor property with a static width (width:300) that the cursor does not show in IE6 when I click on the input area? If I start typing in the box ,then I can see the cursor.

fangzhouxing
13 Apr 2009, 7:17 PM
Does anyone know why upon replacing the anchor property with a static width (width:300) that the cursor does not show in IE6 when I click on the input area? If I start typing in the box ,then I can see the cursor.

I have the same problem in IE7, and the editable config value must be true for the cursor to be visible!

tdupont
14 May 2009, 1:16 PM
I am using the Ext.ux.BoxSelect control, and I want to be able highlight text that I am typing; the control does not seem to allow this by default.
Does anyone know how to do this?

grgur
15 May 2009, 3:15 AM
Great extension! I really LOVE it! =D>

Unfortunately it seems not to work in Ext 3.0 (rc 1.1). Any hopes for this extension in ext 3?

danh2000
20 May 2009, 10:49 PM
As development of this component seems to have stopped, I was asked to create a component like this one which fixes the errors and adds some additional functionality.

Please see the thread here:

http://extjs.com/forum/showthread.php?p=332654

lenatic
18 May 2010, 1:38 AM
Error: uncaught exception: [Exception... "Node was not found" code: "8" nsresult: "0x80530008 (NS_ERROR_DOM_NOT_FOUND_ERR)" location: "http://extjs.cachefly.net/ext-3.0.0/ext-all.js Line: 7"]

darklow
15 Dec 2010, 7:24 AM
Have anyone fixed this BoxSelect component for ExtJS 3.x ?
This is so much simpler and cleaner by design than SuperBoxSelect... Would like to implement better this one.
Anyone?

gurpreet.saini
31 Mar 2011, 9:42 AM
Getting the following error:

Node was not found" code: "8
chrome://firebug/content/blank.gif(function(){var h=Ext.util,k=Ext.each,...lclick",this.onNodeDblClick,this)}});

Can anyone help?

I am using Ext 3.3.1

Thanks
-G

kleins
2 Jul 2011, 3:05 AM
There is an implementation for Ext 4, now: http://www.sencha.com/forum/showthread.php?139170-BoxSelect-for-Ext-4-another-implementation&p=621549#post621549

raj_s37
1 Feb 2012, 1:40 AM
Hi All,

Please provide code in GWT instead of JS

wm003
3 Feb 2012, 5:16 AM
Hi All,

Please provide code in GWT instead of JS

GWT has own Forum Threads, you are in ExtJS here, not ExtGWT

arvindwill
17 Oct 2012, 7:11 AM
TypeError: valueStore is undefined


chrome://firebug/content/blank.gif


valueRecord = valueStore.findExact(valueField, record);BoxSel...6111112 (line 1263)




Cant able to figureout where the valuestore is assigned, even multiple breakpoints didnt helped. The configuration is

xtype:'boxselect',
fieldLabel: 'Select multiple states',
displayField: 'name',
width: 500,
labelWidth: 130,
store: 'StorePickupLocation',
queryMode: 'local',
valueField: 'abbr'

monami
18 Oct 2012, 6:20 AM
TypeError: valueStore is undefined


chrome://firebug/content/blank.gif
valueRecord = valueStore.findExact(valueField, record);BoxSel...6111112 (line 1263)






Cant able to figureout where the valuestore is assigned, even multiple breakpoints didnt helped. The configuration is

xtype:'boxselect',
fieldLabel: 'Select multiple states',
displayField: 'name',
width: 500,
labelWidth: 130,
store: 'StorePickupLocation',
queryMode: 'local',
valueField: 'abbr'

Did you get it work? I just came across identical problem.. I am trying to run the example code..

monami
19 Oct 2012, 4:50 AM
I've found the reason:

Please note, this component does not support versions of ExtJS earlier than 4.1.

:)

arvindwill
19 Oct 2012, 5:10 AM
Yeah sorry to reply late, that will not work in 4.0.x . We found that problem and due to other few layout bugs in 4.0 we are upgrading to 4.1