diff --git a/README.md b/README.md index 4a42a24..4fb70c6 100644 --- a/README.md +++ b/README.md @@ -23,41 +23,54 @@ hard-to-follow callback chains. ```js // Filtering and mapping: -element - .on('click') - .filter((e) => e.target.matches('.foo')) - .map((e) => ({ x: e.clientX, y: e.clientY })) - .subscribe({ next: handleClickAtPoint }); +element.on('click') + .filter(e => e.target.matches('.foo')) + .map(e => ({x: e.clientX, y: e.clientY })) + .subscribe(({ x, y }) => { + console.log(`Clicked foo at ${x}, ${y}`) + })); +``` + +
+Imperative version + +```js +element.addEventListener('click', (e) => { + if (e.target.matches('.foo')) { + const { clientX: x, clientY: y } = e; + console.log(`Clicked foo at ${x}, ${y}`); + } +}); ``` +
+ #### Example 2 +Automatic, declarative unsubscription via the takeUntil method + ```js -// Automatic, declarative unsubscription via the takeUntil method: -element.on('mousemove') - .takeUntil(document.on('mouseup')) - .subscribe({next: e => … }); - -// Since reduce and some other terminators return promises, they also play -// well with async functions: -await element.on('mousemove') - .takeUntil(element.on('mouseup')) - .reduce((soFar, e) => …); +const ac = new AbortController(); +element + .on('mousemove') + .takeUntil(element.on('mouseup')) + .subscribe(console.log, { signal: ac.signal }); ```
Imperative version ```js -// Imperative -const controller = new AbortController(); +const ac = new AbortController(); element.addEventListener( 'mousemove', (e) => { - element.addEventListener('mouseup', (e) => controller.abort()); + element.addEventListener('mouseup', (e) => ac.abort(), { + signal: controller.signal, + }); console.log(e); }, - { signal: controller.signal }, + { signal: ac.signal }, ); ``` @@ -65,20 +78,33 @@ element.addEventListener( #### Example 3 -Tracking all link clicks within a container ([example](https://github.com/whatwg/dom/issues/544#issuecomment-351705380)): ```js +let linkClicks = 0; container .on('click') .filter((e) => e.target.closest('a')) - .subscribe({ - next: (e) => { - // … - }, + .subscribe(() => { + linkClicks++; }); ``` +
+Imperative version + +```js +let linkClicks = 0; +container.addEventListener('click', (e) => { + const found = e.target.closest('a'); + if (found?.href) { + linkClicks++; + } +}); +``` + +
+ #### Example 4 Find the maximum Y coordinate while the mouse is held down @@ -92,6 +118,31 @@ const maxY = await element .reduce((soFar, y) => Math.max(soFar, y), 0); ``` +
+Imperative version + +```js +const maxY = await new Promise((resolve) => { + let max = 0; + const mouseMoveHandler = (e) => { + max = Math.max(max, e.clientX); + }; + + element.addEventListener('mousemove', mouseMoveHandler); + + element.addEventListener( + 'mouseup', + () => { + element.removeEventListener('mousemove', mouseMoveHandler); + resolve(max); + }, + { once: true }, + ); +}); +``` + +
+ #### Example 5 Multiplexing a `WebSocket`, such that a subscription message is send on connection, @@ -239,10 +290,8 @@ keys } }) .filter((matched) => matched) - .subscribe({ - next: (_) => { - console.log('Secret code matched!'); - }, + .subscribe(() => { + console.log('Secret code matched!'); }); ``` @@ -273,6 +322,120 @@ document.addEventListener('keydown', e => {
+#### Example 7 + +When you mousedown on the document, it will start measuring how far your mouse moves, rendering a +line and some text for how far you've measured, and when you mouse up, will log the final +distance before removing the line. + +```js +const measurements = document.on('mousedown').flatMap((e) => { + const { clientX: startX, clientY: startY } = e; + + const svg = document.createElement('svg'); + svg.width = document.body.width; + svg.height = document.body.height; + svg.innerHTML = ` + + 0 + `; + const line = svg.querySelector('line'); + const text = svg.querySelector('text'); + document.body.appendChild(svg); + let dist = 0; + + return document + .on('mousemove') + .map((e) => { + const { clientX: endX, clientY: endY } = e; + const diffX = endX - startX; + const diffY = endY - startY; + const dist = Math.sqrt(diffX ** 2 + diffY ** 2); + return { endX, endY, dist }; + }) + .takeUntil(document.on('mouseup')) + .do(({ endX, endY, dist }) => { + line.x2 = endX; + line.y2 = endY; + text.textContent = dist; + }) + .reduce((_, { dist }) => dist, 0) + .finally(() => { + svg.remove(); + }); +}); + +const ac = new AbortController(); +measurements.subscribe( + (dist) => { + console.log(`Measured distance: ${dist}`); + }, + { signal: ac.signal }, +); + +// Tearing everything down later +ac.abort(); +``` + +
+Imperative version + +```js +const ac = new AbortController(); +document.addEventListener( + 'mousedown', + (e) => { + const { clientX: startX, clientY: startY } = e; + + const svg = document.createElement('svg'); + svg.width = document.body.width; + svg.height = document.body.height; + svg.innerHTML = ` + + 0 + `; + const line = svg.querySelector('line'); + const text = svg.querySelector('text'); + document.body.appendChild(svg); + let dist = 0; + + const mouseMoveHandler = (e) => { + const { clientX: endX, clientY: endY } = e; + const diffX = endX - startX; + const diffY = endY - startY; + dist = Math.sqrt(diffX ** 2 + diffY ** 2); + line.x2 = endX; + line.y2 = endY; + text.textContent = dist; + }; + + document.addEventListener('mousemove', mouseMoveHandler, { + signal: ac.signal, + }); + + document.addEventListener( + 'mouseup', + () => { + document.removeEventListener('mousemove', mouseMoveHandler); + console.log(`Measured distance: ${dist}`); + svg.remove(); + }, + { once: true, signal: ac.signal }, + ); + + ac.signal.addEventListener('abort', () => { + svg.remove(); + }); + }, + { signal: ac.signal }, +); + +// Tearing everything down later +ac.abort(); +``` + +
+ ### The `Observable` API Observables are first-class objects representing composable, repeated events. @@ -465,19 +628,20 @@ synchronously emits data _during_ subscription: ```js // An observable that synchronously emits unlimited data during subscription. -let observable = new Observable((subscriber) => { +const observable = new Observable((subscriber) => { let i = 0; while (true) { subscriber.next(i++); } }); -let controller = new AbortController(); -observable.subscribe({ - next: (data) => { +const controller = new AbortController(); +observable.subscribe( + (data) => { if (data > 100) controller.abort(); - }}, {signal: controller.signal}, -}); + }, + { signal: controller.signal }, +); ``` #### Teardown