🛠️ Dota Heroes: Refactoring
We have finished building the heroes list. Let’s clean up the code before we move on. As before, we’ll look through the code together from top to bottom.
Adding heroes to the DOM
We used this block of code twice to create heroes and add them to the DOM.
const li = document.createElement('li')
li.classList.add('hero')
li.innerHTML = `
<a href="#">
<span class="hero__name"> ${hero.localized_name} </span>
<img src="https://api.opendota.com${hero.img}" alt="${hero.localized_name} image">
</a>
`
heroesList.appendChild(li)
We can put it into a function called addHeroToDOM
.
const addHeroToDom = hero => {
const li = document.createElement('li')
li.classList.add('hero')
li.innerHTML = `
<a href="#">
<span class="hero__name"> ${hero.localized_name} </span>
<img src="https://api.opendota.com${hero.img}" alt="${hero.localized_name} image">
</a>
`
heroesList.appendChild(li)
}
Using addHeroToDOM
:
zlFetch(/*...*/)
.then(response => {
// ...
heroes.forEach(addHeroToDOM)
filterDiv.addEventListener('change', event => {
// ...
filtered.forEach(addHeroToDOM)
})
})
Filtering Heroes
Most of the code we wrote are used to filter heroes. We can put them into a function so its easier for us to read and understand.
Let’s call this function filterHeroesByCategories
const filterHeroesByCategories = heroes => {
const selectedAttackTypes = [...document.querySelectorAll('#attack-type input:checked')]
.map(checkbox => checkbox.id)
const selectedPrimaryAttributes = [...document.querySelectorAll('#primary-attribute input:checked')]
.map(checkbox => checkbox.id)
const selectedRoles = [...document.querySelectorAll('#role input:checked')]
.map(checkbox => checkbox.id)
return heroes
// Filter by attack type
.filter(hero => {
if (selectedAttackTypes.length === 0) return true
const attackType = hero.attack_type.toLowerCase()
return selectedAttackTypes.includes(attackType)
})
// Filter by primary attribute
.filter(hero => {
if (selectedPrimaryAttributes.length === 0) return true
return selectedPrimaryAttributes.includes(hero.primary_attr)
})
// Filter by role
.filter(hero => {
if (selectedRoles.length === 0) return true
const heroRoles = hero.roles.map(role => role.toLowerCase())
return selectedRoles.some(role => {
return heroRoles.includes(role)
})
})
}
Using filterHeroesByCategories
:
filtersDiv.addEventListener('change', event => {
const filtered = filterHeroesByCategories(heroes)
})
We can format the data from the server so it’s easier for us to use later.
For example, we need to use hero.localized_name
to get the hero’s name. But we don’t need to remember localized_name
when we actually create the hero! We can make it easier for us by changing localized_name
to name
instead.
To make this change, we’ll run the response through a map
function.
zlFetch(`${dotaApi}/constants/heroes`)
.then(response => {
const heroes = Object.values(response.body).map(hero => {
return {
name: hero.localized_name,
attackType: hero.attack_type.toLowerCase(),
primaryAttribute: hero.primary_attr,
roles: hero.roles
image: `https://api.opendota.com${hero.img}`
}
})
We can do more than changing the property name. If you looked at filterHeroesByCategories
, you’ll notice we had to make attack_type
and roles
lowercase.
We can lowercase these values in this data-formatting phase so we don’t need to do that again later.
zlFetch(`${dotaApi}/constants/heroes`)
.then(response => {
const heroes = Object.values(response.body).map(hero => {
return {
name: hero.localized_name,
attackType: hero.attack_type.toLowerCase(),
primaryAttribute: hero.primary_attr,
roles: hero.roles.map(role => role.toLowerCase()),
image: hero.img
}
})
})
}
We can also add the domain https://api.opendota.com
to the image property in this phase, so we don’t need to remember the domain when creating the <li>
element!
zlFetch(`${dotaApi}/constants/heroes`)
.then(response => {
const heroes = Object.values(response.body).map(hero => {
return {
name: hero.localized_name,
attackType: hero.attack_type.toLowerCase(),
primaryAttribute: hero.primary_attr,
roles: hero.roles.map(role => role.toLowerCase()),
image: `https://api.opendota.com${hero.img}`
}
})
})
}
I’ll leave you to adjust the your code to work with the formatted heroes
object :)