3.4 אירועים פתרון
פתרון - אירועים¶
פתרון תרגיל 1¶
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<title>Mouse Events</title>
</head>
<body>
<div id="box" style="width: 200px; height: 200px; background: lightblue; border: 2px solid navy;"></div>
<p id="info">Interact with the box above</p>
<script>
const box = document.getElementById("box");
const info = document.getElementById("info");
function randomColor() {
return "#" + Math.floor(Math.random() * 16777215).toString(16).padStart(6, "0");
}
// 1. click - random color
box.addEventListener("click", function() {
box.style.backgroundColor = randomColor();
});
// 2. double click - reset color
box.addEventListener("dblclick", function() {
box.style.backgroundColor = "lightblue";
});
// 3. mouse enter - thick border
box.addEventListener("mouseenter", function() {
box.style.borderWidth = "5px";
});
// 4. mouse leave - reset border
box.addEventListener("mouseleave", function() {
box.style.borderWidth = "2px";
});
// 5. mouse move - show coordinates
box.addEventListener("mousemove", function(event) {
info.textContent = "Mouse: X=" + event.clientX + ", Y=" + event.clientY;
});
// 6. context menu - prevent and show message
box.addEventListener("contextmenu", function(event) {
event.preventDefault();
info.textContent = "Right-click is disabled on this box!";
});
</script>
</body>
</html>
פתרון תרגיל 2¶
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<title>Keyboard Events</title>
</head>
<body>
<input type="text" id="text-input" placeholder="Type something..." style="width: 300px; padding: 8px;">
<p>Characters typed: <span id="char-count">0</span></p>
<p>Last key: <span id="last-key">none</span></p>
<p>Key code: <span id="key-code">none</span></p>
<p>Modifiers: <span id="modifiers">none</span></p>
<div id="output" style="border: 1px solid #ddd; padding: 10px; min-height: 50px; margin-top: 10px;"></div>
<script>
const input = document.getElementById("text-input");
const charCount = document.getElementById("char-count");
const lastKey = document.getElementById("last-key");
const keyCode = document.getElementById("key-code");
const modifiers = document.getElementById("modifiers");
const output = document.getElementById("output");
input.addEventListener("keydown", function(event) {
// 5. prevent numbers
if (event.key >= "0" && event.key <= "9") {
event.preventDefault();
return;
}
// 1. update last key and key code
lastKey.textContent = event.key;
keyCode.textContent = event.code;
// 2. show modifiers
const mods = [];
if (event.shiftKey) mods.push("Shift");
if (event.ctrlKey) mods.push("Ctrl");
if (event.altKey) mods.push("Alt");
if (event.metaKey) mods.push("Meta");
modifiers.textContent = mods.length > 0 ? mods.join(" + ") : "none";
// 3. Enter - add text to output
if (event.key === "Enter") {
const text = input.value.trim();
if (text !== "") {
const p = document.createElement("p");
p.textContent = text;
output.appendChild(p);
input.value = "";
}
}
// 4. Escape - clear input
if (event.key === "Escape") {
input.value = "";
}
});
// update character count on input event
input.addEventListener("input", function() {
charCount.textContent = input.value.length;
});
</script>
</body>
</html>
פתרון תרגיל 3¶
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<title>Form Validation</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; max-width: 400px; }
.form-group { margin: 15px 0; }
label { display: block; margin-bottom: 5px; }
input { padding: 8px; width: 100%; border: 2px solid #ddd; border-radius: 4px; box-sizing: border-box; }
input.valid { border-color: green; }
input.invalid { border-color: red; }
.feedback { font-size: 13px; display: block; margin-top: 3px; }
.feedback.error { color: red; }
.feedback.success { color: green; }
button { padding: 10px 20px; margin-top: 10px; }
button:disabled { opacity: 0.5; cursor: not-allowed; }
</style>
</head>
<body>
<h1>Registration</h1>
<form id="signup-form">
<div class="form-group">
<label>Username:</label>
<input type="text" id="username" required>
<span class="feedback" id="username-fb"></span>
</div>
<div class="form-group">
<label>Email:</label>
<input type="email" id="email" required>
<span class="feedback" id="email-fb"></span>
</div>
<div class="form-group">
<label>Password:</label>
<input type="password" id="password" required>
<span class="feedback" id="password-fb"></span>
</div>
<div class="form-group">
<label>Confirm Password:</label>
<input type="password" id="confirm-password" required>
<span class="feedback" id="confirm-fb"></span>
</div>
<button type="submit" id="submit-btn" disabled>Register</button>
</form>
<script>
const form = document.getElementById("signup-form");
const usernameInput = document.getElementById("username");
const emailInput = document.getElementById("email");
const passwordInput = document.getElementById("password");
const confirmInput = document.getElementById("confirm-password");
const submitBtn = document.getElementById("submit-btn");
// track validity of each field
const validity = {
username: false,
email: false,
password: false,
confirm: false
};
function updateSubmitButton() {
const allValid = validity.username && validity.email &&
validity.password && validity.confirm;
submitBtn.disabled = !allValid;
}
function setFeedback(id, isValid, message) {
const fb = document.getElementById(id);
fb.textContent = message;
fb.className = "feedback " + (isValid ? "success" : "error");
}
// 1. username validation on input
usernameInput.addEventListener("input", function() {
const value = usernameInput.value.trim();
if (value.length === 0) {
usernameInput.className = "";
document.getElementById("username-fb").textContent = "";
validity.username = false;
} else if (value.length < 3) {
usernameInput.className = "invalid";
setFeedback("username-fb", false, "At least 3 characters required");
validity.username = false;
} else {
usernameInput.className = "valid";
setFeedback("username-fb", true, "Looks good!");
validity.username = true;
}
updateSubmitButton();
});
// 2. email validation on blur
emailInput.addEventListener("blur", function() {
const value = emailInput.value.trim();
if (value === "") {
emailInput.className = "";
document.getElementById("email-fb").textContent = "";
validity.email = false;
} else if (value.includes("@") && value.includes(".")) {
emailInput.className = "valid";
setFeedback("email-fb", true, "Valid email!");
validity.email = true;
} else {
emailInput.className = "invalid";
setFeedback("email-fb", false, "Enter a valid email (must contain @ and .)");
validity.email = false;
}
updateSubmitButton();
});
// 3. password validation on input
passwordInput.addEventListener("input", function() {
const value = passwordInput.value;
const hasUpperCase = /[A-Z]/.test(value);
const hasNumber = /[0-9]/.test(value);
const hasMinLength = value.length >= 8;
if (value.length === 0) {
passwordInput.className = "";
document.getElementById("password-fb").textContent = "";
validity.password = false;
} else if (!hasMinLength) {
passwordInput.className = "invalid";
setFeedback("password-fb", false, "At least 8 characters (" + value.length + "/8)");
validity.password = false;
} else if (!hasUpperCase) {
passwordInput.className = "invalid";
setFeedback("password-fb", false, "Must contain at least one uppercase letter");
validity.password = false;
} else if (!hasNumber) {
passwordInput.className = "invalid";
setFeedback("password-fb", false, "Must contain at least one number");
validity.password = false;
} else {
passwordInput.className = "valid";
setFeedback("password-fb", true, "Strong password!");
validity.password = true;
}
// re-validate confirm password if it has content
if (confirmInput.value.length > 0) {
validateConfirmPassword();
}
updateSubmitButton();
});
// 4. confirm password validation on input
function validateConfirmPassword() {
const value = confirmInput.value;
if (value.length === 0) {
confirmInput.className = "";
document.getElementById("confirm-fb").textContent = "";
validity.confirm = false;
} else if (value !== passwordInput.value) {
confirmInput.className = "invalid";
setFeedback("confirm-fb", false, "Passwords do not match");
validity.confirm = false;
} else {
confirmInput.className = "valid";
setFeedback("confirm-fb", true, "Passwords match!");
validity.confirm = true;
}
updateSubmitButton();
}
confirmInput.addEventListener("input", validateConfirmPassword);
// 6. form submission
form.addEventListener("submit", function(event) {
event.preventDefault();
alert("Registration successful!\n\n" +
"Username: " + usernameInput.value + "\n" +
"Email: " + emailInput.value);
});
</script>
</body>
</html>
פתרון תרגיל 4¶
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<title>Window Events</title>
<style>
body { height: 3000px; font-family: Arial, sans-serif; padding: 20px; }
#info-panel { position: fixed; top: 10px; left: 10px; background: rgba(0,0,0,0.8); color: white;
padding: 15px; border-radius: 8px; z-index: 100; }
#back-to-top { position: fixed; bottom: 20px; left: 20px; padding: 10px 20px;
background: #2196F3; color: white; border: none; border-radius: 5px;
cursor: pointer; display: none; z-index: 100; }
</style>
</head>
<body>
<div id="info-panel">
<p>Window: <span id="window-size">-</span></p>
<p>Scroll: <span id="scroll-pos">0</span>px</p>
</div>
<button id="back-to-top">Back to Top</button>
<h1>Scroll Down to Test</h1>
<p>There is a lot of space below...</p>
<script>
const windowSize = document.getElementById("window-size");
const scrollPos = document.getElementById("scroll-pos");
const backToTop = document.getElementById("back-to-top");
// 1. show and update window size
function updateWindowSize() {
windowSize.textContent = window.innerWidth + " x " + window.innerHeight;
}
updateWindowSize();
window.addEventListener("resize", updateWindowSize);
// 2. update scroll position
window.addEventListener("scroll", function() {
scrollPos.textContent = Math.round(window.scrollY);
// 3. show/hide back to top button
if (window.scrollY > 300) {
backToTop.style.display = "block";
} else {
backToTop.style.display = "none";
}
});
// back to top button
backToTop.addEventListener("click", function() {
window.scrollTo({ top: 0, behavior: "smooth" });
});
// 4. warn before leaving
window.addEventListener("beforeunload", function(event) {
event.preventDefault();
event.returnValue = "";
});
</script>
</body>
</html>
פתרון תרגיל 5¶
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<title>Click Speed Test</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; text-align: center; }
button { padding: 15px 30px; font-size: 18px; margin: 10px; cursor: pointer; }
#click-btn { background: #4CAF50; color: white; border: none; border-radius: 8px; }
#click-btn:disabled { background: #ccc; cursor: not-allowed; }
#result { font-size: 24px; font-weight: bold; color: #2196F3; }
</style>
</head>
<body>
<h1>Click Speed Test</h1>
<p>Click the button as fast as you can in 10 seconds!</p>
<button id="click-btn" disabled>Click Me!</button>
<button id="start-btn">Start Game</button>
<p>Clicks: <span id="click-count">0</span></p>
<p>Time left: <span id="timer">10</span> seconds</p>
<p id="result"></p>
<script>
const clickBtn = document.getElementById("click-btn");
const startBtn = document.getElementById("start-btn");
const clickCountEl = document.getElementById("click-count");
const timerEl = document.getElementById("timer");
const resultEl = document.getElementById("result");
let clickCount = 0;
let timeLeft = 10;
let timerInterval = null;
function resetGame() {
clickCount = 0;
timeLeft = 10;
clickCountEl.textContent = "0";
timerEl.textContent = "10";
resultEl.textContent = "";
clickBtn.disabled = true;
startBtn.disabled = false;
}
// count clicks
clickBtn.addEventListener("click", function() {
clickCount++;
clickCountEl.textContent = clickCount;
});
// start game
startBtn.addEventListener("click", function() {
resetGame();
clickBtn.disabled = false;
startBtn.disabled = true;
clickBtn.focus();
timerInterval = setInterval(function() {
timeLeft--;
timerEl.textContent = timeLeft;
if (timeLeft <= 0) {
clearInterval(timerInterval);
clickBtn.disabled = true;
const cps = (clickCount / 10).toFixed(1);
resultEl.textContent = "Result: " + clickCount + " clicks (" + cps + " clicks/sec)";
startBtn.textContent = "Play Again";
startBtn.disabled = false;
}
}, 1000);
});
</script>
</body>
</html>
פתרון תרגיל 6¶
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<title>Interactive List</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; max-width: 500px; }
li { padding: 8px; margin: 5px 0; border: 1px solid #eee; border-radius: 4px; display: flex;
align-items: center; justify-content: space-between; }
.item-text { flex-grow: 1; }
.done .item-text { text-decoration: line-through; color: #999; }
button { margin-left: 5px; padding: 4px 8px; cursor: pointer; }
.delete-btn { background: #f44336; color: white; border: none; border-radius: 3px; }
.edit-btn { background: #2196F3; color: white; border: none; border-radius: 3px; }
#filter { padding: 8px; width: 100%; margin-top: 10px; box-sizing: border-box; }
</style>
</head>
<body>
<h2>Interactive List</h2>
<input type="text" id="new-item" placeholder="New item...">
<button id="add-btn">Add</button>
<input type="text" id="filter" placeholder="Filter items...">
<ul id="item-list"></ul>
<script>
const newItemInput = document.getElementById("new-item");
const addBtn = document.getElementById("add-btn");
const list = document.getElementById("item-list");
const filterInput = document.getElementById("filter");
function createListItem(text) {
const li = document.createElement("li");
const span = document.createElement("span");
span.classList.add("item-text");
span.textContent = text;
// 4. double click to toggle done
span.addEventListener("dblclick", function() {
li.classList.toggle("done");
});
const editBtn = document.createElement("button");
editBtn.textContent = "Edit";
editBtn.classList.add("edit-btn");
// 3. edit button
editBtn.addEventListener("click", function() {
const newText = prompt("Edit item:", span.textContent);
if (newText !== null && newText.trim() !== "") {
span.textContent = newText.trim();
}
});
const deleteBtn = document.createElement("button");
deleteBtn.textContent = "Delete";
deleteBtn.classList.add("delete-btn");
// 2. delete button with confirm
deleteBtn.addEventListener("click", function() {
if (confirm("Delete '" + span.textContent + "'?")) {
li.remove();
}
});
const buttonsDiv = document.createElement("div");
buttonsDiv.append(editBtn, deleteBtn);
li.append(span, buttonsDiv);
return li;
}
// 1. add item
function addItem() {
const text = newItemInput.value.trim();
if (text === "") return;
const li = createListItem(text);
list.appendChild(li);
newItemInput.value = "";
newItemInput.focus();
}
addBtn.addEventListener("click", addItem);
newItemInput.addEventListener("keydown", function(event) {
if (event.key === "Enter") addItem();
});
// add initial items
list.appendChild(createListItem("Learn JavaScript"));
list.appendChild(createListItem("Build a project"));
// 5. filter items
filterInput.addEventListener("input", function() {
const filterText = filterInput.value.toLowerCase();
const items = list.querySelectorAll("li");
items.forEach(function(item) {
const text = item.querySelector(".item-text").textContent.toLowerCase();
if (text.includes(filterText)) {
item.style.display = "";
} else {
item.style.display = "none";
}
});
});
</script>
</body>
</html>
פתרון תרגיל 7¶
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Drag Element</title>
<style>
body { margin: 0; height: 100vh; }
</style>
</head>
<body>
<div id="draggable" style="width: 100px; height: 100px; background: coral; position: absolute;
top: 100px; left: 100px; cursor: grab; display: flex; align-items: center;
justify-content: center; border-radius: 10px; user-select: none; font-family: Arial;">
Drag me!
</div>
<script>
const element = document.getElementById("draggable");
element.addEventListener("mousedown", function(event) {
// prevent text selection while dragging
event.preventDefault();
// calculate offset between mouse and element position
const offsetX = event.clientX - element.offsetLeft;
const offsetY = event.clientY - element.offsetTop;
// change cursor
element.style.cursor = "grabbing";
function onMouseMove(event) {
element.style.left = (event.clientX - offsetX) + "px";
element.style.top = (event.clientY - offsetY) + "px";
}
function onMouseUp() {
element.style.cursor = "grab";
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
}
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
});
</script>
</body>
</html>
נקודות חשובות:
- ה-mousemove ו-mouseup נרשמים על ה-document, לא על האלמנט, כדי שהגרירה תמשיך גם אם העכבר יוצא מגבולות האלמנט
- event.preventDefault() ב-mousedown מונע בחירת טקסט בזמן הגרירה
- שמירת reference לפונקציות מאפשרת להסיר אותן ב-mouseup
פתרון תרגיל 8¶
<!DOCTYPE html>
<html lang="he" dir="rtl">
<head>
<meta charset="UTF-8">
<title>Event Logger</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
#test-area { width: 300px; height: 200px; border: 2px solid #333; padding: 20px; margin-bottom: 10px; }
#test-area input { padding: 5px; margin: 5px; }
#event-log { height: 300px; overflow-y: auto; border: 1px solid #ddd; padding: 10px; font-family: monospace; font-size: 13px; }
.log-click { color: #e91e63; }
.log-dblclick { color: #9c27b0; }
.log-mouseenter { color: #4CAF50; }
.log-mouseleave { color: #FF9800; }
.log-keydown { color: #2196F3; }
.log-focus { color: #009688; }
.log-blur { color: #795548; }
.log-input { color: #607d8b; }
</style>
</head>
<body>
<h2>Event Logger</h2>
<div id="test-area">
<input type="text" placeholder="Type here...">
<button>Click me</button>
<a href="#" id="test-link">Test link</a>
</div>
<div id="event-log"></div>
<button id="clear-log">Clear Log</button>
<script>
const testArea = document.getElementById("test-area");
const eventLog = document.getElementById("event-log");
const clearBtn = document.getElementById("clear-log");
const testLink = document.getElementById("test-link");
function logEvent(event) {
const time = new Date().toLocaleTimeString();
const type = event.type;
const target = event.target.tagName;
let extra = "";
if (type === "keydown") {
extra = " | key: " + event.key;
}
if (type === "input") {
extra = " | value: " + event.target.value;
}
const entry = document.createElement("div");
entry.className = "log-" + type;
entry.textContent = time + " | " + type + " | " + target + extra;
eventLog.prepend(entry);
}
// listen for all event types
const eventTypes = ["click", "dblclick", "mouseenter", "mouseleave",
"keydown", "focus", "blur", "input"];
eventTypes.forEach(function(type) {
// focus and blur don't bubble, so use capture or focusin/focusout
const useCapture = (type === "focus" || type === "blur");
testArea.addEventListener(type, logEvent, useCapture);
});
// 5. prevent link navigation
testLink.addEventListener("click", function(event) {
event.preventDefault();
});
// 4. clear log
clearBtn.addEventListener("click", function() {
eventLog.innerHTML = "";
});
</script>
</body>
</html>
שימו לב: אירועי focus ו-blur לא מבעבעים (don't bubble), אז כדי לתפוס אותם על ה-container צריך להשתמש ב-capture phase (פרמטר שלישי true) או להשתמש ב-focusin/focusout שכן מבעבעים.
תשובות לשאלות¶
-
event.target מול event.currentTarget -
event.targetהוא האלמנט שבאמת הפעיל את האירוע (מה שהמשתמש לחץ עליו).event.currentTargetהוא האלמנט שה-listener רשום עליו. הם שונים כשהאירוע בעבע מאלמנט ילד. -
event.preventDefault() מבטל את ההתנהגות הרגילה של הדפדפן. למשל: מניעת שליחת טופס (ורענון דף), מניעת ניווט בלחיצה על קישור, מניעת תפריט קליק ימני. משתמשים בו כשרוצים לטפל באירוע בעצמנו במקום לתת לדפדפן לעשות את ברירת המחדל.
-
addEventListener עדיף על onclick attribute כי: אפשר להוסיף כמה handlers לאותו אירוע, יש הפרדה בין HTML ל-JS, אין בעיות אבטחה של CSP, ואפשר להסיר listeners ספציפיים. onclick attribute מאפשר רק handler אחד ומערבב HTML עם JS.
-
input מול change -
inputנורה על כל שינוי בזמן אמת (כל הקלדה, הדבקה, מחיקה).changeנורה כשהערך שונה והאלמנט מאבד פוקוס (בשדות טקסט). ב-select, checkbox ו-radio,changeנורה מיד בשינוי. -
event.key מול event.code -
event.keyהוא שם התו שנוצר (מושפע מ-Shift ומשפת המקלדת). למשל: "a", "A", "Enter".event.codeהוא קוד המקש הפיזי (תמיד אותו דבר, לא משנה שפה). למשל: "KeyA", "Enter". כדאי להשתמש ב-keyלרוב ו-codeכשרוצים מקש ספציפי בלי קשר לשפה. -
once: true גורם ל-listener לרוץ פעם אחת בלבד. אחרי שהאירוע מתרחש ו-handler רץ, הוא מוסר אוטומטית. שימושי לאירועים חד-פעמיים כמו אנימציית כניסה, לחיצה ראשונה, או טעינה ראשונית.