From d62bc5bef1ff6696262c3eb03334c553dc68f897 Mon Sep 17 00:00:00 2001 From: Igor Vaynberg Date: Tue, 12 Jun 2012 19:41:21 -0700 Subject: [PATCH 01/55] various small fixes. closes #99 --- select2.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/select2.js b/select2.js index e2a8db5c..788d1ed9 100755 --- a/select2.js +++ b/select2.js @@ -70,7 +70,7 @@ function indexOf(value, array) { var i = 0, l = array.length, v; - if (typeof value == 'undefined') { + if (typeof value === "undefined") { return -1; } @@ -244,7 +244,6 @@ } // TODO 3.0 - replace query.page with query so users have access to term, page, etc. var results = options.results(data, query.page); - self.context = results.context; query.callback(results); } }); @@ -678,8 +677,8 @@ this.opts.query({ term: this.search.val(), page: page, - context: self.context, - matcher: self.opts.matcher, + context: this.context, + matcher: this.opts.matcher, callback: this.bind(function (data) { var parts = [], self = this; $(data.results).each(function () { @@ -740,6 +739,9 @@ var parts = [], // html parts def; // default choice + // save context, if any + this.context = (data.context===undefined) ? null : data.context; + // create a default choice and prepend it to the list if (this.opts.createSearchChoice && search.val() !== "") { def = this.opts.createSearchChoice.call(null, search.val(), data.results); @@ -1478,7 +1480,6 @@ val = (val === null) ? [] : val; this.setVal(val); // val is a list of objects - st $(val).each(function () { data.push(self.id(this)); }); this.setVal(data); this.updateSelection(val); From 2f3262d39e08348103d1b420ab3f341d5e87368f Mon Sep 17 00:00:00 2001 From: Christopher Nadeau Date: Tue, 12 Jun 2012 09:40:37 +0100 Subject: [PATCH 02/55] Absolutely position dropdown. fixes #84. Changes to detach dropdown and append to body, and absolutely position dropdown. Fixes clipping problems with overflowing. Signed-off-by: Igor Vaynberg --- select2.css | 36 ++++++++++++++++---------------- select2.js | 60 +++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 65 insertions(+), 31 deletions(-) diff --git a/select2.css b/select2.css index 244dc03e..660fcb49 100755 --- a/select2.css +++ b/select2.css @@ -13,11 +13,11 @@ Version: @@ver@@ Timestamp: @@timestamp@@ .select2-container, .select2-drop, .select2-search, -.select2-container .select2-search input{ - /* +.select2-search input{ + /* Force border-box so that % widths fit the parent container without overlap because of margin/padding. - + More Info : http://www.quirksmode.org/css/box.html */ -moz-box-sizing: border-box; /* firefox */ @@ -83,7 +83,7 @@ Version: @@ver@@ Timestamp: @@timestamp@@ cursor: pointer; } -.select2-container .select2-drop { +.select2-drop { background: #fff; border: 1px solid #aaa; border-top: 0; @@ -133,7 +133,7 @@ Version: @@ver@@ Timestamp: @@timestamp@@ height: 100%; } -.select2-container .select2-search { +.select2-search { display: inline-block; white-space: nowrap; z-index: 1010; @@ -144,13 +144,13 @@ Version: @@ver@@ Timestamp: @@timestamp@@ padding-right: 4px; } -.select2-container .select2-search-hidden { +.select2-search-hidden { display: block; position: absolute; left: -10000px; } -.select2-container .select2-search input { +.select2-search input { background: #fff url('select2.png') no-repeat 100% -22px; background: url('select2.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee)); background: url('select2.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%); @@ -172,10 +172,10 @@ Version: @@ver@@ Timestamp: @@timestamp@@ box-shadow: none; border-radius: 0; -moz-border-radius: 0; - -webkit-border-radius: 0; + -webkit-border-radius: 0; } -.select2-container .select2-search input.select2-active { +.select2-search input.select2-active { background: #fff url('spinner.gif') no-repeat 100%; background: url('spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee)); background: url('spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%); @@ -228,7 +228,7 @@ Version: @@ver@@ Timestamp: @@timestamp@@ } /* results */ -.select2-container .select2-results { +.select2-results { margin: 4px 4px 4px 0; padding: 0 0 0 4px; position: relative; @@ -236,7 +236,7 @@ Version: @@ver@@ Timestamp: @@timestamp@@ overflow-y: auto; max-height: 200px; } -.select2-container .select2-results li { +.select2-results li { line-height: 80%; padding: 7px 7px 8px; margin: 0; @@ -245,37 +245,37 @@ Version: @@ver@@ Timestamp: @@timestamp@@ display: list-item; } -.select2-container .select2-results .select2-highlighted { +.select2-results .select2-highlighted { background: #3875d7; color: #fff; } -.select2-container .select2-results li em { +.select2-results li em { background: #feffde; font-style: normal; } -.select2-container .select2-results .select2-highlighted em { +.select2-results .select2-highlighted em { background: transparent; } -.select2-container .select2-results .select2-no-results { +.select2-results .select2-no-results { background: #f4f4f4; display: list-item; } /* disabled look for already selected choices in the results dropdown -.select2-container .select2-results .select2-disabled.select2-highlighted { +.select2-results .select2-disabled.select2-highlighted { color: #666; background: #f4f4f4; display: list-item; cursor: default; } -.select2-container .select2-results .select2-disabled { +.select2-results .select2-disabled { background: #f4f4f4; display: list-item; cursor: default; } */ -.select2-container .select2-results .select2-disabled { +.select2-results .select2-disabled { display: none; } diff --git a/select2.js b/select2.js index e2a8db5c..ce449eca 100755 --- a/select2.js +++ b/select2.js @@ -1,6 +1,6 @@ /* Copyright 2012 Igor Vaynberg - + Version: @@ver@@ Timestamp: @@timestamp@@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this work except in @@ -319,9 +319,16 @@ $(document).ready(function () { $(document).delegate("*", "mousedown focusin touchend", function (e) { var target = $(e.target).closest("div.select2-container").get(0); - $(document).find("div.select2-container-active").each(function () { - if (this !== target) $(this).data("select2").blur(); - }); + if (target) { + $(document).find("div.select2-container-active").each(function () { + if (this !== target) $(this).data("select2").blur(); + }); + } else { + target = $(e.target).closest("div.select2-drop").get(0); + $(document).find("div.select2-drop-active").each(function () { + if (this !== target) $(this).data("select2").blur(); + }); + } }); }); @@ -378,8 +385,11 @@ this.container.data("select2", this); this.dropdown = this.container.find(".select2-drop"); + this.dropdown.data("select2", this); + this.results = results = this.container.find(resultsSelector); this.search = search = this.container.find("input[type=text]"); + this.dropdown.detach().appendTo('body'); this.resultsPage = 0; this.context = null; @@ -388,10 +398,10 @@ this.initContainer(); installFilteredMouseMove(this.results); - this.container.delegate(resultsSelector, "mousemove-filtered", this.bind(this.highlightUnderEvent)); + this.dropdown.delegate(resultsSelector, "mousemove-filtered", this.bind(this.highlightUnderEvent)); installDebouncedScroll(80, this.results); - this.container.delegate(resultsSelector, "scroll-debounced", this.bind(this.loadMoreIfNeeded)); + this.dropdown.delegate(resultsSelector, "scroll-debounced", this.bind(this.loadMoreIfNeeded)); // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel if ($.fn.mousewheel) { @@ -412,7 +422,7 @@ search.bind("focus", function () { search.addClass("select2-focused");}); search.bind("blur", function () { search.removeClass("select2-focused");}); - this.container.delegate(resultsSelector, "click", this.bind(function (e) { + this.dropdown.delegate(resultsSelector, "click", this.bind(function (e) { if ($(e.target).closest(".select2-result:not(.select2-disabled)").length > 0) { this.highlightUnderEvent(e); this.selectHighlighted(e); @@ -438,6 +448,7 @@ var select2 = this.opts.element.data("select2"); if (select2 !== undefined) { select2.container.remove(); + select2.dropdown.remove(); select2.opts.element .removeData("select2") .unbind(".select2") @@ -568,10 +579,26 @@ return this.container.hasClass("select2-dropdown-open"); }, + updatePositions: function() { + var offset = this.container.offset(); + var height = this.container.outerHeight(); + var width = this.container.outerWidth(); + + this.dropdown.css({ + top: offset.top + height, + left: offset.left, + width: width, + 'max-height': 300 + }); + }, + open: function () { if (this.opened()) return; this.container.addClass("select2-dropdown-open").addClass("select2-container-active"); + this.dropdown.addClass("select2-drop-active"); + + this.updatePositions(); this.updateResults(true); this.dropdown.show(); @@ -787,6 +814,7 @@ window.setTimeout(this.bind(function () { this.close(); this.container.removeClass("select2-container-active"); + this.dropdown.removeClass("select2-drop-active"); this.clearSearch(); this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); this.search.blur(); @@ -888,7 +916,11 @@ initContainer: function () { - var selection, container = this.container, clickingInside = false, + var selection, + container = this.container, + dropdown = this.dropdown, + containerGroup = $([this.container.get(0), this.dropdown.get(0)]), + clickingInside = false, selector = ".select2-choice"; this.selection = selection = container.find(selector); @@ -912,7 +944,7 @@ } })); - container.delegate(selector, "click", this.bind(function (e) { + containerGroup.delegate(selector, "click", this.bind(function (e) { clickingInside = true; if (this.opened()) { @@ -925,7 +957,7 @@ clickingInside = false; })); - container.delegate(selector, "keydown", this.bind(function (e) { + containerGroup.delegate(selector, "keydown", this.bind(function (e) { if (!this.enabled || e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) { return; } @@ -939,8 +971,8 @@ killEvent(e); } })); - container.delegate(selector, "focus", function () { if (this.enabled) container.addClass("select2-container-active"); }); - container.delegate(selector, "blur", this.bind(function () { + containerGroup.delegate(selector, "focus", function () { if (this.enabled) { containerGroup.addClass("select2-container-active"); dropdown.addClass("select2-drop-active"); }}); + containerGroup.delegate(selector, "blur", this.bind(function () { if (clickingInside) return; if (!this.opened()) this.blur(); })); @@ -1206,6 +1238,7 @@ this.container.delegate(selector, "focus", this.bind(function () { if (!this.enabled) return; this.container.addClass("select2-container-active"); + this.dropdown.addClass("select2-drop-active"); this.clearPlaceholder(); })); @@ -1357,6 +1390,7 @@ })).bind("focus", this.bind(function () { if (!this.enabled) return; this.container.addClass("select2-container-active"); + this.dropdown.addClass("select2-drop-active"); })); choice.data("select2-data", data); @@ -1522,7 +1556,7 @@ var args = Array.prototype.slice.call(arguments, 0), opts, select2, - value, multiple, allowedMethods = ["val", "destroy", "open", "close", "focus", "isFocused", "container", "onSortStart", "onSortEnd", "enable", "disable"]; + value, multiple, allowedMethods = ["val", "destroy", "open", "close", "focus", "isFocused", "container", "onSortStart", "onSortEnd", "enable", "disable", "updatePositions"]; this.each(function () { if (args.length === 0 || typeof(args[0]) === "object") { From efccc62510ec318d58a46bad22188155cf28c5b8 Mon Sep 17 00:00:00 2001 From: Christopher Nadeau Date: Tue, 12 Jun 2012 09:49:48 +0100 Subject: [PATCH 03/55] Few tweaks to multi style Signed-off-by: Igor Vaynberg --- select2.css | 10 ---------- select2.js | 2 +- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/select2.css b/select2.css index 660fcb49..de67c8e5 100755 --- a/select2.css +++ b/select2.css @@ -324,10 +324,6 @@ disabled look for already selected choices in the results dropdown position: relative; } -.select2-container-multi .select2-drop { - margin-top:0; -} - .select2-container-multi .select2-choices { min-height: 26px; } @@ -428,12 +424,6 @@ disabled look for already selected choices in the results dropdown background-position: right -11px; } - -.select2-container-multi .select2-results { - margin: -1px 0 0; - padding: 0; -} - /* disabled styles */ .select2-container-multi.select2-container-disabled .select2-choices{ diff --git a/select2.js b/select2.js index ce449eca..323b63ab 100755 --- a/select2.js +++ b/select2.js @@ -1136,7 +1136,7 @@ " " , " " , "" , - ""].join("")); }, + // single open: function () { if (this.opened()) return; @@ -1016,25 +1043,30 @@ }, + // single close: function () { if (!this.opened()) return; this.parent.close.apply(this, arguments); }, + // single focus: function () { this.close(); this.selection.focus(); }, + // single isFocused: function () { return this.selection.is(":focus"); }, + // single cancel: function () { this.parent.cancel.apply(this, arguments); this.selection.focus(); }, + // single initContainer: function () { var selection, @@ -1112,6 +1144,7 @@ /** * Sets selection based on source element's value */ + // single initSelection: function () { var selected; if (this.opts.element.val() === "") { @@ -1127,6 +1160,7 @@ this.setPlaceholder(); }, + // single prepareOpts: function () { var opts = this.parent.prepareOpts.apply(this, arguments); @@ -1142,6 +1176,7 @@ return opts; }, + // single setPlaceholder: function () { var placeholder = this.getPlaceholder(); @@ -1161,6 +1196,7 @@ } }, + // single postprocessResults: function (data, initial) { var selected = 0, self = this, showSearchInput = true; @@ -1191,6 +1227,7 @@ }, + // single onSelect: function (data) { var old = this.opts.element.val(); @@ -1202,6 +1239,7 @@ if (!equal(old, this.id(data))) { this.triggerChange(); } }, + // single updateSelection: function (data) { this.selection .find("span") @@ -1214,6 +1252,7 @@ } }, + // single val: function () { var val, data = null; @@ -1241,6 +1280,7 @@ }, + // single clearSearch: function () { this.search.val(""); } @@ -1248,6 +1288,7 @@ MultiSelect2 = clazz(AbstractSelect2, { + // multi createContainer: function () { return $("
", { "class": "select2-container select2-container-multi", @@ -1265,6 +1306,7 @@ ""].join("")); }, + // multi prepareOpts: function () { var opts = this.parent.prepareOpts.apply(this, arguments); @@ -1288,6 +1330,7 @@ return opts; }, + // multi initContainer: function () { var selector = ".select2-choices", selection; @@ -1369,6 +1412,7 @@ this.clearSearch(); }, + // multi enable: function() { if (this.enabled) return; @@ -1377,6 +1421,7 @@ this.search.show(); }, + // multi disable: function() { if (!this.enabled) return; @@ -1385,6 +1430,7 @@ this.search.hide(); }, + // multi initSelection: function () { var data; if (this.opts.element.val() === "") { @@ -1403,6 +1449,7 @@ this.clearSearch(); }, + // multi clearSearch: function () { var placeholder = this.getPlaceholder(); @@ -1416,12 +1463,14 @@ } }, + // multi clearPlaceholder: function () { if (this.search.hasClass("select2-default")) { this.search.val("").removeClass("select2-default"); } }, + // multi open: function () { if (this.opened()) return; this.parent.open.apply(this, arguments); @@ -1430,20 +1479,24 @@ this.focusSearch(); }, + // multi close: function () { if (!this.opened()) return; this.parent.close.apply(this, arguments); }, + // multi focus: function () { this.close(); this.search.focus(); }, + // multi isFocused: function () { return this.search.hasClass("select2-focused"); }, + // multi updateSelection: function (data) { var ids = [], filtered = [], self = this; @@ -1463,6 +1516,7 @@ self.postprocessResults(); }, + // multi onSelect: function (data) { this.addSelectedChoice(data); if (this.select) { this.postprocessResults(); } @@ -1482,11 +1536,13 @@ this.focusSearch(); }, + // multi cancel: function () { this.close(); this.focusSearch(); }, + // multi addSelectedChoice: function (data) { var choice, id = this.id(data), @@ -1522,6 +1578,7 @@ this.setVal(val); }, + // multi unselect: function (selected) { var val = this.getVal(), index; @@ -1543,6 +1600,7 @@ this.triggerChange(); }, + // multi postprocessResults: function () { var val = this.getVal(), choices = this.results.find(".select2-result"), @@ -1566,6 +1624,7 @@ }, + // multi resizeSearch: function () { var minimumWidth, left, maxWidth, containerLeft, searchWidth, @@ -1590,6 +1649,7 @@ this.search.width(searchWidth); }, + // multi getVal: function () { var val; if (this.select) { @@ -1601,6 +1661,7 @@ } }, + // multi setVal: function (val) { var unique; if (this.select) { @@ -1615,6 +1676,7 @@ } }, + // multi val: function () { var val, data = [], self=this; @@ -1642,6 +1704,8 @@ this.clearSearch(); }, + + // multi onSortStart: function() { if (this.select) { throw new Error("Sorting of elements is not supported when attached to instead."); @@ -1652,6 +1716,8 @@ // hide the container this.searchContainer.hide(); }, + + // multi onSortEnd:function() { var val=[], self=this; From e13dc416548055fe5ffd9f72c8843200f1aed08e Mon Sep 17 00:00:00 2001 From: Igor Vaynberg Date: Thu, 21 Jun 2012 19:19:19 -0700 Subject: [PATCH 47/55] fix placeholder in multiselects when blurred by clicking outside --- select2.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/select2.js b/select2.js index 0f85746b..c41286a4 100755 --- a/select2.js +++ b/select2.js @@ -959,6 +959,7 @@ this.close(); this.container.removeClass("select2-container-active"); this.dropdown.removeClass("select2-drop-active"); + if (this.search.is(":focus")) { this.search.blur(); } this.clearSearch(); this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); }, @@ -1317,7 +1318,7 @@ // TODO validate placeholder is a string if specified if (opts.element.get(0).tagName.toLowerCase() === "select") { - // install sthe selection initializer + // install the selection initializer opts.initSelection = function (element) { var data = []; element.find(":selected").each2(function (i, elm) { @@ -1454,7 +1455,6 @@ var placeholder = this.getPlaceholder(); if (placeholder !== undefined && this.getVal().length === 0 && this.search.hasClass("select2-focused") === false) { - this.search.val(placeholder).addClass("select2-default"); // stretch the search box to full width of the container so as much of the placeholder is visible as possible this.search.width(this.getContainerWidth()); From 16c3d31e06308dc045cf8a4f176a7572cbe23477 Mon Sep 17 00:00:00 2001 From: Igor Vaynberg Date: Thu, 21 Jun 2012 21:18:13 -0700 Subject: [PATCH 48/55] full tabbing support for the mac. closes #132 --- select2.css | 4 +++- select2.js | 19 +++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/select2.css b/select2.css index 0544120d..859c0c39 100755 --- a/select2.css +++ b/select2.css @@ -468,4 +468,6 @@ disabled look for already selected choices in the results dropdown } /* end multiselect */ -.select2-match { text-decoration: underline; } \ No newline at end of file +.select2-match { text-decoration: underline; } + +.select2-offscreen { position: absolute; left: -1000px; } \ No newline at end of file diff --git a/select2.js b/select2.js index c41286a4..43dd1781 100755 --- a/select2.js +++ b/select2.js @@ -1022,7 +1022,7 @@ "class": "select2-container", "style": "width: " + this.getContainerWidth() }).html([ - " ", + " ", " ", "
" , "
", @@ -1075,7 +1075,8 @@ dropdown = this.dropdown, containers = $([this.container.get(0), this.dropdown.get(0)]), clickingInside = false, - selector = ".select2-choice"; + selector = ".select2-choice", + focusser=container.find("input.select2-focusser"); this.selection = selection = container.find(selector); @@ -1126,9 +1127,10 @@ } })); containers.delegate(selector, "focus", function () { if (this.enabled) { containers.addClass("select2-container-active"); dropdown.addClass("select2-drop-active"); }}); - containers.delegate(selector, "blur", this.bind(function () { + containers.delegate(selector, "blur", this.bind(function (e) { if (clickingInside) return; - if (!this.opened()) this.blur(); + if (e.target===focusser.get(0)) return; // ignore blurs from focusser + if (!this.opened()) { this.blur(); } })); selection.delegate("abbr", "click", this.bind(function (e) { @@ -1137,9 +1139,18 @@ killEvent(e); this.close(); this.triggerChange(); + selection.focus(); })); this.setPlaceholder(); + + focusser.bind("focus", function() { selection.focus(); }); + selection.bind("focus", this.bind(function() { + focusser.hide(); + this.container.addClass("select2-container-active"); + })); + selection.bind("blur", function() { focusser.show(); }); + this.opts.element.bind("open", function() { focusser.hide(); }); }, /** From 54b83c2baf2265ba08ae170a45e1cc40bbea94d8 Mon Sep 17 00:00:00 2001 From: Igor Vaynberg Date: Thu, 21 Jun 2012 21:36:08 -0700 Subject: [PATCH 49/55] fix input field broken by fix to #132 --- select2.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/select2.js b/select2.js index 43dd1781..02d162a5 100755 --- a/select2.js +++ b/select2.js @@ -437,7 +437,7 @@ this.dropdown.data("select2", this); this.results = results = this.container.find(resultsSelector); - this.search = search = this.container.find("input[type=text]"); + this.search = search = this.container.find("input.select2-input"); this.resultsPage = 0; this.context = null; @@ -1028,7 +1028,7 @@ "", "