vendor/assets/javascripts/select2-full.js in select2-rails-4.0.5 vs vendor/assets/javascripts/select2-full.js in select2-rails-4.0.6

- old
+ new

@@ -1,13 +1,13 @@ /*! - * Select2 4.0.5 + * Select2 4.0.6 * https://select2.github.io * * Released under the MIT license * https://github.com/select2/select2/blob/master/LICENSE.md */ -(function (factory) { +;(function (factory) { if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module. define(['jquery'], factory); } else if (typeof module === 'object' && module.exports) { // Node/CommonJS @@ -572,14 +572,14 @@ } DecoratedClass.prototype = new ctr(); for (var m = 0; m < superMethods.length; m++) { - var superMethod = superMethods[m]; + var superMethod = superMethods[m]; - DecoratedClass.prototype[superMethod] = - SuperClass.prototype[superMethod]; + DecoratedClass.prototype[superMethod] = + SuperClass.prototype[superMethod]; } var calledMethod = function (methodName) { // Stub out the original method if it's not decorating an actual method var originalMethod = function () {}; @@ -770,10 +770,72 @@ } $element.append($nodes); }; + // Cache objects in Utils.__cache instead of $.data (see #4346) + Utils.__cache = {}; + + var id = 0; + Utils.GetUniqueElementId = function (element) { + // Get a unique element Id. If element has no id, + // creates a new unique number, stores it in the id + // attribute and returns the new id. + // If an id already exists, it simply returns it. + + var select2Id = element.getAttribute('data-select2-id'); + if (select2Id == null) { + // If element has id, use it. + if (element.id) { + select2Id = element.id; + element.setAttribute('data-select2-id', select2Id); + } else { + element.setAttribute('data-select2-id', ++id); + select2Id = id.toString(); + } + } + return select2Id; + }; + + Utils.StoreData = function (element, name, value) { + // Stores an item in the cache for a specified element. + // name is the cache key. + var id = Utils.GetUniqueElementId(element); + if (!Utils.__cache[id]) { + Utils.__cache[id] = {}; + } + + Utils.__cache[id][name] = value; + }; + + Utils.GetData = function (element, name) { + // Retrieves a value from the cache by its key (name) + // name is optional. If no name specified, return + // all cache items for the specified element. + // and for a specified element. + var id = Utils.GetUniqueElementId(element); + if (name) { + if (Utils.__cache[id]) { + if (Utils.__cache[id][name] != null) { + return Utils.__cache[id][name]; + } + return $(element).data(name); // Fallback to HTML5 data attribs. + } + return $(element).data(name); // Fallback to HTML5 data attribs. + } else { + return Utils.__cache[id]; + } + }; + + Utils.RemoveData = function (element) { + // Removes all cached items for a specified element. + var id = Utils.GetUniqueElementId(element); + if (Utils.__cache[id] != null) { + delete Utils.__cache[id]; + } + }; + return Utils; }); S2.define('select2/results',[ 'jquery', @@ -905,11 +967,11 @@ .find('.select2-results__option[aria-selected]'); $options.each(function () { var $option = $(this); - var item = $.data(this, 'data'); + var item = Utils.GetData(this, 'data'); // id needs to be converted to a string when comparing var id = '' + item.id; if ((item.element != null && item.element.selected) || @@ -1010,11 +1072,11 @@ $option.append($childrenContainer); } else { this.template(data, option); } - $.data(option, 'data', data); + Utils.StoreData(option, 'data', data); return option; }; Results.prototype.bind = function (container, $container) { @@ -1051,20 +1113,26 @@ if (!container.isOpen()) { return; } self.setClasses(); - self.highlightFirstItem(); + + if (self.options.get('scrollAfterSelect')) { + self.highlightFirstItem(); + } }); container.on('unselect', function () { if (!container.isOpen()) { return; } self.setClasses(); - self.highlightFirstItem(); + + if (self.options.get('scrollAfterSelect')) { + self.highlightFirstItem(); + } }); container.on('open', function () { // When the dropdown is open, aria-expended="true" self.$results.attr('aria-expanded', 'true'); @@ -1096,11 +1164,11 @@ if ($highlighted.length === 0) { return; } - var data = $highlighted.data('data'); + var data = Utils.GetData($highlighted[0], 'data'); if ($highlighted.attr('aria-selected') == 'true') { self.trigger('close', {}); } else { self.trigger('select', { @@ -1114,12 +1182,13 @@ var $options = self.$results.find('[aria-selected]'); var currentIndex = $options.index($highlighted); - // If we are already at te top, don't move further - if (currentIndex === 0) { + // If we are already at the top, don't move further + // If no options, currentIndex will be -1 + if (currentIndex <= 0) { return; } var nextIndex = currentIndex - 1; @@ -1208,11 +1277,11 @@ this.$results.on('mouseup', '.select2-results__option[aria-selected]', function (evt) { var $this = $(this); - var data = $this.data('data'); + var data = Utils.GetData(this, 'data'); if ($this.attr('aria-selected') === 'true') { if (self.options.get('multiple')) { self.trigger('unselect', { originalEvent: evt, @@ -1231,11 +1300,11 @@ }); }); this.$results.on('mouseenter', '.select2-results__option[aria-selected]', function (evt) { - var data = $(this).data('data'); + var data = Utils.GetData(this, 'data'); self.getHighlightedResults() .removeClass('select2-results__option--highlighted'); self.trigger('results:focus', { @@ -1346,12 +1415,12 @@ '</span>' ); this._tabindex = 0; - if (this.$element.data('old-tabindex') != null) { - this._tabindex = this.$element.data('old-tabindex'); + if (Utils.GetData(this.$element[0], 'old-tabindex') != null) { + this._tabindex = Utils.GetData(this.$element[0], 'old-tabindex'); } else if (this.$element.attr('tabindex') != null) { this._tabindex = this.$element.attr('tabindex'); } $selection.attr('title', this.$element.attr('title')); @@ -1406,12 +1475,14 @@ // When the dropdown is closed, aria-expanded="false" self.$selection.attr('aria-expanded', 'false'); self.$selection.removeAttr('aria-activedescendant'); self.$selection.removeAttr('aria-owns'); - self.$selection.focus(); - + window.setTimeout(function () { + self.$selection.focus(); + }, 0); + self._detachCloseHandler(container); }); container.on('enable', function () { self.$selection.attr('tabindex', self._tabindex); @@ -1455,11 +1526,11 @@ if (this == $select[0]) { return; } - var $element = $this.data('element'); + var $element = Utils.GetData(this, 'element'); $element.select2('close'); }); }); }; @@ -1516,11 +1587,14 @@ SingleSelection.__super__.bind.apply(this, arguments); var id = container.id + '-container'; - this.$selection.find('.select2-selection__rendered').attr('id', id); + this.$selection.find('.select2-selection__rendered') + .attr('id', id) + .attr('role', 'textbox') + .attr('aria-readonly', 'true'); this.$selection.attr('aria-labelledby', id); this.$selection.on('mousedown', function (evt) { // Only respond to left clicks if (evt.which !== 1) { @@ -1543,18 +1617,16 @@ container.on('focus', function (evt) { if (!container.isOpen()) { self.$selection.focus(); } }); - - container.on('selection:update', function (params) { - self.update(params.data); - }); }; SingleSelection.prototype.clear = function () { - this.$selection.find('.select2-selection__rendered').empty(); + var $rendered = this.$selection.find('.select2-selection__rendered'); + $rendered.empty(); + $rendered.removeAttr('title'); // clear tooltip on empty }; SingleSelection.prototype.display = function (data, container) { var template = this.options.get('templateSelection'); var escapeMarkup = this.options.get('escapeMarkup'); @@ -1576,11 +1648,11 @@ var $rendered = this.$selection.find('.select2-selection__rendered'); var formatted = this.display(selection, $rendered); $rendered.empty().append(formatted); - $rendered.prop('title', selection.title || selection.text); + $rendered.attr('title', selection.title || selection.text); }; return SingleSelection; }); @@ -1628,22 +1700,24 @@ } var $remove = $(this); var $selection = $remove.parent(); - var data = $selection.data('data'); + var data = Utils.GetData($selection[0], 'data'); self.trigger('unselect', { originalEvent: evt, data: data }); } ); }; MultipleSelection.prototype.clear = function () { - this.$selection.find('.select2-selection__rendered').empty(); + var $rendered = this.$selection.find('.select2-selection__rendered'); + $rendered.empty(); + $rendered.removeAttr('title'); }; MultipleSelection.prototype.display = function (data, container) { var template = this.options.get('templateSelection'); var escapeMarkup = this.options.get('escapeMarkup'); @@ -1677,13 +1751,13 @@ var $selection = this.selectionContainer(); var formatted = this.display(selection, $selection); $selection.append(formatted); - $selection.prop('title', selection.title || selection.text); + $selection.attr('title', selection.title || selection.text); - $selection.data('data', selection); + Utils.StoreData($selection[0], 'data', selection); $selections.push($selection); } var $rendered = this.$selection.find('.select2-selection__rendered'); @@ -1744,12 +1818,13 @@ return Placeholder; }); S2.define('select2/selection/allowClear',[ 'jquery', - '../keys' -], function ($, KEYS) { + '../keys', + '../utils' +], function ($, KEYS, Utils) { function AllowClear () { } AllowClear.prototype.bind = function (decorated, container, $container) { var self = this; @@ -1787,28 +1862,41 @@ return; } evt.stopPropagation(); - var data = $clear.data('data'); + var data = Utils.GetData($clear[0], 'data'); + var previousVal = this.$element.val(); + this.$element.val(this.placeholder.id); + + var unselectData = { + data: data + }; + this.trigger('clear', unselectData); + if (unselectData.prevented) { + this.$element.val(previousVal); + return; + } + for (var d = 0; d < data.length; d++) { - var unselectData = { + unselectData = { data: data[d] }; // Trigger the `unselect` event, so people can prevent it from being // cleared. this.trigger('unselect', unselectData); // If the event was prevented, don't clear it out. if (unselectData.prevented) { + this.$element.val(previousVal); return; } } - this.$element.val(this.placeholder.id).trigger('change'); + this.$element.trigger('change'); this.trigger('toggle', {}); }; AllowClear.prototype._handleKeyboardClear = function (_, evt, container) { @@ -1827,16 +1915,18 @@ if (this.$selection.find('.select2-selection__placeholder').length > 0 || data.length === 0) { return; } + var removeAll = this.options.get('translations').get('removeAllItems'); + var $remove = $( - '<span class="select2-selection__clear">' + + '<span class="select2-selection__clear" title="' + removeAll() +'">' + '&times;' + '</span>' ); - $remove.data('data', data); + Utils.StoreData($remove[0], 'data', data); this.$selection.find('.select2-selection__rendered').prepend($remove); }; return AllowClear; @@ -1923,11 +2013,11 @@ if (key === KEYS.BACKSPACE && self.$search.val() === '') { var $previousChoice = self.$searchContainer .prev('.select2-selection__choice'); if ($previousChoice.length > 0) { - var item = $previousChoice.data('data'); + var item = Utils.GetData($previousChoice[0], 'data'); self.searchRemoveChoice(item); evt.preventDefault(); } @@ -2017,11 +2107,17 @@ this.$selection.find('.select2-selection__rendered') .append(this.$searchContainer); this.resizeSearch(); if (searchHadFocus) { - this.$search.focus(); + var isTagInput = this.$element.find('[data-select2-tag]').length; + if (isTagInput) { + // fix IE11 bug where tag input lost focus + this.$element.focus(); + } else { + this.$search.focus(); + } } }; Search.prototype.handleSearch = function () { this.resizeSearch(); @@ -2074,14 +2170,17 @@ var self = this; var relayEvents = [ 'open', 'opening', 'close', 'closing', 'select', 'selecting', - 'unselect', 'unselecting' + 'unselect', 'unselecting', + 'clear', 'clearing' ]; - var preventableEvents = ['opening', 'closing', 'selecting', 'unselecting']; + var preventableEvents = [ + 'opening', 'closing', 'selecting', 'unselecting', 'clearing' + ]; decorated.call(this, container, $container); container.on('*', function (name, params) { // Ignore events that should not be relayed @@ -2410,10 +2509,11 @@ '\u01FE': 'O', '\u0186': 'O', '\u019F': 'O', '\uA74A': 'O', '\uA74C': 'O', + '\u0152': 'OE', '\u01A2': 'OI', '\uA74E': 'OO', '\u0222': 'OU', '\u24C5': 'P', '\uFF30': 'P', @@ -2819,10 +2919,11 @@ '\u01FF': 'o', '\u0254': 'o', '\uA74B': 'o', '\uA74D': 'o', '\u0275': 'o', + '\u0153': 'oe', '\u01A3': 'oi', '\u0223': 'ou', '\uA74F': 'oo', '\u24DF': 'p', '\uFF50': 'p', @@ -2987,12 +3088,13 @@ '\u0390': '\u03B9', '\u03CC': '\u03BF', '\u03CD': '\u03C5', '\u03CB': '\u03C5', '\u03B0': '\u03C5', - '\u03C9': '\u03C9', - '\u03C2': '\u03C3' + '\u03CE': '\u03C9', + '\u03C2': '\u03C3', + '\u2019': '\'' }; return diacritics; }); @@ -3156,11 +3258,11 @@ SelectAdapter.prototype.destroy = function () { // Remove anything added to child elements this.$element.find('*').each(function () { // Remove any custom data set by Select2 - $.removeData(this, 'data'); + Utils.RemoveData(this); }); }; SelectAdapter.prototype.query = function (params, callback) { var data = []; @@ -3229,19 +3331,19 @@ var normalizedData = this._normalizeItem(data); normalizedData.element = option; // Override the option's data with the combined data - $.data(option, 'data', normalizedData); + Utils.StoreData(option, 'data', normalizedData); return $option; }; SelectAdapter.prototype.item = function ($option) { var data = {}; - data = $.data($option[0], 'data'); + data = Utils.GetData($option[0], 'data'); if (data != null) { return data; } @@ -3275,17 +3377,17 @@ } data = this._normalizeItem(data); data.element = $option[0]; - $.data($option[0], 'data', data); + Utils.StoreData($option[0], 'data', data); return data; }; SelectAdapter.prototype._normalizeItem = function (item) { - if (!$.isPlainObject(item)) { + if (item !== Object(item)) { item = { id: item, text: item }; } @@ -3485,11 +3587,12 @@ callback(results); }, function () { // Attempt to detect if a request was aborted // Only works if the transport exposes a status property - if ($request.status && $request.status === '0') { + if ('status' in $request && + ($request.status === 0 || $request.status === '0')) { return; } self.trigger('results:message', { message: 'errorLoading' @@ -3884,11 +3987,11 @@ Dropdown.prototype.bind = function () { // Should be implemented in subclasses }; Dropdown.prototype.position = function ($dropdown, $container) { - // Should be implmented in subclasses + // Should be implemented in subclasses }; Dropdown.prototype.destroy = function () { // Remove the dropdown from the DOM this.$dropdown.remove(); @@ -3957,10 +4060,11 @@ container.on('close', function () { self.$search.attr('tabindex', -1); self.$search.val(''); + self.$search.blur(); }); container.on('focus', function () { if (!container.isOpen()) { self.$search.focus(); @@ -4222,18 +4326,18 @@ var resizeEvent = 'resize.select2.' + container.id; var orientationEvent = 'orientationchange.select2.' + container.id; var $watchers = this.$container.parents().filter(Utils.hasScroll); $watchers.each(function () { - $(this).data('select2-scroll-position', { + Utils.StoreData(this, 'select2-scroll-position', { x: $(this).scrollLeft(), y: $(this).scrollTop() }); }); $watchers.on(scrollEvent, function (ev) { - var position = $(this).data('select2-scroll-position'); + var position = Utils.GetData(this, 'select2-scroll-position'); $(this).scrollTop(position.y); }); $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent, function (e) { @@ -4288,14 +4392,14 @@ var css = { left: offset.left, top: container.bottom }; - // Determine what the parent element is to use for calciulating the offset + // Determine what the parent element is to use for calculating the offset var $offsetParent = this.$dropdownParent; - // For statically positoned elements, we need to get the element + // For statically positioned elements, we need to get the element // that is determining the offset if ($offsetParent.css('position') === 'static') { $offsetParent = $offsetParent.offsetParent(); } @@ -4394,12 +4498,12 @@ return MinimumResultsForSearch; }); S2.define('select2/dropdown/selectOnClose',[ - -], function () { + '../utils' +], function (Utils) { function SelectOnClose () { } SelectOnClose.prototype.bind = function (decorated, container, $container) { var self = this; @@ -4426,11 +4530,11 @@ // Only select highlighted results if ($highlightedResults.length < 1) { return; } - var data = $highlightedResults.data('data'); + var data = Utils.GetData($highlightedResults[0], 'data'); // Don't re-select already selected resulte if ( (data.element != null && data.element.selected) || (data.element == null && data.selected) @@ -4521,10 +4625,13 @@ noResults: function () { return 'No results found'; }, searching: function () { return 'Searching…'; + }, + removeAllItems: function () { + return 'Remove all items'; } }; }); S2.define('select2/defaults',[ @@ -4892,10 +4999,11 @@ minimumInputLength: 0, maximumInputLength: 0, maximumSelectionLength: 0, minimumResultsForSearch: 0, selectOnClose: false, + scrollAfterSelect: false, sorter: function (data) { return data; }, templateResult: function (result) { return result.text; @@ -4914,11 +5022,11 @@ var data = {}; data[camelKey] = value; var convertedData = Utils._convertData(data); - $.extend(this.defaults, convertedData); + $.extend(true, this.defaults, convertedData); }; var defaults = new Defaults(); return defaults; @@ -4979,47 +5087,71 @@ } $e.prop('disabled', this.options.disabled); $e.prop('multiple', this.options.multiple); - if ($e.data('select2Tags')) { + if (Utils.GetData($e[0], 'select2Tags')) { if (this.options.debug && window.console && console.warn) { console.warn( 'Select2: The `data-select2-tags` attribute has been changed to ' + 'use the `data-data` and `data-tags="true"` attributes and will be ' + 'removed in future versions of Select2.' ); } - $e.data('data', $e.data('select2Tags')); - $e.data('tags', true); + Utils.StoreData($e[0], 'data', Utils.GetData($e[0], 'select2Tags')); + Utils.StoreData($e[0], 'tags', true); } - if ($e.data('ajaxUrl')) { + if (Utils.GetData($e[0], 'ajaxUrl')) { if (this.options.debug && window.console && console.warn) { console.warn( 'Select2: The `data-ajax-url` attribute has been changed to ' + '`data-ajax--url` and support for the old attribute will be removed' + ' in future versions of Select2.' ); } - $e.attr('ajax--url', $e.data('ajaxUrl')); - $e.data('ajax--url', $e.data('ajaxUrl')); + $e.attr('ajax--url', Utils.GetData($e[0], 'ajaxUrl')); + Utils.StoreData($e[0], 'ajax-Url', Utils.GetData($e[0], 'ajaxUrl')); } var dataset = {}; + function upperCaseLetter(_, letter) { + return letter.toUpperCase(); + } + + // Pre-load all of the attributes which are prefixed with `data-` + for (var attr = 0; attr < $e[0].attributes.length; attr++) { + var attributeName = $e[0].attributes[attr].name; + var prefix = 'data-'; + + if (attributeName.substr(0, prefix.length) == prefix) { + // Get the contents of the attribute after `data-` + var dataName = attributeName.substring(prefix.length); + + // Get the data contents from the consistent source + // This is more than likely the jQuery data helper + var dataValue = Utils.GetData($e[0], dataName); + + // camelCase the attribute name to match the spec + var camelDataName = dataName.replace(/-([a-z])/g, upperCaseLetter); + + // Store the data attribute contents into the dataset since + dataset[camelDataName] = dataValue; + } + } + // Prefer the element's `dataset` attribute if it exists // jQuery 1.x does not correctly handle data attributes with multiple dashes if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) { - dataset = $.extend(true, {}, $e[0].dataset, $e.data()); - } else { - dataset = $e.data(); + dataset = $.extend(true, {}, $e[0].dataset, dataset); } - var data = $.extend(true, {}, dataset); + // Prefer our internal data cache if it exists + var data = $.extend(true, {}, Utils.GetData($e[0]), dataset); data = Utils._convertData(data); for (var key in data) { if ($.inArray(key, excludedData) > -1) { @@ -5052,12 +5184,12 @@ './options', './utils', './keys' ], function ($, Options, Utils, KEYS) { var Select2 = function ($element, options) { - if ($element.data('select2') != null) { - $element.data('select2').destroy(); + if (Utils.GetData($element[0], 'select2') != null) { + Utils.GetData($element[0], 'select2').destroy(); } this.$element = $element; this.id = this._generateId($element); @@ -5069,11 +5201,11 @@ Select2.__super__.constructor.call(this); // Set up the tabindex var tabindex = $element.attr('tabindex') || 0; - $element.data('old-tabindex', tabindex); + Utils.StoreData($element[0], 'old-tabindex', tabindex); $element.attr('tabindex', '-1'); // Set up containers and adapters var DataAdapter = this.options.get('dataAdapter'); @@ -5130,10 +5262,13 @@ $element.attr('aria-hidden', 'true'); // Synchronize any monitored attributes this._syncAttributes(); + Utils.StoreData($element[0], 'select2', this); + + // Ensure backwards compatibility with $element.data('select2'). $element.data('select2', this); }; Utils.Extend(Select2, Utils.Observable); @@ -5320,11 +5455,28 @@ }); }; Select2.prototype._registerEvents = function () { var self = this; + + this.on('focus', function () { + self.$container.addClass('select2-container--focus'); + if (!self.$container.hasClass('select2-container--disabled') && + !self.isOpen()) { + if (self.options.get('multiple')) { + window.setTimeout(function () { + self.open(); + }, + self.options.get('ajax') ? 300 : 100); + } + else { + self.open(); + } + } + }); + this.on('open', function () { self.$container.addClass('select2-container--open'); }); this.on('close', function () { @@ -5464,11 +5616,12 @@ var actualTrigger = Select2.__super__.trigger; var preTriggerMap = { 'open': 'opening', 'close': 'closing', 'select': 'selecting', - 'unselect': 'unselecting' + 'unselect': 'unselecting', + 'clear': 'clearing' }; if (args === undefined) { args = {}; } @@ -5619,14 +5772,16 @@ this._syncA = null; this._syncS = null; this.$element.off('.select2'); - this.$element.attr('tabindex', this.$element.data('old-tabindex')); + this.$element.attr('tabindex', + Utils.GetData(this.$element[0], 'old-tabindex')); this.$element.removeClass('select2-hidden-accessible'); this.$element.attr('aria-hidden', 'false'); + Utils.RemoveData(this.$element[0]); this.$element.removeData('select2'); this.dataAdapter.destroy(); this.selection.destroy(); this.dropdown.destroy(); @@ -5650,11 +5805,11 @@ this.$container = $container; this.$container.addClass('select2-container--' + this.options.get('theme')); - $container.data('element', this.$element); + Utils.StoreData($container[0], 'element', this.$element); return $container; }; return Select2; @@ -5860,12 +6015,13 @@ return InitSelection; }); S2.define('select2/compat/inputData',[ - 'jquery' -], function ($) { + 'jquery', + '../utils' +], function ($, Utils) { function InputData (decorated, $element, options) { this._currentData = []; this._valueSeparator = options.get('valueSeparator') || ','; if ($element.prop('type') === 'hidden') { @@ -5978,11 +6134,11 @@ }); }; InputData.prototype.addOptions = function (_, $options) { var options = $.map($options, function ($option) { - return $.data($option[0], 'data'); + return Utils.GetData($option[0], 'data'); }); this._currentData.push.apply(this._currentData, options); }; @@ -6381,12 +6537,13 @@ S2.define('jquery.select2',[ 'jquery', 'jquery-mousewheel', './select2/core', - './select2/defaults' -], function ($, _, Select2, Defaults) { + './select2/defaults', + './select2/utils' +], function ($, _, Select2, Defaults, Utils) { if ($.fn.select2 == null) { // All methods that should return the element var thisMethods = ['open', 'close', 'destroy']; $.fn.select2 = function (options) { @@ -6403,10 +6560,10 @@ } else if (typeof options === 'string') { var ret; var args = Array.prototype.slice.call(arguments, 1); this.each(function () { - var instance = $(this).data('select2'); + var instance = Utils.GetData(this, 'select2'); if (instance == null && window.console && console.error) { console.error( 'The select2(\'' + options + '\') method was called on an ' + 'element that is not using Select2.'