Skip to content

Instantly share code, notes, and snippets.

@johnkegd
Last active March 31, 2022 11:11
Show Gist options
  • Select an option

  • Save johnkegd/b912d577062a99c5fc7bf0b130c9b6f3 to your computer and use it in GitHub Desktop.

Select an option

Save johnkegd/b912d577062a99c5fc7bf0b130c9b6f3 to your computer and use it in GitHub Desktop.
Select2 jquery plugin - adapters and decorators templates. (MultipleSelection, SingleSelection, Search and more)
/**
* Results adapter 0
* Hides options from the dropdown list that contain the hidden attribute.
* It is necessary to load the module after the select2 library and before the xfaWidget.dropDownList
*/
$.fn.select2.amd.define(
"resultsAdapter",
[
"jquery",
"select2/utils",
"select2/results",
"select2/selection/eventRelay",
],
function ($, Utils, _Results, EventRelay) {
// to ensure all events are triggered correctly.
var ResultsAdapter = Utils.Decorate(_Results, EventRelay);
ResultsAdapter.prototype.append = function (data) {
this.hideLoading();
var $options = [];
if (data.results == null || data.results.length === 0) {
if (this.$results.children().length === 0) {
this.trigger("results:message", {
message: "noResults",
});
}
return;
}
data.results = this.sort(data.results);
for (var d = 0; d < data.results.length; d++) {
var item = data.results[d];
var $option = this.option(item);
// skip the options with hidden attribute
if (!item.element.hasAttribute("hidden")) {
$options.push($option);
}
}
this.$results.append($options);
};
return ResultsAdapter;
}
);
/**
* Results adapter 1
* TODO: test and description
*
*/
$.fn.select2.amd.define(
"CustomResultsAdapter",
["jquery", "select2/utils", "select2/results"],
function ($, Utils, _Results) {
function Results() {
Results.__super__.constructor.apply(this, arguments);
}
Utils.Extend(Results, _Results);
Results.prototype.bind = function (container) {
var self = this;
container.on("results:select", function () {
var $highlighted = self.getHighlightedResults();
if ($highlighted.length === 0) {
return;
}
var data = Utils.GetData($highlighted[0], "data");
// selecting and unselecting with the same key
if ($highlighted.attr("aria-selected") == "true") {
//self.trigger('close', {});
self.trigger("unselect", {
data: data,
});
this.listeners["results:select"].pop(1);
this.listeners["results:select"].push(
this.listeners["results:select"][0]
);
} else {
self.trigger("select", {
data: data,
});
// self.trigger('close',{});
}
//
});
Results.__super__.bind.apply(this, arguments);
};
Results.prototype.template = function (result, container) {
var template = this.options.get("templateResult");
var escapeMarkup = this.options.get("escapeMarkup");
var content = template(result, container);
if (content == null) {
container.style.display = "none";
} else if (typeof content === "string") {
container.innerHTML = escapeMarkup(content);
} else {
$(container).append(content);
}
};
return Results;
}
);
/**
* Results decorate 2
* TODO: add listeners to act before select2 listener prototypes
* it doesn't override the prototype.
*
*/
$.fn.select2.amd.define(
"customResults",
["select2/utils", "select2/results", "select2/dropdown/hidePlaceholder"],
function (Utils, ResultsList, HidePlaceholder) {
var ResultAdapter = function (decorated, $element, options, dataAdapter) {
return decorated.call(this, $element, options, dataAdapter);
};
ResultAdapter.prototype.bind = function (decorated, container, $container) {
var self = this;
container.on('results:select', function (ev) {
var $highlighted = self.getHighlightedResults();
if ($highlighted.length === 0) {
return;
}
var data = Utils.GetData($highlighted[0], 'data');
if ($highlighted.attr('aria-selected') == 'true') {
// self.trigger('close', {});
self.trigger('unselect', {data: data});
} else {
self.trigger('select', {
data: data
});
ev.data = data;
self.options.get('accessibilityContext').handler(ev);
event.stopPropagation();
}
});
return decorated.call(this, container, $container);
};
var adapter = Utils.Decorate(ResultsList, ResultAdapter);
// needed hidePlaceHolder to skip first empty option
return Utils.Decorate(adapter, HidePlaceholder);
}
);
/**
* Dropdown adapter 0
* TODO: test and description
*
*/
$.fn.select2.amd.define(
"dropdownAdapter",
[
"jquery",
"select2/utils",
"select2/selection/search",
"select2/selection/multiple",
"select2/selection/eventRelay",
],
function ($, Utils, Search, MultipleSelection, EventRelay) {
function Adapter() {
Adapter.__super__.constructor.apply(this, arguments);
}
Utils.Extend(Adapter, Search);
Adapter.prototype.handleSearch = function () {
this.resizeSearch();
if (!this._keyUpPrevented) {
var input = this.$search.val();
this.trigger("query", {
term: input,
});
}
this._keyUpPrevented = false;
Adapter.__super__.handleSearch.apply(this, arguments);
};
return Adapter;
}
);
/**
* Data adapter 0
* TODO: test and description
*
*/
$.fn.select2.amd.define(
"dataAdapter",
[
"jquery",
"select2/utils",
"select2/selection/search",
"select2/selection/multiple",
"select2/selection/eventRelay",
"select2/selection/placeholder",
"select2/selection/single",
"select2/data/select",
],
function (
$,
Utils,
Search,
MultipleSelection,
EventRelay,
Placeholder,
SingleSelection,
BaseAdapter
) {
function SelectAdapter() {
SelectAdapter.__super__.constructor.apply(this, arguments);
}
Utils.Extend(SelectAdapter, BaseAdapter);
SelectAdapter.prototype.query = function (params, callback) {
var data = [];
var dataRefresh = [];
var self = this;
var $options = this.$element.children();
$options.each(function () {
var $option = $(this);
if (!$option.is("option") && !$option.is("optgroup")) {
return;
}
var option = self.item($option);
dataRefresh.push(option);
var matches = self.matches(params, option);
if (matches !== null) {
data.push(matches);
}
});
callback({
results: data,
});
SelectAdapter.__super__.query.apply(this, arguments);
};
SelectAdapter.prototype.current = function (callback) {
var data = [];
var self = this;
this.$element.find(":selected").each(function () {
var $option = $(this);
var option = self.item($option);
data.push(option);
});
callback(data);
SelectAdapter.__super__.current.apply(this, arguments);
};
return SelectAdapter;
}
);
/**
* Dropdown adapter 1
* Change the position of the dropdown via the width adding 2px extra
*
*/
$.fn.select2.amd.define("DropdownAdapter", [
"select2/utils",
"select2/dropdown",
"select2/dropdown/attachBody",
"select2/dropdown/attachContainer",
"select2/dropdown/search",
"select2/dropdown/minimumResultsForSearch",
],
function (Utils, Dropdown, AttachBody, AttachContainer, Search, MinimumResultsForSearch) {
var SingleDropdownAdapter = Utils.Decorate(Dropdown, AttachContainer);
SingleDropdownAdapter = Utils.Decorate(SingleDropdownAdapter, AttachBody);
//decorating module prototype AttachBody
SingleDropdownAdapter.prototype._resizeDropdown = function () {
/* original property
var css = {
width: this.$container.outerWidth(false)'px'
};
*/
var css = {
width: this.$container.outerWidth(false) + 2 + 'px'
};
if (this.options.get('dropdownAutoWidth')) {
css.minWidth = css.width;
css.position = 'relative';
css.width = 'auto';
}
this.$dropdown.css(css);
};
return SingleDropdownAdapter;
});
/**
* selectionAdapter adapter 0
* TODO: test and description
*
*/
$.fn.select2.amd.define("selectionAdapter", [
"select2/utils",
"select2/keys",
"select2/selection/multiple",
"select2/selection/eventRelay",
"select2/selection/search",
],
function (Utils, KEYS, MultipleSelection, EventRelay, Search) {
var SelectionAdapter = Utils.Decorate(MultipleSelection, Search);
// Decorates adapter with EventRelay - ensures events will continue to fire
// e.g. selected, changed
SelectionAdapter = Utils.Decorate(SelectionAdapter, EventRelay);
SelectionAdapter.prototype.render = function () {
var $search = $(
'<li class="select2-search select2-search--inline">' +
'<input class="select2-search__field" type="search" tabindex="-1"' +
' autocomplete="off" autocorrect="off" autocapitalize="none"' +
' spellcheck="false" role="searchbox" aria-autocomplete=""/>' +
'</li>'
);
this.$searchContainer = $search;
this.$search = $search.find('input');
var $selection = MultipleSelection.prototype.render.call(this);
$selection.addClass("select2-selection-adapter");
this._transferTabIndex();
return $selection;
};
SelectionAdapter.prototype.bind = function (container, $container) {
var self = this;
var resultsId = container.id + '-results';
MultipleSelection.prototype.bind.call(this, container, $container);
container.on('open', function () {
self.$search.attr('aria-controls', resultsId);
self.$search.trigger('focus');
});
container.on('close', function () {
self.$search.val('');
self.$search.removeAttr('aria-controls');
self.$search.removeAttr('aria-activedescendant');
self.$search.trigger('focus');
});
container.on('enable', function () {
self.$search.prop('disabled', false);
self._transferTabIndex();
});
container.on('disable', function () {
self.$search.prop('disabled', true);
});
container.on('focus', function (evt) {
self.$search.trigger('focus');
});
container.on('results:focus', function (params) {
if (params.data._resultId) {
self.$search.attr('aria-activedescendant', params.data._resultId);
} else {
self.$search.removeAttr('aria-activedescendant');
}
});
this.$selection.on('focusin', '.select2-search--inline', function (evt) {
self.trigger('focus', evt);
});
this.$selection.on('focusout', '.select2-search--inline', function (evt) {
self._handleBlur(evt);
});
this.$selection.on('keydown', '.select2-search--inline', function (evt) {
evt.stopPropagation();
self.trigger('keypress', evt);
self._keyUpPrevented = evt.isDefaultPrevented();
var key = evt.which;
if (key === KEYS.BACKSPACE && self.$search.val() === '') {
var $previousChoice = self.$searchContainer
.prev('.select2-selection__choice');
if ($previousChoice.length > 0) {
var item = Utils.GetData($previousChoice[0], 'data');
self.searchRemoveChoice(item);
evt.preventDefault();
}
}
});
this.$selection.on('click', '.select2-search--inline', function (evt) {
if (self.$search.val()) {
evt.stopPropagation();
}
});
// Try to detect the IE version should the `documentMode` property that
// is stored on the document. This is only implemented in IE and is
// slightly cleaner than doing a user agent check.
// This property is not available in Edge, but Edge also doesn't have
// this bug.
var msie = document.documentMode;
var disableInputEvents = msie && msie <= 11;
// Workaround for browsers which do not support the `input` event
// This will prevent double-triggering of events for browsers which support
// both the `keyup` and `input` events.
this.$selection.on(
'input.searchcheck',
'.select2-search--inline',
function (evt) {
// IE will trigger the `input` event when a placeholder is used on a
// search box. To get around this issue, we are forced to ignore all
// `input` events in IE and keep using `keyup`.
if (disableInputEvents) {
self.$selection.off('input.search input.searchcheck');
return;
}
// Unbind the duplicated `keyup` event
self.$selection.off('keyup.search');
// Company adapted dropdown single selection
// if contain previous selection, clean current input change
if (this.previousElementSibling) {
this.querySelector("input").value = "";
}
}
);
this.$selection.on(
'keyup.search input.search',
'.select2-search--inline',
function (evt) {
// IE will trigger the `input` event when a placeholder is used on a
// search box. To get around this issue, we are forced to ignore all
// `input` events in IE and keep using `keyup`.
if (disableInputEvents && evt.type === 'input') {
self.$selection.off('input.search input.searchcheck');
return;
}
var key = evt.which;
// We can freely ignore events from modifier keys
if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
return;
}
// Tabbing will be handled during the `keydown` phase
if (key == KEYS.TAB) {
return;
}
self.handleSearch(evt);
}
);
};
return SelectionAdapter;
});
/**
* selectionAdapter adapter 1
* TODO: test and description
*
*/
$.fn.select2.amd.define("CustomSelectionAdapter", [
"select2/utils",
"select2/selection/multiple",
"select2/selection/placeholder",
"select2/selection/eventRelay",
"select2/selection/single",
"select2/selection/search",
],
function (Utils, MultipleSelection, Placeholder, EventRelay, SingleSelection, Search) {
// Decorates MultipleSelection with Placeholder
var adapter = Utils.Decorate(SingleSelection, Search);
// Decorates adapter with EventRelay - ensures events will continue to fire
// e.g. selected, changed
adapter = Utils.Decorate(adapter, EventRelay);
adapter.prototype.render = function () {
// Use selection-box from SingleSelection adapter
// This implementation overrides the default implementation
var $selection = SingleSelection.prototype.render.call(this);
return $selection;
};
adapter.prototype.update = function (data) {
if (data.length === 0) {
this.clear();
return;
}
var selection = data[0];
var $rendered = this.$selection.find('.select2-selection__rendered');
var formatted = this.display(selection, $rendered);
$rendered.empty().append(formatted);
var title = selection.title || selection.text;
if (title) {
$rendered.attr('title', title);
} else {
$rendered.removeAttr('title');
}
};
return adapter;
});
/**
* Google locations autocomplete search adapter 1
* TODO: test and description
*
*/
$.fn.select2.amd.define('select2/data/googleAutocompleteAdapter', ['select2/data/array', 'select2/utils'],
function (ArrayAdapter, Utils) {
function GoogleAutocompleteDataAdapter($element, options) {
GoogleAutocompleteDataAdapter.__super__.constructor.call(this, $element, options);
}
Utils.Extend(GoogleAutocompleteDataAdapter, ArrayAdapter);
GoogleAutocompleteDataAdapter.prototype.query = function (params, callback) {
var returnSuggestions = function (predictions, status) {
var data = {results: []};
if (status != google.maps.places.PlacesServiceStatus.OK) {
callback(data);
}
for (var i = 0; i < predictions.length; i++) {
data.results.push({id: predictions[i].place_id, text: predictions[i].description});
}
callback(data);
};
if (params.term && params.term != '') {
var service = new google.maps.places.AutocompleteService();
service.getPlacePredictions({input: params.term}, returnSuggestions);
} else {
var data = {results: []};
callback(data);
}
};
return GoogleAutocompleteDataAdapter;
}
);
/**
* Google locations autocomplete search adapter 2
* doing via ajax to internal custom service
*
*/
$.fn.select2.amd.define(
'googleAutocompleteAdapter',
['select2/data/array', 'select2/utils'],
function (ArrayAdapter, Utils) {
function GoogleAutocompleteDataAdapter($element, options) {
GoogleAutocompleteDataAdapter.__super__.constructor.call(this, $element, options);
}
Utils.Extend(GoogleAutocompleteDataAdapter, ArrayAdapter);
GoogleAutocompleteDataAdapter.prototype.query = function (params, callback) {
var returnSuggestions = function (predictions, status) {
var data = {results: []};
if (status != 200) {
callback(data);
}
for (var i = 0; i < predictions.length; i++) {
data.results.push({id: predictions[i].place_id, text: predictions[i].addresse_description});
}
callback(data);
};
if (params.term && params.term != '') {
//var service = new google.maps.places.AutocompleteService();
$.ajax({
url: "/bin/com/company/forms/",
type: "GET",
data: {
input: params.term
}, success: function (data) {
returnSuggestions(data, 200)
}, error: function (err) {
console.error("google adapter error: ", err);
}
});
// service.getPlacePredictions({input: params.term}, returnSuggestions);
} else {
var data = {results: []};
callback(data);
}
};
return GoogleAutocompleteDataAdapter;
}
);
/**
* Selection decorator
* bind prototype decorated incomplete - not working properly.
*
*/
$.fn.select2.amd.define("CustomAdapter",
["select2/utils",
"select2/selection/search",
"select2/selection/multiple"],
function (Utils, Search, SelectionMultiple) {
var SearchAdapter = function (decorated, $element, options) {
return decorated.call(this, $element, options);
};
SearchAdapter.prototype.bind = function (decorated, container, $container) {
container.on("open", function () {
console.log("opening");
});
return decorated.call(this, container, $container);
}
return Utils.Decorate(SelectionMultiple, SearchAdapter);
});
/**
* Multiple Selection adapter working perfect with bind prototype
* The order of the decorations have to be
* 1. decorate with needed placeholder
* 2. decorate Multiple selection with the search
* 3. decorate adapter with the allowClear selection
* With the previous decorations you have now the complete control of the Search.prototype.bind of select2
*/
$.fn.select2.amd.define("selectionAdapter", [
"select2/utils",
"select2/keys",
"select2/selection/multiple",
"select2/selection/placeholder",
"select2/selection/eventRelay",
"select2/selection/search",
"select2/selection/allowClear"
],
function (Utils, KEYS, MultipleSelection, Placeholder, EventRelay, Search, AllowClear) {
//step 1
var adapter = Utils.Decorate(MultipleSelection, Placeholder);
// step 2
adapter = Utils.Decorate(adapter, Search);
adapter.prototype.bind = function (container, $container) {
var self = this;
var resultsId = container.id + '-results';
// the decorator is MultipleSelection
MultipleSelection.prototype.bind.call(this, container, $container);
container.on('open', function () {
self.$search.attr('aria-controls', resultsId);
self.$search.trigger('focus');
});
container.on('close', function () {
self.$search.val('');
self.$search.removeAttr('aria-controls');
self.$search.removeAttr('aria-activedescendant');
self.$search.trigger('focus');
});
container.on('enable', function () {
self.$search.prop('disabled', false);
self._transferTabIndex();
});
container.on('disable', function () {
self.$search.prop('disabled', true);
});
container.on('focus', function (evt) {
self.$search.trigger('focus');
});
container.on('results:focus', function (params) {
if (params.data._resultId) {
self.$search.attr('aria-activedescendant', params.data._resultId);
} else {
self.$search.removeAttr('aria-activedescendant');
}
});
this.$selection.on('focusin', '.select2-search--inline', function (evt) {
self.trigger('focus', evt);
});
this.$selection.on('focusout', '.select2-search--inline', function (evt) {
self._handleBlur(evt);
});
this.$selection.on('keydown', '.select2-search--inline', function (evt) {
evt.stopPropagation();
self.trigger('keypress', evt);
self._keyUpPrevented = evt.isDefaultPrevented();
var key = evt.which;
if (key === KEYS.BACKSPACE && self.$search.val() === '') {
var $previousChoice = self.$searchContainer
.prev('.select2-selection__choice');
if ($previousChoice.length > 0) {
var item = Utils.GetData($previousChoice[0], 'data');
self.searchRemoveChoice(item);
evt.preventDefault();
}
}
});
this.$selection.on('click', '.select2-search--inline', function (evt) {
if (self.$search.val()) {
evt.stopPropagation();
}
});
// Try to detect the IE version should the `documentMode` property that
// is stored on the document. This is only implemented in IE and is
// slightly cleaner than doing a user agent check.
// This property is not available in Edge, but Edge also doesn't have
// this bug.
var msie = document.documentMode;
var disableInputEvents = msie && msie <= 11;
// Workaround for browsers which do not support the `input` event
// This will prevent double-triggering of events for browsers which support
// both the `keyup` and `input` events.
this.$selection.on(
'input.searchcheck',
'.select2-search--inline',
function (evt) {
// IE will trigger the `input` event when a placeholder is used on a
// search box. To get around this issue, we are forced to ignore all
// `input` events in IE and keep using `keyup`.
if (disableInputEvents) {
self.$selection.off('input.search input.searchcheck');
return;
}
// Unbind the duplicated `keyup` event
self.$selection.off('keyup.search');
}
);
this.$selection.on(
'keyup.search input.search',
'.select2-search--inline',
function (evt) {
// IE will trigger the `input` event when a placeholder is used on a
// search box. To get around this issue, we are forced to ignore all
// `input` events in IE and keep using `keyup`.
if (disableInputEvents && evt.type === 'input') {
self.$selection.off('input.search input.searchcheck');
return;
}
var key = evt.which;
// We can freely ignore events from modifier keys
if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
return;
}
// Tabbing will be handled during the `keydown` phase
if (key == KEYS.TAB) {
return;
}
self.handleSearch(evt);
}
);
};
// step 3
return Utils.Decorate(adapter, AllowClear);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment