diff options
Diffstat (limited to 'node_modules/materialize-css/js/select.js')
-rw-r--r-- | node_modules/materialize-css/js/select.js | 432 |
1 files changed, 432 insertions, 0 deletions
diff --git a/node_modules/materialize-css/js/select.js b/node_modules/materialize-css/js/select.js new file mode 100644 index 0000000000..2266de8500 --- /dev/null +++ b/node_modules/materialize-css/js/select.js @@ -0,0 +1,432 @@ +(function($) { + 'use strict'; + + let _defaults = { + classes: '', + dropdownOptions: {} + }; + + /** + * @class + * + */ + class FormSelect extends Component { + /** + * Construct FormSelect instance + * @constructor + * @param {Element} el + * @param {Object} options + */ + constructor(el, options) { + super(FormSelect, el, options); + + // Don't init if browser default version + if (this.$el.hasClass('browser-default')) { + return; + } + + this.el.M_FormSelect = this; + + /** + * Options for the select + * @member FormSelect#options + */ + this.options = $.extend({}, FormSelect.defaults, options); + + this.isMultiple = this.$el.prop('multiple'); + + // Setup + this.el.tabIndex = -1; + this._keysSelected = {}; + this._valueDict = {}; // Maps key to original and generated option element. + 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_FormSelect; + } + + /** + * Teardown component + */ + destroy() { + this._removeEventHandlers(); + this._removeDropdown(); + this.el.M_FormSelect = undefined; + } + + /** + * Setup Event Handlers + */ + _setupEventHandlers() { + this._handleSelectChangeBound = this._handleSelectChange.bind(this); + this._handleOptionClickBound = this._handleOptionClick.bind(this); + this._handleInputClickBound = this._handleInputClick.bind(this); + + $(this.dropdownOptions) + .find('li:not(.optgroup)') + .each((el) => { + el.addEventListener('click', this._handleOptionClickBound); + }); + this.el.addEventListener('change', this._handleSelectChangeBound); + this.input.addEventListener('click', this._handleInputClickBound); + } + + /** + * Remove Event Handlers + */ + _removeEventHandlers() { + $(this.dropdownOptions) + .find('li:not(.optgroup)') + .each((el) => { + el.removeEventListener('click', this._handleOptionClickBound); + }); + this.el.removeEventListener('change', this._handleSelectChangeBound); + this.input.removeEventListener('click', this._handleInputClickBound); + } + + /** + * Handle Select Change + * @param {Event} e + */ + _handleSelectChange(e) { + this._setValueToInput(); + } + + /** + * Handle Option Click + * @param {Event} e + */ + _handleOptionClick(e) { + e.preventDefault(); + let option = $(e.target).closest('li')[0]; + let key = option.id; + if (!$(option).hasClass('disabled') && !$(option).hasClass('optgroup') && key.length) { + let selected = true; + + if (this.isMultiple) { + // Deselect placeholder option if still selected. + let placeholderOption = $(this.dropdownOptions).find('li.disabled.selected'); + if (placeholderOption.length) { + placeholderOption.removeClass('selected'); + placeholderOption.find('input[type="checkbox"]').prop('checked', false); + this._toggleEntryFromArray(placeholderOption[0].id); + } + selected = this._toggleEntryFromArray(key); + } else { + $(this.dropdownOptions) + .find('li') + .removeClass('selected'); + $(option).toggleClass('selected', selected); + } + + // Set selected on original select option + // Only trigger if selected state changed + let prevSelected = $(this._valueDict[key].el).prop('selected'); + if (prevSelected !== selected) { + $(this._valueDict[key].el).prop('selected', selected); + this.$el.trigger('change'); + } + } + + e.stopPropagation(); + } + + /** + * Handle Input Click + */ + _handleInputClick() { + if (this.dropdown && this.dropdown.isOpen) { + this._setValueToInput(); + this._setSelectedStates(); + } + } + + /** + * Setup dropdown + */ + _setupDropdown() { + this.wrapper = document.createElement('div'); + $(this.wrapper).addClass('select-wrapper ' + this.options.classes); + this.$el.before($(this.wrapper)); + this.wrapper.appendChild(this.el); + + if (this.el.disabled) { + this.wrapper.classList.add('disabled'); + } + + // Create dropdown + this.$selectOptions = this.$el.children('option, optgroup'); + this.dropdownOptions = document.createElement('ul'); + this.dropdownOptions.id = `select-options-${M.guid()}`; + $(this.dropdownOptions).addClass( + 'dropdown-content select-dropdown ' + (this.isMultiple ? 'multiple-select-dropdown' : '') + ); + + // Create dropdown structure. + if (this.$selectOptions.length) { + this.$selectOptions.each((el) => { + if ($(el).is('option')) { + // Direct descendant option. + let optionEl; + if (this.isMultiple) { + optionEl = this._appendOptionWithIcon(this.$el, el, 'multiple'); + } else { + optionEl = this._appendOptionWithIcon(this.$el, el); + } + + this._addOptionToValueDict(el, optionEl); + } else if ($(el).is('optgroup')) { + // Optgroup. + let selectOptions = $(el).children('option'); + $(this.dropdownOptions).append( + $('<li class="optgroup"><span>' + el.getAttribute('label') + '</span></li>')[0] + ); + + selectOptions.each((el) => { + let optionEl = this._appendOptionWithIcon(this.$el, el, 'optgroup-option'); + this._addOptionToValueDict(el, optionEl); + }); + } + }); + } + + this.$el.after(this.dropdownOptions); + + // Add input dropdown + this.input = document.createElement('input'); + $(this.input).addClass('select-dropdown dropdown-trigger'); + this.input.setAttribute('type', 'text'); + this.input.setAttribute('readonly', 'true'); + this.input.setAttribute('data-target', this.dropdownOptions.id); + if (this.el.disabled) { + $(this.input).prop('disabled', 'true'); + } + + this.$el.before(this.input); + this._setValueToInput(); + + // Add caret + let dropdownIcon = $( + '<svg class="caret" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="M7 10l5 5 5-5z"/><path d="M0 0h24v24H0z" fill="none"/></svg>' + ); + this.$el.before(dropdownIcon[0]); + + // Initialize dropdown + if (!this.el.disabled) { + let dropdownOptions = $.extend({}, this.options.dropdownOptions); + + // Add callback for centering selected option when dropdown content is scrollable + dropdownOptions.onOpenEnd = (el) => { + let selectedOption = $(this.dropdownOptions) + .find('.selected') + .first(); + + if (selectedOption.length) { + // Focus selected option in dropdown + M.keyDown = true; + this.dropdown.focusedIndex = selectedOption.index(); + this.dropdown._focusFocusedItem(); + M.keyDown = false; + + // Handle scrolling to selected option + if (this.dropdown.isScrollable) { + let scrollOffset = + selectedOption[0].getBoundingClientRect().top - + this.dropdownOptions.getBoundingClientRect().top; // scroll to selected option + scrollOffset -= this.dropdownOptions.clientHeight / 2; // center in dropdown + this.dropdownOptions.scrollTop = scrollOffset; + } + } + }; + + if (this.isMultiple) { + dropdownOptions.closeOnClick = false; + } + this.dropdown = M.Dropdown.init(this.input, dropdownOptions); + } + + // Add initial selections + this._setSelectedStates(); + } + + /** + * Add option to value dict + * @param {Element} el original option element + * @param {Element} optionEl generated option element + */ + _addOptionToValueDict(el, optionEl) { + let index = Object.keys(this._valueDict).length; + let key = this.dropdownOptions.id + index; + let obj = {}; + optionEl.id = key; + + obj.el = el; + obj.optionEl = optionEl; + this._valueDict[key] = obj; + } + + /** + * Remove dropdown + */ + _removeDropdown() { + $(this.wrapper) + .find('.caret') + .remove(); + $(this.input).remove(); + $(this.dropdownOptions).remove(); + $(this.wrapper).before(this.$el); + $(this.wrapper).remove(); + } + + /** + * Setup dropdown + * @param {Element} select select element + * @param {Element} option option element from select + * @param {String} type + * @return {Element} option element added + */ + _appendOptionWithIcon(select, option, type) { + // Add disabled attr if disabled + let disabledClass = option.disabled ? 'disabled ' : ''; + let optgroupClass = type === 'optgroup-option' ? 'optgroup-option ' : ''; + let multipleCheckbox = this.isMultiple + ? `<label><input type="checkbox"${disabledClass}"/><span>${option.innerHTML}</span></label>` + : option.innerHTML; + let liEl = $('<li></li>'); + let spanEl = $('<span></span>'); + spanEl.html(multipleCheckbox); + liEl.addClass(`${disabledClass} ${optgroupClass}`); + liEl.append(spanEl); + + // add icons + let iconUrl = option.getAttribute('data-icon'); + if (!!iconUrl) { + let imgEl = $(`<img alt="" src="${iconUrl}">`); + liEl.prepend(imgEl); + } + + // Check for multiple type. + $(this.dropdownOptions).append(liEl[0]); + return liEl[0]; + } + + /** + * Toggle entry from option + * @param {String} key Option key + * @return {Boolean} if entry was added or removed + */ + _toggleEntryFromArray(key) { + let notAdded = !this._keysSelected.hasOwnProperty(key); + let $optionLi = $(this._valueDict[key].optionEl); + + if (notAdded) { + this._keysSelected[key] = true; + } else { + delete this._keysSelected[key]; + } + + $optionLi.toggleClass('selected', notAdded); + + // Set checkbox checked value + $optionLi.find('input[type="checkbox"]').prop('checked', notAdded); + + // use notAdded instead of true (to detect if the option is selected or not) + $optionLi.prop('selected', notAdded); + + return notAdded; + } + + /** + * Set text value to input + */ + _setValueToInput() { + let values = []; + let options = this.$el.find('option'); + + options.each((el) => { + if ($(el).prop('selected')) { + let text = $(el).text(); + values.push(text); + } + }); + + if (!values.length) { + let firstDisabled = this.$el.find('option:disabled').eq(0); + if (firstDisabled.length && firstDisabled[0].value === '') { + values.push(firstDisabled.text()); + } + } + + this.input.value = values.join(', '); + } + + /** + * Set selected state of dropdown to match actual select element + */ + _setSelectedStates() { + this._keysSelected = {}; + + for (let key in this._valueDict) { + let option = this._valueDict[key]; + let optionIsSelected = $(option.el).prop('selected'); + $(option.optionEl) + .find('input[type="checkbox"]') + .prop('checked', optionIsSelected); + if (optionIsSelected) { + this._activateOption($(this.dropdownOptions), $(option.optionEl)); + this._keysSelected[key] = true; + } else { + $(option.optionEl).removeClass('selected'); + } + } + } + + /** + * Make option as selected and scroll to selected position + * @param {jQuery} collection Select options jQuery element + * @param {Element} newOption element of the new option + */ + _activateOption(collection, newOption) { + if (newOption) { + if (!this.isMultiple) { + collection.find('li.selected').removeClass('selected'); + } + let option = $(newOption); + option.addClass('selected'); + } + } + + /** + * Get Selected Values + * @return {Array} Array of selected values + */ + getSelectedValues() { + let selectedValues = []; + for (let key in this._keysSelected) { + selectedValues.push(this._valueDict[key].el.value); + } + return selectedValues; + } + } + + M.FormSelect = FormSelect; + + if (M.jQueryLoaded) { + M.initializeJqueryWrapper(FormSelect, 'formSelect', 'M_FormSelect'); + } +})(cash); |