"In the security systems of Jurassic Park," Ray Arnold shows the large control panel, "we have different types of sensors. Motion detectors react to movement, pressure sensors to touch, sound sensors to noise. Each type requires a different response. In JavaScript, we have a similar situation - different input types require different handling."
Ray points to the monitor and keyboard. "The mouse and keyboard are the two primary sources of user interaction. Each has its own unique events and behaviors."
The mouse generates dozens of different event types, from simple clicks to advanced drag gestures.
1const dinosaurViewingArea = document.getElementById('viewing-area');
2
3// click - single click (most popular)
4dinosaurViewingArea.addEventListener('click', (event) => {
5 console.log('Single click in the viewing area');
6 console.log('Position:', event.clientX, event.clientY);
7
8 selectDinosaurAtPosition(event.clientX, event.clientY);
9});
10
11// dblclick - double click
12dinosaurViewingArea.addEventListener('dblclick', (event) => {
13 console.log('Double click - zooming in');
14
15 // Prevent text selection on double click
16 event.preventDefault();
17
18 zoomInOnDinosaur(event.clientX, event.clientY);
19});
20
21// auxclick - middle/other button click (not left)
22dinosaurViewingArea.addEventListener('auxclick', (event) => {
23 if (event.button === 1) {
24 console.log('Middle button - opening in new window');
25 openInNewWindow(event.target);
26 }
27});
28
29// contextmenu - right mouse button (context menu)
30dinosaurViewingArea.addEventListener('contextmenu', (event) => {
31 event.preventDefault(); // Block default menu
32
33 console.log('Right button - custom context menu');
34 showCustomContextMenu(event.clientX, event.clientY);
35});
36
37function selectDinosaurAtPosition(x, y) {
38 console.log(`Checking if a dinosaur is at position (${x}, ${y})`);
39 // Selection logic
40}
41
42function zoomInOnDinosaur(x, y) {
43 console.log(`Zooming in on position (${x}, ${y})`);
44 // Zoom logic
45}
46
47function showCustomContextMenu(x, y) {
48 const menu = document.getElementById('custom-menu');
49 menu.style.left = x + 'px';
50 menu.style.top = y + 'px';
51 menu.style.display = 'block';
52
53 console.log('Context menu displayed');
54}1const controlPanel = document.getElementById('control-panel');
2
3// mousedown - mouse button pressed (before release)
4controlPanel.addEventListener('mousedown', (event) => {
5 console.log('Mouse button pressed');
6 console.log('Which button:', event.button); // 0=left, 1=middle, 2=right
7
8 if (event.button === 0) {
9 console.log('Left button - starting drag');
10 startDragging(event);
11 }
12});
13
14// mouseup - mouse button released
15controlPanel.addEventListener('mouseup', (event) => {
16 console.log('Mouse button released');
17
18 if (isDragging) {
19 console.log('Ending drag');
20 stopDragging(event);
21 }
22});
23
24let isDragging = false;
25let dragStartX = 0;
26let dragStartY = 0;
27
28function startDragging(event) {
29 isDragging = true;
30 dragStartX = event.clientX;
31 dragStartY = event.clientY;
32
33 console.log(`Drag started from position (${dragStartX}, ${dragStartY})`);
34}
35
36function stopDragging(event) {
37 const deltaX = event.clientX - dragStartX;
38 const deltaY = event.clientY - dragStartY;
39
40 console.log(`Dragged by: X=${deltaX}px, Y=${deltaY}px`);
41
42 isDragging = false;
43}1const parkMap = document.getElementById('interactive-park-map');
2
3// mousemove - mouse movement (CAUTION: very frequent!)
4// Use with care - can generate hundreds of events per second
5
6let mouseMoveCount = 0;
7
8parkMap.addEventListener('mousemove', (event) => {
9 mouseMoveCount++;
10
11 // Update cursor position
12 updateCursorPosition(event.clientX, event.clientY);
13
14 // If dragging, update position
15 if (isDragging) {
16 updateDragPosition(event.clientX, event.clientY);
17 }
18
19 // Log every 10th event (to avoid flooding the console)
20 if (mouseMoveCount % 10 === 0) {
21 console.log(`Mouse move #${mouseMoveCount}: (${event.clientX}, ${event.clientY})`);
22 }
23});
24
25function updateCursorPosition(x, y) {
26 const coordsDisplay = document.getElementById('mouse-coords');
27 if (coordsDisplay) {
28 coordsDisplay.textContent = `X: ${x}, Y: ${y}`;
29 }
30}
31
32function updateDragPosition(x, y) {
33 const draggableElement = document.getElementById('draggable');
34 if (draggableElement) {
35 draggableElement.style.left = x + 'px';
36 draggableElement.style.top = y + 'px';
37 }
38}
39
40// mouseenter - mouse enters the element's area (doesn't bubble!)
41parkMap.addEventListener('mouseenter', (event) => {
42 console.log('Mouse entered the park map');
43 parkMap.classList.add('mouse-active');
44 showMapControls();
45});
46
47// mouseleave - mouse leaves the element's area (doesn't bubble!)
48parkMap.addEventListener('mouseleave', (event) => {
49 console.log('Mouse left the park map');
50 parkMap.classList.remove('mouse-active');
51 hideMapControls();
52});
53
54// mouseover - mouse enters (bubbles - fires for children too!)
55parkMap.addEventListener('mouseover', (event) => {
56 if (event.target.classList.contains('enclosure-marker')) {
57 const enclosureName = event.target.dataset.name;
58 console.log(`Hovered over enclosure marker: ${enclosureName}`);
59 showEnclosureTooltip(event.target, enclosureName);
60 }
61});
62
63// mouseout - mouse leaves (bubbles)
64parkMap.addEventListener('mouseout', (event) => {
65 if (event.target.classList.contains('enclosure-marker')) {
66 console.log('Left enclosure marker');
67 hideEnclosureTooltip();
68 }
69});
70
71function showMapControls() {
72 const controls = document.getElementById('map-controls');
73 if (controls) {
74 controls.style.display = 'block';
75 }
76}
77
78function hideMapControls() {
79 const controls = document.getElementById('map-controls');
80 if (controls) {
81 controls.style.display = 'none';
82 }
83}"One of the most useful features," says Ray, "is drag & drop."
1class DinosaurCardDragSystem {
2 constructor() {
3 this.draggedElement = null;
4 this.dragOffsetX = 0;
5 this.dragOffsetY = 0;
6 this.initialize();
7 }
8
9 initialize() {
10 const cards = document.querySelectorAll('.dinosaur-card');
11
12 cards.forEach(card => {
13 card.addEventListener('mousedown', (e) => this.handleDragStart(e));
14 });
15
16 document.addEventListener('mousemove', (e) => this.handleDragMove(e));
17 document.addEventListener('mouseup', (e) => this.handleDragEnd(e));
18
19 console.log('Drag & drop system initialized');
20 }
21
22 handleDragStart(event) {
23 this.draggedElement = event.currentTarget;
24
25 const rect = this.draggedElement.getBoundingClientRect();
26 this.dragOffsetX = event.clientX - rect.left;
27 this.dragOffsetY = event.clientY - rect.top;
28
29 this.draggedElement.style.position = 'fixed';
30 this.draggedElement.style.zIndex = '1000';
31 this.draggedElement.classList.add('dragging');
32
33 const dinoName = this.draggedElement.dataset.dinoName;
34 console.log(`Started dragging card: ${dinoName}`);
35
36 event.preventDefault(); // Prevent text selection
37 }
38
39 handleDragMove(event) {
40 if (!this.draggedElement) return;
41
42 const x = event.clientX - this.dragOffsetX;
43 const y = event.clientY - this.dragOffsetY;
44
45 this.draggedElement.style.left = x + 'px';
46 this.draggedElement.style.top = y + 'px';
47 }
48
49 handleDragEnd(event) {
50 if (!this.draggedElement) return;
51
52 const dinoName = this.draggedElement.dataset.dinoName;
53 console.log(`Finished dragging card: ${dinoName}`);
54
55 // Check if the card was dropped in an appropriate zone
56 const dropZone = this.findDropZone(event.clientX, event.clientY);
57
58 if (dropZone) {
59 console.log('Card dropped in zone:', dropZone.id);
60 this.handleDrop(this.draggedElement, dropZone);
61 } else {
62 console.log('Card dropped outside zone - returning to original position');
63 this.returnToOriginalPosition();
64 }
65
66 this.draggedElement.classList.remove('dragging');
67 this.draggedElement = null;
68 }
69
70 findDropZone(x, y) {
71 const dropZones = document.querySelectorAll('.drop-zone');
72
73 for (const zone of dropZones) {
74 const rect = zone.getBoundingClientRect();
75
76 if (x >= rect.left && x <= rect.right &&
77 y >= rect.top && y <= rect.bottom) {
78 return zone;
79 }
80 }
81
82 return null;
83 }
84
85 handleDrop(card, dropZone) {
86 console.log('Adding card to drop zone');
87 dropZone.appendChild(card);
88
89 // Reset styles
90 card.style.position = '';
91 card.style.left = '';
92 card.style.top = '';
93 card.style.zIndex = '';
94 }
95
96 returnToOriginalPosition() {
97 // Return animation
98 this.draggedElement.style.transition = 'all 0.3s ease';
99 this.draggedElement.style.position = '';
100 this.draggedElement.style.left = '';
101 this.draggedElement.style.top = '';
102
103 setTimeout(() => {
104 this.draggedElement.style.transition = '';
105 }, 300);
106 }
107}
108
109// Initialization
110const dragSystem = new DinosaurCardDragSystem();"The keyboard," Ray points to the control panel, "is the second key way of interaction. Keyboard shortcuts, navigation, data input."
1// keydown - key pressed (repeats when held)
2document.addEventListener('keydown', (event) => {
3 console.log('=== KEYDOWN ===');
4 console.log('Key:', event.key); // 'a', 'Enter', 'ArrowUp', etc.
5 console.log('Code:', event.code); // 'KeyA', 'Enter', 'ArrowUp', etc.
6 console.log('KeyCode (deprecated):', event.keyCode);
7 console.log('Is it a repeat:', event.repeat);
8 console.log('Ctrl:', event.ctrlKey);
9 console.log('Shift:', event.shiftKey);
10 console.log('Alt:', event.altKey);
11 console.log('Meta (Cmd/Win):', event.metaKey);
12});
13
14// keyup - key released
15document.addEventListener('keyup', (event) => {
16 console.log(`Key released: ${event.key}`);
17});
18
19// keypress - DEPRECATED! Do not use (use keydown instead)
20// keypress is no longer supported in all browsers1// Keyboard shortcut system for the park
2document.addEventListener('keydown', (event) => {
3 // Ctrl/Cmd + S - Save
4 if ((event.ctrlKey || event.metaKey) && event.key === 's') {
5 event.preventDefault();
6 console.log('Saving park configuration...');
7 saveParkConfiguration();
8 return;
9 }
10
11 // Ctrl/Cmd + F - Search
12 if ((event.ctrlKey || event.metaKey) && event.key === 'f') {
13 event.preventDefault();
14 console.log('Opening dinosaur search');
15 openSearchDialog();
16 return;
17 }
18
19 // Escape - Close all windows
20 if (event.key === 'Escape') {
21 console.log('Closing all dialog windows');
22 closeAllModals();
23 return;
24 }
25
26 // Arrows - Map navigation
27 const arrowKeys = ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'];
28 if (arrowKeys.includes(event.key)) {
29 event.preventDefault();
30
31 const direction = {
32 'ArrowUp': 'north',
33 'ArrowDown': 'south',
34 'ArrowLeft': 'west',
35 'ArrowRight': 'east'
36 }[event.key];
37
38 console.log(`Moving map in direction: ${direction}`);
39 moveMap(direction);
40 return;
41 }
42
43 // Space - Pause/Resume monitoring
44 if (event.key === ' ') {
45 event.preventDefault();
46 console.log('Toggle monitoring pause');
47 toggleMonitoringPause();
48 return;
49 }
50
51 // F1 - Help
52 if (event.key === 'F1') {
53 event.preventDefault();
54 console.log('Opening help');
55 openHelp();
56 return;
57 }
58
59 // Numbers 1-9 - Quick access to enclosures
60 if (event.key >= '1' && event.key <= '9') {
61 const enclosureNumber = parseInt(event.key);
62 console.log(`Switching to enclosure #${enclosureNumber}`);
63 jumpToEnclosure(enclosureNumber);
64 return;
65 }
66});
67
68function saveParkConfiguration() {
69 console.log('Configuration saved');
70}
71
72function openSearchDialog() {
73 console.log('Search dialog opened');
74}
75
76function closeAllModals() {
77 document.querySelectorAll('.modal').forEach(modal => {
78 modal.style.display = 'none';
79 });
80 console.log('All windows closed');
81}
82
83function moveMap(direction) {
84 console.log(`Map moved in direction: ${direction}`);
85}
86
87function toggleMonitoringPause() {
88 console.log('Monitoring paused/resumed');
89}
90
91function openHelp() {
92 console.log('Help opened');
93}
94
95function jumpToEnclosure(number) {
96 console.log(`Jumping to enclosure #${number}`);
97}1const securityCodeInput = document.getElementById('security-code');
2const phoneInput = document.getElementById('phone-number');
3const nameInput = document.getElementById('visitor-name');
4
5// Only digits - security code
6securityCodeInput.addEventListener('keydown', (event) => {
7 // Allow function keys
8 const allowedKeys = [
9 'Backspace', 'Delete', 'Tab', 'Escape', 'Enter',
10 'ArrowLeft', 'ArrowRight', 'Home', 'End'
11 ];
12
13 if (allowedKeys.includes(event.key)) {
14 return; // Allow
15 }
16
17 // Only digits
18 if (event.key >= '0' && event.key <= '9') {
19 // Maximum 6 digits
20 if (securityCodeInput.value.length >= 6 && !event.ctrlKey && !event.metaKey) {
21 event.preventDefault();
22 console.log('Maximum 6 digits');
23 }
24 return;
25 }
26
27 // Everything else - block
28 event.preventDefault();
29 console.log('Only digits are allowed');
30});
31
32// Phone number formatting
33phoneInput.addEventListener('keydown', (event) => {
34 const allowedKeys = [
35 'Backspace', 'Delete', 'Tab', 'Escape', 'Enter',
36 'ArrowLeft', 'ArrowRight', 'Home', 'End'
37 ];
38
39 if (allowedKeys.includes(event.key)) return;
40
41 // Only digits, spaces, hyphens, parentheses
42 const phoneChars = /^[0-9 ()-]$/;
43
44 if (!phoneChars.test(event.key)) {
45 event.preventDefault();
46 console.log('Invalid character in phone number');
47 }
48});
49
50// Auto-capitalize first letter
51nameInput.addEventListener('keyup', (event) => {
52 const value = nameInput.value;
53
54 if (value.length === 1) {
55 nameInput.value = value.toUpperCase();
56 }
57});"The best interfaces," says Ray, "combine both types of input."
1const enclosureCards = document.querySelectorAll('.enclosure-card');
2let selectedCards = new Set();
3
4enclosureCards.forEach(card => {
5 card.addEventListener('click', (event) => {
6 const enclosureId = card.dataset.enclosureId;
7
8 // Different behavior depending on modifier keys
9 if (event.ctrlKey || event.metaKey) {
10 // Ctrl + Click = Toggle selection
11 console.log('Ctrl+Click: Toggle selection');
12
13 if (selectedCards.has(enclosureId)) {
14 selectedCards.delete(enclosureId);
15 card.classList.remove('selected');
16 } else {
17 selectedCards.add(enclosureId);
18 card.classList.add('selected');
19 }
20 } else if (event.shiftKey) {
21 // Shift + Click = Range selection
22 console.log('Shift+Click: Range selection');
23 selectRange(lastSelected, enclosureId);
24 } else {
25 // Normal click = Only this one card
26 console.log('Normal click: Single selection');
27
28 selectedCards.clear();
29 document.querySelectorAll('.enclosure-card.selected').forEach(c => {
30 c.classList.remove('selected');
31 });
32
33 selectedCards.add(enclosureId);
34 card.classList.add('selected');
35 }
36
37 lastSelected = enclosureId;
38 updateSelectionInfo();
39 });
40});
41
42let lastSelected = null;
43
44function selectRange(startId, endId) {
45 console.log(`Selecting range from ${startId} to ${endId}`);
46 // Range selection logic
47}
48
49function updateSelectionInfo() {
50 console.log(`Selected ${selectedCards.size} enclosures`);
51}
52
53// Keyboard navigation through cards
54let focusedCardIndex = 0;
55const cards = Array.from(enclosureCards);
56
57document.addEventListener('keydown', (event) => {
58 // Only when no input field is active
59 if (document.activeElement.tagName === 'INPUT') return;
60
61 switch(event.key) {
62 case 'ArrowRight':
63 case 'ArrowDown':
64 event.preventDefault();
65 focusedCardIndex = Math.min(focusedCardIndex + 1, cards.length - 1);
66 focusCard(focusedCardIndex);
67 break;
68
69 case 'ArrowLeft':
70 case 'ArrowUp':
71 event.preventDefault();
72 focusedCardIndex = Math.max(focusedCardIndex - 1, 0);
73 focusCard(focusedCardIndex);
74 break;
75
76 case 'Enter':
77 case ' ':
78 event.preventDefault();
79 cards[focusedCardIndex].click();
80 break;
81 }
82});
83
84function focusCard(index) {
85 cards.forEach(card => card.classList.remove('focused'));
86 cards[index].classList.add('focused');
87 cards[index].scrollIntoView({ behavior: 'smooth', block: 'nearest' });
88
89 console.log(`Focus on card #${index}: ${cards[index].dataset.enclosureName}`);
90}"Mouse and keyboard events," Ray summarizes, "are the foundation of interactivity. Just as different sensors in Jurassic Park detect different types of threats, different events allow for diverse user interactions. The key is using the right events for the right tasks."
Key mouse events:
click, dblclick, contextmenu - clicksmousedown, mouseup - button controlmousemove - position tracking (use carefully!)mouseenter, mouseleave - hovering (don't bubble)mouseover, mouseout - hovering (bubble)Key keyboard events:
keydown - key pressed (use this one)keyup - key releasedevent.key - character/key name ('a', 'Enter', 'ArrowUp')event.code - physical key ('KeyA', 'Enter', 'ArrowUp')ctrlKey, shiftKey, altKey, metaKey