Skip to content

Commit

Permalink
Updates the examples to reflect recent changes. Also ensures that we …
Browse files Browse the repository at this point in the history
…have imperative versions for each example.

Adds a new example that creates a measuring tool to measure distance on the page.
  • Loading branch information
benlesh committed Nov 29, 2023
1 parent 54008be commit 82be7a9
Showing 1 changed file with 198 additions and 34 deletions.
232 changes: 198 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,62 +23,88 @@ 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}`)
}));
```

<details>
<summary>Imperative version</summary>

```js
element.addEventListener('click', (e) => {
if (e.target.matches('.foo')) {
const { clientX: x, clientY: y } = e;
console.log(`Clicked foo at ${x}, ${y}`);
}
});
```

</details>

#### 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 });
```

<details>
<summary>Imperative version</summary>

```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 },
);
```

</details>

#### 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++;
});
```

<details>
<summary>Imperative version</summary>

```js
let linkClicks = 0;
container.addEventListener('click', (e) => {
const found = e.target.closest('a');
if (found?.href) {
linkClicks++;
}
});
```
</details>
#### Example 4
Find the maximum Y coordinate while the mouse is held down
Expand All @@ -92,6 +118,31 @@ const maxY = await element
.reduce((soFar, y) => Math.max(soFar, y), 0);
```
<details>
<summary>Imperative version</summary>
```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 },
);
});
```
</details>
#### Example 5
Multiplexing a `WebSocket`, such that a subscription message is send on connection,
Expand Down Expand Up @@ -239,10 +290,8 @@ keys
}
})
.filter((matched) => matched)
.subscribe({
next: (_) => {
console.log('Secret code matched!');
},
.subscribe(() => {
console.log('Secret code matched!');
});
```
Expand Down Expand Up @@ -273,6 +322,120 @@ document.addEventListener('keydown', e => {
</details>
#### 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 = `
<line x1="${startX}" y1="${startY}" x2="0" y2="0" style="stroke:black;"/>
<text x="0" y="0" fill="black"/>0</text>
`;
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();
```
<details>
<summary>Imperative version</summary>
```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 = `
<line x1="${startX}" y1="${startY}" x2="0" y2="0" style="stroke:black;"/>
<text x="0" y="0" fill="black"/>0</text>
`;
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();
```
</details>
### The `Observable` API
Observables are first-class objects representing composable, repeated events.
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 82be7a9

Please sign in to comment.