PDA

View Full Version : [2.x] Ext.ux.MultiSelectTextField (outlook/gmail style 'to' field)



RLivsey
29 Nov 2007, 1:46 PM
Like in outlook (or gmail) where you can just type into an input field to select multiple items. The value for each item isn't what is displayed, just like a normal multi select box, for example in the screenie below it has a values of "VA", "MI" and "DE".

http://svn.livsey.org/experiments/javascript/ext/2.0/ux/MultiSelectTextField/screenie.gif

We're using this in an app so that users can type in the names/emails/etc of people to send items to, but the value of the fields is the user IDs.

I've only tried it with a local store setup as in the demo, but seems to work well on FF, IE6, IE7 and Safari.

30/11/2007 - v0.0.3 - fixed bug where multiple fields didn't initialize data properly
30/11/2007 - v0.0.2 - added validation, fixed bug deleting items
29/11/2007 - v0.0.1 - initial version

Bugs reports and Suggestions are very welcome! Cheers.

Source on GitHub (http://github.com/rlivsey/ext-multiselecttextfield/tree/master)

george.antoniadis
29 Nov 2007, 2:33 PM
Woa!! Awesome uc! :)

galdaka
29 Nov 2007, 2:35 PM
Excellent work!

One bug: If I select various entries and manually (By keyboard) go to intermediate comma and delete it, I have a error.

Thanks in advance,

RLivsey
29 Nov 2007, 4:50 PM
Ah good catch, I'll have a go at fixing this up in the am, shouldn't be too difficult!

Cheers.

apaa
29 Nov 2007, 9:57 PM
I post field that was used 'hiddenName',but data shows displayField not valueField

JeffHowden
30 Nov 2007, 1:38 AM
Very nice. I haven't taken a peek at the code yet, but here's my initial suggestions based on playing with the demo.


It would be good if when you clicked anywhere within one of the list items, it selected that list item for easy deletion
backspacing all the way to an empty field leaves a "dropdown" of the single item in the store that most closely matched the last character to be removed by pressing the backspace key
the field out to offer in its config the ability to toggle whether or not duplicates are permitted. if not, values that have already been selected should be filtered from the store.

RLivsey
30 Nov 2007, 4:52 AM
Excellent work!

One bug: If I select various entries and manually (By keyboard) go to intermediate comma and delete it, I have a error.

Thanks in advance,

I've fixed this now, thanks again!

RLivsey
30 Nov 2007, 4:55 AM
Very nice. I haven't taken a peek at the code yet, but here's my initial suggestions based on playing with the demo.

It would be good if when you clicked anywhere within one of the list items, it selected that list item for easy deletion
backspacing all the way to an empty field leaves a "dropdown" of the single item in the store that most closely matched the last character to be removed by pressing the backspace key
the field out to offer in its config the ability to toggle whether or not duplicates are permitted. if not, values that have already been selected should be filtered from the store.

Hi, thanks for checking it out.

You can click delete anywhere within an item to delete it, but I agree it could be nice to auto select it so it's more obvious.

Will look into the backspacing issue.

At the moment, it doesn't allow duplicates, I'll need to change a few things about how it works to be able to enable that but defo possible.

I need to add validation to check for the number of items - IE minimum/maximum/exact number of items. I'm adding that in this afternoon.

Cheers.

sfwalter
30 Nov 2007, 5:51 AM
kudos to you! Excellent job. A nice application for this would be if an app supports tagging ala Del.icio.us

wm003
30 Nov 2007, 6:17 AM
WOW!!! Great Widget! Very good work! :D

xwisdom
30 Nov 2007, 7:35 AM
WOW!!! Great Widget! Very good work! :D

Great Widget! Nice work!

One little issue thought.... After selecting an item the cursor jumps to the beginning of the textbox. I think it should default to the end or to the place where the text was inserted.

Tested in IE7

RLivsey
30 Nov 2007, 8:22 AM
One little issue thought.... After selecting an item the cursor jumps to the beginning of the textbox. I think it should default to the end or to the place where the text was inserted.

Tested in IE7

Not like IE to be annoying ;)

I see the same thing, seems to work when pressing enter to select but not when clicking an item. Works as it should in other browsers, just IE decides to play up.

Pretty busy this afternoon but should be able to fix that up over the weekend.

krycek
30 Nov 2007, 10:35 AM
Very very good ux.

Facebook has one field similiar on the "send message". Where you can select the users that you want to send the message to, just like yours, but after selected, an user became a box with a "x" button for delection.

Great work! =D>

