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 mul
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.
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/
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.
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
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..
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.
This is an excellent replacement for select boxes:
http://ivaynberg.github.io/select2/
(If you use the multi-value version.)