shaderone
shaderone

Reputation: 498

setTimeout function not working : javascript

Scenario: Just created a simple book List app, which can accept some inputs and display it on the UI and has other functionalities such as the use of localStorage.

Just as I added more custom alerts (e.g. show alert - on adding a book, - if author name contain numbers etc.) The whole alert stuff has gone AWOL. (Previously defined alerts worked fine before)

Also none of the questions that showed similarity proved useful to me

Method which handles all the alerts in the page

static showAlert(msg, className) {
        const alertBox = document.createElement('div');
        alertBox.className = `alert alert-${className} mb-3 p-2`;
        alertBox.appendChild(document.createTextNode(msg));

        const container = document.querySelector('.container');
        const form = document.querySelector('#book-form');

        // check whether if an alertBox already exist in the UI
        if(document.querySelector('.alert')) {
            // remove alertBox after some time
            setTimeout(() => {
                document.querySelector('.alert').remove();
            }, 3000);
        } else {
            container.insertBefore(alertBox, form);
        }
    }

And this is where I added new custom alerts:

if(bookName === '' || authorName === '' || bookId === '') {
        Dom.showAlert('All fields are required', 'danger');
    } else if (!isNaN(bookName)) {
        Dom.showAlert('Name of the book should not contain numbers', 'danger');
        document.querySelector('#title').value = '';
    } else if (!isNaN(authorName)) {
        Dom.showAlert('Author Name should not contain numbers', 'danger');
        document.querySelector('#author').value = '';
    } else if (isNaN(bookId)) {
        Dom.showAlert('Reference Id should only consist of numbers', 'danger');
        document.querySelector('#bookId').value = '';

Problems facing:

Quick note: I have set the setTimeout delay to 3000

  1. Existing alert is not removed after 3 seconds (which worked fine before)

  2. If there is a need for another alert to exist in dom, then the alert showed previously should be removed and the new alert should be displayed; even if that happens in under 3 seconds.

I have added the code as snippet as I had some trouble setting JSFiddle

// Book class 
class Book { 
    constructor(title, author, id) {
        this.bookName = title; 
        this.authorName = author; 
        this.bookId = id;
    }
}

// Dom class : handle user interface / dom
class Dom {
    static displayBooks() {
        //dummy data to mimic data in localStorage
        const savedBooks = [
            {
                bookName: 'Vikings',
                authorName: 'Michael Hirst',
                bookId: 9764363,
            },

            {
                bookName: 'The Soldier',
                authorName: 'David Gary',
                bookId: 5764363,
            },
        ]
        //const books = Store.getBooks();
        const books = savedBooks;
        books.forEach((book) => Dom.addBookToList(book))
    }

    static addBookToList(bookInfo) {
        const tbody = document.querySelector('#book-list')
        const bookRow = document.createElement('tr');
        bookRow.innerHTML = 
        `
        <tr>
            <td>${bookInfo.bookName}</td>
            <td>${bookInfo.authorName}</td>
            <td>${bookInfo.bookId}</td>
            <td><a href="#" class="btn btn-danger btn-sm deleteBtn">Delete</a></td>
        </tr>
        `
        tbody.appendChild(bookRow);
    }

    static deleteBook(target) {
        if(target.classList.contains('deleteBtn')) {
            target.parentElement.parentElement.remove()
            // show alert if user deletes a book
            Dom.showAlert('Book deleted successfully', 'warning');
        }
    }

    static showAlert(msg, className) {
        const alertBox = document.createElement('div');
        alertBox.className = `alert alert-${className} mb-3 p-2`;
        alertBox.appendChild(document.createTextNode(msg));

        const container = document.querySelector('.container');
        const form = document.querySelector('#book-form');

        // check whether if an alertBox already exist in the UI
        if(document.querySelector('.alert')) {
            // remove alerBox after an interval
            setTimeout(() => {
                document.querySelector('.alert').remove();
            }, 3000);
        } else {
            container.insertBefore(alertBox, form);
        }
    }

    static clearFields() {
        document.querySelector('#title').value = '';
        document.querySelector('#author').value = '';
        document.querySelector('#bookId').value = '';
    }
}


// Store class - deals with localStorage
/*class Store {
    static getBooks() {
        let books; 
        if(localStorage.getItem('books') === null) {
            // if there is no book in the localstorage, then we initialize an empty array (of objects);
            books = [];
        } else {
            // since the data is in the format of string, we need to change it to a javascript object in order to to things with it.Therefore we use JSON.parse
            books = JSON.parse(localStorage.getItem('books'));
        }

        return books;
    }

    static addBooks(book) {
        const books = Store.getBooks();
        books.push(book);

        localStorage.setItem('books', JSON.stringify(books))
    }

    static removeBooks(bookId) {
        const books = Store.getBooks();
        books.forEach((book, index) => {
            if(book.bookId === bookId) {
                books.splice(index, 1);
            }
        });

        localStorage.setItem('books', JSON.stringify(books));
    }
}*/


// Events in Dom 
document.addEventListener('DOMContentLoaded', Dom.displayBooks);

// Add new book to dom/ui
document.querySelector('#book-form').addEventListener('submit', event => {
    event.preventDefault();

    const bookName = document.querySelector('#title').value;
    const authorName = document.querySelector('#author').value;
    const bookId = document.querySelector('#bookId').value;

    // input validation
    if(bookName === '' || authorName === '' || bookId === '') {
        Dom.showAlert('All fields are required', 'danger');
    } else if (!isNaN(bookName)) {
        Dom.showAlert('Name of the book should not contain numbers', 'danger');
        document.querySelector('#title').value = '';
    } else if (!isNaN(authorName)) {
        Dom.showAlert('Author Name should not contain numbers', 'danger');
        document.querySelector('#author').value = '';
    } else if (isNaN(bookId)) {
        Dom.showAlert('Reference Id should only consist of numbers', 'danger');
        document.querySelector('#bookId').value = '';
    } else {
        // object instantiation of book
        const book = new Book(bookName, authorName, bookId);

        // Add submitted book to the list
        Dom.addBookToList(book);

        // Add Submitted bookk to the local Storage
        //Store.addBooks(book);

        // show alert after adding the book
        Dom.showAlert('Book added successfully', 'success');

        // Clear input fields after submitting
        Dom.clearFields();
    }
})


// Remove / Delete a book from the list
document.querySelector('#book-list')
.addEventListener('click', event => {
    // Remove book from the Dom
    Dom.deleteBook(event.target);

    // Remove book from the local storage
    //Store.removeBooks(event.target.parentElement.previousElementSibling.textContent);
});
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Book List app</title>
    <!-- fontAwesome script -->
    <script src="https://kit.fontawesome.com/39350fd9df.js"></script>
    <!-- bootstrap css -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" type="text/css">
</head>
<body>
    <div class="container mt-4">
        
        <h1 class="display-4 text-center mb-4"><i class="fas fa-book text-success"></i> <span class="text-success">BlueStock</span> <span class="text-muted">Book Shop</span></h1>

        <form action="#" id="book-form">
            <div class="form-group mb-2">
                <label for="title">Book Title</label>
                <input type="text" name="title" id="title" class="form-control">
            </div>
            <div class="form-group mb-2">
                <label for="author">Author Name</label>
                <input type="text" name="author" id="author" class="form-control">
            </div>
            <div class="form-group">
                <label for="book-id">Reference Id</label>
                <input type="text" name="title" id="bookId" class="form-control">
            </div>

            <input type="submit" value="Add Book" class="btn btn-primary w-100 mt-3" id="submit">
        </form>

        <table class="table table-striped mt-4">

            <thead>
                <tr>
                    <th>Book Title</th>
                    <th>Author Name</th>
                    <th>Reference Id</th>
                    <th class="text-warning"><small>Delete book</small></th>
                </tr>
            </thead>

            <tbody id="book-list"></tbody>
        </table>
    </div>

    <script src="app.js" type="text/javascript"></script>
</body>
</html>

Upvotes: 0

Views: 697

Answers (1)

choz
choz

Reputation: 17858

The issue I notice with your showAlert:

showAlert(msg, className) {
    // ...
    if (document.querySelector('.alert')) {
        /*
        #1. At the first time this function called, this will always be null. Since '.alert' is not in the DOM yet (it wil be added to the DOM at the following 'else' block.
            Therefore, setTimeout here won't be called, since the code never enters here at the first time.
    
        #2. At the second time and forth, this will be called.
            However, it will not enter the `else` block which is to add the alert into the DOM
        */
        setTimeout(() => {
            document.querySelector('.alert').remove();
        }, 3000);
    
    } else {
        container.insertBefore(alertBox, form);
    }
}

If we break it down your expected behavior, the code should probably somewhere like this;

static showAlert(msg, className) {
    // If there's an existing alert, instantly remove it.
    if (document.querySelector('.alert')) {
        document.querySelector('.alert').remove();
    }

    // Create the alert
    const alertBox = document.createElement('div');
    alertBox.className = `alert alert-${className} mb-3 p-2`;
    alertBox.appendChild(document.createTextNode(msg));

    const container = document.querySelector('.container');
    const form = document.querySelector('#book-form');

    // Insert alert to the DOM.
    container.insertBefore(alertBox, form);

    // Remove the alert above after 3s
    setTimeout(() => {
        alertBox.remove();
    }, 3e3);
}

Example final code as following;

// Book class 
class Book {
  constructor(title, author, id) {
    this.bookName = title;
    this.authorName = author;
    this.bookId = id;
  }
}

// Dom class : handle user interface / dom
class Dom {
  static displayBooks() {
    //dummy data to mimic data in localStorage
    const savedBooks = [{
        bookName: 'Vikings',
        authorName: 'Michael Hirst',
        bookId: 9764363,
      },

      {
        bookName: 'The Soldier',
        authorName: 'David Gary',
        bookId: 5764363,
      },
    ]
    //const books = Store.getBooks();
    const books = savedBooks;
    books.forEach((book) => Dom.addBookToList(book))
  }

  static addBookToList(bookInfo) {
    const tbody = document.querySelector('#book-list')
    const bookRow = document.createElement('tr');
    bookRow.innerHTML =
      `
        <tr>
            <td>${bookInfo.bookName}</td>
            <td>${bookInfo.authorName}</td>
            <td>${bookInfo.bookId}</td>
            <td><a href="#" class="btn btn-danger btn-sm deleteBtn">Delete</a></td>
        </tr>
        `
    tbody.appendChild(bookRow);
  }

  static deleteBook(target) {
    if (target.classList.contains('deleteBtn')) {
      target.parentElement.parentElement.remove()
      // show alert if user deletes a book
      Dom.showAlert('Book deleted successfully', 'warning');
    }
  }

  static showAlert(msg, className) {
    // If there's an existing alert, instantly remove it.
    if (document.querySelector('.alert')) {
      document.querySelector('.alert').remove();
    }

    // Create the alert
    const alertBox = document.createElement('div');
    alertBox.className = `alert alert-${className} mb-3 p-2`;
    alertBox.appendChild(document.createTextNode(msg));

    const container = document.querySelector('.container');
    const form = document.querySelector('#book-form');

    // Insert alert to the DOM.
    container.insertBefore(alertBox, form);

    // Remove the alert above after 3s
    setTimeout(() => {
        alertBox.remove();
    }, 3e3);
  }

  static clearFields() {
    document.querySelector('#title').value = '';
    document.querySelector('#author').value = '';
    document.querySelector('#bookId').value = '';
  }
}


// Store class - deals with localStorage
/*class Store {
    static getBooks() {
        let books; 
        if(localStorage.getItem('books') === null) {
            // if there is no book in the localstorage, then we initialize an empty array (of objects);
            books = [];
        } else {
            // since the data is in the format of string, we need to change it to a javascript object in order to to things with it.Therefore we use JSON.parse
            books = JSON.parse(localStorage.getItem('books'));
        }

        return books;
    }

    static addBooks(book) {
        const books = Store.getBooks();
        books.push(book);

        localStorage.setItem('books', JSON.stringify(books))
    }

    static removeBooks(bookId) {
        const books = Store.getBooks();
        books.forEach((book, index) => {
            if(book.bookId === bookId) {
                books.splice(index, 1);
            }
        });

        localStorage.setItem('books', JSON.stringify(books));
    }
}*/


// Events in Dom 
document.addEventListener('DOMContentLoaded', Dom.displayBooks);

// Add new book to dom/ui
document.querySelector('#book-form').addEventListener('submit', event => {
  event.preventDefault();

  const bookName = document.querySelector('#title').value;
  const authorName = document.querySelector('#author').value;
  const bookId = document.querySelector('#bookId').value;

  // input validation
  if (bookName === '' || authorName === '' || bookId === '') {
    Dom.showAlert('All fields are required', 'danger');
  } else if (!isNaN(bookName)) {
    Dom.showAlert('Name of the book should not contain numbers', 'danger');
    document.querySelector('#title').value = '';
  } else if (!isNaN(authorName)) {
    Dom.showAlert('Author Name should not contain numbers', 'danger');
    document.querySelector('#author').value = '';
  } else if (isNaN(bookId)) {
    Dom.showAlert('Reference Id should only consist of numbers', 'danger');
    document.querySelector('#bookId').value = '';
  } else {
    // object instantiation of book
    const book = new Book(bookName, authorName, bookId);

    // Add submitted book to the list
    Dom.addBookToList(book);

    // Add Submitted bookk to the local Storage
    //Store.addBooks(book);

    // show alert after adding the book
    Dom.showAlert('Book added successfully', 'success');

    // Clear input fields after submitting
    Dom.clearFields();
  }
})


// Remove / Delete a book from the list
document.querySelector('#book-list')
  .addEventListener('click', event => {
    // Remove book from the Dom
    Dom.deleteBook(event.target);

    // Remove book from the local storage
    //Store.removeBooks(event.target.parentElement.previousElementSibling.textContent);
  });
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Book List app</title>
  <!-- fontAwesome script -->
  <script src="https://kit.fontawesome.com/39350fd9df.js"></script>
  <!-- bootstrap css -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" type="text/css">
</head>

<body>
  <div class="container mt-4">

    <h1 class="display-4 text-center mb-4"><i class="fas fa-book text-success"></i> <span class="text-success">BlueStock</span> <span class="text-muted">Book Shop</span></h1>

    <form action="#" id="book-form">
      <div class="form-group mb-2">
        <label for="title">Book Title</label>
        <input type="text" name="title" id="title" class="form-control">
      </div>
      <div class="form-group mb-2">
        <label for="author">Author Name</label>
        <input type="text" name="author" id="author" class="form-control">
      </div>
      <div class="form-group">
        <label for="book-id">Reference Id</label>
        <input type="text" name="title" id="bookId" class="form-control">
      </div>

      <input type="submit" value="Add Book" class="btn btn-primary w-100 mt-3" id="submit">
    </form>

    <table class="table table-striped mt-4">

      <thead>
        <tr>
          <th>Book Title</th>
          <th>Author Name</th>
          <th>Reference Id</th>
          <th class="text-warning"><small>Delete book</small></th>
        </tr>
      </thead>

      <tbody id="book-list"></tbody>
    </table>
  </div>

  <script src="app.js" type="text/javascript"></script>
</body>

</html>

Upvotes: 1

Related Questions