I am trying to implement Bootstrap Tokenfield with Jquery Ui autocomplete and so far i was able to do that except the fact that i am not able to prevent duplicates in the input field, so, unfortunately my user can choose the same value twice.
In my search i have found that Bootstrap Tokenfield has a way of preventing duplicate. However I do not know how to apply to my code because it looks to me that it goes with Twitter typeahead and not Jquery Ui.
How can i prevent duplicate with Bootstrap TokenField Using Jquery Ui autocomplete ?
This is my Bootstrap TokenField code based on jquery ui autocomplete
$('.tokenfield').tokenfield({
autocomplete: {
source: [
{
"id": "1",
"value": "Ferdine Faithfull"
},
{
"id": "2",
"value": "John Carta"
},
{
"id": "3",
"value": "Mezane Smith"
}
],
delay: 100
},
showAutocompleteOnFocus: true
});
And below is what i have found on Github to prevent duplicate though i think it is for Typeahead
$('#my-tokenfield').on('tokenfield:createtoken', function (event) {
var existingTokens = $(this).tokenfield('getTokens');
$.each(existingTokens, function(index, token) {
if (token.value === event.attrs.value)
event.preventDefault();
});
});
I think you've done it all, all you are left to do is to replace the class
So after the first code, instead of the second code write
$('.tokenfield').on('tokenfield:createtoken', function (event) {
var existingTokens = $(this).tokenfield('getTokens');
$.each(existingTokens, function(index, token) {
if (token.value === event.attrs.value)
event.preventDefault();
});
});
The difference here is your class that has to be applied and it works both for Twitter Typeahead and Jquery Ui
This prevents listing items that have already been added as tokens:
$('.tokenfield').on('tokenfield:createdtoken tokenfield:removedtoken', function (event) {
var field = $(this);
var currentTokens = field.tokenfield('getTokens');
var originalSource = field.data('bs.tokenfield').options.autocomplete.source;
var newSource = originalSource.slice(); //clone original autocomplete source
for (var i = newSource.length - 1; i >= 0; i--) {
for (var j = currentTokens.length - 1; j >= 0; j--) {
if (JSON.stringify(currentTokens[j].label) == JSON.stringify(newSource[i])
|| JSON.stringify(currentTokens[j]) == JSON.stringify(newSource[i]) ) {
//remove the token from the newSource
var index = newSource.indexOf(newSource[i]);
if (index > -1)
newSource.splice(index, 1);
};
};
};
//update source
field.data('bs.tokenfield').$input.autocomplete({source: newSource})
})
This function is called after token is created or deleted to update the list. It uses JSON.stringify() to compare objects, and does the comparison for string objects and for {value: "foo", label: "bar"} source objects.
@Javier Your solution work good but sometimes it gets buggy and add twice the token! Have you got idea for this behaviour?
PS After seen the documentation i found the solution. Both event handling are needed. Because events are fired before and after creation/edit/remove of tokens.
So you need this to prevent the add (before create event)
$('#tokenfield').on('tokenfield:createtoken', function (event) {
var existingTokens = $(this).tokenfield('getTokens');
//check the capitalized version
event.attrs.value = capitalizeFirstLetter(event.attrs.value);
$.each(existingTokens, function(index, token) {
if (token.value === event.attrs.value) {
event.preventDefault();
return false;
}
});
});
And this other too, as you suggested, for the source-list (after create event)
$('#tokenfield').on('tokenfield:createdtoken tokenfield:removedtoken', function (event) {
var field = $(this);
var currentTokens = field.tokenfield('getTokens').map(function(i){return i.value});
var originalSource = field.data('bs.tokenfield').options.autocomplete.source;
var newSource = [];
for (var i = 0; i<originalSource.length; i++) {
if(currentTokens.indexOf(originalSource[i])==-1){
newSource.push(originalSource[i]);
}
};
//update source
field.data('bs.tokenfield').$input.autocomplete({source: newSource});
//empty the input field
$(".tokenfield.form-control").find("input.token-input").val("");
});
NOTE: I changed the "check loop", (double for was overkilling), and added a check to avoid "capitalized" matching, just in case you need it.
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1).toLowerCase();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With