🛠️ Dota Heroes: Hero Page

Hey ,

I'm thrilled to help you learn JavaScript. Unfortunately, you've landed on a page where you cannot access with your current purchase.

Please upgrade (use this link) access this content.

I'm super eager to help you learn more!

🛠️ Dota Heroes: Hero Page

We will build the Hero Page for the Dota application. Here’s an example of what it looks like:

Example of the hero page

We’ll eventually combine this page with the Heroes List Page to make a full application. For now, let’s be patient and do one thing at a time.

Please use the Starter file given to you in this lesson. We’ll write the HTML in a file called hero.html and the JavaScript in a file called hero.js.

Please also navigate to /hero.html in your browser to view this page.

Anatomy of the Hero Page

The Hero Page contains 4 pieces of information:

  • The hero’s name
  • The hero’s image
  • The hero’s description
  • The hero’s abilities

For now, let’s say we want to fetch information about the hero Axe. We’ll set heroName to be axe.

const heroName = 'axe'

We can get the hero’s image via the heroes endpoint.

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}`
      }
    })
  })
  .catch(console.log)

Getting the hero’s description and abilities are slightly tougher. After digging around the API, I realized we can also get them through the constants repository.

The constants endpoint

Getting the hero’s description

The hero’s description can be found in hero_lore.json .

Hero lore file

If you click on hero_lore.json, you should see an object. This object contains hero names as keys and the description of each hero as values.

Contents in hero_lore.json.

To access hero_lore.json, we can use the constants API.

zlFetch(`${dotaApi}/constants/hero_lore`).then(response => {
  console.log(response.body)
})
Response from the hero lore endpoint.

To get the description for Axe, we simply need to find Axe’s entry from this data.

zlFetch(`${dotaApi}/constants/hero_lore`).then(response => {
  const heroLore = response.body[heroName]
  console.log(heroLore)
})
Axe's description

Getting the hero’s abilities

Each hero has 4-5 abilities. These abilities can be found in hero_abilities.json.

hero_abilities file.

We can use the hero_abilities endpoint to fetch this data.

zlFetch(`${dotaApi}/constants/hero_abilities`).then(response => {
  console.log(response.body)
})
Response from the hero_abilities endpoint.

We can get Axe’s abilities by looking under npc_dota_hero_axe.

Axe's abilities.

Axe has 4 abilities:

  • Beserker’s call
  • Battle Hunger
  • Counter Helix
  • Culling blade

Note: Ignore the generic_hidden portion. It’s not part of a hero’s abilities and I have no idea why it’s in the data. Since it’s in the data, we simply have to deal with it.

We can get this list of abilities like this:

zlFetch(`${dotaApi}/constants/hero_abilities`).then(response => {
  const heroAbilities = response.body[`npc_dota_hero_${heroName}`].abilities
})

Since we don’t need generic_hidden, we can filter it out from the list of abilities.

zlFetch(`${dotaApi}/constants/hero_abilities`).then(response => {
  const heroAbilities = response.body[
    `npc_dota_hero_${heroName}`
  ].abilities.filter(ability => ability !== 'generic_hidden')

  console.log(heroAbilities)
})
Axe's abilities.

Getting information about each ability

We need to know three things about each ability

  1. Name
  2. Image
  3. Description

We can get this information from abilities.json

Abilities file

If you click on abilities.json you’ll get to a page that “says we can’t show files that are this big right now”.

Abilities file is too large to be shown in Github.

Don’t worry about this. Click on view raw and you’ll see a list of all the abilities that exist in Dota.

Each entry in this JSON file contains the name, image, and description of the ability. Here’s an example of Axe’s first ability, berserker’s call.

Axe's first ability, Berserker's call.

We can fetch abilities.json like this:

zlFetch(`${dotaApi}/constants/abilities`).then(response => {
  console.log(response.body)
})
Response from the abilities endpoint.

We can then loop through each of Axe’s abilities to find their description. We can make the loop using the map function in this case.

zlFetch(`${dotaApi}/constants/abilities`).then(response => {
  const allAbilities = response.body

  zlFetch(`${dotaApi}/constants/hero_abilities`).then(response => {
    const heroAbilities = response.body[`npc_dota_hero_${heroName}`].abilities
      .filter(ability => ability !== 'generic_hidden')
      .map(ability => allAbilities[ability])

    console.log(heroAbilities)
  })
})
Axe's abilities

Each item in this mapped list contains the three pieces of information we need. Here’s an example of what we have about Berserker’s Call.

Axe's first ability.

This step is optional. I like to format othe data from the endpoint so I create a map that contains only the data I need. This makes my life slightly easier so I don’t have to remember the dname property when I search for the ability’s name.

zlFetch(`${dotaApi}/constants/abilities`).then(response => {
  const allAbilities = response.body

  zlFetch(`${dotaApi}/constants/hero_abilities`).then(response => {
    const heroAbilities = response.body[`npc_dota_hero_${heroName}`]
      /* ... */
      .map(ability => {
        return {
          name: ability.dname,
          description: ability.desc,
          image: `https://api.opendota.com${ability.img}`
        }
      })

    console.log(heroAbilities)
  })
})

