3.7 אחסון מקומי פתרון
פתרון תרגול אחסון מקומי¶
תרגיל 1 - שמירת שם¶
<h1 id="greeting"></h1>
<input type="text" id="name-input" placeholder="enter your name">
<button id="save-btn">save</button>
<button id="forget-btn">forget me</button>
let greeting = document.getElementById("greeting");
let nameInput = document.getElementById("name-input");
function updateGreeting() {
let name = localStorage.getItem("savedName");
if (name) {
greeting.textContent = "welcome back, " + name + "!";
} else {
greeting.textContent = "welcome, stranger!";
}
}
document.getElementById("save-btn").addEventListener("click", function () {
let name = nameInput.value.trim();
if (name) {
localStorage.setItem("savedName", name);
nameInput.value = "";
updateGreeting();
}
});
document.getElementById("forget-btn").addEventListener("click", function () {
localStorage.removeItem("savedName");
updateGreeting();
});
// on page load
updateGreeting();
getItemמחזירnullאם אין ערך, מה שגורם לתנאיif (name)לעבוד נכוןremoveItemמוחק את ה-key הספציפי
תרגיל 2 - מונה לחיצות¶
<h1 id="count-display">0</h1>
<button id="plus-btn">+</button>
<button id="minus-btn">-</button>
<button id="reset-btn">reset</button>
let display = document.getElementById("count-display");
// load saved count or start at 0
let count = parseInt(localStorage.getItem("counter")) || 0;
display.textContent = count;
function updateCount(newCount) {
count = newCount;
display.textContent = count;
localStorage.setItem("counter", count);
}
document.getElementById("plus-btn").addEventListener("click", function () {
updateCount(count + 1);
});
document.getElementById("minus-btn").addEventListener("click", function () {
updateCount(count - 1);
});
document.getElementById("reset-btn").addEventListener("click", function () {
updateCount(0);
});
parseIntממיר את המחרוזת מ-localStorage למספר|| 0מטפל במקרה שאין ערך שמור (null -> NaN -> 0)- כל שינוי שומר מיד ל-localStorage
תרגיל 3 - בורר ערכת צבעים¶
<div id="theme-buttons">
<button data-theme="light">light</button>
<button data-theme="dark">dark</button>
<button data-theme="blue">blue</button>
</div>
<h1>Theme Switcher</h1>
<p>Choose your preferred theme. It will be saved for next time.</p>
body.light { background: #ffffff; color: #000000; }
body.dark { background: #1a1a2e; color: #e0e0e0; }
body.blue { background: #0a1628; color: #e0e0e0; }
button.active {
outline: 3px solid #3498db;
font-weight: bold;
}
let themeButtons = document.getElementById("theme-buttons");
function applyTheme(theme) {
// remove all theme classes
document.body.className = theme;
// update active button
themeButtons.querySelectorAll("button").forEach(function (btn) {
btn.classList.toggle("active", btn.dataset.theme === theme);
});
// save to localStorage
localStorage.setItem("selectedTheme", theme);
}
// delegated handler for theme buttons
themeButtons.addEventListener("click", function (event) {
let button = event.target.closest("button");
if (!button) return;
applyTheme(button.dataset.theme);
});
// load saved theme or default to light
let savedTheme = localStorage.getItem("selectedTheme") || "light";
applyTheme(savedTheme);
document.body.className = themeמחליף את כל ה-classes ב-class החדשclassList.toggleעם תנאי מדגיש רק את הכפתור הנכון- בטעינת הדף, נטען ה-theme השמור
תרגיל 4 - רשימת מועדפים¶
<p><span id="fav-count">0</span> favorites</p>
<button id="clear-favs">clear all favorites</button>
<div id="movies-grid"></div>
let movies = [
"The Shawshank Redemption", "The Dark Knight", "Inception",
"Pulp Fiction", "Fight Club", "The Matrix",
"Interstellar", "Goodfellas", "The Godfather", "Parasite"
];
let moviesGrid = document.getElementById("movies-grid");
let favCountEl = document.getElementById("fav-count");
// load favorites from localStorage
function getFavorites() {
return JSON.parse(localStorage.getItem("favorites")) || [];
}
function saveFavorites(favs) {
localStorage.setItem("favorites", JSON.stringify(favs));
favCountEl.textContent = favs.length;
}
function renderMovies() {
let favorites = getFavorites();
moviesGrid.innerHTML = "";
movies.forEach(function (movie) {
let isFav = favorites.includes(movie);
let card = document.createElement("div");
card.className = "movie-card";
card.innerHTML =
"<h3>" + movie + "</h3>" +
'<button class="fav-btn" data-movie="' + movie + '">' +
(isFav ? "remove from favorites" : "add to favorites") +
"</button>";
moviesGrid.appendChild(card);
});
}
// delegated handler for favorite buttons
moviesGrid.addEventListener("click", function (event) {
let btn = event.target.closest(".fav-btn");
if (!btn) return;
let movie = btn.dataset.movie;
let favorites = getFavorites();
let index = favorites.indexOf(movie);
if (index === -1) {
favorites.push(movie);
} else {
favorites.splice(index, 1);
}
saveFavorites(favorites);
renderMovies();
});
// clear all
document.getElementById("clear-favs").addEventListener("click", function () {
saveFavorites([]);
renderMovies();
});
// initial render
renderMovies();
- מערך המועדפים נשמר כ-JSON ב-localStorage
includesבודק אם סרט כבר במועדפיםspliceמוציא פריט מהמערך לפי אינדקס- בכל שינוי, שומרים ומרנדרים מחדש
תרגיל 5 - טופס עם שמירה אוטומטית¶
<form id="contact-form">
<input type="text" name="name" placeholder="name">
<input type="email" name="email" placeholder="email">
<select name="subject">
<option value="">choose subject...</option>
<option value="general">general</option>
<option value="support">support</option>
<option value="feedback">feedback</option>
</select>
<textarea name="message" placeholder="message"></textarea>
<button type="submit">send</button>
<button type="button" id="clear-draft">clear draft</button>
</form>
<div id="draft-msg" style="color: green; display: none;">draft saved</div>
let form = document.getElementById("contact-form");
let draftMsg = document.getElementById("draft-msg");
let draftKey = "contactFormDraft";
// save draft on every input change
form.addEventListener("input", function () {
let data = Object.fromEntries(new FormData(form));
localStorage.setItem(draftKey, JSON.stringify(data));
// show "draft saved" message
draftMsg.style.display = "block";
setTimeout(function () {
draftMsg.style.display = "none";
}, 2000);
});
// restore draft on page load
function restoreDraft() {
let saved = localStorage.getItem(draftKey);
if (!saved) return;
let data = JSON.parse(saved);
Object.keys(data).forEach(function (key) {
let field = form.elements[key];
if (field) {
field.value = data[key];
}
});
}
// clear draft
document.getElementById("clear-draft").addEventListener("click", function () {
localStorage.removeItem(draftKey);
form.reset();
});
// on submit - clear draft
form.addEventListener("submit", function (event) {
event.preventDefault();
localStorage.removeItem(draftKey);
form.reset();
alert("message sent!");
});
// restore on load
restoreDraft();
- אירוע
inputעל ה-form מתפשט מכל שדה ילד (בעבוע) FormData+Object.fromEntriesאוספים את כל הנתונים בשורה אחת- בטעינה, מחזירים את הנתונים לשדות
- בשליחה או ניקוי, מוחקים את הטיוטה
תרגיל 6 - יומן רשומות¶
<textarea id="entry-input" placeholder="write your entry..."></textarea>
<button id="add-entry">add entry</button>
<input type="text" id="search-entries" placeholder="search entries...">
<p id="entry-count">0 entries</p>
<div id="entries-list"></div>
let entryInput = document.getElementById("entry-input");
let searchInput = document.getElementById("search-entries");
let entriesList = document.getElementById("entries-list");
let entryCount = document.getElementById("entry-count");
function getEntries() {
return JSON.parse(localStorage.getItem("journal")) || [];
}
function saveEntries(entries) {
localStorage.setItem("journal", JSON.stringify(entries));
}
function renderEntries(filter) {
let entries = getEntries();
let filtered = entries;
if (filter) {
let query = filter.toLowerCase();
filtered = entries.filter(function (entry) {
return entry.text.toLowerCase().includes(query);
});
}
entryCount.textContent = filtered.length + " entries";
entriesList.innerHTML = "";
filtered.forEach(function (entry, index) {
let div = document.createElement("div");
div.className = "entry";
div.innerHTML =
"<small>" + entry.date + "</small>" +
"<p>" + entry.text + "</p>" +
'<button class="delete-entry" data-index="' + index + '">delete</button>';
entriesList.appendChild(div);
});
}
// add entry
document.getElementById("add-entry").addEventListener("click", function () {
let text = entryInput.value.trim();
if (!text) return;
let entries = getEntries();
let now = new Date();
let dateStr = now.toLocaleDateString() + " " + now.toLocaleTimeString();
// add to beginning (newest first)
entries.unshift({
text: text,
date: dateStr
});
saveEntries(entries);
entryInput.value = "";
renderEntries(searchInput.value);
});
// delete entry (delegated)
entriesList.addEventListener("click", function (event) {
let btn = event.target.closest(".delete-entry");
if (!btn) return;
let index = parseInt(btn.dataset.index);
let entries = getEntries();
entries.splice(index, 1);
saveEntries(entries);
renderEntries(searchInput.value);
});
// search
searchInput.addEventListener("input", function () {
renderEntries(searchInput.value);
});
// initial render
renderEntries();
unshiftמוסיף לתחילת המערך (רשומות חדשות למעלה)data-indexעל כפתור המחיקה מאפשר לזהות את הרשומה הנכונהtoLocaleDateStringו-toLocaleTimeStringמפרמטים תאריך קריא
תרגיל 7 - Storage Wrapper¶
let storage = {
get: function (key, defaultValue) {
let data = localStorage.getItem(key);
if (data === null) {
return defaultValue !== undefined ? defaultValue : null;
}
try {
return JSON.parse(data);
} catch (e) {
return data; // return raw string if not valid JSON
}
},
set: function (key, value) {
localStorage.setItem(key, JSON.stringify(value));
},
remove: function (key) {
localStorage.removeItem(key);
},
has: function (key) {
return localStorage.getItem(key) !== null;
},
clear: function () {
localStorage.clear();
},
getAll: function () {
let all = {};
for (let i = 0; i < localStorage.length; i++) {
let key = localStorage.key(i);
all[key] = this.get(key);
}
return all;
}
};
שימוש ב-wrapper לבניית רשימת משימות:
<input type="text" id="task-input" placeholder="new task...">
<button id="add-task">add</button>
<ul id="task-list"></ul>
function getTasks() {
return storage.get("tasks", []);
}
function renderTasks() {
let tasks = getTasks();
let list = document.getElementById("task-list");
list.innerHTML = "";
tasks.forEach(function (task, index) {
let li = document.createElement("li");
let doneClass = task.done ? "done" : "";
li.innerHTML =
'<span class="' + doneClass + '">' + task.text + "</span> " +
'<button class="toggle-btn" data-index="' + index + '">' +
(task.done ? "undo" : "done") +
"</button> " +
'<button class="delete-btn" data-index="' + index + '">delete</button>';
list.appendChild(li);
});
}
document.getElementById("add-task").addEventListener("click", function () {
let input = document.getElementById("task-input");
let text = input.value.trim();
if (!text) return;
let tasks = getTasks();
tasks.push({ text: text, done: false });
storage.set("tasks", tasks);
input.value = "";
renderTasks();
});
document.getElementById("task-list").addEventListener("click", function (event) {
let tasks = getTasks();
let toggleBtn = event.target.closest(".toggle-btn");
if (toggleBtn) {
let index = parseInt(toggleBtn.dataset.index);
tasks[index].done = !tasks[index].done;
storage.set("tasks", tasks);
renderTasks();
return;
}
let deleteBtn = event.target.closest(".delete-btn");
if (deleteBtn) {
let index = parseInt(deleteBtn.dataset.index);
tasks.splice(index, 1);
storage.set("tasks", tasks);
renderTasks();
return;
}
});
renderTasks();
- ה-wrapper מטפל ב-JSON אוטומטית
getעם defaultValue מחזיר מערך ריק אם אין נתוניםtry/catchב-get מגן מפני JSON לא תקיןgetAllעובר על כל ה-keys ומחזיר אובייקט מלא
תרגיל 8 - תקשורת בין טאבים¶
<input type="text" id="msg-input" placeholder="type a message...">
<button id="send-btn">send</button>
<button id="clear-history">clear history</button>
<h3>received messages:</h3>
<div id="received-messages"></div>
let msgInput = document.getElementById("msg-input");
let receivedDiv = document.getElementById("received-messages");
function getHistory() {
return JSON.parse(localStorage.getItem("chatHistory")) || [];
}
function renderReceived() {
let history = getHistory();
receivedDiv.innerHTML = "";
history.forEach(function (msg) {
let div = document.createElement("div");
div.className = "message";
div.innerHTML = "<small>" + msg.time + "</small><p>" + msg.text + "</p>";
receivedDiv.appendChild(div);
});
}
// send message
document.getElementById("send-btn").addEventListener("click", function () {
let text = msgInput.value.trim();
if (!text) return;
let now = new Date();
let time = now.toLocaleTimeString();
let history = getHistory();
history.push({ text: text, time: time });
localStorage.setItem("chatHistory", JSON.stringify(history));
// also update a "lastMessage" key to trigger the storage event
localStorage.setItem("lastMessage", JSON.stringify({
text: text,
time: time,
id: Date.now() // unique id to ensure value changes
}));
msgInput.value = "";
});
// listen for changes from OTHER tabs
window.addEventListener("storage", function (event) {
if (event.key === "lastMessage" && event.newValue) {
// a new message was sent from another tab
renderReceived();
}
if (event.key === "chatHistory" && event.newValue === null) {
// history was cleared from another tab
renderReceived();
}
});
// clear history
document.getElementById("clear-history").addEventListener("click", function () {
localStorage.removeItem("chatHistory");
localStorage.removeItem("lastMessage");
renderReceived();
});
// initial render
renderReceived();
lastMessagekey נפרד משמש להפעלת אירוע storage (כי chatHistory עשוי להיות ארוך)Date.now()כ-id מבטיח שהערך תמיד משתנה (אחרת storage event לא מופעל אם הערך זהה)- אירוע
storageמופעל רק בטאבים אחרים, לא בטאב ששינה את הערך - פתחו את הדף בשני טאבים ושלחו הודעה מאחד - היא תופיע בשני
תרגיל 9 - מערכת ציונים גבוהים¶
<div>
<input type="text" id="player-name" placeholder="player name">
<input type="number" id="player-score" placeholder="score">
<button id="add-score">add score</button>
<button id="reset-scores">reset scores</button>
</div>
<table id="scores-table">
<thead>
<tr>
<th>#</th>
<th>name</th>
<th>score</th>
<th>date</th>
</tr>
</thead>
<tbody></tbody>
</table>
.new-score {
background: #d4edda;
animation: highlight 2s ease;
}
@keyframes highlight {
from { background: #a8e6cf; }
to { background: transparent; }
}
let nameInput = document.getElementById("player-name");
let scoreInput = document.getElementById("player-score");
let tbody = document.querySelector("#scores-table tbody");
let lastAddedId = null;
function getScores() {
return JSON.parse(localStorage.getItem("highScores")) || [];
}
function saveScores(scores) {
localStorage.setItem("highScores", JSON.stringify(scores));
}
function renderScores() {
let scores = getScores();
tbody.innerHTML = "";
scores.forEach(function (entry, index) {
let tr = document.createElement("tr");
if (entry.id === lastAddedId) {
tr.className = "new-score";
}
tr.innerHTML =
"<td>" + (index + 1) + "</td>" +
"<td>" + entry.name + "</td>" +
"<td>" + entry.score + "</td>" +
"<td>" + entry.date + "</td>";
tbody.appendChild(tr);
});
}
document.getElementById("add-score").addEventListener("click", function () {
let name = nameInput.value.trim();
let score = parseInt(scoreInput.value);
if (!name || isNaN(score)) return;
let scores = getScores();
let id = Date.now();
scores.push({
id: id,
name: name,
score: score,
date: new Date().toLocaleDateString()
});
// sort by score descending
scores.sort(function (a, b) {
return b.score - a.score;
});
// keep only top 10
scores = scores.slice(0, 10);
saveScores(scores);
lastAddedId = id;
renderScores();
nameInput.value = "";
scoreInput.value = "";
});
document.getElementById("reset-scores").addEventListener("click", function () {
localStorage.removeItem("highScores");
lastAddedId = null;
renderScores();
});
// initial render
renderScores();
sortעם comparator ממיין מהגבוה לנמוךslice(0, 10)שומר רק את 10 הציונים הגבוהיםDate.now()כ-id ייחודי מאפשר לזהות את הציון שנוסף אחרון- CSS animation מדגיש את השורה החדשה עם אפקט fade