DigitalSkyline
30 Nov 2007, 12:50 PM
This looks like it could definitely be useful, Nice work!

I've only tried the demo but... why is there a validation (minimum of 2 req)? Also why does each entry have a trailing comma ? Perhaps a semi-colon would be more appropriate? Small issues I know... just thinking out loud again :)

JeffHowden
30 Nov 2007, 2:17 PM
I've only tried the demo but... why is there a validation (minimum of 2 req)? Also why does each entry have a trailing comma ? Perhaps a semi-colon would be more appropriate?

Actually, that brings up a good point. The separator should be a config item, perhaps defaulting to a comma (with a trailing space). Also, the developer ought to be able to indicate in the config whether to insert a trailing or leading separator when an item is added to the list.

galdaka
30 Nov 2007, 2:40 PM
Excellent!

Sorry for my English,

The trigger visibility is configurable?

I think that clearablecombobox functionality should be good for this widget:

1) Fill field with "autocomplete" functionality

OR

2) Select custom value from a combobox

AND

3) Posibility for erase all entries of field

Thanks in advance,

TommyMaintz
30 Nov 2007, 5:06 PM
Hey, great widget you got here!
I created something similar as a plugin for jQuery and I found it quite hard to get everything to work (especially without all the nice Ext features :)), but this looks real solid already!

A configurable seperator, and automatically removing the last seperator when the field is blurred, and inserting it when the user focusses the field again are maybe nice things to add.

Keep up the great work!

jimmyphp
1 Dec 2007, 2:23 AM
Good work!
Sorry for my English,

is possible modify plugin for textarea?

Thanks in advance,

fsakalos
1 Dec 2007, 3:25 AM
Perfect! If I will have application where I can use it, for sure I'll download it. :-)

ThorstenSuckow
2 Dec 2007, 6:22 AM
This is great, I'll use it whenever I need to tag something in my application! ~o)

Bharani
3 Dec 2007, 2:08 AM
This is exactly what i needed to support multi valued fields. Thanks B)

dawesi
4 Dec 2007, 10:23 PM
Magical Extension.... thanks! :-)

A couple of questions in the same area of interest:

1) Is it possible to load default data and what format (ie json array, comma list??)

2) we also want to put a button at the end of the field to pop up a dialogue of tags that would be added to the list. (aka tag picker)... how would we return this to the text field?

For both: is there an update() event on the control that would parse the list to make sure that it is correct?

3) also when I paste a valid list into the text box it clears it.... I suppose this is similar to the above

fidorkozrout
10 Dec 2007, 2:27 PM
Modify the updateDisplay and onBlur methods. The methods must call the immediate parent class of MultiSelectTextField. So Ext.form.ComboBox.superclass.onBlur.call will become Ext.ux.MultiSelectTextField.superclass.onBlur.call and similarly for updateDisplay.

Hani
30 Dec 2007, 6:58 PM
One feature I'd like to request is the ability to have items entered in the field that arent in the store.

For example, think of a tagging system (or an email to field). You can select an existing tag (or email address from an addressbook), but you can also use your own tag (or enter a freeform email address that isnt from the addressbook).

Also, in the example, I dont see a way of querying the underlying selected values. Is the right approach to query the form for the hidden fields and get the values that way? Any chance of a method that does that work for you? (could just return this.values()!)

Would be a nice addition to the demo page, to have the send button pop a message box with the current selected values.

Finally, is it possible to have the separator char also act as a 'select' trigger for the dropdown? So as well as enter selecting the highlighted item, the separator would do the same.