Inserting the data into the DOM

Take a look at the hero.html. You’ll notice some data attributes in this file. These data attributes let us select the relevant elements easily.

Data attributes in the hero.html file
const heroNameEl = document.querySelector('[data-hero-name]')
const heroImageEl = document.querySelector('[data-hero-image]')
const heroDescEl = document.querySelector('[data-hero-description]')
const heroAbilitiesEl = document.querySelector('[data-hero-abilities]')

Inserting the hero’s name

We can insert the hero’s name by replacing the heroNameEl.textContent.

// Inserting the hero's name
heroNameEl.textContent = heroName
Inserted the hero's name.

We can create a simple capitalize function to capitalize this name.

function capitalize (word) {
  return word.slice(0, 1).toUpperCase() + word.slice(1)
}

heroNameEl.textContent = capitalize(heroName)
Capitalized the hero's name.

Inserting the hero’s image

We can insert the hero’s image by changing the element’s src attribute. To do that, we need to find the hero’s image from the /heroes endpoint.

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}`
    }
  })
  const hero = heroes.find(h => h.name.toLowerCase() === heroName)
  heroImageEl.src = `${hero.image}`
})
Insert hero's image.

Inserting the hero’s description

We can insert the hero’s description by changing heroDescEl.textContent.

// Inserting the hero's description
zlFetch(`${dotaApi}/constants/hero_lore`).then(response => {
  const heroLore = response.body[heroName]
  heroDescEl.textContent = heroLore
})
Inserted the hero's description

Inserting the hero’s abilities

Before we can insert the hero’s abilities, we need to create a list of abilities. The required HTML for each ability is as follows:

<li class="ability">
  <p class="ability__title">ABILITY_NAME</p>
  <img class="ability__img" src="ABILITY_IMAGE" alt="ABILITY_NAME" />
  <p class="desc">ABILITY_DESCRIPTION</p>
</li>

We can create the HTML for the list of abilities by chaining another map, followed by a join.

zlFetch(`${dotaApi}/constants/abilities`).then(response => {
  const allAbilities = response.body

  zlFetch(`${dotaApi}/constants/hero_abilities`).then(response => {
    const heroAbilities = response.body[`npc_dota_hero_${heroName}`]
      /* ... */
      .map(ability => {
        return `<li class="ability">
              <p class="ability__title">${ability.name}</p>
              <img class="ability__img" src="${ability.image}" alt="${ability.name}">
              <p class="desc">${ability.description}</p>
            </li>`
      })
      .join('')
  })
})

We can insert this HTML by adding it to the <ul> element.

zlFetch(`${dotaApi}/constants/abilities`).then(response => {
  const allAbilities = response.body

  zlFetch(`${dotaApi}/constants/hero_abilities`).then(response => {
    const heroAbilities = response.body[`npc_dota_hero_${heroName}`]
      /* ... */
      .map(ability => {
        return `<li class="ability">
              <p class="ability__title">${ability.name}</p>
              <img class="ability__img" src="${ability.image}" alt="${ability.name}">
              <p class="desc">${ability.description}</p>
            </li>`
      })
      .join('')

    heroAbilitiesEl.innerHTML = heroAbilities
  })
})
Inserting the hero's abilities

Hiding the ability’s section initially

It can be quite weird to see the empty Abilities section when there’s no content. We can hide this by adding a hidden attribute to the section initially.

<section hidden>
  <h2>Abilities</h2>
  <ul class="abilities flow" data-hero-abilities></ul>
</section>

We’ll remove the hidden attribute after we inserted the hero’s abilities into the DOM.

zlFetch(`${dotaApi}/constants/abilities`)
  .then(response => {
    const allAbilities = response.body

    zlFetch(`${dotaApi}/constants/hero_abilities`)
      .then(response => {
        const heroAbilities = response.body[`npc_dota_hero_${heroName}`]
          /* ... */
	        .join('')

        heroAbilitiesEl.innerHTML = heroAbilities        heroAbilitiesEl.closest('section').removeAttribute('hidden')
      })
  })

Getting another hero’s information

You can change the heroName variable to get information about another hero.

const heroName = 'abaddon'
Getting Abaddon's information