Appearance
Extend
Subclass a shipped element to change what it renders or how it behaves. The subclass needs a tag of its own, since the package defines the shipped tag. To write a new component, subclass BaseSearchElement, which receives the search context and the search methods.
ts
import {SearchResults} from 'drupal-search-web-components/dist/search-results/search-results.js';
customElements.define(
'my-results',
class extends SearchResults {
override _getResults() {
// Your markup here.
}
}
);On a Drupal page, the subclass takes the class from window.SearchWebComponents instead of importing it. For the library and the template, see A subclass on a Drupal page.
Hooks and methods
- Hooks. Methods with a leading underscore, e.g.,
_getResults()and_getOptions(), are the hooks a subclass overrides to change what an element renders or does. - Methods. Methods without an underscore are the ones a page or a subclass calls, e.g.,
applyFacet()andgetResults(), and the data methods ofDropdownMixin, e.g.,getAllOptions()andgetOptionLabel().
Each component's Storybook page lists its hooks and its methods under Hooks and methods, inherited ones included, with what each returns and its signature. Three groups of hooks are shared:
- Focus hooks,
_getFocusedControlKey()and_getFocusTargetAfterRender(), on every component exceptsearch-rootandsearch-box. For where focus goes, see Focus. - Facet hooks, on the four facet elements, e.g.,
_getFacetData()and_configOrAttribute(). - Dropdown hooks, on
facet-dropdown-html, and onsearch-sortandsearch-results-per-pagewithtype="html".
For the request hooks of search-root, see Searching another API.
Defaults in a subclass
To give a property a different default in a subclass, set it in the constructor:
ts
constructor() {
super();
this.labelText = 'Order by';
}Do not declare it as a class field, e.g., labelText = 'Order by';. A class field over a reactive property hides the property, and the element then ignores later changes to the property, from an attribute or from code.
This search-sort subclass sets its label in the constructor. It marks the default option in _getSelectOptions(), copied from SearchSort with one change, to the option text: ${item.label}${item.default ? ' (default)' : ''}.
ts
import {html} from 'lit';
import {repeat} from 'lit/directives/repeat.js';
import {SearchSort} from 'drupal-search-web-components/dist/search-sort/search-sort.js';
class RecipeSort extends SearchSort {
constructor() {
super();
this.labelText = 'Order by';
}
override _getSelectOptions() {
const s = this.getOptionValue(this._getSelected());
return html`${repeat(
this.sorts,
(item) => this.getOptionValue(item),
(item) => {
const v = this.getOptionValue(item);
return html`<option value=${v} ?selected=${v === s}>
${item.label}${item.default ? ' (default)' : ''}
</option>`;
}
)}`;
}
}
customElements.define('recipe-sort', RecipeSort);Custom facet options
Focus after a pick
After the search, the facet puts focus back on the control a keyboard user used: the same option, or the first option after the reset button. The shipped focus logic finds the control by its markup, so a subclass that renders its own options keeps it when:
- each option is an
inputor abuttoninside anli, with the option'skeyin itsvalueattribute - the reset button is a
buttonwith the classreset.
A subclass with other markup, e.g., options rendered as links, overrides the two focus hooks instead: _getFocusedControlKey(control) returns a key for the control the visitor used, and _getFocusTargetAfterRender(key) returns the control to focus for that key once the facet has rendered. For where focus goes on each component, see Focus.
Applying a value
An option applies its value with applyFacet(this.urlAlias, value). urlAlias is the facet's URL alias, from settings.url_alias in the response. The URL alias names the facet in the page URL. urlAlias is a property, not an attribute. The facet's key attribute is its machine name, which is a different value. For the difference, see What "key" means in code.
Writing to the context
A component can change view state by publishing a changed context with updateContext(context). The root merges it:
resultDisplay,dialogOpen,dialogBreakpoint, andthirdPartySettingsare a component's to write.thirdPartySettingsholds a site's own state.loading,response, andresponseReadyfrom a component are ignored.queryfrom a component is merged, but only until the next search, which sets its ownquery.url,additionalParams,defaultPerPage, andupdateUrlare set from the root's attributes for every search, so a component's value lasts until the next search.
To change the URL state, ask for a search with getResults(query).
Custom result elements
search-results renders each result with the element its mappings name, from the mappings attribute or from swc_results in the response. For the shape of a mapping, see The mappings shape.
A mapping's keys hold values of the match field, which resultField names. For each result, the first key that matches is used. The keys are checked in this order:
| Checked | Key | Matches | Example |
|---|---|---|---|
| 1 | The result's value and the listing layout shown | Results with that value, when results are shown as that listing layout | recipe-grid |
| 2 | default and the listing layout shown | Results with any value, when results are shown as that listing layout | default-grid |
| 3 | The result's value | Results with that value | recipe |
| 4 | default | Results with any value | default |
A result with no matching mapping is not shown. The browser console logs a warning for it. A mapping whose element is not defined on the page shows nothing either.
The element gets the result as its data property and the mapping's settings as its settings property. The element can be a plain HTMLElement, as in the Quickstart, or a subclass of BaseSearchElement to read the search context too.
Here the search for curry shows recipes as recipe-card-result, a bordered card with the summary, and the article as title-link-result, a link. Switch to Grid to see every result as recipe-card-result: