Search code examples
javascriptfunctionxmlhttprequestsubmitcontenteditable

name.forEach is not a function after button is clicked


I am trying to edit/update current data using the contenteditable attribute which I have successfully enabled onclick. My 'enter' key allows the data to be submitted. However, the console.log reads that a PUT request has been made for a particular list item but without the 'title' or 'isbn' being updated along with it.

Another prominent issue is that my console.log shows books.forEach is not a function, and I have no idea why this is the case since the code inside that function is processed.

HTML ('li' items are solely JS-Generated with a POST request)

<div id="divShowBooks">
  <li id="[object HTMLParagraphElement]">
    <p id="24" name="anID" placeholder="24">1</p>
    <p id="TEST" name="aTitle" placeholder="TEST">TEST</p>
    <p id="12345" name="anISBN" placeholder="12345" contenteditable="true">12345</p>
    <button>Delete</button>
  </li>
</div>

JavaScript

var book_list = document.querySelector('#divShowBooks');

    book_list.innerHTML = "";

    var books = JSON.parse(this.response);

    books.forEach(function (book) {

        // Text information to be displayed per item
        var id = document.createElement('p');
        id.type = 'text';
        id.innerHTML = book.id;
        var title = document.createElement('p');
        title.type = 'text';
        title.innerHTML = book.title;

        var isbn = document.createElement('p');
        isbn.type = 'text';
        isbn.innerHTML = book.isbn;

        // Defining the element that will be created as a list item
        var book_item = document.createElement('li');

        // Displays id, title and ISBN of the books from the database
        book_item.appendChild(id);
        book_item.appendChild(title);
        book_item.appendChild(isbn);

        // Creates an ID attribute per list item
        book_item.setAttribute("id", id)

        // Assigns attributes to p items within book items
        id.setAttribute("id", book.id)
        title.setAttribute("id", book.title)
        isbn.setAttribute("id", book.isbn)

        // Adding a generic name to these elements
        id.setAttribute("name", "anID")
        title.setAttribute("name", "aTitle")
        isbn.setAttribute("name", "anISBN")


        title.addEventListener('click', function (e) {
            e.preventDefault();
            title.contentEditable = "true";
            title.setAttribute("contenteditable", true);
            title.addEventListener('keypress', function (e) {
                if (e.keyCode === 13) {
                    e.preventDefault();
                    xhttp.open("PUT", books_url + '/' + book.id, true);
                    var editTitle = new FormData() /
                        editTitle.append("title", document.getElementsByName("aTitle")[0].value)
                    xhttp.setRequestHeader('Content-type', 'application/x-www-form-urlencoded')
                    xhttp.send(); //
                }
            });
        });

UPDATE

I have added the following to my code. This seems to display my database items as an array in the log. But, I am now having a similar issue with Uncaught TypeError: JSON.parse(...).map is not a function:

var params = [
    id = 'id',
    title = 'title',
    isbn = 'isbn',
    createdAt = 'createdAt',
    updatedAt = 'updatedAt'
];

var books = JSON.parse(this.response).map(function(obj) {
    return params.map(function(key) {
        return obj[key];
    });
});
console.log(books);

UPDATE 2

Here is an image of what I receive in the console.log. The first part displays the original JSON content and the second is my attempt to convert each object into an array.

See Image


Solution

  • You have to make sure that your books variable actually contains an Array after parsing.

    Alternatively, but this wouldn't make sense, just to address the "books.forEach is not a function" issue, You can use Object.assign([], this.response);. To make sure that books will contain an array, you wrap it in a try catch and make something like this:

    var books = [];
    
    try {
        books = Object.assign([], this.response);
    
    } catch (error) {
        books = [];
    }
    

    books.forEach will then be expected to always work but you have to be careful because something like this could happen:

    var myStringObject = "{'myProperty':'value'}";
    var myArray = Object.assign([], myStringObject );
    //myArray value is ["{", "'", "myProperty", "'", ":", "'", "value", "'", "}"]
    

    Which will leave you having to check the book in your forEach callback if it is correct:

    //at the topmost of your forEach callback
    if(!book.id) throw BreakException; //A simple break will not work on forEach
    

    This will leave you again with another exception to handle. Or leave you having to use the traditional for loop since you cannot short circuit Array.forEach with a break.

    TLDR: make sure books always contains an Array.