Pages
    • GETTING STARTED
          • SPECIFICATIONS
                    • RESOURCES
                          • LEARN
                              On this page

                                      The State API

                                      The State API is a DOM API that lets us maintain application state at the document level and at individual element levels. It brings application state closer to the UI and makes it easy to keep the UI in sync with all the changes taking place.

                                      OOHTML is being proposed as a native browser technology while currently available through a polyfill. Be sure to check the Polyfill Support section below for the features on this page.

                                      API

                                      This API exposes a document-level state object on a document.state property, and an element-level state object on an element.state property. Arbitrary values can be set and retrieved on state objects the same way we would with regular objects.

                                      Document-Level State

                                      Document-level state represents the global state of an application.

                                      • document.state: Object - This readonly property exposes a state object whose properties can be written to and read from - from any part of the page.

                                        // Assign properties
                                        document.state.pageTitle = 'Hello World!';
                                        // Access properties
                                        let pageTitle = document.state.pageTitle; // Hello World!
                                      • document.setState(state[, params]): Void - This method provides a programmatic way to set data on the document.state property. It lets us set multiple properties in a call, and gives us control over state mutation.

                                        Parameters:

                                        • state: Object - The object to set as state or whose properties to update existing state with.
                                        • params: Object - (Optional) Parameters for controlling state mutation:
                                          • update: Boolean - Specifies whether to simply update properties of existing state or to establish the given object as new state. Default: false - establish object as new state.
                                        // Set object as state
                                        document.setState({
                                            pageTitle: 'Hello World!',
                                        });
                                        // Access properties
                                        let pageTitle = document.state.pageTitle; // Hello World!
                                        
                                        // ----------
                                        
                                        // Update existing state object
                                        document.setState({
                                            pageContent: {
                                                main: 'Thanks for visiting.',
                                                aside: '',
                                            },
                                        }, {update: true});
                                        // Access properties
                                        let pageTitle = document.state.pageTitle; // Hello World!
                                        let pageContent = document.state.pageContent; // {main, aside}
                                        
                                        // ----------
                                        
                                        // Set new state object
                                        document.setState({
                                            pageTitle: 'Bonjour le Monde!',
                                        });
                                        // Access properties
                                        let pageTitle = document.state.pageTitle; // Bonjour le Monde!
                                        let pageContent = document.state.pageContent; // undefined
                                      • document.clearState(): Void - This method provides a programmatic way to clear existing data from the document's state object.

                                        // Clear existing data
                                        document.clearState();

                                      Element-Level State

                                      Element-level state represents the local state of an element.

                                      • Element.prototype.state: Object - This readonly property exposes a state object for the element whose properties can written to and read as-is.

                                        // Assign properties
                                        myCollapsible.state.collapsed = true;
                                        // Access properties
                                        let isCollapsed = myCollapsible.state.collapsed; // true
                                        
                                        // -------
                                        
                                        // Example usage - a simple toggle
                                        myCollapsible.onclick = () => {
                                            if (myCollapsible.state.collapsed) {
                                                myCollapsible.style.height = 'auto';
                                            } else {
                                                myCollapsible.style.height = '0px';
                                            }
                                            myCollapsible.state.collapsed = !myCollapsible.state.collapsed;
                                        };
                                      • Element.prototype.setState(state[, params]): Void - This method provides a programmatic way to set data on an element's .state property. It lets us set multiple properties in a call, and gives us control over state mutation.

                                        Parameters:

                                        • state: Object - The object to set as state or whose properties to update existing state with.
                                        • params: Object - (Optional) Parameters for controlling state mutation:
                                          • update: Boolean - Specifies whether to simply update properties of existing state or to establish the given object as new state. Default: false - establish object as new state.
                                        // Set object as state
                                        myCollapsible.setState({
                                            collapsed: false,
                                        });
                                        // Access properties
                                        let isCollapsed = myCollapsible.state.collapsed; // false
                                        
                                        // ----------
                                        
                                        // Update existing state object
                                        myCollapsible.setState({
                                            inView: true,
                                        }, {update: true});
                                        // Access properties
                                        let isCollapsed = myCollapsible.state.collapsed; // false
                                        let inView = myCollapsible.state.inView; // false
                                        
                                        // ----------
                                        
                                        // Set new state object
                                        myCollapsible.setState({
                                            collapsed: true,
                                        });
                                        // Access properties
                                        let isCollapsed = myCollapsible.state.collapsed; // true
                                        let inView = myCollapsible.state.inView; // undefined
                                      • Element.prototype.clearState(): Void - This method provides a programmatic way to clear existing data from an element's state object.

                                        // Clear existing data
                                        myCollapsible.clearState();

                                      State Observability

                                      State objects are a special kind of objects in that they support observability. The document.state property and the Element.prototype.state property are implemented as live objects that can be observed for property changes using the Observer API.

                                      // Obtain the Observer API and use the Observer.observe() method
                                      Observer.observe(document.state, events => {
                                          events.forEach(e => {
                                              console.log(e.type, e.name, e.path, e.value);
                                          });
                                      });

                                      We could as well specify just the path to observe on the function's second parameter.

                                      Observer.observe(document.state, 'pageTitle', e => {
                                          console.log(e.type, e.name, e.path, e.value);
                                      });

                                      With the code above, adding or updating the pageTitle property on the document's state object would be reported in the console.

                                      document.state.pageTitle = 'Bonjour!';

                                      Deleting this property would trigger our observer in the same way.

                                      delete document.state.pageTitle;

                                      To observe changes down the state tree, we would set the observer's params.subtree to true.

                                      Observer.observe(document.state, events => {
                                          events.forEach(e => {
                                              console.log(e.type, e.name, e.path/*watch this*/, e.value);
                                          });
                                      }, {subtree: true});

                                      We could as well specify just the path to observe.

                                      Observer.observe(document.state, ['pageContent', 'aside'], e => {
                                          console.log(e.type, e.name, e.path, e.value);
                                      });

                                      With the code above, mutating a nested property would trigger the observer.

                                      // State object
                                      let data = {
                                          pageTitle: 'Hello World!',
                                          pageContent: {
                                              main: 'Thanks for visiting.',
                                              aside: '',
                                          },
                                      };
                                      document.setState(data);
                                      
                                      // Mutate pageContent afterwards...
                                      Observer.set(data.pageContent, 'aside', 'Related content...');

                                      A Custom Element Example

                                      The following example demonstrates state observability in a custom element. Our logic below helps keep the UI and application state in sync. Noteworthy is that we are reflecting the collapsed state in the data-collapsed attribute and keeping a part of that state - content - bound to a descendant element.

                                      customElements.define('my-collapsible', class extends HTMLElement {
                                      
                                          /**
                                           * Creates the Shadow DOM
                                           */
                                          constructor() {
                                              super();
                                              let contentElement = this.querySelector('.content');
                                      
                                              // Observe state and get the UI synced
                                              Observer.observe(this.state, events => {
                                                  events.forEach(event => {
                                                      switch(event.name) {
                                                          case 'collapsed':
                                                              this.style.height = event.value ? '0px' : 'auto';
                                                              this.setAttribute('data-active', event.value ? 'true' : 'false');
                                                          break;
                                                          case 'inView':
                                                              this.style.animation = event.value ? 'fadein 440ms' : 'fadeout 440ms';
                                                          break;
                                                          case 'content':
                                                              contentElement.setState(event.value);
                                                          break;
                                                      }
                                                  });
                                              });
                                      
                                              // Implement the logic for toggling collapsion
                                              this.addEventListener('click', function(event) {
                                                  this.state.collapsed = !this.state.collapsed;
                                              });
                                      
                                              // Implement the logic for detecting when in view
                                              let io = new IntersectionObserver(function(entries) {
                                                  if (entries[0].isIntersecting) {
                                                      this.state.inView = entries[0].intersectionRatio;
                                                  }
                                              });
                                              io.observe(this);
                                          }
                                      
                                      });

                                      External code gets a standard way to infer the state of the <my-collapsible> element.

                                      let myCollapsible = document.querySelector('my-collapsible');
                                      let isCollapsed = myCollapsible.state.collapsed;
                                      let inView = myCollapsible.state.inView;

                                      External code gets a standard way to control state.

                                      myCollapsible.state.collapsed = true;

                                      Polyfill Support

                                      The current OOHTML polyfill implementation has full support for The State API. The polyfill additionally makes it possible to customise following areas of its implementation of the API using the OOHTML META tag:

                                      • api.state - The property name for exposing the state object on DOM elements and the document object. The standard property name is state, but you may use a custom property name, where necessary.

                                        <head>
                                            <meta name="oohtml" content="api.state=stateObject;" />
                                        </head>
                                        myCollapsible.stateObject.collapsed = true;
                                      • api.setState - The method name for setting data on state objects. The standard method name is setState, but you may use a custom method name, where necessary.

                                        <head>
                                            <meta name="oohtml" content="api.setState=setStateObject;" />
                                        </head>
                                        document.setStateObject(data);
                                      • api.clearState - The method name for clearing data from state objects. The standard method name is clearState, but you may use a custom method name, where necessary.

                                        <head>
                                            <meta name="oohtml" content="api.clearState=clearStateObject;" />
                                        </head>
                                        document.clearStateObject();

                                      Learn more about customization and the OOHTML meta tag here.