zilionis
30 Mar 2008, 10:24 PM
Server down? Damn then i need it, it gone :(

msuresh
4 Apr 2008, 4:23 PM
Unable to download the plugin. Can you upload the plugin.

ext_fan
4 Apr 2008, 10:01 PM
Hi,
the article matches with the exact requirement of mine..please upload the plugin again in valid site.

cmendez21
5 Apr 2008, 7:41 AM
hi all, i requested a copy on Help 2.0 forum but no answer
If some one has a copy please share
:s

ext_fan
6 Apr 2008, 11:59 AM
Could anyone help us uploading the code

wm003
6 Apr 2008, 12:36 PM
:-| I only have the first version 0.0.1. But better than nothing? Here we go...

cmendez21
7 Apr 2008, 12:20 AM
wm003, You give this extension more life
Thanks a lot :D

symfony
17 Apr 2008, 4:29 AM
Is anybody here, who have the latest version?

apaa
21 Apr 2008, 3:32 AM
I just found I have a copy of this widget...share it with you

RLivsey
24 Jun 2008, 1:01 AM
Sorry for the lack of response, hellishly busy lately.

Anyway, I've finally got round to moving my code from the old SVN server to GitHub.

This can now be found here:

http://github.com/rlivsey/ext-multiselecttextfield/tree/master

kmoore
8 Jul 2008, 6:53 PM
This is a great extension which I have found very useful.


I'm getting the following error when trying to reset my form


this.hiddenFields[i].remove is not a function

in the handler for my reset form button I am using the following


function(){
Ext.getCmp('search_form').getForm().reset();

any ideas to get around this?

cmendez21
15 Jul 2008, 11:33 PM
as long as i have used this control

i've never get this error also i always set the value blank and manually populate values if i have to edit something, but what i have its that when i reset the form it makes validation right away

also you need to use control.clearValue(); instead
this.hiddenFields[i].remove

are you using the latest version ?

kmoore
16 Jul 2008, 8:21 PM
I have the lates version, which is version 0.0.3

Thanks I am using clearValue() now and that seems to work well

Zyclops
6 Aug 2008, 3:51 PM
I found that clearValue was not actually removing the hidden form fields. Calling an Ext.get seemed to fix the issue:


clearValue : function()
{
this.values = [];
this.value = '';
this.displayValues = [];

var len = this.hiddenFields.length;
for (var i=0; i<len; i++)
{
Ext.get(this.hiddenFields[i]).remove();
}
this.hiddenFields = [];

//
this.setRawValue('');
this.lastSelectionText = '';
this.applyEmptyText();
},

Zyclops
6 Aug 2008, 9:39 PM
I've got an issue with being able to type and delete the underlying default (click to type text)

Steps to replicate:
Create a combobox with default text (i.e. Search ... )
1.) Search for something in the autocomplete
2.) Click to select it
3.) Use the backspace key until you delete the entire selection and keep going, it will then start deleting any default text specified

jerrybrown5
14 Sep 2008, 8:47 PM
I found that clearValue was not actually removing the hidden form fields. Calling an Ext.get seemed to fix the issue:


clearValue : function()
{
...
for (var i=0; i<len; i++)
{
Ext.fly(this.hiddenFields[i]).remove(); /*not Ext.get*/
}
...
},


Thanks for the bug fix; however, you should always use Ext.fly when you are not saving the component. It is designed to be slightly faster.

Cheers,
Jerry

gelleneu
16 Sep 2008, 12:09 AM
Yesterday I tested this Extension the first time. I decided to change from BoxSelect because this is already buggy.

But I have some problems:

1.) I only get the texts as Value from the field - like this: tags: 'news, politics, sport, '.
But I want to have the underlying id numbers from the store: '12, 17, 45'...

I have a normal combo-typical config, with valueField='id' and displayField='text'.

Where are the Id's stored?

Scorpie
16 Sep 2008, 1:16 AM
Very nice indeed!

jerrybrown5
16 Sep 2008, 8:55 PM
But I have some problems:

1.) I only get the texts as Value from the field - like this: tags: 'news, politics, sport, '.
But I want to have the underlying id numbers from the store: '12, 17, 45'...

I have a normal combo-typical config, with valueField='id' and displayField='text'.

Where are the Id's stored?

I recently gave Richard an update, that among other things, provides an appropriate getValue override to do just this. I believe he is going to post it on the ext-ux repository within a few days.

http://extjs-ux.org/docs/


Cheers,
Jerry

mfiandesio
3 Dec 2008, 3:57 AM
Very nice extension.
I am facing some problems when i want to insert a value that is not in the store.
Does anyone tried that?
Thank you
Matteo

cool.akshay
25 Apr 2009, 4:22 AM
please do help in Multiselect, i have question to show selected field in multiselect on edit, have a data which is come from JsonReader, want to apply this to multiselect control to show respective field selected.........

Thanks in advanced............

Zyclops
17 Aug 2009, 10:54 PM
The current issue i have are:
* Field Label doesn't seem to work
* It seems to require the use of the id field for other things than just specify a reference to the object

We've used this in about 30 places and so fixed
* Loading Issues (covering different scenarios)
* Set the delimiter
* Fixes to clearValue



Ext.namespace('Ext.ux');

/**
* Ext.ux.MultiSelectTextField Extension Class
*
* @author Richard Livsey
* @version 0.0.3
*
* @class Ext.ux.MultiSelectTextField
* @extends Ext.form.ComboBox
* @constructor
* @param {Object} config Configuration options
*/
Ext.ux.MultiSelectTextField = function(config) {

// init data
this.values = [];
this.displayValues = [];
this.hiddenFields = [];
if (config.delimiter === undefined) {
this.delimiter = ',';
} else {
this.delimiter = config.delimiter;
}
Ext.ux.MultiSelectTextField.superclass.constructor.call(this, config);
};

