diff --git a/src/js/jquery.select2.js b/src/js/jquery.select2.js index a3f8eff412..9136ff784e 100644 --- a/src/js/jquery.select2.js +++ b/src/js/jquery.select2.js @@ -3,8 +3,9 @@ define([ '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']; @@ -25,7 +26,7 @@ define([ 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( diff --git a/src/js/select2/compat/inputData.js b/src/js/select2/compat/inputData.js index a338bf1eea..6e1dee2614 100644 --- a/src/js/select2/compat/inputData.js +++ b/src/js/select2/compat/inputData.js @@ -1,6 +1,7 @@ define([ - 'jquery' -], function ($) { + 'jquery', + '../utils' +], function ($, Utils) { function InputData (decorated, $element, options) { this._currentData = []; this._valueSeparator = options.get('valueSeparator') || ','; @@ -117,7 +118,7 @@ define([ 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); diff --git a/src/js/select2/core.js b/src/js/select2/core.js index 4ba06edb3e..4ba2b2f97e 100644 --- a/src/js/select2/core.js +++ b/src/js/select2/core.js @@ -5,8 +5,8 @@ define([ './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; @@ -22,7 +22,7 @@ define([ // 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 @@ -83,6 +83,9 @@ define([ // Synchronize any monitored attributes this._syncAttributes(); + Utils.StoreData($element[0], 'select2', this); + + // Ensure backwards compatibility with $element.data('select2'). $element.data('select2', this); }; @@ -572,10 +575,12 @@ define([ 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(); @@ -603,7 +608,7 @@ define([ this.$container.addClass('select2-container--' + this.options.get('theme')); - $container.data('element', this.$element); + Utils.StoreData($container[0], 'element', this.$element); return $container; }; diff --git a/src/js/select2/data/select.js b/src/js/select2/data/select.js index 6865a47daf..02cf9bdf08 100644 --- a/src/js/select2/data/select.js +++ b/src/js/select2/data/select.js @@ -119,7 +119,7 @@ define([ // Remove anything added to child elements this.$element.find('*').each(function () { // Remove any custom data set by Select2 - $.removeData(this, 'data'); + Utils.RemoveData(this); }); }; @@ -192,7 +192,7 @@ define([ normalizedData.element = option; // Override the option's data with the combined data - $.data(option, 'data', normalizedData); + Utils.StoreData(option, 'data', normalizedData); return $option; }; @@ -200,7 +200,7 @@ define([ SelectAdapter.prototype.item = function ($option) { var data = {}; - data = $.data($option[0], 'data'); + data = Utils.GetData($option[0], 'data'); if (data != null) { return data; @@ -238,7 +238,7 @@ define([ data = this._normalizeItem(data); data.element = $option[0]; - $.data($option[0], 'data', data); + Utils.StoreData($option[0], 'data', data); return data; }; diff --git a/src/js/select2/defaults.js b/src/js/select2/defaults.js index ad4d224f84..54aa95bbc5 100644 --- a/src/js/select2/defaults.js +++ b/src/js/select2/defaults.js @@ -352,6 +352,14 @@ define([ } this.defaults = { + ajax: null, + placeholder: null, + tags: false, + tokenSeparators: [], + multiple: false, + dropdownCssClass: null, + allowClear: false, + containerCssClass: null, amdBase: './', amdLanguageBase: './i18n/', closeOnSelect: true, diff --git a/src/js/select2/dropdown/attachBody.js b/src/js/select2/dropdown/attachBody.js index 70a10d40d0..2c09996347 100644 --- a/src/js/select2/dropdown/attachBody.js +++ b/src/js/select2/dropdown/attachBody.js @@ -90,14 +90,14 @@ define([ 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); }); diff --git a/src/js/select2/dropdown/selectOnClose.js b/src/js/select2/dropdown/selectOnClose.js index 430c0329b5..8c633356aa 100644 --- a/src/js/select2/dropdown/selectOnClose.js +++ b/src/js/select2/dropdown/selectOnClose.js @@ -1,6 +1,6 @@ define([ - -], function () { + '../utils' +], function (Utils) { function SelectOnClose () { } SelectOnClose.prototype.bind = function (decorated, container, $container) { @@ -31,7 +31,7 @@ define([ return; } - var data = $highlightedResults.data('data'); + var data = Utils.GetData($highlightedResults[0], 'data'); // Don't re-select already selected resulte if ( diff --git a/src/js/select2/options.js b/src/js/select2/options.js index 3e48deea50..a6870da04a 100644 --- a/src/js/select2/options.js +++ b/src/js/select2/options.js @@ -6,9 +6,21 @@ define([ ], function (require, $, Defaults, Utils) { function Options (options, $element) { this.options = options; + this.jQuery1x = false; + this.elementData = null; + + // Html already attempted to be read from html5-* attribs. + this.attemptedHtml5DataItems = {}; if ($element != null) { + // Override `options` with html5 data-* attribs if any. this.fromElement($element); + + // Prefetch properties. + for(var propertyName in Defaults.defaults) { + this.get(propertyName); + } + } this.options = Defaults.apply(this.options); @@ -55,7 +67,7 @@ define([ $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 ' + @@ -64,11 +76,11 @@ define([ ); } - $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 ' + @@ -77,8 +89,9 @@ define([ ); } - $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 = {}; @@ -86,9 +99,10 @@ define([ // 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()); + this.jQuery1x = true; + dataset = $.extend(true, {}, $e[0].dataset, Utils.GetData($e[0])); } else { - dataset = $e.data(); + dataset = Utils.GetData($e[0]); } var data = $.extend(true, {}, dataset); @@ -107,10 +121,36 @@ define([ } } + // Store normalized element data for later retrieval. + this.elementData = Utils._convertData($e.data()); + return this; }; Options.prototype.get = function (key) { + + if (!this.elementData || this.attemptedHtml5DataItems[key]) { + return this.options[key]; + } + // If the option value is stored in html5 data-* attribs, + // fetch the value and store it for performance. + if (!this.jQuery1x) { + // For jQuery 1.x, the html5 data-* attribs have already + // been parsed above. + // Read all attribs that start with `key`. + for (var dataKey in this.elementData) { + if (dataKey.indexOf(key) === 0) { + + if ($.isPlainObject(this.options[key])) { + $.extend(this.options[key], this.elementData[dataKey]); + } else { + this.options[key] = this.elementData[dataKey]; + } + } + } + } + // Mark as attempted. + this.attemptedHtml5DataItems[key] = true; return this.options[key]; }; diff --git a/src/js/select2/results.js b/src/js/select2/results.js index a16b65fd0f..f155821cea 100644 --- a/src/js/select2/results.js +++ b/src/js/select2/results.js @@ -130,7 +130,7 @@ define([ $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; @@ -235,7 +235,7 @@ define([ this.template(data, option); } - $.data(option, 'data', data); + Utils.StoreData(option, 'data', data); return option; }; @@ -321,7 +321,7 @@ define([ return; } - var data = $highlighted.data('data'); + var data = Utils.GetData($highlighted[0], 'data'); if ($highlighted.attr('aria-selected') == 'true') { self.trigger('close', {}); @@ -433,7 +433,7 @@ define([ 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')) { @@ -456,7 +456,7 @@ define([ 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'); diff --git a/src/js/select2/selection/allowClear.js b/src/js/select2/selection/allowClear.js index d3502069f5..5c4e1fc8f3 100644 --- a/src/js/select2/selection/allowClear.js +++ b/src/js/select2/selection/allowClear.js @@ -1,7 +1,8 @@ define([ 'jquery', - '../keys' -], function ($, KEYS) { + '../keys', + '../utils' +], function ($, KEYS, Utils) { function AllowClear () { } AllowClear.prototype.bind = function (decorated, container, $container) { @@ -43,7 +44,7 @@ define([ evt.stopPropagation(); - var data = $clear.data('data'); + var data = Utils.GetData($clear[0], 'data'); for (var d = 0; d < data.length; d++) { var unselectData = { @@ -88,7 +89,7 @@ define([ '×' + '' ); - $remove.data('data', data); + Utils.StoreData($remove[0], 'data', data); this.$selection.find('.select2-selection__rendered').prepend($remove); }; diff --git a/src/js/select2/selection/base.js b/src/js/select2/selection/base.js index cd3aafdab7..2efb103922 100644 --- a/src/js/select2/selection/base.js +++ b/src/js/select2/selection/base.js @@ -21,8 +21,8 @@ define([ 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'); } @@ -130,7 +130,7 @@ define([ return; } - var $element = $this.data('element'); + var $element = Utils.GetData(this, 'element'); $element.select2('close'); }); diff --git a/src/js/select2/selection/multiple.js b/src/js/select2/selection/multiple.js index ae0f7cce7e..df0cb08a86 100644 --- a/src/js/select2/selection/multiple.js +++ b/src/js/select2/selection/multiple.js @@ -44,7 +44,7 @@ define([ var $remove = $(this); var $selection = $remove.parent(); - var data = $selection.data('data'); + var data = Utils.GetData($selection[0], 'data'); self.trigger('unselect', { originalEvent: evt, @@ -95,7 +95,7 @@ define([ $selection.append(formatted); $selection.prop('title', selection.title || selection.text); - $selection.data('data', selection); + Utils.StoreData($selection[0], 'data', selection); $selections.push($selection); } diff --git a/src/js/select2/selection/search.js b/src/js/select2/selection/search.js index 1bc4b096bb..9ce321beae 100644 --- a/src/js/select2/selection/search.js +++ b/src/js/select2/selection/search.js @@ -81,7 +81,7 @@ define([ .prev('.select2-selection__choice'); if ($previousChoice.length > 0) { - var item = $previousChoice.data('data'); + var item = Utils.GetData($previousChoice[0], 'data'); self.searchRemoveChoice(item); diff --git a/src/js/select2/utils.js b/src/js/select2/utils.js index d1a23d293e..7905e49b0a 100644 --- a/src/js/select2/utils.js +++ b/src/js/select2/utils.js @@ -272,5 +272,68 @@ define([ $element.append($nodes); }; + // Cache objects in Utils.__cache instead of $.data + 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]) { + return Utils.__cache[id][name] != null ? + Utils.__cache[id][name]: + $(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; }); diff --git a/tests/data/array-tests.js b/tests/data/array-tests.js index 65a6e32c3d..39a2c9e24d 100644 --- a/tests/data/array-tests.js +++ b/tests/data/array-tests.js @@ -3,6 +3,7 @@ module('Data adapters - Array'); var ArrayData = require('select2/data/array'); var $ = require('jquery'); var Options = require('select2/options'); +var Utils = require('select2/utils'); var arrayOptions = new Options({ data: [ @@ -237,7 +238,7 @@ test('option tags can receive new data', function(assert) { }); assert.ok( - $select.find(':selected').data('data').extra, + Utils.GetData($select.find(':selected')[0], 'data').extra, '