3
3
4
4
= Project: Skill-Sharing Website =
5
5
6
- (((meetup)))(((project chapter)))A _((skill-sharing))_ meeting is an
7
- event where people with a shared interest come together and give
8
- small, informal presentations about things they know. At a
9
- ((gardening)) skill-sharing meeting, someone might explain how to
10
- cultivate ((celery)). Or in a programming-oriented skill-sharing
11
- group, you could drop by and tell everybody about Node.js.
6
+ (((skill-sharing project)))(((meetup)))(((project chapter)))A
7
+ _((skill-sharing))_ meeting is an event where people with a shared
8
+ interest come together and give small, informal presentations about
9
+ things they know. At a ((gardening)) skill-sharing meeting, someone
10
+ might explain how to cultivate ((celery)). Or in a
11
+ programming-oriented skill-sharing group, you could drop by and tell
12
+ everybody about Node.js.
12
13
13
14
(((learning)))(((users' group)))Such meetups, also often called
14
15
_users’ groups_ when they are about computers, are a great way to
@@ -36,11 +37,11 @@ http://eloquentjavascript.net/code/skillsharing.zip[_eloquentjavascript.net/code
36
37
37
38
== Design ==
38
39
39
- (((persistence)))There is a _((server))_ part to this project, written
40
- for ((Node.js)), and a _((client))_ part, written for the ((browser)).
41
- The server stores the system's data and provides it to the client. It
42
- also serves the HTML and JavaScript files that implement the
43
- client-side system.
40
+ (((skill-sharing project)))((( persistence)))There is a _((server))_
41
+ part to this project, written for ((Node.js)), and a _((client))_
42
+ part, written for the ((browser)). The server stores the system's data
43
+ and provides it to the client. It also serves the HTML and JavaScript
44
+ files that implement the client-side system.
44
45
45
46
The server keeps a list of ((talk))s proposed for the next meeting,
46
47
and the client shows this list. Each talk has a presenter name, a
@@ -117,9 +118,9 @@ system.
117
118
118
119
== HTTP interface ==
119
120
120
- Before we start fleshing out either the server or the client, let's
121
- think about the point where they touch: the ((HTTP)) ((interface))
122
- over which they communicate.
121
+ (((skill-sharing project))) Before we start fleshing out either the
122
+ server or the client, let's think about the point where they touch:
123
+ the ((HTTP)) ((interface)) over which they communicate.
123
124
124
125
We will base our interface on ((JSON)), and, like in the file server
125
126
from link:20_node.html#file_server[Chapter 20], try to make good use
@@ -200,8 +201,6 @@ delayed until something happens, or a given time period (we will use
200
201
90 seconds) has elapsed.
201
202
202
203
[[poll_time]]
203
-
204
-
205
204
(((Unix time)))(((Date.now function)))(((synchronization)))The time
206
205
must be indicated as the number of milliseconds elapsed since the
207
206
start of 1970, the same type of number that is returned by
@@ -263,8 +262,8 @@ always risky). But all that is outside of the scope of this chapter.
263
262
264
263
== The server ==
265
264
266
- Let's start by writing the ((server))-side part of the program. The
267
- following code runs on ((Node.js)).
265
+ (((skill-sharing project))) Let's start by writing the ((server))-side
266
+ part of the program. The following code runs on ((Node.js)).
268
267
269
268
=== Routing ===
270
269
@@ -692,8 +691,9 @@ the `/talks` URL.
692
691
693
692
== The client ==
694
693
695
- The ((client))-side part of the talk-managing website consists of
696
- three files: an HTML page, a style sheet, and a JavaScript file.
694
+ (((skill-sharing project)))The ((client))-side part of the
695
+ talk-managing website consists of three files: an HTML page, a style
696
+ sheet, and a JavaScript file.
697
697
698
698
=== HTML ===
699
699
@@ -927,10 +927,10 @@ function displayTalks(talks) {
927
927
}
928
928
----
929
929
930
- (((instantiation)))Building up the DOM structure for talks is done
931
- using the ((template))s that were included in the HTML document. First
932
- we must define `instantiateTemplate`, which looks up and fills in a
933
- template.
930
+ (((drawTalk function)))((( instantiation)))Building up the DOM
931
+ structure for talks is done using the ((template))s that were included
932
+ in the HTML document. First we must define `instantiateTemplate`,
933
+ which looks up and fills in a template.
934
934
935
935
(((class (CSS))))(((querySelector method)))The `name` parameter is the
936
936
template's name. To look up the template element, we search for an
@@ -977,8 +977,8 @@ whose properties hold the strings that are to be filled into the
977
977
template. A ((placeholder)) like `{{title}}` will be replaced with the
978
978
value of `values`’ `title` property.
979
979
980
- This is a crude approach to templating, but it is enough to implement
981
- `drawTalk`.
980
+ (((drawTalk function))) This is a crude approach to templating, but it
981
+ is enough to implement `drawTalk`.
982
982
983
983
// include_code >code/skillsharing/public/skillsharing_client.js
984
984
@@ -1175,63 +1175,68 @@ about efficiency—do the simplest thing that works.
1175
1175
1176
1176
!!solution!!
1177
1177
1178
- The simplest solution I can come up with is to simply encode the whole
1179
- `talks` object as JSON and dump it to a file with `fs.writeFile`.
1180
- There is already a function (`registerChange`) which is called every
1181
- time the server's data changes. It can be extended to write the new
1182
- data to disk.
1183
-
1184
- Pick a file name, for example `./talks.json`. When the server starts
1185
- up, it can try to read that file with `fs.readFile`, and if that
1186
- succeeds, use its contents as our starting data.
1187
-
1188
- Beware though, the `talks` object started out as a prototype-less
1189
- object, so that the `in` operator could be sanely used. `JSON.parse`
1190
- will return regular objects with `Object.prototype` as their
1191
- prototype. If you use JSON as your file format, you'll have to copy
1192
- the properties of the object returned by `JSON.parse` into a new,
1193
- prototype-less object.
1178
+ (((file system)))(((writeFile function)))(((registerChange
1179
+ function)))(((persistence)))The simplest solution I can come up with
1180
+ is to simply encode the whole `talks` object as ((JSON)) and dump it
1181
+ to a file with `fs.writeFile`. There is already a function
1182
+ (`registerChange`) which is called every time the server's data
1183
+ changes. It can be extended to write the new data to disk.
1184
+
1185
+ (((readFile function)))Pick a ((file)) name, for example
1186
+ `./talks.json`. When the server starts up, it can try to read that
1187
+ file with `fs.readFile`, and if that succeeds, use its contents as our
1188
+ starting data.
1189
+
1190
+ (((prototype)))(((JSON.parse function)))Beware though, the `talks`
1191
+ object started out as a prototype-less object, so that the `in`
1192
+ operator could be sanely used. `JSON.parse` will return regular
1193
+ objects with `Object.prototype` as their prototype. If you use JSON as
1194
+ your file format, you'll have to copy the properties of the object
1195
+ returned by `JSON.parse` into a new, prototype-less object.
1194
1196
1195
1197
!!solution!!
1196
1198
1197
1199
=== Comment field resets ===
1198
1200
1199
- The wholesale redrawing of talks works pretty well because you usually
1201
+ (((comment field reset (exercise))))(((template)))(((state)))The
1202
+ wholesale redrawing of talks works pretty well because you usually
1200
1203
can't tell the difference between a DOM node and its identical
1201
1204
replacement. But there are exceptions. If you start typing something
1202
- in the comment field for a talk in one browser window, and then, in
1203
- another, add a comment to that talk, the field in the first window
1204
- will be redrawn, removing both its content and its focus.
1205
+ in the comment (( field)) for a talk in one browser window, and then,
1206
+ in another, add a comment to that talk, the field in the first window
1207
+ will be redrawn, removing both its content and its (( focus)) .
1205
1208
1206
1209
In a heated discussion, where multiple people are adding comments to a
1207
1210
single talk, this would be very annoying. Can you come up with a way
1208
1211
to avoid it?
1209
1212
1210
1213
!!solution!!
1211
1214
1212
- The ad-hoc approach is to simply store the state a talk's comment
1213
- field (its content and whether it is focused) before redrawing the
1214
- talk, and then reset the field to its old state afterwards.
1215
+ (((comment field reset (exercise))))(((template)))The ad-hoc approach
1216
+ is to simply store the state a talk's comment field (its content and
1217
+ whether it is ((focus))ed) before redrawing the talk, and then
1218
+ ((reset)) the ((field)) to its old state afterwards.
1215
1219
1216
- Another solution would be to not simply replace the old DOM structure
1217
- with the new one, but recursively compare them, node by node, and only
1218
- update the parts that actually changed. This is more general (it
1219
- continues working even if we add another text field), but also a lot
1220
- harder to implement.
1220
+ (((recursion)))(((comparison,of DOM nodes))) Another solution would be
1221
+ to not simply replace the old DOM structure with the new one, but
1222
+ recursively compare them, node by node, and only update the parts that
1223
+ actually changed. This is more general (it continues working even if
1224
+ we add another text field), but also a lot harder to implement.
1221
1225
1222
1226
!!solution!!
1223
1227
1224
1228
=== Better templates ===
1225
1229
1226
- Most templating systems do more than just fill in a bunch of fields.
1227
- At the very least, they also allow conditional inclusion of parts of
1228
- the template, analogous to `if` statements, and repetition of parts of
1229
- a template, similar to a loop.
1230
+ (((conditional execution)))(((repetition)))(((template)))Most
1231
+ templating systems do more than just fill in some strings. At the very
1232
+ least, they also allow conditional inclusion of parts of the template,
1233
+ analogous to `if` statements, and repetition of parts of a template,
1234
+ similar to a loop.
1230
1235
1231
1236
If we were able to repeat a piece of template for each element in an
1232
1237
array, we would not need the second template (`"comment"`), but rather
1233
- could specify the `"talk"` template to loop over the array held in a
1234
- talk's `comments` property, and render the nodes that make up a
1238
+ could specify the `"talk"` template to (( loop)) over the array held in
1239
+ a talk's `comments` property, and render the nodes that make up a
1235
1240
comment for every element in the array.
1236
1241
1237
1242
It could look like this:
@@ -1245,32 +1250,36 @@ It could look like this:
1245
1250
</div>
1246
1251
----
1247
1252
1248
- The idea being that whenever a node with a `template-repeat` attribute
1249
- is found during template instantiation, the instantiating code loops
1250
- over the array held in the property indicated by the attribute. For
1251
- each element in the array, it adds an instance of the node. The
1252
- template's context (the `values` variable in `instantiateTemplate`)
1253
- would, during this loop, point at the current element of the array, so
1254
- that `{{author}}`, rather than being looked up in the original context
1255
- (the talk), would be looked up in the comment object.
1253
+ (((template-repeat attribute)))The idea being that whenever a node
1254
+ with a `template-repeat` attribute is found during template
1255
+ instantiation, the instantiating code loops over the array held in the
1256
+ property named by that attribute. For each element in the array, it
1257
+ adds an instance of the node. The template's context (the `values`
1258
+ variable in `instantiateTemplate`) would, during this loop, point at
1259
+ the current element of the array, so that `{{author}}`, rather than
1260
+ being looked up in the original context (the talk), would be looked up
1261
+ in the comment object.
1256
1262
1257
- Rewrite `instantiateTemplate` to implement this, and then change the
1258
- templates to use this feature and remove the explicit rendering of
1259
- comments from the `drawTalk` function.
1263
+ (((drawTalk function)))(((instantiateTemplate function)))Rewrite
1264
+ `instantiateTemplate` to implement this, and then change the templates
1265
+ to use this feature and remove the explicit rendering of comments from
1266
+ the `drawTalk` function.
1260
1267
1261
- How would you add conditional instantiation of nodes, where it is
1268
+ How would you add conditional instantiation of nodes, making it
1262
1269
possible to omit parts of the template when a given value is true or
1263
1270
false?
1264
1271
1265
1272
!!solution!!
1266
1273
1267
- One way to do this is to change `instantiateTemplate` so that its
1268
- inner function takes not just a node, but also a current context as
1269
- argument. You can then, when looping over a node's child nodes, check
1270
- whether the child has a `template-repeat` attribute. If it does, don't
1271
- instantiate it once, but instead loop over the array indicated by the
1272
- attribute's value, and instantiate it once for every element in the
1273
- array, passing the current array element as context.
1274
+ (((template)))(((repetition)))(((instantiateTemplate
1275
+ function)))(((recursion)))(((template-repeat attribute)))One way to do
1276
+ this is to change `instantiateTemplate` so that its inner function
1277
+ takes not just a node, but also a current context as argument. You can
1278
+ then, when looping over a node's child nodes, check whether the child
1279
+ has a `template-repeat` attribute. If it does, don't instantiate it
1280
+ once, but instead loop over the array indicated by the attribute's
1281
+ value, and instantiate it once for every element in the array, passing
1282
+ the current array element as context.
1274
1283
1275
1284
Conditionals can be implemented in a similar way, with attributes
1276
1285
called, for example, `template-when` and `template-unless`, which
@@ -1281,45 +1290,48 @@ false).
1281
1290
1282
1291
=== The unscriptables ===
1283
1292
1284
- When someone visits our website with a browser that has JavaScript
1285
- disabled, or is simply not capable of displaying JavaScript, they will
1286
- get a completely broken, inoperable page. This is not nice.
1293
+ (((skill-sharing project)))(((JavaScript,absence of)))When someone
1294
+ visits our website with a ((browser)) that has JavaScript disabled, or
1295
+ is simply not capable of displaying JavaScript, they will get a
1296
+ completely broken, inoperable page. This is not nice.
1287
1297
1288
- Some types of web applications really can't be done without
1289
- JavaScript. For others, you just don't have the budget or patience to
1290
- bother about clients that can't run scripts. But for pages with a wide
1291
- audience, it is polite to support script-less users.
1298
+ Some types of (( web application))s really can't be done without
1299
+ JavaScript. For others, you just don't have the (( budget)) or patience
1300
+ to bother about clients that can't run scripts. But for pages with a
1301
+ wide audience, it is polite to support script-less users.
1292
1302
1293
- Try to think of a way the skill sharing website could be set up to
1294
- preserve basic functionality when ran without JavaScript. The
1295
- automatic updates will have to go, and people will have to refresh
1296
- their page the old-fashioned way. But being able to see existing
1297
- talks, create new ones, and submit comments would be nice.
1303
+ (((graceful degradation)))Try to think of a way the skill sharing
1304
+ website could be set up to preserve basic functionality when ran
1305
+ without JavaScript. The automatic updates will have to go, and people
1306
+ will have to refresh their page the old-fashioned way. But being able
1307
+ to see existing talks, create new ones, and submit comments would be
1308
+ nice.
1298
1309
1299
1310
Don't feel obliged to actually implement this. Outlining a solution is
1300
1311
enough. Does the revised approach strike you as more, or less elegant
1301
1312
than what we did initially?
1302
1313
1303
1314
!!solution!!
1304
1315
1305
- Two central aspects of the approach taken in this chapter—a clean HTTP
1306
- interface, and client-side template rendering—don't work without
1307
- JavaScript. Normal HTML forms can send `GET` and `POST` requests, but
1308
- no `PUT` or `DELETE ` requests, and can only send their data to a fixed
1309
- URL.
1316
+ (((form (HTML tag))))(((page reload))) Two central aspects of the
1317
+ approach taken in this chapter—a clean HTTP interface, and client-side
1318
+ template rendering—don't work without JavaScript. Normal HTML forms
1319
+ can send `GET` and `POST ` requests, but no `PUT` or `DELETE` requests,
1320
+ and can only send their data to a fixed URL.
1310
1321
1311
- Thus, the server would have to be revised to accept comments, new
1312
- talks, and deleted talks through `POST` requests, whose bodies aren't
1313
- JSON, but rather use the URL-encoded format that HTML forms use (see
1322
+ (((query string)))(((POST method)))(((URL encoding)))Thus, the server
1323
+ would have to be revised to accept comments, new talks, and deleted
1324
+ talks through `POST` requests, whose bodies aren't JSON, but rather
1325
+ use the URL-encoded format that HTML forms use (see
1314
1326
link:18_forms.html#forms[Chapter 17]). These requests would have to
1315
1327
return the full new page, so that users see the new state of the site
1316
1328
after they make a change. This would not be too hard to engineer, and
1317
1329
could be implemented alongside the “clean” HTTP interface.
1318
1330
1319
- The code for rendering talks would have to be duplicated on the
1320
- server. The `index.html` file, rather than being a static file, would
1321
- have to be generated dynamically (by adding a handler for it to the
1322
- router), so that it already includes the current talks when it gets
1323
- served.
1331
+ (((template))) The code for rendering talks would have to be duplicated
1332
+ on the server. The `index.html` file, rather than being a static file,
1333
+ would have to be generated dynamically (by adding a handler for it to
1334
+ the router), so that it already includes the current talks and
1335
+ comments when it gets served.
1324
1336
1325
1337
!!solution!!
0 commit comments