7 * @class Roo.bootstrap.ComboBox
8 * @extends Roo.bootstrap.Component
9 * A combobox control with support for autocomplete, remote-loading, paging and many other features.
10 * @cfg {Boolean} showSearchBar (true|false) default false
13 * Create a new ComboBox.
14 * @param {Object} config Configuration options
16 Roo.bootstrap.ComboBox = function(config){
17 Roo.bootstrap.ComboBox.superclass.constructor.call(this, config);
21 Roo.extend(Roo.bootstrap.ComboBox, Roo.bootstrap.Component, {
23 showSearchBar : false,
24 resultsSelector : ".select2-results",
26 getAutoCreate : function()
56 initEvents : function()
58 Roo.log('initEvents');
60 this.container = Roo.bootstrap.ComboBox.SingleSelect2.createContainer();
65 cls: 'select2-hidden-accessible'
68 this.liveRegion = Roo.get(document.body).createChild(liveRegion);
70 this.containerId="s2id_"+(this.el.attr("id") || Roo.id());
72 this.container.attr("id", "s2id_"+(this.el.attr("id") || Roo.id()));
74 this.container.attr("title", this.el.attr("title"));
76 this.container.attr("style", this.el.attr("style"));
78 this.container.addClass(this.el.attr('class'));
80 this.container.on("click", this.killEvent);
82 this.elementTabIndex = this.el.attr("tabindex");
84 this.dropdown = this.container.select(".select2-drop", true).first();
86 this.dropdown.addClass(this.el.attr('class'));
88 this.dropdown.on("click", this.killEvent);
90 this.results = this.container.select(this.resultsSelector, true).first();
92 this.search = this.container.select("input.select2-input", true).first();
98 populateResults: function(container, results, query) {
99 var populate, id=this.opts.id, liveRegion=this.liveRegion;
101 populate=function(results, container, depth) {
103 var i, l, result, selectable, disabled, compound, node, label, innerContainer, formatted;
105 results = this.sortResults(results, container, query);
107 for (i = 0, l = results.length; i < l; i = i + 1) {
111 disabled = (result.disabled === true);
112 selectable = (!disabled) && (id(result) !== undefined);
114 compound=result.children && result.children.length > 0;
116 node=new Roo.Element(document.createElement("li"), true);
117 node.addClass("select2-results-dept-"+depth);
118 node.addClass("select2-result");
119 node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable");
120 if (disabled) { node.addClass("select2-disabled"); }
121 if (compound) { node.addClass("select2-result-with-children"); }
122 node.addClass(this.formatResultCssClass(result));
123 node.attr("role", "presentation");
125 label=new Roo.Element(document.createElement("div"), true);
126 label.addClass("select2-result-label");
127 label.attr("id", "select2-result-label-" + nextUid());
128 label.attr("role", "option");
130 formatted=this.formatResult(result, label, query, this.defaultEscapeMarkup);
131 if (formatted!==undefined) {
132 label.html(formatted);
139 innerContainer=new Roo.Element(document.createElement("ul"), true);
140 innerContainer.addClass("select2-result-sub");
141 populate(result.children, innerContainer, depth+1);
142 node.append(innerContainer);
145 node.data("select2-data", result);
146 container.append(node);
149 liveRegion.text(this.formatMatches(results.length));
152 populate(results, container, 0);
155 sortResults: function (results, container, query)
160 formatResultCssClass: function(data)
165 formatResult: function(result, container, query, escapeMarkup)
168 markMatch(result.text, query.term, markup, escapeMarkup);
169 return markup.join("");
172 defaultEscapeMarkup : function (markup) {
183 return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
184 return replace_map[match];
188 formatMatches: function (matches)
190 return matches + " results are available, use up and down arrow keys to navigate.";
193 initContainer: function () {
195 var idSuffix = Roo.id(),
198 this.hideSearchBar();
200 if (this.showSearchBar) {
201 this.showSearchBar();
204 this.selection = this.container.select(".select2-choice", true).first();
206 this.focusser = this.container.select(".select2-focusser", true).first();
208 // add aria associations
209 this.selection.select(".select2-chosen",true).first().attr("id", "select2-chosen-" + idSuffix);
210 this.focusser.attr("aria-labelledby", "select2-chosen-" + idSuffix);
211 this.results.attr("id", "select2-results-" + idSuffix);
212 this.search.attr("aria-owns", "select2-results-" + idSuffix);
214 // rewrite labels from original element to focusser
215 this.focusser.attr("id", "s2id_" + idSuffix);
217 Roo.get(this.focusser.getPrevSibling()).attr('for', this.focusser.attr('id'));
219 // Ensure the original element retains an accessible name
220 // var originalTitle = this.el.attr("title");
221 // this.opts.element.attr("title", (originalTitle || elementLabel.text()));
223 this.focusser.attr("tabindex", this.elementTabIndex);
225 // write label for search field using the label from the focusser element
226 this.search.attr("id", this.focusser.attr('id') + '_search');
228 Roo.get(this.search.getPrevSibling()).attr('for', this.search.attr('id'));
230 this.search.on("keydown", this.bind(function (e) {
231 if (!this.isInterfaceEnabled()) return;
233 if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) {
234 // prevent the page from scrolling
242 this.moveHighlight((e.which === KEY.UP) ? -1 : 1);
246 this.selectHighlighted();
250 this.selectHighlighted({noFocus: true});
259 this.search.on("blur", this.bind(function(e) {
261 // a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown.
262 // without this the search field loses focus which is annoying
263 if (document.activeElement === this.body.get(0)) {
264 window.setTimeout(this.bind(function() {
272 this.focusser.on("keydown", this.bind(function (e) {
273 console.log('focusser on keydown');
274 if (!this.isInterfaceEnabled()) return;
276 if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) {
280 if (this.opts.openOnEnter === false && e.which === KEY.ENTER) {
285 if (e.which == KEY.DOWN || e.which == KEY.UP
286 || (e.which == KEY.ENTER && this.opts.openOnEnter)) {
288 if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) return;
295 if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) {
296 if (this.opts.allowClear) {
305 installKeyUpChangeEvent(this.focusser);
306 this.focusser.on("keyup-change input", this.bind(function(e) {
307 if (this.opts.minimumResultsForSearch >= 0) {
309 if (this.opened()) return;
314 selection.on("mousedown touchstart", "abbr", this.bind(function (e) {
315 if (!this.isInterfaceEnabled()) return;
317 killEventImmediately(e);
319 this.selection.focus();
322 selection.on("mousedown touchstart", this.bind(function (e) {
323 // Prevent IE from generating a click event on the body
324 reinsertElement(selection);
326 if (!this.container.hasClass("select2-container-active")) {
327 this.opts.element.trigger($.Event("select2-focus"));
332 } else if (this.isInterfaceEnabled()) {
339 dropdown.on("mousedown touchstart", this.bind(function() {
340 if (this.opts.shouldFocusInput(this)) {
345 selection.on("focus", this.bind(function(e) {
349 this.focusser.on("focus", this.bind(function(){
350 if (!this.container.hasClass("select2-container-active")) {
351 this.opts.element.trigger($.Event("select2-focus"));
353 this.container.addClass("select2-container-active");
354 })).on("blur", this.bind(function() {
355 if (!this.opened()) {
356 this.container.removeClass("select2-container-active");
357 this.opts.element.trigger($.Event("select2-blur"));
360 this.search.on("focus", this.bind(function(){
361 if (!this.container.hasClass("select2-container-active")) {
362 this.opts.element.trigger($.Event("select2-focus"));
364 this.container.addClass("select2-container-active");
367 this.initContainerWidth();
368 this.opts.element.addClass("select2-offscreen");
369 this.setPlaceholder();
373 showSearchBar: function() {
375 if(!this.dropdown.select(".select2-search",true).first().hasClass("select2-search-hidden")){
376 this.dropdown.select(".select2-search",true).first().addClass("select2-search-hidden");
379 if(!this.dropdown.select(".select2-search",true).first().hasClass("select2-offscreen")){
380 this.dropdown.select(".select2-search",true).first().addClass("select2-offscreen");
383 if(!this.dropdown.hasClass("select2-with-searchbox")){
384 this.dropdown.addClass("select2-with-searchbox");
387 if(!this.container.hasClass("select2-with-searchbox")){
388 this.container.addClass("select2-with-searchbox");
393 hideSearchBar: function() {
395 if(this.dropdown.select(".select2-search",true).first().hasClass("select2-search-hidden")){
396 this.dropdown.select(".select2-search",true).first().removeClass("select2-search-hidden");
399 if(this.dropdown.select(".select2-search",true).first().hasClass("select2-offscreen")){
400 this.dropdown.select(".select2-search",true).first().removeClass("select2-offscreen");
403 if(this.dropdown.hasClass("select2-with-searchbox")){
404 this.dropdown.removeClass("select2-with-searchbox");
407 if(this.container.hasClass("select2-with-searchbox")){
408 this.container.removeClass("select2-with-searchbox");
413 killEvent : function (event) {
414 Roo.log('KILLEVENT');
415 event.preventDefault();
416 event.stopPropagation();
419 moveHighlight: function (delta) {
420 var choices = this.findHighlightableChoices(),
421 index = this.highlight();
423 while (index > -1 && index < choices.length) {
425 var choice = $(choices[index]);
426 if (choice.hasClass("select2-result-selectable") && !choice.hasClass("select2-disabled") && !choice.hasClass("select2-selected")) {
427 this.highlight(index);
433 findHighlightableChoices: function() {
434 return this.results.select(".select2-result-selectable:not(.select2-disabled):not(.select2-selected)", true).elements;
440 Roo.apply(Roo.bootstrap.ComboBox, {
442 createContainer: function () {
443 var container = new Roo.Element(document.createElement("div"), true);
444 container.addClass("select2-container");
446 container.createChild({
448 href: 'javascript:void(0)',
449 cls: 'select2-choice',
454 cls: 'select2-chosen',
459 cls: 'select2-search-choice-close'
463 cls: 'select2-arrow',
473 container.createChild({
476 cls: 'select2-offscreen'
479 container.createChild({
482 cls: 'select2-focusser select2-offscreen',
483 'aria-haspopup': true,
487 container.createChild({
489 cls: 'select2-drop select2-display-none',
493 cls: 'select2-search',
498 cls: 'select2-offscreen'
503 cls: 'select2-input',
507 autocapitalize: 'off',
509 'aria-expanded': true,
510 'aria-autocomplete': 'lsit'
517 cls: 'select2-results',