MDL-72321 core: Support disabled options in form-autocomplete
Autocomplete fields can now include disabled options in the suggestion list. When calling .enchance() on a select list with disabled options, those options will be added to the autocomplete suggestions with the aria-disabled arribute set, and will not be selectable. Datafilters using the autocomplete element can also hook into this by including disabled options in their list of values.
This commit is contained in:
+1
-1
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -68,13 +68,7 @@ define([
|
||||
// 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;
|
||||
}
|
||||
index = wrapListIndex(index, selectionElement.children('[aria-selected=true]').length);
|
||||
// Find the specified element.
|
||||
var element = $(selectionElement.children('[aria-selected=true]').get(index));
|
||||
// Create an id we can assign to this element.
|
||||
@@ -156,21 +150,8 @@ define([
|
||||
M.util.js_pending(pendingKey);
|
||||
|
||||
// Build up a valid context to re-render the template.
|
||||
var items = [];
|
||||
var items = rebuildOptions(originalSelect.children('option:selected'), false);
|
||||
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);
|
||||
@@ -307,6 +288,91 @@ define([
|
||||
}, 100).promise();
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the index of the currently selected item in the suggestions list.
|
||||
*
|
||||
* @param {jQuery} suggestionsElement
|
||||
* @return {Integer}
|
||||
*/
|
||||
var getCurrentItem = function(suggestionsElement) {
|
||||
// Find the active one.
|
||||
var element = suggestionsElement.children('[aria-selected=true]');
|
||||
// Find its index.
|
||||
return suggestionsElement.children(':not([aria-hidden])').index(element);
|
||||
};
|
||||
|
||||
/**
|
||||
* Limit the index to the upper/lower bounds of the list (wrap in both directions).
|
||||
*
|
||||
* @param {Integer} index The target index.
|
||||
* @param {Integer} length The length of the list of visible items.
|
||||
* @return {Integer} The resulting index with necessary wrapping applied.
|
||||
*/
|
||||
var wrapListIndex = function(index, length) {
|
||||
index = index % length;
|
||||
while (index < 0) {
|
||||
index += length;
|
||||
}
|
||||
return index;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the index of the next item in the list without aria-disabled=true.
|
||||
*
|
||||
* @param {Integer} current The index of the current item.
|
||||
* @param {Array} suggestions The list of suggestions.
|
||||
* @return {Integer}
|
||||
*/
|
||||
var getNextEnabledItem = function(current, suggestions) {
|
||||
var nextIndex = wrapListIndex(current + 1, suggestions.length);
|
||||
if (suggestions[nextIndex].getAttribute('aria-disabled')) {
|
||||
return getNextEnabledItem(nextIndex, suggestions);
|
||||
}
|
||||
return nextIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the index of the previous item in the list without aria-disabled=true.
|
||||
*
|
||||
* @param {Integer} current The index of the current item.
|
||||
* @param {Array} suggestions The list of suggestions.
|
||||
* @return {Integer}
|
||||
*/
|
||||
var getPreviousEnabledItem = function(current, suggestions) {
|
||||
var previousIndex = wrapListIndex(current - 1, suggestions.length);
|
||||
if (suggestions[previousIndex].getAttribute('aria-disabled')) {
|
||||
return getPreviousEnabledItem(previousIndex, suggestions);
|
||||
}
|
||||
return previousIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build a list of renderable options based on a set of option elements from the original select list.
|
||||
*
|
||||
* @param {jQuery} originalOptions
|
||||
* @param {Boolean} includeEmpty
|
||||
* @return {Array}
|
||||
*/
|
||||
var rebuildOptions = function(originalOptions, includeEmpty) {
|
||||
var options = [];
|
||||
originalOptions.each(function(index, ele) {
|
||||
var label;
|
||||
if ($(ele).data('html')) {
|
||||
label = $(ele).data('html');
|
||||
} else {
|
||||
label = $(ele).html();
|
||||
}
|
||||
if (includeEmpty || label !== '') {
|
||||
options.push({
|
||||
label: label,
|
||||
value: $(ele).attr('value'),
|
||||
disabled: ele.disabled,
|
||||
});
|
||||
}
|
||||
});
|
||||
return options;
|
||||
};
|
||||
|
||||
/**
|
||||
* Find the index of the current active suggestion, and activate the next one.
|
||||
*
|
||||
@@ -318,12 +384,10 @@ define([
|
||||
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);
|
||||
var suggestions = suggestionsElement.children(':not([aria-hidden])');
|
||||
var current = getCurrentItem(suggestionsElement);
|
||||
// Activate the next one.
|
||||
return activateItem(current + 1, state);
|
||||
return activateItem(getNextEnabledItem(current, suggestions), state);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -385,17 +449,11 @@ define([
|
||||
* @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);
|
||||
|
||||
var suggestions = suggestionsElement.children(':not([aria-hidden])');
|
||||
var current = getCurrentItem(suggestionsElement);
|
||||
// Activate the previous one.
|
||||
return activateItem(current - 1, state);
|
||||
return activateItem(getPreviousEnabledItem(current, suggestions), state);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -447,12 +505,7 @@ define([
|
||||
// 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')};
|
||||
}
|
||||
});
|
||||
var suggestions = rebuildOptions(originalSelect.children('option:not(:selected)'), true);
|
||||
|
||||
// Re-render the list of suggestions.
|
||||
var searchquery = state.caseSensitive ? query : query.toLocaleLowerCase();
|
||||
@@ -1153,10 +1206,7 @@ define([
|
||||
|
||||
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')};
|
||||
});
|
||||
var suggestions = rebuildOptions(originalSelect.children('option'), true);
|
||||
|
||||
// Render all the parts of our UI.
|
||||
var context = $.extend({}, options, state);
|
||||
|
||||
@@ -58,6 +58,6 @@
|
||||
}}{{#filtertypeclass}}data-filter-type-class="{{filtertypeclass}}" {{/filtertypeclass}}{{!
|
||||
}}>
|
||||
{{#values}}
|
||||
<option value="{{value}}">{{{title}}}</option>
|
||||
<option value="{{value}}"{{#disabled}} disabled{{/disabled}}>{{{title}}}</option>
|
||||
{{/values}}
|
||||
</select>
|
||||
|
||||
@@ -32,11 +32,12 @@
|
||||
Example context (json):
|
||||
{ "suggestionsId": 1, "options": [
|
||||
{ "label": "Item label with <strong>tags</strong>", "value": "5" },
|
||||
{ "label": "Another item label with <strong>tags</strong>", "value": "4" }
|
||||
{ "label": "Another item label with <strong>tags</strong>", "value": "4"},
|
||||
{ "label": "A disabled item", "value": "", "disabled": true}
|
||||
]}
|
||||
}}
|
||||
<ul class="form-autocomplete-suggestions" id="{{suggestionsId}}" role="listbox" aria-label="{{#str}}suggestions, form{{/str}}" aria-hidden="true" tabindex="-1">
|
||||
{{#options}}
|
||||
<li role="option" data-value="{{value}}">{{{label}}}</li>
|
||||
<li role="option" data-value="{{value}}"{{#disabled}} aria-disabled="true"{{/disabled}}>{{{label}}}</li>
|
||||
{{/options}}
|
||||
</ul>
|
||||
|
||||
@@ -98,6 +98,8 @@ being forced open in all behat tests.
|
||||
* New "joinlist" parameter added to core\output\datafilter::get_filter_object(). This takes an array of datafilter::JOINTYPE_*
|
||||
constants to define which types of join the filter supports. By default this will include all three of "Any", "All" and "None",
|
||||
but a subset can be specified instead.
|
||||
* core/form-autocomplete now supports disabled options in the source select list. These will be displayed in the autocomplete
|
||||
options with the aria-disabled attribute, and will not be selectable.
|
||||
|
||||
=== 4.2 ===
|
||||
|
||||
|
||||
@@ -301,6 +301,11 @@ fieldset.coursesearchbox label {
|
||||
background-color: $dropdown-link-active-bg;
|
||||
color: $dropdown-link-active-color;
|
||||
}
|
||||
&[aria-disabled="true"] {
|
||||
pointer-events: none;
|
||||
color: $custom-select-disabled-color;
|
||||
background-color: $custom-select-disabled-bg;
|
||||
}
|
||||
&::before {
|
||||
content: "\200B";
|
||||
}
|
||||
|
||||
@@ -32872,6 +32872,11 @@ fieldset.coursesearchbox label {
|
||||
background-color: #0f6cbf;
|
||||
color: #fff;
|
||||
}
|
||||
.form-autocomplete-suggestions li[aria-disabled=true] {
|
||||
pointer-events: none;
|
||||
color: #6a737b;
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
.form-autocomplete-suggestions li::before {
|
||||
content: "";
|
||||
}
|
||||
|
||||
@@ -32872,6 +32872,11 @@ fieldset.coursesearchbox label {
|
||||
background-color: #0f6cbf;
|
||||
color: #fff;
|
||||
}
|
||||
.form-autocomplete-suggestions li[aria-disabled=true] {
|
||||
pointer-events: none;
|
||||
color: #6a737b;
|
||||
background-color: #e9ecef;
|
||||
}
|
||||
.form-autocomplete-suggestions li::before {
|
||||
content: "";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user