{"version":3,"file":"form-autocomplete.min.js","sources":["https:\/\/www.alsg.org\/home\/lib\/amd\/src\/form-autocomplete.js"],"sourcesContent":["\/\/ This file is part of Moodle - http:\/\/moodle.org\/\n\/\/\n\/\/ Moodle is free software: you can redistribute it and\/or modify\n\/\/ it under the terms of the GNU General Public License as published by\n\/\/ the Free Software Foundation, either version 3 of the License, or\n\/\/ (at your option) any later version.\n\/\/\n\/\/ Moodle is distributed in the hope that it will be useful,\n\/\/ but WITHOUT ANY WARRANTY; without even the implied warranty of\n\/\/ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n\/\/ GNU General Public License for more details.\n\/\/\n\/\/ You should have received a copy of the GNU General Public License\n\/\/ along with Moodle. If not, see .\n\n\/**\n * Autocomplete wrapper for select2 library.\n *\n * @module core\/form-autocomplete\n * @copyright 2015 Damyon Wiese \n * @license http:\/\/www.gnu.org\/copyleft\/gpl.html GNU GPL v3 or later\n * @since 3.0\n *\/\ndefine([\n 'jquery',\n 'core\/log',\n 'core\/str',\n 'core\/templates',\n 'core\/notification',\n 'core\/loadingicon',\n 'core\/aria',\n 'core_form\/changechecker',\n], function(\n $,\n log,\n str,\n templates,\n notification,\n LoadingIcon,\n Aria,\n FormChangeChecker\n) {\n \/\/ Private functions and variables.\n \/** @var {Object} KEYS - List of keycode constants. *\/\n var KEYS = {\n DOWN: 40,\n ENTER: 13,\n SPACE: 32,\n ESCAPE: 27,\n COMMA: 44,\n UP: 38,\n LEFT: 37,\n RIGHT: 39\n };\n\n var uniqueId = Date.now();\n\n \/**\n * Make an item in the selection list \"active\".\n *\n * @method activateSelection\n * @private\n * @param {Number} index The index in the current (visible) list of selection.\n * @param {Object} state State variables for this autocomplete element.\n * @return {Promise}\n *\/\n var activateSelection = function(index, state) {\n \/\/ Find the elements in the DOM.\n var selectionElement = $(document.getElementById(state.selectionId));\n\n \/\/ Count the visible items.\n var length = selectionElement.children('[aria-selected=true]').length;\n \/\/ Limit the index to the upper\/lower bounds of the list (wrap in both directions).\n index = index % length;\n while (index < 0) {\n index += length;\n }\n \/\/ Find the specified element.\n var element = $(selectionElement.children('[aria-selected=true]').get(index));\n \/\/ Create an id we can assign to this element.\n var itemId = state.selectionId + '-' + index;\n\n \/\/ Deselect all the selections.\n selectionElement.children().attr('data-active-selection', null).attr('id', '');\n\n \/\/ Select only this suggestion and assign it the id.\n element.attr('data-active-selection', true).attr('id', itemId);\n\n \/\/ Tell the input field it has a new active descendant so the item is announced.\n selectionElement.attr('aria-activedescendant', itemId);\n selectionElement.attr('data-active-value', element.attr('data-value'));\n\n return $.Deferred().resolve();\n };\n\n \/**\n * Get the actively selected element from the state object.\n *\n * @param {Object} state\n * @returns {jQuery}\n *\/\n var getActiveElementFromState = function(state) {\n var selectionRegion = $(document.getElementById(state.selectionId));\n var activeId = selectionRegion.attr('aria-activedescendant');\n\n if (activeId) {\n var activeElement = $(document.getElementById(activeId));\n if (activeElement.length) {\n \/\/ The active descendent still exists.\n return activeElement;\n }\n }\n\n \/\/ Ensure we are creating a properly formed selector based on the active value.\n var activeValue = selectionRegion.attr('data-active-value')?.replace(\/\"\/g, '\\\\\"');\n return selectionRegion.find('[data-value=\"' + activeValue + '\"]');\n };\n\n \/**\n * Update the active selection from the given state object.\n *\n * @param {Object} state\n *\/\n var updateActiveSelectionFromState = function(state) {\n var activeElement = getActiveElementFromState(state);\n var activeValue = activeElement.attr('data-value');\n\n var selectionRegion = $(document.getElementById(state.selectionId));\n if (activeValue) {\n \/\/ Find the index of the currently selected index.\n var activeIndex = selectionRegion.find('[aria-selected=true]').index(activeElement);\n\n if (activeIndex !== -1) {\n activateSelection(activeIndex, state);\n return;\n }\n }\n\n \/\/ Either the active index was not set, or it could not be found.\n \/\/ Select the first value instead.\n activateSelection(0, state);\n };\n\n \/**\n * Update the element that shows the currently selected items.\n *\n * @method updateSelectionList\n * @private\n * @param {Object} options Original options for this autocomplete element.\n * @param {Object} state State variables for this autocomplete element.\n * @param {JQuery} originalSelect The JQuery object matching the hidden select list.\n * @return {Promise}\n *\/\n var updateSelectionList = function(options, state, originalSelect) {\n var pendingKey = 'form-autocomplete-updateSelectionList-' + state.inputId;\n M.util.js_pending(pendingKey);\n\n \/\/ Build up a valid context to re-render the template.\n var items = [];\n var newSelection = $(document.getElementById(state.selectionId));\n originalSelect.children('option').each(function(index, ele) {\n if ($(ele).prop('selected')) {\n var label;\n if ($(ele).data('html')) {\n label = $(ele).data('html');\n } else {\n label = $(ele).html();\n }\n if (label !== '') {\n items.push({label: label, value: $(ele).attr('value')});\n }\n }\n });\n\n if (!hasItemListChanged(state, items)) {\n M.util.js_complete(pendingKey);\n return Promise.resolve();\n }\n\n state.items = items;\n\n var context = $.extend(options, state);\n \/\/ Render the template.\n return templates.render(options.templates.items, context)\n .then(function(html, js) {\n \/\/ Add it to the page.\n templates.replaceNodeContents(newSelection, html, js);\n\n updateActiveSelectionFromState(state);\n\n return;\n })\n .then(function() {\n return M.util.js_complete(pendingKey);\n })\n .catch(notification.exception);\n };\n\n \/**\n * Check whether the list of items stored in the state has changed.\n *\n * @param {Object} state\n * @param {Array} items\n * @returns {Boolean}\n *\/\n var hasItemListChanged = function(state, items) {\n if (state.items.length !== items.length) {\n return true;\n }\n\n \/\/ Check for any items in the state items which are not present in the new items list.\n return state.items.filter(item => items.indexOf(item) === -1).length > 0;\n };\n\n \/**\n * Notify of a change in the selection.\n *\n * @param {jQuery} originalSelect The jQuery object matching the hidden select list.\n *\/\n var notifyChange = function(originalSelect) {\n FormChangeChecker.markFormChangedFromNode(originalSelect[0]);\n\n \/\/ Note, jQuery .change() was not working here. Better to\n \/\/ use plain JavaScript anyway.\n originalSelect[0].dispatchEvent(new Event('change'));\n };\n\n \/**\n * Remove the given item from the list of selected things.\n *\n * @method deselectItem\n * @private\n * @param {Object} options Original options for this autocomplete element.\n * @param {Object} state State variables for this autocomplete element.\n * @param {Element} item The item to be deselected.\n * @param {Element} originalSelect The original select list.\n * @return {Promise}\n *\/\n var deselectItem = function(options, state, item, originalSelect) {\n var selectedItemValue = $(item).attr('data-value');\n\n \/\/ Preprend an empty option to the select list to avoid having a default selected option.\n if (originalSelect.find('option').first().attr('value') !== undefined) {\n originalSelect.prepend($('