Skip to main content

JavaScript
3 Ways to Store Data in the Browser

script.js

// AZUL CODING ---------------------------------------
// JavaScript - 3 Ways to Store Data in the Browser
// https://youtu.be/tkMD51-KMfk


let db;
let editingUserId = null;

const DB_NAME = "UserDatabase";
const DB_VERSION = 1;
const STORE_NAME = "users";
const SAMPLE_USERS = "/users.json";

const userList = document.getElementById("user-list");
const clearDbBtn = document.getElementById("clear-database-btn");
const loadSampleBtn = document.getElementById("load-sample-btn");
const addUserBtn = document.getElementById("add-user-btn");
const toggleThemeBtn = document.getElementById("theme-btn");
const statusEl = document.getElementById("status");
const dbInfoEl = document.getElementById("entry-count");
const newNameInput = document.getElementById("new-name");
const newEmailInput = document.getElementById("new-email");
const newAgeInput = document.getElementById("new-age");

function initDatabase() {
    return new Promise((resolve, reject) => {
        const request = indexedDB.open(DB_NAME, DB_VERSION);

        request.onerror = (event) => {
            showStatus("Failed to open database");
            reject(event.target.errorCode);
        };

        request.onsuccess = (event) => {
            db = event.target.result;
            showStatus("Database opened successfully");
            updateDbInfo();
            loadUsers();
            resolve(db);
        };

        request.onupgradeneeded = (event) => {
            const db = event.target.result;
            const objectStore = db.createObjectStore(STORE_NAME, {
                keyPath: "id",
                autoIncrement: true
            });

            objectStore.createIndex("name", "name", { unique: false });
            objectStore.createIndex("email", "email", { unique: true });
            objectStore.createIndex("age", "age", { unique: false });

            showStatus("Database setup complete");
        };
    });
}

function loadUsers() {
    userList.innerHTML = "";
    
    const transaction = db.transaction([STORE_NAME], "readonly");
    const objectStore = transaction.objectStore(STORE_NAME);
    const request = objectStore.openCursor();
    
    request.onerror = () => {
        showStatus("Error fetching users");
    };
    
    request.onsuccess = (event) => {
        const cursor = event.target.result;
        if (cursor) {
            const user = cursor.value;
            const row = document.createElement("tr");

            row.setAttribute("data-id", user.id);
            row.innerHTML = `
                <td>${user.name}</td>
                <td>${user.email}</td>
                <td>${user.age || "N/A"}</td>
                <td>
                    <button type="button" class="edit">Edit</button>
                    <button type="button" class="delete">Delete</button>
                </td>
            `;

            row.querySelector("button.edit").addEventListener("click", () => {
                startEditing(row, user);
            });
            row.querySelector("button.delete").addEventListener("click", () => {
                deleteUser(user.id);
            });
            
            userList.appendChild(row);
            cursor.continue();
        }
    };

    updateDbInfo();
}

/* editing users */

function startEditing(row, user) {
    if (editingUserId !== null && editingUserId !== user.id) {
        cancelEditing();
    }

    editingUserId = user.id;
    row.innerHTML = `
        <td><input type="text" class="edit-name" value="${user.name}" required></td>
        <td><input type="email" class="edit-email" value="${user.email}" required></td>
        <td><input type="number" class="edit-age" value="${user.age || ""}" min="0" max="120"></td>
        <td>
            <button class="edit">Save</button>
            <button class="cancel">Cancel</button>
        </td>
    `;

    row.querySelector("button.edit").addEventListener("click", () => {
        saveEdits(row, user.id);
    });
    row.querySelector("button.cancel").addEventListener("click", () => {
        cancelEditing();
    });
}

function saveEdits(row, userId) {
    const nameInput = row.querySelector(".edit-name");
    const emailInput = row.querySelector(".edit-email");
    const ageInput = row.querySelector(".edit-age");
    
    const userData = {
        id: userId,
        name: nameInput.value,
        email: emailInput.value,
        age: ageInput.value ? parseInt(ageInput.value) : null
    };
    
    saveUser(userData)
        .then(() => {
            showStatus("User updated successfully");
            loadUsers();
            editingUserId = null;
        })
        .catch(error => {
            showStatus(error);
        });
}

