diff --git a/docs/apis/subsystems/group/index.md b/docs/apis/subsystems/group/index.md index 58b338ce79..95df14e34b 100644 --- a/docs/apis/subsystems/group/index.md +++ b/docs/apis/subsystems/group/index.md @@ -189,6 +189,27 @@ A query like this must join on `group_members` as group visibility is dependant ::: + + +### Group GeoPattern images + +Moodle now allows groups to generate GeoPattern images. This is intended to improve the overall user experience by allowing users to differentiate groups easier in their Moodle activities and resources. + +Generate the SVG image for the group and use it in your Moodle activities and resources: + +```php +$group_svg_image = moodle_url::make_pluginfile_url( + $context->id, + 'group', + 'generated', + $group->id, + '/', + 'group.svg' +); +``` + +This will create an SVG image for the specified group, which can then be used in Moodle activities and resources. Make sure to customize the code to fit your specific use case. + ## Further reading - [Groups FAQ](https://docs.moodle.org/en/Groups_FAQ) diff --git a/docs/guides/javascript/comboboxsearch/examples.md b/docs/guides/javascript/comboboxsearch/examples.md new file mode 100644 index 0000000000..a48f1fbec8 --- /dev/null +++ b/docs/guides/javascript/comboboxsearch/examples.md @@ -0,0 +1,61 @@ +--- +title: Example usages +tags: + - Javascript + - AJAX + - Searching + - Navigation + - UI + - UX + - Frontend +--- + +## Core Search Dropdown Migration + +Several grade report modules have been migrated to use the new core search dropdown component. This migration provides a more consistent user experience across different grade report modules and allows for easier maintenance and updates. + +The following grade reports were migrated within MDL-77991: + +- Grade report user +- Grade report single view +- Grade report grader + +## Third party usage + +Whilst working on MDL-77991, the Moodle development team also migrated the following third party plugin to use the new core search dropdown component as a proof of concept: + +- [Block stash](https://moodle.org/plugins/block_stash) + +## Quick start + +To use the core components in your own Moodle module, you can follow these steps: + +- Import the core component(s) you want to use: + +```php +use core\output\comboboxsearch; +``` + +- Instantiate the component with the appropriate options in PHP: + +```php +$searchdropdows = new comboboxsearch( + true, + 'Trigger button content', + null, + 'parent-class', + 'trigger-button-class', + 'search-dropdown-class', + null, + false, +); +$data['templatevalue'] = $searchdropdown->export_for_template(\renderer_base); +``` + +- Instantiate the component JS within PHP: + +```php +$PAGE->requires->js_call_amd('/', 'init'); +``` + +This will output the HTML for the search dropdown component with the specified options. You can customize the options to fit your specific use case. diff --git a/docs/guides/javascript/comboboxsearch/index.md b/docs/guides/javascript/comboboxsearch/index.md new file mode 100644 index 0000000000..0735c3b94e --- /dev/null +++ b/docs/guides/javascript/comboboxsearch/index.md @@ -0,0 +1,259 @@ +--- +title: Combobox searching +tags: + - Javascript + - AJAX + - Searching + - Navigation + - UI + - UX + - Frontend +--- + + + +A combobox search component has been added to the core Moodle system. This component provides an additional layer of search functionality, allowing users to easily navigate and filter search results. The combobox search component is designed to be reusable and can be integrated into various areas of the Moodle platform. + +To implement the combobox search component, follow these steps: + +1. Add the necessary HTML structure for the dropdown in your template file +2. Initialize the component via PHP +3. Initialize the component using JavaScript + +## Benefits + +By moving the tertiary search dropdown component to the core, the Moodle development team aimed to achieve the following benefits: + +- Improved consistency: Using a single, core component for the search dropdown ensures that the look and feel of this UI element remains consistent across different parts of Moodle. +- Improved code maintainability: Having the component in the core makes it easier for the development team to manage the codebase and ensure that any updates to the component are applied consistently across the entire application. +- Reduced code duplication: By making the component available to all Moodle modules, there is no need to duplicate the code in different parts of the application. + +## Structure of the component + +### Extending the search_combobox + +If you want to get started quickly, you can extend the `search_combobox` class. This class provides a lot of the boilerplate code that you would otherwise need to write yourself. You'll also need to implement the functions that throw errors if undefined however as we need some information from you about what and how you are searching. + +
+ View example +
+ +```js title="path/to/plugin/amd/src/yourcomponent.js" +import search_combobox from 'core/comboboxsearch/search_combobox'; + +export default class extends search_combobox { + (...) +} +``` + +
+
+ +### Instantiate a Component + +If you'll be using JS for other functionality, you can do the following: + + + + +```js title="path/to/plugin/amd/src/main.js" +import YourComponent from 'YOUR_PLUGIN/yourcomponent; + +(...) + +export const init = () => { + return new YourComponent({}); +}; + +``` + + + + +```php title="path/to/plugin/index.php" +$PAGE->requires->js_call_amd('YOUR_PLUGIN/main', 'init'); +``` + + + + +### Initialize a component from a mustache template + +Components are easy to embed in mustache files. To do so your class must have a static "init" method that could be called inside the `{{#js}}`. + + + + +```js title="path/to/plugin/amd/src/childcomponent.js" +import YourComponent from 'YOUR_PLUGIN/yourcomponent; + +export default class extends YourComponent { + + constructor() { + super(); + } + + static init() { + return new this(); + } +} + +``` + + + + +```handlebars title="path/to/plugin/templates/yourthing/childcomponent.mustache" +{{#js}} + require(['YOUR_PLUGIN/local/yourthing/childcomponent'], function(component) { + component.init(); + }); +{{/js}} +``` + +:::tip Generating unique id attributes + +You can use the `{{uniqid}}` Mustache helper within your code to help you generate a unique id for your HTML attributes and target them in your React component. + +Please note that the `{{uniqid}}` helper generates a single value each time it is rendered, and you will need to combine it with other data to create a truly unique value. In this example the name of the plugin, and a static element `id` is used to generate a unique value. + +::: + + + + +### search_combobox helpers + +The search_combobox class offers some helpers to standardize the components' code and make them more maintainable. + +#### getDataset() + +Calls the implemented `fetchDataset` method and returns the dataset. + +#### getDatasetSize() + +Returns the size of the dataset without having to call `getDataset` first. + +#### getMatchedResults() + +Once a result set has been filtered, this method returns the matched results based on the users search input. + +#### setMatchedResults() + +By default, returns the dataset but can be overridden to show how exactly a result set matched upon the data. + +#### getSearchTerm() + +Provide the current search term that the user entered without manually accessing the DOM. + +#### getPreppedSearchTerm() + +Return the parsed (lowercase) search term. + +#### setSearchTerms() + +When a user changes the value of the input, after we debounce, we update the search term in memory. + +#### getHTMLElements() + +Update and return some of the typical HTML elements that are used in the component. + +#### closeSearch() + +Close the associated dropdown manually since we control the dropdown rather than purely relying on Bootstrap. +We can optionally clear the users' search term. + +#### searchResultsVisible() + +Shorthand for confirming if the search results are currently visible. + +#### toggleDropdown() + +Manually open and close the dropdown rather than purely relying on Bootstrap. + +#### updateNodes() + +Ensure that nodes that are susceptible to change are up-to-date when we need them. + +#### registerClickHandlers() + +Handle our base case of click handlers i.e. opening and closing the dropdown. This can be further extended in callers for any special handling. + +#### registerKeyHandlers() + +Handle our base case of keyboard handlers i.e. opening and closing the dropdown, accessibility handling. This can be further extended in callers for any special handling. + +#### registerInputHandlers() + +Register the text input handlers for the search input and debounce the input so that we don't need to fire a bunch of calls as the user is still typing. + +#### filterrenderpipe() + +Combine the filter and render methods into a single method to be called by the input handlers as a QoL shorthand call. + +#### renderAndShow() + +Given we need to update the display, ensure we have the latest dataset and render it. + +#### keyUpDown() + +Given the user is navigating the dropdown with the keyboard, handle the common up and down arrow key cases. + +#### clickHandler() + +Used within [registerClickHandlers](#registerClickHandlers()) to handle the common click cases like selecting results, closing the dropdown, etc. + +#### keyHandler() + +Used within [registerKeyHandlers](#registerKeyHandlers()) to handle the common keyboard cases like navigating nodes, closing the dropdown, etc. + +#### selectNode() + +When used in conjunction with [keyUpDown](#keyUpDown()) and other similar functions, this function will select the node that the user has navigated to. + +#### moveToFirstNode() + +When used in conjunction with [keyUpDown](#keyUpDown()) and other similar functions, this function will move the user to the first node in the dropdown. + +#### moveToLastNode() + +When used in conjunction with [keyUpDown](#keyUpDown()) and other similar functions, this function will move the user to the last node in the dropdown. + +#### moveToNode() + +When used in conjunction with [keyUpDown](#keyUpDown()) and other similar functions, this function will move the user to the node that is passed in. + +### Required functions to implement + +We bootstrap a lot of the functionality within the component but there are some functions that you will need to implement yourself. +This is because we don't know what your data looks like, how you want to filter it, etc. + +#### fetchDataset() + +Implementors should return a dataset that will be used to filter and render the results, this can be provided as a promise or a synchronous call. + +#### filterDataset(dataset) + +Implementors should return a filtered dataset based on the search term that the user has entered, this is entirely up to your as long as you set the results. + +#### filterMatchDataset() + +This can either just return the base dataset or you can use it to mutate the dataset to show how the results matched the search term i.e. adding links and whatnot. + +#### renderDropdown() + +Where and how do you want the data to be rendered? This is entirely up to you. + +#### componentSelector() + +We need to know where to find the component in the DOM, this is the selector that will be used to find the component. + +#### dropdownSelector() + +We need to know where to find the dropdown in the DOM, this is the selector that will be used to find the dropdown. + +#### triggerSelector() + +We need to know where to find the trigger in the DOM, this is the selector that will be used to find the trigger. + +For example usages view the [examples](comboboxsearch/examples) page. diff --git a/versioned_docs/version-4.3/devupdate.md b/versioned_docs/version-4.3/devupdate.md index dc9d1439b6..5d4b347fb5 100644 --- a/versioned_docs/version-4.3/devupdate.md +++ b/versioned_docs/version-4.3/devupdate.md @@ -603,3 +603,11 @@ public function add_custom_instance(stdClass $course, ?array $fields = null): ?i ``` In [MDL-73839](https://tracker.moodle.org/browse/MDL-73839) cohort enrolment method has been updated to support CSV course upload. + +## Addition of comboboxsearch component + + + +As part of [MDL-77991](https://tracker.moodle.org/browse/MDL-77991) multiple gradebook report searching functionalities were migrated into a centralised core component. + +Details on its use can be found via [Combobox searching](./guides/javascript/comboboxsearch) diff --git a/versioned_docs/version-4.3/guides/javascript/comboboxsearch/examples.md b/versioned_docs/version-4.3/guides/javascript/comboboxsearch/examples.md new file mode 100644 index 0000000000..a48f1fbec8 --- /dev/null +++ b/versioned_docs/version-4.3/guides/javascript/comboboxsearch/examples.md @@ -0,0 +1,61 @@ +--- +title: Example usages +tags: + - Javascript + - AJAX + - Searching + - Navigation + - UI + - UX + - Frontend +--- + +## Core Search Dropdown Migration + +Several grade report modules have been migrated to use the new core search dropdown component. This migration provides a more consistent user experience across different grade report modules and allows for easier maintenance and updates. + +The following grade reports were migrated within MDL-77991: + +- Grade report user +- Grade report single view +- Grade report grader + +## Third party usage + +Whilst working on MDL-77991, the Moodle development team also migrated the following third party plugin to use the new core search dropdown component as a proof of concept: + +- [Block stash](https://moodle.org/plugins/block_stash) + +## Quick start + +To use the core components in your own Moodle module, you can follow these steps: + +- Import the core component(s) you want to use: + +```php +use core\output\comboboxsearch; +``` + +- Instantiate the component with the appropriate options in PHP: + +```php +$searchdropdows = new comboboxsearch( + true, + 'Trigger button content', + null, + 'parent-class', + 'trigger-button-class', + 'search-dropdown-class', + null, + false, +); +$data['templatevalue'] = $searchdropdown->export_for_template(\renderer_base); +``` + +- Instantiate the component JS within PHP: + +```php +$PAGE->requires->js_call_amd('/', 'init'); +``` + +This will output the HTML for the search dropdown component with the specified options. You can customize the options to fit your specific use case. diff --git a/versioned_docs/version-4.3/guides/javascript/comboboxsearch/index.md b/versioned_docs/version-4.3/guides/javascript/comboboxsearch/index.md new file mode 100644 index 0000000000..0735c3b94e --- /dev/null +++ b/versioned_docs/version-4.3/guides/javascript/comboboxsearch/index.md @@ -0,0 +1,259 @@ +--- +title: Combobox searching +tags: + - Javascript + - AJAX + - Searching + - Navigation + - UI + - UX + - Frontend +--- + + + +A combobox search component has been added to the core Moodle system. This component provides an additional layer of search functionality, allowing users to easily navigate and filter search results. The combobox search component is designed to be reusable and can be integrated into various areas of the Moodle platform. + +To implement the combobox search component, follow these steps: + +1. Add the necessary HTML structure for the dropdown in your template file +2. Initialize the component via PHP +3. Initialize the component using JavaScript + +## Benefits + +By moving the tertiary search dropdown component to the core, the Moodle development team aimed to achieve the following benefits: + +- Improved consistency: Using a single, core component for the search dropdown ensures that the look and feel of this UI element remains consistent across different parts of Moodle. +- Improved code maintainability: Having the component in the core makes it easier for the development team to manage the codebase and ensure that any updates to the component are applied consistently across the entire application. +- Reduced code duplication: By making the component available to all Moodle modules, there is no need to duplicate the code in different parts of the application. + +## Structure of the component + +### Extending the search_combobox + +If you want to get started quickly, you can extend the `search_combobox` class. This class provides a lot of the boilerplate code that you would otherwise need to write yourself. You'll also need to implement the functions that throw errors if undefined however as we need some information from you about what and how you are searching. + +
+ View example +
+ +```js title="path/to/plugin/amd/src/yourcomponent.js" +import search_combobox from 'core/comboboxsearch/search_combobox'; + +export default class extends search_combobox { + (...) +} +``` + +
+
+ +### Instantiate a Component + +If you'll be using JS for other functionality, you can do the following: + + + + +```js title="path/to/plugin/amd/src/main.js" +import YourComponent from 'YOUR_PLUGIN/yourcomponent; + +(...) + +export const init = () => { + return new YourComponent({}); +}; + +``` + + + + +```php title="path/to/plugin/index.php" +$PAGE->requires->js_call_amd('YOUR_PLUGIN/main', 'init'); +``` + + + + +### Initialize a component from a mustache template + +Components are easy to embed in mustache files. To do so your class must have a static "init" method that could be called inside the `{{#js}}`. + + + + +```js title="path/to/plugin/amd/src/childcomponent.js" +import YourComponent from 'YOUR_PLUGIN/yourcomponent; + +export default class extends YourComponent { + + constructor() { + super(); + } + + static init() { + return new this(); + } +} + +``` + + + + +```handlebars title="path/to/plugin/templates/yourthing/childcomponent.mustache" +{{#js}} + require(['YOUR_PLUGIN/local/yourthing/childcomponent'], function(component) { + component.init(); + }); +{{/js}} +``` + +:::tip Generating unique id attributes + +You can use the `{{uniqid}}` Mustache helper within your code to help you generate a unique id for your HTML attributes and target them in your React component. + +Please note that the `{{uniqid}}` helper generates a single value each time it is rendered, and you will need to combine it with other data to create a truly unique value. In this example the name of the plugin, and a static element `id` is used to generate a unique value. + +::: + + + + +### search_combobox helpers + +The search_combobox class offers some helpers to standardize the components' code and make them more maintainable. + +#### getDataset() + +Calls the implemented `fetchDataset` method and returns the dataset. + +#### getDatasetSize() + +Returns the size of the dataset without having to call `getDataset` first. + +#### getMatchedResults() + +Once a result set has been filtered, this method returns the matched results based on the users search input. + +#### setMatchedResults() + +By default, returns the dataset but can be overridden to show how exactly a result set matched upon the data. + +#### getSearchTerm() + +Provide the current search term that the user entered without manually accessing the DOM. + +#### getPreppedSearchTerm() + +Return the parsed (lowercase) search term. + +#### setSearchTerms() + +When a user changes the value of the input, after we debounce, we update the search term in memory. + +#### getHTMLElements() + +Update and return some of the typical HTML elements that are used in the component. + +#### closeSearch() + +Close the associated dropdown manually since we control the dropdown rather than purely relying on Bootstrap. +We can optionally clear the users' search term. + +#### searchResultsVisible() + +Shorthand for confirming if the search results are currently visible. + +#### toggleDropdown() + +Manually open and close the dropdown rather than purely relying on Bootstrap. + +#### updateNodes() + +Ensure that nodes that are susceptible to change are up-to-date when we need them. + +#### registerClickHandlers() + +Handle our base case of click handlers i.e. opening and closing the dropdown. This can be further extended in callers for any special handling. + +#### registerKeyHandlers() + +Handle our base case of keyboard handlers i.e. opening and closing the dropdown, accessibility handling. This can be further extended in callers for any special handling. + +#### registerInputHandlers() + +Register the text input handlers for the search input and debounce the input so that we don't need to fire a bunch of calls as the user is still typing. + +#### filterrenderpipe() + +Combine the filter and render methods into a single method to be called by the input handlers as a QoL shorthand call. + +#### renderAndShow() + +Given we need to update the display, ensure we have the latest dataset and render it. + +#### keyUpDown() + +Given the user is navigating the dropdown with the keyboard, handle the common up and down arrow key cases. + +#### clickHandler() + +Used within [registerClickHandlers](#registerClickHandlers()) to handle the common click cases like selecting results, closing the dropdown, etc. + +#### keyHandler() + +Used within [registerKeyHandlers](#registerKeyHandlers()) to handle the common keyboard cases like navigating nodes, closing the dropdown, etc. + +#### selectNode() + +When used in conjunction with [keyUpDown](#keyUpDown()) and other similar functions, this function will select the node that the user has navigated to. + +#### moveToFirstNode() + +When used in conjunction with [keyUpDown](#keyUpDown()) and other similar functions, this function will move the user to the first node in the dropdown. + +#### moveToLastNode() + +When used in conjunction with [keyUpDown](#keyUpDown()) and other similar functions, this function will move the user to the last node in the dropdown. + +#### moveToNode() + +When used in conjunction with [keyUpDown](#keyUpDown()) and other similar functions, this function will move the user to the node that is passed in. + +### Required functions to implement + +We bootstrap a lot of the functionality within the component but there are some functions that you will need to implement yourself. +This is because we don't know what your data looks like, how you want to filter it, etc. + +#### fetchDataset() + +Implementors should return a dataset that will be used to filter and render the results, this can be provided as a promise or a synchronous call. + +#### filterDataset(dataset) + +Implementors should return a filtered dataset based on the search term that the user has entered, this is entirely up to your as long as you set the results. + +#### filterMatchDataset() + +This can either just return the base dataset or you can use it to mutate the dataset to show how the results matched the search term i.e. adding links and whatnot. + +#### renderDropdown() + +Where and how do you want the data to be rendered? This is entirely up to you. + +#### componentSelector() + +We need to know where to find the component in the DOM, this is the selector that will be used to find the component. + +#### dropdownSelector() + +We need to know where to find the dropdown in the DOM, this is the selector that will be used to find the dropdown. + +#### triggerSelector() + +We need to know where to find the trigger in the DOM, this is the selector that will be used to find the trigger. + +For example usages view the [examples](comboboxsearch/examples) page.