Skip to content

Commit a478ceb

Browse files
committed
Prepare chapter 21 for editing
1 parent 008c448 commit a478ceb

File tree

2 files changed

+32
-42
lines changed

2 files changed

+32
-42
lines changed

21_skillsharing.md

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@ quote}}
1414

1515
A _((skill-sharing))_ meeting is an event where people with a shared interest come together and give small, informal presentations about things they know. At a ((gardening)) skill-sharing meeting, someone might explain how to cultivate ((celery)). Or in a programming skill-sharing group, you could drop by and tell people about Node.js.
1616

17-
{{index learning, "users' group"}}
18-
19-
Such meetups—also often called _users' groups_ when they are about computers—can be a great way to learn things, or simply meet people with similar interests. Many larger cities have JavaScript meetups. They are typically free to attend, and I've found the ones I've visited to be friendly and welcoming.
20-
2117
In this final project chapter, our goal is to set up a ((website)) for managing ((talk))s given at a skill-sharing meeting. Imagine a small group of people meeting up regularly in the office of one of the members to talk about ((unicycling)). The previous organizer of the meetings moved to another town, and nobody stepped forward to take over this task. We want a system that will let the participants propose and discuss talks among themselves, without an active organizer.
2218

2319
[Just like in the [previous chapter](node), some of the code in this chapter is written for Node.js, and running it directly in the HTML page that you are looking at is unlikely to work.]{if interactive} The full code for the project can be ((download))ed from [_https://eloquentjavascript.net/code/skillsharing.zip_](https://eloquentjavascript.net/code/skillsharing.zip).
@@ -52,7 +48,7 @@ We can arrange for the client to open the connection and keep it around so that
5248

5349
{{index socket}}
5450

55-
But an ((HTTP)) request allows only a simple flow of information: the client sends a request, the server comes back with a single response, and that is it. There is a technology called _((WebSockets))_, supported by modern browsers, that makes it possible to open ((connection))s for arbitrary data exchange. But using them properly is somewhat tricky.
51+
But an ((HTTP)) request allows only a simple flow of information: the client sends a request, the server comes back with a single response, and that is it. There is a technology called _((WebSockets))_ that makes it possible to open ((connection))s for arbitrary data exchange. But using them properly is somewhat tricky.
5652

5753
In this chapter, we use a simpler technique—((long polling))—where clients continuously ask the server for new information using regular HTTP requests, and the server stalls its answer when it has nothing new to report.
5854

@@ -193,41 +189,33 @@ export class Router {
193189
add(method, url, handler) {
194190
this.routes.push({method, url, handler});
195191
}
196-
}
197-
198-
async function resolveRequest(router, context, request) {
199-
let path = parse(request.url).pathname;
200-
201-
for (let {method, url, handler} of this.routes) {
202-
let match = url.exec(path);
203-
if (!match || request.method != method) continue;
204-
let urlParts = match.slice(1).map(decodeURIComponent);
205-
let response =
206-
await handler(context, ...urlParts, request);
207-
if (response) return response;
192+
async resolve(request, context) {
193+
let path = parse(request.url).pathname;
194+
for (let {method, url, handler} of this.routes) {
195+
let match = url.exec(path);
196+
if (!match || request.method != method) continue;
197+
let parts = match.slice(1).map(decodeURIComponent);
198+
return handler(context, ...parts, request);
199+
}
208200
}
209201
}
210202
```
211203

212204
{{index "Router class"}}
213205

214-
The module exports the `Router` class. A router object allows new handlers to be registered with the `add` method and can resolve requests with its `resolve` method.
215-
216-
{{index "some method"}}
217-
218-
The latter will return a response when a handler was found, and `null` otherwise. It tries the routes one at a time (in the order in which they were defined) until a matching one is found.
206+
The module exports the `Router` class. A router object allows you to register handlers for specific methods and URL patterns with its `add` method. When a request is resolved with the `resolve` method, the router calls the handler whose method and URL match the request and return its result.
219207

220208
{{index "capture group", "decodeURIComponent function", [escaping, "in URLs"]}}
221209

222-
The handler functions are called with the `context` value (which will be the server instance in our case), match strings for any groups they defined in their ((regular expression)), and the request object. The strings have to be URL-decoded since the raw URL may contain `%20`-style codes.
210+
Handler functions are called with the `context` value given to `resolve`. We will use this to give them access to our server state. Additionally, they receive the match strings for any groups they defined in their ((regular expression)), and the request object. The strings have to be URL-decoded since the raw URL may contain `%20`-style codes.
223211

224212
### Serving files
225213

226214
When a request matches none of the request types defined in our router, the server must interpret it as a request for a file in the `public` directory. It would be possible to use the file server defined in [Chapter ?](node#file_server) to serve such files, but we neither need nor want to support `PUT` and `DELETE` requests on files, and we would like to have advanced features such as support for caching. So let's use a solid, well-tested ((static file)) server from ((NPM)) instead.
227215

228216
{{index "createServer function", "serve-static package"}}
229217

230-
I opted for `serve-static`. This isn't the only such server on NPM, but it works well and fits our purposes. The `serve-static` package exports a function that can be called with a root directory to produce a request handler function. The handler function accepts the `request` and `response` arguments provided by the server, and a third argument, a function that it will call if no file matches the request. We want our server to first check for requests that we should handle specially, as defined in the router, so we wrap it in another function.
218+
I opted for `serve-static`. This isn't the only such server on NPM, but it works well and fits our purposes. The `serve-static` package exports a function that can be called with a root directory to produce a request handler function. The handler function accepts the `request` and `response` arguments provided by the server from `"node:http"`, and a third argument, a function that it will call if no file matches the request. We want our server to first check for requests that we should handle specially, as defined in the router, so we wrap it in another function.
231219

232220
```{includeCode: ">code/skillsharing/skillsharing_server.mjs"}
233221
import {createServer} from "node:http";
@@ -262,28 +250,32 @@ class SkillShareServer {
262250

263251
The `serveFromRouter` function has the same interface as `fileServer`, taking `(request, response, next)` arguments. This allows us to “chain” several request handlers, allowing each to either handle the request, or pass responsibility for that on to the next handler. The final handler, `notFound`, simply responds with a “not found” error.
264252

265-
Our `serveFromRouter` function uses a similar convention as the file server from the [previous chapter](node) for responses—handler in the router return promises that resolve to objects describing the response.
253+
Our `serveFromRouter` function uses a similar convention as the file server from the [previous chapter](node) for responses—handlers in the router return promises that resolve to objects describing the response.
266254

267255
```{includeCode: ">code/skillsharing/skillsharing_server.mjs"}
268256
import {Router} from "./router.mjs";
269257
270258
const router = new Router();
271259
const defaultHeaders = {"Content-Type": "text/plain"};
272260
273-
async function serveResponse(value, response) {
274-
let {body, status = 200, headers = defaultHeaders} =
275-
await resolved.catch(error => {
261+
async function serveFromRouter(server, request,
262+
response, next) {
263+
let resolved = await router.resolve(request, server)
264+
.catch(error => {
276265
if (error.status != null) return error;
277-
return {body: String(error), status: 500};
266+
return {body: String(err), status: 500};
278267
});
268+
if (!resolved) return next();
269+
let {body, status = 200, headers = defaultHeaders} =
270+
await resolved;
279271
response.writeHead(status, headers);
280272
response.end(body);
281273
}
282274
```
283275

284276
### Talks as resources
285277

286-
The ((talk))s that have been proposed are stored in the `talks` property of the server, an object whose property names are the talk titles. These will be exposed as HTTP ((resource))s under `/talks/[title]`, so we need to add handlers to our router that implement the various methods that clients can use to work with them.
278+
The ((talk))s that have been proposed are stored in the `talks` property of the server, an object whose property names are the talk titles. We will add some handlers to our router that expose these as HTTP ((resource))s under `/talks/[title]`.
287279

288280
{{index "GET method", "404 (HTTP status code)" "hasOwn function"}}
289281

@@ -507,9 +499,7 @@ Thus, if we want a page to show up when a browser is pointed at our server, we s
507499

508500
{{index CSS}}
509501

510-
It defines the document ((title)) and includes a style sheet, which defines a few styles to, among other things, make sure there is some space between talks.
511-
512-
At the bottom, it adds a heading at the top of the page and loads the script that contains the ((client))-side application.
502+
It defines the document ((title)) and includes a style sheet, which defines a few styles to, among other things, make sure there is some space between talks. Then it adds a heading at the top of the page and loads the script that contains the ((client))-side application.
513503

514504
### Actions
515505

@@ -830,7 +820,7 @@ hint}}
830820

831821
The wholesale redrawing of talks works pretty well because you usually can't tell the difference between a DOM node and its identical replacement. But there are exceptions. If you start typing something in the comment ((field)) for a talk in one browser window and then, in another, add a comment to that talk, the field in the first window will be redrawn, removing both its content and its ((focus)).
832822

833-
In a heated discussion, where multiple people are adding comments at the same time, this would be annoying. Can you come up with a way to solve it?
823+
When multiple people are adding comments at the same time, this would be annoying. Can you come up with a way to solve it?
834824

835825
{{hint
836826

code/solutions/21_2_comment_field_resets.js

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,15 @@ class SkillShareApp {
5555
this.talks = state.talks;
5656

5757
for (let talk of state.talks) {
58-
let cmp = this.talkMap[talk.title];
59-
if (cmp && cmp.talk.presenter == talk.presenter &&
60-
cmp.talk.summary == talk.summary) {
61-
cmp.syncState(talk);
58+
let found = this.talkMap[talk.title];
59+
if (found && found.talk.presenter == talk.presenter &&
60+
found.talk.summary == talk.summary) {
61+
found.syncState(talk);
6262
} else {
63-
if (cmp) cmp.dom.remove();
64-
cmp = new Talk(talk, this.dispatch);
65-
this.talkMap[talk.title] = cmp;
66-
this.talkDOM.appendChild(cmp.dom);
63+
if (found) found.dom.remove();
64+
found = new Talk(talk, this.dispatch);
65+
this.talkMap[talk.title] = found;
66+
this.talkDOM.appendChild(found.dom);
6767
}
6868
}
6969
for (let title of Object.keys(this.talkMap)) {

0 commit comments

Comments
 (0)