PDA

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


RLivsey
11-29-2007, 04: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
11-29-2007, 05:33 PM
Woa!! Awesome uc! :)

galdaka
11-29-2007, 05: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
11-29-2007, 07:50 PM
Ah good catch, I'll have a go at fixing this up in the am, shouldn't be too difficult!

Cheers.

apaa
11-30-2007, 12:57 AM
I post field that was used 'hiddenName',but data shows displayField not valueField

JeffHowden
11-30-2007, 04: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
11-30-2007, 07: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
11-30-2007, 07: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
11-30-2007, 08:51 AM
kudos to you! Excellent job. A nice application for this would be if an app supports tagging ala Del.icio.us

wm003
11-30-2007, 09:17 AM
WOW!!! Great Widget! Very good work! :D

xwisdom
11-30-2007, 10: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
11-30-2007, 11: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
11-30-2007, 01:35 PM
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
11-30-2007, 03: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
11-30-2007, 05: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
11-30-2007, 05: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
11-30-2007, 08: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
12-01-2007, 05:23 AM
Good work!
Sorry for my English,

is possible modify plugin for textarea?

Thanks in advance,

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

MindPatterns
12-02-2007, 09:22 AM
This is great, I'll use it whenever I need to tag something in my application! ~o)

Bharani
12-03-2007, 05:08 AM
This is exactly what i needed to support multi valued fields. Thanks B)

dawesi
12-05-2007, 01:23 AM
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
12-10-2007, 05: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
12-30-2007, 09: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
03-31-2008, 02:24 AM
Server down? Damn then i need it, it gone :(

msuresh
04-04-2008, 08:23 PM
Unable to download the plugin. Can you upload the plugin.

ext_fan
04-05-2008, 02:01 AM
Hi,
the article matches with the exact requirement of mine..please upload the plugin again in valid site.

cmendez21
04-05-2008, 11: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
04-06-2008, 03:59 PM
Could anyone help us uploading the code

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

cmendez21
04-07-2008, 04:20 AM
wm003, You give this extension more life
Thanks a lot :D

symfony
04-17-2008, 08:29 AM
Is anybody here, who have the latest version?

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

RLivsey
06-24-2008, 05: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
07-08-2008, 10: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
07-16-2008, 03:33 AM
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
07-17-2008, 12:21 AM
I have the lates version, which is version 0.0.3

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

Zyclops
08-06-2008, 07: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
08-07-2008, 01:39 AM
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
09-15-2008, 12:47 AM
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
09-16-2008, 04: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
09-16-2008, 05:16 AM
Very nice indeed!

jerrybrown5
09-17-2008, 12:55 AM
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
12-03-2008, 06: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
04-25-2009, 08: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
08-18-2009, 02:54 AM
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
08-27-2009, 12:49 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