This document explains how to add your own fields to a new section in AdvancedSearch, using the advancedSearch.configureFields
JavaScript hook.
The search keyword prefix:
allows to search for pages where the page title starts with the search string. To add a search field for the prefix:
keyword to AdvancedSearch, you need to add a hook handler for the advancedSearch.configureFields
hook. The hook gets an instance of FieldCollection and you can use its add
method to add a field configuration for your own field.
The fieldCollection.add
method takes your field definition and the section name where the new field should appear (in our case we're using the section name extra
). The field definition object needs three methods:
formatter
converts the user input to the search keyword string,init
creates the field widgetlayout
defines the label, help text and placement of the field widgetconst { createSearchFieldFromObject, TextInput } = require( 'ext.advancedSearch.elements' ); mw.hook( 'advancedSearch.configureFields' ).add( function ( fieldCollection ) { var fieldDefinition = { id: 'prefix', defaultValue: '', formatter: function ( val ) { return 'prefix:' + val.trim(); }, init: function ( state, config ) { return new TextInput( state, config ); }, layout: function ( widget, field ) { return new OO.ui.FieldLayout( widget, { label: mw.msg( 'advancedsearch-field-' + field.id ), align: 'right', help: mw.msg( 'advancedsearch-field-help-' + field.id ), $overlay: true } ); } }; fieldCollection.add( createSearchFieldFromObject( fieldDefinition , 'extra' ) ); } );
createSearchFieldFromObject
is a helper function that returns a subclassed instance of SearchField
. SearchField
is an abstract class that defines the properties and methods that all search field configuration entries must implement. All the object properties and methods you pass into the field definition in createSearchFieldFromObject
will become properties and methods of the new SearchField
subclass.
You can place the code for adding new fields in the common.js
page or in a gadget definition.
A unique name for the field.
The default value for the field. The correct value (empty string, empty array, etc) depends on the widget you use in the init
function (see below). For example, for TextInput
, the default value needs to be an empty string. For ArbitraryWordInput
(the pill-style input), the default value needs to be an empty array.
The formatter function takes the field value and converts it to MediaWiki search keywords.
The type of the input parameter depends on the field widget you chose - TextInput
values are strings, ArbitraryWordInput
values are an array of strings, etc.
Here is an example for the formatter for the pages without this word field that uses ArbitraryWordInput
:
function ( val ) { if ( Array.isArray( val ) ) { return val.map( function ( el ) { return '-' + el; } ).join( ' ' ); } return '-' + val; }
If you're getting strings as input, remember to trim the whitespace at the beginning and end of the value. Please think of all possible values that might come from your field. You might need to quote and/or escape the values coming from your field.
A function that returns an AdvancedSearch widget instance. AdvancedSearch widgets are subclasses of OOUI widgets with additional state handling capabilities.
If you want to have a plain text input field or a "pill box" text input field like most of the other AdvancedSearch fields, use the TextInput
and ArbitraryWordInput
widgets.
For more complex custom widgets, have a look at the in-depth explanation the section "Implementing your own widgets" to see what state-handling methods you need.
A function that returns an OO.ui.FieldLayout
instance. The function gets passed the widget that the init
function created and the field
instance from your call to createSearchFieldFromObject
.
If you want to make the help
option translatable and if it contains complex HTML (with examples and lists), you can't use mw.msg
until issue https://phabricator.wikimedia.org/T27349 is resolved. A possible workaround for that is to assign the translated strings to mw.config
.
If you want to make a dependent field, where you show or hide the field depending on the value of another field (like the image dimensions), you can use the third parameter for the layout function, state
, and subscribe to its update
method to check for specific values in the current form state. See the function createImageDimensionLayout
in the default field definitions as an example.
Boolean, default false
. Usually, the FieldElementBuilder
takes care of connecting the change
event of AdvancedSearch widgets to the store. If your widget has no change
event or you want to handle it yourself, set this option to true
.
A function that returns a boolean. The function does runtime checks to determine if the field should be shown (e.g. if certain other extensions are enabled).
You provide the i18n keys for the translated field label and help text in the layout
function, just like the example does.
The i18n label for the section name has the prefix advancedsearch-optgroup-
. So if you're adding the group extra
, you must provide translations for the message key advancedsearch-optgroup-extra
.
AdvancedSearch has a unidirectional data flow: User input events in AdvancedSearch widgets collect the user input and send it to its store
property, a SearchModel
instance. All calls to SearchModel.storeField
trigger the update
event of SearchModel
. All AdvancedSearch widgets listen to the update
event from SearchModel
to update their value.
+--------+ User event (e.g. "change") +---------------+ field data +----------+ | +----------------------------->+ Event Handler +------------>+ | | | +---------------+ | | | Widget | "update" event | Store | | +<-----------------------------------------------------------+ | +--------+ +----------+
This event-based, one-directional data flow enables
Your widgets don't have to implement the event handler themselves, AdvancedSearch initialization code in the FieldElementBuilder
class takes care of setting up the event handler.
Here is some minimal widget example code that shows how to handle the "update" event from the store:
const MyWidget = function ( store, config ) { this.fieldId = config.fieldId; this.store = store; store.connect( this, { update: 'onStoreUpdate' } ); MyWidget.parent.call( this, config ); this.setValuesFromStore(); }; OO.inheritClass( MyWidget, OO.ui.Widget ); MyWidget.prototype.onStoreUpdate = function () { this.setValuesFromStore(); }; MyWidget.prototype.setValuesFromStore = function () { if ( this.store.hasFieldChanged( this.fieldId, this.data ) ) { this.setValue( this.store.getField( this.fieldId ) ); } };
Note: To avoid infinite loops, the event handler in the widget must check if the value coming from the update
event is the same as the one already in the widget. If the values are the same, don't set it again, otherwise the widget will trigger another change event, sending the value to the store. As you can see, there is a utility method in the store for that.