function cancelEditing() {
    loadUsers();
    editingUserId = null;
}

function addUser() {
    const userData = {
        name: newNameInput.value,
        email: newEmailInput.value,
        age: newAgeInput.value ? parseInt(newAgeInput.value) : null
    };
    
    if (!userData.name || !userData.email) {
        showStatus("Name and email are required fields");
        return;
    }
    
    saveUser(userData)
        .then(() => {
            showStatus("User added successfully");
            loadUsers();

            newNameInput.value = "";
            newEmailInput.value = "";
            newAgeInput.value = "";
        })
        .catch(error => {
            showStatus(error);
        });
}

function saveUser(userData) {
    return new Promise((resolve, reject) => {
        const transaction = db.transaction([STORE_NAME], "readwrite");
        const objectStore = transaction.objectStore(STORE_NAME);
        
        let request;
        if (userData.id) {
            // Update existing user
            request = objectStore.put(userData);
        } else {
            // Add new user - remove empty id to allow auto-increment
            delete userData.id;
            request = objectStore.add(userData);
        }
        
        request.onerror = (event) => {
            const error = event.target.error;
            if (error.name === "ConstraintError") {
                reject("Email already exists");
            } else {
                reject("Error saving user: " + error);
            }
        };
        
        request.onsuccess = (event) => {
            resolve(event.target.result);
        };
    });
}

function deleteUser(id) {
    if (!confirm("Are you sure you want to delete this user?")) return;
    
    const transaction = db.transaction([STORE_NAME], "readwrite");
    const objectStore = transaction.objectStore(STORE_NAME);
    const request = objectStore.delete(id);
    
    request.onerror = () => {
        showStatus("Error deleting user");
    };
    
    request.onsuccess = () => {
        showStatus("User deleted successfully");
        loadUsers();
    };
}

/* database management */

function clearDatabase() {
    if (!confirm("Are you sure you want to delete all users?")) return;
    
    const transaction = db.transaction([STORE_NAME], "readwrite");
    const objectStore = transaction.objectStore(STORE_NAME);
    const request = objectStore.clear();
    
    request.onerror = () => {
        showStatus("Error clearing database");
    };
    
    request.onsuccess = () => {
        showStatus("Database cleared successfully");
        loadUsers();
    };
}

function getSampleData() {
    return new Promise((resolve) => {
        caches.match(SAMPLE_USERS).then(async (response) => {
            if (response) {
                resolve(await response.json());
                return;
            }

            fetch(SAMPLE_USERS).then(response => {
                if (!response.ok) {
                    showStatus("Failed to fetch sample data");
                    return;
                }

                caches.open(STORE_NAME).then(async (cache) => {
                    cache.put(SAMPLE_USERS, response.clone());
                    resolve(await response.json());
                });
            });
        });
    });
}

async function loadSampleData() {
    const sampleUsers = await getSampleData();
    
    const transaction = db.transaction([STORE_NAME], "readwrite");
    const objectStore = transaction.objectStore(STORE_NAME);
    
    let successCount = 0;
    sampleUsers.forEach(user => {
        const request = objectStore.put(user);
        
        request.onsuccess = () => {
            successCount++;
            if (successCount === sampleUsers.length) {
                showStatus("Sample data loaded successfully");
                loadUsers();
            }
        };

        request.onerror = () => {
            showStatus("Sample data already loaded");
        };
    });
}

/* utility functions */

function updateDbInfo() {
    const transaction = db.transaction([STORE_NAME], "readonly");
    const objectStore = transaction.objectStore(STORE_NAME);
    const countRequest = objectStore.count();
    
    countRequest.onsuccess = () => {
        dbInfoEl.textContent = `${countRequest.result} entries`;
    };
}

function showStatus(message) {
    statusEl.textContent = message;
}