Ext.extend(Ext.ux.MultiSelectTextField, Ext.form.ComboBox, {

/**
* @cfg {Boolean} hideTrigger True to hide the trigger element and display only the base text field (defaults to true)
*/
hideTrigger: true,

// private
values: [],

// private
displayValues: [],

// private
hiddenFields: [],

/**
* Add a value
* @param {String} value The value to match
* @param {Boolean} defer True to not update the field
*/
addValue: function(v, defer)
{
var r = this.findRecord(this.valueField || this.displayField, v);
if (!r)
{
return;
}

var value = r.data[this.valueField];
var text = r.data[this.displayField];

// only if the value hasn't already been added
if (this.values.indexOf(value) == -1)
{
var hidden = this.el.insertSibling(
{ tag:'input',
type:'hidden',
value: value,
name: (this.hiddenName || this.name)},
'before', true);

this.values.push(value);
this.displayValues.push(text);
this.hiddenFields.push(hidden);
}

if (!defer)
{
this.updateDisplay();
}
},

/**
* Remove a value
* @param {String} value The value to match
*/
removeValue: function(v, defer)
{
var r = this.findRecord(this.valueField || this.displayField, v);
if (!r)
{
return;
}

var value = r.data[this.valueField];
var text = r.data[this.displayField];

var idx = this.values.indexOf(value);
if (idx == -1)
{
return;
}

this.removeItemAtIndex(idx);

if (!defer)
{
this.updateDisplay();
}
},

// private
removeItemAtIndex: function(idx, defer)
{
var field = Ext.get(this.hiddenFields[idx]);
field.remove();

this.values[idx] = null;
this.displayValues[idx] = null;
this.hiddenFields[idx] = null;

if (!defer)
{
this.cleanData();
}
},

// private
cleanData: function()
{
this.values = this.cleanArray(this.values);
this.displayValues = this.cleanArray(this.displayValues);
this.hiddenFields = this.cleanArray(this.hiddenFields);
},

// private
cleanArray: function(arr)
{
var cleaned = [];
var len = arr.length;
for (var i=0; i<len; i++)
{
if (arr[i])
{
cleaned.push(arr[i]);
}
}
return cleaned;
},

/**
* Sets the specified value(s) into the field.
* If the value(s) finds a match, they will be added to the field.
* @param {Mixed} value The value to match
* @param {Mixed}
*
* If you pass in an array of hashes, then we will assume this is to populate the store. It is
* important that the store has the fields defined for the population (i.e. in a JsonStore configuration
* you will have to add them.
*
* [
* {id: '12345', name: 'Fred Joans'},
* {id: '15689', name: 'Jeremy Watson'}
* ]
*
* This will then populate the store with those values and then iterate through each item and call set value
* on whatever the fields valueField is set too
*/
setValue: function(v) {
this.clearValue();

if (!(v instanceof Array)) {
v = v.split(this.delimiter);
}

var len = v.length;

// Loads the store if the passed object is an array of hashes
if (typeof(v[0]) === 'object') {
this.store.loadData(v);
}

for (var i=0; i<len; i++) {
//Either searches through the object calling add value on the current valueField
//or just sets addValue
if (typeof(v) === 'object') {
this.addValue(v[i][this.valueField], true);
} else {
this.addValue(v[i], true);
}
}

this.updateDisplay();
},

// private
onBlur: function()
{
this.updateDisplay();
Ext.form.ComboBox.superclass.onBlur.call(this);
},

// private
updateDisplay: function()
{
var text = this.displayValues.join(this.delimiter + ' ');
if (text.trim() !== '')
{
text += this.delimiter + ' ';
}

Ext.form.ComboBox.superclass.setValue.call(this, text);
},

/**
* Clears any value(s) currently set in the field
*/
clearValue : function()
{
this.values = [];
this.value = '';
this.displayValues = [];

var len = this.hiddenFields.length;
for (var i=0; i<len; i++)
{
Ext.get(this.hiddenFields[i]).remove();
}
this.hiddenFields = [];

//
this.setRawValue('');
this.lastSelectionText = '';
this.applyEmptyText();
},

// private
onSelect : function(record, index)
{
if (this.fireEvent('beforeselect', this, record, index) !== false){
this.addValue(record.data[this.valueField || this.displayField]);
this.collapse();
this.fireEvent('select', this, record, index);
}
},

// private
onRender : function(ct, position)
{
Ext.form.ComboBox.superclass.onRender.call(this, ct, position);

// prevent input submission
this.el.dom.removeAttribute('name');

if (Ext.isGecko)
{
this.el.dom.setAttribute('autocomplete', 'off');
}

if (!this.lazyInit)
{
this.initList();
}
else
{
this.on('focus', this.initList, this, {single: true});
}

if (!this.editable)
{
this.editable = true;
this.setEditable(false);
}
},

// private
getLastValue: function()
{
var parts = this.getRawValue().split(this.delimiter);
return parts[parts.length - 1].trim();
},

// private
// Implements the default empty TriggerField.onTriggerClick function
onTriggerClick : function()
{
if (this.disabled)
{
return;
}

if (this.isExpanded())
{
this.collapse();
this.el.focus();
}
else
{
this.onFocus({});
if (this.triggerAction == 'all')
{
this.doQuery(this.allQuery, true);
}
else
{
this.doQuery(this.getLastValue());
}
this.el.focus();
}
},

//private
initQuery : function()
{
var val = this.getLastValue();
if (val.trim() !== '')
{
this.doQuery(val);
}
this.removeOld();
},

// private
// clean out the data from ones you've deleted
removeOld: function()
{
var str = this.getRawValue();
var len = this.displayValues.length;
// sorted by length descending
var items = this.displayValues.slice().sort(function(x,y){ return y.length - x.length; });
var removed = false;

for (var i=0; i<len; i++)
{
var val = items[i];
if (str.indexOf(val) == -1)
{
removed = true;
this.removeItemAtIndex(this.displayValues.indexOf(val), true);
}
}

if (removed)
{
this.cleanData();
this.updateDisplay();
}
},

// private
fieldParts: function()
{
var parts = this.getRawValue().split(this.delimiter);
var len = parts.length;
for (var i=0; i<len; i++)
{
parts[i] = parts[i].trim();
}
return parts;
},

//private
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.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();
}
//this.el.focus();
},

