Search code examples
javascriptfunctional-programmingfocusrxjscyclejs

How do I focus an input with Cycle.js and RxJS?


How do I focus an input with Cycle? Do I need to reach inside the DOM and call .focus() either with or without jQuery, or is there some other way with Cycle/RxJS?


Solution

  • Yes, you do need to reach inside the DOM and call .focus() either with or without jQuery. However this is a side-effect and it is Cycle.js convention to move these kinds of side effects to a so-called driver.

    The two questions the driver needs to know are:

    • which element do you want to focus?
    • when do you want to focus the element?

    The answer to both questions can be provided by a single stream of DOM elements.

    Create the driver

    First make your driver. Let's call it SetFocus. We'll make it a so-called read-only driver. It will read from the app's sinks but it will not provide a source to the app. Because it is reading, the driver's function will need to accept a formal parameter that will be a stream, call it elem$:

    function makeSetFocusDriver() {
      function SetFocusDriver(elem$) {
        elem$.subscribe(elem => {
          elem.focus();
        });
      }
      return SetFocusDriver;
    }
    

    This driver takes whatever DOM element arrives in the stream and calls .focus() on it.

    Use the Driver

    Add it to the list of drivers provided to the Cycle.run function:

    Cycle.run(main, {
      DOM: makeDOMDriver('#app'),
      SetFocus: makeSetFocusDriver() // add a driver
    });
    

    Then in your main function:

    function main({DOM}) {
    
      // setup some code to produce the elem$ stream
      // that will be read by the driver ...
      // [1]: say _when_ we want to focus, perhaps we need to focus when
      //      the user clicked somewhere, or maybe when some model value
      //      has changed
      // [2]: say _what_ we want to focus
      //      provide the textbox dom element as actual value to the stream
      //      the result is:
      //      |----o-----o-----o--->
      //      where each o indicates we want to focus the textfield
      //      with the class 'field'
      const textbox$ = DOM.select('.field').observable.flatMap(x => x); // [2]
      const focusNeeded = [
        clickingSomewhere$,    // [1]
        someKindofStateChange$ // [1]
      ];
      const focus$ = Observable.merge(...focusNeeded)
        .withLatestFrom(textbox$, (_, textbox) => textbox); // [2]
    
      // ...
    
      // [*]: Add driver to sinks, the driver reads from sinks.
      //      Cycle.js will call your driver function with the parameter
      //      `elem$` being supplied with the argument of `focus$`
      return {
        DOM: vtree$,
        SetFocus: focus$, // [*]
      };
    }
    

    You can then configure focusNeeded to say when you want .field to be focused.