I hope my question makes sense - wasn't sure on the best way to describe this. I have a grouped Select2 select form input something like this:
So you start typing App
and of course you get Apples
from the Select2 dropdown. If you type veg
you get Vegemite
and the Vegetables
group heading but all the options are hidden. I would like to keep all the group options visible if a search term matches the group heading.
I did some digging in the select2 source code and I think it's actually easy but I could be wrong and if I am right I am stuck on how to make it work. Here is the source code: https://github.com/select2/select2/blob/81a4a68b113e0d3e0fb1d0f8b1c33ae1b48ba04f/src/js/select2/defaults.js:
and a Gist I created vs. trying to paste it in here:
https://gist.github.com/jasper502/40b810e55b2195476342
I switched the order of the code and made some slight variable name changes to reflect this. I think this would keep the option group open. I tried to make a custom matcher based on this (see my Gist) but I was stuck at the point where it calls DIACRITICS
:
After some Googling I realized that this is replacing accented characters which I know I don't have so I removed that portion.
Now my matcher fails with TypeError: data.indexOf is not a function. (In 'data.indexOf(term)', 'data.indexOf' is undefined)
errors in my browser.
I am sure I am very close here but I am a bit over my head and beyond my experience and/or skill level to finish this off. Any pointers or ideas would be appreciated.
UPDATE
Here is a JSfiddle with what I am working with:
What I gather from your question is you want to be able to show option
s for selection when there's a match in either the option
text OR the option
's parent optgroup
value attribute.
This is relatively straightforward: Mainly, look at both of the values and if either matches, return true
using Select2's matcher
option:
(Note: Using Select2 v3.5.4.)
(function() {
function matcher(term, text, opt) {
var $option = $(opt),
$optgroup = $option.parent('optgroup'),
label = $optgroup.attr('label');
term = term.toUpperCase();
text = text.toUpperCase();
if (text.indexOf(term) > -1
|| (label !== undefined
&& label.toUpperCase().indexOf(term) > -1)) {
return true;
}
return false;
}
$(".select2").select2({
matcher: matcher
});
})();
https://jsfiddle.net/xfw4tmbx/2/
v4.* and above changed the term
and text
to a more complex object, so it'll be slightly different, but the main concept is the same. As you can see, all I'm doing is using jQuery to select up to the option
's parent if it's an optgroup
element and including that in the matcher
check.
Also, an optgroup
will display if any of it's children are shown, so you only have to worry about displaying one or more of the option
's, and not actually "show" the optgroup
by manually showing it.
If you have a different requirement, please provide a (working/non-working?) demonstration fiddle showing what you have where we can actually run it.
EDIT
Select2 custom matching changed significantly with the 4.0 release. Here is a custom matcher that was posted to this GitHub issue. It is reproduced as-is below for completeness.
Notice that it's calling itself for child elements (the option
elements within the optgroup
elements), so modelMatcher()
is running against both the optgroup
and the option
elements, but the combined set is returned after removing the optgroup
and option
elements that don't match. In the version above, you got every option
element and simply returned true/false if you wanted it (and the parent) displayed. Not that much more complicated, but you do have to think about it a little bit more.
(function() {
function modelMatcher(params, data) {
data.parentText = data.parentText || "";
// Always return the object if there is nothing to compare
if ($.trim(params.term) === '') {
return data;
}
// Do a recursive check for options with children
if (data.children && data.children.length > 0) {
// Clone the data object if there are children
// This is required as we modify the object to remove any non-matches
var match = $.extend(true, {}, data);
// Check each child of the option
for (var c = data.children.length - 1; c >= 0; c--) {
var child = data.children[c];
child.parentText += data.parentText + " " + data.text;
var matches = modelMatcher(params, child);
// If there wasn't a match, remove the object in the array
if (matches == null) {
match.children.splice(c, 1);
}
}
// If any children matched, return the new object
if (match.children.length > 0) {
return match;
}
// If there were no matching children, check just the plain object
return modelMatcher(params, match);
}
// If the typed-in term matches the text of this term, or the text from any
// parent term, then it's a match.
var original = (data.parentText + ' ' + data.text).toUpperCase();
var term = params.term.toUpperCase();
// Check if the text contains the term
if (original.indexOf(term) > -1) {
return data;
}
// If it doesn't contain the term, don't return anything
return null;
}
$(".select2").select2({
matcher: modelMatcher
});
})();