Bootstrap açılır düğmenin html yapısı:
<div class="btn-group"> <button class="btn btn-mini">Action</button> <button class="btn btn-mini dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button> <ul class="dropdown-menu"> <li><a href="#">Action</a></li> <li><a href="#">Another action</a></li> <li><a href="#">Something else here</a></li> <li class="divider"></li> <li><a href="#">Separated link</a></li> </ul> </div>
Önceki yazımda jQuery nesnelerindeki trigger, on olaylarını yüklü jQuery'e iliştirme olayını okuyabilirsiniz:
var obj = $({}); jQuery["on"] = function () { obj["on"].apply(obj, arguments); }; jQuery["trigger"] = function () { obj["trigger"].apply(obj, arguments); };
Bunu yaptık çünkü Select2 içinde bir event oluşturup her arama sonucunda bir eleman seçtiğinizde tetiklenmesini istiyoruz. Böylece eklenmiş elemanı Select2 dışında kontrol edebilmemizi sağlayacağız.
addSelectedChoice: function (data) { var enableChoice = !data.locked, enabledItem = $( "
" + this.opts.escapeMarkup(formatted) + "
");
choice.find("div").replaceWith("" + formatted + "
");
$.trigger({ type: "cem", val: data, choice: $(choice.find("div")[0])});
}
Tag şeklinde açılacağı ve multiselect hedeflediğim için container içinde menü elemanlarını görüntüleyebilmek için aşağıdaki css içinde bazı değişiklikler yapacağız.
.select2-container-multi .select2-choices { height: auto !important; height: 1%; margin: 0; padding: 0; position: relative; border: 1px solid #aaa; cursor: text; overflow: hidden; background-color: #fff; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); background-image: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background-image: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background-image: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background-image: -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background-image: linear-gradient(top, #eeeeee 1%, #ffffff 15%); }
overflow: hidden
ile menü elemanları gizleniyordu. Bunu visible yapacağız ki menü gözüksün.display: inline-block;
genişlemeyi otomatik yapabilmemiz için.min-width: 200px;
en azından 200px genişliğinde görünsün istiyoruz.Sonuç:
.select2-container-multi .select2-choices { height: auto !important; height: 1%; margin: 0; padding: 0; position: relative; border: 1px solid #aaa; cursor: text; overflow: visible; display: inline-block; min-width: 200px; background-color: #fff; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); background-image: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background-image: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background-image: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background-image: -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background-image: linear-gradient(top, #eeeeee 1%, #ffffff 15%); }
Select2'nin css içinde ul içinde li taglarını float:left ile inline-block şeklinde gösteriyor. Bizde dropdown button içinde li kullandığımıza göre ve dikey dizilmiş li menu item dizimiz olsun istediğimiz için
.select2-container-multi .select2-choices li
şeklinde tüm li elemanlarını etkileyen stilimizi sadece ilk seviye li elemanlarına uygulamak için şu şekle getiririz: .select2-container-multi .select2-choices > li
Tüm html kodu:
<!DOCTYPE html> <html> <head> <link rel="stylesheet" href="select2-3.3.2/select2.css" /> <link rel="stylesheet" href="bootstrap/css/bootstrap.css" /> </head> <body> <input type="hidden" id="hdn" value="" style="width: 200px;" /> </body> <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script> <script type="text/javascript" src="bootstrap/js/bootstrap.min.js"></script> <script type="text/javascript" src="select2-3.3.2/select2.js"></script> <script type="text/javascript"> var l = console.log.bind(console); var d = [{ id: 1, text: "bir" }, { id: 2, text: "iki" }, { id: 3, text: "üç" }]; $(function () { //$('.dropdown-toggle').dropdown(); var btn = ["<div class='btn-group'> " + " <button class='btn btn-mini'>Action</button> " + " <button class='btn btn-mini dropdown-toggle' data-toggle='dropdown'> " + " <span class='caret'></span> " + " </button> " + " <ul class='dropdown-menu'> " + " <li><a href='#'>Action</a></li> " + " <li><a href='#'>Another action</a></li> " + " <li><a href='#'>Something else here</a></li> " + " <li class='divider'></li> " + " <li><a href='#'>Separated link</a></li> " + " </ul> " + "</div> "]; $('body').append($(btn.join())); $('#hdn').on('cem', function (a) { console.log(a); alert('cemmmm'); }); var obj = $({}); jQuery["on"] = function () { obj["on"].apply(obj, arguments); }; jQuery["trigger"] = function () { obj["trigger"].apply(obj, arguments); }; $.on('cem', function (a) { l(a); l('jquery cemmmm'); }); $('#hdn').select2({ autoWidth: true, multiple: true, query: function (query) { var data = { results: [] }; $.each(d, function () { if (query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0) { data.results.push({ id: this.id, text: this.text }); } }); query.callback(data); }, formatSelection: function (item) { return btn.join(); } }); }); $('#hdn').select2('data', d); </script> </html>
Select2 Kodu:
= 112 && k <= 123; } }, MEASURE_SCROLLBAR_TEMPLATE = "<div class='select2-measure-scrollbar'></div>"; $document = $(document); nextUid=(function() { var counter=1; return function() { return counter++; }; }()); function indexOf(value, array) { var i = 0, l = array.length; for (; i < l; i = i + 1) { if (equal(value, array[i])) return i; } return -1; } function measureScrollbar () { var $template = $( MEASURE_SCROLLBAR_TEMPLATE ); $template.appendTo('body'); var dim = { width: $template.width() - $template[0].clientWidth, height: $template.height() - $template[0].clientHeight }; $template.remove(); return dim; } /** * Compares equality of a and b * @param a * @param b */ function equal(a, b) { if (a === b) return true; if (a === undefined || b === undefined) return false; if (a === null || b === null) return false; if (a.constructor === String) return a+'' === b+''; // IE requires a+'' instead of just a if (b.constructor === String) return b+'' === a+''; // IE requires b+'' instead of just b return false; } /** * Splits the string into an array of values, trimming each value. An empty array is returned for nulls or empty * strings * @param string * @param separator */ function splitVal(string, separator) { var val, i, l; if (string === null || string.length < 1) return []; val = string.split(separator); for (i = 0, l = val.length; i < l; i = i + 1) val[i] = $.trim(val[i]); return val; } function getSideBorderPadding(element) { return element.outerWidth(false) - element.width(); } function installKeyUpChangeEvent(element) { var key="keyup-change-value"; element.bind("keydown", function () { if ($.data(element, key) === undefined) { $.data(element, key, element.val()); } }); element.bind("keyup", function () { var val= $.data(element, key); if (val !== undefined && element.val() !== val) { $.removeData(element, key); element.trigger("keyup-change"); } }); } $document.bind("mousemove", function (e) { lastMousePosition = {x: e.pageX, y: e.pageY}; }); /** * filters mouse events so an event is fired only if the mouse moved. * * filters out mouse events that occur when mouse is stationary but * the elements under the pointer are scrolled. */ function installFilteredMouseMove(element) { element.bind("mousemove", function (e) { var lastpos = lastMousePosition; if (lastpos === undefined || lastpos.x !== e.pageX || lastpos.y !== e.pageY) { $(e.target).trigger("mousemove-filtered", e); } }); } /** * Debounces a function. Returns a function that calls the original fn function only if no invocations have been made * within the last quietMillis milliseconds. * * @param quietMillis number of milliseconds to wait before invoking fn * @param fn function to be debounced * @param ctx object to be used as this reference within fn * @return debounced version of fn */ function debounce(quietMillis, fn, ctx) { ctx = ctx || undefined; var timeout; return function () { var args = arguments; window.clearTimeout(timeout); timeout = window.setTimeout(function() { fn.apply(ctx, args); }, quietMillis); }; } /** * A simple implementation of a thunk * @param formula function used to lazily initialize the thunk * @return {Function} */ function thunk(formula) { var evaluated = false, value; return function() { if (evaluated === false) { value = formula(); evaluated = true; } return value; }; }; function installDebouncedScroll(threshold, element) { var notify = debounce(threshold, function (e) { element.trigger("scroll-debounced", e);}); element.bind("scroll", function (e) { if (indexOf(e.target, element.get()) >= 0) notify(e); }); } function focus($el) { if ($el[0] === document.activeElement) return; /* set the focus in a 0 timeout - that way the focus is set after the processing of the current event has finished - which seems like the only reliable way to set focus */ window.setTimeout(function() { var el=$el[0], pos=$el.val().length, range; $el.focus(); /* make sure el received focus so we do not error out when trying to manipulate the caret. sometimes modals or others listeners may steal it after its set */ if ($el.is(":visible") && el === document.activeElement) { /* after the focus is set move the caret to the end, necessary when we val() just before setting focus */ if(el.setSelectionRange) { el.setSelectionRange(pos, pos); } else if (el.createTextRange) { range = el.createTextRange(); range.collapse(false); range.select(); } } }, 0); } function killEvent(event) { event.preventDefault(); event.stopPropagation(); } function killEventImmediately(event) { event.preventDefault(); event.stopImmediatePropagation(); } function measureTextWidth(e) { if (!sizer){ var style = e[0].currentStyle || window.getComputedStyle(e[0], null); sizer = $(document.createElement("div")).css({ position: "absolute", left: "-10000px", top: "-10000px", display: "none", fontSize: style.fontSize, fontFamily: style.fontFamily, fontStyle: style.fontStyle, fontWeight: style.fontWeight, letterSpacing: style.letterSpacing, textTransform: style.textTransform, whiteSpace: "nowrap" }); sizer.attr("class","select2-sizer"); $("body").append(sizer); } sizer.text(e.val()); return sizer.width(); } function syncCssClasses(dest, src, adapter) { var classes, replacements = [], adapted; classes = dest.attr("class"); if (classes) { classes = '' + classes; // for IE which returns object $(classes.split(" ")).each2(function() { if (this.indexOf("select2-") === 0) { replacements.push(this); } }); } classes = src.attr("class"); if (classes) { classes = '' + classes; // for IE which returns object $(classes.split(" ")).each2(function() { if (this.indexOf("select2-") !== 0) { adapted = adapter(this); if (adapted) { replacements.push(this); } } }); } dest.attr("class", replacements.join(" ")); } function markMatch(text, term, markup, escapeMarkup) { var match=text.toUpperCase().indexOf(term.toUpperCase()), tl=term.length; if (match<0) { markup.push(escapeMarkup(text)); return; } markup.push(escapeMarkup(text.substring(0, match))); markup.push("<span class='select2-match'>"); markup.push(escapeMarkup(text.substring(match, match + tl))); markup.push("</span>"); markup.push(escapeMarkup(text.substring(match + tl, text.length))); } /** * Produces an ajax-based query function * * @param options object containing configuration paramters * @param options.transport function that will be used to execute the ajax request. must be compatible with parameters supported by $.ajax * @param options.url url for the data * @param options.data a function(searchTerm, pageNumber, context) that should return an object containing query string parameters for the above url. * @param options.dataType request data type: ajax, jsonp, other datatatypes supported by jQuery's $.ajax function or the transport function if specified * @param options.cache set to true to disable jquery's cache-busting url parameters * @param options.jsonpCallback set to override the jquery callback function - useful in conjunction with options.cache * @param options.traditional a boolean flag that should be true if you wish to use the traditional style of param serialization for the ajax request * @param options.quietMillis (optional) milliseconds to wait before making the ajaxRequest, helps debounce the ajax function if invoked too often * @param options.results a function(remoteData, pageNumber) that converts data returned form the remote request to the format expected by Select2. * The expected format is an object containing the following keys: * results array of objects that will be used as choices * more (optional) boolean indicating whether there are more results available * Example: {results:[{id:1, text:'Red'},{id:2, text:'Blue'}], more:true} */ function ajax(options) { var timeout, // current scheduled but not yet executed request requestSequence = 0, // sequence used to drop out-of-order responses handler = null, quietMillis = options.quietMillis || 100, ajaxUrl = options.url, self = this; return function (query) { window.clearTimeout(timeout); timeout = window.setTimeout(function () { requestSequence += 1; // increment the sequence var requestNumber = requestSequence, // this request's sequence number data = options.data, // ajax data function url = ajaxUrl, // ajax url string or function transport = options.transport || $.ajax, cache = options.cache || false, jsonpCallback = options.jsonpCallback || undefined, type = options.type || 'GET', // set type of request (GET or POST) params = {}; data = data ? data.call(self, query.term, query.page, query.context) : null; url = (typeof url === 'function') ? url.call(self, query.term, query.page, query.context) : url; if( null !== handler) { handler.abort(); } if (options.params) { if ($.isFunction(options.params)) { $.extend(params, options.params.call(self)); } else { $.extend(params, options.params); } } $.extend(params, { url: url, dataType: options.dataType, data: data, type: type, cache: cache, jsonpCallback: jsonpCallback, success: function (data) { if (requestNumber < requestSequence) { return; } // TODO - replace query.page with query so users have access to term, page, etc. var results = options.results(data, query.page); query.callback(results); } }); handler = transport.call(self, params); }, quietMillis); }; } /** * Produces a query function that works with a local array * * @param options object containing configuration parameters. The options parameter can either be an array or an * object. * * If the array form is used it is assumed that it contains objects with 'id' and 'text' keys. * * If the object form is used ti is assumed that it contains 'data' and 'text' keys. The 'data' key should contain * an array of objects that will be used as choices. These objects must contain at least an 'id' key. The 'text' * key can either be a String in which case it is expected that each element in the 'data' array has a key with the * value of 'text' which will be used to match choices. Alternatively, text can be a function(item) that can extract * the text. */ function local(options) { var data = options, // data elements dataText, tmp, text = function (item) { return ""+item.text; }; // function used to retrieve the text portion of a data item that is matched against the search if ($.isArray(data)) { tmp = data; data = { results: tmp }; } if ($.isFunction(data) === false) { tmp = data; data = function() { return tmp; }; } var dataItem = data(); if (dataItem.text) { text = dataItem.text; // if text is not a function we assume it to be a key name if (!$.isFunction(text)) { dataText = dataItem.text; // we need to store this in a separate variable because in the next step data gets reset and data.text is no longer available text = function (item) { return item[dataText]; }; } } return function (query) { var t = query.term, filtered = { results: [] }, process; if (t === "") { query.callback(data()); return; } process = function(datum, collection) { var group, attr; datum = datum[0]; if (datum.children) { group = {}; for (attr in datum) { if (datum.hasOwnProperty(attr)) group[attr]=datum[attr]; } group.children=[]; $(datum.children).each2(function(i, childDatum) { process(childDatum, group.children); }); if (group.children.length || query.matcher(t, text(group), datum)) { collection.push(group); } } else { if (query.matcher(t, text(datum), datum)) { collection.push(datum); } } }; $(data().results).each2(function(i, datum) { process(datum, filtered.results); }); query.callback(filtered); }; } // TODO javadoc function tags(data) { var isFunc = $.isFunction(data); return function (query) { var t = query.term, filtered = {results: []}; $(isFunc ? data() : data).each(function () { var isObject = this.text !== undefined, text = isObject ? this.text : this; if (t === "" || query.matcher(t, text)) { filtered.results.push(isObject ? this : {id: this, text: this}); } }); query.callback(filtered); }; } /** * Checks if the formatter function should be used. * * Throws an error if it is not a function. Returns true if it should be used, * false if no formatting should be performed. * * @param formatter */ function checkFormatter(formatter, formatterName) { if ($.isFunction(formatter)) return true; if (!formatter) return false; throw new Error("formatterName must be a function or a falsy value"); } function evaluate(val) { return $.isFunction(val) ? val() : val; } function countResults(results) { var count = 0; $.each(results, function(i, item) { if (item.children) { count += countResults(item.children); } else { count++; } }); return count; } /** * Default tokenizer. This function uses breaks the input on substring match of any string from the * opts.tokenSeparators array and uses opts.createSearchChoice to create the choice object. Both of those * two options have to be defined in order for the tokenizer to work. * * @param input text user has typed so far or pasted into the search field * @param selection currently selected choices * @param selectCallback function(choice) callback tho add the choice to selection * @param opts select2's opts * @return undefined/null to leave the current input unchanged, or a string to change the input to the returned value */ function defaultTokenizer(input, selection, selectCallback, opts) { var original = input, // store the original so we can compare and know if we need to tell the search to update its text dupe = false, // check for whether a token we extracted represents a duplicate selected choice token, // token index, // position at which the separator was found i, l, // looping variables separator; // the matched separator if (!opts.createSearchChoice || !opts.tokenSeparators || opts.tokenSeparators.length < 1) return undefined; while (true) { index = -1; for (i = 0, l = opts.tokenSeparators.length; i < l; i++) { separator = opts.tokenSeparators[i]; index = input.indexOf(separator); if (index >= 0) break; } if (index < 0) break; // did not find any token separator in the input string, bail token = input.substring(0, index); input = input.substring(index + separator.length); if (token.length > 0) { token = opts.createSearchChoice(token, selection); if (token !== undefined && token !== null && opts.id(token) !== undefined && opts.id(token) !== null) { dupe = false; for (i = 0, l = selection.length; i < l; i++) { if (equal(opts.id(token), opts.id(selection[i]))) { dupe = true; break; } } if (!dupe) selectCallback(token); } } } if (original!==input) return input; } /** * Creates a new class * * @param superClass * @param methods */ function clazz(SuperClass, methods) { var constructor = function () {}; constructor.prototype = new SuperClass; constructor.prototype.constructor = constructor; constructor.prototype.parent = SuperClass.prototype; constructor.prototype = $.extend(constructor.prototype, methods); return constructor; } AbstractSelect2 = clazz(Object, { // abstract bind: function (func) { var self = this; return function () { func.apply(self, arguments); }; }, // abstract init: function (opts) { var results, search, resultsSelector = ".select2-results", mask; // prepare options this.opts = opts = this.prepareOpts(opts); this.id=opts.id; // destroy if called on an existing component if (opts.element.data("select2") !== undefined && opts.element.data("select2") !== null) { this.destroy(); } this.enabled=true; this.container = this.createContainer(); this.containerId="s2id_"+(opts.element.attr("id") || "autogen"+nextUid()); this.containerSelector="#"+this.containerId.replace(/([;&,\.\+\*\~':"\!\^#$%@\[\]\(\)=>\|])/g, '\\$1'); this.container.attr("id", this.containerId); // cache the body so future lookups are cheap this.body = thunk(function() { return opts.element.closest("body"); }); syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass); this.container.css(evaluate(opts.containerCss)); this.container.addClass(evaluate(opts.containerCssClass)); this.elementTabIndex = this.opts.element.attr("tabindex"); // swap container for the element this.opts.element .data("select2", this) .bind("focus.select2", function() { $(this).select2("focus"); }) .attr("tabindex", "-1") .before(this.container); this.container.data("select2", this); this.dropdown = this.container.find(".select2-drop"); this.dropdown.addClass(evaluate(opts.dropdownCssClass)); this.dropdown.data("select2", this); this.results = results = this.container.find(resultsSelector); this.search = search = this.container.find("input.select2-input"); this.resultsPage = 0; this.context = null; // initialize the container this.initContainer(); installFilteredMouseMove(this.results); this.dropdown.delegate(resultsSelector, "mousemove-filtered touchstart touchmove touchend", this.bind(this.highlightUnderEvent)); installDebouncedScroll(80, this.results); this.dropdown.delegate(resultsSelector, "scroll-debounced", this.bind(this.loadMoreIfNeeded)); // do not propagate change event from the search field out of the component $(this.container).delegate(".select2-input", "change", function(e) {e.stopPropagation();}); $(this.dropdown).delegate(".select2-input", "change", function(e) {e.stopPropagation();}); // if jquery.mousewheel plugin is installed we can prevent out-of-bounds scrolling of results via mousewheel if ($.fn.mousewheel) { results.mousewheel(function (e, delta, deltaX, deltaY) { var top = results.scrollTop(), height; if (deltaY > 0 && top - deltaY <= 0) { results.scrollTop(0); killEvent(e); } else if (deltaY < 0 && results.get(0).scrollHeight - results.scrollTop() + deltaY <= results.height()) { results.scrollTop(results.get(0).scrollHeight - results.height()); killEvent(e); } }); } installKeyUpChangeEvent(search); search.bind("keyup-change input paste", this.bind(this.updateResults)); search.bind("focus", function () { search.addClass("select2-focused"); }); search.bind("blur", function () { search.removeClass("select2-focused");}); this.dropdown.delegate(resultsSelector, "mouseup", this.bind(function (e) { if ($(e.target).closest(".select2-result-selectable").length > 0) { this.highlightUnderEvent(e); this.selectHighlighted(e); } })); // trap all mouse events from leaving the dropdown. sometimes there may be a modal that is listening // for mouse events outside of itself so it can close itself. since the dropdown is now outside the select2's // dom it will trigger the popup close, which is not what we want this.dropdown.bind("click mouseup mousedown", function (e) { e.stopPropagation(); }); if ($.isFunction(this.opts.initSelection)) { // initialize selection based on the current value of the source element this.initSelection(); // if the user has provided a function that can set selection based on the value of the source element // we monitor the change event on the element and trigger it, allowing for two way synchronization this.monitorSource(); } if (opts.element.is(":disabled") || opts.element.is("[readonly='readonly']")) this.disable(); // Calculate size of scrollbar scrollBarDimensions = scrollBarDimensions || measureScrollbar(); }, // abstract destroy: function () { var select2 = this.opts.element.data("select2"); if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; } if (select2 !== undefined) { select2.container.remove(); select2.dropdown.remove(); select2.opts.element .removeClass("select2-offscreen") .removeData("select2") .unbind(".select2") .attr({"tabindex": this.elementTabIndex}) .show(); } }, // abstract optionToData: function(element) { if (element.is("option")) { return { id:element.attr("value"), text:element.text(), element: element.get(), css: element.attr("class"), disabled: equal(element.attr("disabled"), "disabled"), locked: equal(element.attr("locked"), "locked") }; } else if (element.is("optgroup")) { return { text:element.attr("label"), children:[], element: element.get(), css: element.attr("class") }; } }, // abstract prepareOpts: function (opts) { var element, select, idKey, ajaxUrl, self = this; element = opts.element; if (element.get(0).tagName.toLowerCase() === "select") { this.select = select = opts.element; } if (select) { // these options are not allowed when attached to a select because they are picked up off the element itself $.each(["id", "multiple", "ajax", "query", "createSearchChoice", "initSelection", "data", "tags"], function () { if (this in opts) { throw new Error("Option '" + this + "' is not allowed for Select2 when attached to a <select> element."); } }); } opts = $.extend({}, { populateResults: function(container, results, query) { var populate, data, result, children, id=this.opts.id; populate=function(results, container, depth) { var i, l, result, selectable, disabled, compound, node, label, innerContainer, formatted; results = opts.sortResults(results, container, query); for (i = 0, l = results.length; i < l; i = i + 1) { result=results[i]; disabled = (result.disabled === true); selectable = (!disabled) && (id(result) !== undefined); compound=result.children && result.children.length > 0; node=$("<li></li>"); node.addClass("select2-results-dept-"+depth); node.addClass("select2-result"); node.addClass(selectable ? "select2-result-selectable" : "select2-result-unselectable"); if (disabled) { node.addClass("select2-disabled"); } if (compound) { node.addClass("select2-result-with-children"); } node.addClass(self.opts.formatResultCssClass(result)); label=$(document.createElement("div")); label.addClass("select2-result-label"); formatted=opts.formatResult(result, label, query, self.opts.escapeMarkup); if (formatted!==undefined) { label.html(formatted); } node.append(label); if (compound) { innerContainer=$("<ul></ul>"); innerContainer.addClass("select2-result-sub"); populate(result.children, innerContainer, depth+1); node.append(innerContainer); } node.data("select2-data", result); container.append(node); } }; populate(results, container, 0); } }, $.fn.select2.defaults, opts); if (typeof(opts.id) !== "function") { idKey = opts.id; opts.id = function (e) { return e[idKey]; }; } if ($.isArray(opts.element.data("select2Tags"))) { if ("tags" in opts) { throw "tags specified as both an attribute 'data-select2-tags' and in options of Select2 " + opts.element.attr("id"); } opts.tags=opts.element.data("select2Tags"); } if (select) { opts.query = this.bind(function (query) { var data = { results: [], more: false }, term = query.term, children, firstChild, process; process=function(element, collection) { var group; if (element.is("option")) { if (query.matcher(term, element.text(), element)) { collection.push(self.optionToData(element)); } } else if (element.is("optgroup")) { group=self.optionToData(element); element.children().each2(function(i, elm) { process(elm, group.children); }); if (group.children.length>0) { collection.push(group); } } }; children=element.children(); // ignore the placeholder option if there is one if (this.getPlaceholder() !== undefined && children.length > 0) { firstChild = children[0]; if ($(firstChild).text() === "") { children=children.not(firstChild); } } children.each2(function(i, elm) { process(elm, data.results); }); query.callback(data); }); // this is needed because inside val() we construct choices from options and there id is hardcoded opts.id=function(e) { return e.id; }; opts.formatResultCssClass = function(data) { return data.css; }; } else { if (!("query" in opts)) { if ("ajax" in opts) { ajaxUrl = opts.element.data("ajax-url"); if (ajaxUrl && ajaxUrl.length > 0) { opts.ajax.url = ajaxUrl; } opts.query = ajax.call(opts.element, opts.ajax); } else if ("data" in opts) { opts.query = local(opts.data); } else if ("tags" in opts) { opts.query = tags(opts.tags); if (opts.createSearchChoice === undefined) { opts.createSearchChoice = function (term) { return {id: term, text: term}; }; } if (opts.initSelection === undefined) { opts.initSelection = function (element, callback) { var data = []; $(splitVal(element.val(), opts.separator)).each(function () { var id = this, text = this, tags=opts.tags; if ($.isFunction(tags)) tags=tags(); $(tags).each(function() { if (equal(this.id, id)) { text = this.text; return false; } }); data.push({id: id, text: text}); }); callback(data); }; } } } } if (typeof(opts.query) !== "function") { throw "query function not defined for Select2 " + opts.element.attr("id"); } return opts; }, /** * Monitor the original element for changes and update select2 accordingly */ // abstract monitorSource: function () { var el = this.opts.element, sync; el.bind("change.select2", this.bind(function (e) { if (this.opts.element.data("select2-change-triggered") !== true) { this.initSelection(); } })); sync = this.bind(function () { var enabled, readonly, self = this; // sync enabled state enabled = this.opts.element.attr("disabled") !== "disabled"; readonly = this.opts.element.attr("readonly") === "readonly"; enabled = enabled && !readonly; if (this.enabled !== enabled) { if (enabled) { this.enable(); } else { this.disable(); } } syncCssClasses(this.container, this.opts.element, this.opts.adaptContainerCssClass); this.container.addClass(evaluate(this.opts.containerCssClass)); syncCssClasses(this.dropdown, this.opts.element, this.opts.adaptDropdownCssClass); this.dropdown.addClass(evaluate(this.opts.dropdownCssClass)); }); // mozilla and IE el.bind("propertychange.select2 DOMAttrModified.select2", sync); // safari and chrome if (typeof WebKitMutationObserver !== "undefined") { if (this.propertyObserver) { delete this.propertyObserver; this.propertyObserver = null; } this.propertyObserver = new WebKitMutationObserver(function (mutations) { mutations.forEach(sync); }); this.propertyObserver.observe(el.get(0), { attributes:true, subtree:false }); } }, // abstract triggerSelect: function(data) { var evt = $.Event("selected", { val: this.id(data), object: data }); this.opts.element.trigger(evt); return !evt.isDefaultPrevented(); }, /** * Triggers the change event on the source element */ // abstract triggerChange: function (details) { details = details || {}; details= $.extend({}, details, { type: "change", val: this.val() }); // prevents recursive triggering this.opts.element.data("select2-change-triggered", true); this.opts.element.trigger(details); this.opts.element.data("select2-change-triggered", false); // some validation frameworks ignore the change event and listen instead to keyup, click for selects // so here we trigger the click event manually this.opts.element.click(); // ValidationEngine ignorea the change event and listens instead to blur // so here we trigger the blur event manually if so desired if (this.opts.blurOnChange) this.opts.element.blur(); }, // abstract enable: function() { if (this.enabled) return; this.enabled=true; this.container.removeClass("select2-container-disabled"); this.opts.element.removeAttr("disabled"); }, // abstract disable: function() { if (!this.enabled) return; this.close(); this.enabled=false; this.container.addClass("select2-container-disabled"); this.opts.element.attr("disabled", "disabled"); }, // abstract opened: function () { return this.container.hasClass("select2-dropdown-open"); }, // abstract positionDropdown: function() { var $dropdown = this.dropdown, offset = this.container.offset(), height = this.container.outerHeight(false), width = this.container.outerWidth(false), dropHeight = $dropdown.outerHeight(false), viewPortRight = $(window).scrollLeft() + $(window).width(), viewportBottom = $(window).scrollTop() + $(window).height(), dropTop = offset.top + height, dropLeft = offset.left, enoughRoomBelow = dropTop + dropHeight <= viewportBottom, enoughRoomAbove = (offset.top - dropHeight) >= this.body().scrollTop(), dropWidth = $dropdown.outerWidth(false), enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight, aboveNow = $dropdown.hasClass("select2-drop-above"), bodyOffset, above, css, resultsListNode; if (this.opts.dropdownAutoWidth) { resultsListNode = $('.select2-results', $dropdown)[0]; $dropdown.addClass('select2-drop-auto-width'); $dropdown.css('width', ''); // Add scrollbar width to dropdown if vertical scrollbar is present dropWidth = $dropdown.outerWidth(false) + (resultsListNode.scrollHeight === resultsListNode.clientHeight ? 0 : scrollBarDimensions.width); dropWidth > width ? width = dropWidth : dropWidth = width; enoughRoomOnRight = dropLeft + dropWidth <= viewPortRight; } else { this.container.removeClass('select2-drop-auto-width'); } //console.log("below/ droptop:", dropTop, "dropHeight", dropHeight, "sum", (dropTop+dropHeight)+" viewport bottom", viewportBottom, "enough?", enoughRoomBelow); //console.log("above/ offset.top", offset.top, "dropHeight", dropHeight, "top", (offset.top-dropHeight), "scrollTop", this.body().scrollTop(), "enough?", enoughRoomAbove); // fix positioning when body has an offset and is not position: static if (this.body().css('position') !== 'static') { bodyOffset = this.body().offset(); dropTop -= bodyOffset.top; dropLeft -= bodyOffset.left; } // always prefer the current above/below alignment, unless there is not enough room if (aboveNow) { above = true; if (!enoughRoomAbove && enoughRoomBelow) above = false; } else { above = false; if (!enoughRoomBelow && enoughRoomAbove) above = true; } if (!enoughRoomOnRight) { dropLeft = offset.left + width - dropWidth; } if (above) { dropTop = offset.top - dropHeight; this.container.addClass("select2-drop-above"); $dropdown.addClass("select2-drop-above"); } else { this.container.removeClass("select2-drop-above"); $dropdown.removeClass("select2-drop-above"); } css = $.extend({ top: dropTop, left: dropLeft, width: width }, evaluate(this.opts.dropdownCss)); $dropdown.css(css); }, // abstract shouldOpen: function() { var event; if (this.opened()) return false; event = $.Event("opening"); this.opts.element.trigger(event); return !event.isDefaultPrevented(); }, // abstract clearDropdownAlignmentPreference: function() { // clear the classes used to figure out the preference of where the dropdown should be opened this.container.removeClass("select2-drop-above"); this.dropdown.removeClass("select2-drop-above"); }, /** * Opens the dropdown * * @return {Boolean} whether or not dropdown was opened. This method will return false if, for example, * the dropdown is already open, or if the 'open' event listener on the element called preventDefault(). */ // abstract open: function () { if (!this.shouldOpen()) return false; window.setTimeout(this.bind(this.opening), 1); return true; }, /** * Performs the opening of the dropdown */ // abstract opening: function() { var cid = this.containerId, scroll = "scroll." + cid, resize = "resize."+cid, orient = "orientationchange."+cid, mask; this.container.addClass("select2-dropdown-open").addClass("select2-container-active"); this.clearDropdownAlignmentPreference(); if(this.dropdown[0] !== this.body().children().last()[0]) { this.dropdown.detach().appendTo(this.body()); } // create the dropdown mask if doesnt already exist mask = $("#select2-drop-mask"); if (mask.length == 0) { mask = $(document.createElement("div")); mask.attr("id","select2-drop-mask").attr("class","select2-drop-mask"); mask.hide(); mask.appendTo(this.body()); mask.bind("mousedown touchstart", function (e) { var dropdown = $("#select2-drop"), self; if (dropdown.length > 0) { self=dropdown.data("select2"); if (self.opts.selectOnBlur) { self.selectHighlighted({noFocus: true}); } self.close(); } }); } // ensure the mask is always right before the dropdown if (this.dropdown.prev()[0] !== mask[0]) { this.dropdown.before(mask); } // move the global id to the correct dropdown $("#select2-drop").removeAttr("id"); this.dropdown.attr("id", "select2-drop"); // show the elements mask.css(_makeMaskCss()); mask.show(); this.dropdown.show(); this.positionDropdown(); this.dropdown.addClass("select2-drop-active"); this.ensureHighlightVisible(); // attach listeners to events that can change the position of the container and thus require // the position of the dropdown to be updated as well so it does not come unglued from the container var that = this; this.container.parents().add(window).each(function () { $(this).bind(resize+" "+scroll+" "+orient, function (e) { $("#select2-drop-mask").css(_makeMaskCss()); that.positionDropdown(); }); }); function _makeMaskCss() { return { width : Math.max(document.documentElement.scrollWidth, $(window).width()), height : Math.max(document.documentElement.scrollHeight, $(window).height()) } } }, // abstract close: function () { if (!this.opened()) return; var cid = this.containerId, scroll = "scroll." + cid, resize = "resize."+cid, orient = "orientationchange."+cid; // unbind event listeners this.container.parents().add(window).each(function () { $(this).unbind(scroll).unbind(resize).unbind(orient); }); this.clearDropdownAlignmentPreference(); $("#select2-drop-mask").hide(); this.dropdown.removeAttr("id"); // only the active dropdown has the select2-drop id this.dropdown.hide(); this.container.removeClass("select2-dropdown-open"); this.results.empty(); this.clearSearch(); this.search.removeClass("select2-active"); this.opts.element.trigger($.Event("close")); }, // abstract clearSearch: function () { }, //abstract getMaximumSelectionSize: function() { return evaluate(this.opts.maximumSelectionSize); }, // abstract ensureHighlightVisible: function () { var results = this.results, children, index, child, hb, rb, y, more; index = this.highlight(); if (index < 0) return; if (index == 0) { // if the first element is highlighted scroll all the way to the top, // that way any unselectable headers above it will also be scrolled // into view results.scrollTop(0); return; } children = this.findHighlightableChoices().find('.select2-result-label'); child = $(children[index]); hb = child.offset().top + child.outerHeight(true); // if this is the last child lets also make sure select2-more-results is visible if (index === children.length - 1) { more = results.find("li.select2-more-results"); if (more.length > 0) { hb = more.offset().top + more.outerHeight(true); } } rb = results.offset().top + results.outerHeight(true); if (hb > rb) { results.scrollTop(results.scrollTop() + (hb - rb)); } y = child.offset().top - results.offset().top; // make sure the top of the element is visible if (y < 0 && child.css('display') != 'none' ) { results.scrollTop(results.scrollTop() + y); // y is negative } }, // abstract findHighlightableChoices: function() { return this.results.find(".select2-result-selectable:not(.select2-selected):not(.select2-disabled)"); }, // abstract moveHighlight: function (delta) { var choices = this.findHighlightableChoices(), index = this.highlight(); while (index > -1 && index < choices.length) { index += delta; var choice = $(choices[index]); if (choice.hasClass("select2-result-selectable") && !choice.hasClass("select2-disabled") && !choice.hasClass("select2-selected")) { this.highlight(index); break; } } }, // abstract highlight: function (index) { var choices = this.findHighlightableChoices(), choice, data; if (arguments.length === 0) { return indexOf(choices.filter(".select2-highlighted")[0], choices.get()); } if (index >= choices.length) index = choices.length - 1; if (index < 0) index = 0; this.results.find(".select2-highlighted").removeClass("select2-highlighted"); choice = $(choices[index]); choice.addClass("select2-highlighted"); this.ensureHighlightVisible(); data = choice.data("select2-data"); if (data) { this.opts.element.trigger({ type: "highlight", val: this.id(data), choice: data }); } }, // abstract countSelectableResults: function() { return this.findHighlightableChoices().length; }, // abstract highlightUnderEvent: function (event) { var el = $(event.target).closest(".select2-result-selectable"); if (el.length > 0 && !el.is(".select2-highlighted")) { var choices = this.findHighlightableChoices(); this.highlight(choices.index(el)); } else if (el.length == 0) { // if we are over an unselectable item remove al highlights this.results.find(".select2-highlighted").removeClass("select2-highlighted"); } }, // abstract loadMoreIfNeeded: function () { var results = this.results, more = results.find("li.select2-more-results"), below, // pixels the element is below the scroll fold, below==0 is when the element is starting to be visible offset = -1, // index of first element without data page = this.resultsPage + 1, self=this, term=this.search.val(), context=this.context; if (more.length === 0) return; below = more.offset().top - results.offset().top - results.height(); if (below <= this.opts.loadMorePadding) { more.addClass("select2-active"); this.opts.query({ element: this.opts.element, term: term, page: page, context: context, matcher: this.opts.matcher, callback: this.bind(function (data) { // ignore a response if the select2 has been closed before it was received if (!self.opened()) return; self.opts.populateResults.call(this, results, data.results, {term: term, page: page, context:context}); self.postprocessResults(data, false, false); if (data.more===true) { more.detach().appendTo(results).text(self.opts.formatLoadMore(page+1)); window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10); } else { more.remove(); } self.positionDropdown(); self.resultsPage = page; self.context = data.context; })}); } }, /** * Default tokenizer function which does nothing */ tokenize: function() { }, /** * @param initial whether or not this is the call to this method right after the dropdown has been opened */ // abstract updateResults: function (initial) { var search = this.search, results = this.results, opts = this.opts, data, self = this, input, term = search.val(), lastTerm=$.data(this.container, "select2-last-term"); // prevent duplicate queries against the same term if (initial !== true && lastTerm && equal(term, lastTerm)) return; $.data(this.container, "select2-last-term", term); // if the search is currently hidden we do not alter the results if (initial !== true && (this.showSearchInput === false || !this.opened())) { return; } function postRender() { results.scrollTop(0); search.removeClass("select2-active"); self.positionDropdown(); } function render(html) { results.html(html); postRender(); } var maxSelSize = this.getMaximumSelectionSize(); if (maxSelSize >=1) { data = this.data(); if ($.isArray(data) && data.length >= maxSelSize && checkFormatter(opts.formatSelectionTooBig, "formatSelectionTooBig")) { render("<li class='select2-selection-limit'>" + opts.formatSelectionTooBig(maxSelSize) + "</li>"); return; } } if (search.val().length < opts.minimumInputLength) { if (checkFormatter(opts.formatInputTooShort, "formatInputTooShort")) { render("<li class='select2-no-results'>" + opts.formatInputTooShort(search.val(), opts.minimumInputLength) + "</li>"); } else { render(""); } return; } if (opts.maximumInputLength && search.val().length > opts.maximumInputLength) { if (checkFormatter(opts.formatInputTooLong, "formatInputTooLong")) { render("<li class='select2-no-results'>" + opts.formatInputTooLong(search.val(), opts.maximumInputLength) + "</li>"); } else { render(""); } return; } if (opts.formatSearching && this.findHighlightableChoices().length === 0) { render("<li class='select2-searching'>" + opts.formatSearching() + "</li>"); } search.addClass("select2-active"); // give the tokenizer a chance to pre-process the input input = this.tokenize(); if (input != undefined && input != null) { search.val(input); } this.resultsPage = 1; opts.query({ element: opts.element, term: search.val(), page: this.resultsPage, context: null, matcher: opts.matcher, callback: this.bind(function (data) { var def; // default choice // ignore a response if the select2 has been closed before it was received if (!this.opened()) { this.search.removeClass("select2-active"); return; } // 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); if (def !== undefined && def !== null && self.id(def) !== undefined && self.id(def) !== null) { if ($(data.results).filter( function () { return equal(self.id(this), self.id(def)); }).length === 0) { data.results.unshift(def); } } } if (data.results.length === 0 && checkFormatter(opts.formatNoMatches, "formatNoMatches")) { render("<li class='select2-no-results'>" + opts.formatNoMatches(search.val()) + "</li>"); return; } results.empty(); self.opts.populateResults.call(this, results, data.results, {term: search.val(), page: this.resultsPage, context:null}); if (data.more === true && checkFormatter(opts.formatLoadMore, "formatLoadMore")) { results.append("<li class='select2-more-results'>" + self.opts.escapeMarkup(opts.formatLoadMore(this.resultsPage)) + "</li>"); window.setTimeout(function() { self.loadMoreIfNeeded(); }, 10); } this.postprocessResults(data, initial); postRender(); this.opts.element.trigger({ type: "loaded", data:data }); })}); }, // abstract cancel: function () { this.close(); }, // abstract blur: function () { // if selectOnBlur == true, select the currently highlighted option if (this.opts.selectOnBlur) this.selectHighlighted({noFocus: true}); this.close(); this.container.removeClass("select2-container-active"); // synonymous to .is(':focus'), which is available in jquery >= 1.6 if (this.search[0] === document.activeElement) { this.search.blur(); } this.clearSearch(); this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); }, // abstract focusSearch: function () { focus(this.search); }, // abstract selectHighlighted: function (options) { var index=this.highlight(), highlighted=this.results.find(".select2-highlighted"), data = highlighted.closest('.select2-result').data("select2-data"); if (data) { this.highlight(index); this.onSelect(data, options); } }, // abstract getPlaceholder: function () { return this.opts.element.attr("placeholder") || this.opts.element.attr("data-placeholder") || // jquery 1.4 compat this.opts.element.data("placeholder") || this.opts.placeholder; }, /** * Get the desired width for the container element. This is * derived first from option `width` passed to select2, then * the inline 'style' on the original element, and finally * falls back to the jQuery calculated element width. */ // abstract initContainerWidth: function () { function resolveContainerWidth() { var style, attrs, matches, i, l; if (this.opts.width === "off") { return null; } else if (this.opts.width === "element"){ return this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px'; } else if (this.opts.width === "copy" || this.opts.width === "resolve") { // check if there is inline style on the element that contains width style = this.opts.element.attr('style'); if (style !== undefined) { attrs = style.split(';'); for (i = 0, l = attrs.length; i < l; i = i + 1) { matches = attrs[i].replace(/\s/g, '') .match(/width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i); if (matches !== null && matches.length >= 1) return matches[1]; } } // next check if css('width') can resolve a width that is percent based, this is sometimes possible // when attached to input type=hidden or elements hidden via css style = this.opts.element.css('width'); if (style && style.length > 0) return style; if (this.opts.width === "resolve") { // finally, fallback on the calculated width of the element return (this.opts.element.outerWidth(false) === 0 ? 'auto' : this.opts.element.outerWidth(false) + 'px'); } return null; } else if ($.isFunction(this.opts.width)) { return this.opts.width(); } else { return this.opts.width; } }; var width = resolveContainerWidth.call(this); if (width !== null) { this.container.css("width", width); } } }); SingleSelect2 = clazz(AbstractSelect2, { // single createContainer: function () { var container = $(document.createElement("div")).attr({ "class": "select2-container" }).html([ "<a href='javascript:void(0)' onclick='return false;' class='select2-choice' tabindex='-1'>", " <span> </span><abbr class='select2-search-choice-close select2-display-none'></abbr>", " <div><b></b></div>" , "</a>", "<input class='select2-focusser select2-offscreen' type='text'/>", "<div class='select2-drop select2-display-none'>" , " <div class='select2-search'>" , " <input type='text' autocomplete='off' class='select2-input'/>" , " </div>" , " <ul class='select2-results'>" , " </ul>" , "</div>"].join("")); return container; }, // single disable: function() { if (!this.enabled) return; this.parent.disable.apply(this, arguments); this.focusser.attr("disabled", "disabled"); }, // single enable: function() { if (this.enabled) return; this.parent.enable.apply(this, arguments); this.focusser.removeAttr("disabled"); }, // single opening: function () { this.parent.opening.apply(this, arguments); if (this.showSearchInput !== false) { this.search.val(this.focusser.val()); } this.search.focus(); this.focusser.attr("disabled", "disabled").val(""); this.updateResults(true); this.opts.element.trigger($.Event("open")); }, // single close: function () { if (!this.opened()) return; this.parent.close.apply(this, arguments); this.focusser.removeAttr("disabled"); focus(this.focusser); }, // single focus: function () { if (this.opened()) { this.close(); } else { this.focusser.removeAttr("disabled"); this.focusser.focus(); } }, // single isFocused: function () { return this.container.hasClass("select2-container-active"); }, // single cancel: function () { this.parent.cancel.apply(this, arguments); this.focusser.removeAttr("disabled"); this.focusser.focus(); }, // single initContainer: function () { var selection, container = this.container, dropdown = this.dropdown, clickingInside = false; this.showSearch(this.opts.minimumResultsForSearch >= 0); this.selection = selection = container.find(".select2-choice"); this.focusser = container.find(".select2-focusser"); // rewrite labels from original element to focusser this.focusser.attr("id", "s2id_autogen"+nextUid()); $("label[for='" + this.opts.element.attr("id") + "']") .attr('for', this.focusser.attr('id')); this.focusser.attr("tabindex", this.elementTabIndex); this.search.bind("keydown", this.bind(function (e) { if (!this.enabled) return; if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { // prevent the page from scrolling killEvent(e); return; } switch (e.which) { case KEY.UP: case KEY.DOWN: this.moveHighlight((e.which === KEY.UP) ? -1 : 1); killEvent(e); return; case KEY.TAB: case KEY.ENTER: this.selectHighlighted(); killEvent(e); return; case KEY.ESC: this.cancel(e); killEvent(e); return; } })); this.search.bind("blur", this.bind(function(e) { // a workaround for chrome to keep the search field focussed when the scroll bar is used to scroll the dropdown. // without this the search field loses focus which is annoying if (document.activeElement === this.body().get(0)) { window.setTimeout(this.bind(function() { this.search.focus(); }), 0); } })); this.focusser.bind("keydown", this.bind(function (e) { if (!this.enabled) return; if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.ESC) { return; } if (this.opts.openOnEnter === false && e.which === KEY.ENTER) { killEvent(e); return; } if (e.which == KEY.DOWN || e.which == KEY.UP || (e.which == KEY.ENTER && this.opts.openOnEnter)) { this.open(); killEvent(e); return; } if (e.which == KEY.DELETE || e.which == KEY.BACKSPACE) { if (this.opts.allowClear) { this.clear(); } killEvent(e); return; } })); installKeyUpChangeEvent(this.focusser); this.focusser.bind("keyup-change input", this.bind(function(e) { e.stopPropagation(); if (this.opened()) return; this.open(); })); selection.delegate("abbr", "mousedown", this.bind(function (e) { if (!this.enabled) return; this.clear(); killEventImmediately(e); this.close(); this.selection.focus(); })); selection.bind("mousedown", this.bind(function (e) { clickingInside = true; if (this.opened()) { this.close(); } else if (this.enabled) { this.open(); } killEvent(e); clickingInside = false; })); dropdown.bind("mousedown", this.bind(function() { this.search.focus(); })); selection.bind("focus", this.bind(function(e) { killEvent(e); })); this.focusser.bind("focus", this.bind(function(){ this.container.addClass("select2-container-active"); })).bind("blur", this.bind(function() { if (!this.opened()) { this.container.removeClass("select2-container-active"); } })); this.search.bind("focus", this.bind(function(){ this.container.addClass("select2-container-active"); })); this.initContainerWidth(); this.opts.element.addClass("select2-offscreen"); this.setPlaceholder(); }, // single clear: function(triggerChange) { var data=this.selection.data("select2-data"); if (data) { // guard against queued quick consecutive clicks this.opts.element.val(""); this.selection.find("span").empty(); this.selection.removeData("select2-data"); this.setPlaceholder(); if (triggerChange !== false){ this.opts.element.trigger({ type: "removed", val: this.id(data), choice: data }); this.triggerChange({removed:data}); } } }, /** * Sets selection based on source element's value */ // single initSelection: function () { var selected; if (this.opts.element.val() === "" && this.opts.element.text() === "") { this.updateSelection([]); this.close(); this.setPlaceholder(); } else { var self = this; this.opts.initSelection.call(null, this.opts.element, function(selected){ if (selected !== undefined && selected !== null) { self.updateSelection(selected); self.close(); self.setPlaceholder(); } }); } }, // single prepareOpts: function () { var opts = this.parent.prepareOpts.apply(this, arguments), self=this; if (opts.element.get(0).tagName.toLowerCase() === "select") { // install the selection initializer opts.initSelection = function (element, callback) { var selected = element.find(":selected"); // a single select box always has a value, no need to null check 'selected' callback(self.optionToData(selected)); }; } else if ("data" in opts) { // install default initSelection when applied to hidden input and data is local opts.initSelection = opts.initSelection || function (element, callback) { var id = element.val(); //search in data by id, storing the actual matching item var match = null; opts.query({ matcher: function(term, text, el){ var is_match = equal(id, opts.id(el)); if (is_match) { match = el; } return is_match; }, callback: !$.isFunction(callback) ? $.noop : function() { callback(match); } }); }; } return opts; }, // single getPlaceholder: function() { // if a placeholder is specified on a single select without the first empty option ignore it if (this.select) { if (this.select.find("option").first().text() !== "") { return undefined; } } return this.parent.getPlaceholder.apply(this, arguments); }, // single setPlaceholder: function () { var placeholder = this.getPlaceholder(); if (this.opts.element.val() === "" && placeholder !== undefined) { // check for a first blank option if attached to a select if (this.select && this.select.find("option:first").text() !== "") return; this.selection.find("span").html(this.opts.escapeMarkup(placeholder)); this.selection.addClass("select2-default"); this.container.removeClass("select2-allowclear"); this.selection.find("abbr").hide(); } }, // single postprocessResults: function (data, initial, noHighlightUpdate) { var selected = 0, self = this, showSearchInput = true; // find the selected element in the result list this.findHighlightableChoices().each2(function (i, elm) { if (equal(self.id(elm.data("select2-data")), self.opts.element.val())) { selected = i; return false; } }); // and highlight it if (noHighlightUpdate !== false) { this.highlight(selected); } // hide the search box if this is the first we got the results and there are a few of them if (initial === true) { var min=this.opts.minimumResultsForSearch; showSearchInput = min < 0 ? false : countResults(data.results) >= min; this.showSearch(showSearchInput); } }, // single showSearch: function(showSearchInput) { this.showSearchInput = showSearchInput; this.dropdown.find(".select2-search")[showSearchInput ? "removeClass" : "addClass"]("select2-search-hidden"); //add "select2-with-searchbox" to the container if search box is shown $(this.dropdown, this.container)[showSearchInput ? "addClass" : "removeClass"]("select2-with-searchbox"); }, // single onSelect: function (data, options) { if (!this.triggerSelect(data)) { return; } var old = this.opts.element.val(), oldData = this.data(); this.opts.element.val(this.id(data)); this.updateSelection(data); this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data }); this.close(); if (!options || !options.noFocus) this.selection.focus(); if (!equal(old, this.id(data))) { this.triggerChange({added:data,removed:oldData}); } }, // single updateSelection: function (data) { var container=this.selection.find("span"), formatted; this.selection.data("select2-data", data); container.empty(); formatted=this.opts.formatSelection(data, container); if (formatted !== undefined) { container.append(this.opts.escapeMarkup(formatted)); } this.selection.removeClass("select2-default"); if (this.opts.allowClear && this.getPlaceholder() !== undefined) { this.container.addClass("select2-allowclear"); this.selection.find("abbr").show(); } }, // single val: function () { var val, triggerChange = false, data = null, self = this, oldData = this.data(); if (arguments.length === 0) { return this.opts.element.val(); } val = arguments[0]; if (arguments.length > 1) { triggerChange = arguments[1]; } if (this.select) { this.select .val(val) .find(":selected").each2(function (i, elm) { data = self.optionToData(elm); return false; }); this.updateSelection(data); this.setPlaceholder(); if (triggerChange) { this.triggerChange({added: data, removed:oldData}); } } else { if (this.opts.initSelection === undefined) { throw new Error("cannot call val() if initSelection() is not defined"); } // val is an id. !val is true for [undefined,null,'',0] - 0 is legal if (!val && val !== 0) { this.clear(triggerChange); return; } this.opts.element.val(val); this.opts.initSelection(this.opts.element, function(data){ self.opts.element.val(!data ? "" : self.id(data)); self.updateSelection(data); self.setPlaceholder(); if (triggerChange) { self.triggerChange({added: data, removed:oldData}); } }); } }, // single clearSearch: function () { this.search.val(""); this.focusser.val(""); }, // single data: function(value, triggerChange) { var data; if (arguments.length === 0) { data = this.selection.data("select2-data"); if (data == undefined) data = null; return data; } else { if (!value || value === "") { this.clear(triggerChange); } else { data = this.data(); this.opts.element.val(!value ? "" : this.id(value)); this.updateSelection(value); if (triggerChange) { this.triggerChange({added: value, removed:data}); } } } } }); MultiSelect2 = clazz(AbstractSelect2, { // multi createContainer: function () { var container = $(document.createElement("div")).attr({ "class": "select2-container select2-container-multi" }).html([ " <ul class='select2-choices'>", //"<li class='select2-search-choice'><span>California</span><a href="javascript:void(0)" class="select2-search-choice-close"></a></li>" , " <li class='select2-search-field'>" , " <input type='text' autocomplete='off' class='select2-input'>" , " </li>" , "</ul>" , "<div class='select2-drop select2-drop-multi select2-display-none'>" , " <ul class='select2-results'>" , " </ul>" , "</div>"].join("")); return container; }, // multi prepareOpts: function () { var opts = this.parent.prepareOpts.apply(this, arguments), self=this; // TODO validate placeholder is a string if specified if (opts.element.get(0).tagName.toLowerCase() === "select") { // install sthe selection initializer opts.initSelection = function (element, callback) { var data = []; element.find(":selected").each2(function (i, elm) { data.push(self.optionToData(elm)); }); callback(data); }; } else if ("data" in opts) { // install default initSelection when applied to hidden input and data is local opts.initSelection = opts.initSelection || function (element, callback) { var ids = splitVal(element.val(), opts.separator); //search in data by array of ids, storing matching items in a list var matches = []; opts.query({ matcher: function(term, text, el){ var is_match = $.grep(ids, function(id) { return equal(id, opts.id(el)); }).length; if (is_match) { matches.push(el); } return is_match; }, callback: !$.isFunction(callback) ? $.noop : function() { callback(matches); } }); }; } return opts; }, // multi initContainer: function () { var selector = ".select2-choices", selection; this.searchContainer = this.container.find(".select2-search-field"); this.selection = selection = this.container.find(selector); // rewrite labels from original element to focusser this.search.attr("id", "s2id_autogen"+nextUid()); $("label[for='" + this.opts.element.attr("id") + "']") .attr('for', this.search.attr('id')); this.search.bind("input paste", this.bind(function() { if (!this.enabled) return; if (!this.opened()) { this.open(); } })); this.search.attr("tabindex", this.elementTabIndex); this.search.bind("keydown", this.bind(function (e) { if (!this.enabled) return; if (e.which === KEY.BACKSPACE && this.search.val() === "") { this.close(); var choices, selected = selection.find(".select2-search-choice-focus"); if (selected.length > 0) { this.unselect(selected.first()); this.search.width(10); killEvent(e); return; } choices = selection.find(".select2-search-choice:not(.select2-locked)"); if (choices.length > 0) { choices.last().addClass("select2-search-choice-focus"); } } else { selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); } if (this.opened()) { switch (e.which) { case KEY.UP: case KEY.DOWN: this.moveHighlight((e.which === KEY.UP) ? -1 : 1); killEvent(e); return; case KEY.ENTER: case KEY.TAB: this.selectHighlighted(); killEvent(e); return; case KEY.ESC: this.cancel(e); killEvent(e); return; } } if (e.which === KEY.TAB || KEY.isControl(e) || KEY.isFunctionKey(e) || e.which === KEY.BACKSPACE || e.which === KEY.ESC) { return; } if (e.which === KEY.ENTER) { if (this.opts.openOnEnter === false) { return; } else if (e.altKey || e.ctrlKey || e.shiftKey || e.metaKey) { return; } } this.open(); if (e.which === KEY.PAGE_UP || e.which === KEY.PAGE_DOWN) { // prevent the page from scrolling killEvent(e); } if (e.which === KEY.ENTER) { // prevent form from being submitted killEvent(e); } })); this.search.bind("keyup", this.bind(this.resizeSearch)); this.search.bind("blur", this.bind(function(e) { this.container.removeClass("select2-container-active"); this.search.removeClass("select2-focused"); this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); if (!this.opened()) this.clearSearch(); e.stopImmediatePropagation(); })); this.container.delegate(selector, "mousedown", this.bind(function (e) { if (!this.enabled) return; if ($(e.target).closest(".select2-search-choice").length > 0) { // clicked inside a select2 search choice, do not open return; } this.clearPlaceholder(); this.open(); this.focusSearch(); e.preventDefault(); })); 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(); })); this.initContainerWidth(); this.opts.element.addClass("select2-offscreen"); // set the placeholder if necessary this.clearSearch(); }, // multi enable: function() { if (this.enabled) return; this.parent.enable.apply(this, arguments); this.search.removeAttr("disabled"); }, // multi disable: function() { if (!this.enabled) return; this.parent.disable.apply(this, arguments); this.search.attr("disabled", true); }, // multi initSelection: function () { var data; if (this.opts.element.val() === "" && this.opts.element.text() === "") { this.updateSelection([]); this.close(); // set the placeholder if necessary this.clearSearch(); } if (this.select || this.opts.element.val() !== "") { var self = this; this.opts.initSelection.call(null, this.opts.element, function(data){ if (data !== undefined && data !== null) { self.updateSelection(data); self.close(); // set the placeholder if necessary self.clearSearch(); } }); } }, // multi clearSearch: function () { var placeholder = this.getPlaceholder(), maxWidth = this.getMaxSearchWidth(); 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 // we could call this.resizeSearch(), but we do not because that requires a sizer and we do not want to create one so early because of a firefox bug, see #944 this.search.width(maxWidth > 0 ? maxWidth : this.container.css("width")); } else { this.search.val("").width(10); } }, // multi clearPlaceholder: function () { if (this.search.hasClass("select2-default")) { this.search.val("").removeClass("select2-default"); } }, // multi opening: function () { this.clearPlaceholder(); // should be done before super so placeholder is not used to search this.resizeSearch(); this.parent.opening.apply(this, arguments); this.focusSearch(); this.updateResults(true); this.search.focus(); this.opts.element.trigger($.Event("open")); }, // multi close: function () { if (!this.opened()) return; this.parent.close.apply(this, arguments); }, // multi focus: function () { this.close(); this.search.focus(); //this.opts.element.triggerHandler("focus"); }, // multi isFocused: function () { return this.search.hasClass("select2-focused"); }, // multi updateSelection: function (data) { var ids = [], filtered = [], self = this; // filter out duplicates $(data).each(function () { if (indexOf(self.id(this), ids) < 0) { ids.push(self.id(this)); filtered.push(this); } }); data = filtered; this.selection.find(".select2-search-choice").remove(); $(data).each(function () { self.addSelectedChoice(this); }); self.postprocessResults(); }, // multi tokenize: function() { var input = this.search.val(); input = this.opts.tokenizer(input, this.data(), this.bind(this.onSelect), this.opts); if (input != null && input != undefined) { this.search.val(input); if (input.length > 0) { this.open(); } } }, // multi onSelect: function (data, options) { if (!this.triggerSelect(data)) { return; } this.addSelectedChoice(data); this.opts.element.trigger({ type: "selected", val: this.id(data), choice: data }); if (this.select || !this.opts.closeOnSelect) this.postprocessResults(); if (this.opts.closeOnSelect) { this.close(); this.search.width(10); } else { if (this.countSelectableResults()>0) { this.search.width(10); this.resizeSearch(); if (this.getMaximumSelectionSize() > 0 && this.val().length >= this.getMaximumSelectionSize()) { // if we reached max selection size repaint the results so choices // are replaced with the max selection reached message this.updateResults(true); } this.positionDropdown(); } else { // if nothing left to select close this.close(); this.search.width(10); } } // since its not possible to select an element that has already been // added we do not need to check if this is a new element before firing change this.triggerChange({ added: data }); if (!options || !options.noFocus) this.focusSearch(); }, // multi cancel: function () { this.close(); this.focusSearch(); }, addSelectedChoice: function (data) { var enableChoice = !data.locked, enabledItem = $( "<li class='select2-search-choice'>" + " <div></div>" + " <a href='#' onclick='return false;' class='select2-search-choice-close' tabindex='-1'></a>" + "</li>"), disabledItem = $( "<li class='select2-search-choice select2-locked'>" + "<div></div>" + "</li>"); var choice = enableChoice ? enabledItem : disabledItem, id = this.id(data), val = this.getVal(), formatted; formatted = this.opts.formatSelection(data, choice.find("div")); if (formatted != undefined) { //choice.find("div").replaceWith("<div>" + this.opts.escapeMarkup(formatted) + "</div>"); choice.find("div").replaceWith("<div>" + formatted + "</div>"); $.trigger({ type: "cem", val: data, choice: $(choice.find("div")[0])}); } if(enableChoice){ choice.find(".select2-search-choice-close") .bind("mousedown", killEvent) .bind("click dblclick", this.bind(function (e) { if (!this.enabled) return; $(e.target).closest(".select2-search-choice").fadeOut('fast', this.bind(function(){ this.unselect($(e.target)); this.selection.find(".select2-search-choice-focus").removeClass("select2-search-choice-focus"); this.close(); this.focusSearch(); })).dequeue(); killEvent(e); })).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); choice.insertBefore(this.searchContainer); val.push(id); this.setVal(val); }, // multi unselect: function (selected) { var val = this.getVal(), data, index; selected = selected.closest(".select2-search-choice"); if (selected.length === 0) { throw "Invalid argument: " + selected + ". Must be .select2-search-choice"; } data = selected.data("select2-data"); if (!data) { // prevent a race condition when the 'x' is clicked really fast repeatedly the event can be queued // and invoked on an element already removed return; } index = indexOf(this.id(data), val); if (index >= 0) { val.splice(index, 1); this.setVal(val); if (this.select) this.postprocessResults(); } selected.remove(); this.opts.element.trigger({ type: "removed", val: this.id(data), choice: data }); this.triggerChange({ removed: data }); }, // multi postprocessResults: function () { var val = this.getVal(), choices = this.results.find(".select2-result"), compound = this.results.find(".select2-result-with-children"), self = this; choices.each2(function (i, choice) { var id = self.id(choice.data("select2-data")); if (indexOf(id, val) >= 0) { choice.addClass("select2-selected"); // mark all children of the selected parent as selected choice.find(".select2-result-selectable").addClass("select2-selected"); } }); compound.each2(function(i, choice) { // hide an optgroup if it doesnt have any selectable children if (!choice.is('.select2-result-selectable') && choice.find(".select2-result-selectable:not(.select2-selected)").length === 0) { choice.addClass("select2-selected"); } }); if (this.highlight() == -1){ self.highlight(0); } }, // multi getMaxSearchWidth: function() { return this.selection.width() - getSideBorderPadding(this.search); }, // multi resizeSearch: function () { var minimumWidth, left, maxWidth, containerLeft, searchWidth, sideBorderPadding = getSideBorderPadding(this.search); minimumWidth = measureTextWidth(this.search) + 10; left = this.search.offset().left; maxWidth = this.selection.width(); containerLeft = this.selection.offset().left; searchWidth = maxWidth - (left - containerLeft) - sideBorderPadding; if (searchWidth < minimumWidth) { searchWidth = maxWidth - sideBorderPadding; } if (searchWidth < 40) { searchWidth = maxWidth - sideBorderPadding; } if (searchWidth <= 0) { searchWidth = minimumWidth; } this.search.width(searchWidth); }, // multi getVal: function () { var val; if (this.select) { val = this.select.val(); return val === null ? [] : val; } else { val = this.opts.element.val(); return splitVal(val, this.opts.separator); } }, // multi setVal: function (val) { var unique; if (this.select) { this.select.val(val); } else { unique = []; // filter out duplicates $(val).each(function () { if (indexOf(this, unique) < 0) unique.push(this); }); this.opts.element.val(unique.length === 0 ? "" : unique.join(this.opts.separator)); } }, // multi buildChangeDetails: function (old, current) { var current = current.slice(0), old = old.slice(0); // remove intersection from each array for (var i = 0; i < current.length; i++) { for (var j = 0; j < old.length; j++) { if (equal(this.opts.id(current[i]), this.opts.id(old[j]))) { current.splice(i, 1); i--; old.splice(j, 1); j--; } } } return {added: current, removed: old}; }, // multi val: function (val, triggerChange) { var oldData, self=this, changeDetails; if (arguments.length === 0) { return this.getVal(); } oldData=this.data(); if (!oldData.length) oldData=[]; // val is an id. !val is true for [undefined,null,'',0] - 0 is legal if (!val && val !== 0) { this.opts.element.val(""); this.updateSelection([]); this.clearSearch(); if (triggerChange) { this.triggerChange({added: this.data(), removed: oldData}); } return; } // val is a list of ids this.setVal(val); if (this.select) { this.opts.initSelection(this.select, this.bind(this.updateSelection)); if (triggerChange) { this.triggerChange(this.buildChangeDetails(oldData, this.data())); } } else { if (this.opts.initSelection === undefined) { throw new Error("val() cannot be called if initSelection() is not defined"); } this.opts.initSelection(this.opts.element, function(data){ var ids=$(data).map(self.id); self.setVal(ids); self.updateSelection(data); self.clearSearch(); if (triggerChange) { self.triggerChange(this.buildChangeDetails(oldData, this.data())); } }); } this.clearSearch(); }, // multi onSortStart: function() { if (this.select) { throw new Error("Sorting of elements is not supported when attached to <select>. Attach to <input type='hidden'/> instead."); } // collapse search field into 0 width so its container can be collapsed as well this.search.width(0); // hide the container this.searchContainer.hide(); }, // multi onSortEnd:function() { var val=[], self=this; // show search and move it to the end of the list this.searchContainer.show(); // make sure the search container is the last item in the list this.searchContainer.appendTo(this.searchContainer.parent()); // since we collapsed the width in dragStarted, we resize it here this.resizeSearch(); // update selection this.selection.find(".select2-search-choice").each(function() { val.push(self.opts.id($(this).data("select2-data"))); }); this.setVal(val); this.triggerChange(); }, // multi data: function(values, triggerChange) { var self=this, ids, old; if (arguments.length === 0) { return this.selection .find(".select2-search-choice") .map(function() { return $(this).data("select2-data"); }) .get(); } else { old = this.data(); if (!values) { values = []; } ids = $.map(values, function(e) { return self.opts.id(e); }); this.setVal(ids); this.updateSelection(values); this.clearSearch(); if (triggerChange) { this.triggerChange(this.buildChangeDetails(old, this.data())); } } } }); $.fn.select2 = function () { var args = Array.prototype.slice.call(arguments, 0), opts, select2, value, multiple, allowedMethods = ["val", "destroy", "opened", "open", "close", "focus", "isFocused", "container", "onSortStart", "onSortEnd", "enable", "disable", "positionDropdown", "data","cem"]; this.each(function () { if (args.length === 0 || typeof(args[0]) === "object") { opts = args.length === 0 ? {} : $.extend({}, args[0]); opts.element = $(this); if (opts.element.get(0).tagName.toLowerCase() === "select") { multiple = opts.element.attr("multiple"); } else { multiple = opts.multiple || false; if ("tags" in opts) {opts.multiple = multiple = true;} } select2 = multiple ? new MultiSelect2() : new SingleSelect2(); select2.init(opts); } else if (typeof(args[0]) === "string") { if (indexOf(args[0], allowedMethods) < 0) { throw "Unknown method: " + args[0]; } value = undefined; select2 = $(this).data("select2"); if (select2 === undefined) return; if (args[0] === "container") { value=select2.container; } else { value = select2[args[0]].apply(select2, args.slice(1)); } if (value !== undefined) {return false;} } else { throw "Invalid arguments to select2 plugin: " + args; } }); return (value === undefined) ? this : value; }; // plugin defaults, accessible to users $.fn.select2.defaults = { width: "copy", loadMorePadding: 0, closeOnSelect: true, openOnEnter: true, containerCss: {}, dropdownCss: {}, containerCssClass: "", dropdownCssClass: "", formatResult: function(result, container, query, escapeMarkup) { var markup=[]; markMatch(result.text, query.term, markup, escapeMarkup); return markup.join(""); }, formatSelection: function (data, container) { return data ? data.text : undefined; }, sortResults: function (results, container, query) { return results; }, formatResultCssClass: function(data) {return undefined;}, formatNoMatches: function () { return "No matches found"; }, formatInputTooShort: function (input, min) { var n = min - input.length; return "Please enter " + n + " more character" + (n == 1? "" : "s"); }, formatInputTooLong: function (input, max) { var n = input.length - max; return "Please delete " + n + " character" + (n == 1? "" : "s"); }, formatSelectionTooBig: function (limit) { return "You can only select " + limit + " item" + (limit == 1 ? "" : "s"); }, formatLoadMore: function (pageNumber) { return "Loading more results..."; }, formatSearching: function () { return "Searching..."; }, minimumResultsForSearch: 0, minimumInputLength: 0, maximumInputLength: null, maximumSelectionSize: 0, id: function (e) { return e.id; }, matcher: function(term, text) { return (''+text).toUpperCase().indexOf((''+term).toUpperCase()) >= 0; }, separator: ",", tokenSeparators: [], tokenizer: defaultTokenizer, escapeMarkup: function (markup) { var replace_map = { '\\': '\', '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', "/": '/' }; return String(markup).replace(/[&<>"'\/\\]/g, function (match) { return replace_map[match[0]]; }); }, blurOnChange: false, selectOnBlur: false, adaptContainerCssClass: function(c) { return c; }, adaptDropdownCssClass: function(c) { return null; } }; // exports window.Select2 = { query: { ajax: ajax, local: local, tags: tags }, util: { debounce: debounce, markMatch: markMatch }, "class": { "abstract": AbstractSelect2, "single": SingleSelect2, "multi": MultiSelect2 } }; }(jQuery));
Select2.css Kodu:
/* Version: @@ver@@ Timestamp: @@timestamp@@ */ .select2-container { position: relative; display: inline-block; /* inline-block for ie7 */ zoom: 1; *display: inline; vertical-align: middle; } .select2-container, .select2-drop, .select2-search, .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 */ -webkit-box-sizing: border-box; /* webkit */ -khtml-box-sizing: border-box; /* konqueror */ -moz-box-sizing: border-box; /* firefox */ -ms-box-sizing: border-box; /* ie */ box-sizing: border-box; /* css3 */ } .select2-container .select2-choice { display: block; height: 26px; padding: 0 0 0 8px; overflow: hidden; position: relative; border: 1px solid #aaa; white-space: nowrap; line-height: 26px; color: #444; text-decoration: none; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; -webkit-background-clip: padding-box; -moz-background-clip: padding; background-clip: padding-box; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-color: #fff; background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.5, white)); background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 50%); background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 50%); background-image: -o-linear-gradient(bottom, #eeeeee 0%, #ffffff 50%); background-image: -ms-linear-gradient(top, #ffffff 0%, #eeeeee 50%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#ffffff', endColorstr = '#eeeeee', GradientType = 0); background-image: linear-gradient(top, #ffffff 0%, #eeeeee 50%); } .select2-container.select2-drop-above .select2-choice { border-bottom-color: #aaa; -webkit-border-radius:0 0 4px 4px; -moz-border-radius:0 0 4px 4px; border-radius:0 0 4px 4px; background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.9, white)); background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 90%); background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 90%); background-image: -o-linear-gradient(bottom, #eeeeee 0%, white 90%); background-image: -ms-linear-gradient(top, #eeeeee 0%,#ffffff 90%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 ); background-image: linear-gradient(top, #eeeeee 0%,#ffffff 90%); } .select2-container.select2-allowclear .select2-choice span { margin-right: 42px; } .select2-container .select2-choice span { margin-right: 26px; display: block; overflow: hidden; white-space: nowrap; -ms-text-overflow: ellipsis; -o-text-overflow: ellipsis; text-overflow: ellipsis; } .select2-container .select2-choice abbr { display: inline-block; width: 12px; height: 12px; position: absolute; right: 24px; top: 8px; font-size: 1px; text-decoration: none; border: 0; background: url('select2.png') right top no-repeat; cursor: pointer; outline: 0; } .select2-container .select2-choice abbr:hover { background-position: right -11px; cursor: pointer; } .select2-drop-mask { position: absolute; left: 0; top: 0; z-index: 9998; background-color: #fff; opacity: 0; -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; /* works in IE 8 */ filter: "alpha(opacity=0)"; /* expected to work in IE 8 */ filter: alpha(opacity=0); /* IE 4-7 */ } .select2-drop { width: 100%; margin-top:-1px; position: absolute; z-index: 9999; top: 100%; background: #fff; color: #000; border: 1px solid #aaa; border-top: 0; -webkit-border-radius: 0 0 4px 4px; -moz-border-radius: 0 0 4px 4px; border-radius: 0 0 4px 4px; -webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15); -moz-box-shadow: 0 4px 5px rgba(0, 0, 0, .15); box-shadow: 0 4px 5px rgba(0, 0, 0, .15); } .select2-drop-auto-width { border-top: 1px solid #aaa; width: auto; } .select2-drop-auto-width .select2-search { padding-top: 4px; } .select2-drop.select2-drop-above { margin-top: 1px; border-top: 1px solid #aaa; border-bottom: 0; -webkit-border-radius: 4px 4px 0 0; -moz-border-radius: 4px 4px 0 0; border-radius: 4px 4px 0 0; -webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); -moz-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); box-shadow: 0 -4px 5px rgba(0, 0, 0, .15); } .select2-container .select2-choice div { display: inline-block; width: 18px; height: 100%; position: absolute; right: 0; top: 0; border-left: 1px solid #aaa; -webkit-border-radius: 0 4px 4px 0; -moz-border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0; -webkit-background-clip: padding-box; -moz-background-clip: padding; background-clip: padding-box; background: #ccc; background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee)); background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%); background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%); background-image: -o-linear-gradient(bottom, #ccc 0%, #eee 60%); background-image: -ms-linear-gradient(top, #cccccc 0%, #eeeeee 60%); filter: progid:DXImageTransform.Microsoft.gradient(startColorstr = '#eeeeee', endColorstr = '#cccccc', GradientType = 0); background-image: linear-gradient(top, #cccccc 0%, #eeeeee 60%); } .select2-container .select2-choice div b { display: block; width: 100%; height: 100%; background: url('select2.png') no-repeat 0 1px; } .select2-search { display: inline-block; width: 100%; min-height: 26px; margin: 0; padding-left: 4px; padding-right: 4px; position: relative; z-index: 10000; white-space: nowrap; } .select2-search-hidden { display: block; position: absolute; left: -10000px; } .select2-search input { width: 100%; height: auto !important; min-height: 26px; padding: 4px 20px 4px 5px; margin: 0; outline: 0; font-family: sans-serif; font-size: 1em; border: 1px solid #aaa; -webkit-border-radius: 0; -moz-border-radius: 0; border-radius: 0; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; 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%); background: url('select2.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%); background: url('select2.png') no-repeat 100% -22px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%); background: url('select2.png') no-repeat 100% -22px, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%); background: url('select2.png') no-repeat 100% -22px, linear-gradient(top, #ffffff 85%, #eeeeee 99%); } .select2-drop.select2-drop-above .select2-search input { margin-top: 4px; } .select2-search input.select2-active { background: #fff url('select2-spinner.gif') no-repeat 100%; background: url('select2-spinner.gif') no-repeat 100%, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee)); background: url('select2-spinner.gif') no-repeat 100%, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%); background: url('select2-spinner.gif') no-repeat 100%, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%); background: url('select2-spinner.gif') no-repeat 100%, -o-linear-gradient(bottom, white 85%, #eeeeee 99%); background: url('select2-spinner.gif') no-repeat 100%, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%); background: url('select2-spinner.gif') no-repeat 100%, linear-gradient(top, #ffffff 85%, #eeeeee 99%); } .select2-container-active .select2-choice, .select2-container-active .select2-choices { border: 1px solid #5897fb; outline: none; -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3); -moz-box-shadow: 0 0 5px rgba(0,0,0,.3); box-shadow: 0 0 5px rgba(0,0,0,.3); } .select2-dropdown-open .select2-choice { border-bottom-color: transparent; -webkit-box-shadow: 0 1px 0 #fff inset; -moz-box-shadow: 0 1px 0 #fff inset; box-shadow: 0 1px 0 #fff inset; -webkit-border-bottom-left-radius: 0; -moz-border-radius-bottomleft: 0; border-bottom-left-radius: 0; -webkit-border-bottom-right-radius: 0; -moz-border-radius-bottomright: 0; border-bottom-right-radius: 0; background-color: #eee; background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, white), color-stop(0.5, #eeeeee)); background-image: -webkit-linear-gradient(center bottom, white 0%, #eeeeee 50%); background-image: -moz-linear-gradient(center bottom, white 0%, #eeeeee 50%); background-image: -o-linear-gradient(bottom, white 0%, #eeeeee 50%); background-image: -ms-linear-gradient(top, #ffffff 0%,#eeeeee 50%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 ); background-image: linear-gradient(top, #ffffff 0%,#eeeeee 50%); } .select2-dropdown-open.select2-drop-above .select2-choice, .select2-dropdown-open.select2-drop-above .select2-choices { border: 1px solid #5897fb; border-top-color: transparent; background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0, white), color-stop(0.5, #eeeeee)); background-image: -webkit-linear-gradient(center top, white 0%, #eeeeee 50%); background-image: -moz-linear-gradient(center top, white 0%, #eeeeee 50%); background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%); background-image: -ms-linear-gradient(bottom, #ffffff 0%,#eeeeee 50%); filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 ); background-image: linear-gradient(bottom, #ffffff 0%,#eeeeee 50%); } .select2-dropdown-open .select2-choice div { background: transparent; border-left: none; filter: none; } .select2-dropdown-open .select2-choice div b { background-position: -18px 1px; } /* results */ .select2-results { max-height: 200px; padding: 0 0 0 4px; margin: 4px 4px 4px 0; position: relative; overflow-x: hidden; overflow-y: auto; -webkit-tap-highlight-color: rgba(0,0,0,0); } .select2-results ul.select2-result-sub { margin: 0; } .select2-results ul.select2-result-sub > li .select2-result-label { padding-left: 20px } .select2-results ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 40px } .select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 60px } .select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 80px } .select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 100px } .select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 110px } .select2-results ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub ul.select2-result-sub > li .select2-result-label { padding-left: 120px } .select2-results li { list-style: none; display: list-item; background-image: none; } .select2-results li.select2-result-with-children > .select2-result-label { font-weight: bold; } .select2-results .select2-result-label { padding: 3px 7px 4px; margin: 0; cursor: pointer; min-height: 1em; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; } .select2-results .select2-highlighted { background: #3875d7; color: #fff; } .select2-results li em { background: #feffde; font-style: normal; } .select2-results .select2-highlighted em { background: transparent; } .select2-results .select2-highlighted ul { background: white; color: #000; } .select2-results .select2-no-results, .select2-results .select2-searching, .select2-results .select2-selection-limit { background: #f4f4f4; display: list-item; } /* disabled look for disabled choices in the results dropdown */ .select2-results .select2-disabled.select2-highlighted { color: #666; background: #f4f4f4; display: list-item; cursor: default; } .select2-results .select2-disabled { background: #f4f4f4; display: list-item; cursor: default; } .select2-results .select2-selected { display: none; } .select2-more-results.select2-active { background: #f4f4f4 url('select2-spinner.gif') no-repeat 100%; } .select2-more-results { background: #f4f4f4; display: list-item; } /* disabled styles */ .select2-container.select2-container-disabled .select2-choice { background-color: #f4f4f4; background-image: none; border: 1px solid #ddd; cursor: default; } .select2-container.select2-container-disabled .select2-choice div { background-color: #f4f4f4; background-image: none; border-left: 0; } .select2-container.select2-container-disabled .select2-choice abbr { display: none; } /* multiselect */ .select2-container-multi .select2-choices { height: auto !important; height: 1%; margin: 0; padding: 0; position: relative; border: 1px solid #aaa; cursor: text; overflow: visible; display: inline-block; min-width: 200px; background-color: #fff; background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(1%, #eeeeee), color-stop(15%, #ffffff)); background-image: -webkit-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background-image: -moz-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background-image: -o-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background-image: -ms-linear-gradient(top, #eeeeee 1%, #ffffff 15%); background-image: linear-gradient(top, #eeeeee 1%, #ffffff 15%); } .select2-locked { padding: 3px 5px 3px 5px !important; } .select2-container-multi .select2-choices { min-height: 26px; } .select2-container-multi.select2-container-active .select2-choices { border: 1px solid #5897fb; outline: none; -webkit-box-shadow: 0 0 5px rgba(0,0,0,.3); -moz-box-shadow: 0 0 5px rgba(0,0,0,.3); box-shadow: 0 0 5px rgba(0,0,0,.3); } .select2-container-multi .select2-choices > li { float: left; list-style: none; } .select2-container-multi .select2-choices .select2-search-field { margin: 0; padding: 0; white-space: nowrap; } .select2-container-multi .select2-choices .select2-search-field input { padding: 5px; margin: 1px 0; font-family: sans-serif; font-size: 100%; color: #666; outline: 0; border: 0; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; background: transparent !important; } .select2-container-multi .select2-choices .select2-search-field input.select2-active { background: #fff url('select2-spinner.gif') no-repeat 100% !important; } .select2-default { color: #999 !important; } .select2-container-multi .select2-choices .select2-search-choice { padding: 3px 5px 3px 18px; margin: 3px 0 3px 5px; position: relative; line-height: 13px; color: #333; cursor: default; border: 1px solid #aaaaaa; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; -webkit-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); -moz-box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); box-shadow: 0 0 2px #ffffff inset, 0 1px 0 rgba(0,0,0,0.05); -webkit-background-clip: padding-box; -moz-background-clip: padding; background-clip: padding-box; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; background-color: #e4e4e4; filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#f4f4f4', GradientType=0 ); background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, color-stop(20%, #f4f4f4), color-stop(50%, #f0f0f0), color-stop(52%, #e8e8e8), color-stop(100%, #eeeeee)); background-image: -webkit-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); background-image: -moz-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); background-image: -o-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); background-image: -ms-linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); background-image: linear-gradient(top, #f4f4f4 20%, #f0f0f0 50%, #e8e8e8 52%, #eeeeee 100%); } .select2-container-multi .select2-choices .select2-search-choice span { cursor: default; } .select2-container-multi .select2-choices .select2-search-choice-focus { background: #d4d4d4; } .select2-search-choice-close { display: block; width: 12px; height: 13px; position: absolute; right: 3px; top: 4px; font-size: 1px; outline: none; background: url('select2.png') right top no-repeat; } .select2-container-multi .select2-search-choice-close { left: 3px; } .select2-container-multi .select2-choices .select2-search-choice .select2-search-choice-close:hover { background-position: right -11px; } .select2-container-multi .select2-choices .select2-search-choice-focus .select2-search-choice-close { background-position: right -11px; } /* disabled styles */ .select2-container-multi.select2-container-disabled .select2-choices{ background-color: #f4f4f4; background-image: none; border: 1px solid #ddd; cursor: default; } .select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice { padding: 3px 5px 3px 5px; border: 1px solid #ddd; background-image: none; background-color: #f4f4f4; } .select2-container-multi.select2-container-disabled .select2-choices .select2-search-choice .select2-search-choice-close { display: none; background:none; } /* end multiselect */ .select2-result-selectable .select2-match, .select2-result-unselectable .select2-match { text-decoration: underline; } .select2-offscreen { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } .select2-display-none { display: none; } .select2-measure-scrollbar { position: absolute; top: -10000px; left: -10000px; width: 100px; height: 100px; overflow: scroll; } /* Retina-ize icons */ @media only screen and (-webkit-min-device-pixel-ratio: 1.5), only screen and (min-resolution: 144dpi) { .select2-search input, .select2-search-choice-close, .select2-container .select2-choice abbr, .select2-container .select2-choice div b { background-image: url('select2x2.png') !important; background-repeat: no-repeat !important; background-size: 60px 40px !important; } .select2-search input { background-position: 100% -21px !important; } }