Reputation: 144
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?
Upvotes: 5
Views: 1200
Reputation: 8618
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:
The answer to both questions can be provided by a single stream of DOM elements.
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.
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.
Upvotes: 6
Reputation: 10857
You can tailor for your own situation, but this should illustrate how to solve your problem. Let's assume you have a text input and a button. When the button is clicked, you want the focus to remain on the text input.
First write the intent() function:
function intent(DOMSource) {
const textStream$ = DOMSource.select('#input-msg').events('keyup').map(e => e.target);
const buttonClick$ = DOMSource.select('#send-btn').events('click').map(e => e.target);
return buttonClick$.withLatestFrom(textStream$, (buttonClick, textStream) => {
return textStream;
});
}
Then the main which has a sink to handle the lost focus side effect
function main(sources) {
const textStream$ = intent(sources.DOM);
const sink = {
DOM: view(sources.DOM),
EffectLostFocus: textStream$,
}
return sink;
}
Then the driver to handle this side effect would look something like
Cycle.run(main, {
DOM: makeDOMDriver('#app'),
EffectLostFocus: function(textStream$) {
textStream$.subscribe((textStream) => {
console.log(textStream.value);
textStream.focus();
textStream.value = '';
})
}
});
The entire example is in this codepen.
Upvotes: 5
Reputation: 144
Here's one example, written by Mr. Staltz himself: https://github.com/cyclejs/cycle-examples/blob/master/autocomplete-search/src/main.js#L298
Upvotes: 2