How to prevent duplicate with Bootstrap Tokenfield

2020-05-27 09:33发布

问题:

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

回答1:

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



回答2:

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.



回答3:

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


回答4:

$('.tokenfield').on('tokenfield:createtoken', function (event) {
    var existingTokens = $(this).tokenfield('getTokens');
    $.each(existingTokens, function(index, token) {
        if (token.value === event.attrs.value)
            event.preventDefault();
    });
});