forked from marijnh/Eloquent-JavaScript
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy path18_forms.txt
743 lines (604 loc) · 25.2 KB
/
18_forms.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
:chap_num: 18
:prev_link: 17_http
:next_link: 19_FIXME
:load_files: ["js/promise.js"]
= Forms and Form Fields =
Forms were introduced briefly in the previous chapter as a way to
_submit_ information entered by the user over HTTP. They were designed
for a pre-JavaScript Web, and assume that interaction with the server
always happens by navigating to a new page.
But their elements are part of the DOM like the rest of the page, and
the DOM elements that represent form fields supports a number of
properties and events that are not present on other elements. These
make it possible to inspect and control them through JavaScript
programs, either to add additional functionality to a traditional
form, or by using such fields as building blocks in a JavaScript
application.
== Fields ==
A web form consists of any number of input fields, grouped by a
`<form>` tag. HTML allows a number of different styles of fields,
ranging from simple on/off checkboxes to drop-down menus and fields
for text input. This book won't try to comprehensively discuss all of
them, but we will start with a rough overview.
The `<input>` tag is used for a lot of field types. Its `type`
attribute is used to select the field's style. These are some commonly
used types:
[cols="1,5"]
|====
|`text` |A single-line text field
|`password`|Same as above, but hides the text that is typed
|`checkbox`|An on/off switch
|`radio` |(Part of) a multiple choice field
|`file` |Allows the user to choose a file from their computer
|====
Form fields do not necessarily have to appear in a `<form>` tag. You
can put them anywhere in a page. Such fields can not be submitted
(only a form as a whole can), but when responding to input with
JavaScript, that is often not what we want anyway.
[source,text/html]
----
<p><input type="text" value="abc"> (text)</p>
<p><input type="password" value="abc"> (password)</p>
<p><input type="checkbox" checked> (checkbox)</p>
<p><input type="radio" value="A" name="choice">
<input type="radio" value="B" name="choice" checked>
<input type="radio" value="C" name="choice"> (radio)</p>
<p><input type="file" checked> (file)</p>
----
ifdef::tex_target[]
image::img/form_fields.png[alt="Various types of input tags"]
endif::tex_target[]
The JavaScript interface for such elements differs with the type of
the element. We'll go over each of them later on in the chapter.
Multi-line text fields have their own tag, `<textarea>`, mostly
because using an attribute to specify a multi-line starting value
would be awkward. The `<textarea>` requires a matching `</textarea>`
closing tag, and uses the text between those two, instead of using its
`value` attribute, as starting text.
[source,text/html]
----
<textarea>
one
two
three
</textarea>
----
Finally, the `<select>` tag is used to create a field that allows the
user to select from a number of pre-defined options.
[source,text/html]
----
<select>
<option>Pancakes</option>
<option>Pudding</option>
<option>Ice cream</option>
</select>
----
ifdef::tex_target[]
image::img/form_select.png[alt="A select field"]
endif::tex_target[]
Whenever the value of a form field changes, it fires a `"change"`
event.
== Focus ==
Unlike most elements in an HTML document, form fields can get
_keyboard focus_. When clicked—or activated in some other way—they
become the currently active element, the main recipient of keyboard
input.
If a document has a text field, text typed will only end up in there
when it is focused. Other fields respond differently to keyboard
events, for example a `<select>` menu will try to move to the option
that contains the text the user typed, and respond to the arrow keys
by moving its selection up and down.
We can influence focusing from JavaScript with the `focus` and `blur`
methods. The first moves focus to the DOM element it is called on, the
second removes focus. The value in `document.activeElement`
corresponds to the currently focused element.
[source,text/html]
----
<input type="text">
<script>
document.querySelector("input").focus();
console.log(document.activeElement.tagName);
// → INPUT
document.querySelector("input").blur();
console.log(document.activeElement.tagName);
// → BODY
</script>
----
For some pages, it is expected that the user will want to start
interacting with a form field immediately. JavaScript can be used to
focus this field when the document is loaded, but HTML also provides
the `autofocus` attribute, which produces the same effect, but lets
the browser know what we are trying to achieve, and disable the
behavior when it is not appropriate, such as when the user has focused
something else.
[source,text/html]
[focus="yes"]
----
<input type="text" autofocus>
----
Browsers traditionally also allow the user to move the focus through
the document by pressing the Tab key. We can influence the order in
which elements receive focus with the `tabindex` attribute. The
example document below will let focus jump from the text input to the
“OK” button, rather than going through the help link first.
[source,text/html]
[focus="yes"]
----
<input type="text" tabindex=1> <a href=".">(help)</a>
<button onclick="console.log('ok')" tabindex=2>OK</button>
----
By default, most types of HTML elements can not be focused. But you
can add a `tabindex` property to any element, which will make it
focusable.
== Disabled fields ==
All form fields can be _disabled_ through their `disabled` attribute,
which also exists as a property on the element's DOM object. Disabled
fields can not be focused or changed. They also have a different
appearance, usually they look grey and faded.
[source,text/html]
----
<button>I'm all right</button>
<button disabled>I'm out</button>
----
ifdef::tex_target[]
image::img/button_disabled.png[alt="A disabled button"]
endif::tex_target[]
When the program is busy processing the action caused by some button
or other control, for example making requests to a server, it is often
a good idea to disable the control until the action finishes, so that
when the user gets impatient and clicks it again, they don't
accidentally repeat their action.
== A forms as a whole ==
When a field is contained in a `<form>` element, its DOM element will
have a property `form` linking back to the form's DOM element. The
`<form>` element, in turn, has a property `elements` containing an
array-like collection of the fields inside of it.
The `name` attribute of a form field determines the way its value will
be identified when the form is submitted. It can also be used as a
property name when accessing the form's `elements` property, which
acts both as a pseudo-array (accessible by number) and a map
(access-able by name).
[source,text/html]
----
<form action="example/submit.html">
Name: <input type="text" name="name"><br>
Password: <input type="password" name="password"><br>
<button type="submit">Log in</button>
</form>
<script>
var form = document.querySelector("form");
console.log(form.elements[1].type);
// → password
console.log(form.elements.password.type);
// → password
console.log(form.elements.name.form == form);
// → true
</script>
----
A button with a `type` attribute of `submit` will, when pressed, cause
the form to be submitted. Pressing Enter in when a form field is
focused has the same effect.
When this happens, it generates a `"submit"` event. This event can be
handled by JavaScript, and the handler can even prevent the default
behavior, which is to navigate to the page indicated by the form's
`action` attribute, by calling `preventDefault` on the event object.
[source,text/html]
----
<form action="example/submit.html">
Value: <input type="text" name="value">
<button type="submit">Save</button>
</form>
<script>
var form = document.querySelector("form");
form.addEventListener("submit", function(event) {
console.log("Saving value", form.elements.value.value);
event.preventDefault();
});
</script>
----
Intercepting `"submit"` events in JavaScript has various uses. We can
write code to verify that the values the user entered make sense, and
immediately show an error message instead of submitting the form when
they don't. Or we can disable the regular way of submitting the form
entirely, as in the example above, and have out program handle the
input, possibly using `XMLHttpRequest` to send it over to a server
without reloading the page.
== Text fields ==
Fields created by `<input>` tags with a type of `text` or `password`,
as well as `textarea` tags, share a common interface. Their DOM
elements have a `value` property which holds their current content, as
a string. To change the field's content, you can set this property to
a different string.
Their `selectionStart` and `selectionEnd` give us information about
the cursor and selection in the text. When nothing is selected, these
two properties hold the same number, indicating the position of the
cursor, for example 0 to indicate the start of the text, or 10 when it
is after the 10^th^ character. When part of the field is selected,
they will differ, giving us the start and end of the selected text.
Like `value`, these properties may also be written to.
As an example, imagine you are writing an article about Srinivasa
Ramanujan, but have some trouble spelling his name. The code below
wires up a `<textarea>` tag with an event handler that, when you press
F1, inserts the string “Srinivasa Ramanujan” for you.
[source,text/html]
----
<textarea style="width: 100%; height: 50px"></textarea>
<script>
var text = document.querySelector("textarea");
text.addEventListener("keydown", function(event) {
// The key code for F1 happens to 112
if (event.keyCode == 112) {
replaceSelection(text, "Srinivasa Ramanujan");
event.preventDefault();
}
});
function replaceSelection(field, word) {
var from = field.selectionStart, to = field.selectionEnd;
field.value = field.value.slice(0, from) + word +
field.value.slice(to);
// Put the cursor after the word
field.selectionStart = field.selectionEnd =
from + word.length;
};
</script>
----
The `replaceSelection` function replaces the currently selected part
of a text field's content with the given word, and then moves the
cursor after that word, so that the user can continue typing.
The `"change"` event for a text field does not fire every time
something is typed. Rather, it happens when the event loses focus
after its content was changed. To respond immediately to changes in a
text field, you should register a handler for the `"input"` event
instead, which fires for every character typed (or deleted, or
otherwise manipulated by the user).
The example below shows a text field and a counter showing the current
length of the text entered.
[source,text/html]
----
<input type="text"> length: <span id="length">0</span>
<script>
var text = document.querySelector("input");
var output = document.querySelector("#length");
text.addEventListener("input", function() {
output.textContent = text.value.length;
});
</script>
----
== Checkboxes and radio buttons ==
A checkbox field is a simple binary toggle. Its value can be extracted
or changed through its `checked` property, which holds a boolean
value.
[source,text/html]
----
<input type="checkbox" id="purple">
<label for="purple">Make this page purple</label>
<script>
var checkbox = document.querySelector("#purple");
checkbox.addEventListener("change", function() {
document.body.style.background =
checkbox.checked ? "mediumpurple" : "";
});
</script>
----
The `<label>` tag is used to associate a piece of text with an input
field. Its `for` attribute should refer to the `id` of the field.
Clicking the label will activate the field (focus it, or change it in
case of a checkbox).
A radio button is similar to a checkbox, but implicitly linked to
other radio buttons with the same `name` attribute, so that only one
of them can be active at any time.
[source,text/html]
----
Color:
<input type="radio" name="color" value="mediumpurple"> Purple
<input type="radio" name="color" value="lightgreen"> Green
<input type="radio" name="color" value="lightblue"> Blue
<script>
var buttons = document.getElementsByName("color");
function setColor(event) {
document.body.style.background = event.target.value;
}
for (var i = 0; i < buttons.length; i++)
buttons[i].addEventListener("change", setColor);
</script>
----
The `document.getElementsByName` method gives us all elements with a
given `name` attribute. The example loops over those (with a regular
`for` loop, not `forEach`, because the returned collection is not a
real array), and registers an event handler for each element. Remember
that event objects have a `target` property referring to the element
that triggered the event. This is often useful in event handlers like
this one, which will be called on different elements and need some way
to access the current target.
== Select fields ==
Select fields are conceptually similar to radio buttons—they also
allow the user to choose from a set of options. But where a radio
button puts the layout of the options under our control, the
appearance of a `<select>` tag is determined by the browser.
Select fields also have a variant that is more akin to a list of
checkboxes, rather than radio boxes. When given the `multiple`
attribute, a `<select>` tag will allow the user to select any number
of options, rather than just a single option.
[source,text/html]
----
<select multiple>
<option>Pancakes</option>
<option>Pudding</option>
<option>Ice cream</option>
</select>
----
ifdef::tex_target[]
image::img/form_multi_select.png[alt="A multiple select field"]
endif::tex_target[]
This will, in most browsers, show up differently than a non-`multiple`
select field, which is commonly drawn as a _drop-down_ control that
only shows the options when you _open_ it. The `size` attribute to the
`<select>` tag allows explicit control over this—it is used to set the
amount of options that are visible at the same time. For example
setting it to `"3"` will make the field show three lines, whether it
has the `multiple` option enabled or not.
Each `<option>` tag has a value. This value can be defined with a
`value` attribute, but when that is not given, the text inside the
option will count as the option's value. The `value` property of a
`<select>` element reflects the currently selected option. For a
`multiple` field, though, this property doesn't mean much, since it
will only give the value of _one_ of the currently selected options.
The `<option>` tags for a `<select>` field can be accessed as an
array-like object through its `options` property. Each option has a
property `selected`, which indicates whether that option is currently
selected. It can also be written to.
The example below extracts the selected values from a `multiple`
select field, and uses them to compose a binary number from individual
bits. Hold control (or command on Mac) to select multiple options.
[source,text/html]
----
<select multiple>
<option value="1">0001</option>
<option value="2">0010</option>
<option value="4">0100</option>
<option value="8">1000</option>
</select> = <span id="output">0</span>
<script>
var select = document.querySelector("select");
var output = document.querySelector("#output");
select.addEventListener("change", function() {
var number = 0;
for (var i = 0; i < select.options.length; i++) {
var option = select.options[i];
if (option.selected)
number += Number(option.value);
}
output.textContent = number;
});
</script>
----
== File fields ==
File fields were originally designed as a way to upload files from the
browser's machine through a form. In modern browsers, they also
provide a way to read such files from JavaScript programs. The field
acts as a manner of gatekeeper—the script can not simply start reading
private files from the user's computer, but when a file has been
selected in such a field, that is understood to mean that the script
may read it.
A file field usually looks like a button labeled something like
“choose file” or “browse”, with information about the chosen file next
to it.
[source,text/html]
----
<input type="file">
<script>
var input = document.querySelector("input");
input.addEventListener("change", function() {
if (input.files.length > 0) {
var file = input.files[0];
console.log("You chose", file.name);
if (file.type)
console.log("It has type", file.type);
}
});
</script>
----
The `files` property of a file field element is an array-like object
(again, not a real array) containing the files chosen in the field. It
is initially empty. The reason there isn't simply a `file` property is
that file fields also support a `multiple` attribute, which makes it
possible to select multiple files at the same time.
For a singular file field, there will be either zero or one file
objects in the `files` object. Such an object has properties like
`name` (the file name), `size` (the file's size in bytes), and `type`
(the media type of the file, like `text/plain` or `image/jpeg`).
What it does not have is a property that contains the content of the
file. Getting at that is a little more involved. Since reading a file
from disk can take time, the interface will have to be asynchronous to
avoid freezing the document. You can think of the `FileReader`
constructor as being similar to `XMLHttpRequest`, but for files.
[source,text/html]
----
<input type="file" multiple>
<script>
var input = document.querySelector("input");
input.addEventListener("change", function() {
Array.prototype.forEach.call(input.files, function(file) {
var reader = new FileReader();
reader.addEventListener("load", function() {
console.log("File", file.name, "starts with",
reader.result.slice(0, 20));
});
reader.readAsText(file);
});
});
</script>
----
Reading a file is done by creating a `FileReader` object, registering
a `"load"` event handler for it, and calling its `readAsText` method,
giving it the file we want to read. Once loading finishes, the
reader's `result` property contains the file's content.
The example uses `Array.prototype.forEach` to iterate over the array,
since in a normal loop it would be awkward to get the correct `file`
and `reader` objects from the event handler—the variables would be
shared by all iterations of the loop.
`FileReader`s also fire an `"error"` event when reading the file
failed for any reason. The error object itself will end up in the
reader's `error` property. You could wrap it in a `Promise` (see
Chapter 17) like this:
[source,javascript]
----
function readFile(file) {
return new Promise(function(succeed, fail) {
var reader = new FileReader();
reader.addEventListener("load", function() {
succeed(reader.result);
});
reader.addEventListener("error", function() {
fail(reader.error);
});
reader.readAsString(file);
});
}
----
It is possible to read only part of a file by calling `slice` on it,
and passing the result of that (a so-called _blob_ object) to the file
reader.
== FIXME a bigger example ==
== Summary ==
Form fields can be inspected and manipulated with JavaScript. They
fire events like `"change"`, when changed, `"input"`, when text is
typed, as well as the various keyboard events. These allow us to
notice when the user is interacting with them. Properties like `value`
(for text and select fields) or `checked` (for checkboxes and radio
buttons) are used to read or set the field's content.
When a form is submitted, its `"submit"` event fires. A JavaScript
handler can `preventDefault` that event to prevent the submission from
happening. Form fields elements do not have to be wrapped in `<form>`
tag, if they are only going to be used by a script.
== Exercises ==
=== A JavaScript workbench ===
Build an interface that allows people to type and run pieces of
JavaScript.
Put a button next to a `<textarea>` field, which, when pressed, uses
the `Function` constructor we saw in Chapter 10 to wrap the text in
the field in a function, and call it. Convert the return value of the
function, or any error it raised, to a string and display it below the
text field.
ifdef::html_target[]
// test: no
[source,text/html]
----
<textarea id="code">return "hi";</textarea>
<button id="button">Run</button>
<pre id="output"></pre>
<script>
// Your code here.
</script>
----
endif::html_target[]
!!solution!!
Use `document.querySelector` or `document.getElementById`, to get
access to the elements defined in your HTML. An event handler for
`"click"` or `"mousedown"` events on the button can get the `value`
property of the text field, and call `new Function` on it.
Make sure you wrap both the call to `new Function` and the call to its
result in a `try` block, so that you can catch exceptions that it
produces. In this case, we really don't know what type of exception we
are looking for, so catch everything.
The `textContent` property of the output element can be used to fill
it with a string message. Or, if you want to keep the old content
around, create a new text node using `document.createTextNode`, and
append it to the element. Remember to add a newline character to the
end, so that not all output appears on a single line.
!!solution!!
=== Autocompletion ===
Extend a text field so that when the user types, a list of suggested
values is shown below the field. You have an array of known common
values, and should show those that start with the text that was typed.
When a suggestion is clicked, replace the text field's current value
with it.
ifdef::html_target[]
// test: no
[source,text/html]
----
<input type="text" id="field">
<div id="suggestions"></div>
<script>
// Builds up an array with global variable names, like
// 'alert', 'document', and 'scrollTo'
var terms = [];
for (var name in window)
terms.push(name);
// Your code here.
</script>
----
endif::html_target[]
!!solution!!
The best event for updating the suggestion list is `"input"`, since
that will fire immediately when the content of the field is changed.
Then loop over the array of terms and see if they start with the given
string, for example by calling `indexOf` and seeing if the result is
zero. For each matching string, add an element to the suggestions
`<div>`. You should probably also empty that each time you start
updating the suggestions, for example by setting its `textContent` to
the empty string.
You could either add a `"click"` event handler to every suggestion
element, or add a single one to the outer `<div>` that holds them, and
look at the `target` property of the event to find out which
suggestion was clicked.
To get the suggestion text out of a DOM node, you could look at its
`textContent`, or set an attribute to explicitly store the text when
you create the element.
!!solution!!
=== Conway's Game of Life ===
Conway's Game of Life is a simple simulation that creates artificial
“life” on a grid, each cell of which is either live or not. Each
generation (turn), the following rules are applied:
1. Any live cell with fewer than two or more than three live
neighbors dies.
2. Any live cell with two or three live neighbors lives on to the
next generation.
3. Any dead cell with exactly three live neighbors becomes a live
cell.
Where a neighbor is defined as any adjacent cell, including diagonally
adjacent ones.
Note that these rules are applied to the whole grid at once, not one
square at a time. That means that the counting of neighbors is based
on the situation at the start of the generation, and changes happening
to neighbor cells this generation should not influence the new state
of a given cell.
Implement this game, taking the grid size as parameters, using
whichever data structure you find appropriate. Use `Math.random` to
populate the grid with a random pattern initially. Display it as a
grid of checkbox fields, with a button next to it to advance to the
next generation. When the user checks or unchecks the checkboxes,
their changes should be included when computing the next generation.
ifdef::html_target[]
// test: no
[source,text/html]
----
<div id="grid"></div>
<br> <!-- <br> inserts a line break -->
<button id="next">Next generation</button>
<script>
// Your code here.
</script>
----
endif::html_target[]
!!solution!!
To solve the problem of having the changes conceptually happen at the
same time, try to see the computation of a turn as a pure function,
which takes one grid, and produces a new grid that represents the next
turn.
Representing the grid can be done in any of the way seen in Chapters 7
and 15. Counting live neighbors can be done with two nested loops,
looping over adjacent coordinates. Take care not to count cells
outside of the field, and to ignore the cell in the center, for which
we are counting.
Making changes to checkboxes take affect on the next generation can be
done in two ways. An event handler could notice these changes and
update the current grid to reflect them, or you could generate a fresh
grid from the values in the checkboxes before computing the next turn.
If you choose to go with event handlers, you might want to attach
attributes that identify the position that each checkbox corresponds
to, so that it is easy to find out which cell to change.
To draw the grid of checkboxes, you can use either a `<table>` element
(see Chapter 13) or simply put them all in the same element, and
put `<br>` (line break) elements between the rows.
!!solution!!