diff options
Diffstat (limited to 'node_modules/materialize-css/js/autocomplete.js')
-rw-r--r-- | node_modules/materialize-css/js/autocomplete.js | 450 |
1 files changed, 450 insertions, 0 deletions
diff --git a/node_modules/materialize-css/js/autocomplete.js b/node_modules/materialize-css/js/autocomplete.js new file mode 100644 index 0000000000..6366736cd5 --- /dev/null +++ b/node_modules/materialize-css/js/autocomplete.js @@ -0,0 +1,450 @@ +(function($) { + 'use strict'; + + let _defaults = { + data: {}, // Autocomplete data set + limit: Infinity, // Limit of results the autocomplete shows + onAutocomplete: null, // Callback for when autocompleted + 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); + } + }; + + /** + * @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); + + this.dropdown = M.Dropdown.init(this.el, { + autoFocus: false, + closeOnClick: false, + coverTrigger: false, + onItemClick: (itemEl) => { + this.selectOption($(itemEl)); + } + }); + + // 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'); + } + } + } + + /** + * 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(string, $el) { + let img = $el.find('img'); + let matchStart = $el + .text() + .toLowerCase() + .indexOf('' + string.toLowerCase() + ''), + matchEnd = matchStart + string.length - 1, + beforeMatch = $el.text().slice(0, matchStart), + matchText = $el.text().slice(matchStart, matchEnd + 1), + afterMatch = $el.text().slice(matchEnd + 1); + $el.html( + `<span>${beforeMatch}<span class='highlight'>${matchText}</span>${afterMatch}</span>` + ); + if (img.length) { + $el.prepend(img); + } + } + + /** + * 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) { + // Break if past limit + if (this.count >= this.options.limit) { + break; + } + + 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); + } + + // Render + for (let i = 0; i < matchingData.length; i++) { + let entry = matchingData[i]; + let $autocompleteOption = $('<li></li>'); + if (!!entry.data) { + $autocompleteOption.append( + `<img src="${entry.data}" class="right circle"><span>${entry.key}</span>` + ); + } else { + $autocompleteOption.append('<span>' + entry.key + '</span>'); + } + + $(this.container).append($autocompleteOption); + this._highlight(val, $autocompleteOption); + } + } + + /** + * 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); |