How do I conditionally buffer key input based on event in RxJs

∥☆過路亽.° 提交于 2019-12-08 14:22:40

You have a very common case to deal with, which is that of an event whose associated action depends on some control state (i.e. you have a basic state machine). Basically, if you consider those two control states : SIMPLE_KEY, COMBO, and those two events : keyup, and keyupTimer, then :

  • in the SIMPLE_KEY state
    • if the key pressed is o, then you switch to COMBO state
    • if not o, then we remain in same state, and pass the key downstream
    • if timer event, then we pass a null value to be filtered out downstream
  • in the COMBO state
    • we accumulate key pressed
    • if timer events, then we revert to SIMPLE_KEY state

So, to map your event to an action, you need to know the control state you are in. The operator scan allows you to decide how to process an event depending on an accumulated state.

It could be something like this (https://jsfiddle.net/cbhmxaas/):

function getKeyFromEvent(ev) {return ev.key}
function isKeyPressedIsO(ev) {return getKeyFromEvent(ev) === 'o'}

const KEY = 'key';
const TIMER = 'timer';
const COMBO = 'combo';
const SIMPLE_KEY = 'whatever';
const timeSpanInMs = 1000;
const keyup$ = Rx.Observable.fromEvent(document, 'keyup')
  .map(ev => ({event: KEY, data: getKeyFromEvent(ev)}));
const keyupTimer$ = keyup$.flatMapLatest(eventStruct => 
  Rx.Observable.of(eventStruct)
//    .tap(console.warn.bind(console, 'timer event'))
    .delay(timeSpanInMs)
    .map(() => ({event : TIMER, data: null}))
    );

Rx.Observable.merge(keyup$, keyupTimer$)
//  .tap(console.warn.bind(console))
  .scan((acc, eventStruct) => {
    if (acc.control === SIMPLE_KEY) {
      if (eventStruct.event === KEY) {
        if (eventStruct.data === `o`) {
          return {control: COMBO, keyOrKeys : []}
        }
        else {
          return {control: SIMPLE_KEY, keyOrKeys : eventStruct.data}
        }
      }
      else {
      // TIMER event
        return {control: SIMPLE_KEY, keyOrKeys : null}
      }
    }
    else {
      // we only have two states, so it is COMBO state here
      if (eventStruct.event === KEY) {
        return {control: COMBO, keyOrKeys : acc.keyOrKeys.concat([eventStruct.data])}
      }
      else {
        // this is a TIMER event, we only have two events
        return {control: SIMPLE_KEY, keyOrKeys : acc.keyOrKeys}
      }
    }
  }, {control: SIMPLE_KEY, keyOrKeys : null})
//  .tap(console.warn.bind(console))
  .filter(state => state.keyOrKeys) // TIMER event in SIMPLE_KEY state
  .map (({control, keyOrKeys}) => {
  // Here you associate your action
  // if keyOrKeys is an array, then you have a combo
  // else you have a single key
  console.log('key(s) pressed', keyOrKeys)
  return keyOrKeys
})
  .subscribe (console.log.bind(console))
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!