MDL-67263 course: Add search functionality in the activity chooser

This commit is contained in:
Mihail Geshoski
2020-03-05 11:56:39 +08:00
parent d7374522ed
commit f152b71dd4
14 changed files with 519 additions and 136 deletions
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -1,2 +1,2 @@
define ("core_course/local/activitychooser/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={regions:{chooser:b("region","chooser-container"),chooserOptions:b("region","chooser-options-container"),chooserOption:{container:b("region","chooser-option-container"),actions:b("region","chooser-option-actions-container"),info:b("region","chooser-option-info-container")},chooserSummary:{container:b("region","chooser-option-summary-container"),content:b("region","chooser-option-summary-content-container"),description:b("region","summary-description"),actions:b("region","chooser-option-summary-actions-container")},carousel:b("region","carousel"),help:b("region","help"),modules:b("region","modules"),favouriteTabNav:b("region","favourite-tab-nav"),recommendedTabNav:b("region","recommended-tab-nav"),defaultTabNav:b("region","default-tab-nav"),favouriteTab:b("region","favourites"),recommendedTab:b("region","recommended"),defaultTab:b("region","default"),getModuleSelector:function getModuleSelector(a){return"[role=\"menuitem\"][data-modname=\"".concat(a,"\"]")}},actions:{optionActions:{showSummary:b("action","show-option-summary"),manageFavourite:b("action","manage-module-favourite")},addChooser:b("action","add-chooser-option"),closeOption:b("action","close-chooser-option-summary"),hide:b("action","hide")},render:{favourites:b("render","favourites-area")},elements:{section:".section",sectionmodchooser:"button.section-modchooser-link",sitemenu:".block_site_main_menu",sitetopic:"div.sitetopic"}};a.default=c;return a.default});
define ("core_course/local/activitychooser/selectors",["exports"],function(a){"use strict";Object.defineProperty(a,"__esModule",{value:!0});a.default=void 0;var b=function(a,b){return"[data-".concat(a,"=\"").concat(b,"\"]")},c={regions:{chooser:b("region","chooser-container"),getSectionChooserOptions:function getSectionChooserOptions(a){return"".concat(a," ").concat(b("region","chooser-options-container"))},chooserOption:{container:b("region","chooser-option-container"),actions:b("region","chooser-option-actions-container"),info:b("region","chooser-option-info-container")},chooserSummary:{container:b("region","chooser-option-summary-container"),content:b("region","chooser-option-summary-content-container"),description:b("region","summary-description"),actions:b("region","chooser-option-summary-actions-container")},carousel:b("region","carousel"),help:b("region","help"),modules:b("region","modules"),favouriteTabNav:b("region","favourite-tab-nav"),recommendedTabNav:b("region","recommended-tab-nav"),defaultTabNav:b("region","default-tab-nav"),favouriteTab:b("region","favourites"),recommendedTab:b("region","recommended"),defaultTab:b("region","default"),getModuleSelector:function getModuleSelector(a){return"[role=\"menuitem\"][data-modname=\"".concat(a,"\"]")},searchResults:b("region","search-results-container"),searchResultItems:b("region","search-result-items-container")},actions:{optionActions:{showSummary:b("action","show-option-summary"),manageFavourite:b("action","manage-module-favourite")},addChooser:b("action","add-chooser-option"),closeOption:b("action","close-chooser-option-summary"),hide:b("action","hide"),search:b("action","search"),clearSearch:b("action","clearsearch")},render:{favourites:b("render","favourites-area")},elements:{section:".section",sectionmodchooser:"button.section-modchooser-link",sitemenu:".block_site_main_menu",sitetopic:"div.sitetopic",tab:"a[data-toggle=\"tab\"]",activetab:"a[data-toggle=\"tab\"][aria-selected=\"true\"]",searchicon:".searchbar-append .search-icon",clearsearch:".searchbar-append .clear"}};a.default=c;return a.default});
//# sourceMappingURL=selectors.min.js.map
File diff suppressed because one or more lines are too long
+192 -36
View File
@@ -30,6 +30,7 @@ import {end, arrowLeft, arrowRight, home, enter, space} from 'core/key_codes';
import {addIconToContainer} from 'core/loadingicon';
import * as Repository from 'core_course/local/activitychooser/repository';
import Notification from 'core/notification';
import {debounce} from 'core/utils';
/**
* Given an event from the main module 'page' navigate to it's help section via a carousel.
@@ -134,6 +135,15 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => {
caller.focus();
});
}
// The "clear search" button is triggered.
if (e.target.closest(selectors.actions.clearSearch)) {
// Clear the entered search query in the search bar and hide the search results container.
const searchInput = modal.getBody()[0].querySelector(selectors.actions.search);
searchInput.value = "";
searchInput.focus();
toggleSearchResultsView(modal.getBody()[0], mappedModules, searchInput.value);
}
};
modal.getBodyPromise()
@@ -159,9 +169,26 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => {
return body;
})
// Add a listener for an input change in the activity chooser's search bar.
.then(body => {
const searchInput = body.querySelector(selectors.actions.search);
// The search input is triggered.
searchInput.addEventListener('input', debounce(() => {
// Display the search results.
toggleSearchResultsView(body, mappedModules, searchInput.value);
}, 300));
return body;
})
// Register event listeners related to the keyboard navigation controls.
.then(body => {
initKeyboardNavigation(body, mappedModules);
// Get the active chooser options section.
const activeSectionId = body.querySelector(selectors.elements.activetab).getAttribute("href");
const sectionChooserOptions = body.querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));
const firstChooserOption = sectionChooserOptions.querySelector(selectors.regions.chooserOption.container);
toggleFocusableChooserOption(firstChooserOption, true);
initTabsKeyboardNavigation(body);
initChooserOptionsKeyboardNavigation(body, mappedModules, sectionChooserOptions);
return body;
})
.catch();
@@ -169,14 +196,12 @@ const registerListenerEvents = (modal, mappedModules, partialFavourite) => {
};
/**
* Initialise the keyboard navigation controls for the chooser.
* Initialise the keyboard navigation controls for the tab list items.
*
* @method initKeyboardNavigation
* @method initTabsKeyboardNavigation
* @param {HTMLElement} body Our modal that we are working with
* @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}
*/
const initKeyboardNavigation = (body, mappedModules) => {
const initTabsKeyboardNavigation = (body) => {
// Set up the tab handlers.
const favTabNav = body.querySelector(selectors.regions.favouriteTabNav);
const recommendedTabNav = body.querySelector(selectors.regions.recommendedTabNav);
@@ -235,13 +260,21 @@ const initKeyboardNavigation = (body, mappedModules) => {
}
});
});
};
// Set up the handlers for the modules.
const chooserOptions = body.querySelectorAll(selectors.regions.chooserOption.container);
/**
* Initialise the keyboard navigation controls for the chooser options.
*
* @method initChooserOptionsKeyboardNavigation
* @param {HTMLElement} body Our modal that we are working with
* @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}
* @param {HTMLElement} chooserOptionsContainer The section that contains the chooser items
*/
const initChooserOptionsKeyboardNavigation = (body, mappedModules, chooserOptionsContainer) => {
const chooserOptions = chooserOptionsContainer.querySelectorAll(selectors.regions.chooserOption.container);
Array.from(chooserOptions).forEach((element) => {
return element.addEventListener('keyup', (e) => {
const chooserOptions = document.querySelector(selectors.regions.chooserOptions);
// Check for enter/ space triggers for showing the help.
if (e.keyCode === enter || e.keyCode === space) {
@@ -265,7 +298,7 @@ const initKeyboardNavigation = (body, mappedModules) => {
e.preventDefault();
const currentOption = e.target.closest(selectors.regions.chooserOption.container);
const nextOption = currentOption.nextElementSibling;
const firstOption = chooserOptions.firstElementChild;
const firstOption = chooserOptionsContainer.firstElementChild;
const toFocusOption = clickErrorHandler(nextOption, firstOption);
focusChooserOption(toFocusOption, currentOption);
}
@@ -275,7 +308,7 @@ const initKeyboardNavigation = (body, mappedModules) => {
e.preventDefault();
const currentOption = e.target.closest(selectors.regions.chooserOption.container);
const previousOption = currentOption.previousElementSibling;
const lastOption = chooserOptions.lastElementChild;
const lastOption = chooserOptionsContainer.lastElementChild;
const toFocusOption = clickErrorHandler(previousOption, lastOption);
focusChooserOption(toFocusOption, currentOption);
}
@@ -283,14 +316,14 @@ const initKeyboardNavigation = (body, mappedModules) => {
if (e.keyCode === home) {
e.preventDefault();
const currentOption = e.target.closest(selectors.regions.chooserOption.container);
const firstOption = chooserOptions.firstElementChild;
const firstOption = chooserOptionsContainer.firstElementChild;
focusChooserOption(firstOption, currentOption);
}
if (e.keyCode === end) {
e.preventDefault();
const currentOption = e.target.closest(selectors.regions.chooserOption.container);
const lastOption = chooserOptions.lastElementChild;
const lastOption = chooserOptionsContainer.lastElementChild;
focusChooserOption(lastOption, currentOption);
}
});
@@ -302,39 +335,51 @@ const initKeyboardNavigation = (body, mappedModules) => {
*
* @method focusChooserOption
* @param {HTMLElement} currentChooserOption The current chooser option element that we want to focus
* @param {HTMLElement} previousChooserOption The previous focused option element
* @param {HTMLElement|null} previousChooserOption The previous focused option element
*/
const focusChooserOption = (currentChooserOption, previousChooserOption = false) => {
if (previousChooserOption !== false) {
const previousChooserOptionLink = previousChooserOption.querySelector(selectors.actions.addChooser);
const previousChooserOptionHelp = previousChooserOption.querySelector(selectors.actions.optionActions.showSummary);
const previousChooserOptionFavourite = previousChooserOption.querySelector(selectors.actions.optionActions.manageFavourite);
// Set tabindex to -1 to remove the previous chooser option element from the focus order.
previousChooserOption.tabIndex = -1;
previousChooserOptionLink.tabIndex = -1;
previousChooserOptionHelp.tabIndex = -1;
previousChooserOptionFavourite.tabIndex = -1;
const focusChooserOption = (currentChooserOption, previousChooserOption = null) => {
if (previousChooserOption !== null) {
toggleFocusableChooserOption(previousChooserOption, false);
}
const currentChooserOptionLink = currentChooserOption.querySelector(selectors.actions.addChooser);
const currentChooserOptionHelp = currentChooserOption.querySelector(selectors.actions.optionActions.showSummary);
const currentChooserOptionFavourite = currentChooserOption.querySelector(selectors.actions.optionActions.manageFavourite);
// Set tabindex to 0 to add current chooser option element to the focus order.
currentChooserOption.tabIndex = 0;
currentChooserOptionLink.tabIndex = 0;
currentChooserOptionHelp.tabIndex = 0;
currentChooserOptionFavourite.tabIndex = 0;
// Focus the current chooser option element.
toggleFocusableChooserOption(currentChooserOption, true);
currentChooserOption.focus();
};
/**
* Add or remove a chooser option from the focus order.
*
* @method toggleFocusableChooserOption
* @param {HTMLElement} chooserOption The chooser option element which should be added or removed from the focus order
* @param {Boolean} isFocusable Whether the chooser element is focusable or not
*/
const toggleFocusableChooserOption = (chooserOption, isFocusable) => {
const chooserOptionLink = chooserOption.querySelector(selectors.actions.addChooser);
const chooserOptionHelp = chooserOption.querySelector(selectors.actions.optionActions.showSummary);
const chooserOptionFavourite = chooserOption.querySelector(selectors.actions.optionActions.manageFavourite);
if (isFocusable) {
// Set tabindex to 0 to add current chooser option element to the focus order.
chooserOption.tabIndex = 0;
chooserOptionLink.tabIndex = 0;
chooserOptionHelp.tabIndex = 0;
chooserOptionFavourite.tabIndex = 0;
} else {
// Set tabindex to -1 to remove the previous chooser option element from the focus order.
chooserOption.tabIndex = -1;
chooserOptionLink.tabIndex = -1;
chooserOptionHelp.tabIndex = -1;
chooserOptionFavourite.tabIndex = -1;
}
};
/**
* Small error handling function to make sure the navigated to object exists
*
* @method clickErrorHandler
* @param {HTMLElement} item What we want to check exists
* @param {HTMLElement} fallback If we dont match anything fallback the focus
* @return {String}
* @return {HTMLElement}
*/
const clickErrorHandler = (item, fallback) => {
if (item !== null) {
@@ -344,6 +389,104 @@ const clickErrorHandler = (item, fallback) => {
}
};
/**
* Render the search results in a defined container
*
* @method renderSearchResults
* @param {HTMLElement} searchResultsContainer The container where the data should be rendered
* @param {Object} searchResultsData Data containing the module items that satisfy the search criteria
*/
const renderSearchResults = async(searchResultsContainer, searchResultsData) => {
const templateData = {
'searchresultsnumber': searchResultsData.length,
'searchresults': searchResultsData
};
// Build up the html & js ready to place into the help section.
const {html, js} = await Templates.renderForPromise('core_course/chooser_search_results', templateData);
await Templates.replaceNodeContents(searchResultsContainer, html, js);
};
/**
* Toggle (display/hide) the search results depending on the value of the search query
*
* @method toggleSearchResultsView
* @param {HTMLElement} modalBody The body of the created modal for the section
* @param {Map} mappedModules A map of all of the modules we are working with with K: mod_name V: {Object}
* @param {String} searchQuery The search query
*/
const toggleSearchResultsView = async(modalBody, mappedModules, searchQuery) => {
const searchResultsContainer = modalBody.querySelector(selectors.regions.searchResults);
const chooserContainer = modalBody.querySelector(selectors.regions.chooser);
const clearSearchButton = modalBody.querySelector(selectors.elements.clearsearch);
const searchIcon = modalBody.querySelector(selectors.elements.searchicon);
if (searchQuery.length > 0) { // Search query is present.
const searchResultsData = searchModules(mappedModules, searchQuery);
await renderSearchResults(searchResultsContainer, searchResultsData);
const searchResultItemsContainer = searchResultsContainer.querySelector(selectors.regions.searchResultItems);
const firstSearchResultItem = searchResultItemsContainer.querySelector(selectors.regions.chooserOption.container);
if (firstSearchResultItem) {
// Set the first result item to be focusable.
toggleFocusableChooserOption(firstSearchResultItem, true);
// Register keyboard events on the created search result items.
initChooserOptionsKeyboardNavigation(modalBody, mappedModules, searchResultItemsContainer);
}
// Display the "clear" search button in the activity chooser search bar.
searchIcon.classList.add('d-none');
clearSearchButton.classList.remove('d-none');
// Hide the default chooser options container.
chooserContainer.setAttribute('hidden', 'hidden');
// Display the search results container.
searchResultsContainer.removeAttribute('hidden');
} else { // Search query is not present.
// Hide the "clear" search button in the activity chooser search bar.
clearSearchButton.classList.add('d-none');
searchIcon.classList.remove('d-none');
// Hide the search results container.
searchResultsContainer.setAttribute('hidden', 'hidden');
// Display the default chooser options container.
chooserContainer.removeAttribute('hidden');
}
};
/**
* Return the list of modules which have a name or description that matches the given search term.
*
* @method searchModules
* @param {Array} modules List of available modules
* @param {String} searchTerm The search term to match
* @return {Array}
*/
const searchModules = (modules, searchTerm) => {
if (searchTerm === '') {
return modules;
}
searchTerm = searchTerm.toLowerCase();
const searchResults = [];
modules.forEach((activity) => {
const activityName = activity.title.toLowerCase();
const activityDesc = activity.help.toLowerCase();
if (activityName.includes(searchTerm) || activityDesc.includes(searchTerm)) {
searchResults.push(activity);
}
});
return searchResults;
};
/**
* Disable the focus of all chooser options in a specific container (section).
*
* @method disableFocusAllChooserOptions
* @param {HTMLElement} sectionChooserOptions The section that contains the chooser items
*/
const disableFocusAllChooserOptions = (sectionChooserOptions) => {
const allChooserOptions = sectionChooserOptions.querySelectorAll(selectors.regions.chooserOption.container);
allChooserOptions.forEach((chooserOption) => {
toggleFocusableChooserOption(chooserOption, false);
});
};
/**
* Display the module chooser.
*
@@ -375,9 +518,22 @@ export const displayChooser = (origin, modal, sectionModules, partialFavourite)
modal.getBodyPromise()
.then(body => {
const firstChooserOption = body[0].querySelector(selectors.regions.chooserOption.container);
focusChooserOption(firstChooserOption);
$(selectors.elements.tab).on('shown.bs.tab', (e) => {
const activeSectionId = e.target.getAttribute("href");
const activeSectionChooserOptions = body[0]
.querySelector(selectors.regions.getSectionChooserOptions(activeSectionId));
const firstChooserOption = activeSectionChooserOptions
.querySelector(selectors.regions.chooserOption.container);
const prevActiveSectionId = e.relatedTarget.getAttribute("href");
const prevActiveSectionChooserOptions = body[0]
.querySelector(selectors.regions.getSectionChooserOptions(prevActiveSectionId));
// Disable the focus of every chooser option in the previous active section.
disableFocusAllChooserOptions(prevActiveSectionChooserOptions);
// Enable the focus of the first chooser option in the current active section.
toggleFocusableChooserOption(firstChooserOption, true);
initChooserOptionsKeyboardNavigation(body[0], mappedModules, activeSectionChooserOptions);
});
return;
})
.catch(Notification.exception);
@@ -36,7 +36,7 @@ const getDataSelector = (name, value) => {
export default {
regions: {
chooser: getDataSelector('region', 'chooser-container'),
chooserOptions: getDataSelector('region', 'chooser-options-container'),
getSectionChooserOptions: containerid => `${containerid} ${getDataSelector('region', 'chooser-options-container')}`,
chooserOption: {
container: getDataSelector('region', 'chooser-option-container'),
actions: getDataSelector('region', 'chooser-option-actions-container'),
@@ -57,7 +57,9 @@ export default {
favouriteTab: getDataSelector('region', 'favourites'),
recommendedTab: getDataSelector('region', 'recommended'),
defaultTab: getDataSelector('region', 'default'),
getModuleSelector: modname => `[role="menuitem"][data-modname="${modname}"]`
getModuleSelector: modname => `[role="menuitem"][data-modname="${modname}"]`,
searchResults: getDataSelector('region', 'search-results-container'),
searchResultItems: getDataSelector('region', 'search-result-items-container'),
},
actions: {
optionActions: {
@@ -66,7 +68,9 @@ export default {
},
addChooser: getDataSelector('action', 'add-chooser-option'),
closeOption: getDataSelector('action', 'close-chooser-option-summary'),
hide: getDataSelector('action', 'hide')
hide: getDataSelector('action', 'hide'),
search: getDataSelector('action', 'search'),
clearSearch: getDataSelector('action', 'clearsearch'),
},
render: {
favourites: getDataSelector('render', 'favourites-area'),
@@ -76,5 +80,9 @@ export default {
sectionmodchooser: 'button.section-modchooser-link',
sitemenu: '.block_site_main_menu',
sitetopic: 'div.sitetopic',
tab: 'a[data-toggle="tab"]',
activetab: 'a[data-toggle="tab"][aria-selected="true"]',
searchicon: '.searchbar-append .search-icon',
clearsearch: '.searchbar-append .clear'
},
};
+69 -67
View File
@@ -32,78 +32,80 @@
}
}
}}
<div data-region="carousel" class="carousel slide">
<div class="carousel-inner" aria-live="polite">
<div class="carousel-item p-4 active" data-region="modules">
<ul class="nav nav-tabs mb-2" id="activities-{{uniqid}}" role="tablist">
<li class="nav-item">
<a class="nav-link {{#favouritesFirst}}active{{/favouritesFirst}} {{^favourites}}d-none{{/favourites}}"
id="starred-tab-{{uniqid}}"
data-toggle="tab"
data-region="favourite-tab-nav"
href="#starred-{{uniqid}}"
role="tab"
aria-label="{{#str}} aria:favouritestab, core_course {{/str}}"
aria-controls="starred-{{uniqid}}"
aria-selected="{{#favouritesFirst}}true{{/favouritesFirst}}{{^favouritesFirst}}false{{/favouritesFirst}}"
tabindex="{{#favouritesFirst}}0{{/favouritesFirst}}{{^favouritesFirst}}-1{{/favouritesFirst}}"
>
{{#str}}favourites{{/str}}
</a>
</li>
<li class="nav-item">
<a class="nav-link {{#recommendedFirst}}active{{/recommendedFirst}} {{^recommended}}d-none{{/recommended}}"
id="recommended-tab-{{uniqid}}"
data-region="recommended-tab-nav"
data-toggle="tab"
href="#recommended-{{uniqid}}"
role="tab"
aria-label="{{#str}} aria:recommendedtab, core_course {{/str}}"
aria-controls="recommended-{{uniqid}}"
aria-selected="{{#recommendedFirst}}true{{/recommendedFirst}}{{^recommendedFirst}}false{{/recommendedFirst}}"
tabindex="{{#recommendedFirst}}0{{/recommendedFirst}}{{^recommendedFirst}}-1{{/recommendedFirst}}">
{{#str}}recommended{{/str}}
</a>
</li>
<li class="nav-item">
<a class="nav-link {{#fallback}}active{{/fallback}}"
id="all-tab-{{uniqid}}"
data-toggle="tab"
data-region="default-tab-nav"
href="#all-{{uniqid}}"
role="tab"
aria-label="{{#str}} aria:defaulttab, core_course {{/str}}"
aria-controls="all-{{uniqid}}"
aria-selected="{{#fallback}}true{{/fallback}}{{^fallback}}false{{/fallback}}"
tabindex="{{#fallback}}0{{/fallback}}{{^fallback}}-1{{/fallback}}"
>
{{#str}}activities{{/str}}
</a>
</li>
</ul>
<div class="tab-content" id="tabbed-activities-{{uniqid}}">
<div class="tab-pane {{#favouritesFirst}}active{{/favouritesFirst}}" id="starred-{{uniqid}}" data-region="favourites" role="tabpanel" aria-labelledby="starred-tab-{{uniqid}}">
<div class="modchoosercontainer" data-region="chooser-container" aria-label="{{#str}} activitymodules {{/str}}">
<div class="optionscontainer d-flex flex-wrap mw-100 p-3 position-relative" role="menubar" data-region="chooser-options-container" data-render="favourites-area">
{{>core_course/chooser_favourites}}
</div>
</div>
<div class="modchoosercontainer" aria-label="{{#str}} activitymodules, core {{/str}}">
<div class="searchcontainer mb-3">
{{>core_course/chooser_search}}
</div>
<div class="tab-pane {{#recommendedFirst}}active{{/recommendedFirst}}" id="recommended-{{uniqid}}" data-region="recommended" role="tabpanel" aria-labelledby="recommended-tab-{{uniqid}}">
<div class="modchoosercontainer" data-region="chooser-container" aria-label="{{#str}} activitymodules {{/str}}">
<div class="optionscontainer d-flex flex-wrap mw-100 p-3 position-relative" role="menubar" data-region="chooser-options-container">
{{#recommended}}
{{>core_course/chooser_item}}
{{/recommended}}
<div data-region="chooser-container">
<ul class="nav nav-tabs mb-2" id="activities-{{uniqid}}" role="tablist">
<li class="nav-item">
<a class="nav-link {{#favouritesFirst}}active{{/favouritesFirst}} {{^favourites}}d-none{{/favourites}}"
id="starred-tab-{{uniqid}}"
data-toggle="tab"
data-region="favourite-tab-nav"
href="#starred-{{uniqid}}"
role="tab"
aria-label="{{#str}} aria:favouritestab, core_course {{/str}}"
aria-controls="starred-{{uniqid}}"
aria-selected="{{#favouritesFirst}}true{{/favouritesFirst}}{{^favouritesFirst}}false{{/favouritesFirst}}"
tabindex="{{#favouritesFirst}}0{{/favouritesFirst}}{{^favouritesFirst}}-1{{/favouritesFirst}}"
>
{{#str}} favourites, core {{/str}}
</a>
</li>
<li class="nav-item">
<a class="nav-link {{#recommendedFirst}}active{{/recommendedFirst}} {{^recommended}}d-none{{/recommended}}"
id="recommended-tab-{{uniqid}}"
data-region="recommended-tab-nav"
data-toggle="tab"
href="#recommended-{{uniqid}}"
role="tab"
aria-label="{{#str}} aria:recommendedtab, core_course {{/str}}"
aria-controls="recommended-{{uniqid}}"
aria-selected="{{#recommendedFirst}}true{{/recommendedFirst}}{{^recommendedFirst}}false{{/recommendedFirst}}"
tabindex="{{#recommendedFirst}}0{{/recommendedFirst}}{{^recommendedFirst}}-1{{/recommendedFirst}}">
{{#str}} recommended, core {{/str}}
</a>
</li>
<li class="nav-item">
<a class="nav-link {{#fallback}}active{{/fallback}}"
id="all-tab-{{uniqid}}"
data-toggle="tab"
data-region="default-tab-nav"
href="#all-{{uniqid}}"
role="tab"
aria-label="{{#str}} aria:defaulttab, core_course {{/str}}"
aria-controls="all-{{uniqid}}"
aria-selected="{{#fallback}}true{{/fallback}}{{^fallback}}false{{/fallback}}"
tabindex="{{#fallback}}0{{/fallback}}{{^fallback}}-1{{/fallback}}"
>
{{#str}} activities, core {{/str}}
</a>
</li>
</ul>
<div class="tab-content" id="tabbed-activities-{{uniqid}}">
<div class="tab-pane {{#favouritesFirst}}active{{/favouritesFirst}}" id="starred-{{uniqid}}" data-region="favourites" role="tabpanel" aria-labelledby="starred-tab-{{uniqid}}">
<div class="optionscontainer d-flex flex-wrap mw-100 p-3 position-relative" role="menubar" data-region="chooser-options-container" data-render="favourites-area">
{{>core_course/chooser_favourites}}
</div>
</div>
</div>
</div>
<div class="tab-pane {{#fallback}}active{{/fallback}}" id="all-{{uniqid}}" data-region="default" role="tabpanel" aria-labelledby="all-tab-{{uniqid}}">
<div class="modchoosercontainer" data-region="chooser-container" aria-label="{{#str}} activitymodules {{/str}}">
<div class="optionscontainer d-flex flex-wrap mw-100 p-3 position-relative" role="menubar" data-region="chooser-options-container">
{{#default}}
{{>core_course/chooser_item}}
{{/default}}
<div class="tab-pane {{#recommendedFirst}}active{{/recommendedFirst}}" id="recommended-{{uniqid}}" data-region="recommended" role="tabpanel" aria-labelledby="recommended-tab-{{uniqid}}">
<div class="optionscontainer d-flex flex-wrap mw-100 p-3 position-relative" role="menubar" data-region="chooser-options-container">
{{#recommended}}
{{>core_course/chooser_item}}
{{/recommended}}
</div>
</div>
<div class="tab-pane {{#fallback}}active{{/fallback}}" id="all-{{uniqid}}" data-region="default" role="tabpanel" aria-labelledby="all-tab-{{uniqid}}">
<div class="optionscontainer d-flex flex-wrap mw-100 p-3 position-relative" role="menubar" data-region="chooser-options-container">
{{#default}}
{{>core_course/chooser_item}}
{{/default}}
</div>
</div>
</div>
</div>
+50
View File
@@ -0,0 +1,50 @@
{{!
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/>.
}}
{{!
@template core_course/chooser_search
Chooser search template.
Example context (json):
{}
}}
<div class="searchbar input-group" role="search">
<label for="searchinput">
<span class="sr-only">{{#str}} searchactivities, core {{/str}}</span>
</label>
<input type="text"
data-action="search"
id="searchinput"
class="form-control form-control-lg searchinput pl-3 pr-0 py-2"
placeholder="{{#str}} search, core {{/str}}"
name="search"
autocomplete="off"
>
<div class="searchbar-append d-flex px-3">
<div class="search-icon">
{{#pix}} a/search, core {{/pix}}
</div>
<div class="clear d-none">
<button class="btn p-0" data-action="clearsearch">
<span class="d-flex" aria-hidden="true">{{#pix}} e/cancel_solid_circle, core {{/pix}}</span>
<span class="sr-only">{{#str}} clearsearch, core {{/str}}</span>
</button>
</div>
</div>
</div>
<div class="searchresultscontainer" data-region="search-results-container" hidden="hidden" aria-live="polite">
</div>
@@ -0,0 +1,40 @@
{{!
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/>.
}}
{{!
@template core_course/chooser_search_results
Chooser search results template.
Example context (json):
{
"searchresultsnumber": "10",
"searchresults": {
"label": "Option name",
"description": "Option description",
"urls": {
"addoption": "http://addoptionurl.com"
},
"icon": "<img class='icon' src='http://urltooptionicon'>"
}
}
}}
<p class="mt-4 px-3">{{#str}} resultsfound, core, {{searchresultsnumber}} {{/str}}</p>
<div class="searchresultitemscontainer d-flex flex-wrap mw-100 position-relative mt-4" role="menubar" data-region="search-result-items-container">
{{#searchresults}}
{{>core_course/chooser_item}}
{{/searchresults}}
</div>
+3
View File
@@ -247,6 +247,7 @@ $string['changesmadereallygoaway'] = 'You have made changes. Are you sure you wa
$string['city'] = 'City/town';
$string['cleaningtempdata'] = 'Cleaning temp data';
$string['clear'] = 'Clear';
$string['clearsearch'] = 'Clear search input';
$string['clickhelpiconformoreinfo'] = '... continues ... Click on the help icon to read the full article';
$string['clickhere'] = 'Click here ...';
$string['clicktohideshow'] = 'Click to expand or collapse';
@@ -1724,6 +1725,7 @@ $string['resourcedisplayopen'] = 'Open';
$string['resourcedisplaypopup'] = 'In pop-up';
$string['resources'] = 'Resources';
$string['resources_help'] = 'Resource types enable almost any kind of web content to be inserted into the course.';
$string['resultsfound'] = '{$a} results found';
$string['restore'] = 'Restore';
$string['restorecancelled'] = 'Restore cancelled';
$string['restorecannotassignroles'] = 'Restore needs to assign roles and you do not have permission to do so';
@@ -1789,6 +1791,7 @@ $string['search_help'] = 'For basic searching of one or more words anywhere in t
For advanced searching, press the search button without typing anything in the search box to access the advanced search form.';
$string['searchagain'] = 'Search again';
$string['searchactivities'] = 'Search for activities by name or description';
$string['searchbyemail'] = 'Search by email address';
$string['searchbyusername'] = 'Search by username';
$string['searchcourses'] = 'Search courses';
@@ -85,6 +85,7 @@ class icon_system_fontawesome extends icon_system_font {
'core:e/bold' => 'fa-bold',
'core:e/bullet_list' => 'fa-list-ul',
'core:e/cancel' => 'fa-times',
'core:e/cancel_solid_circle' => 'fas fa-times-circle',
'core:e/cell_props' => 'fa-info',
'core:e/cite' => 'fa-quote-right',
'core:e/cleanup_messy_code' => 'fa-eraser',
+51 -4
View File
@@ -1528,7 +1528,51 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
overflow-y: hidden;
}
.modchoosercontainer .optionscontainer {
.modchooser .searchcontainer .searchbar {
.searchinput {
font-size: inherit;
border: 1px solid #dee2e6;
border-top-left-radius: .3rem;
border-bottom-left-radius: .3rem;
border-right: none;
}
.searchbar-append {
align-items: center;
border: 1px solid #dee2e6;
border-left: none;
border-top-right-radius: .3rem;
border-bottom-right-radius: .3rem;
i {
color: #868e96;
margin: 0;
vertical-align: middle;
}
.clear {
i {
height: 23px;
width: 23px;
font-size: 23px;
pointer-events: none;
}
}
.search-icon {
i {
height: 20px;
width: 20px;
font-size: 20px;
}
}
}
}
.modchoosercontainer .optionscontainer,
.modchoosercontainer .searchresultitemscontainer {
overflow-x: hidden;
.option {
// Six items per line.
@@ -1592,7 +1636,8 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
}
@include media-breakpoint-down(lg) {
.modchoosercontainer .optionscontainer .option {
.modchoosercontainer .optionscontainer .option,
.modchoosercontainer .searchresultitemscontainer .option {
// Five items per line.
flex-basis: 20%;
}
@@ -1607,13 +1652,15 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
.modchooser .modal-body .carousel-item,
.modchooser .modal-body .optionsummary,
.modchoosercontainer,
.optionscontainer {
.optionscontainer,
.searchresultitemscontainer {
height: 100%;
}
.path-course-view .modal-dialog.modal-lg {
margin: 0;
}
.modchoosercontainer .optionscontainer .option {
.modchoosercontainer .optionscontainer .option,
.modchoosercontainer .searchresultitemscontainer .option {
// Four items per line.
flex-basis: 25%;
}
+49 -11
View File
@@ -10666,23 +10666,58 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
.modchoosercontainer.noscroll {
overflow-y: hidden; }
.modchoosercontainer .optionscontainer {
.modchooser .searchcontainer .searchbar .searchinput {
font-size: inherit;
border: 1px solid #dee2e6;
border-top-left-radius: .3rem;
border-bottom-left-radius: .3rem;
border-right: none; }
.modchooser .searchcontainer .searchbar .searchbar-append {
align-items: center;
border: 1px solid #dee2e6;
border-left: none;
border-top-right-radius: .3rem;
border-bottom-right-radius: .3rem; }
.modchooser .searchcontainer .searchbar .searchbar-append i {
color: #868e96;
margin: 0;
vertical-align: middle; }
.modchooser .searchcontainer .searchbar .searchbar-append .clear i {
height: 23px;
width: 23px;
font-size: 23px;
pointer-events: none; }
.modchooser .searchcontainer .searchbar .searchbar-append .search-icon i {
height: 20px;
width: 20px;
font-size: 20px; }
.modchoosercontainer .optionscontainer,
.modchoosercontainer .searchresultitemscontainer {
overflow-x: hidden; }
.modchoosercontainer .optionscontainer .option {
.modchoosercontainer .optionscontainer .option,
.modchoosercontainer .searchresultitemscontainer .option {
flex-basis: 16%; }
.modchoosercontainer .optionscontainer .option .optionactions .optionaction {
.modchoosercontainer .optionscontainer .option .optionactions .optionaction,
.modchoosercontainer .searchresultitemscontainer .option .optionactions .optionaction {
cursor: pointer;
margin: 0.2rem;
color: #868e96; }
.modchoosercontainer .optionscontainer .option .optionactions .optionaction i {
.modchoosercontainer .optionscontainer .option .optionactions .optionaction i,
.modchoosercontainer .searchresultitemscontainer .option .optionactions .optionaction i {
margin: 0; }
.modchoosercontainer .optionscontainer .option .optioninfo a {
.modchoosercontainer .optionscontainer .option .optioninfo a,
.modchoosercontainer .searchresultitemscontainer .option .optioninfo a {
color: #495057; }
.modchoosercontainer .optionscontainer .option .optioninfo a:hover {
.modchoosercontainer .optionscontainer .option .optioninfo a:hover,
.modchoosercontainer .searchresultitemscontainer .option .optioninfo a:hover {
text-decoration: none; }
.modchoosercontainer .optionscontainer .option .optioninfo a .optionname {
.modchoosercontainer .optionscontainer .option .optioninfo a .optionname,
.modchoosercontainer .searchresultitemscontainer .option .optioninfo a .optionname {
margin-top: 0.5em; }
.modchoosercontainer .optionscontainer .option .optioninfo a .optionicon .icon {
.modchoosercontainer .optionscontainer .option .optioninfo a .optionicon .icon,
.modchoosercontainer .searchresultitemscontainer .option .optioninfo a .optionicon .icon {
margin: 0;
padding: 0;
width: 32px;
@@ -10707,7 +10742,8 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
background: #fff; }
@media (max-width: 1199.98px) {
.modchoosercontainer .optionscontainer .option {
.modchoosercontainer .optionscontainer .option,
.modchoosercontainer .searchresultitemscontainer .option {
flex-basis: 20%; } }
@media (max-width: 575.98px) {
@@ -10719,11 +10755,13 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
.modchooser .modal-body .carousel-item,
.modchooser .modal-body .optionsummary,
.modchoosercontainer,
.optionscontainer {
.optionscontainer,
.searchresultitemscontainer {
height: 100%; }
.path-course-view .modal-dialog.modal-lg {
margin: 0; }
.modchoosercontainer .optionscontainer .option {
.modchoosercontainer .optionscontainer .option,
.modchoosercontainer .searchresultitemscontainer .option {
flex-basis: 25%; } }
/* Form element: listing */
+49 -11
View File
@@ -10873,23 +10873,58 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
.modchoosercontainer.noscroll {
overflow-y: hidden; }
.modchoosercontainer .optionscontainer {
.modchooser .searchcontainer .searchbar .searchinput {
font-size: inherit;
border: 1px solid #dee2e6;
border-top-left-radius: .3rem;
border-bottom-left-radius: .3rem;
border-right: none; }
.modchooser .searchcontainer .searchbar .searchbar-append {
align-items: center;
border: 1px solid #dee2e6;
border-left: none;
border-top-right-radius: .3rem;
border-bottom-right-radius: .3rem; }
.modchooser .searchcontainer .searchbar .searchbar-append i {
color: #868e96;
margin: 0;
vertical-align: middle; }
.modchooser .searchcontainer .searchbar .searchbar-append .clear i {
height: 23px;
width: 23px;
font-size: 23px;
pointer-events: none; }
.modchooser .searchcontainer .searchbar .searchbar-append .search-icon i {
height: 20px;
width: 20px;
font-size: 20px; }
.modchoosercontainer .optionscontainer,
.modchoosercontainer .searchresultitemscontainer {
overflow-x: hidden; }
.modchoosercontainer .optionscontainer .option {
.modchoosercontainer .optionscontainer .option,
.modchoosercontainer .searchresultitemscontainer .option {
flex-basis: 16%; }
.modchoosercontainer .optionscontainer .option .optionactions .optionaction {
.modchoosercontainer .optionscontainer .option .optionactions .optionaction,
.modchoosercontainer .searchresultitemscontainer .option .optionactions .optionaction {
cursor: pointer;
margin: 0.2rem;
color: #868e96; }
.modchoosercontainer .optionscontainer .option .optionactions .optionaction i {
.modchoosercontainer .optionscontainer .option .optionactions .optionaction i,
.modchoosercontainer .searchresultitemscontainer .option .optionactions .optionaction i {
margin: 0; }
.modchoosercontainer .optionscontainer .option .optioninfo a {
.modchoosercontainer .optionscontainer .option .optioninfo a,
.modchoosercontainer .searchresultitemscontainer .option .optioninfo a {
color: #495057; }
.modchoosercontainer .optionscontainer .option .optioninfo a:hover {
.modchoosercontainer .optionscontainer .option .optioninfo a:hover,
.modchoosercontainer .searchresultitemscontainer .option .optioninfo a:hover {
text-decoration: none; }
.modchoosercontainer .optionscontainer .option .optioninfo a .optionname {
.modchoosercontainer .optionscontainer .option .optioninfo a .optionname,
.modchoosercontainer .searchresultitemscontainer .option .optioninfo a .optionname {
margin-top: 0.5em; }
.modchoosercontainer .optionscontainer .option .optioninfo a .optionicon .icon {
.modchoosercontainer .optionscontainer .option .optioninfo a .optionicon .icon,
.modchoosercontainer .searchresultitemscontainer .option .optioninfo a .optionicon .icon {
margin: 0;
padding: 0;
width: 32px;
@@ -10914,7 +10949,8 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
background: #fff; }
@media (max-width: 1199.98px) {
.modchoosercontainer .optionscontainer .option {
.modchoosercontainer .optionscontainer .option,
.modchoosercontainer .searchresultitemscontainer .option {
flex-basis: 20%; } }
@media (max-width: 575.98px) {
@@ -10926,11 +10962,13 @@ body#page-lib-editor-tinymce-plugins-moodlemedia-preview {
.modchooser .modal-body .carousel-item,
.modchooser .modal-body .optionsummary,
.modchoosercontainer,
.optionscontainer {
.optionscontainer,
.searchresultitemscontainer {
height: 100%; }
.path-course-view .modal-dialog.modal-lg {
margin: 0; }
.modchoosercontainer .optionscontainer .option {
.modchoosercontainer .optionscontainer .option,
.modchoosercontainer .searchresultitemscontainer .option {
flex-basis: 25%; } }
/* Form element: listing */