Step 8 - State and QueryStateModel - Interacting With Component State and With the URL

The Coveo JavaScript Search Framework stores its state within a centralized location using a key-value set. This state is updated every time a component affecting the query and its results is modified, such as when the end user changes the query in the query box or selects a facet value. State changes trigger events that can be monitored by external code. Components also monitor the state in order to update themselves as needed.

Optionally, the state can be persisted in the URL string to allow for browser history management.

In this step of the tutorial, you will learn how to use the state top level function to access the QueryStateModel object. You will also learn about state attributes and state events. Finally, you will learn how to use the URL to represent states.

The Key-Value Store

The QueryStateModel (or state) object contains several key-value pairs representing different component states. Components (as well as any custom code) can interact with this key-value store, essentially calling its getter and setter methods.

Whenever a setter is called, a chain of events is triggered.

Any component (or external code) listening to those events is notified of the new state value and can react accordingly.

Use the Key-Value Store Getter and Setter

Reference documentation on the state top level function explains how you can get and set values in the QueryStateModel object using that function.

This example shows how to get a value:

// `root` is the HTMLElement containing the root of the search interface.
// This could be any element. In this case, it is the body of the page.
var root = document.body;
// `currentPage` represents the state which the `Pager` component is to interact with.
// It contains the value of the current result page, if a `Pager` component is available in the search interface.
var currentPage = Coveo.state(root, 'first');
// `q` represents the state which the `Searchbox` component is to interact with.
// It contains the value of the search box, if a `Searchbox` component is available in the search interface.
var q = Coveo.state(root, 'q');

This example shows how to set a value:

var root = document.body;
// If there is a `Tab` component whose `data-id` is 'foobar' in the search interface, this tab will activate and be selected.
Coveo.state(root, 'tab', 'foobar');
// If there is a `Searchbox` component in the search interface, it will update its content to 'foobar'.
Coveo.state(root, 'q' , 'foobar');

Get a Reference to the QueryStateModel Object

You can get a reference to the QueryStateModel object thus:

var root = document.body;
// queryState will reference the QueryStateModel instance described in the reference documentation.
// See https://coveo.github.io/search-ui/classes/querystatemodel.html
var queryState = Coveo.state(root);

Find All Available Attributes in the Key-Value Store

This example shows you how to get all currently available attributes in the state.

var root = document.body;
// allAttributes will be the internal object that the state object uses.
// This object contains a complete list of all possible attributes as well as their current values.
var allAttributes = Coveo.state(root).attributes;

Getting all available attributes and their values is rarely useful. On the other hand, what can be useful is knowing which attributes are currently active.

An active attribute is a key-value pair for which the value is not in its default state.

If the search box contains the empty string (""), then the q attribute is not considered active.

However, if the search box contains a non-empty string such as "foobar", then it is considered active.

You can get active attributes by using the getAttributes method as shown in the example below:

var root = document.body;
var allActiveAttribute = Coveo.state(root).getAttributes();

Common State Attributes

The following table describes state attributes which are commonly used in typical search interfaces. Most of these states are bound to specific components.

Component Attribute Name Description
Querybox q The current query in the query box.
Pager first The index of the first result to display.
Tab t The name of the active tab.
Sort sort The currently active sort order.
Facet f:@id

The list of selected values in facet @id.

If you do not specify an id option on a Facet component, then the @id parameter will be set to the specified field option.

<!-- In this case, the @id parameter of the f:@id attribute will be @sysauthor. -->
<div class='CoveoFacet' data-field='@sysauthor'></div>
<!-- Here, the @id parameter of the f:@id attribute will be @MyFacet. -->
<div class='CoveoFacet' data-field='@sysauthor' data-id='MyFacet'></div>
Facet f:@id:not The list of excluded values in facet @id.
Facet f:@id:operator The operator used for facet @id.

State Events

Whenever a value changes in the key-value store, the QueryStateModel object will trigger various JavaScript events. Components will generally bind one or many handlers to those events. External code can also bind handlers to those events (see Step 3 - Understanding the Event System).

