MDL-51970 autocomplete: Small refactor because too many options

Each function was taking 8-10 args - combine them all in state and options
objects and pass those around instead.
This commit is contained in:
Damyon Wiese 2015-10-30 14:57:59 +08:00
parent 97d2ea7f87
commit 273b255673
2 changed files with 163 additions and 233 deletions

File diff suppressed because one or more lines are too long

View File

@ -46,11 +46,11 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
* @method activateSelection
* @private
* @param {Number} index The index in the current (visible) list of selection.
* @param {String} selectionId The id of the selection element for this instance of the autocomplete.
* @param {Object} state State variables for this autocomplete element.
*/
var activateSelection = function(index, selectionId) {
var activateSelection = function(index, state) {
// Find the elements in the DOM.
var selectionElement = $(document.getElementById(selectionId));
var selectionElement = $(document.getElementById(state.selectionId));
// Count the visible items.
var length = selectionElement.children('[aria-selected=true]').length;
@ -62,7 +62,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
// Find the specified element.
var element = $(selectionElement.children('[aria-selected=true]').get(index));
// Create an id we can assign to this element.
var itemId = selectionId + '-' + index;
var itemId = state.selectionId + '-' + index;
// Deselect all the selections.
selectionElement.children().attr('data-active-selection', false).attr('id', '');
@ -77,17 +77,16 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method deselectItem
* @private
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} selectionId The id of the selection element for this instance of the autocomplete.
* @param {Object} options Original options for this autocomplete element.
* @param {Object} state State variables for this autocomplete element.
* @param {Element} The item to be deselected.
* @param {Element} originalSelect The original select list.
* @param {Boolean} multiple Is this a multi select.
*/
var deselectItem = function(inputId, selectionId, item, originalSelect, multiple) {
var deselectItem = function(options, state, item, originalSelect) {
var selectedItemValue = $(item).attr('data-value');
// We can only deselect items if this is a multi-select field.
if (multiple) {
if (options.multiple) {
// 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) {
@ -100,7 +99,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
});
}
// Rerender the selection list.
updateSelectionList(selectionId, inputId, originalSelect, multiple);
updateSelectionList(options, state, originalSelect);
};
/**
@ -109,13 +108,12 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
* @method activateItem
* @private
* @param {Number} index The index in the current (visible) list of suggestions.
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {Object} state State variables for this instance of autocomplete.
*/
var activateItem = function(index, inputId, suggestionsId) {
var activateItem = function(index, state) {
// Find the elements in the DOM.
var inputElement = $(document.getElementById(inputId));
var suggestionsElement = $(document.getElementById(suggestionsId));
var inputElement = $(document.getElementById(state.inputId));
var suggestionsElement = $(document.getElementById(state.suggestionsId));
// Count the visible items.
var length = suggestionsElement.children('[aria-hidden=false]').length;
@ -129,7 +127,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
// 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 = suggestionsId + '-' + globalIndex;
var itemId = state.suggestionsId + '-' + globalIndex;
// Deselect all the suggestions.
suggestionsElement.children().attr('aria-selected', false).attr('id', '');
@ -153,18 +151,17 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method activateNextItem
* @private
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {Object} state State variable for this auto complete element.
*/
var activateNextItem = function(inputId, suggestionsId) {
var activateNextItem = function(state) {
// Find the list of suggestions.
var suggestionsElement = $(document.getElementById(suggestionsId));
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('[aria-hidden=false]').index(element);
// Activate the next one.
activateItem(current+1, inputId, suggestionsId);
activateItem(current+1, state);
};
/**
@ -172,42 +169,42 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method activatePreviousSelection
* @private
* @param {String} selectionId The id of the selection element for this instance of the autocomplete.
* @param {Object} state State variables for this instance of autocomplete.
*/
var activatePreviousSelection = function(selectionId) {
var activatePreviousSelection = function(state) {
// Find the list of selections.
var selectionsElement = $(document.getElementById(selectionId));
var selectionsElement = $(document.getElementById(state.selectionId));
// Find the active one.
var element = selectionsElement.children('[data-active-selection=true]');
if (!element) {
activateSelection(0, selectionId);
activateSelection(0, state);
return;
}
// Find it's index.
var current = selectionsElement.children('[aria-selected=true]').index(element);
// Activate the next one.
activateSelection(current-1, selectionId);
activateSelection(current-1, state);
};
/**
* Find the index of the current active selection, and activate the next one.
*
* @method activateNextSelection
* @private
* @param {String} selectionId The id of the selection element for this instance of the autocomplete.
* @param {Object} state State variables for this instance of autocomplete.
*/
var activateNextSelection = function(selectionId) {
var activateNextSelection = function(state) {
// Find the list of selections.
var selectionsElement = $(document.getElementById(selectionId));
var selectionsElement = $(document.getElementById(state.selectionId));
// Find the active one.
var element = selectionsElement.children('[data-active-selection=true]');
if (!element) {
activateSelection(0, selectionId);
activateSelection(0, state);
return;
}
// Find it's index.
var current = selectionsElement.children('[aria-selected=true]').index(element);
// Activate the next one.
activateSelection(current+1, selectionId);
activateSelection(current+1, state);
};
/**
@ -215,18 +212,17 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method activatePreviousItem
* @private
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {Object} state State variables for this autocomplete element.
*/
var activatePreviousItem = function(inputId, suggestionsId) {
var activatePreviousItem = function(state) {
// Find the list of suggestions.
var suggestionsElement = $(document.getElementById(suggestionsId));
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('[aria-hidden=false]').index(element);
// Activate the next one.
activateItem(current-1, inputId, suggestionsId);
activateItem(current-1, state);
};
/**
@ -234,16 +230,15 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method closeSuggestions
* @private
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {Object} state State variables for this autocomplete element.
*/
var closeSuggestions = function(inputId, suggestionsId, selectionId) {
var closeSuggestions = function(state) {
// Find the elements in the DOM.
var inputElement = $(document.getElementById(inputId));
var suggestionsElement = $(document.getElementById(suggestionsId));
var inputElement = $(document.getElementById(state.inputId));
var suggestionsElement = $(document.getElementById(state.suggestionsId));
// Announce the list of suggestions was closed, and read the current list of selections.
inputElement.attr('aria-expanded', false).attr('aria-activedescendant', selectionId);
inputElement.attr('aria-expanded', false).attr('aria-activedescendant', state.selectionId);
// Hide the suggestions list (from screen readers too).
suggestionsElement.hide().attr('aria-hidden', true);
};
@ -253,46 +248,44 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method updateSuggestions
* @private
* @param {String} query The current query typed in the input field.
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @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.
* @param {Boolean} multiple Are multiple items allowed to be selected?
* @param {Boolean} tags Are we allowed to create new items on the fly?
* @param {Boolean} caseSensitive - If search has to be made case sensitive.
*/
var updateSuggestions = function(query, inputId, suggestionsId, originalSelect, multiple, tags, caseSensitive) {
var updateSuggestions = function(options, state, query, originalSelect) {
// Find the elements in the DOM.
var inputElement = $(document.getElementById(inputId));
var suggestionsElement = $(document.getElementById(suggestionsId));
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 options = [];
var suggestions = [];
originalSelect.children('option').each(function(index, option) {
if ($(option).prop('selected') !== true) {
options[options.length] = { label: option.innerHTML, value: $(option).attr('value') };
suggestions[suggestions.length] = { label: option.innerHTML, value: $(option).attr('value') };
}
});
// Re-render the list of suggestions.
var searchquery = caseSensitive ? query : query.toLocaleLowerCase();
var searchquery = state.caseSensitive ? query : query.toLocaleLowerCase();
var context = $.extend({ options: suggestions}, options, state);
templates.render(
'core/form_autocomplete_suggestions',
{ inputId: inputId, suggestionsId: suggestionsId, options: options, multiple: multiple}
context
).done(function(newHTML) {
// We have the new template, insert it in the page.
suggestionsElement.replaceWith(newHTML);
// Get the element again.
suggestionsElement = $(document.getElementById(suggestionsId));
suggestionsElement = $(document.getElementById(state.suggestionsId));
// Show it if it is hidden.
suggestionsElement.show().attr('aria-hidden', false);
// For each option in the list, hide it if it doesn't match the query.
suggestionsElement.children().each(function(index, node) {
node = $(node);
if ((caseSensitive && node.text().indexOf(searchquery) > -1) ||
(!caseSensitive && node.text().toLocaleLowerCase().indexOf(searchquery) > -1)) {
if ((options.caseSensitive && node.text().indexOf(searchquery) > -1) ||
(!options.caseSensitive && node.text().toLocaleLowerCase().indexOf(searchquery) > -1)) {
node.show().attr('aria-hidden', false);
matchingElements = true;
} else {
@ -305,8 +298,8 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
// 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 (!tags) {
activateItem(0, inputId, suggestionsId);
if (!options.tags) {
activateItem(0, state);
}
} else {
// Nothing matches. Tell them that.
@ -323,14 +316,13 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method createItem
* @private
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {Boolean} multiple Are multiple items allowed to be selected?
* @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.
*/
var createItem = function(inputId, suggestionsId, selectionId, multiple, originalSelect) {
var createItem = function(options, state, originalSelect) {
// Find the element in the DOM.
var inputElement = $(document.getElementById(inputId));
var inputElement = $(document.getElementById(state.inputId));
// Get the current text in the input field.
var query = inputElement.val();
var tags = query.split(',');
@ -340,7 +332,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
// If we can only select one at a time, deselect any current value.
tag = tag.trim();
if (tag !== '') {
if (!multiple) {
if (!options.multiple) {
originalSelect.children('option').prop('selected', false);
}
// Look for an existing option in the select list that matches this new tag.
@ -363,12 +355,12 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
}
});
updateSelectionList(selectionId, inputId, originalSelect, multiple);
updateSelectionList(options, state, originalSelect);
// Clear the input field.
inputElement.val('');
// Close the suggestions list.
closeSuggestions(inputId, suggestionsId, selectionId);
closeSuggestions(state);
};
/**
@ -376,15 +368,14 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method updateSelectionList
* @private
* @param {String} selectionId The id of the selections element for this instance of the autocomplete.
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @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.
* @param {Boolean} multiple Does this element support multiple selections.
*/
var updateSelectionList = function(selectionId, inputId, originalSelect, multiple) {
var updateSelectionList = function(options, state, originalSelect) {
// Build up a valid context to re-render the template.
var items = [];
var newSelection = $(document.getElementById(selectionId));
var newSelection = $(document.getElementById(state.selectionId));
var activeId = newSelection.attr('aria-activedescendant');
var activeValue = false;
@ -396,11 +387,8 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
items.push( { label: $(ele).html(), value: $(ele).attr('value') } );
}
});
var context = {
selectionId: selectionId,
items: items,
multiple: multiple
};
var context = $.extend({ items: items }, options, state);
// Render the template.
templates.render('core/form_autocomplete_selection', context).done(function(newHTML) {
// Add it to the page.
@ -410,7 +398,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
// Reselect any previously selected item.
newSelection.children('[aria-selected=true]').each(function(index, ele) {
if ($(ele).attr('data-value') === activeValue) {
activateSelection(index, selectionId);
activateSelection(index, state);
}
});
}
@ -425,16 +413,14 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method selectCurrentItem
* @private
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {String} selectionId The id of the selection element for this instance of the autocomplete.
* @param {Boolean} multiple Are multiple items allowed to be selected?
* @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.
*/
var selectCurrentItem = function(inputId, suggestionsId, selectionId, multiple, originalSelect) {
var selectCurrentItem = function(options, state, originalSelect) {
// Find the elements in the page.
var inputElement = $(document.getElementById(inputId));
var suggestionsElement = $(document.getElementById(suggestionsId));
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');
@ -442,7 +428,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
// 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 (!multiple) {
if (!options.multiple) {
originalSelect.children('option').prop('selected', false);
}
// Look for a match, and toggle the selected property if there is a match.
@ -452,11 +438,11 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
}
});
// Rerender the selection list.
updateSelectionList(selectionId, inputId, originalSelect, multiple);
updateSelectionList(options, state, originalSelect);
// Clear the input element.
inputElement.val('');
// Close the list of suggestions.
closeSuggestions(inputId, suggestionsId, selectionId);
closeSuggestions(state);
};
/**
@ -465,22 +451,18 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
* @method updateAjax
* @private
* @param {Event} e The event that triggered this update.
* @param {String} selector The selector pointing to the original select.
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @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 {Boolean} multiple Are multiple items allowed to be selected?
* @param {Boolean} tags Are we allowed to create new items on the fly?
* @param {Object} ajaxHandler This is a module that does the ajax fetch and translates the results.
* @param {Boolean} caseSensitive - If search has to be made case sensitive.
*/
var updateAjax = function(e, selector, inputId, suggestionsId, originalSelect, multiple, tags, ajaxHandler, caseSensitive) {
var updateAjax = function(e, options, state, originalSelect, ajaxHandler) {
// 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(selector, query, function(results) {
ajaxHandler.transport(options.selector, query, function(results) {
// We got a result - pass it through the translator before using it.
var processedResults = ajaxHandler.processResults(selector, results);
var processedResults = ajaxHandler.processResults(options.selector, results);
var existingValues = [];
// Now destroy all options that are not currently selected.
@ -502,7 +484,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
}
});
// Update the list of suggestions now from the new values in the select list.
updateSuggestions('', inputId, suggestionsId, originalSelect, multiple, tags, caseSensitive);
updateSuggestions(options, state, '', originalSelect);
}, notification.exception);
};
@ -511,93 +493,60 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*
* @method addNavigation
* @private
* @param {String} inputId The id of the input element for this instance of the autocomplete.
* @param {String} suggestionsId The id of the suggestions element for this instance of the autocomplete.
* @param {String} downArrowId The id of arrow to open the suggestions list.
* @param {String} selectionId The id of element that shows the current selections.
* @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.
* @param {Boolean} multiple Are multiple items allowed to be selected?
* @param {Boolean} tags Are we allowed to create new items on the fly?
* @param {String} selector The selector for this select list.
* @param {String} ajax Name of an AMD module to handle ajax requests. If specified, the AMD
* module must expose 2 functions "transport" and "processResults".
* @param {Boolean} caseSensitive - If search has to be made case sensitive.
* @param {Boolean} showSuggestions - Auto complete suggestions can be disabled completely.
*/
var addNavigation = function(inputId,
suggestionsId,
downArrowId,
selectionId,
originalSelect,
multiple,
tags,
selector,
ajax,
caseSensitive,
showSuggestions) {
var addNavigation = function(options, state, originalSelect) {
// Start with the input element.
var inputElement = $(document.getElementById(inputId));
var inputElement = $(document.getElementById(state.inputId));
// Add keyboard nav with keydown.
inputElement.on('keydown', function(e) {
switch (e.keyCode) {
case KEYS.DOWN:
// If the suggestion list is open, move to the next item.
if (!showSuggestions) {
if (!options.showSuggestions) {
// Do not consume this event.
return true;
} else if (inputElement.attr('aria-expanded') === "true") {
activateNextItem(inputId, suggestionsId);
activateNextItem(state);
} else {
// Handle ajax population of suggestions.
if (!inputElement.val() && ajax) {
require([ajax], function(ajaxHandler) {
updateAjax(e,
selector,
inputId,
suggestionsId,
originalSelect,
multiple,
tags,
ajaxHandler,
caseSensitive);
if (!inputElement.val() && options.ajax) {
require([options.ajax], function(ajaxHandler) {
updateAjax(e, options, state, originalSelect, ajaxHandler);
});
} else {
// Else - open the suggestions list.
updateSuggestions(inputElement.val(),
inputId,
suggestionsId,
originalSelect,
multiple,
tags,
caseSensitive);
updateSuggestions(options, state, inputElement.val(), originalSelect);
}
}
// We handled this event, so prevent it.
e.preventDefault();
return false;
case KEYS.COMMA:
if (tags) {
if (options.tags) {
// If we are allowing tags, comma should create a tag (or enter).
createItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
createItem(options, state, originalSelect);
}
// We handled this event, so prevent it.
e.preventDefault();
return false;
case KEYS.UP:
// Choose the previous active item.
activatePreviousItem(inputId, suggestionsId);
activatePreviousItem(state);
// We handled this event, so prevent it.
e.preventDefault();
return false;
case KEYS.ENTER:
var suggestionsElement = $(document.getElementById(suggestionsId));
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.
selectCurrentItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
} else if (tags) {
selectCurrentItem(options, state, originalSelect);
} else if (options.tags) {
// If tags are enabled, create a tag.
createItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
createItem(options, state, originalSelect);
}
// We handled this event, so prevent it.
e.preventDefault();
@ -605,7 +554,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
case KEYS.ESCAPE:
if (inputElement.attr('aria-expanded') === "true") {
// If the suggestion list is open, close it.
closeSuggestions(inputId, suggestionsId, selectionId);
closeSuggestions(state);
}
// We handled this event, so prevent it.
e.preventDefault();
@ -615,8 +564,8 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
});
// Handler used to force set the value from behat.
inputElement.on('behat:set-value', function() {
if (tags) {
createItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
if (options.tags) {
createItem(options, state, originalSelect);
}
});
inputElement.on('blur focus', function(e) {
@ -628,15 +577,15 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
}
closeSuggestionsTimer = window.setTimeout(function() {
if (e.type == 'blur') {
if (tags) {
createItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
if (options.tags) {
createItem(options, state, originalSelect);
}
closeSuggestions(inputId, suggestionsId, selectionId);
closeSuggestions(state);
}
}, 500);
});
if (showSuggestions) {
var arrowElement = $(document.getElementById(downArrowId));
if (options.showSuggestions) {
var arrowElement = $(document.getElementById(state.downArrowId));
arrowElement.on('click', function() {
// Prevent the close timer, or we will open, then close the suggestions.
inputElement.focus();
@ -644,52 +593,52 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
window.clearTimeout(closeSuggestionsTimer);
}
// Show the suggestions list.
updateSuggestions(inputElement.val(), inputId, suggestionsId, originalSelect, multiple, tags, caseSensitive);
updateSuggestions(options, state, inputElement.val(), originalSelect);
});
}
var suggestionsElement = $(document.getElementById(suggestionsId));
var suggestionsElement = $(document.getElementById(state.suggestionsId));
suggestionsElement.parent().on('click', '[role=option]', function(e) {
// Handle clicks on suggestions.
var element = $(e.currentTarget).closest('[role=option]');
var suggestionsElement = $(document.getElementById(suggestionsId));
var suggestionsElement = $(document.getElementById(state.suggestionsId));
// Find the index of the clicked on suggestion.
var current = suggestionsElement.children('[aria-hidden=false]').index(element);
// Activate it.
activateItem(current, inputId, suggestionsId);
activateItem(current, state);
// And select it.
selectCurrentItem(inputId, suggestionsId, selectionId, multiple, originalSelect);
selectCurrentItem(options, state, originalSelect);
});
var selectionElement = $(document.getElementById(selectionId));
var selectionElement = $(document.getElementById(state.selectionId));
// Handle clicks on the selected items (will unselect an item).
selectionElement.on('click', '[role=listitem]', function(e) {
// Get the item that was clicked.
var item = $(e.currentTarget);
// Remove it from the selection.
deselectItem(inputId, selectionId, item, originalSelect, multiple);
deselectItem(options, state, item, originalSelect);
});
// Keyboard navigation for the selection list.
selectionElement.on('keydown', function(e) {
switch (e.keyCode) {
case KEYS.DOWN:
// Choose the next selection item.
activateNextSelection(selectionId);
activateNextSelection(state);
// We handled this event, so prevent it.
e.preventDefault();
return false;
case KEYS.UP:
// Choose the previous selection item.
activatePreviousSelection(selectionId);
activatePreviousSelection(state);
// We handled this event, so prevent it.
e.preventDefault();
return false;
case KEYS.SPACE:
case KEYS.ENTER:
// Get the item that is currently selected.
var selectedItem = $(document.getElementById(selectionId)).children('[data-active-selection=true]');
var selectedItem = $(document.getElementById(state.selectionId)).children('[data-active-selection=true]');
if (selectedItem) {
// Unselect this item.
deselectItem(inputId, selectionId, selectedItem, originalSelect, multiple);
deselectItem(options, state, selectedItem, originalSelect);
// We handled this event, so prevent it.
e.preventDefault();
}
@ -698,7 +647,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
return true;
});
// Whenever the input field changes, update the suggestion list.
if (showSuggestions) {
if (options.showSuggestions) {
inputElement.on('input', function(e) {
var query = $(e.currentTarget).val();
var last = $(e.currentTarget).data('last-value');
@ -709,7 +658,7 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
// 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).
if (last != query) {
updateSuggestions(query, inputId, suggestionsId, originalSelect, multiple, tags, caseSensitive);
updateSuggestions(options, state, query, originalSelect);
$(e.currentTarget).data('last-value', query);
}
});
@ -732,17 +681,25 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
*/
enhance: function(selector, tags, ajax, placeholder, caseSensitive, showSuggestions) {
// Set some default values.
if (typeof tags === "undefined") {
tags = false;
var options = {
selector: selector,
tags: false,
ajax: false,
placeholder: placeholder,
caseSensitive: false,
showSuggestions: true
};
if (typeof tags !== "undefined") {
options.tags = tags;
}
if (typeof ajax === "undefined") {
ajax = false;
if (typeof ajax !== "undefined") {
options.ajax = ajax;
}
if (typeof caseSensitive === "undefined") {
caseSensitive = false;
if (typeof caseSensitive !== "undefined") {
options.caseSensitive = caseSensitive;
}
if (typeof showSuggestions === "undefined") {
showSuggestions = true;
if (typeof showSuggestions !== "undefined") {
options.showSuggestions = showSuggestions;
}
// Look for the select element.
@ -756,39 +713,30 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
originalSelect.hide().attr('aria-hidden', true);
// Find or generate some ids.
var selectId = originalSelect.attr('id');
var multiple = originalSelect.attr('multiple');
var inputId = 'form_autocomplete_input-' + $.now();
var suggestionsId = 'form_autocomplete_suggestions-' + $.now();
var selectionId = 'form_autocomplete_selection-' + $.now();
var downArrowId = 'form_autocomplete_downarrow-' + $.now();
var state = {
selectId: originalSelect.attr('id'),
inputId: 'form_autocomplete_input-' + $.now(),
suggestionsId: 'form_autocomplete_suggestions-' + $.now(),
selectionId: 'form_autocomplete_selection-' + $.now(),
downArrowId: 'form_autocomplete_downarrow-' + $.now()
};
options.multiple = originalSelect.attr('multiple');
var originalLabel = $('[for=' + selectId + ']');
var originalLabel = $('[for=' + state.selectId + ']');
// Create the new markup and insert it after the select.
var options = [];
var suggestions = [];
originalSelect.children('option').each(function(index, option) {
options[index] = { label: option.innerHTML, value: $(option).attr('value') };
suggestions[index] = { label: option.innerHTML, value: $(option).attr('value') };
});
// Render all the parts of our UI.
var renderInput = templates.render(
'core/form_autocomplete_input',
{ downArrowId: downArrowId,
inputId: inputId,
suggestionsId: suggestionsId,
selectionId: selectionId,
placeholder: placeholder,
multiple: multiple,
showSuggestions: showSuggestions }
);
var renderDatalist = templates.render(
'core/form_autocomplete_suggestions',
{ inputId: inputId, suggestionsId: suggestionsId, options: options, multiple: multiple}
);
var renderSelection = templates.render(
'core/form_autocomplete_selection',
{ selectionId: selectionId, items: [], multiple: multiple}
);
var context = $.extend({}, options, state);
context.options = suggestions;
context.items = [];
var renderInput = templates.render('core/form_autocomplete_input', context);
var renderDatalist = templates.render('core/form_autocomplete_suggestions', context);
var renderSelection = templates.render('core/form_autocomplete_selection', context);
$.when(renderInput, renderDatalist, renderSelection).done(function(input, suggestions, selection) {
// Add our new UI elements to the page.
@ -796,47 +744,29 @@ define(['jquery', 'core/log', 'core/str', 'core/templates', 'core/notification']
originalSelect.after(input);
originalSelect.after(selection);
// Update the form label to point to the text input.
originalLabel.attr('for', inputId);
originalLabel.attr('for', state.inputId);
// Add the event handlers.
addNavigation(inputId,
suggestionsId,
downArrowId,
selectionId,
originalSelect,
multiple,
tags,
selector,
ajax,
caseSensitive,
showSuggestions);
addNavigation(options, state, originalSelect);
var inputElement = $(document.getElementById(inputId));
var suggestionsElement = $(document.getElementById(suggestionsId));
var inputElement = $(document.getElementById(state.inputId));
var suggestionsElement = $(document.getElementById(state.suggestionsId));
// Hide the suggestions by default.
suggestionsElement.hide().attr('aria-hidden', true);
// If this field uses ajax, set it up.
if (ajax) {
require([ajax], function(ajaxHandler) {
if (options.ajax) {
require([options.ajax], function(ajaxHandler) {
var handler = function(e) {
updateAjax(e,
selector,
inputId,
suggestionsId,
originalSelect,
multiple,
tags,
ajaxHandler,
caseSensitive);
updateAjax(e, options, state, originalSelect, ajaxHandler);
};
// Trigger an ajax update after the text field value changes.
inputElement.on("input keypress", handler);
var arrowElement = $(document.getElementById(downArrowId));
var arrowElement = $(document.getElementById(state.downArrowId));
arrowElement.on("click", handler);
});
}
// Show the current values in the selection list.
updateSelectionList(selectionId, inputId, originalSelect, multiple);
updateSelectionList(options, state, originalSelect);
});
}
};