JSFiddle - React, Tailwind, and code Playground
by alexb
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.3.3/rxjs.umd.min.js"></script>
<div id="list">
<div id="controls">
<button id="add">Add</button>
<button id="clear">Clear</button>
</div>
<div id="items"></div>
</div>
<div id="trackpad">
<div id="coords"></div>
Click to set coordinates
</div>
<div id="log">
<div>Both mutate the list and set coordinates at least once to start logging</div>
</div>
CSS
* { box-sizing: border-box; }
body { display: flex; flex-flow: column; height: 100vh; }
body > * { flex: 1 0 0; }
#list { display: flex; align-items: center; padding: 0 2em; }
#controls { display: flex; flex-flow: column }
#items { display: flex; }
#items > * { background: #693; width: 2em; height: 2em; margin-left: 1em; line-height: 2; text-align: center; }
#trackpad {
position: relative;
display: flex;
align-items: center;
justify-content: center;
background: rebeccapurple;
color: white;}
#coords { position: absolute; top: 0; left: 0; }
#log { flex-grow: 2; background: lightgray; overflow-y: scroll; font-family: monospace; }
#log > * { margin: 0.5em }
JavaScript
// This stuff could also be implemented as RxJS streams.
add.addEventListener('click', e => {
const el = document.createElement('div')
el.innerText = Math.ceil(Math.random() * 100);
items.appendChild(el);
});
clear.addEventListener('click', e => { items.innerHTML = ''; });
trackpad.addEventListener('mousemove', e => { coords.innerText = `${e.x}, ${e.y}`; })
// RxJS has a built-in way to create a stream from MutationObserver in
// the rxjs-dom package, but I can't get that to import to JSFiddle.
function createMutationStream(target, options) {
return rxjs.Observable.create(rxjsObserver => {
const domMutObs = new MutationObserver(mutations => {
rxjsObserver.next(mutations);
});
domMutObs.observe(target, options);
return () => domMutObs.disconnect();
});
}
// Helper function.
function describeMutations(mutations) {
return mutations
.map(mut =>
mut.addedNodes.length ?
'added ' + [...mut.addedNodes].map(el => el.innerText).toString() :
'cleared')
.join(', ');
}
// Create two discrete streams of events.
const click$ = rxjs.fromEvent(trackpad, 'click');
const listUpdate$ = createMutationStream(items, { childList: true });
// Use the combineLatest operator to create a stream of the LATEST
// values from each stream when EITHER stream emits something.
rxjs.combineLatest(click$, listUpdate$).subscribe(([mouse, mutations]) => {
log.innerHTML += `<div>
last click: ${mouse.x}, ${mouse.y},
last mutation: ${describeMutations(mutations)}
</div>`;
log.scrollTop = log.scrollHeight;
});