Linux aries.aptans.com 4.18.0-348.20.1.lve.1.el8.x86_64 #1 SMP Wed Mar 16 08:45:39 EDT 2022 x86_64
Apache
: 135.181.142.107 | : 172.70.130.44
Cant Read [ /etc/named.conf ]
7.4.33
aja
www.github.com/MadExploits
Terminal
AUTO ROOT
Adminer
Backdoor Destroyer
Linux Exploit
Lock Shell
Lock File
Create User
CREATE RDP
PHP Mailer
BACKCONNECT
UNLOCK SHELL
HASH IDENTIFIER
CPANEL RESET
CREATE WP USER
README
+ Create Folder
+ Create File
/
home /
aja /
public_html /
ead /
lib /
amd /
src /
[ HOME SHELL ]
Name
Size
Permission
Action
emoji
[ DIR ]
drwxr-xr-x
local
[ DIR ]
drwxr-xr-x
adapter.js
190.74
KB
-rw-r--r--
addblockmodal.js
2.52
KB
-rw-r--r--
ajax.js
11.97
KB
-rw-r--r--
aria.js
1011
B
-rw-r--r--
auto_rows.js
3.46
KB
-rw-r--r--
autoscroll.js
6.49
KB
-rw-r--r--
backoff_timer.js
4.98
KB
-rw-r--r--
chart_axis.js
7.39
KB
-rw-r--r--
chart_bar.js
3.08
KB
-rw-r--r--
chart_base.js
11.36
KB
-rw-r--r--
chart_builder.js
1.65
KB
-rw-r--r--
chart_line.js
2.08
KB
-rw-r--r--
chart_output.js
1.08
KB
-rw-r--r--
chart_output_base.js
2.03
KB
-rw-r--r--
chart_output_chartjs.js
10.83
KB
-rw-r--r--
chart_output_htmltable.js
3.3
KB
-rw-r--r--
chart_pie.js
3.03
KB
-rw-r--r--
chart_series.js
8.35
KB
-rw-r--r--
chartjs-lazy.js
670.93
KB
-rw-r--r--
chartjs.js
913
B
-rw-r--r--
checkbox-toggleall.js
12.78
KB
-rw-r--r--
config.js
1002
B
-rw-r--r--
custom_interaction_events.js
22.18
KB
-rw-r--r--
dragdrop.js
12.92
KB
-rw-r--r--
drawer.js
3.49
KB
-rw-r--r--
drawer_events.js
967
B
-rw-r--r--
event.js
3.97
KB
-rw-r--r--
first.js
1.36
KB
-rw-r--r--
form-autocomplete.js
50.77
KB
-rw-r--r--
form-cohort-selector.js
2.46
KB
-rw-r--r--
form-course-selector.js
3.77
KB
-rw-r--r--
fragment.js
4.64
KB
-rw-r--r--
fullscreen.js
1.62
KB
-rw-r--r--
icon_system.js
2.56
KB
-rw-r--r--
icon_system_fontawesome.js
4.01
KB
-rw-r--r--
icon_system_standard.js
2.3
KB
-rw-r--r--
inplace_editable.js
13.29
KB
-rw-r--r--
key_codes.js
1.34
KB
-rw-r--r--
loadingicon.js
3.87
KB
-rw-r--r--
localstorage.js
2.08
KB
-rw-r--r--
log.js
1.74
KB
-rw-r--r--
loglevel.js
9.66
KB
-rw-r--r--
modal.js
28.21
KB
-rw-r--r--
modal_backdrop.js
4.46
KB
-rw-r--r--
modal_cancel.js
1.44
KB
-rw-r--r--
modal_events.js
1.27
KB
-rw-r--r--
modal_factory.js
8.65
KB
-rw-r--r--
modal_registry.js
2.38
KB
-rw-r--r--
modal_save_cancel.js
2.16
KB
-rw-r--r--
mustache.js
26.69
KB
-rw-r--r--
network.js
8.02
KB
-rw-r--r--
normalise.js
1.6
KB
-rw-r--r--
notification.js
8.86
KB
-rw-r--r--
page_global.js
5.38
KB
-rw-r--r--
paged_content.js
2.87
KB
-rw-r--r--
paged_content_events.js
1.17
KB
-rw-r--r--
paged_content_factory.js
20.68
KB
-rw-r--r--
paged_content_pages.js
11.75
KB
-rw-r--r--
paged_content_paging_bar.js
20.34
KB
-rw-r--r--
paged_content_paging_bar_limit...
2.36
KB
-rw-r--r--
paged_content_paging_dropdown....
7.36
KB
-rw-r--r--
pending.js
1.57
KB
-rw-r--r--
permissionmanager.js
9.77
KB
-rw-r--r--
popover_region_controller.js
12.52
KB
-rw-r--r--
popper.js
79.2
KB
-rw-r--r--
prefetch.js
5.85
KB
-rw-r--r--
pubsub.js
2.17
KB
-rw-r--r--
sessionstorage.js
2.22
KB
-rw-r--r--
showhidesettings.js
11.82
KB
-rw-r--r--
sortable_list.js
29.51
KB
-rw-r--r--
storagewrapper.js
5.15
KB
-rw-r--r--
str.js
7.84
KB
-rw-r--r--
tag.js
19.77
KB
-rw-r--r--
templates.js
49.66
KB
-rw-r--r--
toast.js
3.05
KB
-rw-r--r--
tooltip.js
4.09
KB
-rw-r--r--
tree.js
18.12
KB
-rw-r--r--
truncate.js
6.56
KB
-rw-r--r--
url.js
3.65
KB
-rw-r--r--
user_date.js
9.32
KB
-rw-r--r--
userfeedback.js
2.91
KB
-rw-r--r--
utils.js
2.73
KB
-rw-r--r--
yui.js
1.13
KB
-rw-r--r--
Delete
Unzip
Zip
${this.title}
Close
Code Editor : form-autocomplete.js
// This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * Autocomplete wrapper for select2 library. * * @module core/form-autocomplete * @copyright 2015 Damyon Wiese <damyon@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later * @since 3.0 */ define( ['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification', 'core/loadingicon', 'core/aria'], function($, log, str, templates, notification, LoadingIcon, Aria) { // Private functions and variables. /** @var {Object} KEYS - List of keycode constants. */ var KEYS = { DOWN: 40, ENTER: 13, SPACE: 32, ESCAPE: 27, COMMA: 44, UP: 38, LEFT: 37, RIGHT: 39 }; var uniqueId = Date.now(); /** * Make an item in the selection list "active". * * @method activateSelection * @private * @param {Number} index The index in the current (visible) list of selection. * @param {Object} state State variables for this autocomplete element. * @return {Promise} */ var activateSelection = function(index, state) { // Find the elements in the DOM. var selectionElement = $(document.getElementById(state.selectionId)); // Count the visible items. var length = selectionElement.children('[aria-selected=true]').length; // Limit the index to the upper/lower bounds of the list (wrap in both directions). index = index % length; while (index < 0) { index += length; } // Find the specified element. var element = $(selectionElement.children('[aria-selected=true]').get(index)); // Create an id we can assign to this element. var itemId = state.selectionId + '-' + index; // Deselect all the selections. selectionElement.children().attr('data-active-selection', null).attr('id', ''); // Select only this suggestion and assign it the id. element.attr('data-active-selection', true).attr('id', itemId); // Tell the input field it has a new active descendant so the item is announced. selectionElement.attr('aria-activedescendant', itemId); selectionElement.attr('data-active-value', element.attr('data-value')); return $.Deferred().resolve(); }; /** * Get the actively selected element from the state object. * * @param {Object} state * @returns {jQuery} */ var getActiveElementFromState = function(state) { var selectionRegion = $(document.getElementById(state.selectionId)); var activeId = selectionRegion.attr('aria-activedescendant'); if (activeId) { var activeElement = $(document.getElementById(activeId)); if (activeElement.length) { // The active descendent still exists. return activeElement; } } var activeValue = selectionRegion.attr('data-active-value'); return selectionRegion.find('[data-value="' + activeValue + '"]'); }; /** * Update the active selection from the given state object. * * @param {Object} state */ var updateActiveSelectionFromState = function(state) { var activeElement = getActiveElementFromState(state); var activeValue = activeElement.attr('data-value'); var selectionRegion = $(document.getElementById(state.selectionId)); if (activeValue) { // Find the index of the currently selected index. var activeIndex = selectionRegion.find('[aria-selected=true]').index(activeElement); if (activeIndex !== -1) { activateSelection(activeIndex, state); return; } } // Either the active index was not set, or it could not be found. // Select the first value instead. activateSelection(0, state); }; /** * Update the element that shows the currently selected items. * * @method updateSelectionList * @private * @param {Object} options Original options for this autocomplete element. * @param {Object} state State variables for this autocomplete element. * @param {JQuery} originalSelect The JQuery object matching the hidden select list. * @return {Promise} */ var updateSelectionList = function(options, state, originalSelect) { var pendingKey = 'form-autocomplete-updateSelectionList-' + state.inputId; M.util.js_pending(pendingKey); // Build up a valid context to re-render the template. var items = []; var newSelection = $(document.getElementById(state.selectionId)); originalSelect.children('option').each(function(index, ele) { if ($(ele).prop('selected')) { var label; if ($(ele).data('html')) { label = $(ele).data('html'); } else { label = $(ele).html(); } if (label !== '') { items.push({label: label, value: $(ele).attr('value')}); } } }); if (!hasItemListChanged(state, items)) { M.util.js_complete(pendingKey); return Promise.resolve(); } state.items = items; var context = $.extend(options, state); // Render the template. return templates.render(options.templates.items, context) .then(function(html, js) { // Add it to the page. templates.replaceNodeContents(newSelection, html, js); updateActiveSelectionFromState(state); return; }) .then(function() { return M.util.js_complete(pendingKey); }) .catch(notification.exception); }; /** * Check whether the list of items stored in the state has changed. * * @param {Object} state * @param {Array} items * @returns {Boolean} */ var hasItemListChanged = function(state, items) { if (state.items.length !== items.length) { return true; } // Check for any items in the state items which are not present in the new items list. return state.items.filter(item => items.indexOf(item) === -1).length > 0; }; /** * Notify of a change in the selection. * * @param {jQuery} originalSelect The jQuery object matching the hidden select list. */ var notifyChange = function(originalSelect) { if (typeof M.core_formchangechecker !== 'undefined') { M.core_formchangechecker.set_form_changed(); } // Note, jQuery .change() was not working here. Better to // use plain JavaScript anyway. originalSelect[0].dispatchEvent(new Event('change')); }; /** * Remove the given item from the list of selected things. * * @method deselectItem * @private * @param {Object} options Original options for this autocomplete element. * @param {Object} state State variables for this autocomplete element. * @param {Element} item The item to be deselected. * @param {Element} originalSelect The original select list. * @return {Promise} */ var deselectItem = function(options, state, item, originalSelect) { var selectedItemValue = $(item).attr('data-value'); // Look for a match, and toggle the selected property if there is a match. originalSelect.children('option').each(function(index, ele) { if ($(ele).attr('value') == selectedItemValue) { $(ele).prop('selected', false); // We remove newly created custom tags from the suggestions list when they are deselected. if ($(ele).attr('data-iscustom')) { $(ele).remove(); } } }); // Rerender the selection list. return updateSelectionList(options, state, originalSelect) .then(function() { // Notify that the selection changed. notifyChange(originalSelect); return; }); }; /** * Make an item in the suggestions "active" (about to be selected). * * @method activateItem * @private * @param {Number} index The index in the current (visible) list of suggestions. * @param {Object} state State variables for this instance of autocomplete. * @return {Promise} */ var activateItem = function(index, state) { // Find the elements in the DOM. var inputElement = $(document.getElementById(state.inputId)); var suggestionsElement = $(document.getElementById(state.suggestionsId)); // Count the visible items. var length = suggestionsElement.children(':not([aria-hidden])').length; // Limit the index to the upper/lower bounds of the list (wrap in both directions). index = index % length; while (index < 0) { index += length; } // Find the specified element. var element = $(suggestionsElement.children(':not([aria-hidden])').get(index)); // Find the index of this item in the full list of suggestions (including hidden). var globalIndex = $(suggestionsElement.children('[role=option]')).index(element); // Create an id we can assign to this element. var itemId = state.suggestionsId + '-' + globalIndex; // Deselect all the suggestions. suggestionsElement.children().attr('aria-selected', false).attr('id', ''); // Select only this suggestion and assign it the id. element.attr('aria-selected', true).attr('id', itemId); // Tell the input field it has a new active descendant so the item is announced. inputElement.attr('aria-activedescendant', itemId); // Scroll it into view. var scrollPos = element.offset().top - suggestionsElement.offset().top + suggestionsElement.scrollTop() - (suggestionsElement.height() / 2); return suggestionsElement.animate({ scrollTop: scrollPos }, 100).promise(); }; /** * Find the index of the current active suggestion, and activate the next one. * * @method activateNextItem * @private * @param {Object} state State variable for this auto complete element. * @return {Promise} */ var activateNextItem = function(state) { // Find the list of suggestions. var suggestionsElement = $(document.getElementById(state.suggestionsId)); // Find the active one. var element = suggestionsElement.children('[aria-selected=true]'); // Find it's index. var current = suggestionsElement.children(':not([aria-hidden])').index(element); // Activate the next one. return activateItem(current + 1, state); }; /** * Find the index of the current active selection, and activate the previous one. * * @method activatePreviousSelection * @private * @param {Object} state State variables for this instance of autocomplete. * @return {Promise} */ var activatePreviousSelection = function(state) { // Find the list of selections. var selectionsElement = $(document.getElementById(state.selectionId)); // Find the active one. var element = selectionsElement.children('[data-active-selection]'); if (!element) { return activateSelection(0, state); } // Find it's index. var current = selectionsElement.children('[aria-selected=true]').index(element); // Activate the next one. return activateSelection(current - 1, state); }; /** * Find the index of the current active selection, and activate the next one. * * @method activateNextSelection * @private * @param {Object} state State variables for this instance of autocomplete. * @return {Promise} */ var activateNextSelection = function(state) { // Find the list of selections. var selectionsElement = $(document.getElementById(state.selectionId)); // Find the active one. var element = selectionsElement.children('[data-active-selection]'); var current = 0; if (element) { // The element was found. Determine the index and move to the next one. current = selectionsElement.children('[aria-selected=true]').index(element); current = current + 1; } else { // No selected item found. Move to the first. current = 0; } return activateSelection(current, state); }; /** * Find the index of the current active suggestion, and activate the previous one. * * @method activatePreviousItem * @private * @param {Object} state State variables for this autocomplete element. * @return {Promise} */ var activatePreviousItem = function(state) { // Find the list of suggestions. var suggestionsElement = $(document.getElementById(state.suggestionsId)); // Find the active one. var element = suggestionsElement.children('[aria-selected=true]'); // Find it's index. var current = suggestionsElement.children(':not([aria-hidden])').index(element); // Activate the previous one. return activateItem(current - 1, state); }; /** * Close the list of suggestions. * * @method closeSuggestions * @private * @param {Object} state State variables for this autocomplete element. * @return {Promise} */ var closeSuggestions = function(state) { // Find the elements in the DOM. var inputElement = $(document.getElementById(state.inputId)); var suggestionsElement = $(document.getElementById(state.suggestionsId)); if (inputElement.attr('aria-expanded') === "true") { // Announce the list of suggestions was closed. inputElement.attr('aria-expanded', false); } // Read the current list of selections. inputElement.attr('aria-activedescendant', state.selectionId); // Hide the suggestions list (from screen readers too). Aria.hide(suggestionsElement.get()); suggestionsElement.hide(); return $.Deferred().resolve(); }; /** * Rebuild the list of suggestions based on the current values in the select list, and the query. * * @method updateSuggestions * @private * @param {Object} options The original options for this autocomplete. * @param {Object} state The state variables for this autocomplete. * @param {String} query The current text for the search string. * @param {JQuery} originalSelect The JQuery object matching the hidden select list. * @return {Promise} */ var updateSuggestions = function(options, state, query, originalSelect) { var pendingKey = 'form-autocomplete-updateSuggestions-' + state.inputId; M.util.js_pending(pendingKey); // Find the elements in the DOM. var inputElement = $(document.getElementById(state.inputId)); var suggestionsElement = $(document.getElementById(state.suggestionsId)); // Used to track if we found any visible suggestions. var matchingElements = false; // Options is used by the context when rendering the suggestions from a template. var suggestions = []; originalSelect.children('option').each(function(index, option) { if ($(option).prop('selected') !== true) { suggestions[suggestions.length] = {label: option.innerHTML, value: $(option).attr('value')}; } }); // Re-render the list of suggestions. var searchquery = state.caseSensitive ? query : query.toLocaleLowerCase(); var context = $.extend({options: suggestions}, options, state); var returnVal = templates.render( 'core/form_autocomplete_suggestions', context ) .then(function(html, js) { // We have the new template, insert it in the page. templates.replaceNode(suggestionsElement, html, js); // Get the element again. suggestionsElement = $(document.getElementById(state.suggestionsId)); // Show it if it is hidden. Aria.unhide(suggestionsElement.get()); suggestionsElement.show(); // For each option in the list, hide it if it doesn't match the query. suggestionsElement.children().each(function(index, node) { node = $(node); if ((options.caseSensitive && node.text().indexOf(searchquery) > -1) || (!options.caseSensitive && node.text().toLocaleLowerCase().indexOf(searchquery) > -1)) { Aria.unhide(node.get()); node.show(); matchingElements = true; } else { node.hide(); Aria.hide(node.get()); } }); // If we found any matches, show the list. inputElement.attr('aria-expanded', true); if (originalSelect.attr('data-notice')) { // Display a notice rather than actual suggestions. suggestionsElement.html(originalSelect.attr('data-notice')); } else if (matchingElements) { // We only activate the first item in the list if tags is false, // because otherwise "Enter" would select the first item, instead of // creating a new tag. if (!options.tags) { activateItem(0, state); } } else { // Nothing matches. Tell them that. str.get_string('nosuggestions', 'form').done(function(nosuggestionsstr) { suggestionsElement.html(nosuggestionsstr); }); } return suggestionsElement; }) .then(function() { return M.util.js_complete(pendingKey); }) .catch(notification.exception); return returnVal; }; /** * Create a new item for the list (a tag). * * @method createItem * @private * @param {Object} options The original options for the autocomplete. * @param {Object} state State variables for the autocomplete. * @param {JQuery} originalSelect The JQuery object matching the hidden select list. * @return {Promise} */ var createItem = function(options, state, originalSelect) { // Find the element in the DOM. var inputElement = $(document.getElementById(state.inputId)); // Get the current text in the input field. var query = inputElement.val(); var tags = query.split(','); var found = false; $.each(tags, function(tagindex, tag) { // If we can only select one at a time, deselect any current value. tag = tag.trim(); if (tag !== '') { if (!options.multiple) { originalSelect.children('option').prop('selected', false); } // Look for an existing option in the select list that matches this new tag. originalSelect.children('option').each(function(index, ele) { if ($(ele).attr('value') == tag) { found = true; $(ele).prop('selected', true); } }); // Only create the item if it's new. if (!found) { var option = $('<option>'); option.append(document.createTextNode(tag)); option.attr('value', tag); originalSelect.append(option); option.prop('selected', true); // We mark newly created custom options as we handle them differently if they are "deselected". option.attr('data-iscustom', true); } } }); return updateSelectionList(options, state, originalSelect) .then(function() { // Notify that the selection changed. notifyChange(originalSelect); return; }) .then(function() { // Clear the input field. inputElement.val(''); return; }) .then(function() { // Close the suggestions list. return closeSuggestions(state); }); }; /** * Select the currently active item from the suggestions list. * * @method selectCurrentItem * @private * @param {Object} options The original options for the autocomplete. * @param {Object} state State variables for the autocomplete. * @param {JQuery} originalSelect The JQuery object matching the hidden select list. * @return {Promise} */ var selectCurrentItem = function(options, state, originalSelect) { // Find the elements in the page. var inputElement = $(document.getElementById(state.inputId)); var suggestionsElement = $(document.getElementById(state.suggestionsId)); // Here loop through suggestions and set val to join of all selected items. var selectedItemValue = suggestionsElement.children('[aria-selected=true]').attr('data-value'); // The select will either be a single or multi select, so the following will either // select one or more items correctly. // Take care to use 'prop' and not 'attr' for selected properties. // If only one can be selected at a time, start by deselecting everything. if (!options.multiple) { originalSelect.children('option').prop('selected', false); } // Look for a match, and toggle the selected property if there is a match. originalSelect.children('option').each(function(index, ele) { if ($(ele).attr('value') == selectedItemValue) { $(ele).prop('selected', true); } }); return updateSelectionList(options, state, originalSelect) .then(function() { // Notify that the selection changed. notifyChange(originalSelect); return; }) .then(function() { if (options.closeSuggestionsOnSelect) { // Clear the input element. inputElement.val(''); // Close the list of suggestions. return closeSuggestions(state); } else { // Focus on the input element so the suggestions does not auto-close. inputElement.focus(); // Remove the last selected item from the suggestions list. return updateSuggestions(options, state, inputElement.val(), originalSelect); } }); }; /** * Fetch a new list of options via ajax. * * @method updateAjax * @private * @param {Event} e The event that triggered this update. * @param {Object} options The original options for the autocomplete. * @param {Object} state The state variables for the autocomplete. * @param {JQuery} originalSelect The JQuery object matching the hidden select list. * @param {Object} ajaxHandler This is a module that does the ajax fetch and translates the results. * @return {Promise} */ var updateAjax = function(e, options, state, originalSelect, ajaxHandler) { var pendingPromise = addPendingJSPromise('updateAjax'); // We need to show the indicator outside of the hidden select list. // So we get the parent id of the hidden select list. var parentElement = $(document.getElementById(state.selectId)).parent(); LoadingIcon.addIconToContainerRemoveOnCompletion(parentElement, pendingPromise); // Get the query to pass to the ajax function. var query = $(e.currentTarget).val(); // Call the transport function to do the ajax (name taken from Select2). ajaxHandler.transport(options.selector, query, function(results) { // We got a result - pass it through the translator before using it. var processedResults = ajaxHandler.processResults(options.selector, results); var existingValues = []; // Now destroy all options that are not currently selected. if (!options.multiple) { originalSelect.children('option').remove(); } originalSelect.children('option').each(function(optionIndex, option) { option = $(option); if (!option.prop('selected')) { option.remove(); } else { existingValues.push(String(option.attr('value'))); } }); if (!options.multiple && originalSelect.children('option').length === 0) { // If this is a single select - and there are no current options // the first option added will be selected by the browser. This causes a bug! // We need to insert an empty option so that none of the real options are selected. var option = $('<option>'); originalSelect.append(option); } if ($.isArray(processedResults)) { // Add all the new ones returned from ajax. $.each(processedResults, function(resultIndex, result) { if (existingValues.indexOf(String(result.value)) === -1) { var option = $('<option>'); option.append(result.label); option.attr('value', result.value); originalSelect.append(option); } }); originalSelect.attr('data-notice', ''); } else { // The AJAX handler returned a string instead of the array. originalSelect.attr('data-notice', processedResults); } // Update the list of suggestions now from the new values in the select list. pendingPromise.resolve(updateSuggestions(options, state, '', originalSelect)); }, function(error) { pendingPromise.reject(error); }); return pendingPromise; }; /** * Add all the event listeners required for keyboard nav, blur clicks etc. * * @method addNavigation * @private * @param {Object} options The options used to create this autocomplete element. * @param {Object} state State variables for this autocomplete element. * @param {JQuery} originalSelect The JQuery object matching the hidden select list. */ var addNavigation = function(options, state, originalSelect) { // Start with the input element. var inputElement = $(document.getElementById(state.inputId)); // Add keyboard nav with keydown. inputElement.on('keydown', function(e) { var pendingJsPromise = addPendingJSPromise('addNavigation-' + state.inputId + '-' + e.keyCode); switch (e.keyCode) { case KEYS.DOWN: // If the suggestion list is open, move to the next item. if (!options.showSuggestions) { // Do not consume this event. pendingJsPromise.resolve(); return true; } else if (inputElement.attr('aria-expanded') === "true") { pendingJsPromise.resolve(activateNextItem(state)); } else { // Handle ajax population of suggestions. if (!inputElement.val() && options.ajax) { require([options.ajax], function(ajaxHandler) { pendingJsPromise.resolve(updateAjax(e, options, state, originalSelect, ajaxHandler)); }); } else { // Open the suggestions list. pendingJsPromise.resolve(updateSuggestions(options, state, inputElement.val(), originalSelect)); } } // We handled this event, so prevent it. e.preventDefault(); return false; case KEYS.UP: // Choose the previous active item. pendingJsPromise.resolve(activatePreviousItem(state)); // We handled this event, so prevent it. e.preventDefault(); return false; case KEYS.ENTER: var suggestionsElement = $(document.getElementById(state.suggestionsId)); if ((inputElement.attr('aria-expanded') === "true") && (suggestionsElement.children('[aria-selected=true]').length > 0)) { // If the suggestion list has an active item, select it. pendingJsPromise.resolve(selectCurrentItem(options, state, originalSelect)); } else if (options.tags) { // If tags are enabled, create a tag. pendingJsPromise.resolve(createItem(options, state, originalSelect)); } else { pendingJsPromise.resolve(); } // We handled this event, so prevent it. e.preventDefault(); return false; case KEYS.ESCAPE: if (inputElement.attr('aria-expanded') === "true") { // If the suggestion list is open, close it. pendingJsPromise.resolve(closeSuggestions(state)); } else { pendingJsPromise.resolve(); } // We handled this event, so prevent it. e.preventDefault(); return false; } pendingJsPromise.resolve(); return true; }); // Support multi lingual COMMA keycode (44). inputElement.on('keypress', function(e) { if (e.keyCode === KEYS.COMMA) { if (options.tags) { // If we are allowing tags, comma should create a tag (or enter). addPendingJSPromise('keypress-' + e.keyCode) .resolve(createItem(options, state, originalSelect)); } // We handled this event, so prevent it. e.preventDefault(); return false; } return true; }); // Support submitting the form without leaving the autocomplete element, // or submitting too quick before the blur handler action is completed. inputElement.closest('form').on('submit', function() { if (options.tags) { // If tags are enabled, create a tag. addPendingJSPromise('form-autocomplete-submit') .resolve(createItem(options, state, originalSelect)); } return true; }); inputElement.on('blur', function() { var pendingPromise = addPendingJSPromise('form-autocomplete-blur'); window.setTimeout(function() { // Get the current element with focus. var focusElement = $(document.activeElement); var timeoutPromise = $.Deferred(); // Only close the menu if the input hasn't regained focus and if the element still exists, // and regain focus if the scrollbar is clicked. // Due to the half a second delay, it is possible that the input element no longer exist // by the time this code is being executed. if (focusElement.is(document.getElementById(state.suggestionsId))) { inputElement.focus(); // Probably the scrollbar is clicked. Regain focus. } else if (!focusElement.is(inputElement) && $(document.getElementById(state.inputId)).length) { if (options.tags) { timeoutPromise.then(function() { return createItem(options, state, originalSelect); }) .catch(); } timeoutPromise.then(function() { return closeSuggestions(state); }) .catch(); } timeoutPromise.then(function() { return pendingPromise.resolve(); }) .catch(); timeoutPromise.resolve(); }, 500); }); if (options.showSuggestions) { var arrowElement = $(document.getElementById(state.downArrowId)); arrowElement.on('click', function(e) { var pendingPromise = addPendingJSPromise('form-autocomplete-show-suggestions'); // Prevent the close timer, or we will open, then close the suggestions. inputElement.focus(); // Handle ajax population of suggestions. if (!inputElement.val() && options.ajax) { require([options.ajax], function(ajaxHandler) { pendingPromise.resolve(updateAjax(e, options, state, originalSelect, ajaxHandler)); }); } else { // Else - open the suggestions list. pendingPromise.resolve(updateSuggestions(options, state, inputElement.val(), originalSelect)); } }); } var suggestionsElement = $(document.getElementById(state.suggestionsId)); // Remove any click handler first. suggestionsElement.parent().prop("onclick", null).off("click"); suggestionsElement.parent().on('click', `#${state.suggestionsId} [role=option]`, function(e) { var pendingPromise = addPendingJSPromise('form-autocomplete-parent'); // Handle clicks on suggestions. var element = $(e.currentTarget).closest('[role=option]'); var suggestionsElement = $(document.getElementById(state.suggestionsId)); // Find the index of the clicked on suggestion. var current = suggestionsElement.children(':not([aria-hidden])').index(element); // Activate it. activateItem(current, state) .then(function() { // And select it. return selectCurrentItem(options, state, originalSelect); }) .then(function() { return pendingPromise.resolve(); }) .catch(); }); var selectionElement = $(document.getElementById(state.selectionId)); // Handle clicks on the selected items (will unselect an item). selectionElement.on('click', '[role=option]', function(e) { var pendingPromise = addPendingJSPromise('form-autocomplete-clicks'); // Remove it from the selection. pendingPromise.resolve(deselectItem(options, state, $(e.currentTarget), originalSelect)); }); // When listbox is focused, focus on the first option if there is no focused option. selectionElement.on('focus', function() { updateActiveSelectionFromState(state); }); // Keyboard navigation for the selection list. selectionElement.on('keydown', function(e) { var pendingPromise = addPendingJSPromise('form-autocomplete-keydown-' + e.keyCode); switch (e.keyCode) { case KEYS.RIGHT: case KEYS.DOWN: // We handled this event, so prevent it. e.preventDefault(); // Choose the next selection item. pendingPromise.resolve(activateNextSelection(state)); return; case KEYS.LEFT: case KEYS.UP: // We handled this event, so prevent it. e.preventDefault(); // Choose the previous selection item. pendingPromise.resolve(activatePreviousSelection(state)); return; case KEYS.SPACE: case KEYS.ENTER: // Get the item that is currently selected. var selectedItem = $(document.getElementById(state.selectionId)).children('[data-active-selection]'); if (selectedItem) { e.preventDefault(); // Unselect this item. pendingPromise.resolve(deselectItem(options, state, selectedItem, originalSelect)); } return; } // Not handled. Resolve the promise. pendingPromise.resolve(); }); // Whenever the input field changes, update the suggestion list. if (options.showSuggestions) { // Store the value of the field as its last value, when the field gains focus. inputElement.on('focus', function(e) { var query = $(e.currentTarget).val(); $(e.currentTarget).data('last-value', query); }); // If this field uses ajax, set it up. if (options.ajax) { require([options.ajax], function(ajaxHandler) { // Creating throttled handlers free of race conditions, and accurate. // This code keeps track of a throttleTimeout, which is periodically polled. // Once the throttled function is executed, the fact that it is running is noted. // If a subsequent request comes in whilst it is running, this request is re-applied. var throttleTimeout = null; var inProgress = false; var pendingKey = 'autocomplete-throttledhandler'; var handler = function(e) { // Empty the current timeout. throttleTimeout = null; // Mark this request as in-progress. inProgress = true; // Process the request. updateAjax(e, options, state, originalSelect, ajaxHandler) .then(function() { // Check if the throttleTimeout is still empty. // There's a potential condition whereby the JS request takes long enough to complete that // another task has been queued. // In this case another task will be kicked off and we must wait for that before marking htis as // complete. if (null === throttleTimeout) { // Mark this task as complete. M.util.js_complete(pendingKey); } inProgress = false; return arguments[0]; }) .catch(notification.exception); }; // For input events, we do not want to trigger many, many updates. var throttledHandler = function(e) { window.clearTimeout(throttleTimeout); if (inProgress) { // A request is currently ongoing. // Delay this request another 100ms. throttleTimeout = window.setTimeout(throttledHandler.bind(this, e), 100); return; } if (throttleTimeout === null) { // There is currently no existing timeout handler, and it has not been recently cleared, so // this is the start of a throttling check. M.util.js_pending(pendingKey); } // There is currently no existing timeout handler, and it has not been recently cleared, so this // is the start of a throttling check. // Queue a call to the handler. throttleTimeout = window.setTimeout(handler.bind(this, e), 300); }; // Trigger an ajax update after the text field value changes. inputElement.on('input', function(e) { var query = $(e.currentTarget).val(); var last = $(e.currentTarget).data('last-value'); // IE11 fires many more input events than required - even when the value has not changed. if (last !== query) { throttledHandler(e); } $(e.currentTarget).data('last-value', query); }); }); } else { inputElement.on('input', function(e) { var query = $(e.currentTarget).val(); var last = $(e.currentTarget).data('last-value'); // IE11 fires many more input events than required - even when the value has not changed. // We need to only do this for real value changed events or the suggestions will be // unclickable on IE11 (because they will be rebuilt before the click event fires). // Note - because of this we cannot close the list when the query is empty or it will break // on IE11. if (last !== query) { updateSuggestions(options, state, query, originalSelect); } $(e.currentTarget).data('last-value', query); }); } } }; /** * Create and return an unresolved Promise for some pending JS. * * @param {String} key The unique identifier for this promise * @return {Promise} */ var addPendingJSPromise = function(key) { var pendingKey = 'form-autocomplete:' + key; M.util.js_pending(pendingKey); var pendingPromise = $.Deferred(); pendingPromise .then(function() { M.util.js_complete(pendingKey); return arguments[0]; }) .catch(notification.exception); return pendingPromise; }; return { // Public variables and functions. /** * Turn a boring select box into an auto-complete beast. * * @method enhance * @param {string} selector The selector that identifies the select box. * @param {boolean} tags Whether to allow support for tags (can define new entries). * @param {string} ajax Name of an AMD module to handle ajax requests. If specified, the AMD * module must expose 2 functions "transport" and "processResults". * These are modeled on Select2 see: https://select2.github.io/options.html#ajax * @param {String} placeholder - The text to display before a selection is made. * @param {Boolean} caseSensitive - If search has to be made case sensitive. * @param {Boolean} showSuggestions - If suggestions should be shown * @param {String} noSelectionString - Text to display when there is no selection * @param {Boolean} closeSuggestionsOnSelect - Whether to close the suggestions immediately after making a selection. * @param {Object} templateOverrides A set of templates to use instead of the standard templates * @return {Promise} */ enhance: function(selector, tags, ajax, placeholder, caseSensitive, showSuggestions, noSelectionString, closeSuggestionsOnSelect, templateOverrides) { // Set some default values. var options = { selector: selector, tags: false, ajax: false, placeholder: placeholder, caseSensitive: false, showSuggestions: true, noSelectionString: noSelectionString, templates: $.extend({ input: 'core/form_autocomplete_input', items: 'core/form_autocomplete_selection_items', layout: 'core/form_autocomplete_layout', selection: 'core/form_autocomplete_selection', suggestions: 'core/form_autocomplete_suggestions', }, templateOverrides), }; var pendingKey = 'autocomplete-setup-' + selector; M.util.js_pending(pendingKey); if (typeof tags !== "undefined") { options.tags = tags; } if (typeof ajax !== "undefined") { options.ajax = ajax; } if (typeof caseSensitive !== "undefined") { options.caseSensitive = caseSensitive; } if (typeof showSuggestions !== "undefined") { options.showSuggestions = showSuggestions; } if (typeof noSelectionString === "undefined") { str.get_string('noselection', 'form').done(function(result) { options.noSelectionString = result; }).fail(notification.exception); } // Look for the select element. var originalSelect = $(selector); if (!originalSelect) { log.debug('Selector not found: ' + selector); M.util.js_complete(pendingKey); return false; } Aria.hide(originalSelect.get()); originalSelect.css('visibility', 'hidden'); // Hide the original select. // Find or generate some ids. var state = { selectId: originalSelect.attr('id'), inputId: 'form_autocomplete_input-' + uniqueId, suggestionsId: 'form_autocomplete_suggestions-' + uniqueId, selectionId: 'form_autocomplete_selection-' + uniqueId, downArrowId: 'form_autocomplete_downarrow-' + uniqueId, items: [], }; // Increment the unique counter so we don't get duplicates ever. uniqueId++; options.multiple = originalSelect.attr('multiple'); if (!options.multiple) { // If this is a single select then there is no way to de-select the current value - // unless we add a bogus blank option to be selected when nothing else is. // This matches similar code in updateAjax above. originalSelect.prepend('<option>'); } if (typeof closeSuggestionsOnSelect !== "undefined") { options.closeSuggestionsOnSelect = closeSuggestionsOnSelect; } else { // If not specified, this will close suggestions by default for single-select elements only. options.closeSuggestionsOnSelect = !options.multiple; } var originalLabel = $('[for=' + state.selectId + ']'); // Create the new markup and insert it after the select. var suggestions = []; originalSelect.children('option').each(function(index, option) { suggestions[index] = {label: option.innerHTML, value: $(option).attr('value')}; }); // Render all the parts of our UI. var context = $.extend({}, options, state); context.options = suggestions; context.items = []; // Collect rendered inline JS to be executed once the HTML is shown. var collectedjs = ''; var renderLayout = templates.render(options.templates.layout, {}) .then(function(html) { return $(html); }); var renderInput = templates.render(options.templates.input, context).then(function(html, js) { collectedjs += js; return $(html); }); var renderDatalist = templates.render(options.templates.suggestions, context).then(function(html, js) { collectedjs += js; return $(html); }); var renderSelection = templates.render(options.templates.selection, context).then(function(html, js) { collectedjs += js; return $(html); }); return $.when(renderLayout, renderInput, renderDatalist, renderSelection) .then(function(layout, input, suggestions, selection) { originalSelect.hide(); var container = originalSelect.parent(); // Ensure that the data-fieldtype is set for behat. $(input).find('input').attr('data-fieldtype', 'autocomplete'); container.append(layout); container.find('[data-region="form_autocomplete-input"]').replaceWith(input); container.find('[data-region="form_autocomplete-suggestions"]').replaceWith(suggestions); container.find('[data-region="form_autocomplete-selection"]').replaceWith(selection); templates.runTemplateJS(collectedjs); // Update the form label to point to the text input. originalLabel.attr('for', state.inputId); // Add the event handlers. addNavigation(options, state, originalSelect); var suggestionsElement = $(document.getElementById(state.suggestionsId)); // Hide the suggestions by default. suggestionsElement.hide(); Aria.hide(suggestionsElement.get()); return; }) .then(function() { // Show the current values in the selection list. return updateSelectionList(options, state, originalSelect); }) .then(function() { return M.util.js_complete(pendingKey); }) .catch(function(error) { M.util.js_complete(pendingKey); notification.exception(error); }); } }; });
Close