function toggleTheme() {
    const lightMode = document.documentElement.classList.toggle("light");

    // Watch this video to understand the differences between localStorage and sessionStorage:
    // https://youtu.be/0taLxS1xadM

    localStorage.setItem("theme", lightMode ? "light" : "dark");
    // sessionStorage.setItem("theme", lightMode ? "light" : "dark");
}


const savedTheme = localStorage.getItem("theme");
if (savedTheme === "light") {
    document.documentElement.classList.add("light");
}

addUserBtn.addEventListener("click", addUser);
clearDbBtn.addEventListener("click", clearDatabase);
loadSampleBtn.addEventListener("click", loadSampleData);
toggleThemeBtn.addEventListener("click", toggleTheme);

initDatabase().catch(error => {
    console.error("Database initialization failed:", error);
});

Enjoying this tutorial?


users.json

// AZUL CODING ---------------------------------------
// JavaScript - 3 Ways to Store Data in the Browser
// https://youtu.be/tkMD51-KMfk


[
    { "name": "John Doe", "email": "john@example.com", "age": 32 },
    { "name": "Jane Smith", "email": "jane@example.com", "age": 28 },
    { "name": "Bob Johnson", "email": "bob@example.com", "age": 45 },
    { "name": "Alice Brown", "email": "alice@example.com", "age": 24 }
]

index.html

<!-- AZUL CODING --------------------------------------- -->
<!-- JavaScript - 3 Ways to Store Data in the Browser -->
<!-- https://youtu.be/tkMD51-KMfk -->


<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Azul Coding</title>
        <style>
            :root {
                --back-colour: #03506E;
                --text-colour: white;
            }
            .light {
                --back-colour: white;
                --text-colour: #03506E;
            }

            body {
                margin: 30px;
                background-color: var(--back-colour);
                color: var(--text-colour);
            }
            * {
                font-family: 'Golos Text', system-ui, sans-serif;
                font-weight: 500;
                font-size: 18px;
            }
            h1 {
                font-size: 32px;
                font-weight: 700;
                margin-bottom: 0;
            }
            #status {
                border: 2px solid #49c8fc;
                padding: 8px 10px;
            }

            input {
                padding: 5px 8px;
                width: 100%;
                box-sizing: border-box;
            }
            button {
                background-color: var(--text-colour);
                color: var(--back-colour);
                border: none;
                padding: 6px 12px;
                cursor: pointer;
                display: inline;
            }
            button:hover {
                background-color: #49c8fc;
            }
            .actions {
                display: flex;
                gap: 10px;
                margin: 40px 0;
                padding-top: 15px;
                border-top: 2px solid #49c8fc;
            }

            table {
                border: none;
                width: 100%;
                margin: 20px auto;
                color: var(--text-colour);
                border-collapse: collapse;
            }
            th {
                font-weight: 700;
                text-align: left;
                font-size: 20px;
            }
            thead th, tbody td {
                border-bottom: 1px solid #49c8fc;
            }
            th, td {
                padding: 8px;
            }
        </style>
        <script defer src="/script.js"></script>
    </head>
    <body>
        <h1>User Database</h1>
        <p id="entry-count">0 entries</p>
        <div id="status" role="status">Loading data...</div>

        <table id="user-table">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Email</th>
                    <th>Age</th>
                    <th>Actions</th>
                </tr>
            </thead>
            <tbody id="user-list"></tbody>
            <tfoot>
                <tr>
                    <td><input type="text" id="new-name" placeholder="Name" required></td>
                    <td><input type="email" id="new-email" placeholder="Email" required></td>
                    <td><input type="number" id="new-age" placeholder="Age" min="0" max="120"></td>
                    <td><button type="button" id="add-user-btn">Add user</button></td>
                </tr>
            </tfoot>
        </table>

        <div class="actions">
            <button type="button" id="clear-database-btn">Clear database</button>
            <button type="button" id="load-sample-btn">Load sample data</button>
            <button type="button" id="theme-btn">Toggle theme</button>
        </div>
    </body>
</html>