Actualmente nuestra vista de Hero tiene un bloque de cabecera, otro de atributos (stats) del personaje y el último, con las habilidades.
Lo siguiente que vamos a hacer es mostrar los items del personaje. La API de items nos devuelve un objeto que tiene este formato:
{ "mainHand":{ "id":"P6_Unique_Scythe1H_04", "name":"Jesseth Skullscythe", "icon":"p6_unique_scythe1h_04_demonhunter_male", "displayColor":"green", "tooltipParams":"/item/jesseth-skullscythe-P6_Unique_Scythe1H_04", "requiredLevel":70, "itemLevel":1, "stackSizeMax":0, "accountBound":true, "flavorText":"This assembly of sharpened bones will make you a true artist in combat.", "typeName":"Ancient Set Scythe", "type":{ "twoHanded":false, "id":"scythe1h"
}, "armor":0, "damage":"1682-2259 Damage\n1.38 Attacks per Second", "dps":"2,715.3", "attacksPerSecond":1.3779999, "minDamage":1682, "maxDamage":2259, "slots":"mainHand", "attributes":{ "primary":[ "+1433-1798 Damage", "+998 Intelligence", "+899 Vitality", "Increases Attack Speed by 6%"
], "secondary":[ "+19 Maximum Essence", "Monster kills grant +254 experience."
]}, "openSockets":0, "gems":[{ "item":{ "id":"x1_Emerald_10", "slug":"flawless-royal-emerald", "name":"Flawless Royal Emerald", "icon":"x1_emerald_10_demonhunter_male", "path":"item/flawless-royal-emerald-x1_Emerald_10"
}, "attributes":[ "Critical Hit Damage Increased by 130.0%"
], "isGem":true, "isJewel":false}], "seasonRequiredToDrop":-1, "isSeasonRequiredToDrop":false}}
Este modelo de datos corresponde al slot del arma (mano principal o main hand). Tenemos 13 tipos de objetos (items) distintos, que son los siguientes: cabeza (head), cuello (neck), pecho (torso), hombros (shoulders), piernas (legs), cintura (waist), manos (hands), brazales (bracers), pies (feet), dedo izquierdo (leftFinger), dedo derecho (rightFinger), mano dominante (mainHand) y mano secundaria (offHand).
Según la documentación, para obtener la imagen de un objeto, tenemos que usar esta url http://media.blizzard.com/d3/icons/items/large/ + la propiedad icon + .png.
Si quisiéramos obtener la imagen del arma principal (mainHand) tendríamos que poner lo siguiente: http://media.blizzard.com/d3/icons/items/large/p6_unique_scythe1h_04_demonhunter_male.png
Que se corresponde con esta imagen:
Como has podido ver, cada item tiene una gran cantidad de propiedades que podríamos usar para crear nuestra app, pero para evitar complejidad y no alargar mucho el tema, no vamos a usar todas.
Aparte de la propiedad icon, vamos a usar name, displayColor (para saber la calidad del objeto) y gems (para saber si tiene gemas o joyas).
La estructura HTML que vamos a seguir para mostrar los objetos de nuestro personaje en pantalla es la misma que se ve en este boceto, en el bloque de items:
Un pequeño problema que podríamos tener es que nuestro personaje no tenga objetos equipados en todos los huecos posibles. Por ejemplo, puede darse el caso de que el personaje no tenga equipado el objeto de legs. Si esto pasara, se nos descuadraría la cuadrícula o se mostraría vacía. Es nuestro deber, como developers, controlar estos posibles casos.
La lista de objetos de nuestro personaje descansa en la variable items del componente /views/Hero/Index.vue. Tenemos que crear el componente de HeroItems para poder usarlo.
Dentro del directorio /HeroItems creamos nuestro componente principal Index.vue y le damos este contenido:
Ahora, desde el componente principal de la vista Hero, es decir, en /views/Hero/Index.vue hacemos el ritual de siempre: traer, declarar y utilizar el componente:
Vamos a crear el componente correspondiente a un objeto, que luego reutilizaremos para pintar todos los objetos. A nivel de API, todos los objetos del personaje son iguales, por lo que no tenemos que hacer distinciones.
Empezamos creando el fichero /Hero/HeroItems/ItemDetail.vue. Continuamos con la definición básica de nuestro componente:
Lo siguiente que vamos a definir son las clases CSS que necesitamos para este componente. Para ello, dentro de style agregamos lo siguiente:
.d3-icon-itemmin-height100px// El borde de la caja va determinar la rareza del objeto, segun el color que tengaborder-top-style solidborder-top-width4px&.item-noneborder-colortransparent&.item-greenborder-color#8bc34a&.item-orangeborder-color#ff9800&.item-yellowborder-color#ffeb3b&.item-blueborder-color#03a9f4&.item-whiteborder-color#a0aab5
Estas clases las vamos a usar para determinar la calidad de nuestro objeto. Recuerda que el objeto puede ser blanco (normal), azul (mágico), amarillo (raro), verde (de conjunto) o naranja (legendario).
En el HTML vamos a poner lo siguiente:
<template><!-- Clase que determina el color --><div class="text-center bg-dark h-100 pt-3 d3-icon-item" :class="itemClassColor"><div class="d-flex flex-column justify-content-between h-100"><!-- Si el item tiene ID, es que tenemos la información --><!-- Es decir, que tiene un objeto equipado. --><template v-if="item.id"><div><div v-if="item" class="item-image"><!-- Nombre del objeto --><p class="text-muted">{{ item.name }}</p><!-- Imagen correspondiente al objeto --><img :src="itemUrl" :alt="item.slotName + ' ' + item.name "></div></div><div><!-- No todos los objetos tienen gemas --><!-- Por lo tanto, si el objeto tiene gemas engarzadas --><template v-if="itemHasGems"><!-- Puede ser Gema o Joya --><small>{{ gemOrJewel }}:</small><ul class="list-inline"><!-- Un objeto puede tener varias gemas --><li v-for="(gem, index) in item.gems" :key="'gem-'+index" class="list-inline-item"><!-- Componente gema --><ItemDetailGem :gem="gem"/></li></ul></template></div></template><!-- En caso de que no tenga el objeto equipado --><p v-else><!-- Mostramos el nombre del slot y dejamos el contenido vacío --><b-badge class="text-dark"> {{item.slotName}} </b-badge></p></div></div></template>
Con los comentarios queda bastante claro que hace cada cosa.
Ahora necesitamos crear las computed properties que estamos usando en el template. Agregamos, en nuestro bloque de JavaScript, las siguientes funciones (computadas):
computed:{// Resuelve la URL de la imagenitemUrl(){const host ='http://media.blizzard.com/d3/icons/items/large/'return`${host}${this.item.icon}.png`},// Comprueba si el item tiene gemasitemHasGems(){returnObject.prototype.hasOwnProperty.call(this.item,'gems')},// Si tiene gemas, comprueba si es Gema o Joya// Puede haber varias Gemas. Solo puede haber una Joya. No puede haber joyas y gemas mezcladasgemOrJewel(){returnthis.item.gems[0].isGem?'Gems':'Jewel'},// Clase CSS para saber la rarezaitemClassColor(){if(Object.prototype.hasOwnProperty.call(this.item,'displayColor')){return`item-${this.item.displayColor}`}// Si no tiene color (es que no hay objeto equipado)return'item-none'}}
El código completo se ve así:
<template>
<!-- Clase CSS que determina el color -->
<div class="text-center bg-dark h-100 pt-3 d3-icon-item" :class="itemClassColor">
<div class="d-flex flex-column justify-content-between h-100">
<!-- Si el item tiene `id`, es que tenemos la información -->
<!-- Es decir, que tiene un objeto equipado. -->
<template v-if="item.id">
<div>
<div v-if="item" class="item-image">
<!-- Nombre del objeto -->
<p class="text-muted">{{ item.name }}</p>
<!-- Imagen correspondiente al objeto -->
<img :src="itemUrl" :alt="item.slotName + ' ' + item.name ">
</div>
</div>
<div>
<!-- No todos los objetos tienen gemas -->
<!-- Por lo tanto, si el objeto tiene gemas engarzadas -->
<template v-if="itemHasGems">
<!-- Puede ser Gema o Joya -->
<small>{{ gemOrJewel }}:</small>
<ul class="list-inline">
<!-- Un objeto puede tener varias gemas -->
<li v-for="(gem, index) in item.gems" :key="'gem-'+index" class="list-inline-item">
<!-- Componente gema -->
<ItemDetailGem :gem="gem"/>
</li>
</ul>
</template>
</div>
</template>
<!-- En caso de que no tenga el objeto equipado -->
<p v-else>
<!-- Mostramos el nombre del slot y dejamos el contenido vacío -->
<b-badge class="text-dark"> {{item.slotName}}</b-badge>
</p>
</div>
</div>
</template>
<script>
export default {
name: 'ItemDetail',
props: {
item: {
type: Object || undefined,
required: true
}
},
computed: {
itemUrl () {
const host = 'http://media.blizzard.com/d3/icons/items/large/'
return `${host}${this.item.icon}.png`
},
itemHasGems () {
return Object.prototype.hasOwnProperty.call(this.item, 'gems')
},
gemOrJewel () {
return this.item.gems[0].isGem ? 'Gems' : 'Jewel'
},
itemClassColor () {
if (Object.prototype.hasOwnProperty.call(this.item, 'displayColor')) {
return `item-${this.item.displayColor}`
}
return 'item-none'
}
}
}
</script>
<style lang="stylus">
.d3-icon-item
min-height 100px
border-top-style solid
border-top-width 4px
&.item-none
border-color transparent
&.item-green
border-color #8bc34a
&.item-orange
border-color #ff9800
&.item-yellow
border-color #ffeb3b
&.item-blue
border-color #03a9f4
&.item-white
border-color #a0aab5
</style>
Para terminar, vamos a crear el componente de la gema (o joya). Creamos un nuevo archivo ItemDetailGem.vue al mismo nivel de ItemDetail.vue, es decir, dentro de /HeroItems.
El único cambio que hay aquí es que en la URL de la imagen estamos usando el tamaño pequeño (small) en vez del grande (large) que usamos en los demás items.
Tenemos que importar este componente en el componente de detalle. Para ello, desde /Hero/HeroItems/ItemDetail.vue agregamos lo siguiente:
Con esto ya tenemos los componentes de items terminados. Queda llamarlos desde la vista principal y ¡💥! Deberían aparecer todos.
Desde /Hero/HeroItems/Index.vue, cargamos el componente de ItemDetail, lo declaramos y los utilizamos:
<template>
<section class="hero-items mb-5">
<h2 class="font-diablo">Items</h2>
<hr class="bg-white">
<!-- Grid de 3 columnas. Mostramos solo una centrada -->
<b-row>
<b-col cols="4" offset="4">
<ItemDetail :item="itemsData.head"/>
</b-col>
</b-row>
<hr>
<!-- Grid de 3 columnas -->
<b-row>
<b-col>
<ItemDetail :item="itemsData.shoulders"/>
</b-col>
<b-col>
<ItemDetail :item="itemsData.torso"/>
</b-col>
<b-col>
<ItemDetail :item="itemsData.neck"/>
</b-col>
</b-row>
<hr>
<!-- Grid de 3 columnas -->
<b-row>
<b-col>
<ItemDetail :item="itemsData.hands"/>
</b-col>
<b-col>
<ItemDetail :item="itemsData.waist"/>
</b-col>
<b-col>
<ItemDetail :item="itemsData.bracers"/>
</b-col>
</b-row>
<hr>
<!-- Grid de 3 columnas -->
<b-row>
<b-col>
<ItemDetail :item="itemsData.leftFinger"/>
</b-col>
<b-col>
<ItemDetail :item="itemsData.legs"/>
</b-col>
<b-col>
<ItemDetail :item="itemsData.rightFinger"/>
</b-col>
</b-row>
<hr>
<!-- Grid de 3 columnas -->
<b-row>
<b-col>
<ItemDetail :item="itemsData.mainHand"/>
</b-col>
<b-col>
<ItemDetail :item="itemsData.feet"/>
</b-col>
<b-col>
<ItemDetail :item="itemsData.offHand"/>
</b-col>
</b-row>
</section>
</template>
<script>
import ItemDetail from './ItemDetail'
// Objeto con las keys de los 'items' del personaje
const defaultItems = {
head: {
slotName: 'head'
},
shoulders: {
slotName: 'Shoulders'
},
torso: {
slotName: 'Torso'
},
neck: {
slotName: 'Neck'
},
hands: {
slotName: 'Hands'
},
waist: {
slotName: 'Waist'
},
bracers: {
slotName: 'Bracers'
},
leftFinger: {
slotName: 'Left Finger'
},
legs: {
slotName: 'Legs'
},
rightFinger: {
slotName: 'Right Finger'
},
mainHand: {
slotName: 'Main Hand'
},
feet: {
slotName: 'Feet'
},
offHand: {
slotName: 'Off Hand'
}
}
export default {
name: 'HeroItems',
components: { ItemDetail },
props: {
items: {
type: Object,
required: true
}
},
computed: {
itemsData () {
// Fusionar objetos:
// Esto lo hacemos para mostrar el hueco vacío en caso de que ese objeto no esté equipado
// Si NO hay item equipado, manda el valor de 'defaultItems' correspondiente
// Si hay item equipado, manda la info del item
return {
...defaultItems,
...this.items
}
}
}
}
</script>
Si tienes todo correcto y sin errores deberías ver algo como esto:
En mi caso, todos los personajes que tenía en el momento de crear este escrito estaban a niveles altos y con todos los huecos de equipación completos.
Sin embargo, rebuscando por internet, he encontrado un perfil a nivel 1, con varios huecos de items vacíos, y solo una habilidad activa. Así es como se vería:
Si has llegado hasta aquí... 🎉 ¡Enhorabuena! 🎉 Ya tienes una super aplicación con datos reales de personajes Diablo III.
Esta vista de la aplicación da mucho juego, pues la información que nos devuelve la API es muy extensa. En este curso no hemos trabajado con toda la información existente, ya que se haría demasiado complejo, pero te animo a que hagas extensiones y/o mejoras.
También te recuerdo que si quieres contribuir, ya sabes que el repositorio original siempre está abierto a mejoras y sugerencias.
En la siguiente lectura veremos cómo crear directivas personalizadas (custom directives).