Twitter bootstrap typeahead multiple values?

57,067

Solution 1

Edit There already was a pull about that : https://github.com/twitter/bootstrap/pull/2007


You can approach the desired behavior by using a proxy for the typeahead : Demo (jsfiddle)

var $myTextarea = $('#myTextarea');

$('.typeahead').typeahead({
    source: source,
    updater: function(item) {
        $myTextarea.append(item, ' ');
        return '';
    }
});

I think the updater method is meant for this kind of thing, you just return what will be displayed.


Or if you really want everything to be in the same input element, you would have to override more methods so that it only matches the currently-typed element : Demo (jsfiddle)

function extractor(query) {
    var result = /([^,]+)$/.exec(query);
    if(result && result[1])
        return result[1].trim();
    return '';
}

$('.typeahead').typeahead({
    source: source,
    updater: function(item) {
        return this.$element.val().replace(/[^,]*$/,'')+item+',';
    },
    matcher: function (item) {
      var tquery = extractor(this.query);
      if(!tquery) return false;
      return ~item.toLowerCase().indexOf(tquery.toLowerCase())
    },
    highlighter: function (item) {
      var query = extractor(this.query).replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
      return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
        return '<strong>' + match + '</strong>'
      })
    }
});

This one is not idiot proof, because you have to type at the end, after the special character.

Solution 2

This is an excellent replacement for select boxes:

http://ivaynberg.github.io/select2/

(If you use the multi-value version.)

Solution 3

The top answer doesn't seem to work anymore with the latest typeahead, so I offer the following.

FIDDLE

function MultiTypeahead(id, data, trigger, vertAdjustMenu)
{
    trigger = (undefined !== trigger) ? trigger : '';
    var validChars = /^[a-zA-Z]+$/;


    function extractor(query)
    {
        var result = (new RegExp('([^,; \r\n]+)$')).exec(query);
        if(result && result[1])
            return result[1].trim();
        return '';
    }

    var lastUpper = false;
    function strMatcher(id, strs) 
    {
        return function findMatches(q, sync, async) 
        {
            var pos = $(id).caret('pos');
            q = (0 < pos) ? extractor(q.substring(0, pos)) : '';

            if (q.length <= trigger.length)
                return;

            if (trigger.length)
            {
                if(trigger != q.substr(0, trigger.length))
                    return;

                q = q.substr(trigger.length);
            }

            if (!q.match(validChars))
                return;

            var firstChar = q.substr(0, 1);
            lastUpper = (firstChar === firstChar.toUpperCase() && firstChar !== firstChar.toLowerCase());

            var cpos = $(id).caret('position');
            $(id).parent().find('.tt-menu').css('left', cpos.left + 'px');
            if (vertAdjustMenu)
                $(id).parent().find('.tt-menu').css('top', (cpos.top + cpos.height) + 'px');

            var matches = [];
            var matches = [], substrRegex = new RegExp(q, 'i');
            $.each(strs, function(i, str) 
            {
                if (str.length > q.length && substrRegex.test(str))
                    matches.push(str);
            });

            if (!matches.length)
                return;

            sync(matches);
        };
    };

    var lastVal = '';
    var lastPos = 0;
    function beforeReplace(event, data)
    {
        lastVal = $(id).val();
        lastPos = $(id).caret('pos');
        return true;
    }

    function onReplace(event, data)
    {            
        if (!data || !data.length)
            return;

        if (!lastVal.length)
            return;

        var root = lastVal.substr(0, lastPos);
        var post = lastVal.substr(lastPos);

        var typed = extractor(root);
        if (!lastUpper && typed.length >= root.length && 0 >= post.length)
            return;

        var str = root.substr(0, root.length - typed.length);

        str += lastUpper ? (data.substr(0, 1).toUpperCase() + data.substr(1)) : data;
        var cursorPos = str.length;

        str += post;

        $(id).val(str);
        $(id).caret('pos', cursorPos);      
    }

    this.typeahead = 
        $(id).typeahead({hint: false, highlight: false}, {'limit': 5, 'source': strMatcher(id, data)})
                .on('typeahead:beforeselect', beforeReplace)
                .on('typeahead:beforeautocomplete', beforeReplace)
                .on('typeahead:beforecursorchange', beforeReplace)
                .on('typeahead:selected', function(event,data){setTimeout(function(){ onReplace(event, data); }, 0);})
                .on('typeahead:autocompleted', onReplace)
                .on('typeahead:cursorchange', onReplace)
                ;
}

EDIT: Realizing the previous code had too much extra stuff, I narrowed it down to a minimal working example.

