モジュールの公開 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 を流す、とかにも使える感じなのでマジメに使おうと思うと必須っぽい。