forked from sarathavasarala/EloquentJavaScript
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchapter13.html
More file actions
697 lines (671 loc) · 83.3 KB
/
chapter13.html
File metadata and controls
697 lines (671 loc) · 83.3 KB
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
<html><head><link rel="stylesheet" type="text/css" href="css/book.css"/><link rel="stylesheet" type="text/css" href="css/highlight.css"/><link rel="stylesheet" type="text/css" href="css/console.css"/><link rel="stylesheet" type="text/css" href="css/codemirror.css"/><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/><title>Browser Events -- Eloquent JavaScript</title></head><body><script type="text/javascript" src="js/before.js"> </script><div class="content"><script type="text/javascript">var chapterTag = 'event';</script><div class="navigation"></div><h1><span class="number">Chapter 13: </span>Browser Events</h1><div class="block"><p><a class="paragraph" href="#p2e865690" name="p2e865690"> ¶ </a>To add interesting functionality to a web-page, just being able to
inspect or modify the document is generally not enough. We also need
to be able to detect what the user is doing, and respond to it. For
this, we will use a thing called <a name="key1"></a>event handlers. Pressed keys are
events, mouse clicks are events, even mouse motion can be seen as a
series of events. In <a href="chapter11.html">chapter 11</a>, we added an <code>onclick</code> property to a
button, in order to do something when it was pressed. This is a simple
event handler.</p><p><a class="paragraph" href="#p7203e803" name="p7203e803"> ¶ </a>The way browser events work is, fundamentally, very simple. It is
possible to register handlers for specific event types and specific
DOM nodes. Whenever an <a name="key2"></a>event occurs, the handler for that event, if
any, is called. For some events, such as key presses, knowing just
that the event occurred is not good enough, you also want to know
which key was pressed. To store such information, every event creates
an <a name="key3"></a>event object, which the handler can look at.</p><p><a class="paragraph" href="#p3398870e" name="p3398870e"> ¶ </a>It is important to realise that, even though events can fire at any
time, no two handlers ever run at the same moment. If other JavaScript
code is still running, the browser waits until it finishes before it
calls the next handler. This also holds for code that is triggered in
other ways, such as with <code>setTimeout</code>. In programmer jargon, browser
JavaScript is <a name="key4"></a>single-threaded, there are never two '<a name="key5"></a>threads'
running at the same time. This is, in most cases, a good thing. It is
very easy to get strange results when multiple things happen at the
same time.</p><p><a class="paragraph" href="#p18afb353" name="p18afb353"> ¶ </a>An event, when not handled, can 'bubble' through the DOM tree. What
this means is that if you click on, for example, a link in a
paragraph, any handlers associated with the link are called first. If
there are no such handlers, or these handlers do not indicate that
they have finished handling the event, the handlers for the paragraph,
which is the parent of the link, are tried. After that, the handlers
for <code>document.body</code> get a turn. Finally, if no JavaScript handlers
have taken care of the event, the browser handles it. When clicking a
link, this means that the link will be followed.</p></div><hr/><div class="block"><p><a class="paragraph" href="#p218ad643" name="p218ad643"> ¶ </a>So, as you see, events are easy. The only hard thing about them is
that browsers, while all supporting more or less the same
functionality, support this functionality through different
interfaces. As usual, the most incompatible browser is Internet
Explorer, which ignores the standard that most other browsers follow.
After that, there is Opera, which does not properly support some
useful events, such as the <code>onunload</code> event which fires when leaving a
page, and sometimes gives confusing information about keyboard events.</p><p><a class="paragraph" href="#p44695f6c" name="p44695f6c"> ¶ </a>There are four event-related actions one might want to take.</p><ul><li>Registering an event handler.</li><li>Getting the event object.</li><li>Extracting information from this object.</li><li>Signalling that an event has been handled.</li></ul><p><a class="paragraph" href="#p229378ff" name="p229378ff"> ¶ </a>None of them work the same across all major browsers.</p></div><hr/><div class="block"><p><a class="paragraph" href="#p4a7a380d" name="p4a7a380d"> ¶ </a>As a practice field for our event-handling, we open a document with a
button and a text field. Keep this window open (and attached) for the
rest of the chapter.</p><pre class="code"><span class="variable">attach</span>(<span class="variable">window</span>.<span class="property">open</span>(<span class="string">"example_events.html"</span>));</pre></div><hr/><div class="block"><p><a class="paragraph" href="#p200601ff" name="p200601ff"> ¶ </a>The first action, registering a handler, can be done by setting an
element's <code>onclick</code> (or <code>onkeypress</code>, and so on) property. This does
in fact work across browsers, but it has an important drawback ― you
can only attach one handler to an element. Most of the time, one is
enough, but there are cases, especially when a program has to be able
to work together with other programs (which might also be adding
handlers), that this is annoying.</p><p><a class="paragraph" href="#p586e6d02" name="p586e6d02"> ¶ </a><a name="key6"></a>In Internet Explorer, one can add a click handler to a
button like this:</p><pre class="code invalid"><span class="variable">$</span>(<span class="string">"button"</span>).<span class="property">attachEvent</span>(<span class="string">"onclick"</span>, <span class="keyword">function</span>(){<span class="variable">print</span>(<span class="string">"Click!"</span>);});</pre><p><a class="paragraph" href="#p40a18227" name="p40a18227"> ¶ </a><a name="key7"></a>On the other browsers, it goes like this:</p><pre class="code invalid"><span class="variable">$</span>(<span class="string">"button"</span>).<span class="property">addEventListener</span>(<span class="string">"click"</span>, <span class="keyword">function</span>(){<span class="variable">print</span>(<span class="string">"Click!"</span>);},
<span class="atom">false</span>);</pre><p><a class="paragraph" href="#pade8bb0" name="pade8bb0"> ¶ </a>Note how <code>"on"</code> is left off in the second case. The third argument
to <code>addEventListener</code>, <code>false</code>, indicates that the event should
'bubble' through the DOM tree as normal. Giving <code>true</code> instead can be
used to give this handler priority over the handlers 'beneath' it, but
since Internet Explorer does not support such a thing, this is rarely
useful.</p></div><hr/><div class="block"><a name="exercise1"></a><div class="exercisenum">Ex. 13.1</div><div class="exercise"><p><a class="paragraph" href="#p20e3de18" name="p20e3de18"> ¶ </a>Write a function called <code>registerEventHandler</code> to wrap the
incompatibilities of these two models. It takes three arguments: first
a DOM node that the handler should be attached to, then the name of
the event type, such as <code>"click"</code> or <code>"keypress"</code>, and finally the
handler function.</p><p><a class="paragraph" href="#p7b38739" name="p7b38739"> ¶ </a>To determine which method should be called, look for the methods
themselves ― if the DOM node has a method called <code>attachEvent</code>, you
may assume that this is the correct method. Note that this is much
preferable to directly checking whether the browser is Internet
Explorer. If a new browser arrives which uses Internet Explorer's
model, or Internet Explorer suddenly switches to the standard model,
the code will still work. Both are rather unlikely, of course, but
doing something in a smart way never hurts.</p></div><div class="solution"><pre class="code"><span class="keyword">function</span> <span class="variable">registerEventHandler</span>(<span class="variabledef">node</span>, <span class="variabledef">event</span>, <span class="variabledef">handler</span>) {
<span class="keyword">if</span> (typeof <span class="localvariable">node</span>.<span class="property">addEventListener</span> == <span class="string">"function"</span>)
<span class="localvariable">node</span>.<span class="property">addEventListener</span>(<span class="localvariable">event</span>, <span class="localvariable">handler</span>, <span class="atom">false</span>);
<span class="keyword">else</span>
<span class="localvariable">node</span>.<span class="property">attachEvent</span>(<span class="string">"on"</span> + <span class="localvariable">event</span>, <span class="localvariable">handler</span>);
}
<span class="variable">registerEventHandler</span>(<span class="variable">$</span>(<span class="string">"button"</span>), <span class="string">"click"</span>,
<span class="keyword">function</span>(){<span class="variable">print</span>(<span class="string">"Click (2)"</span>);});</pre><p><a class="paragraph" href="#pb5af88b" name="pb5af88b"> ¶ </a>Don't fret about the long, clumsy name. Later on, we will have to add
an extra wrapper to wrap this wrapper, and it will have a shorter
name.</p><p><a class="paragraph" href="#p231ebc85" name="p231ebc85"> ¶ </a>It is also possible to do this check only once, and define
<code>registerEventHandler</code> to hold a different function depending on the
browser. This is more efficient, but a little strange.</p><pre class="code"><span class="keyword">if</span> (typeof <span class="variable">document</span>.<span class="property">addEventListener</span> == <span class="string">"function"</span>)
<span class="keyword">var</span> <span class="variable">registerEventHandler</span> = <span class="keyword">function</span>(<span class="variabledef">node</span>, <span class="variabledef">event</span>, <span class="variabledef">handler</span>) {
<span class="localvariable">node</span>.<span class="property">addEventListener</span>(<span class="localvariable">event</span>, <span class="localvariable">handler</span>, <span class="atom">false</span>);
};
<span class="keyword">else</span>
<span class="keyword">var</span> <span class="variable">registerEventHandler</span> = <span class="keyword">function</span>(<span class="variabledef">node</span>, <span class="variabledef">event</span>, <span class="variabledef">handler</span>) {
<span class="localvariable">node</span>.<span class="property">attachEvent</span>(<span class="string">"on"</span> + <span class="localvariable">event</span>, <span class="localvariable">handler</span>);
};</pre></div></div><hr/><div class="block"><p><a class="paragraph" href="#p1cb19245" name="p1cb19245"> ¶ </a>Removing events works very much like adding them, but this time the
methods <a name="key8"></a><code>detachEvent</code> and <a name="key9"></a><code>removeEventListener</code> are used. Note
that, to remove a handler, you need to have access to the function you
attached to it.</p><pre class="code"><span class="keyword">function</span> <span class="variable">unregisterEventHandler</span>(<span class="variabledef">node</span>, <span class="variabledef">event</span>, <span class="variabledef">handler</span>) {
<span class="keyword">if</span> (typeof <span class="localvariable">node</span>.<span class="property">removeEventListener</span> == <span class="string">"function"</span>)
<span class="localvariable">node</span>.<span class="property">removeEventListener</span>(<span class="localvariable">event</span>, <span class="localvariable">handler</span>, <span class="atom">false</span>);
<span class="keyword">else</span>
<span class="localvariable">node</span>.<span class="property">detachEvent</span>(<span class="string">"on"</span> + <span class="localvariable">event</span>, <span class="localvariable">handler</span>);
}</pre></div><hr/><div class="block"><p><a class="paragraph" href="#p6ccdc648" name="p6ccdc648"> ¶ </a>Exceptions produced by event handlers can, because of technical
limitations, not be caught by the console. Thus, they are handled by
the browser, which might mean they get hidden in some kind of 'error
console' somewhere, or cause a message to pop up. When you write an
event handler and it does not seem to work, it might be silently
aborting because it causes some kind of error.</p></div><hr/><div class="block"><p><a class="paragraph" href="#pd0463c7" name="pd0463c7"> ¶ </a><a name="key10"></a>Most browsers pass the <a name="key11"></a>event object as an argument to the
handler. Internet Explorer stores it in the top-level variable called
<code>event</code>. When looking at JavaScript code, you will often come across
something like <code>event || window.event</code>, which takes the local variable
<code>event</code> or, if that is undefined, the top-level variable by that same
name.</p><pre class="code"><span class="keyword">function</span> <span class="variable">showEvent</span>(<span class="variabledef">event</span>) {
<span class="variable">show</span>(<span class="localvariable">event</span> || <span class="variable">window</span>.<span class="property">event</span>);
}
<span class="variable">registerEventHandler</span>(<span class="variable">$</span>(<span class="string">"textfield"</span>), <span class="string">"keypress"</span>, <span class="variable">showEvent</span>);</pre><p><a class="paragraph" href="#p19f5211" name="p19f5211"> ¶ </a>Type a few characters in the field, look at the objects, and shut it
up again:</p><pre class="code"><span class="variable">unregisterEventHandler</span>(<span class="variable">$</span>(<span class="string">"textfield"</span>), <span class="string">"keypress"</span>, <span class="variable">showEvent</span>);</pre></div><hr/><div class="block"><p><a class="paragraph" href="#p593ad57c" name="p593ad57c"> ¶ </a><a name="key12"></a><a name="key13"></a><a name="key14"></a><a name="key15"></a>When the user
clicks his mouse, three events are generated. First <a name="key16"></a><code>mousedown</code>, at
the moment the mouse button is pressed. Then, <a name="key17"></a><code>mouseup</code>, at the
moment it is released. And finally, <a name="key18"></a><code>click</code>, to indicate something
was clicked. When this happens two times in quick succession, a
<a name="key19"></a><code>dblclick</code> (double-click) event is also generated. Note that it is
possible for the <code>mousedown</code> and <code>mouseup</code> events to happen some time
apart ― when the mouse button is held for a while.</p><p><a class="paragraph" href="#p6346f948" name="p6346f948"> ¶ </a>When you attach an event handler to, for example, a button, the fact
that it has been clicked is often all you need to know. When the
handler, on the other hand, is attached to a node that has children,
clicks from the children will 'bubble' up to it, and you will want to
find out which child has been clicked. For this purpose, event objects
have a property called <a name="key20"></a><code>target</code>... or <code>srcElement</code>, depending on the
browser.</p><p><a class="paragraph" href="#p6f353756" name="p6f353756"> ¶ </a><a name="key21"></a><a name="key22"></a>Another interesting piece of information
are the precise coordinates at which the click occurred. Event objects
related to the mouse contain <a name="key23"></a><code>clientX</code> and <a name="key24"></a><code>clientY</code> properties,
which give the <code>x</code> and <code>y</code> coordinates of the mouse, in pixels, on the
screen. Documents can scroll, though, so often these coordinates do
not tell us much about the part of the document that the mouse is
over. Some browsers provide <a name="key25"></a><code>pageX</code> and <a name="key26"></a><code>pageY</code> properties for
this purpose, but others (guess which) do not. Fortunately, the
information about the amount of pixels the document has been scrolled
can be found in <code>document.body.scrollLeft</code> and
<code>document.body.scrollTop</code>.</p><p><a class="paragraph" href="#p70cc7708" name="p70cc7708"> ¶ </a>This handler, attached to the whole document, intercepts all mouse
clicks, and prints some information about them.</p><pre class="code"><span class="keyword">function</span> <span class="variable">reportClick</span>(<span class="variabledef">event</span>) {
<span class="localvariable">event</span> = <span class="localvariable">event</span> || <span class="variable">window</span>.<span class="property">event</span>;
<span class="keyword">var</span> <span class="variabledef">target</span> = <span class="localvariable">event</span>.<span class="property">target</span> || <span class="localvariable">event</span>.<span class="property">srcElement</span>;
<span class="keyword">var</span> <span class="variabledef">pageX</span> = <span class="localvariable">event</span>.<span class="property">pageX</span>, <span class="variabledef">pageY</span> = <span class="localvariable">event</span>.<span class="property">pageY</span>;
<span class="keyword">if</span> (<span class="localvariable">pageX</span> == <span class="atom">undefined</span>) {
<span class="localvariable">pageX</span> = <span class="localvariable">event</span>.<span class="property">clientX</span> + <span class="variable">document</span>.<span class="property">body</span>.<span class="property">scrollLeft</span>;
<span class="localvariable">pageY</span> = <span class="localvariable">event</span>.<span class="property">clientY</span> + <span class="variable">document</span>.<span class="property">body</span>.<span class="property">scrollTop</span>;
}
<span class="variable">print</span>(<span class="string">"Mouse clicked at "</span>, <span class="localvariable">pageX</span>, <span class="string">", "</span>, <span class="localvariable">pageY</span>,
<span class="string">". Inside element:"</span>);
<span class="variable">show</span>(<span class="localvariable">target</span>);
}
<span class="variable">registerEventHandler</span>(<span class="variable">document</span>, <span class="string">"click"</span>, <span class="variable">reportClick</span>);</pre><p><a class="paragraph" href="#p46809e6c" name="p46809e6c"> ¶ </a>And get rid of it again:</p><pre class="code"><span class="variable">unregisterEventHandler</span>(<span class="variable">document</span>, <span class="string">"click"</span>, <span class="variable">reportClick</span>);</pre><p><a class="paragraph" href="#p7769f920" name="p7769f920"> ¶ </a>Obviously, writing all these checks and workarounds is not something
you want to do in every single event handler. In a moment, after we
have gotten acquainted with a few more incompatibilities, we will
write a function to 'normalise' event objects to work the same across
browsers.</p><p><a class="paragraph" href="#p335ed4e9" name="p335ed4e9"> ¶ </a>It is also sometimes possible to find out which mouse button was
pressed, using the <a name="key27"></a><code>which</code> and <a name="key28"></a><code>button</code> properties of event
objects. Unfortunately, this is very unreliable ― some browsers
pretend mouses have only one button, others report right-clicks as
clicks during which the control key was held down, and so on.</p></div><hr/><div class="block"><p><a class="paragraph" href="#p5cf5df61" name="p5cf5df61"> ¶ </a><a name="key29"></a><a name="key30"></a><a name="key31"></a>Apart from clicks, we
might also be interested in the movement of the mouse. The
<a name="key32"></a><code>mousemove</code> event of a DOM node is fired whenever the mouse moves
while it is over that element. There are also <a name="key33"></a><code>mouseover</code> and
<a name="key34"></a><code>mouseout</code>, which are fired only when the mouse enters or leaves a
node. For events of this last type, the <code>target</code> (or <code>srcElement</code>)
property points at the node that the event is fired for, while the
<a name="key35"></a><code>relatedTarget</code> (or <code>toElement</code>, or <code>fromElement</code>) property gives
the node that the mouse came from (for <code>mouseover</code>) or left to (for
<code>mouseout</code>).</p><p><a class="paragraph" href="#p41646d83" name="p41646d83"> ¶ </a><code>mouseover</code> and <code>mouseout</code> can be tricky when they are registered on
an element that has child nodes. Events fired for the child nodes will
bubble up to the parent element, so you will also see a <code>mouseover</code>
event when the mouse enters one of the child nodes. The <code>target</code> and
<code>relatedTarget</code> properties can be used to detect (and ignore) such
events.</p></div><hr/><div class="block"><p><a class="paragraph" href="#p7061d6eb" name="p7061d6eb"> ¶ </a><a name="key36"></a><a name="key37"></a><a name="key38"></a>For every key that the user
presses, three events are generated: <a name="key39"></a><code>keydown</code>, <a name="key40"></a><code>keyup</code>, and
<a name="key41"></a><code>keypress</code>. In general, you should use the first two in cases where
you really want to know which key was pressed, for example when you
want to do something when the arrow keys are pressed. <code>keypress</code>, on
the other hand, is to be used when you are interested in the character
that is being typed. The reason for this is that there is often no
character information in <code>keyup</code> and <code>keydown</code> events, and Internet
Explorer does not generate a <code>keypress</code> event at all for special keys
such as the arrow keys.</p><p><a class="paragraph" href="#p73b0b817" name="p73b0b817"> ¶ </a>Finding out which key was pressed can be quite a challenge by itself.
For <code>keydown</code> and <code>keyup</code> events, the event object will have a
<a name="key42"></a><code>keyCode</code> property, which contains a number. Most of the time, these
codes can be used to identify keys in a reasonably browser-independent
way. Finding out which code corresponds to which key can be done by
simple experiments...</p><pre class="code"><span class="keyword">function</span> <span class="variable">printKeyCode</span>(<span class="variabledef">event</span>) {
<span class="localvariable">event</span> = <span class="localvariable">event</span> || <span class="variable">window</span>.<span class="property">event</span>;
<span class="variable">print</span>(<span class="string">"Key "</span>, <span class="localvariable">event</span>.<span class="property">keyCode</span>, <span class="string">" was pressed."</span>);
}
<span class="variable">registerEventHandler</span>(<span class="variable">$</span>(<span class="string">"textfield"</span>), <span class="string">"keydown"</span>, <span class="variable">printKeyCode</span>);</pre><pre class="code"><span class="variable">unregisterEventHandler</span>(<span class="variable">$</span>(<span class="string">"textfield"</span>), <span class="string">"keydown"</span>, <span class="variable">printKeyCode</span>);</pre><p><a class="paragraph" href="#p59d12e73" name="p59d12e73"> ¶ </a>In most browsers, a single key code corresponds to a single <em>physical</em>
key on your keyboard. The Opera browser, however, will generate
different key codes for some keys depending on whether shift is
pressed or not. Even worse, some of these shift-is-pressed codes are
the same codes that are also used for other keys ― shift-9, which on
most keyboards is used to type a parenthesis, gets the same code as
the down arrow, and as such is hard to distinguish from it. When this
threatens to sabotage your programs, you can usually resolve it by
ignoring key events that have shift pressed.</p><p><a class="paragraph" href="#p64163b55" name="p64163b55"> ¶ </a>To find out whether the shift, control, or alt key was held during a
key or mouse event, you can look at the <a name="key43"></a><code>shiftKey</code>, <a name="key44"></a><code>ctrlKey</code>, and
<a name="key45"></a><code>altKey</code> properties of the event object.</p><p><a class="paragraph" href="#p35cd0c5d" name="p35cd0c5d"> ¶ </a>For <code>keypress</code> events, you will want to know which character was
typed. The event object will have a <a name="key46"></a><code>charCode</code> property, which, if
you are lucky, contains the <a name="key47"></a>Unicode number corresponding to the
character that was typed, which can be converted to a 1-character
string by using <a name="key48"></a><code>String.fromCharCode</code>. Unfortunately, some browsers
do not define this property, or define it as <code>0</code>, and store the
character code in the <a name="key49"></a><code>keyCode</code> property instead.</p><pre class="code"><span class="keyword">function</span> <span class="variable">printCharacter</span>(<span class="variabledef">event</span>) {
<span class="localvariable">event</span> = <span class="localvariable">event</span> || <span class="variable">window</span>.<span class="property">event</span>;
<span class="keyword">var</span> <span class="variabledef">charCode</span> = <span class="localvariable">event</span>.<span class="property">charCode</span>;
<span class="keyword">if</span> (<span class="localvariable">charCode</span> == <span class="atom">undefined</span> || <span class="localvariable">charCode</span> === <span class="atom">0</span>)
<span class="localvariable">charCode</span> = <span class="localvariable">event</span>.<span class="property">keyCode</span>;
<span class="variable">print</span>(<span class="string">"Character '"</span>, <span class="variable">String</span>.<span class="property">fromCharCode</span>(<span class="localvariable">charCode</span>), <span class="string">"'"</span>);
}
<span class="variable">registerEventHandler</span>(<span class="variable">$</span>(<span class="string">"textfield"</span>), <span class="string">"keypress"</span>, <span class="variable">printCharacter</span>);</pre><pre class="code"><span class="variable">unregisterEventHandler</span>(<span class="variable">$</span>(<span class="string">"textfield"</span>), <span class="string">"keypress"</span>, <span class="variable">printCharacter</span>);</pre></div><hr/><div class="block"><p><a class="paragraph" href="#p42b3dc0" name="p42b3dc0"> ¶ </a>An event handler can 'stop' the event it is handling. There are two
different ways to do this. You can prevent the event from bubbling up
to parent nodes and the handlers defined on those, and you can prevent
the browser from taking the standard action associated with such an
event. It should be noted that browsers do not always follow this ―
preventing the default behaviour for the pressing of certain 'hotkeys'
will, on many browsers, not actually keep the browser from executing
the normal effect of these keys.</p><p><a class="paragraph" href="#p4ffe0969" name="p4ffe0969"> ¶ </a>On most browsers, stopping event bubbling is done with the
<a name="key50"></a><code>stopPropagation</code> method of the event object, and preventing default
behaviour is done with the <a name="key51"></a><code>preventDefault</code> method. For Internet
Explorer, this is done by setting the <a name="key52"></a><code>cancelBubble</code> property of
this object to <code>true</code>, and the <a name="key53"></a><code>returnValue</code> property to <code>false</code>,
respectively.</p><p><a class="paragraph" href="#p67028bf0" name="p67028bf0"> ¶ </a>And that was the last of the long list of incompatibilities that we
will discuss in this chapter. Which means that we can finally write
the event normaliser function and move on to more interesting things.</p><pre class="code"><span class="keyword">function</span> <span class="variable">normaliseEvent</span>(<span class="variabledef">event</span>) {
<span class="keyword">if</span> (!<span class="localvariable">event</span>.<span class="property">stopPropagation</span>) {
<span class="localvariable">event</span>.<span class="property">stopPropagation</span> = <span class="keyword">function</span>() {<span class="localvariable">this</span>.<span class="property">cancelBubble</span> = <span class="atom">true</span>;};
<span class="localvariable">event</span>.<span class="property">preventDefault</span> = <span class="keyword">function</span>() {<span class="localvariable">this</span>.<span class="property">returnValue</span> = <span class="atom">false</span>;};
}
<span class="keyword">if</span> (!<span class="localvariable">event</span>.<span class="property">stop</span>) {
<span class="localvariable">event</span>.<span class="property">stop</span> = <span class="keyword">function</span>() {
<span class="localvariable">this</span>.<span class="property">stopPropagation</span>();
<span class="localvariable">this</span>.<span class="property">preventDefault</span>();
};
}
<span class="keyword">if</span> (<span class="localvariable">event</span>.<span class="property">srcElement</span> && !<span class="localvariable">event</span>.<span class="property">target</span>)
<span class="localvariable">event</span>.<span class="property">target</span> = <span class="localvariable">event</span>.<span class="property">srcElement</span>;
<span class="keyword">if</span> ((<span class="localvariable">event</span>.<span class="property">toElement</span> || <span class="localvariable">event</span>.<span class="property">fromElement</span>) && !<span class="localvariable">event</span>.<span class="property">relatedTarget</span>)
<span class="localvariable">event</span>.<span class="property">relatedTarget</span> = <span class="localvariable">event</span>.<span class="property">toElement</span> || <span class="localvariable">event</span>.<span class="property">fromElement</span>;
<span class="keyword">if</span> (<span class="localvariable">event</span>.<span class="property">clientX</span> != <span class="atom">undefined</span> && <span class="localvariable">event</span>.<span class="property">pageX</span> == <span class="atom">undefined</span>) {
<span class="localvariable">event</span>.<span class="property">pageX</span> = <span class="localvariable">event</span>.<span class="property">clientX</span> + <span class="variable">document</span>.<span class="property">body</span>.<span class="property">scrollLeft</span>;
<span class="localvariable">event</span>.<span class="property">pageY</span> = <span class="localvariable">event</span>.<span class="property">clientY</span> + <span class="variable">document</span>.<span class="property">body</span>.<span class="property">scrollTop</span>;
}
<span class="keyword">if</span> (<span class="localvariable">event</span>.<span class="property">type</span> == <span class="string">"keypress"</span>) {
<span class="keyword">if</span> (<span class="localvariable">event</span>.<span class="property">charCode</span> === <span class="atom">0</span> || <span class="localvariable">event</span>.<span class="property">charCode</span> == <span class="atom">undefined</span>)
<span class="localvariable">event</span>.<span class="property">character</span> = <span class="variable">String</span>.<span class="property">fromCharCode</span>(<span class="localvariable">event</span>.<span class="property">keyCode</span>);
<span class="keyword">else</span>
<span class="localvariable">event</span>.<span class="property">character</span> = <span class="variable">String</span>.<span class="property">fromCharCode</span>(<span class="localvariable">event</span>.<span class="property">charCode</span>);
}
<span class="keyword">return</span> <span class="localvariable">event</span>;
}</pre><p><a class="paragraph" href="#p4a2c08d5" name="p4a2c08d5"> ¶ </a>A <a name="key54"></a><code>stop</code> method is added, which cancels both the bubbling and
the default action of the event. Some browsers already provide this,
in which case we leave it as it is.</p><p><a class="paragraph" href="#p625baa2a" name="p625baa2a"> ¶ </a>Next we can write convenient wrappers for <code>registerEventHandler</code> and
<code>unregisterEventHandler</code>:</p><pre class="code"><span class="keyword">function</span> <span class="variable">addHandler</span>(<span class="variabledef">node</span>, <span class="variabledef">type</span>, <span class="variabledef">handler</span>) {
<span class="keyword">function</span> <span class="variabledef">wrapHandler</span>(<span class="variabledef">event</span>) {
<span class="localvariable">handler</span>(<span class="variable">normaliseEvent</span>(<span class="localvariable">event</span> || <span class="variable">window</span>.<span class="property">event</span>));
}
<span class="variable">registerEventHandler</span>(<span class="localvariable">node</span>, <span class="localvariable">type</span>, <span class="localvariable">wrapHandler</span>);
<span class="keyword">return</span> {<span class="property">node</span>: <span class="localvariable">node</span>, <span class="property">type</span>: <span class="localvariable">type</span>, <span class="property">handler</span>: <span class="localvariable">wrapHandler</span>};
}
<span class="keyword">function</span> <span class="variable">removeHandler</span>(<span class="variabledef">object</span>) {
<span class="variable">unregisterEventHandler</span>(<span class="localvariable">object</span>.<span class="property">node</span>, <span class="localvariable">object</span>.<span class="property">type</span>, <span class="localvariable">object</span>.<span class="property">handler</span>);
}
<span class="keyword">var</span> <span class="variable">blockQ</span> = <span class="variable">addHandler</span>(<span class="variable">$</span>(<span class="string">"textfield"</span>), <span class="string">"keypress"</span>, <span class="keyword">function</span>(<span class="variabledef">event</span>) {
<span class="keyword">if</span> (<span class="localvariable">event</span>.<span class="property">character</span>.<span class="property">toLowerCase</span>() == <span class="string">"q"</span>)
<span class="localvariable">event</span>.<span class="property">stop</span>();
});</pre><p><a class="paragraph" href="#p662315ec" name="p662315ec"> ¶ </a>The new <code>addHandler</code> function wraps the handler function it is given
in a new function, so it can take care of normalising the event
objects. It returns an object that can be given to <code>removeHandler</code>
when we want to remove this specific handler. Try typing a '<code>q</code>' in
the text field.</p><pre class="code"><span class="variable">removeHandler</span>(<span class="variable">blockQ</span>);</pre></div><hr/><div class="block"><p><a class="paragraph" href="#p7a173854" name="p7a173854"> ¶ </a>Armed with <code>addHandler</code> and the <code>dom</code> function from the last chapter,
we are ready for more challenging feats of document-manipulation. As
an exercise, we will implement the game known as <a name="key55"></a>Sokoban. This is
something of a classic, but you may not have seen it before. The rules
are this: There is a grid, made up of walls, empty space, and one or
more 'exits'. On this grid, there are a number of crates or stones,
and a little dude that the player controls. This dude can be moved
horizontally and vertically into empty squares, and can push the
boulders around, provided that there is empty space behind them. The
goal of the game is to move a given number of boulders into the exits.</p><p><a class="paragraph" href="#p4dafac9b" name="p4dafac9b"> ¶ </a>Just like the terraria from <a href="chapter8.html">chapter 8</a>, a Sokoban level can be represented
as text. The variable <code>sokobanLevels</code>, in the <code>example_events.html</code>
window, contains an array of level objects. Each level has a property
<code>field</code>, containing a textual representation of the level, and a
property <code>boulders</code>, indicating the amount of boulders that must be
expelled to finish the level.</p><pre class="code"><span class="variable">show</span>(<span class="variable">sokobanLevels</span>.<span class="property">length</span>);
<span class="variable">show</span>(<span class="variable">sokobanLevels</span>[<span class="atom">1</span>].<span class="property">boulders</span>);
<span class="variable">forEach</span>(<span class="variable">sokobanLevels</span>[<span class="atom">1</span>].<span class="property">field</span>, <span class="variable">print</span>);</pre><p><a class="paragraph" href="#pf84c174" name="pf84c174"> ¶ </a>In such a level, the <code>#</code> characters are walls, spaces are empty
squares, <code>0</code> characters are used for for boulders, an <code>@</code> for the
starting location of the player, and a <code>*</code> for the exit.</p></div><hr/><div class="block"><p><a class="paragraph" href="#p370825d1" name="p370825d1"> ¶ </a>But, when playing the game, we do not want to be looking at this
textual representation. Instead, we will put a <a name="key56"></a>table into the
document. I made small <a name="key57"></a>style-sheet (<a href="css/sokoban.css">sokoban.css</a>,
if you are curious what it looks like) to give the cells of this table
a fixed square size, and added it to the example document. Each of the
cells in this table will get a background image, representing the type
of the square (empty, wall, or exit). To show the location of the
player and the boulders, images are added to these table cells, and
moved to different cells as appropriate.</p><p><a class="paragraph" href="#p2c88e34" name="p2c88e34"> ¶ </a>It would be possible to use this table as the main representation of
our data ― when we want to look whether there is a wall in a given
square, we just inspect the background of the appropriate table cell,
and to find the player, we just search for the image node with the
correct <code>src</code> property. In some cases, this approach is practical, but
for this program I chose to keep a separate data structure for the
grid, because it makes things much more straightforward.</p><p><a class="paragraph" href="#p1634434a" name="p1634434a"> ¶ </a>This data structure is a two-dimensional grid of objects, representing
the squares of the playing field. Each of the objects must store the
type of background it has and whether there is a boulder or player
present in that cell. It should also contain a reference to the table
cell that is used to display it in the document, to make it easy to
move images in and out of this table cell.</p><p><a class="paragraph" href="#p7cf79a44" name="p7cf79a44"> ¶ </a>That gives us two kinds of objects ― one to hold the grid of the
playing field, and one to represent the individual cells in this grid.
If we want the game to also do things like moving the next level at
the appropriate moment, and being able to reset the current level when
you mess up, we will also need a 'controller' object, which creates or
removes the field objects at the appropriate moment. For convenience,
we will be using the prototype approach outlined at the end of <a href="chapter8.html">chapter 8</a>,
so object types are just prototypes, and the <code>create</code> method, rather
than the <code>new</code> operator, is used to make new objects.</p></div><hr/><div class="block"><p><a class="paragraph" href="#p63e27462" name="p63e27462"> ¶ </a>Let us start with the objects representing the squares of the game's
field. They are responsible for setting the background of their cells
correctly, and adding images as appropriate. The <code>img/sokoban/</code>
directory contains a set of images, based on another ancient game,
which will be used to visualise the game. For a start, the <code>Square</code>
prototype could look like this.</p><pre class="code"><span class="keyword">var</span> <span class="variable">Square</span> = {
<span class="property">construct</span>: <span class="keyword">function</span>(<span class="variabledef">character</span>, <span class="variabledef">tableCell</span>) {
<span class="localvariable">this</span>.<span class="property">background</span> = <span class="string">"empty"</span>;
<span class="keyword">if</span> (<span class="localvariable">character</span> == <span class="string">"#"</span>)
<span class="localvariable">this</span>.<span class="property">background</span> = <span class="string">"wall"</span>;
<span class="keyword">else</span> <span class="keyword">if</span> (<span class="localvariable">character</span> == <span class="string">"*"</span>)
<span class="localvariable">this</span>.<span class="property">background</span> = <span class="string">"exit"</span>;
<span class="localvariable">this</span>.<span class="property">tableCell</span> = <span class="localvariable">tableCell</span>;
<span class="localvariable">this</span>.<span class="property">tableCell</span>.<span class="property">className</span> = <span class="localvariable">this</span>.<span class="property">background</span>;
<span class="localvariable">this</span>.<span class="property">content</span> = <span class="atom">null</span>;
<span class="keyword">if</span> (<span class="localvariable">character</span> == <span class="string">"0"</span>)
<span class="localvariable">this</span>.<span class="property">content</span> = <span class="string">"boulder"</span>;
<span class="keyword">else</span> <span class="keyword">if</span> (<span class="localvariable">character</span> == <span class="string">"@"</span>)
<span class="localvariable">this</span>.<span class="property">content</span> = <span class="string">"player"</span>;
<span class="keyword">if</span> (<span class="localvariable">this</span>.<span class="property">content</span> != <span class="atom">null</span>) {
<span class="keyword">var</span> <span class="variabledef">image</span> = <span class="variable">dom</span>(<span class="string">"IMG"</span>, {<span class="property">src</span>: <span class="string">"img/sokoban/"</span> +
<span class="localvariable">this</span>.<span class="property">content</span> + <span class="string">".gif"</span>});
<span class="localvariable">this</span>.<span class="property">tableCell</span>.<span class="property">appendChild</span>(<span class="localvariable">image</span>);
}
},
<span class="property">hasPlayer</span>: <span class="keyword">function</span>() {
<span class="keyword">return</span> <span class="localvariable">this</span>.<span class="property">content</span> == <span class="string">"player"</span>;
},
<span class="property">hasBoulder</span>: <span class="keyword">function</span>() {
<span class="keyword">return</span> <span class="localvariable">this</span>.<span class="property">content</span> == <span class="string">"boulder"</span>;
},
<span class="property">isEmpty</span>: <span class="keyword">function</span>() {
<span class="keyword">return</span> <span class="localvariable">this</span>.<span class="property">content</span> == <span class="atom">null</span> && <span class="localvariable">this</span>.<span class="property">background</span> == <span class="string">"empty"</span>;
},
<span class="property">isExit</span>: <span class="keyword">function</span>() {
<span class="keyword">return</span> <span class="localvariable">this</span>.<span class="property">background</span> == <span class="string">"exit"</span>;
}
};
<span class="keyword">var</span> <span class="variable">testSquare</span> = <span class="variable">Square</span>.<span class="property">create</span>(<span class="string">"@"</span>, <span class="variable">dom</span>(<span class="string">"TD"</span>));
<span class="variable">show</span>(<span class="variable">testSquare</span>.<span class="property">hasPlayer</span>());</pre><p><a class="paragraph" href="#p1da705b8" name="p1da705b8"> ¶ </a>The <code>character</code> argument to the constructor will be used to transform
characters from the level blueprints into actual <code>Square</code> objects. To
set the background of the cells, style-sheet classes are used (defined
in <a href="css/sokoban.css">sokoban.css</a>), which are assigned to the <code>td</code>
elements' <code>className</code> property.</p><p><a class="paragraph" href="#p2f328f59" name="p2f328f59"> ¶ </a>The methods like <code>hasPlayer</code> and <code>isEmpty</code> are a way to 'isolate' the
code that uses objects of this type from the internals of the objects.
They are not strictly necessary in this case, but they will make the
other code look better.</p></div><hr/><div class="block"><a name="exercise2"></a><div class="exercisenum">Ex. 13.2</div><div class="exercise"><p><a class="paragraph" href="#p543d0e5f" name="p543d0e5f"> ¶ </a>Add methods <code>moveContent</code> and <code>clearContent</code> to the <code>Square</code>
prototype. The first one takes another <code>Square</code> object as an argument,
and moves the content of the <code>this</code> square into the argument by
updating the <code>content</code> properties and moving the image node associated
with this content. This will be used to move boulders and players
around the grid. It may assume the square is not currently empty.
<code>clearContent</code> removes the content from the square without moving it
anywhere. Note that the <code>content</code> property for empty squares contains
<code>null</code>.</p><p><a class="paragraph" href="#p7c07d4d4" name="p7c07d4d4"> ¶ </a>The <code>removeElement</code> function we defined in <a href="chapter12.html">chapter 12</a> is available in this
chapter too, for your node-removing convenience. You may assume that
the images are the only child nodes of the table cells, and can thus
be reached through, for example, <code>this.tableCell.lastChild</code>.</p></div><div class="solution"><pre class="code"><span class="variable">Square</span>.<span class="property">moveContent</span> = <span class="keyword">function</span>(<span class="variabledef">target</span>) {
<span class="localvariable">target</span>.<span class="property">content</span> = <span class="localvariable">this</span>.<span class="property">content</span>;
<span class="localvariable">this</span>.<span class="property">content</span> = <span class="atom">null</span>;
<span class="localvariable">target</span>.<span class="property">tableCell</span>.<span class="property">appendChild</span>(<span class="localvariable">this</span>.<span class="property">tableCell</span>.<span class="property">lastChild</span>);
};
<span class="variable">Square</span>.<span class="property">clearContent</span> = <span class="keyword">function</span>() {
<span class="localvariable">this</span>.<span class="property">content</span> = <span class="atom">null</span>;
<span class="variable">removeElement</span>(<span class="localvariable">this</span>.<span class="property">tableCell</span>.<span class="property">lastChild</span>);
};</pre></div></div><hr/><div class="block"><p><a class="paragraph" href="#p67b7510d" name="p67b7510d"> ¶ </a>The next object type will be called <code>SokobanField</code>. Its constructor is
given an object from the <code>sokobanLevels</code> array, and is responsible for
building both a table of DOM nodes and a grid of <code>Square</code> objects.
This object will also take care of the details of moving the player
and boulders around, through a <code>move</code> method that is given an argument
indicating which way the player wants to move.</p><p><a class="paragraph" href="#p3a2d101e" name="p3a2d101e"> ¶ </a>To identify the individual squares, and to indicate directions, we
will again use the <code>Point</code> object type from <a href="chapter8.html">chapter 8</a>, which, as you might
remember, has an <code>add</code> method.</p><p><a class="paragraph" href="#p25ce8f2d" name="p25ce8f2d"> ¶ </a>The base of the field prototype looks like this:</p><pre class="code"><span class="keyword">var</span> <span class="variable">SokobanField</span> = {
<span class="property">construct</span>: <span class="keyword">function</span>(<span class="variabledef">level</span>) {
<span class="keyword">var</span> <span class="variabledef">tbody</span> = <span class="variable">dom</span>(<span class="string">"TBODY"</span>);
<span class="localvariable">this</span>.<span class="property">squares</span> = [];
<span class="localvariable">this</span>.<span class="property">bouldersToGo</span> = <span class="localvariable">level</span>.<span class="property">boulders</span>;
<span class="keyword">for</span> (<span class="keyword">var</span> <span class="variabledef">y</span> = <span class="atom">0</span>; <span class="localvariable">y</span> < <span class="localvariable">level</span>.<span class="property">field</span>.<span class="property">length</span>; <span class="localvariable">y</span>++) {
<span class="keyword">var</span> <span class="variabledef">line</span> = <span class="localvariable">level</span>.<span class="property">field</span>[<span class="localvariable">y</span>];
<span class="keyword">var</span> <span class="variabledef">tableRow</span> = <span class="variable">dom</span>(<span class="string">"TR"</span>);
<span class="keyword">var</span> <span class="variabledef">squareRow</span> = [];
<span class="keyword">for</span> (<span class="keyword">var</span> <span class="variabledef">x</span> = <span class="atom">0</span>; <span class="localvariable">x</span> < <span class="localvariable">line</span>.<span class="property">length</span>; <span class="localvariable">x</span>++) {
<span class="keyword">var</span> <span class="variabledef">tableCell</span> = <span class="variable">dom</span>(<span class="string">"TD"</span>);
<span class="localvariable">tableRow</span>.<span class="property">appendChild</span>(<span class="localvariable">tableCell</span>);
<span class="keyword">var</span> <span class="variabledef">square</span> = <span class="variable">Square</span>.<span class="property">create</span>(<span class="localvariable">line</span>.<span class="property">charAt</span>(<span class="localvariable">x</span>), <span class="localvariable">tableCell</span>);
<span class="localvariable">squareRow</span>.<span class="property">push</span>(<span class="localvariable">square</span>);
<span class="keyword">if</span> (<span class="localvariable">square</span>.<span class="property">hasPlayer</span>())
<span class="localvariable">this</span>.<span class="property">playerPos</span> = <span class="keyword">new</span> <span class="variable">Point</span>(<span class="localvariable">x</span>, <span class="localvariable">y</span>);
}
<span class="localvariable">tbody</span>.<span class="property">appendChild</span>(<span class="localvariable">tableRow</span>);
<span class="localvariable">this</span>.<span class="property">squares</span>.<span class="property">push</span>(<span class="localvariable">squareRow</span>);
}
<span class="localvariable">this</span>.<span class="property">table</span> = <span class="variable">dom</span>(<span class="string">"TABLE"</span>, {<span class="string">"class"</span>: <span class="string">"sokoban"</span>}, <span class="localvariable">tbody</span>);
<span class="localvariable">this</span>.<span class="property">score</span> = <span class="variable">dom</span>(<span class="string">"DIV"</span>, <span class="atom">null</span>, <span class="string">"..."</span>);
<span class="localvariable">this</span>.<span class="property">updateScore</span>();
},
<span class="property">getSquare</span>: <span class="keyword">function</span>(<span class="variabledef">position</span>) {
<span class="keyword">return</span> <span class="localvariable">this</span>.<span class="property">squares</span>[<span class="localvariable">position</span>.<span class="property">y</span>][<span class="localvariable">position</span>.<span class="property">x</span>];
},
<span class="property">updateScore</span>: <span class="keyword">function</span>() {
<span class="localvariable">this</span>.<span class="property">score</span>.<span class="property">firstChild</span>.<span class="property">nodeValue</span> = <span class="localvariable">this</span>.<span class="property">bouldersToGo</span> +
<span class="string">" boulders to go."</span>;
},
<span class="property">won</span>: <span class="keyword">function</span>() {
<span class="keyword">return</span> <span class="localvariable">this</span>.<span class="property">bouldersToGo</span> <= <span class="atom">0</span>;
}
};
<span class="keyword">var</span> <span class="variable">testField</span> = <span class="variable">SokobanField</span>.<span class="property">create</span>(<span class="variable">sokobanLevels</span>[<span class="atom">0</span>]);
<span class="variable">show</span>(<span class="variable">testField</span>.<span class="property">getSquare</span>(<span class="keyword">new</span> <span class="variable">Point</span>(<span class="atom">10</span>, <span class="atom">2</span>)).<span class="property">content</span>);</pre><p><a class="paragraph" href="#paa123b2" name="paa123b2"> ¶ </a>The constructor goes over the lines and characters in the level, and
stores the <code>Square</code> objects in the <code>squares</code> property. When it
encounters the square with the player, it saves this position as
<code>playerPos</code>, so that we can easily find the square with the player
later on. <code>getSquare</code> is used to find a <code>Square</code> object corresponding
to a certain <code>x,y</code> position on the field. Note that it doesn't take
the edges of the field into account ― to avoid writing some boring
code, we assume that the field is properly walled off, making it
impossible to walk out of it.</p><p><a class="paragraph" href="#p7764e02d" name="p7764e02d"> ¶ </a>The word <code>"class"</code> in the <code>dom</code> call that makes the <code>table</code> node is
quoted as a string. This is necessary because <a name="key58"></a><code>class</code> is a 'reserved
word' in JavaScript, and may not be used as a variable or property
name.</p><p><a class="paragraph" href="#p4737ffd1" name="p4737ffd1"> ¶ </a>The amount of boulders that have to be cleared to win the level (this
may be less than the total amount of boulders on the level) is stored
in <code>bouldersToGo</code>. Whenever a boulder is brought to the exit, we can
subtract 1 from this, and see whether the game is won yet. To show the
player how he is doing, we will have to show this amount somehow. For
this purpose, a <code>div</code> element with text is used. <code>div</code> nodes are
containers without inherent markup. The score text can be updated with
the <code>updateScore</code> method. The <code>won</code> method will be used by the
controller object to determine when the game is over, so the player
can move on to the next level.</p></div><hr/><div class="block"><p><a class="paragraph" href="#p4009788c" name="p4009788c"> ¶ </a>If we want to actually see the playing field and the score, we will
have to insert them into the document somehow. That is what the
<code>place</code> method is for. We'll also add a <code>remove</code> method to make it
easy to remove a field when we are done with it.</p><pre class="code"><span class="variable">SokobanField</span>.<span class="property">place</span> = <span class="keyword">function</span>(<span class="variabledef">where</span>) {
<span class="localvariable">where</span>.<span class="property">appendChild</span>(<span class="localvariable">this</span>.<span class="property">score</span>);
<span class="localvariable">where</span>.<span class="property">appendChild</span>(<span class="localvariable">this</span>.<span class="property">table</span>);
};
<span class="variable">SokobanField</span>.<span class="property">remove</span> = <span class="keyword">function</span>() {
<span class="variable">removeElement</span>(<span class="localvariable">this</span>.<span class="property">score</span>);
<span class="variable">removeElement</span>(<span class="localvariable">this</span>.<span class="property">table</span>);
};
<span class="variable">testField</span>.<span class="property">place</span>(<span class="variable">document</span>.<span class="property">body</span>);</pre><p><a class="paragraph" href="#p5e826291" name="p5e826291"> ¶ </a>If all went well, you should see a Sokoban field now.</p></div><hr/><div class="block"><a name="exercise3"></a><div class="exercisenum">Ex. 13.3</div><div class="exercise"><p><a class="paragraph" href="#p6aedd00" name="p6aedd00"> ¶ </a>But this field doesn't do very much yet. Add a method called <code>move</code>.
It takes a <code>Point</code> object specifying the move as argument (for example
<code>-1,0</code> to move left), and takes care of moving the game elements in
the correct way.</p><p><a class="paragraph" href="#p221dad7d" name="p221dad7d"> ¶ </a>The correct way is this: The <code>playerPos</code> property can be used to
determine where the player is trying to move. If there is a boulder
here, look at the square behind this boulder. When there is an exit
there, remove the boulder and update the score. When there is empty
space there, move the boulder into it. Next, try to move the player.
If the square he is trying to move into is not empty, ignore the move.</p></div><div class="solution"><pre class="code"><span class="variable">SokobanField</span>.<span class="property">move</span> = <span class="keyword">function</span>(<span class="variabledef">direction</span>) {
<span class="keyword">var</span> <span class="variabledef">playerSquare</span> = <span class="localvariable">this</span>.<span class="property">getSquare</span>(<span class="localvariable">this</span>.<span class="property">playerPos</span>);
<span class="keyword">var</span> <span class="variabledef">targetPos</span> = <span class="localvariable">this</span>.<span class="property">playerPos</span>.<span class="property">add</span>(<span class="localvariable">direction</span>);
<span class="keyword">var</span> <span class="variabledef">targetSquare</span> = <span class="localvariable">this</span>.<span class="property">getSquare</span>(<span class="localvariable">targetPos</span>);
<span class="comment">// Possibly pushing a boulder</span>
<span class="keyword">if</span> (<span class="localvariable">targetSquare</span>.<span class="property">hasBoulder</span>()) {
<span class="keyword">var</span> <span class="variabledef">pushTarget</span> = <span class="localvariable">this</span>.<span class="property">getSquare</span>(<span class="localvariable">targetPos</span>.<span class="property">add</span>(<span class="localvariable">direction</span>));
<span class="keyword">if</span> (<span class="localvariable">pushTarget</span>.<span class="property">isEmpty</span>()) {
<span class="localvariable">targetSquare</span>.<span class="property">moveContent</span>(<span class="localvariable">pushTarget</span>);
}
<span class="keyword">else</span> <span class="keyword">if</span> (<span class="localvariable">pushTarget</span>.<span class="property">isExit</span>()) {
<span class="localvariable">targetSquare</span>.<span class="property">moveContent</span>(<span class="localvariable">pushTarget</span>);
<span class="localvariable">pushTarget</span>.<span class="property">clearContent</span>();
<span class="localvariable">this</span>.<span class="property">bouldersToGo</span>--;
<span class="localvariable">this</span>.<span class="property">updateScore</span>();
}
}
<span class="comment">// Moving the player</span>
<span class="keyword">if</span> (<span class="localvariable">targetSquare</span>.<span class="property">isEmpty</span>()) {
<span class="localvariable">playerSquare</span>.<span class="property">moveContent</span>(<span class="localvariable">targetSquare</span>);
<span class="localvariable">this</span>.<span class="property">playerPos</span> = <span class="localvariable">targetPos</span>;
}
};</pre><p><a class="paragraph" href="#p3fc959ad" name="p3fc959ad"> ¶ </a>By taking care of boulders first, the move code can work the same way
when the player is moving normally and when he is pushing a boulder.
Note how the square behind the boulder is found by adding the
<code>direction</code> to the <code>playerPos</code> twice. Test it by moving left two
squares:</p><pre class="code"><span class="variable">testField</span>.<span class="property">move</span>(<span class="keyword">new</span> <span class="variable">Point</span>(-<span class="atom">1</span>, <span class="atom">0</span>));
<span class="variable">testField</span>.<span class="property">move</span>(<span class="keyword">new</span> <span class="variable">Point</span>(-<span class="atom">1</span>, <span class="atom">0</span>));</pre><p><a class="paragraph" href="#p11bc85e4" name="p11bc85e4"> ¶ </a>If that worked, we moved a boulder into a place from which we can't
get it out anymore, so we'd better throw this field away.</p><pre class="code"><span class="variable">testField</span>.<span class="property">remove</span>();</pre></div></div><hr/><div class="block"><p><a class="paragraph" href="#p6a573480" name="p6a573480"> ¶ </a>All the 'game logic' has been taken care of now, and we just need a
controller to make it playable. The controller will be an object type
called <code>SokobanGame</code>, which is responsible for the following things:</p><ul><li>Preparing a place where the game field can be placed.</li><li>Building and removing <code>SokobanField</code> objects.</li><li>Capturing key events and calling the <code>move</code> method on current field with the correct argument.</li><li>Keeping track of the current level number and moving to the next level when a level is won.</li><li>Adding buttons to reset the current level or the whole game (back to level 0).</li></ul><p><a class="paragraph" href="#p1877ef36" name="p1877ef36"> ¶ </a>We start again with an unfinished prototype.</p><pre class="code"><span class="keyword">var</span> <span class="variable">SokobanGame</span> = {
<span class="property">construct</span>: <span class="keyword">function</span>(<span class="variabledef">place</span>) {
<span class="localvariable">this</span>.<span class="property">level</span> = <span class="atom">null</span>;
<span class="localvariable">this</span>.<span class="property">field</span> = <span class="atom">null</span>;
<span class="keyword">var</span> <span class="variabledef">newGame</span> = <span class="variable">dom</span>(<span class="string">"BUTTON"</span>, <span class="atom">null</span>, <span class="string">"New game"</span>);
<span class="variable">addHandler</span>(<span class="localvariable">newGame</span>, <span class="string">"click"</span>, <span class="variable">method</span>(<span class="localvariable">this</span>, <span class="string">"newGame"</span>));
<span class="keyword">var</span> <span class="variabledef">reset</span> = <span class="variable">dom</span>(<span class="string">"BUTTON"</span>, <span class="atom">null</span>, <span class="string">"Reset level"</span>);
<span class="variable">addHandler</span>(<span class="localvariable">reset</span>, <span class="string">"click"</span>, <span class="variable">method</span>(<span class="localvariable">this</span>, <span class="string">"reset"</span>));
<span class="localvariable">this</span>.<span class="property">container</span> = <span class="variable">dom</span>(<span class="string">"DIV"</span>, <span class="atom">null</span>,
<span class="variable">dom</span>(<span class="string">"H1"</span>, <span class="atom">null</span>, <span class="string">"Sokoban"</span>),
<span class="variable">dom</span>(<span class="string">"DIV"</span>, <span class="atom">null</span>, <span class="localvariable">newGame</span>, <span class="string">" "</span>, <span class="localvariable">reset</span>));
<span class="localvariable">place</span>.<span class="property">appendChild</span>(<span class="localvariable">this</span>.<span class="property">container</span>);
<span class="variable">addHandler</span>(<span class="variable">document</span>, <span class="string">"keydown"</span>, <span class="variable">method</span>(<span class="localvariable">this</span>, <span class="string">"keyDown"</span>));
<span class="localvariable">this</span>.<span class="property">newGame</span>();
},
<span class="property">newGame</span>: <span class="keyword">function</span>() {
<span class="localvariable">this</span>.<span class="property">level</span> = <span class="atom">0</span>;
<span class="localvariable">this</span>.<span class="property">reset</span>();
},
<span class="property">reset</span>: <span class="keyword">function</span>() {
<span class="keyword">if</span> (<span class="localvariable">this</span>.<span class="property">field</span>)
<span class="localvariable">this</span>.<span class="property">field</span>.<span class="property">remove</span>();
<span class="localvariable">this</span>.<span class="property">field</span> = <span class="variable">SokobanField</span>.<span class="property">create</span>(<span class="variable">sokobanLevels</span>[<span class="localvariable">this</span>.<span class="property">level</span>]);
<span class="localvariable">this</span>.<span class="property">field</span>.<span class="property">place</span>(<span class="localvariable">this</span>.<span class="property">container</span>);
},
<span class="property">keyDown</span>: <span class="keyword">function</span>(<span class="variabledef">event</span>) {
<span class="comment">// To be filled in</span>
}
};</pre><p><a class="paragraph" href="#p1e3f49a8" name="p1e3f49a8"> ¶ </a>The constructor builds a <code>div</code> element to hold the field, along with
two buttons and a title. Note how <code>method</code> is used to attach methods
on the <code>this</code> object to events.</p><p><a class="paragraph" href="#p4ea51667" name="p4ea51667"> ¶ </a>We can put a Sokoban game into our document like this:</p><pre class="code"><span class="keyword">var</span> <span class="variable">sokoban</span> = <span class="variable">SokobanGame</span>.<span class="property">create</span>(<span class="variable">document</span>.<span class="property">body</span>);</pre></div><hr/><div class="block"><a name="exercise4"></a><div class="exercisenum">Ex. 13.4</div><div class="exercise"><p><a class="paragraph" href="#p3640f18" name="p3640f18"> ¶ </a>All that is left to do now is filling in the key event handler.
Replace the <code>keyDown</code> method of the prototype with one that detects
presses of the arrow keys and, when it finds them, moves the player in
the correct direction. The following <code>Dictionary</code> will probably come
in handy:</p><pre class="code"><span class="keyword">var</span> <span class="variable">arrowKeyCodes</span> = <span class="keyword">new</span> <span class="variable">Dictionary</span>({
<span class="atom">37</span>: <span class="keyword">new</span> <span class="variable">Point</span>(-<span class="atom">1</span>, <span class="atom">0</span>), <span class="comment">// left</span>
<span class="atom">38</span>: <span class="keyword">new</span> <span class="variable">Point</span>(<span class="atom">0</span>, -<span class="atom">1</span>), <span class="comment">// up</span>
<span class="atom">39</span>: <span class="keyword">new</span> <span class="variable">Point</span>(<span class="atom">1</span>, <span class="atom">0</span>), <span class="comment">// right</span>
<span class="atom">40</span>: <span class="keyword">new</span> <span class="variable">Point</span>(<span class="atom">0</span>, <span class="atom">1</span>) <span class="comment">// down</span>
});</pre><p><a class="paragraph" href="#p7e0ff0c5" name="p7e0ff0c5"> ¶ </a>After an arrow key has been handled, check <code>this.field.won()</code> to find
out if that was the winning move. If the player won, use <code>alert</code> to
show a message, and go to the next level. If there is no next level
(check <code>sokobanLevels.length</code>), restart the game instead.</p><p><a class="paragraph" href="#p38320932" name="p38320932"> ¶ </a>It is probably wise to stop the events for key presses after handling
them, otherwise pressing arrow-up and arrow-down will scroll your
window, which is rather annoying.</p></div><div class="solution"><pre class="code"><span class="variable">SokobanGame</span>.<span class="property">keyDown</span> = <span class="keyword">function</span>(<span class="variabledef">event</span>) {
<span class="keyword">if</span> (<span class="variable">arrowKeyCodes</span>.<span class="property">contains</span>(<span class="localvariable">event</span>.<span class="property">keyCode</span>)) {
<span class="localvariable">event</span>.<span class="property">stop</span>();
<span class="localvariable">this</span>.<span class="property">field</span>.<span class="property">move</span>(<span class="variable">arrowKeyCodes</span>.<span class="property">lookup</span>(<span class="localvariable">event</span>.<span class="property">keyCode</span>));
<span class="keyword">if</span> (<span class="localvariable">this</span>.<span class="property">field</span>.<span class="property">won</span>()) {
<span class="keyword">if</span> (<span class="localvariable">this</span>.<span class="property">level</span> < <span class="variable">sokobanLevels</span>.<span class="property">length</span> - <span class="atom">1</span>) {
<span class="variable">alert</span>(<span class="string">"Excellent! Going to the next level."</span>);
<span class="localvariable">this</span>.<span class="property">level</span>++;
<span class="localvariable">this</span>.<span class="property">reset</span>();
}
<span class="keyword">else</span> {
<span class="variable">alert</span>(<span class="string">"You win! Game over."</span>);
<span class="localvariable">this</span>.<span class="property">newGame</span>();
}
}
}
};</pre><p><a class="paragraph" href="#p5c329e93" name="p5c329e93"> ¶ </a>It has to be noted that capturing keys like this ― adding a handler
to the <code>document</code> and stopping the events that you are looking for ―
is not very nice when there are other elements in the document. For
example, try moving the cursor around in the text field at the top of
the document. ― It won't work, you'll only move the little man in the
Sokoban game. If a game like this were to be used in a real site, it
is probably best to put it in a frame or window of its own, so that it
only grabs events aimed at its own window.</p></div></div><hr/><div class="block"><a name="exercise5"></a><div class="exercisenum">Ex. 13.5</div><div class="exercise"><p><a class="paragraph" href="#p51b4bdcf" name="p51b4bdcf"> ¶ </a>When brought to the exit, the boulders vanish rather abrubtly. By
modifying the <code>Square.clearContent</code> method, try to show a 'falling'
animation for boulders that are about to be removed. Make them grow
smaller for a moment before, and then disappear. You can use
<code>style.width = "50%"</code>, and similarly for <code>style.height</code>, to make an
image appear, for example, half as big as it usually is.</p></div><div class="solution"><p><a class="paragraph" href="#p1c7ab346" name="p1c7ab346"> ¶ </a>We can use <code>setInterval</code> to handle the timing of the animation. Note
that the method makes sure to clear the interval after it is done. If
you don't do that, it will continue wasting your computer's time until
the page is closed.</p><pre class="code"><span class="variable">Square</span>.<span class="property">clearContent</span> = <span class="keyword">function</span>() {
<span class="variable">self</span>.<span class="property">content</span> = <span class="atom">null</span>;
<span class="keyword">var</span> <span class="variabledef">image</span> = <span class="localvariable">this</span>.<span class="property">tableCell</span>.<span class="property">lastChild</span>;
<span class="keyword">var</span> <span class="variabledef">size</span> = <span class="atom">100</span>;
<span class="keyword">var</span> <span class="variabledef">animate</span> = <span class="variable">setInterval</span>(<span class="keyword">function</span>() {
<span class="localvariable">size</span> -= <span class="atom">10</span>;
<span class="localvariable">image</span>.<span class="property">style</span>.<span class="property">width</span> = <span class="localvariable">size</span> + <span class="string">"%"</span>;
<span class="localvariable">image</span>.<span class="property">style</span>.<span class="property">height</span> = <span class="localvariable">size</span> + <span class="string">"%"</span>;
<span class="keyword">if</span> (<span class="localvariable">size</span> < <span class="atom">60</span>) {
<span class="variable">clearInterval</span>(<span class="localvariable">animate</span>);
<span class="variable">removeElement</span>(<span class="localvariable">image</span>);
}
}, <span class="atom">70</span>);
};</pre><p><a class="paragraph" href="#p62b85871" name="p62b85871"> ¶ </a>Now, if you have a few hours to waste, try finishing all levels.</p></div></div><hr/><div class="block"><p><a class="paragraph" href="#p18f205b2" name="p18f205b2"> ¶ </a><a name="key59"></a><a name="key60"></a>Other event types that can be useful are
<a name="key61"></a><code>focus</code> and <a name="key62"></a><code>blur</code>, which are fired on elements that can be
'focused', such as form inputs. <code>focus</code>, obviously, happens when you
put the focus on the element, for example by clicking on it. <code>blur</code> is
JavaScript-speak for 'unfocus', and is fired when the focus leaves the
element.</p><pre class="code"><span class="variable">addHandler</span>(<span class="variable">$</span>(<span class="string">"textfield"</span>), <span class="string">"focus"</span>, <span class="keyword">function</span>(<span class="variabledef">event</span>) {
<span class="localvariable">event</span>.<span class="property">target</span>.<span class="property">style</span>.<span class="property">backgroundColor</span> = <span class="string">"yellow"</span>;
});
<span class="variable">addHandler</span>(<span class="variable">$</span>(<span class="string">"textfield"</span>), <span class="string">"blur"</span>, <span class="keyword">function</span>(<span class="variabledef">event</span>) {
<span class="localvariable">event</span>.<span class="property">target</span>.<span class="property">style</span>.<span class="property">backgroundColor</span> = <span class="string">""</span>;
});</pre><p><a class="paragraph" href="#p1c7aa223" name="p1c7aa223"> ¶ </a><a name="key63"></a>Another event related to form inputs is <a name="key64"></a><code>change</code>. This
is fired when the content of the input has changed... except that for
some inputs, such as text inputs, it does not fire until the element
is unfocused.</p><pre class="code"><span class="variable">addHandler</span>(<span class="variable">$</span>(<span class="string">"textfield"</span>), <span class="string">"change"</span>, <span class="keyword">function</span>(<span class="variabledef">event</span>) {
<span class="variable">print</span>(<span class="string">"Content of text field changed to '"</span>,
<span class="localvariable">event</span>.<span class="property">target</span>.<span class="property">value</span>, <span class="string">"'."</span>);
});</pre><p><a class="paragraph" href="#p342d25e3" name="p342d25e3"> ¶ </a>You can type all you want, the event will only fire when you click
outside of the input, press tab, or unfocus it in some other way.</p><p><a class="paragraph" href="#p414e8cfc" name="p414e8cfc"> ¶ </a><a name="key65"></a>Forms also have a <a name="key66"></a><code>submit</code> event, which is fired when
they submit. It can be stopped to prevent the submit from taking
place. This gives us a <em>much</em> better way to do the form validation we
saw in the previous chapter. You just register a <code>submit</code> handler,
which stops the event when the content of the form is not valid. That
way, when the user does not have JavaScript enabled, the form will
still work, it just won't have instant validation.</p><p><a class="paragraph" href="#p30928c91" name="p30928c91"> ¶ </a><a name="key67"></a><a name="key68"></a>Window objects have a <a name="key69"></a><code>load</code> event that fires
when the document is fully loaded, which can be useful if your script
needs to do some kind of initialisation that has to wait until the
whole document is present. For example, the scripts on the pages for
this book go over the current chapter to hide solutions to exercises.
You can't do that when the exercises are not loaded yet. There is also
an <a name="key70"></a><code>unload</code> event, firing when the user leaves the document, but
this is not properly supported by all browsers.</p><p><a class="paragraph" href="#p6ad482d9" name="p6ad482d9"> ¶ </a><a name="key71"></a>Most of the time it is best to leave the laying out of a
document to the browser, but there are effects that can only be
produced by having a piece of JavaScript set the exact sizes of some
nodes in a document. When you do this, make sure you also listen for
<a name="key72"></a><code>resize</code> events on the window, and re-calculate the sizes of your
element every time the window is resized.</p></div><hr/><div class="block"><p><a class="paragraph" href="#p14dc3cde" name="p14dc3cde"> ¶ </a>Finally, I have to tell you something about event handlers that you
would rather not know. The Internet Explorer browser (which means, at
the time of writing, the browser used by a majority of web-surfers)
has a bug that causes values to not be cleaned up as normal: Even when
they are no longer used, they stay in the machine's memory. This is
known as a <a name="key73"></a>memory leak, and, once enough memory has been leaked,
will seriously slow down a computer.</p><p><a class="paragraph" href="#p7b28b2b2" name="p7b28b2b2"> ¶ </a>When does this leaking occur? Due to a deficiency in Internet
Explorer's <a name="key74"></a>garbage collector, the system whose purpose it is to
reclaim unused values, when you have a DOM node that, through one of
its properties or in a more indirect way, refers to a normal
JavaScript object, and this object, in turn, refers back to that DOM
node, both objects will not be collected. This has something to do
with the fact that DOM nodes and other JavaScript objects are
collected by different systems ― the system that cleans up DOM nodes
will take care to leave any nodes that are still referenced by
JavaScript objects, and vice versa for the system that collects normal
JavaScript values.</p><p><a class="paragraph" href="#p24cd2757" name="p24cd2757"> ¶ </a>As the above description shows, the problem is not specifically
related to event handlers. This code, for example, creates a bit of
un-collectable memory:</p><pre class="code invalid"><span class="keyword">var</span> <span class="variable">jsObject</span> = {<span class="property">link</span>: <span class="variable">document</span>.<span class="property">body</span>};
<span class="variable">document</span>.<span class="property">body</span>.<span class="property">linkBack</span> = <span class="variable">jsObject</span>;</pre><p><a class="paragraph" href="#p5bb7b5fe" name="p5bb7b5fe"> ¶ </a>Even after such an Internet Explorer browser goes to a different page,
it will still hold on to the <code>document.body</code> shown here. The reason
this bug is often associated with event handlers is that it is
extremely easy to make such circular links when registering a handler.
The DOM node keeps references to its handlers, and the handler, most
of the time, has a reference to the DOM node. Even when this reference
is not intentionally made, JavaScript's scoping rules tend to add it
implicitly. Consider this function:</p><pre class="code invalid"><span class="keyword">function</span> <span class="variable">addAlerter</span>(<span class="variabledef">element</span>) {
<span class="variable">addHandler</span>(<span class="localvariable">element</span>, <span class="string">"click"</span>, <span class="keyword">function</span>() {
<span class="variable">alert</span>(<span class="string">"Alert! ALERT!"</span>);
});
}</pre><p><a class="paragraph" href="#p5ca0cdd9" name="p5ca0cdd9"> ¶ </a>The anonymous function that is created by the <code>addAlerter</code> function
can 'see' the <code>element</code> variable. It doesn't use it, but that does not
matter ― just because it can see it, it will have a reference to it.
By registering this function as an event handler on that same
<code>element</code> object, we have created a circle.</p><p><a class="paragraph" href="#p222868e6" name="p222868e6"> ¶ </a>There are three ways to deal with this problem. The first approach, a
very popular one, is to ignore it. Most scripts will only leak a
little bit, so it takes a long time and a lot of pages before the
problems become noticeable. And, when the problems are so subtle,
who's going to hold <em>you</em> responsible? Programmers given to this
approach will often searingly denounce Microsoft for their shoddy
programming, and state that the problem is not their fault, so <em>they</em>
shouldn't be fixing it.</p><p><a class="paragraph" href="#p7300bfbb" name="p7300bfbb"> ¶ </a>Such reasoning is not entirely without merit, of course. But when half
your users are having problems with the web-pages you make, it is hard
to deny that there is a practical problem. Which is why people working
on 'serious' sites usually make an attempt not to leak any memory.
Which brings us to the second approach: Painstakingly making sure that
no circular references between DOM objects and regular objects are
created. This means, for example, rewriting the above handler like
this:</p><pre class="code"><span class="keyword">function</span> <span class="variable">addAlerter</span>(<span class="variabledef">element</span>) {
<span class="variable">addHandler</span>(<span class="localvariable">element</span>, <span class="string">"click"</span>, <span class="keyword">function</span>() {
<span class="variable">alert</span>(<span class="string">"Alert! ALERT!"</span>);
});
<span class="localvariable">element</span> = <span class="atom">null</span>;
}</pre><p><a class="paragraph" href="#p4334aca3" name="p4334aca3"> ¶ </a>Now the <code>element</code> variable no longer points at the DOM node, and the
handler will not leak. This approach is viable, but requires the
programmer to <em>really</em> pay attention.</p><p><a class="paragraph" href="#p491729a5" name="p491729a5"> ¶ </a>The third solution, finally, is to not worry too much about creating
leaky structures, but to make sure to clean them up when you are done
with them. This means unregistering any event handlers when they are
no longer needed, and registering an <code>onunload</code> event to unregister
the handlers that are needed until the page is unloaded. It is
possible to extend an event-registering system, like our <code>addHandler</code>
function, to automatically do this. When taking this approach, you
must keep in mind that event handlers are not the only possible source
of memory leaks ― adding properties to DOM node objects can cause
similar problems.</p></div><div class="navigation"></div><div class="footer">© <a href="mailto:marijnh@gmail.com">Marijn Haverbeke</a> (<a href="http://creativecommons.org/licenses/by/3.0/">license</a>), written March to July 2007, last modified on May 10 2012.</div></div><script type="text/javascript" src="js/mochi.js"> </script><script type="text/javascript" src="js/codemirror.js"> </script><script type="text/javascript" src="js/ejs.js"> </script></body></html>