validateValue:function(value)
{
if (this.values.length === 0 && !this.allowBlank)
{
this.markInvalid(this.blankText);
return false;
}

if (this.values.length < this.minLength)
{
this.markInvalid(String.format(this.minLengthText, this.minLength));
return false;
}

if (this.values.length > this.maxLength)
{
this.markInvalid(String.format(this.maxLengthText, this.maxLength));
return false;
}

return true;
}

});

Ext.reg('multitextfield', Ext.ux.MultiSelectTextField);

cmendez21
27 Aug 2009, 8:49 AM
:-? I have an issue on 2.3.0 and 3.x versions

when i wrote the values (select multiple values) the first time the field values were ok then i leave the field and the values still ok but if wanted to add a new value to that selection the field only leave the new value

Also If i get again the focus and leave without any changes then the text and values were cleared.

I assume that was the something to do on the combo source code beforeBlur function

Any ideas ? :-/

cause for the moment I added this code



beforeBlur:function(){ },


to override the combo beforeBlur function

thrillhouse
13 Jul 2010, 4:38 PM
:-? I have an issue on 2.3.0 and 3.x versions

when i wrote the values (select multiple values) the first time the field values were ok then i leave the field and the values still ok but if wanted to add a new value to that selection the field only leave the new value

Also If i get again the focus and leave without any changes then the text and values were cleared.

I assume that was the something to do on the combo source code beforeBlur function

Any ideas ? :-/

cause for the moment I added this code



beforeBlur:function(){ },
to override the combo beforeBlur function

I believe I'm experiencing the same issue running 2.3. Overriding the beforeBlur function appears to yield the desired result. Has anyone else rolled with this override as a solution and have any wisdom to offer? cmendez21 has this worked ok for you?

Does anyone know if this plugin is still being actively developed?

cmendez21
13 Jul 2010, 4:48 PM
I believe I'm experiencing the same issue running 2.3. Overriding the beforeBlur function appears to yield the desired result. Has anyone else rolled with this override as a solution and have any wisdom to offer? cmendez21 has this worked ok for you?

Does anyone know if this plugin is still being actively developed?

yes indeed has worked out for me the only issue it's i have to let the store (if remote) finish loading before setting any value except from that its working fine (im using it on 3 production environments right now, which 2 of them are quite big (200+ users))

thrillhouse
13 Jul 2010, 5:35 PM
Thanks buddy, I wasted 4 hours on that problem till I found your thread.