モジュールの公開 I/F に Bacon.Bus 便利っぽい
Posted: Updated:
続・Bacon.js
ある処理を連結した EventStream に外から値を流し込んだり、動作の起点になるユーザーアクション(event)をつなぎたいときの話。全体からの抜粋コードで説明してるので、雑な感じなのはご容赦願いたい。
Before
スライドのコントロールをしてくれるモジュールで、適当な EventStream や Property を公開していたのですが肝心のスライドを next, prev させるユーザー操作を外に出せていなかった。
// スライドのページ送りとかをしてくれる // 公開している EventStream は現在のページ番号表示とかを更新するためのもの export default function(options) { let right = control.key('right'); let left = control.key('left'); right = right.merge(control.click(options.nextButton)); left = left.merge(control.click(options.prevButton)); let next = right.map(1); let prev = left.map(-1); let initialPage = options.startPage || 1; let correctPage = util.compose(inRangeOf(1, options.endPage), add); let both = next.merge(prev); let current = both.scan(initialPage, correctPage).skipDuplicates(); Bacon.combineAsArray(current, options.slideElements).onValue(function(data) { let [current, all] = data; all.forEach(toInvisible); toVisible(all[current - 1 /* fix page to index */]); }); return { current : current, start : current.filter((v) => v === 1), end : current.filter((v) => v === options.endPage), onNext : next, onPrev : prev }; }
というのも Bacon.fromEventTarget
で作成したユーザーアクションに紐付く EventStream に直接それ以降の処理をつないでいたから。これだと、ユーザーアクションの EventStream ありきでしか書けない。
After
ということで、次のように new Bacon.Bus()
で生成した bus オブジェクトを公開するように修正した。
export default function(options) { let nextBus = new Bacon.Bus(); let prevBus = new Bacon.Bus(); let nextEs = nextBus.map(1); let prevEs = prevBus.map(-1); let initialPage = options.startPage || 1; let correctPage = util.compose(inRangeOf(1, options.endPage), add); let bothEs = nextEs.merge(prevEs); let current = bothEs.scan(initialPage, correctPage).skipDuplicates(); Bacon.combineAsArray(current, options.slideElements) .onValue(function(data) { let [current, all] = data; all.forEach(toInvisible); toVisible(all[current - 1 /* fix page to index */]); }); return { currentEs : current, nextBus : nextBus, prevBus : prevBus }; }
bus は外から別のイベントストリームをつないだり ( bus.plug(eventStream)
) 、任意の値をストリームに流す ( bus.push(value)
) ことができる。
Usage
paging
モジュールが公開している nextBus
と prevBus
に、キーボード操作の EventStream を後から bus.plug()
している。こうしておけば他のキーやUIのクリックなどを後付けするのも容易だ。
import Paging from './paging'; import control from './control'; let paging = Paging({ startPage : params.startPage || 1, endPage : slides.length, slideElements : toArray(document.querySelectorAll(`.slide`)) }); paging.nextBus.plug(control.key('right')); paging.prevBus.plug(control.key('left')); paging.nextBus.plug(control.click(document.getElementById('next'))); paging.prevBus.plug(control.click(document.getElementById('prev')));
補足しておくと、ここで併用している control
モジュールは次のような感じ。
'use strict'; import Bacon from 'baconjs'; import keycode from 'keycode'; const EVENT_KEYUP = Bacon.fromEventTarget(document, 'keyup'); /** * create EventStream from user input */ export default { /** * @param {String|Number} charKey * @returns {EventStream} */ key(charKey) { let keyCode = typeof(charKey) === 'string' ? keycode(charKey) : charKey; return EVENT_KEYUP.filter(keyCodeIs(keyCode)); }, /** * @param {Element} el * @returns {EventStream} */ click(el) { return Bacon.fromEventTarget(el, 'click'); } }; /** * @param {Number} keyCode * @returns {Function} */ function keyCodeIs(keyCode) { return function(event) { return event.keyCode === keyCode; }; }
Bacon.Bus
Bus is an EventStream that allows you to push values into the stream. It also allows plugging other streams into the Bus. The Bus practically merges all plugged-in streams and the values pushed using the push method. Bacon.js - API reference
とのことであります。
Ajax とか他の非同期処理の結果をあとから bus.push()
して EventStream を流す、とかにも使える感じなのでマジメに使おうと思うと必須っぽい。