From 6151ddbb0dc1e050160495c56056c17764520e7d Mon Sep 17 00:00:00 2001 From: Christopher Nadeau Date: Tue, 12 Jun 2012 12:35:56 +0100 Subject: [PATCH] Add support for hierarchies and unselectable items. issue #58 Signed-off-by: Igor Vaynberg --- select2.css | 16 ++++- select2.js | 166 +++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 145 insertions(+), 37 deletions(-) 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); }