The following steps describe a typical scenario:

  1. The current content of the state object for the q attribute is the empty string (""); the search box is empty.
  2. The end user types "foobar" in the search box and presses Enter.
  3. The Searchbox component is in charge of updating the state. Roughly speaking, it executes Coveo.state(root, 'q', 'foobar').
  4. The QueryStateModel stores the new value ("foobar") and compares it to the preceding value (""). Since the value has effectively changed, an event needs to be triggered.
  5. The QueryStateModel triggers the state:change:q event on the root of the interface.

If other components or external code are listening to the state change event, then those listeners will be notified and be able to react accordingly. The Searchbox component is also in charge of listening to those events so that it can update itself correctly.

Now, consider the following (reverse) scenario:

  1. The current content of the state object for the q attribute is the empty string (""); the search box is empty.
  2. An external piece of custom code calls Coveo.state(root, 'q', 'foobar').
  3. The QueryStateModel stores the new value ("foobar") and compares it to the preceding value (""). Since the value has effectively changed, an event needs to be triggered.
  4. The QueryStateModel triggers the state:change:q event on the root of the interface.

When it is initialized, the Searchbox component always registers a handler on the state:change:q event. Consequently, it is notified and can update itself accordingly when the content of q is modified.

Name of Stage Change Events

The names of stage change events are always formed with the same rule: state:change: followed by the attribute name (e.g., state:change:t).

Examples of State Event Binding

Binding a handler to a state change event can be done just as explained in the third step of this tutorial (see Step 3 - Understanding the Event System).

All handlers will receive the attribute that was changed, as well as its new value.

The following examples show how to bind state event handlers.

  • Ex 1:

      <script>
        document.addEventListener('DOMContentLoaded', function() {
          Coveo.SearchEndpoint.configureSampleEndpoint();
          var root = document.body;
          // Whenever the Searchbox component content changes, either after the Enter key or the search button is pressed, 
          // this handler will print the new searchbox content in the console.
          Coveo.$$(root).on('state:change:q', function(e, args) {
            console.log('The new value of ' + args.attribute + ' is now : ' + args.value);
          });
          Coveo.init(document.body);
        })
      </script>
    
  • Ex 2:

      <script>
        document.addEventListener('DOMContentLoaded', function() {
          Coveo.SearchEndpoint.configureSampleEndpoint();
          var root = document.body;
          // Whenever the filetype Facet component selected values change,
          // this handler will print the currently selected values in the console.
          Coveo.$$(root).on('state:change:f:@filetype', function(e, args) {
            console.log('The new value of ' + args.attribute + ' is now : ' + args.value.join(','));
          });
          Coveo.init(document.body);
        })
      </script>
    

URL as State Representation

Every time you submit a new query, or change a facet selection, the fragment identifier (or hash) portion of the URL is updated (see Fragment identifier).

This feature is enabled with an option in the SearchInterface itself, as shown below.

<body class='CoveoSearchInterface' data-enable-history="true">
</body>

If you set the enableHistory option to true, the SearchInterface will automatically use the URL to store and restore its state.

You can therefore expect the following scenarios to work if you set enableHistory to true:

  • Back and forward navigation:
    • Load a web page with a working search interface. The search box is empty.
    • Change the content of the search box to "foo", and execute a query. The URL gets updated.
    • Hit the back button in your web browser. The content of the search box is updated to the empty string again. A new query is executed with the empty string.
    • Hit the forward button in your web browser. The content of the search box is updated to "foo".
  • Initial page load:
    • Load a web page with a working search interface. Make sure that the hash portion of the URL contains the value q=foo (i.e., localhost:8080/#q=foo).
    • The initial state is applied before the first query occurs. This means that when the interface fully loads after the first query, you will see results for the "foo" query, and the search box will contain the keyword "foo".

While these scenarios only mention q as a parameter, the same logic also applies to other components such as Facet, Pager, Tab, and Sort.

Knowing this, you can use this feature to preselect certain Facet values or a different Tab when your search interface is loaded.

For example, you might want to force all links pointing to a specific search interface to automatically narrow down the results by including a certain facet selection in the URL.

What’s Next?

You should now proceed to Step 9 - Advanced Integration With Custom TypeScript Component - Configuration.