fouroperations/node_modules/@materializecss/materialize/js/autocomplete.js

480 lines
13 KiB
JavaScript
Executable File

(function($) {
'use strict';
let _defaults = {
data: {}, // Autocomplete data set
limit: Infinity, // Limit of results the autocomplete shows
onAutocomplete: null, // Callback for when autocompleted
dropdownOptions: {
// Default dropdown options
autoFocus: false,
closeOnClick: false,
coverTrigger: false
},
minLength: 1, // Min characters before autocomplete starts
sortFunction: function(a, b, inputString) {
// Sort function for sorting autocomplete results
return a.indexOf(inputString) - b.indexOf(inputString);
},
allowUnsafeHTML: false
};
/**
* @class
*
*/
class Autocomplete extends Component {
/**
* Construct Autocomplete instance
* @constructor
* @param {Element} el
* @param {Object} options
*/
constructor(el, options) {
super(Autocomplete, el, options);
this.el.M_Autocomplete = this;
/**
* Options for the autocomplete
* @member Autocomplete#options
* @prop {Number} duration
* @prop {Number} dist
* @prop {number} shift
* @prop {number} padding
* @prop {Boolean} fullWidth
* @prop {Boolean} indicators
* @prop {Boolean} noWrap
* @prop {Function} onCycleTo
*/
this.options = $.extend({}, Autocomplete.defaults, options);
// Setup
this.isOpen = false;
this.count = 0;
this.activeIndex = -1;
this.oldVal;
this.$inputField = this.$el.closest('.input-field');
this.$active = $();
this._mousedown = false;
this._setupDropdown();
this._setupEventHandlers();
}
static get defaults() {
return _defaults;
}
static init(els, options) {
return super.init(this, els, options);
}
/**
* Get Instance
*/
static getInstance(el) {
let domElem = !!el.jquery ? el[0] : el;
return domElem.M_Autocomplete;
}
/**
* Teardown component
*/
destroy() {
this._removeEventHandlers();
this._removeDropdown();
this.el.M_Autocomplete = undefined;
}
/**
* Setup Event Handlers
*/
_setupEventHandlers() {
this._handleInputBlurBound = this._handleInputBlur.bind(this);
this._handleInputKeyupAndFocusBound = this._handleInputKeyupAndFocus.bind(this);
this._handleInputKeydownBound = this._handleInputKeydown.bind(this);
this._handleInputClickBound = this._handleInputClick.bind(this);
this._handleContainerMousedownAndTouchstartBound = this._handleContainerMousedownAndTouchstart.bind(
this
);
this._handleContainerMouseupAndTouchendBound = this._handleContainerMouseupAndTouchend.bind(
this
);
this.el.addEventListener('blur', this._handleInputBlurBound);
this.el.addEventListener('keyup', this._handleInputKeyupAndFocusBound);
this.el.addEventListener('focus', this._handleInputKeyupAndFocusBound);
this.el.addEventListener('keydown', this._handleInputKeydownBound);
this.el.addEventListener('click', this._handleInputClickBound);
this.container.addEventListener(
'mousedown',
this._handleContainerMousedownAndTouchstartBound
);
this.container.addEventListener('mouseup', this._handleContainerMouseupAndTouchendBound);
if (typeof window.ontouchstart !== 'undefined') {
this.container.addEventListener(
'touchstart',
this._handleContainerMousedownAndTouchstartBound
);
this.container.addEventListener('touchend', this._handleContainerMouseupAndTouchendBound);
}
}
/**
* Remove Event Handlers
*/
_removeEventHandlers() {
this.el.removeEventListener('blur', this._handleInputBlurBound);
this.el.removeEventListener('keyup', this._handleInputKeyupAndFocusBound);
this.el.removeEventListener('focus', this._handleInputKeyupAndFocusBound);
this.el.removeEventListener('keydown', this._handleInputKeydownBound);
this.el.removeEventListener('click', this._handleInputClickBound);
this.container.removeEventListener(
'mousedown',
this._handleContainerMousedownAndTouchstartBound
);
this.container.removeEventListener('mouseup', this._handleContainerMouseupAndTouchendBound);
if (typeof window.ontouchstart !== 'undefined') {
this.container.removeEventListener(
'touchstart',
this._handleContainerMousedownAndTouchstartBound
);
this.container.removeEventListener(
'touchend',
this._handleContainerMouseupAndTouchendBound
);
}
}
/**
* Setup dropdown
*/
_setupDropdown() {
this.container = document.createElement('ul');
this.container.id = `autocomplete-options-${M.guid()}`;
$(this.container).addClass('autocomplete-content dropdown-content');
this.$inputField.append(this.container);
this.el.setAttribute('data-target', this.container.id);
// Initialize dropdown
let dropdownOptions = $.extend(
{},
Autocomplete.defaults.dropdownOptions,
this.options.dropdownOptions
);
let userOnItemClick = dropdownOptions.onItemClick;
// Ensuring the selectOption call when user passes custom onItemClick function to dropdown
dropdownOptions.onItemClick = (el) => {
this.selectOption($(el));
// Handle user declared onItemClick if needed
if (userOnItemClick && typeof userOnItemClick === 'function') {
userOnItemClick.call(this.dropdown, this.el);
}
};
this.dropdown = M.Dropdown.init(this.el, dropdownOptions);
// Sketchy removal of dropdown click handler
this.el.removeEventListener('click', this.dropdown._handleClickBound);
}
/**
* Remove dropdown
*/
_removeDropdown() {
this.container.parentNode.removeChild(this.container);
}
/**
* Handle Input Blur
*/
_handleInputBlur() {
if (!this._mousedown) {
this.close();
this._resetAutocomplete();
}
}
/**
* Handle Input Keyup and Focus
* @param {Event} e
*/
_handleInputKeyupAndFocus(e) {
if (e.type === 'keyup') {
Autocomplete._keydown = false;
}
this.count = 0;
let val = this.el.value.toLowerCase();
// Don't capture enter or arrow key usage.
if (e.keyCode === 13 || e.keyCode === 38 || e.keyCode === 40) {
return;
}
// Check if the input isn't empty
// Check if focus triggered by tab
if (this.oldVal !== val && (M.tabPressed || e.type !== 'focus')) {
this.open();
}
// Update oldVal
this.oldVal = val;
}
/**
* Handle Input Keydown
* @param {Event} e
*/
_handleInputKeydown(e) {
Autocomplete._keydown = true;
// Arrow keys and enter key usage
let keyCode = e.keyCode,
liElement,
numItems = $(this.container).children('li').length;
// select element on Enter
if (keyCode === M.keys.ENTER && this.activeIndex >= 0) {
liElement = $(this.container)
.children('li')
.eq(this.activeIndex);
if (liElement.length) {
this.selectOption(liElement);
e.preventDefault();
}
return;
}
// Capture up and down key
if (keyCode === M.keys.ARROW_UP || keyCode === M.keys.ARROW_DOWN) {
e.preventDefault();
if (keyCode === M.keys.ARROW_UP && this.activeIndex > 0) {
this.activeIndex--;
}
if (keyCode === M.keys.ARROW_DOWN && this.activeIndex < numItems - 1) {
this.activeIndex++;
}
this.$active.removeClass('active');
if (this.activeIndex >= 0) {
this.$active = $(this.container)
.children('li')
.eq(this.activeIndex);
this.$active.addClass('active');
// Focus selected
this.container.children[this.activeIndex].scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'nearest'
});
}
}
}
/**
* Handle Input Click
* @param {Event} e
*/
_handleInputClick(e) {
this.open();
}
/**
* Handle Container Mousedown and Touchstart
* @param {Event} e
*/
_handleContainerMousedownAndTouchstart(e) {
this._mousedown = true;
}
/**
* Handle Container Mouseup and Touchend
* @param {Event} e
*/
_handleContainerMouseupAndTouchend(e) {
this._mousedown = false;
}
/**
* Highlight partial match
*/
_highlight(input, label) {
const start = label.toLowerCase().indexOf('' + input.toLowerCase() + '');
const end = start + input.length - 1;
//custom filters may return results where the string does not match any part
if (start == -1 || end == -1) {
return [label, '', ''];
}
return [label.slice(0, start), label.slice(start, end + 1), label.slice(end + 1)];
}
/**
* Reset current element position
*/
_resetCurrentElement() {
this.activeIndex = -1;
this.$active.removeClass('active');
}
/**
* Reset autocomplete elements
*/
_resetAutocomplete() {
$(this.container).empty();
this._resetCurrentElement();
this.oldVal = null;
this.isOpen = false;
this._mousedown = false;
}
/**
* Select autocomplete option
* @param {Element} el Autocomplete option list item element
*/
selectOption(el) {
let text = el.text().trim();
this.el.value = text;
this.$el.trigger('change');
this._resetAutocomplete();
this.close();
// Handle onAutocomplete callback.
if (typeof this.options.onAutocomplete === 'function') {
this.options.onAutocomplete.call(this, text);
}
}
/**
* Render dropdown content
* @param {Object} data data set
* @param {String} val current input value
*/
_renderDropdown(data, val) {
this._resetAutocomplete();
let matchingData = [];
// Gather all matching data
for (let key in data) {
if (data.hasOwnProperty(key) && key.toLowerCase().indexOf(val) !== -1) {
let entry = {
data: data[key],
key: key
};
matchingData.push(entry);
this.count++;
}
}
// Sort
if (this.options.sortFunction) {
let sortFunctionBound = (a, b) => {
return this.options.sortFunction(
a.key.toLowerCase(),
b.key.toLowerCase(),
val.toLowerCase()
);
};
matchingData.sort(sortFunctionBound);
}
// Limit
matchingData = matchingData.slice(0, this.options.limit);
// Render
for (let i = 0; i < matchingData.length; i++) {
const entry = matchingData[i];
const item = document.createElement('li');
if (!!entry.data) {
const img = document.createElement('img');
img.classList.add('right', 'circle');
img.src = entry.data;
item.appendChild(img);
}
const parts = this._highlight(val, entry.key);
const s = document.createElement('span');
if (this.options.allowUnsafeHTML) {
s.innerHTML = parts[0] + '<span class="highlight">' + parts[1] + '</span>' + parts[2];
} else {
s.appendChild(document.createTextNode(parts[0]));
if (!!parts[1]) {
const highlight = document.createElement('span');
highlight.textContent = parts[1];
highlight.classList.add('highlight');
s.appendChild(highlight);
s.appendChild(document.createTextNode(parts[2]));
}
}
item.appendChild(s);
$(this.container).append(item);
}
}
/**
* Open Autocomplete Dropdown
*/
open() {
let val = this.el.value.toLowerCase();
this._resetAutocomplete();
if (val.length >= this.options.minLength) {
this.isOpen = true;
this._renderDropdown(this.options.data, val);
}
// Open dropdown
if (!this.dropdown.isOpen) {
this.dropdown.open();
} else {
// Recalculate dropdown when its already open
this.dropdown.recalculateDimensions();
}
}
/**
* Close Autocomplete Dropdown
*/
close() {
this.dropdown.close();
}
/**
* Update Data
* @param {Object} data
*/
updateData(data) {
let val = this.el.value.toLowerCase();
this.options.data = data;
if (this.isOpen) {
this._renderDropdown(data, val);
}
}
}
/**
* @static
* @memberof Autocomplete
*/
Autocomplete._keydown = false;
M.Autocomplete = Autocomplete;
if (M.jQueryLoaded) {
M.initializeJqueryWrapper(Autocomplete, 'autocomplete', 'M_Autocomplete');
}
})(cash);