-
Notifications
You must be signed in to change notification settings - Fork 0
/
script_25.txt
1376 lines (1012 loc) · 65.9 KB
/
script_25.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
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
____________________________________________________________________________
25_dom_events_____________________________________________________________25
____________________________________________________________________________
(aici folosim slide-urile din cap. 24)
Now it's time to talk about DOM events. Events are the key of creating any sort of interactive websites. Doing anything in response to what users are doing. This comes down to responding to a running code when a user does certain things like [s16] clicking, dragging, dropping, hovering, scrolling, submitting a form, pressing a key, moving the mouse wheel, double clicking, copying and pasting on their clipboard, resizing their screen. These are all different things that we can listen for and react to so we can run JavaScript code when somebody clicks, when you click this button, delete something or when you click this button, add something a new pokemon to the page. For example this website: https://play2048.co/ we can use arrow keys, which are listened for, when we click on new game and so on. Another example is https://unsplash.com/ on their website they implement infinite scroll, so as we scroll to the end of the page, new images are loaded. We don't end up with a massive page that loads all at once, instead it is loaded in chunks and new images are appendend as we scroll. So that's responding to our scrolling event and appending let's say 50 more image to the page. These events are all things we can listen for and react to with our code. And this is really the missing piece to combining everything. We've seen that we can create new elements with JavaScript or change elements or add styles, but it's kind of weird to do that when the page loads or do it from the console. A lot of the time we want to do that in response to something: when a user clicks, show something that wasn't there before or delete something, or when a user scrolls, add a bunch of new images to the bottom of the page. When a user hovers over the menu, create a new element and display it. When a user types the left, right, up or down arrow, figure out which one they hit and then move all our divs up or down. These are all things that we can do.
There are three main ways that we can use to respond to user events. So let's run some simple code when an user clicks on something. We'll see three ways for this. First let's head over to events-basics_starter/index.html and make an h1 with text "Events" and a button:
<body>
<h1>Events</h1>
<button>Click Me!</button>
</body>
What we'd like to happen is when we click on this button, let's do something like an alert. So the first option is, that we can add directly inline on the element an attribute called onclick:
<body>
<h1>Events</h1>
<button onclick="alert('you clicked me!')">Click Me!</button>
</body>
inside the quotes we can put the JavaScript code that we want t orun when we click this button. We also have to pay attention to our quotes in there. There are also other downsides to this. First of all it's just bizarre to write our JavaScript in HTML. It makes our markup longer and just more annoying to write, plus having to work inside those quotation marks is kind of annoying too. We have to make sure we're using double quotes on the outside and then single quotes on the inside or vice versa. Also if we want to write multiple lines, it won't look that good.
<body>
<h1>Events</h1>
<button onclick="alert('you clicked me!'); alert('stop clicking')">Click Me!</button>
</body>
But a more legitimate complaint other than saying it's annoying, is that if we want to have a similar behaviour on a second button, we would have to copy the entire thing and move it over to the second button. If we had five buttons that did the same thing, that would be a lot of copying and pasting, duplicating a lot of code. And also if we would later want to change the message, it would be a mess.
<body>
<h1>Events</h1>
<button onclick="alert('you clicked me!'); alert('stop clicking')">Click Me!</button>
<button onclick="alert('you clicked me!'); alert('stop clicking')">Click Me!</button>
<button onclick="alert('you clicked me!'); alert('stop clicking')">Click Me!</button>
<button onclick="alert('you clicked me!'); alert('stop clicking')">Click Me!</button>
</body>
So as we saw, this is not a great approach. The next option is done in our JavaScript. First of all let's include our app.js in our index.html:
<body>
<h1>Events</h1>
<button onclick="alert('you clicked me!'); alert('stop clicking')">Click Me!</button>
<script src="app.js"></script>
</body>
We're going to then need to have some elements that we can select in our app using what we already know how to do: querySelector or document.getElementById. So let's add a new button with id "v2" and text "Click Me (2nd version):
<body>
<h1>Events</h1>
<button onclick="alert('you clicked me!'); alert('stop clicking')">Click Me!</button>
<button id="v2">Click Me (2nd version)</button>
<script src="app.js"></script>
</body>
So we'll select that button over in our JavaScript app.js and then save it to a variable:
const btn = document.querySelector('#v2');
if we do in the f12 devconsole
console.dir(btn)
we'll see that we have a whole bunch of properties that start with on like: onclick, ondrag, ondrop and so on, which are all set to null. We can actually change that property. So let's change the onclick one from null to a function. Let's make a simple function first and then set the onclick property.
const btn = document.querySelector('#v2');
btn.onclick = function () {
console.log("YOU CLICKED ME!")
console.log("I HOPE IT WORKED!!")
}
if we do in f12 devtools
btn.onclick //we whould see that it has been set to a function
Now if we click, it does work. This is definitely better because we are writing our code in JavaScript, instead in HTML inside of quotes. Also this does make it easier to apply stuff to multiple buttons or to repeat this process. If we wanted every button to do that function, we could select them and then loop over them and give each one an onclick and that's a lot better than earlier where we have to copy and paste the code if we wanted to manually change it in many places.
Let's do another example for onmouseenter. This event is for when a pointing device, typically a mouse is initially moved so that it's hotspot is within the element at which the event was fired. An online example from mdn https://developer.mozilla.org/en-US/docs/Web/API/Element/mouseenter_event So let's try this our ourselves. Let's write this time a standalone function scream and give it onmouseenter.
const btn = document.querySelector('#v2');
btn.onclick = function () {
console.log("YOU CLICKED ME!")
console.log("I HOPE IT WORKED!!")
}
function scream() {
console.log("AAAAAHHHHH");
console.log("STOP TOUCHING ME!")
}
btn.onmouseenter = scream;
A thing to note here is that we are not executing the function here. We are simply passing it to onmouseenter and onclick another function. We are setting them and when the user clicks or they go over with the mouse, then they will be executed.
So we worked to far with buttons but we are not limited only to buttons, we can use them on any element, like h1 if we wanted to.
const btn = document.querySelector('#v2');
btn.onclick = function () {
console.log("YOU CLICKED ME!")
console.log("I HOPE IT WORKED!!")
}
function scream() {
console.log("AAAAAHHHHH");
console.log("STOP TOUCHING ME!")
}
btn.onmouseenter = scream;
document.querySelector('h1').onclick = () => {
alert('you clicked the h1!')
}
Also, we always need to wrap it in a function be cause if we do:
document.querySelector('h1').onclick = alert('you clicked the h1!')
the code will execute straight away. So the value should reference a function and not execute right away.
Now the third and best option for working with events is [s17] a special method called addEventListener. Let's first see how it works before we talk about how it's better than the others. This method addEventListener is like the swiss army of events. We pass in an event that we want to listen for and then the second argument is our callback function, the code that we want to run. So let's add a new button in our index.html
<body>
<h1>Events</h1>
<button onclick="alert('you clicked me!'); alert('stop clicking')">Click Me!</button>
<button id="v2">Click Me (2nd version)</button>
<button id="v3">Click Me (3rd version)</button>
<script src="app.js"></script>
</body>
Now we need to select that button, save it to a variable and add the event listener for a click and run an alert on it.
const btn = document.querySelector('#v2');
btn.onclick = function () {
console.log("YOU CLICKED ME!")
console.log("I HOPE IT WORKED!!")
}
function scream() {
console.log("AAAAAHHHHH");
console.log("STOP TOUCHING ME!")
}
btn.onmouseenter = scream;
document.querySelector('h1').onclick = () => {
alert('you clicked the h1!')
}
const btn3 = document.querySelector('#v3');
btn3.addEventListener('click', function () {
alert("CLICKED!");
})
If we want to see the full list of events, we can go to: https://developer.mozilla.org/en-US/docs/Web/Events So the first argument is the event type and the second argument is the function that we want to run with addEventListener. Let's explore now why this is better that the other two options we had before: inline and .onclick. Let's add these two new functions to app.js:
const btn = document.querySelector('#v2');
btn.onclick = function () {
console.log("YOU CLICKED ME!")
console.log("I HOPE IT WORKED!!")
}
function scream() {
console.log("AAAAAHHHHH");
console.log("STOP TOUCHING ME!")
}
btn.onmouseenter = scream;
document.querySelector('h1').onclick = () => {
alert('you clicked the h1!')
}
const btn3 = document.querySelector('#v3');
btn3.addEventListener('click', function () {
alert("CLICKED!");
})
function twist() {
console.log("TWIST!")
}
function shout() {
console.log("SHOUT!")
}
Let's also create a new button with text "Twist & Shout" and id="tas" in index.html.
<body>
<h1>Events</h1>
<button onclick="alert('you clicked me!'); alert('stop clicking')">Click Me!</button>
<button id="v2">Click Me (2nd version)</button>
<button id="v3">Click Me (3rd version)</button>
<button id="tas">Twist & Shout</button>
<script src="app.js"></script>
</body>
So we're going to select that button in our app.js, save it to a variable and then call shout on it with onclick:
const btn = document.querySelector('#v2');
btn.onclick = function () {
console.log("YOU CLICKED ME!")
console.log("I HOPE IT WORKED!!")
}
function scream() {
console.log("AAAAAHHHHH");
console.log("STOP TOUCHING ME!")
}
btn.onmouseenter = scream;
document.querySelector('h1').onclick = () => {
alert('you clicked the h1!')
}
const btn3 = document.querySelector('#v3');
btn3.addEventListener('click', function () {
alert("CLICKED!");
})
function twist() {
console.log("TWIST!")
}
function shout() {
console.log("SHOUT!")
}
const tasButton = document.querySelector('#tas');
tasButton.onclick = shout;
So that works, if we click on that button, we get shout. Now we also want to call twist which is a separate function. So we want twist and then shout for example:
const btn = document.querySelector('#v2');
btn.onclick = function () {
console.log("YOU CLICKED ME!")
console.log("I HOPE IT WORKED!!")
}
function scream() {
console.log("AAAAAHHHHH");
console.log("STOP TOUCHING ME!")
}
btn.onmouseenter = scream;
document.querySelector('h1').onclick = () => {
alert('you clicked the h1!')
}
const btn3 = document.querySelector('#v3');
btn3.addEventListener('click', function () {
alert("CLICKED!");
})
function twist() {
console.log("TWIST!")
}
function shout() {
console.log("SHOUT!")
}
const tasButton = document.querySelector('#tas');
tasButton.onclick = twist;
tasButton.onclick = shout;
We're only getting shout. What's happening here is that we can't have more than one. It's just a property like any other property. If we have a color for example in CSS and it starts as purple and then we make it green, we've lost purple, it got overwritten. It's the same exact thing here. It's just an object which we give a property value of twist and the it gets overwritten with shout. So we cannot have two different callback functions for the same event, at least not using this property method or this property approach. If we instead use addEventListener and do the same thing:
const btn = document.querySelector('#v2');
btn.onclick = function () {
console.log("YOU CLICKED ME!")
console.log("I HOPE IT WORKED!!")
}
function scream() {
console.log("AAAAAHHHHH");
console.log("STOP TOUCHING ME!")
}
btn.onmouseenter = scream;
document.querySelector('h1').onclick = () => {
alert('you clicked the h1!')
}
const btn3 = document.querySelector('#v3');
btn3.addEventListener('click', function () {
alert("CLICKED!");
})
function twist() {
console.log("TWIST!")
}
function shout() {
console.log("SHOUT!")
}
const tasButton = document.querySelector('#tas');
// tasButton.onclick = twist;
// tasButton.onclick = shout;
tasButton.addEventListener('click', twist)
tasButton.addEventListener('click', shout)
This allows us to have as many callbacks as we want. So when we now click on the button, we get both of them. That's one of the big advantages. Nevermind the fact that we could combine those two functions if we wanted them both to run at the same time, but there are situation where we may have more than one click event, more than one callback that we want t orun when something is dragged or dropped and it doesn't make sense to combine them into a single function. So that's one thing we needed to highligh here.
Another reason is that with addEventListener, there are a lot of other arguments that we can pass in as different options. This options object can contain different options, including things like once:
tasButton.addEventListener('click', twist, {once: true})
tasButton.addEventListener('click', shout)
now it will only run twist once and afterwards that addEventListener is gone. More on this can be found on mdn https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener This is not that common but that's proof of the fact that we can pass in these options. There are other options but they require a bit more explanation, things like capture for example. So with addEventListener we have more control over how the event listeners work and when our callbacks are triggered. Thhere is also a companion method called removeEventListener, which we can use to easily remove event listeners. This is useful for complex appgs where lots of stuff going on and lots of events, event listenenrs and it can become too crowded and we can have performance issues if we are not managing those events or those listeners, so we can remove them if we add them with addEventListener. So these are a couple of reasons to use addEventListener, also most people use it and what we'll see the most.
Now it's time for a little bit of exercise. We'll start a pretty simple project called random-colors. So we have an h1 and then a button underneath. When we click the button, we get a random new background color and the h1 now displays that background color in rgb value. We're using RGB colors because they are the easies to randomly generate. It's just three numbers from zero to 255. And that's all there is to it. Every time we click, we get a new color and we're showing that new color up top. So we're updating the background color of the body and the text of the heading. So let's do it. Let's head over to our random-colors_starter/index.html and include our app.js, make an h1 with text Welcom and a button with text Click Me:
<body>
<h1>Welcome!</h1>
<button>Click Me</button>
<script src="app.js"></script>
</body>
Now let's head over to random-colors_starter/app.js and select our button, save it to a variable and add an addEventListener to it with a console log first to see that it works:
const button = document.querySelector('button');
button.addEventListener('click', function () {
console.log('i work nicely')
}
now what we want to do is generate a random color. We can simplify that random color part and first just change the body's color once. So let's pick a color: olive and give the body that background color. Now we talked about the fact that generally is not a good idea to modify styles using the inline properties in the DOM. Usually, if we're doing a lot of styles at once, we create a class in a CSS stylesheet and we apply that class. But that doesn't work for randomly creating a color. It's just not easy to do it. We could predefine like ten different color classes, bu we cannot define random asset claseses. So instead what we're going to do is just set the background color property on the body. To access the body we can do document.querySelector('body') but there's a shortcut. We can just access the document.body directly.
const button = document.querySelector('button');
button.addEventListener('click', function () {
document.body.style.backgroundColor = 'olive';
}
Now we're seeing that change happen once. So that's working. Now that we simplified it, we can now generate the random color. So to generate the random color, we will need three numbers. The RGB follows this format: one number from 0 to 255 comma another number from 0 to 255 comma and a last number from 0 to 255:
rgb(200,100,24)
So we need to generate three numbers from 0 to 255.
const button = document.querySelector('button');
button.addEventListener('click', function () {
document.body.style.backgroundColor = 'olive';
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
}
now that we have generate the three random rgb numbers, we need to set the background to be that color. So we need to make a new variable and give it the rgb value via string template literal:
const button = document.querySelector('button');
button.addEventListener('click', function () {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
newColor = `rgb(${r}, ${g}, ${b})`
document.body.style.backgroundColor = newColor;
}
and it appears that it's working. Now we can update our h1 to have this new color as its actual text. So we need to select the h1:
const button = document.querySelector('button');
const h1 = document.querySelector('h1');
button.addEventListener('click', function () {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
newColor = `rgb(${r}, ${g}, ${b})`
document.body.style.backgroundColor = newColor;
}
and then we need to overwrite the text inside of the h1. We can do that with innerText:
const button = document.querySelector('button');
const h1 = document.querySelector('h1');
button.addEventListener('click', function () {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
newColor = `rgb(${r}, ${g}, ${b})`
document.body.style.backgroundColor = newColor;
h1.innerText = newColor;
}
And there we are. We can also now make this look nicer by moving the random number generating part to a new function called makeRandColor which will instead return our new string via string template literal:
const button = document.querySelector('button');
const h1 = document.querySelector('h1');
button.addEventListener('click', function () {
const newColor = makeRandColor();
document.body.style.backgroundColor = newColor;
h1.innerText = newColor;
})
const makeRandColor = () => {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
return `rgb(${r}, ${g}, ${b})`;
}
so we saved it into a new function for the case we want to reuse it somewhere. For something simple like this it doesn't really matter but it's good practice to get in the habit of. Anytime we have some functionality that could stand alone, like make random color, we should move it to a separate function. We could also center the text or another cool thing that can be done is write some code to change the color of the text depending on the color itself, the color of the rgb. Basically a lower number, like three low numbers for example rgb(11,12,17) is very close to black. Black is 0,0,0. So we could figure out some thershold and if there are values less than 100 for example, we could give it a white color, otherwise keep it black.
Now we'll talk about how the keyword this comes into play when we're working with events. For this we have in events-and-this_starter/index.html some larger buttons with the text of click. What we want to do is, when we click on each button, to give them a random background color. So we'll head over in events-and-this_starter/app.js and we'll start by making sure we can click on each button and something happens. We have our function that we wrote for our previous exercise. We're just going to leave it and ignore it for now. So let's select all buttons with querySelectorAll:
const makeRandColor = () => {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
return `rgb(${r}, ${g}, ${b})`;
}
const buttons = document.querySelectorAll('button');
now let's loop over all those buttons and do a very simple event handler first:
const makeRandColor = () => {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
return `rgb(${r}, ${g}, ${b})`;
}
const buttons = document.querySelectorAll('button');
for (let button of buttons) {
button.addEventListener('click', function() {
console.log('clicked')
})
}
now we have to make sure everything is connecter properly so we should be seeing clicked in f12 devtools. So every single button is going to console.log('clicked'). But now what we want to do is change the background color of each button. For that we have the makeRandColor, so we can use that. All that we need to do is figure out how to reference the particular button, the one button that was clicked. Well in the for loop we have access to a button so we can use:
const makeRandColor = () => {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
return `rgb(${r}, ${g}, ${b})`;
}
const buttons = document.querySelectorAll('button');
for (let button of buttons) {
button.addEventListener('click', function() {
button.style.backgroundColor = makeRandColor();
})
}
now we added an event listener to each button. Now it works for us. What we want to see now is what if we wanted to move this makeRandColor to another function in order to affect other elements as well, not just buttons. Let's first add a couple of h1's in index.html with the text of "Click Me!". Now to do the same for the h1's as we did for buttons, we would have some duplicated logic:
const makeRandColor = () => {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
return `rgb(${r}, ${g}, ${b})`;
}
const buttons = document.querySelectorAll('button');
for (let button of buttons) {
button.addEventListener('click', function() {
button.style.backgroundColor = makeRandColor();
})
}
const h1s = document.querySelectorAll('h1');
for (let h1 of h1s) {
h1.addEventListener('click', function() {
h1.style.backgroundColor = makeRandColor();
})
}
and now our h1's get a random bg color as well. Let's say we also want a random text color for the buttons and the h1's
const makeRandColor = () => {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
return `rgb(${r}, ${g}, ${b})`;
}
const buttons = document.querySelectorAll('button');
for (let button of buttons) {
button.addEventListener('click', function() {
button.style.backgroundColor = makeRandColor();
button.style.color = makeRandColor();
})
}
const h1s = document.querySelectorAll('h1');
for (let h1 of h1s) {
h1.addEventListener('click', function() {
h1.style.backgroundColor = makeRandColor();
h1.style.color = makeRandColor();
})
}
now the text color changes too when we click on them. Let's say this is now too much to duplicate, the only difference is that we are doing it to a button and then to a h1. We would now like to make a generic function that will do that. If we write a function colorize(), how will it work if we don't have access to h1 or to button? This is a generic function. This is where the keyword "this" comes into play:
const makeRandColor = () => {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
return `rgb(${r}, ${g}, ${b})`;
}
const buttons = document.querySelectorAll('button');
for (let button of buttons) {
button.addEventListener('click', function() {
button.style.backgroundColor = makeRandColor();
button.style.color = makeRandColor();
})
}
const h1s = document.querySelectorAll('h1');
for (let h1 of h1s) {
h1.addEventListener('click', function() {
h1.style.backgroundColor = makeRandColor();
h1.style.color = makeRandColor();
})
}
function colorize(){
}
so inside an event handler callback, the function that we pass into addEventListener, the keyword 'this' is going to refer to whatever was clicked on, that triggered this function being called. Whatever that event occured on the keyword 'this' will refer to that element. Now if you remember the keyword 'this' is dependent on the execution context or the invocation context. So that's not a hard rule that the keyword 'this' will always refer to the element. There are ways of changing the keyword 'this' inside our function. Let's see how it behaves in our code now:
const makeRandColor = () => {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
return `rgb(${r}, ${g}, ${b})`;
}
const buttons = document.querySelectorAll('button');
for (let button of buttons) {
button.addEventListener('click', function() {
console.log(this)
this.style.backgroundColor = makeRandColor();
this.style.color = makeRandColor();
})
}
const h1s = document.querySelectorAll('h1');
for (let h1 of h1s) {
h1.addEventListener('click', function() {
h1.style.backgroundColor = makeRandColor();
h1.style.color = makeRandColor();
})
}
function colorize(){
}
now we log the keyword 'this' for the button. The value for the keyword 'this' is the value of the exact h1 we're clicking. So we could replace them with the keyword 'this' and everything would work perfectly like before. So let's finish our colorize function and rewrite the button and h1 for loops to use the colorize function:
const makeRandColor = () => {
const r = Math.floor(Math.random() * 255);
const g = Math.floor(Math.random() * 255);
const b = Math.floor(Math.random() * 255);
return `rgb(${r}, ${g}, ${b})`;
}
const buttons = document.querySelectorAll('button');
for (let button of buttons) {
button.addEventListener('click', colorize)
}
const h1s = document.querySelectorAll('h1');
for (let h1 of h1s) {
h1.addEventListener('click', colorize)
}
function colorize() {
this.style.backgroundColor = makeRandColor();
this.style.color = makeRandColor();
}
now this function will work for both our buttons and h1s. This is a simple example to illustrate how the keyword 'this' works. When we use it inside of a callback function that is invoked by some event handler, the keyword 'this' will refer to that something that was acted upon or interacted with, whatever the even was triggered on.
Next up we'll talk about two topics that are kind of related. The first one is called event object and the second is called keyboard events. So let's start with event object. Let's go to event-object_starter/index.html and add in a button with text "CLICK!" and also include our app.js to our body at the end:
<body>
<button>CLICK</button>
<script src="app.js"></script>
</body>
Now let's got in app.js and select it with querySelector and add an event listener for click and with a function that makes an alert with text "click":
document.querySelector('button').addEventListener('click', function () {
alert('click')
})
now there's this thing called the event object that is automatically passed in to our callback here, the function () { alert('click')}. We're not using it, we're not capturing it but it's being passed in every single time. so we can capture it, we can put a parameter in here. So let's name it evt and console.log it:
document.querySelector('button').addEventListener('click', function (evt) {
console.log(evt)
})
It's an object constructed for us and it contains information about the object. In this case it's a MouseEvent object and it has things like clientX and clientY, which are relative to the window where we click, coordinates. We also have type, target and so on. So this is an object that contains information about the event. If we click on the far right side of the button, we'll get different clientX and clientY coordinates. If we ever needed that, let's say we wanted to do a nice animation or highlight, or to make something appear on the page, we could use this event object. The reason we're looking at this now is that we often need to rely on the event object when we're working with keyboard events. Frequently we want to know what key was pressed and that information is included in the keyboard event object. We can listen for keyboard events on the entire window. Like if we're making a game and we wanted to do something when we hit the arrow keys. So let's add a single input of type text in our index.html:
<body>
<button>CLICK</button>
<input type="text">
<script src="app.js"></script>
</body>
Now let's select it in our app.js and save it to a variable:
const input = document.querySelector('input');
Let's add an event listener for keydown and execute some console logs for it:
const input = document.querySelector('input');
input.addEventListener('keydown', function () {
console.log("KEYDOWN")
}
so this is on the input when the keydown event occurs, it prints KEYDOWN. Now if we press something in the input field, we'll get the console logs. We can also add one for keyup:
const input = document.querySelector('input');
input.addEventListener('keydown', function () {
console.log("KEYDOWN")
}
input.addEventListener('keyup', function () {
console.log("KEYUP")
}
Now if we keep the key pressed, we'll get more console logs and then when we release it we get another last log from the keyup. Now a lot of the time we would like to know what key was pressed. So to do that we need to use the event object. So let's put an e inside of keydown for now and console.log it. Also let's comment out the keyup part too:
const input = document.querySelector('input');
input.addEventListener('keydown', function (e) {
console.log(e)
}
// input.addEventListener('keyup', function () {
// console.log("KEYUP")
// })
Now on this keyboard event, what we care about are the properties called code and key. So let's print them out:
document.querySelector('button').addEventListener('click', function (evt) {
console.log(evt)
})
const input = document.querySelector('input');
input.addEventListener('keydown', function (e) {
console.log(e.key)
console.log(e.code)
})
// input.addEventListener('keyup', function () {
// console.log("KEYUP")
// })
As we can see, the key and code are different sometimes and sometimes not. For Shift we have Shift and ShiftLeft because we have two shifts on the keyboard. When we press backspace they are the same. We can use either, but it really depends on what our goal is. If we care about the character that was generated, then we can use key. If we care about the actual key that was pressed on the keyboard, the location of it, then we can use code. A user can change their language settings on their keyboard so the same exact letter q will have a different key, but it will always have the same code. So if we want the users for example to always use WASD in our game, we would use code because the position is always the same on their keyboard. And if we just want the letters WASD for something and we don't care what key was used to generate them, we can just use key.
So let's do an example and image we are making a game. We want to listen for up, down, left and right arrows. So let's add another event listener for keydown and console log out first the keys that we are pressing to find our their code. We can alos use directly window.addEventListener.
document.querySelector('button').addEventListener('click', function (evt) {
console.log(evt)
})
// const input = document.querySelector('input');
// input.addEventListener('keydown', function (e) {
// console.log(e.key)
// console.log(e.code)
// })
// input.addEventListener('keyup', function () {
// console.log("KEYUP")
// })
window.addEventListener('keydown', function (e) {
console.log(e.code)
})
now we see that codes that we need are: ArrowUp, ArrowDown, ArrowLeft and ArrowRight. So we can use a switch statement for them and console log the key what was pressed. As a default we can console log "IGNORED!":
document.querySelector('button').addEventListener('click', function (evt) {
console.log(evt)
})
// const input = document.querySelector('input');
// input.addEventListener('keydown', function (e) {
// console.log(e.key)
// console.log(e.code)
// })
// input.addEventListener('keyup', function () {
// console.log("KEYUP")
// })
window.addEventListener('keydown', function (e) {
switch (e.code) {
case 'ArrowUp':
console.log("UP!");
break;
case 'ArrowDown':
console.log("DOWN!");
break;
case 'ArrowLeft':
console.log("LEFT!");
break;
case 'ArrowRight':
console.log("RIGHT!");
break
default:
console.log("IGNORED!")
}
})
now for arrow keys we get the direction and for other keys we get the console log "IGNORED!". Now we can use those to do something.
So to recap, the event object is something we have access to in every event handler, it's passed in automatically. We're not calling the function, but when it's called for use, it's passed in. We can also add in a parameter to capture it with "e" or "evt" and it contains information about the event. The type of event determines what information is inside the object, what's included in it. So with keyboard events we have the key letter itself, the code for the actual location, the actual physical key. Also we saw that we can listen for those specific events, both on particular inputs as an example or on the window as a whole.
Next up we're going to talk about working with forms. This is a very important part of creating web applications, we usually have some forms and we want to do something when the user submits those forms. Just to recap how those forms typically work without JavaScript. So let's head over to our form-events_starte/index.html and add in an h1 that says Form Events and a form with attribute action with value "/dogs":
<body>
<h1>Form Events</h1>
<form action="/dogs">
</form>
</body>
now let's add an input to it of type text and name username, another input with type password and name password and finally a button with innerText Register
<body>
<h1>Form Events</h1>
<form action="/dogs">
<input type="text" name="username">
<input type="text" name="tweet">
<button>Register</button>
</form>
</body>
now if we open index.html in our browser and enter in a user and a password an press Register, it will submit our data to /dogs?username=ourTypedInUser&password=ourTypedInPassword. Whatever our action attribute is in form, is where our data will be sent to. Now what we're going to do is use JavaScript to stop that and do something with the data on the same page. So the default behaviour is for that form to submit and to reload the page and take us somewhere else. We're going to say stop, that, don't do that and give me the username and password and let's do something here locally. Now let's do something like a comment: when we submit a comment, it shouldn't refresh the page or go somewhere else, instead it should just append the comment at the bottom of the page. So let's change our form a bit. Let's add to input username a placeholder of username and change the password type to text and name to tweet and add in a placeholder of "tweet". Then lastly let's change the button's innerText to Post Tweet.
<body>
<h1>Form Events</h1>
<form action="/dogs" id="tweetForm">
<input type="text" name="username" placeholder="username">
<input type="text" name="tweet" placeholder="tweet">
<button>Post Tweet</button>
</form>
</body>
Now what we want to do here is when a user submits this form, we want to just run some basic code to start. So let's also add in our app.js script at the bottom of body in index.html. Also let's add in an id for our form with value tweetForm
<body>
<h1>Form Events</h1>
<form action="/dogs" id="tweetForm">
<input type="text" name="username" placeholder="username">
<input type="text" name="tweet" placeholder="tweet">
<button>Post Tweet</button>
</form>
<script src="app.js"></script>
</body>
Now let's go in app.js. What we want to listen here for is the form submission. So let's select the tweetForm with querySelector and save it to a variable:
const tweetForm = document.querySelector('#tweetForm');
now the event we want to listen for is the submit:
const tweetForm = document.querySelector('#tweetForm');
tweetForm.addEventListener('submit', function (e) {
alert('SUBMIT!!!")
})
now let's verify that it works. It says submit but when we click ok it sends us to the dogs page. If we don't want to do anything after that, so basically stop it from going to the other page, we need to tell the browser that we do not want to have that default behaviour. So what we can do is on that tweetForm.addEventListener, inside the function, we can use a particular method called preventDefault(), which will stop any default behaviour as a result of that event:
const tweetForm = document.querySelector('#tweetForm');
tweetForm.addEventListener('submit', function (e) {
e.preventDefault();
console.log('SUBMIT!!!")
})
now we are still on the same page. Now if we want to use this information from the form we would first need some kind of section in which we could post our comments in. So let's go back to our index.html and add an h2 with innerText "Tweets:" after our form and after the h2 let's add an ul with id="tweets":
<body>
<h1>Form Events</h1>
<form action="/dogs" id="tweetForm">
<input type="text" name="username" placeholder="username">
<input type="text" name="tweet" placeholder="tweet">
<button>Post Tweet</button>
</form>
<h2>Tweets:</h2>
<ul id="tweets">
</ul>
<script src="app.js"></script>
</body>
no we want to append the comments asa a new li's in our ul. So we need to figure out how we can extract that username and that tweet text here. To do that we can use the value attribute from the input. So for example if we enter some text in our input and then do f12 devtools:
document.querySelectorAll('input')[1] //this selects the second input which is the comment.
document.querySelectorAll('input')[1].value //on this input there is an attribute called value and it corresponds to whatever is in there.
So if we were to change that to something else like
document.querySelectorAll('input')[1].value = 'I like cars'
we just updated what's in the input. So that attribute value can also be accessed through getAttribute or setAttribute. But this is the shorter way of accessing it. So what we want to do is, when we access it, take the value from the inputs and make a new li in our Tweets ul that has the username and the tween and we'll go from there. One option is to select those inputs ahead of time in our tweetForm function:
const tweetForm = document.querySelector('#tweetForm');
tweetForm.addEventListener('submit', function (e) {
e.preventDefault();
console.log('SUBMIT!!!")
const usernameInput = document.querySelectorAll('input')[0];
const tweetInput = document.querySelectorAll('input')[1];
console.log(usernameInput.value, tweetInput.value)
})
so we are console logging the first and then the second input fields when we click on the Post Tweet button. Now we are successfully printing out those values, so we can now do something with them. But before we do that, if we had a lot of pieces of inforamtion in our for, more input fields, it can get annoying, having to select every input individually. For this we have on every form element a property called elements, so let's have a look at that in f12 devconsole:
console.dir(tweetForm)
The elements is just a collection of the form elements inside it. What's also nice is that it's an ordered collection. So we can say give me elements of zero, of one and so on. This all depends of the order, if we swap the elements between them, the order here in the collection will also change. But if we give our elements a name, that name will be set up as a property name in elements. So we can ask for tweetForm.elements.tweet for example and we can quickly get to the input itself. So let's do that instead of our querySelectorAll in app.js:
const tweetForm = document.querySelector('#tweetForm');
tweetForm.addEventListener('submit', function (e) {
e.preventDefault();
// const usernameInput = document.querySelectorAll('input')[0];
// const tweetInput = document.querySelectorAll('input')[1];
const username = tweetForm.elements.username,value;
const tweet = tweetForm.elements.tweet,value;
console.log(username.value, tweet.value)
})
So that is an easier way to go from a form to the elements and their values that are contained inside of that form, rather than having to select that form from scratch. We can also iterate over the elements if we have a lot of them.
Alright, now let's make a new li with JavaScript:
const tweetForm = document.querySelector('#tweetForm');
tweetForm.addEventListener('submit', function (e) {
e.preventDefault();
// const usernameInput = document.querySelectorAll('input')[0];
// const tweetInput = document.querySelectorAll('input')[1];
const username = tweetForm.elements.username,value;
const tweet = tweetForm.elements.tweet,value;
const newTweet = document.createElement('li');
})
Now we would like a bold tag with the username inside it.
const tweetForm = document.querySelector('#tweetForm');
tweetForm.addEventListener('submit', function (e) {
e.preventDefault();
// const usernameInput = document.querySelectorAll('input')[0];
// const tweetInput = document.querySelectorAll('input')[1];
const username = tweetForm.elements.username,value;
const tweet = tweetForm.elements.tweet,value;
const newTweet = document.createElement('li');
const bTag = document.createElement('b');
bTag.append(username)
newTweet.append(bTag);
newTweet.append(`- ${tweet}`)
console.log(newTweet)
})
So we made a bold tag with the username inside it, we appended it to our newTweet and than we did a string template literal dash tweet to append the tweet text itself. So it would look something like this, step by step
<b></b>
<b>Cristian</b>
<li><b>Cristian</b></li>
<li><b>Cristian</b> - nice tweet text here</li>
Now all we have to do is append that to our ul. So we need to make some kind of tweetsContainer which selects the id of "tweets" from our ul:
const tweetForm = document.querySelector('#tweetForm');
const tweetsContainer = document.querySelector('#tweets');
tweetForm.addEventListener('submit', function (e) {
e.preventDefault();
// const usernameInput = document.querySelectorAll('input')[0];
// const tweetInput = document.querySelectorAll('input')[1];
const username = tweetForm.elements.username,value;
const tweet = tweetForm.elements.tweet,value;
const newTweet = document.createElement('li');
const bTag = document.createElement('b');
bTag.append(username)
newTweet.append(bTag);
newTweet.append(`- ${tweet}`)
console.log(newTweet)
})
and ad the end, instead of our console.log, we'll do tweets.tweetsContainer.appen(newTweet)
const tweetForm = document.querySelector('#tweetForm');
const tweetsContainer = document.querySelector('#tweets');
tweetForm.addEventListener('submit', function (e) {
e.preventDefault();
// const usernameInput = document.querySelectorAll('input')[0];
// const tweetInput = document.querySelectorAll('input')[1];
const username = tweetForm.elements.username,value;
const tweet = tweetForm.elements.tweet,value;
const newTweet = document.createElement('li');
const bTag = document.createElement('b');
bTag.append(username)
newTweet.append(bTag);
newTweet.append(`- ${tweet}`)
tweetsContainer.append(newTweet);
})
and it works. Now we still have the usernameInput and tweetInput there. We can empty them by setting their .value to an empty string ''.
const tweetForm = document.querySelector('#tweetForm');
const tweetsContainer = document.querySelector('#tweets');
tweetForm.addEventListener('submit', function (e) {
e.preventDefault();
// const usernameInput = document.querySelectorAll('input')[0];
// const tweetInput = document.querySelectorAll('input')[1];
const username = tweetForm.elements.username,value;
const tweet = tweetForm.elements.tweet,value;
const newTweet = document.createElement('li');
const bTag = document.createElement('b');
bTag.append(username)
newTweet.append(bTag);
newTweet.append(`- ${tweet}`)
tweetsContainer.append(newTweet);
usernameInput.value = '';
tweetInput.value = '';
})
What we can do now is move the actual creation of this newTweet to a separate function called addTweet:
const tweetForm = document.querySelector('#tweetForm');
const tweetsContainer = document.querySelector('#tweets');
tweetForm.addEventListener('submit', function (e) {
e.preventDefault();
// const usernameInput = document.querySelectorAll('input')[0];
// const tweetInput = document.querySelectorAll('input')[1];
const username = tweetForm.elements.username,value;
const tweet = tweetForm.elements.tweet,value;
addTweet(usernameInput.value, tweetInput.value)
usernameInput.value = '';
tweetInput.value = '';
});
const addTweet = (username, tweet) => {
const newTweet = document.createElement('li');
const bTag = document.createElement('b');