Bin Ury
Bin Ury

Reputation: 839

What is the behavior of document.createElement when passed as an argument?

When attempting to pass an element created by the document.createElement method, we encounter unusual results:

var Todo = (function () {
  return {
    items: [],
    addItem: function () {
      var task = document
        .querySelector('#top-todo')
        .value
        .trim()
      if (task !== '') {
        this.items.push({ task, complete: false })
        document.querySelector('#top-todo').value = ''
        console.log('Added item: ' + task)
        var newTodo = document.createElement('li')
          .appendChild(document.createElement('p'))
          .appendChild(document.createTextNode(task))
        document.querySelector('#todo-list').appendChild(newTodo)
      }
    }
  }
})()
<div>
  <ul id="todo-list">
    <li>
      <input id="top-todo" placeholder="I need to..." type="text" />
    </li>
  </ul>
  <input onclick="Todo.addItem()" type="button" value="add" />
</div>

Rather than a total failure or typeError we see only the textNode element being appended to the #todo-list element. What is the cause of this behavior? And is there a valid way to use the method as an argument to append or insert?

Upvotes: 1

Views: 937

Answers (3)

guest271314
guest271314

Reputation: 1

Node.appendChild

The returned value is the appended child.

You can chain .parentElement.parentElement, the number of nested elements set as child elements of li, to last .appendChild() call to get reference to document.createElement('li') at newTodo

var Todo = (function () {
  return {
    items: [],
    addItem: function () {
      var task = document
        .querySelector('#top-todo')
        .value
        .trim()
      if (task !== '') {
        this.items.push({ task, complete: false })
        document.querySelector('#top-todo').value = ''
        console.log('Added item: ' + task)
        var newTodo = document.createElement('li')
          .appendChild(document.createElement('p'))
          .appendChild(document.createTextNode(task))
          .parentElement.parentElement                    
        document.querySelector('#todo-list').appendChild(newTodo)
      }
    }
  }
})()
<div>
  <ul id="todo-list">
    <li>
      <input id="top-todo" placeholder="I need to..." type="text" />
    </li>
  </ul>
  <input onclick="Todo.addItem()" type="button" value="add" />
</div>

Upvotes: 1

Damien
Damien

Reputation: 3220

document.createElement('li')

returns a li element

document.createElement('li')
  .appendChild(document.createElement('p'))

returns a p element

document.createElement('li')
  .appendChild(document.createElement('p'))
  .appendChild(document.createTextNode(task))

returns a text node

So you are actually appending a text node.

Upvotes: 1

Ouroborus
Ouroborus

Reputation: 16875

var newTodo = document.createElement('li')
  .appendChild(document.createElement('p'))
  .appendChild(document.createTextNode(task))

.appendChild() returns the element that was appended. When you chain functions, you get the last returned value in the chain.

It goes kinda like this:

  1. Create li (li is current working value)
  2. Create p
  3. Append p to li (p is current working value)
  4. Create text node
  5. Append text node to p (text node is current working value)
  6. Assign current working value to newTodo.

And then you go on to insert newTodo, which is a text node, into the document.

Breaking it up like this would get you what you want:

var newTodo = document.createElement('li');
newTodo
  .appendChild(document.createElement('p'))
  .appendChild(document.createTextNode(task));

Upvotes: 1

Related Questions