diff --git a/select2.css b/select2.css
index de67c8e5..883af077 100755
--- a/select2.css
+++ b/select2.css
@@ -236,13 +236,25 @@ Version: @@ver@@ Timestamp: @@timestamp@@
overflow-y: auto;
max-height: 200px;
}
+
+.select2-results ul.select2-result-sub {
+ margin: 0 0 0 15px;
+}
+
.select2-results li {
+ list-style: none;
+ display: list-item;
+}
+
+.select2-results li.select2-result-with-children > .select2-result-label {
+ font-weight: bold;
+}
+
+.select2-results .select2-result-label {
line-height: 80%;
padding: 7px 7px 8px;
margin: 0;
- list-style: none;
cursor: pointer;
- display: list-item;
}
.select2-results .select2-highlighted {
diff --git a/select2.js b/select2.js
index 323b63ab..22b5807c 100755
--- a/select2.js
+++ b/select2.js
@@ -20,7 +20,7 @@
return;
}
- var KEY, AbstractSelect2, SingleSelect2, MultiSelect2;
+ var KEY, AbstractSelect2, SingleSelect2, MultiSelect2, uid = 1;;
KEY = {
TAB: 9,
@@ -475,8 +475,46 @@
}
opts = $.extend({}, {
- formatResult: function (data) { return data.text; },
- formatSelection: function (data) { return data.text; },
+ formatList: function(results) {
+ var proc = function(results, depth) {
+ depth = depth || 0;
+ var parts = [];
+
+ $.each(results, function() {
+ var result = this;
+ parts.push('
');
+
+ parts.push('' + result.text + '
');
+
+ if (result.children && result.children.length) {
+ parts.push('');
+ parts.push(proc(result.children, depth + 1))
+ parts.push('
');
+ }
+
+ parts.push('');
+ });
+
+
+ return parts.join('');
+ };
+
+ return proc(results, 0);
+ },
+ formatSelection: function (data) {
+ if (data.fullText) {
+ return data.fullText;
+ } else {
+ return data.text;
+ }
+ },
formatNoMatches: function () { return "No matches found"; },
formatInputTooShort: function (input, min) { return "Please enter " + (min - input.length) + " more characters"; },
minimumResultsForSearch: 0,
@@ -494,19 +532,80 @@
if (select) {
opts.query = this.bind(function (query) {
- var data = {results: [], more: false},
+ var data = {results: [], map: {}, more: false},
term = query.term,
+ idx = 0,
placeholder = this.getPlaceholder();
- element.find("option").each(function (i) {
- var e = $(this),
- text = e.text();
- if (i === 0 && placeholder !== undefined && text === "") return true;
+ element.find("> *").each(function() {
+ var el = $(this);
- if (query.matcher(term, text)) {
- data.results.push({id: e.attr("value"), text: text});
+ if (el.is('optgroup')) {
+ var item = {
+ id: 'select2-group-' + (uid++),
+ uid: uid++,
+ text: el.attr('label'),
+ unselectable: true,
+ children: []
+ };
+ data.map[item.uid] = item;
+
+ el.find('> option').each(function() {
+ var sub = {
+ id: $(this).attr('value'),
+ uid: uid++,
+ text: $(this).text(),
+ unselectable: false,
+ fullText: el.attr('label') + ' > ' + $(this).text(),
+ children: []
+ };
+
+ data.map[sub.uid] = sub;
+ item.children.push(sub);
+ });
+
+ data.results.push(item);
+ } else {
+ var item = {
+ id: $(this).attr('value'),
+ uid: uid++,
+ text: $(this).text(),
+ unselectable: false,
+ children: []
+ };
+
+ data.map[item.uid] = item;
+ data.results.push(item);
}
});
+
+ if (term !== "") {
+ var filterDeep = function(items, depth) {
+ var filtered = [];
+ for (var itemIdx = 0; itemIdx < items.length; itemIdx++) {
+ var newItem = $.extend(true, [], items[itemIdx]);
+ if (newItem.children) {
+ newItem.children = filterDeep(newItem.children);
+ }
+
+ var isMatch = false;
+ if (newItem.children && newItem.children.length) {
+ isMatch = true;
+ } else if (!newItem.unselectable && query.matcher(term, newItem.text)) {
+ isMatch = true;
+ }
+
+ if (isMatch) {
+ filtered.push(newItem);
+ }
+ }
+
+ return filtered;
+ }
+
+ data.results = filterDeep(data.results);
+ }
+
query.callback(data);
});
// this is needed because inside val() we construct choices from options and there id is hardcoded
@@ -622,7 +721,7 @@
ensureHighlightVisible: function () {
var results = this.results, children, index, child, hb, rb, y, more;
- children = results.children(".select2-result");
+ children = results.find(".select2-result");
index = this.highlight();
if (index < 0) return;
@@ -652,7 +751,7 @@
},
moveHighlight: function (delta) {
- var choices = this.results.children(".select2-result"),
+ var choices = this.results.find(".select2-result"),
index = this.highlight();
while (index > -1 && index < choices.length) {
@@ -665,17 +764,21 @@
},
highlight: function (index) {
- var choices = this.results.children(".select2-result");
+ var choices = this.results.find(".select2-result .select2-result-label");
if (arguments.length === 0) {
return indexOf(choices.filter(".select2-highlighted")[0], choices.get());
}
- choices.removeClass("select2-highlighted");
-
if (index >= choices.length) index = choices.length - 1;
if (index < 0) index = 0;
+ if ($(choices[index]).parent().is('.select2-result-unselectable')) {
+ return;
+ }
+
+ choices.removeClass("select2-highlighted");
+
$(choices[index]).addClass("select2-highlighted");
this.ensureHighlightVisible();
@@ -684,8 +787,9 @@
highlightUnderEvent: function (event) {
var el = $(event.target).closest(".select2-result");
+ var choices = this.results.find('.select2-result');
if (el.length > 0) {
- this.highlight(el.index());
+ this.highlight(choices.index(el));
}
},
@@ -708,19 +812,15 @@
context: self.context,
matcher: self.opts.matcher,
callback: this.bind(function (data) {
- var parts = [], self = this;
- $(data.results).each(function () {
- parts.push("");
- parts.push(self.opts.formatResult(this));
- parts.push("");
- });
- more.before(parts.join(""));
- results.find(".select2-result").each(function (i) {
+ var self = this;
+ var htmlResult = self.opts.formatList(data.results);
+ more.before(htmlResult);
+ results.find(".select2-result").each(function () {
var e = $(this);
if (e.data("select2-data") !== undefined) {
offset = i;
} else {
- e.data("select2-data", data.results[i - offset - 1]);
+ e.data("select2-data", data.map[e.data('select2-uid')]);
}
});
if (data.more) {
@@ -785,19 +885,15 @@
return;
}
- $(data.results).each(function () {
- parts.push("");
- parts.push(opts.formatResult(this));
- parts.push("");
- });
+ var htmlResult = self.opts.formatList(data.results);
if (data.more === true) {
- parts.push("Loading more results...");
+ htmlResult += "Loading more results...";
}
- render(parts.join(""));
- results.children(".select2-result").each(function (i) {
- var d = data.results[i];
+ render(htmlResult);
+ results.find(".select2-result").each(function () {
+ var d = data.map[$(this).data('select2-uid')];
$(this).data("select2-data", d);
});
this.postprocessResults(data, initial);
@@ -830,7 +926,7 @@
},
selectHighlighted: function () {
- var data = this.results.find(".select2-highlighted:not(.select2-disabled)").data("select2-data");
+ var data = this.results.find(".select2-highlighted").not(".select2-disabled").closest('.select2-result').not('.select2-result-unselectable').data("select2-data");
if (data) {
this.onSelect(data);
}