Notes on Day 15 of 30 Days of JavaScript

Table of Contents

Lessons Learned

Event delegation

Wes used a nice analogy for this concept: “responsible parents and negligent children”. Basically we delegate a single event listener for a group of items to their common direct parent.

Problem

In the example, checkboxes are added to the DOM as the user submits an entry. However we cannot attach an event listener to them as they are created because there are better ways to handle this problem.

<input type="checkbox" name="task" data-id="task-1" />
<input type="checkbox" name="task" data-id="task-2" />
...
<input type="checkbox" name="task" data-id="task-100" />

Solution

We can group the checkboxes in a container an set the event listener to this element, or their parent. Usually input elements are enclosed in a form so we have:

<form id="task-form">
  <input type="checkbox" name="task" data-id="task-1" />
  <input type="checkbox" name="task" data-id="task-2" />
  ...
  <input type="checkbox" name="task" data-id="task-100" />
</form>

and we can attach a single listener to the parent and just set a general filter to select checkboxes via the event.target attribute:

function toggleCheckbox(event) {
  if(event.target.matches('input[type=checkbox']))
    // do something
}

document.getElementById('task-form').addEventListener('click', toggleCheckbox)

This pattern is ideal for groups of DOM elements that are created dynamically and/or need time before being rendered to the screen.

Element.matches() & event.target.is()

The matches() method checks to see if the Element would be selected by the provided selectorString – in other words – checks if the element “is” the selector.

this binding inside methods

While doing the task I mindlessly chose to declare the method as arrow function because it’s what I’m used to.

Problem

I was following the task when the following code appeared inside the method:

const item = this.querySelector('input[name="item"]')

After execution, it didn’t work and an error occurred:

Uncaught TypeError: this.querySelector is not a function

Solution

I forgot that arrow functions have limitations and it includes no binding to the `this` keyword. The traditional function-declared methods have.

function addItem(e) {
  e.preventDefault()
  const text = (this.querySelector('input[name=task]')).value
  ...
}

Its value is represented by a DOMString containing the HTML serialization of the element’s descendants.

Element.innerHTML

The Element property innerHTML gets or sets the HTML or XML markup contained within the element.

const container = document.querySelector('container')

const content = container.innerHTML

const.innerHTML = `<p>Hello World</p>`

Storage.getItem() & Storage.setItem()

getItem() and setItem() are used to access the Storage interface of modern browsers to set and retrieve data.

  • setItem(key, value) takes both parameters as String type and adds to the Storage
  • getItem(key) will return the value if the key exists otherwise null
localStorage.setItem('message', 'Hello World!')
localStorage.getItem('message')
> 'Hello World!'
  • setItem() overwrites its value if called to the same key
localStorage.setItem('message', 'Hello World!')
localStorage.setItem('message', 'Good day!')
localStorage.getItem('message')
> 'Good day!'

Behavior with arrays

Arrays with primitive types (string, number) as content will be converted to strings and stored. Upon access, they will all be concatenated with commas.

// string
localStorage.setItem('arr', ['a', 'b', 'c'])
localStorage.getItem('arr')
> 'a,b,c'

localStorage.setItem('arr', ['Apple',' Banana', 'Carrot'])
localStorage.getItem('arr')
> 'Apple,Banana,Carrot'

// number
localStorage.setItem('arr', [1, 2, 3, 4, 5])
localStorage.getItem('arr')
> '1,2,3,4,5'

// boolean
localStorage.setItem('arr', [true, false, true])
localStorage.getItem('arr')
> 'true,false,true'

// null and undefined
localStorage.setItem('arr', [null, undefined, null, undefined])
localStorage.getItem('arr')
> ',,,'

Behavior with objects

Regardless if the value is an object or an array of objects, each element will be stored as "[object Object]".

// object
localStorage.setItem('obj', { name: 'Abc', id: 1 })
localStorage.getItem('obj')
> '[object Object]'

// array of objects
localStorage.setItem('obj', [{ name: 'Abc', id: 1 }, { name: 'Xyz', id: 2 }])
localStorage.getItem('obj')
> '[object Object],[object Object]'

JSON.stringify() & JSON.parse()

In relation to accessing the contents of the Storage object, we can use these methods to properly store and retrieve object types.

Actually we can store objects as JSON strings and parse them after retrieval.

// array of objects
const data = JSON.stringify([{ name: 'Abc', id: 1 }, { name: 'Xyz', id: 2 }])
localStorage.setItem('data', data)

JSON.parse(localStorage.getItem('data'))
> '[{ name: "Abc", id: 1 }, { name: "Xyz", id: 2 }]'

References

Twitter, LinkedIn