Skip to content

Commit feac6c1

Browse files
marxjohnsonandrewnicols
authored andcommitted
MDL-83862 Add question bank filter validation documentation
1 parent b6cecff commit feac6c1

1 file changed

Lines changed: 39 additions & 50 deletions

File tree

docs/apis/plugintypes/qbank/filters.md

Lines changed: 39 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,26 @@ documentationDraft: true
99
---
1010

1111
<Since
12-
version="4.3"
13-
issueNumber="MDL-72321"
12+
version="4.3"
13+
issueNumber="MDL-72321"
1414
/>
1515

16-
Question bank plugins allow you define additional filters. These can be used when viewing the question bank, and are included
17-
in the URL so that a filtered view of the question bank can be shared. They are also used when defining the criteria for adding
18-
random questions to a quiz.
16+
Question bank plugins allow you define additional filters. These can be used when viewing the question bank, and are included in the URL so that a filtered view of the question bank can be shared. They are also used when defining the criteria for adding random questions to a quiz.
1917

2018
## Creating a new filter condition
2119

2220
A filter condition consists of two parts - the backend "condition" PHP class, and the frontend "filter" JavaScript class.
2321

24-
The "condition" class defines the general properties of the filter - its name, various options, and how it is applied to the
25-
question bank query.
22+
The "condition" class defines the general properties of the filter - its name, various options, and how it is applied to the question bank query.
2623
The "filter" class defines how the filter is displayed in the UI, and how values selected in the UI are passed back to the condition.
2724

2825
Each new filter condition must define a new "condition" class in the qbank plugin based on `core_question\local\bank\condition`.
2926
By default this will use the `core/datafilter/filtertype` "filter" class, although this can be overridden too if required.
3027

3128
### Basic example
3229

33-
This outlines the bare minimum required to implement a new filter condition. This will give you a field that allows you to
34-
enter keywords and add them to a list of selected search terms, the filter the questions by that list of terms.
35-
This assumes that you already have the basic framework of a qbank plugin in place. For real-world examples,
36-
look for classes that extend `core_question\local\bank\condition`.
30+
This outlines the bare minimum required to implement a new filter condition. This will give you a field that allows you to enter keywords and add them to a list of selected search terms, the filter the questions by that list of terms.
31+
This assumes that you already have the basic framework of a qbank plugin in place. For real-world examples, look for classes that extend `core_question\local\bank\condition`.
3732

3833
Create a `condition` class within your plugin's namespace. For a plugin called `qbank_myplugin` this would look something like:
3934