This is what was previously posted..

Solution 4

I'm late to the party, but this is what I came up with for Bootstrap v4. It also requires d3 (as I am not so familiar with jQuery).

You can see an example running at http://sumneuron.gitlab.io/multitags/ enter image description here

It has some useful features, is written in a clearly, and should get anyone who finds this off to a good start. Especially if you were ever curious as to how tagging is implemented.

enter image description here

Code is available at https://gitlab.com/SumNeuron/multitags/pipelines

The logic is roughly as follows:

if keydown in [enter, ","]:
    // logic of function "doneTyping" 
    text = parse(text) // get text from textarea and grab latest value
    if text is valid:
        renderTag(text) // put the text in a tag element
        updateHiddenForm(text) // update the hidden form to include the tag
    else:
        notifyUserOfInvalidTag(text) // alert user
else:
    // logic of function "stillTyping" 
    suggest = bloodhoundSearch(text) // use twitter typeahead
    updateSuggestionBox(suggest) // display results from typeahead

Solution 5

I guess you could edit the plugin to allow multiple selections (just don't close the drop-down menu) and add the selected values separated by comma. The only problem I see is that you don't know when to close the drop-down.

Share:
57,067

Related videos on Youtube

denislexic
Author by

denislexic

Started learning programming on youtube and forums. Now I am addicted to it.

Updated on July 09, 2022

Comments

  • denislexic
    denislexic almost 2 years

    I'm using Twitter Bootstrap with Jquery. I want to use the TYPEAHEAD function for a textarea, which I got to work super easily. But I also need it to allow multiple selection.

    By that I mean, after I selected one word from the autocomplete, it takes me back to textarea with an extra space afterwards and then if I start typing again, it offers me to do it again.

    Here is a JS bin: http://jsbin.com/ewubuk/1/edit (Nothing special inside).

    Is there an easy solution to allow multiple selection with typeahead? If yes, how?

    Thanks in advance.

  • Glorious Kale
    Glorious Kale about 11 years
    Hmm this seemed like a great plan, but it doesn't work for bootstrap 2.3.1 anymore. At least for me. updater method doesn't do anything for me, but the typehead works as single.
  • Glorious Kale
    Glorious Kale about 11 years
    Ok, I missed the HTML part. I put <input type="text" data-provide="typeahead" /> instead of <input type="text" class="typeahead" />
  • Sherbrow
    Sherbrow about 11 years
    @IvanIvković If you encounter problems, you should try in something like jsfiddle and ask a new question about it (referencing this question). But I see you solved it, so that's great !
  • Glorious Kale
    Glorious Kale about 11 years
    Hmm strange I still can't answer any questions even dogh I've contributed. :/ Thanks. :)
  • andrewtweber
    andrewtweber about 11 years
    I am experiencing a problem with this in both the jsfiddle and my own code. It only seems to work if you start typing in lowercase. If you type a capital letter first, nothing is returned.
  • andrewtweber
    andrewtweber about 11 years
    Okay I think the solution was in matcher, return ~item.toLowerCase().indexOf(tquery.toLowerCase())
  • Sherbrow
    Sherbrow over 10 years
    @AtaS. This plugin has been removed from twbs. Check the version you are using, and if you are using typeahead.js you should look for doc/questions related to that only.
  • rilar
    rilar about 10 years
    Any idea how to do this with Strap Angular typeahead? mgcrea.github.io/angular-strap/##typeaheads
  • lucentx
    lucentx over 9 years
    Is there a way to make this work like you can select multiple items by doing ctrl + click?
  • andrewtweber
    andrewtweber over 9 years
    Just a note, the authors of typeahead have stated that they never plan to add this feature. They actually recommend using Select2 in this case as well. github.com/twitter/typeahead.js/issues/135
  • Sherbrow
    Sherbrow about 6 years
    @SumNeuron that part of the library was already on its way to disappear, if you want the same with newer versions of the library, you might have to use another one or reimplement it yourself
  • SumNeuron
    SumNeuron about 6 years
    @Sherbrow ok, I just wanted to confirm prior to putting effort in. Won't be too hard, but why reinvent the wheel
  • SumNeuron
    SumNeuron about 6 years
    +1 for updating old code, although it is easy enough to do. I know why you are using regex but it does obfuscate editing your code for others. The only other things - that in my eyes would improve your answer - is new lines instead of same line and not using @.
  • SumNeuron
    SumNeuron about 6 years
    @Sherbrow it would be cool - time permitting - if you could take a look and give some feedback stackoverflow.com/a/49601565/5623899