@@ -69,8 +64,7 @@ public function get_name(): string {
6964
}
7065
```
7166

72-
Define `get_condition_key()`, which returns a unique machine-readable ID for this filter condition, used when passing the filter
73-
as a parameter.
67+
Define `get_condition_key()`, which returns a unique machine-readable ID for this filter condition, used when passing the filter as a parameter.
7468

7569
```php title="Define the condition key"
7670
public function get_condition_key(): string {
@@ -79,12 +73,9 @@ public function get_condition_key(): string {
7973
```
8074

8175
To actually filter the results, define `build_query_from_filter()` which returns an SQL `WHERE` condition, and an array of parameters.
82-
The `$filter` parameter receives an array with a `'values'` key, containing an array of the selected values, and a `'jointype'` key,
83-
containing one of the `JOINTTYPE_ANY`, `JOINTYPE_ALL` or `JOINTYPE_NONE` constants. Use these to build your condition as required.
76+
The `$filter` parameter receives an array with a `'values'` key, containing an array of the selected values, and a `'jointype'` key, containing one of the `JOINTTYPE_ANY`, `JOINTYPE_ALL` or `JOINTYPE_NONE` constants. Use these to build your condition as required.
8477

85-
The conditions from each filter are combined with the query in
86-
[`core_question\local\bank\view::build_query()`](https://github.com/moodle/moodle/blob/c741492c38b9945abbfc7e90dfe8f943279f8265/question/classes/local/bank/view.php#L733)
87-
to select the filtered question list.
78+
The conditions from each filter are combined with the query in [`core_question\local\bank\view::build_query()`](https://github.com/moodle/moodle/blob/c741492c38b9945abbfc7e90dfe8f943279f8265/question/classes/local/bank/view.php#L733)
8879

8980
```php title="Filter questions"
9081
public function build_query_from_filter(array $filter): array {
@@ -109,15 +100,13 @@ public function build_query_from_filter(array $filter): array {
109100
}
110101
```
111102

112-
Following this pattern with your own fields and options will give you a basic functional filter. Most filters will require
113-
more complex functionality, which can be achieved through additional methods.
103+
Following this pattern with your own fields and options will give you a basic functional filter. Most filters will require more complex functionality, which can be achieved through additional methods.
114104

115105
### Additional options
116106

117107
#### Pre-defined values
118108

119-
To define the list of possible filter values, define `get_initial_values()`, which returns an array of `['value', 'title']` for each
120-
option. These will then be searchable and selectable in the autocomplete field.
109+
To define the list of possible filter values, define `get_initial_values()`, which returns an array of `['value', 'title']` for each option. These will then be searchable and selectable in the autocomplete field.
121110

122111
```php title="Define initial filter values"
123112
public function get_initial_values(): string {
@@ -146,8 +135,7 @@ public function allow_custom(): bool {
146135

147136
#### Restrict join types
148137

149-
Not all join types are relevant to all filters. If each question will only match one of the selected values, it does not make
150-
sense to allow `JOINTYPE_ALL`. Define `get_join_list()` and return an array of the applicable join types.
138+
Not all join types are relevant to all filters. If each question will only match one of the selected values, it does not make sense to allow `JOINTYPE_ALL`. Define `get_join_list()` and return an array of the applicable join types.
151139

152140
```php title="Define a restricted list of join types"
153141
public function get_join_list(): array {
@@ -171,8 +159,7 @@ public function allow_multiple(): bool {
171159

172160
#### Allow empty values?
173161

174-
By default, conditions can be left empty, and therefore will not be included in the filter. To make it compulsory to select a
175-
value for this condition when it is added, override `allow_empty()` to return false.
162+
By default, conditions can be left empty, and therefore will not be included in the filter. To make it compulsory to select a value for this condition when it is added, override `allow_empty()` to return false.
176163

177164
```php title="Disable empty values"
178165
public function allow_empty(): bool {
@@ -198,8 +185,7 @@ If this does not fit your filter's use case, you can tell your condition to use
198185

199186
You can either use a different core filter type from `/lib/amd/src/datafilter/filtertypes`, or define your own.
200187

201-
To tell your filter condition to use a different filter class, override the `get_filter_class()` method to return the namespaced
202-
path to your JavaScript class.
188+
To tell your filter condition to use a different filter class, override the `get_filter_class()` method to return the namespaced path to your JavaScript class.
203189

204190
```php title="Override the default filter class"
205191
public function get_filter_class(): string {
@@ -208,37 +194,40 @@ public function get_filter_class(): string {
208194
```
209195

210196
To create your own filter class, a new JavaScript file in your plugin under `amd/src/datafilter/filtertypes/myfilter.js`.
211-
In this file, export a default class that extends `core/datafilter/filtertype`
212-
(or another core filter type from `/lib/amd/src/datafilter/filtertypes`) and override the base methods as required.
213-
For example, if your filter uses textual rather than numeric values, you can override `get values()` to return the raw values
214-
without running `parseInt()` (see
215-
[`qbank_viewquestiontype/datafilter/filtertypes/type`](https://github.com/moodle/moodle/blob/main/mod/quiz/tests/behat/editing_add_from_question_bank.feature)).
197+
In this file, export a default class that extends `core/datafilter/filtertype` (or another core filter type from `/lib/amd/src/datafilter/filtertypes`) and override the base methods as required.
198+
For example, if your filter uses textual rather than numeric values, you can override `get values()` to return the raw values without running `parseInt()` (see [`qbank_viewquestiontype/datafilter/filtertypes/type`](https://github.com/moodle/moodle/blob/main/mod/quiz/tests/behat/editing_add_from_question_bank.feature)).
216199

217200
If you want a different UI for selecting your filter values instead of a single autocomplete, you can override `addValueSelector()`.
218201
This also provides flexibility over how the values provided by `get_initial_values()` are used by the UI.
219202

220203
#### Filter options
221204

222-
If your condition supports additional options as to how the selected values are applied to the query, such as whether child
223-
categories are included when parent categories are selected, you can define "Filter options".
205+
If your condition supports additional options as to how the selected values are applied to the query, such as whether child categories are included when parent categories are selected, you can define "Filter options".
224206

225-
In your condition class, define `get_filteroptions()` which returns an object containing the current filter options. You will
226-
probably want to add some code to the constructor to read in the current filter options, and some code the `build_query_from_filter()`
227-
to use the option. See
228-
[`qbank_managecategories\category_condition`](https://github.com/moodle/moodle/blob/main/question/bank/managecategories/classes/category_condition.php)
229-
as an example.
207+
In your condition class, define `get_filteroptions()` which returns an object containing the current filter options. You will probably want to add some code to the constructor to read in the current filter options, and some code the `build_query_from_filter()` to use the option.
208+
See [`qbank_managecategories\category_condition`](https://github.com/moodle/moodle/blob/main/question/bank/managecategories/classes/category_condition.php) as an example.
230209

231-
You JavaScript filter class will also need to support your filter options. Override the constructor an add additional code
232-
for the UI required to set your filter options, and override `get filterOptions()` to return the current value for any options set
233-
in this UI. See
234-
[`qbank_managecategories/datafilter/filtertypes/categories`](https://github.com/moodle/moodle/blob/main/question/bank/managecategories/amd/src/datafilter/filtertypes/categories.js)
235-
as an example.
210+
You JavaScript filter class will also need to support your filter options. Override the constructor an add additional code for the UI required to set your filter options, and override `get filterOptions()` to return the current value for any options set in this UI.
211+
See [`qbank_managecategories/datafilter/filtertypes/categories`](https://github.com/moodle/moodle/blob/main/question/bank/managecategories/amd/src/datafilter/filtertypes/categories.js) as an example.
236212

237213
#### Context-sensitive configuration
238214

239-
You may want your filter to behave differently depending on where it is being displayed. In this case you can override the
240-
constructor which receives the current `$qbank` view object, and extract some data that is used later on by your other methods.
215+
You may want your filter to behave differently depending on where it is being displayed. In this case you can override the constructor which receives the current `$qbank` view object, and extract some data that is used later on by your other methods.
241216

242-
For example, the
243-
[tag condition](https://github.com/moodle/moodle/blob/main/question/bank/tagquestion/classes/tag_condition.php)
244-
will find the context of the current page, and use that to control which tags are available in the filter.
217+
For example, the [tag condition](https://github.com/moodle/moodle/blob/main/question/bank/tagquestion/classes/tag_condition.php) will find the context of the current page, and use that to control which tags are available in the filter.
218+
219+
#### Validation
220+
221+
<Since
222+
version="5.0"
223+
issueNumber="MDL-83862"
224+
/>
225+
226+
Filters support standard [Client-side form validation](https://developer.mozilla.org/en-US/docs/Learn_web_development/Extensions/Forms/Form_validation).
227+
The simplest way to implement this is to set validation properties on your inputs in the mustache template used by your `addValueSelector()` method.
228+
229+
If you need something more advanced, you can define a `validation()` method in your filter class. This is called when the "Apply filters" button is clicked, giving you the opportunity to inspect the current values of the filter, and perform validation checks.
230+
If validation fails, you should display errors using the standard `setCustomValidity()` and `reportValidity()` methods on your filter's input elements, and return `false`.
231+
See `core/datafilter/filtertypes/datetime` for an example.
232+
233+
This client-side validation is only to prevent invalid values being entered in the UI. You should also validate data received by the `build_query_from_filter()` method in your condition class, and throw exceptions in the event of validation failures.

0 commit comments

Comments
 (0)