Introducción

1

Desarrollo de Aplicaciones Profesionales con VueJS

2

VueJS: Creación y Desarrollo de Aplicaciones Web Progresivas

CLI y Dev Tools

3

Herramientas esenciales para desarrollar con BioJS y NodeJS

4

Creación de una Aplicación Vue.js con CLI y Webpack Simple

5

Configuración y uso de Webpack en proyectos JavaScript

6

Configuración y uso de Babel para compatibilidad JavaScript

7

Configuración de eSlimt con EstándarJS y Webpack

8

Integración de SaaS y Bulma en Aplicaciones Vue.js

9

Configuración de Pag para optimizar el workflow de desarrollo HTML

10

Diseño de Mockups: Práctica y Colaboración en Comunidad

11

Creación de Vistas con Mockups y Vue.js

Manipulación del DOM

12

Expresiones en Vue: Manipulación Dinámica del DOM

13

Directivas de Vue: Uso y Funciones Principales

14

Data Binding y Directivas en Vue: bmodel y bevined

15

Propiedades Computadas en JavaScript: Creación y Uso Práctico

16

Uso de Watchers en Biomóvil para Gestión de Cambios de Propiedades

17

Manipulación de Eventos y Métodos en Aplicaciones Web

18

Creación de Interfaz con Vue.js y Framework CSS Bulma

19

Manipulación del DOM con Vue.js: Práctica y Ejercicios

REST y HTTP

20

Integración de Servicios HTTP con JavaScript y API Fetch

21

Uso de la librería Trae para peticiones HTTP con Fetch API en JavaScript

22

Integración de Servicios en Vue.js: Uso de API y Mejora de Código

Sistema de Componentes

23

Creación y Registro de Componentes en Vue.js

24

Creación de Componentes en Vue.js con Bulma para Platzi Music

25

Reactividad y Virtual DOM en Vue.js: Conceptos y Soluciones

26

Ciclo de Vida de Componentes en Vue.js: Hooks y Ejecución de Código

27

Comunicación entre Componentes en Vue.js: Props y Eventos

28

Comunicación entre Componentes en Vue: Eventos de Hijo a Padre

29

Uso de Slots para Inyección Dinámica de HTML en Componentes Vue

30

Comunicación entre Componentes Vue con Event Bus y Plugins

Vue Router

31

Vue Router: Creación de Single Page Applications

32

Instalación y Configuración de Vue Router en Aplicaciones Vue.js

33

Navegación de Rutas con Vue Router en Aplicaciones SPA

Extendiendo VueJS

34

Uso de Modifiers en VueJS para Mejorar Funcionalidad de Eventos

35

Filtros en Vue: Formateo de Duraciones de Canciones en Componentes

36

Creación de Directivas Personalizadas en Vue.js

37

Reutilización de Funcionalidad con Mixins en VueJS

Clases, Animaciones y Transiciones36

38

Integración de Animaciones CSS3 en Aplicaciones VueJS

Vuex

39

Gestión de Estados Centralizados con la Librería BuX

40

Integración de VueX y arquitectura Flux en Vue.js

41

Mutaciones en Vuex: Gestión y Actualización del Estado Reactivo

42

Uso de Getters en Vuex para Acceso Personalizado de Estado

43

Acciones asincrónicas en Vuex: cómo implementarlas y utilizarlas

44

Integración de VueX y Babel en PlatziMusic

Nuxt.js

45

Renderizado del Lado del Servidor con Vue.js y Nuxt

46

Creación de Proyectos con Nact.js en Vue para Server-Side Rendering

47

Integración de VueJS con Nuxt para Server-Side Rendering

Deploy a Producción con Now

48

Despliegue de Aplicaciones con NOW en BIOS JS

49

Despliegue de Aplicaciones Node.js con NOW y Webpack

Conclusiones

50

Construcción de Aplicaciones Profesionales con Vue.js

Bonus

51

Internacionalización de Aplicaciones con Vue I18n

52

Pruebas Unitarias con Vue.js y Webpack: Configuración y Ejecución

53

Autenticación en Vue.js con JSON Web Tokens (JWT)

No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Comunicación entre Componentes Vue con Event Bus y Plugins

30/53
Recursos

¿Cómo mejorar un notification component en Vue?

Al abordar el desarrollo de aplicaciones web con Vue.js, mejorar la experiencia del usuario y la funcionalidad de los componentes es clave para un resultado exitoso. Un aspecto fundamental es cómo manejar y presentar notificaciones dentro de la aplicación. En este artículo, exploraremos cómo podemos enriquecer un notification component para mostrar mensajes contextuales basados en los resultados de búsqueda utilizando las herramientas potentes de Vue y Bulma CSS.

¿Cómo agregar propiedades dinámicas a un componente?

Para hacer que un notification component en Vue sea más versátil, podemos:

  1. Añadir una Propiedad de Color: Modificar el componente para que acepte una propiedad que defina su color. Esto permite que las notificaciones cambien de color según el contexto de alerta que se quiera mostrar. Se pueden usar clases de Bulma como is-danger o is-success para reflejar estados negativos o positivos, respectivamente.

  2. Mostrar Mensajes Contextuales: Hacer que el componente siempre muestre un mensaje tras una búsqueda, ya sea un error o el número de resultados encontrados. Esto mejora la experiencia del usuario al proporcionar retroalimentación inmediata.

  3. Usar Directivas Condicionales: Implementar un v-if o v-show para determinar el tipo de mensaje que se debe mostrar, adaptando dinámicamente el contenido de la notificación.

¿Cómo comunicar componentes hermanos en Vue?

En Vue, la comunicación entre componentes que no tienen una relación directa (como padre-hijo) presenta un desafío curioso. Aquí es donde la magia de Vue, combinada con plugins, entra en juego.

Creación de un Plugin EventBus

Los plugins en Vue son potentes para extender el comportamiento de la aplicación. Veamos cómo crear un plugin EventBus para emitir y escuchar eventos entre componentes:

// Creación de un plugin EventBus en event-bus.js
const EventBus = {};

EventBus.install = function(Vue) {
    Vue.prototype.$bus = new Vue();
};

export default EventBus;

Este objeto EventBus actúa como un emisor de eventos compartido por todos los componentes. Al utilizar funciones como $emit y $on, los componentes pueden comunicarse independientemente de su jerarquía.

Instalación del Plugin

Para integrar el plugin en la aplicación, necesitarás modificar el archivo main.js para registrarlo globalmente:

// main.js
import Vue from 'vue';
import App from './App.vue';
import EventBus from '@/plugins/event-bus';

// Registro del plugin
Vue.use(EventBus);

new Vue({
  render: h => h(App),
}).$mount('#app');

¿Cómo utilizar el plugin EventBus en componentes?

Una vez que nuestro plugin está configurado, podemos utilizarlo para enviar información entre componentes:

  1. Emitir Evento en un Componente:

En un componente, usa el evento $emit para emitir un evento al seleccionar un elemento como una canción:

methods: {
    selectTrack(track) {
        this.$bus.$emit('set-track', track);
    }
}
  1. Escuchar el Evento en Otro Componente:

En otro componente, como un reproductor de música, usa $on para escuchar cuando un evento se emite, y así actualizar la información del componente:

created() {
    this.$bus.$on('set-track', (track) => {
        this.track = track;
    });
}

¿Cuál es el papel del diseño adaptable en aplicaciones Vue?

Diseñar aplicaciones que se adapten a diferentes dispositivos es esencial. Al construir componentes de interfaz como reproductores de música en Vue, es importante considerar etiquetas HTML5 como <audio> que ofrecen controles nativos y personalizables del sistema operativo.

Por ejemplo, en la creación de un componente player, se utiliza la etiqueta <audio controls>, que garantiza que el reproductor de música funcione de manera consistente a través de distintos navegadores y sistemas operativos:

<audio controls :src="track.preview_url"></audio>

Consideraciones finales en la implementación de plugins

La creación de plugins en Vue tiene aplicaciones extensas, no solo para comunicación entre componentes, sino también para reutilizar código y funcionalidades comunes en diferentes partes de una aplicación. Esto no solo promueve un código más limpio y mantenible, sino que también facilita futuras expansiones y mejoras del proyecto. Además, te invito a que explores tus propias ideas sobre el uso de plugins y compartas tus experiencias o dudas en los foros de discusión.

Con esta poderosa herramienta a tu disposición, puedes seguir avanzando en tus habilidades de desarrollo frontend con Vue, asegurando aplicaciones más eficientes y con una experiencia de usuario óptima. ¡Mucha suerte en tu viaje de aprendizaje continuo!

Aportes 43

Preguntas 7

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Estoy de acuerdo que el uso de Vuex soluciona esto mejor centralizando la data, pero ese es un tema para ver en futuros videos, por el momento si me gustaría resaltar que no veo la necesidad en este caso de hacer uso de un plugin (no estoy claro si en el momento de la grabación del mismo si lo era, o si querían dar un ejemplo de plugins), ya que existe el elemento $root:

Player.vue

this.$root.$on("set-track", (track) => {
	this.track = track;
});

Track.vue

this.$root.$emit("set-track", this.track);

Vale, este capítulo ya es difícil de seguir en Vue 3, se puede hacer la comunicación entre hermanos pero es de otra manera, en Vue 3, en el método install del plugin ya NO te pasan el constructor de Vue, yo lo que hice fue hacer un downgrade de mi proyecto a la versión 2 de Vue

En Vue 3 ya no existe el método $on, en su lugar sugieren usar una libreria llamada “mitt” para emitir los eventos y cacharlos

Como emitir / recibir eventos en v3:

Instalar Mitt

npm i mitt

En la carpeta de services crear el archivo ( emitter.js )

import mitt from 'mitt'
export default mitt()

Luego debemos importar emitter.js en Player.vue (Recibe) Track.vue (Emite)

en el archivo Player.vue

import emitter from '@/services/emitter'

created(){
        emitter.on('set-track', (track) =>{
            this.track = track
            this.image = track.album.images[0].url
            console.log(this.image)
        })
    }

en el archivo Track.vue

methods:{
        selectTrack(){
            //emitimos un evento al padre
            emitter.emit('set-track', this.track)
            this.$emit('select', this.track.id)
        }
    }

Event Bus y Plugins permite la comunicacion entre componentes que no tienes relacion de padre/hijo sino q son componentes llamos desde distintos puntos de la aplicacion.

Hola.
Estoy necesitando graficar con highchart y Vue. Los datos para graficar vienen por api.
Estoy usando Axios para graficar pero me responde que no reconoce la variable.
¿En que estoy fallando?
Es importante!! gracias!

<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://code.highcharts.com/stock/highstock.js"></script>
<script src="https://code.highcharts.com/stock/modules/exporting.js"></script>
<script src="https://code.highcharts.com/stock/modules/export-data.js"></script>
  <!-- NPM de el highcharts para VUE -->
<script src="https://cdn.jsdelivr.net/npm/highcharts/highcharts.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue-highcharts/dist/vue-highcharts.min.js"></script>

  <!--
<script src="https://code.highcharts.com/highcharts.js"></script>
-->
<script type="text/javascript" src="https://cdn.rawgit.com/highcharts/highcharts-vue/1ce7e656/dist/script-tag/highcharts-vue.min.js"></script>



<div id="app">
  <div class="title-row">
    <p>Haciendo pruebas de graficos</p>
  </div>

  <highcharts :options="chartOptions"></highcharts>

</div>

<hr>
<p>Probando llegada de datos</p>
{{barra.total}}

<hr>
{{barra.fecha}}
<hr>


<style media="screen">
.title-row {
display: flex;
flex-direction: column;
align-items: center;
background: #eee;
padding: 20px;
}
</style>

<!-- ESTA CARGADA LA VERSION DE VUE PARA PRODUCCION
-->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

<script type="text/javascript">


Vue.use(HighchartsVue.default)

var app = new Vue({
  el: '#app',
  data: {
   barra: null,
   mensaje: null
 },
//filters: {
//  currencydecimal (value) {
//    return value.toFixed(2)
//    }
//  },

created() {
  this.pedidoJson();
},
  data() {
    return {
      chartOptions: {
        chart: {
          type: 'spline'
        },
        title: {
          text: 'Vue y Highcharts'
        },
        series: [{
          data: [10, 0, 8, 2, 6, 4, 5, 5]
        }]
      },
    }
  },
  methods: {
    pedidoJson: function(){
      axios.get('https://desa-tablero-asistire.educar.gob.ar/servicios/asist_institucion.php')
        .then(response => (
          //alert("hizo el pedido json")
          //alert(response.data.fecha)
         this.barra = response.data
        ))
        .catch(error => {
          alert("Error del pedido por axios a la api")
          this.errored = true
        })
    }
  }
})



</script>

Hola! tengo un pequeño problema, me funciona todo super bn, pero! en Chrome no me funciona el reproductor si le doy audio(controls) no veo nada pero en el inspector de elementos me aparece, ahora si le digo audio(controls autoplay) me suena la musica al momento de seleccionar la cancion, pero en firefox me funciona sin problemas le paso a alguien?

Saludos!

mi ejemplo
App.vue html

<pm-notification v-show="showNotification" :isSucceed="isSucceed">
      <p slot="body" v-if="isSucceed">
        {{ searchMessages }}
      </p>
       <p slot="body" v-else>
        No se encontraron resultados
      </p>

App.vue js

 data () {
    return {
      showNotification: false,
      isSucceed: false, 
     }
  },
search () {
      if (!this.searchQuery ) { return }
      this.isLoading = true
      trackService.search(this.searchQuery)
        .then( res => {
            if (res.tracks.total === 0) {
           	 this.isSucceed = false
          }else {
            this.isSucceed = true 
    
          this.isLoading = false
          this.tracks = res.tracks.items
	  this.showNotification = true

        })
    },

Notification.vue html

.notification(:class="{'is-success': isSucceed, 'is-danger': !isSucceed }")
          slot(name="body") mensaje

Notification.vue js

props:{
      isSucceed: {
        Type: Boolean,
        
      }

Mi showNotification:

    showNotification(newVal, prevVal){
      if(this.showNotification){
        setTimeout(() => {
          this.showNotification = !this.showNotification
          if(this.isFound) this.isFound = !this.isFound
        }, 3000)
      }
    }

Mi fetchTrackByName:

fetchTrackByName(this.searchQuery)
      .then(res => {
        if(res.tracks.total > 0){
          this.isFound = true;
        }
        this.showNotification = res.tracks.total === 0 || this.isFound;
        this.tracks = res.tracks.items
      })

Mi contenedor de la notificacion:

.notification(v-bind:class="{ isFound: isFound, isNotFound: !isFound }")

Mis estilos para el bind de la clase:

    .isFound{
  background-color: #23d160;
  color: whitesmoke;
    }

.isNotFound{
  background-color: #DA4644;
  color: whitesmoke;
}

Mi rendereo condicional para el mensaje del slot:

p(slot='body') {{ isFound ? searchMessage : 'No se encontraron resultados'}}

Así vamos de momento:

creo que la desventaja de usar alias para el @ es que se pierde el autocomplete de vscode…

solucion al desafio, en el query agregas el tipo de notificacion:

      trackService.search(this.searchQuery).then(res => {
        this.showNotification = true;
        this.notificationType = res.tracks.total === 0 ? "danger" : "success";
        this.tracks = res.tracks.items;
        this.isLoading = false;
      });

lo usas en el html

    pm-notification(v-show="showNotification", :notificationType="notificationType")
      p( v-show="notificationType === 'danger'",slot="body") No se encontraron resultados
      p( v-show="notificationType === 'success'",slot="body") Se encontraron {{this.tracks.length}} resultados

y en el componente hijo agregas la clase dinamicamente

          .notification(:class="{'is-danger' : notificationType === 'danger','is-success' : notificationType === 'success'}")
            slot(name="body")

no olviden registrar el notificationType en data y recibirlo como prop

Thoughts? 🤣

  <h2>Canciones que estan Vue-nisimas</h2>

Un event-bus va a actuar como un emisor de eventos que podremos usar en todos los componentes y así obtener la información que necesitamos

Comparto mi solucion:

Cambie la propiedad “searchMessage” para que devolviera un objeto el cual leerá el componente de las notificaciones y modifique el watch para que cuando sea el objeto con la propiedad “class: alert” este se borre en 2 segundos (me gusto mas tener 2 segundos)

Componente:

pm-notification(v-show="showNotification" :class="searchMessage.class")
  p(slot="texto") {{ searchMessage.text }}

Variable y watcher:

computed: {
  searchMessage () {
    if (this.tracks.length) {
      return {class:'success', text: `Econtrados: ${this.tracks.length}`}
    } else {
      return {class:'alert', text:'No se encontraron resultados'}
    }
  },
},

watch: {
  showNotification () {
    if (this.showNotification && this.searchMessage.class === 'alert') {
      setTimeout(() => this.showNotification = false,2000)
    }
  }
}

App.vue

<template lang="pug">
  #app
    pm-header

    pm-notification(v-show="showNotification", :notificationColor="notificationClass")
      p(slot="body") {{ notificationMessage }}

    pm-loader(v-show="isLoading")

    section.section(v-show="!isLoading")
      nav.nav
        .container
          input.input.is-large(
            type="text",
            placeholder="Buscar canciones"
            v-model="searchQuery"
          )
          a.button.is-info.is-large(@click="search") Buscar
          a.button.is-danger.is-large &times;
      //.container
        p
          small {{ searchMessage }}
      .container.results
        .columns.is-multiline
          .column.is-one-quarter(v-for="t in tracks")
            pm-track(:class="{ 'is-active': t.id === selectedTrack }", :track="t", @select="setSelectedTrack")

    pm-footer
</template>

<script>
import trackService from '@/services/track'

import PmFooter from '@/components/layout/Footer.vue'
import PmHeader from '@/components/layout/Header.vue'

import PmTrack from '@/components/Track.vue'

import PmLoader from '@/components/shared/Loader.vue'
import PmNotification from '@/components/shared/Notification.vue'

export default {
  name: 'app',

  components: { PmFooter, PmHeader, PmTrack, PmLoader, PmNotification },

  data () {
    return {
      searchQuery: '',
      tracks: [],

      isLoading: false,

      showNotification: false,
      notificationMessage: '',
      notificationClass: '',

      selectedTrack: ''
    }
  },

  computed: {
    searchMessage () {
      return `Se encontraron: ${this.tracks.length} canciones`
    }
  },

  watch: {
    showNotification () {
      if (this.showNotification) {
        if (this.tracks.length === 0) {
          this.notificationMessage = 'No se encontraron resultados'
          this.notificationClass = 'is-danger'
          setTimeout(() => {
            this.showNotification = false
          }, 2500)
        } else {
          this.notificationMessage = this.searchMessage
          this.notificationClass = 'is-success'
        }
      }
    }  
  },

  methods: {
    search () {
      if (!this.searchQuery) { return }

      this.isLoading = true

      trackService.search(this.searchQuery)
        .then(res => {
          this.showNotification = true
          this.tracks = res.tracks.items
          this.isLoading = false
        })
    },

    setSelectedTrack (id) {
      this.selectedTrack = id
    }
  }
}
</script>

<style lang="scss">
  @import './scss/main.scss';
  .results {
    margin-top: 30px;
  }

  a.button {
    margin-right: 10px;
    margin-top: 10px;
  }

  .is-active {
    border: 3px #23D160 solid;
  }
</style>

Notification.vue

<template lang="pug">
  .container
    .columns
      .columns.is-5.is-offset-4
        div(:class="classType")
          slot(name="body") Algo anduvo mal
</template>

<script>
export default {
  props: {
    notificationColor: ''
  },
  computed: {
    classType () {
      return `notification ${this.notificationColor}`
    }
  }
}
</script>

<style lang="scss" scoped>
  .columns {
    margin: auto;
    margin-top: 5px;
  }
</style>

Sumo al texto de recien que la idea es usar esto para pasarle otro json y cambiarle que sea de torta en vez de linea y funciona, y asi

Si el componente player(reproductor no aparece) den le un ancho a la etiqueta audio

Hola, una inquietud tengo este error:
[Vue warn]: Error in render: "TypeError: _vm.track.album is undefined" found in ---> <PmPlayer> at src/components/Player.vue <PmHeader> at src/components/layout/Header.vue <App> at src/App.vue <Root> 1:592:7 TypeError: "_vm.track.album is undefined"

Pero su funcionalidad esta bien, alguien sabe a que se debe?

<template>
  <div class="container">
    <div class="columns">
      <div class="column is-5 is-offset-4">
        <div class="notification" :class=" totalResults == 0 ? 'is-danger' : 'is-success' ">
          <slot name="body"></slot>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    totalResults: {
        type: Number,
        required: true
    }
  }
};
</script>
<style lang="scss" scoped>
.notification {
  margin: 10px;
}
</style>```

Buenas tardes, hay algo que no entiendo y estoy ansioso por resolverlo. Digamos que refrescas la página o presionas la tecla F5, ¿Porque no aparece al menos una imagen sin src, el elemento audio sin src? Está claro que el texto título de la canción no aparece porque es la primera carga y no tiene nada que mostrar, pero ¿y lo demás ?

Obviamente si busco y selecciono un track, si aparecera todo, pero mi consulta es que cuando actualizo o cargo el proyecto por primera vez no aparece el html del pm-player. En ningún momento hemos puesto un v-if o v-show al compomente pm-player. Incluso si agrego un texto fijo en el componente pm-player:

p
   strong Reproductor (30 segundos)
p
   strong {{ track.name }}
p
   small [{{ track.duration_ms }}]
p
   audio(controls, :src="track.preview_url")

Este no aparece en la primera carga del proyecto o cuando actualizo la página. Si inspecciono la página aparece así

<div class=“container has-text-centered”>
      <h1 class=“title”>Platzi music</h1>
      <h2 class=“subtitle”>Canciones buenísimas</h2>
      <!---->
</div>

La parte donde debería aparecer el html de pm-player aparece comentada.

Sin embargo… si reemplazo todo el contenido de pm-player y lo dejo así:

<template lang=“pug”>
.content
   p
      strong Texto fijo
</template>

Se comporta como debería, aparece el texto ‘Texto fijo’ al refrescar la página y se muestra el html del componente pm-player al inspeccionar la página

Por favor, necesito que me ayuden a resolver esta duda. ¿A qué se debe este comportamiento?

Solo se crearía un solo archivo de plugin para cualquier funcionalidad o sería recomendable crear uno por cada funcionalidad que existiera dentro del proyecto (?). Es decir, si en el ejemplo de Platzi-music hubiese otra situación en la que se necesitara usar un plugin, se podría usar el mismo event-bus.js o se crearía otro (?)

Un event bus seria el equivalente a una base de datos en memoria cierto? Osea, hace algo muy similar, pero si tenemos una base de datos por lo menos podermos persisitir los cambios

<code>
<div :class="call ? 'notification is-success' : 'notification is-danger'">
	<slot name="body">algo andubo mal</slot>
</div>


export default{
	name: 'PmNotification',
	props: {call:{type: Boolean, required: true}}
}
``

Solución:

Notification.vue

<template lang="pug">
  .container
    .columns
      .column.is-4.is-offset-5
        .notification(:class="type")
          slot(name="body") Algo salio mal!
</template>

<script>
export default {
  name: 'notificacion',
  props: {
    type: {
      type: String,
      default: 'is-danger'
    }
  }
}
</script>

<style lang="scss" scoped>
  .container {
    margin: 10px 0;
  }
</style>

App.vue

<template lang="pug">
  #app
    pm-header
    pm-notificacion(
      :type="typeNotification",
      v-show="showNotification",
    )
      p(slot="body") {{ mesaggeNotification }}
    pm-loading(v-show="isLoading")
    section.section(v-show="!isLoading")
      nav.navbar
        .field.has-addons
          .control.is-expanded
            input.input(
              type="text"
              placeholder="Buscar canciones"
              v-model="searchQuery",
              v-on:keyup.enter="buscar"
            )
          .control
            button.button.is-info(@click="buscar") Buscar
          .control
            button.button.is-danger &times;
      p
        small Encontrados: {{ cantidad }}
      .container.results
        .columns.is-multiline
          .column.is-one-quarter(v-for="t in tracks")
            pm-track(
              :class="{ 'is-active': t.id === trackIdSeleted }"
              :track="t",
              @select="setSelectedTrack"
            )
    pm-footer
</template>

<script>
import trackService from '@/services/traks'

import PmHeader from '@/components/layout/Header.vue'
import PmFooter from '@/components/layout/Footer.vue'

import PmTrack from '@/components/Track.vue'

import PmLoading from '@/components/shared/Loading.vue'
import PmNotificacion from '@/components/shared/Notification.vue'

export default {
  name: 'app',
  components: {
    PmHeader,
    PmFooter,
    PmTrack,
    PmLoading,
    PmNotificacion
  },
  data () {
    return {
      tracks: [],
      searchQuery: '',
      isLoading: false,
      trackIdSeleted: '',
      showNotification: false,
      typeNotification: '',
      mesaggeNotification: ''
    }
  },
  methods: {
    buscar () {
      if (!this.searchQuery) { return }
      this.isLoading = true
      trackService.search(this.searchQuery)
        .then(res => {
          this.tracks = res.tracks.items
          this.isLoading = false

          const total = res.tracks.total
          let message, type
          if (total) {
            message = `Cantidad de Resultados: ${total}`
            type = 'is-info'
          } else {
            message = 'No se encontraron resultados'
            type = 'is-danger'
          }
          this.updateNotification(message, type)
        })
        .catch(() => {
          const type = 'is-danger'
          const message = `Ocurrio un error`
          this.updateNotification(message, type)
          this.isLoading = false
        })
    },
    setSelectedTrack (trackid) {
      this.trackIdSeleted = trackid
    },
    updateNotification (message, type) {
      this.mesaggeNotification = message
      this.typeNotification = type
      this.showNotification = true
    }
  },

  watch: {
    showNotification () {
      if (this.showNotification) {
        setTimeout(() => {
          this.showNotification = false
        }, 3000)
      }
    }
  },
  computed: {
    cantidad () {
      return this.tracks.length
    }
  }
}
</script>

<style lang="scss">
@import 'scss/main.scss';

#app {
  .results {
    margin-top: 30px;
  }

  .is-active {
    border: solid $primary;
  }
}
</style>

Dejo mi solución, sencilla pero espero les ayude

APP.VUE

 pm-notification(v-show="showNotification", :typeNotification="totalTracks > 0 ? 'is-success' : 'is-danger'")
      p(slot="body")
        span(v-if="totalTracks > 0") Se encontraron {{ totalTracks }} resultados
        span(v-else) No se encontraron resultados
data() {
      return {
        searchQuery: '',
        tracks: [],
        isLoading: false,
        showNotification: false,
        totalTracks: 0,
        selectedTrack: ''
      }
    },
    watch: {
      showNotification () {
        if (this.showNotification) {
          setTimeout(() => {
            this.showNotification = false
          }, 3000)
        }
      }
    },
search() {
        if (this.searchQuery.trim().length > 0) {
          this.isLoading = true
          trackService.search(this.searchQuery).then(res => {
            this.showNotification = true
            this.totalTracks = res.tracks.total
            this.tracks = res.tracks.items
            this.isLoading = false
          })
        }
      }

Notification.vue

<template lang="pug">
  .container
    .columns
      .column.is-5.is-offset-4
        .notification(:class="typeNotification")
          slot(name="body") Algo anduvo mal
</template>

<script>
  export default {
    props: {
      typeNotification: {
        type: String,
        required: true
      }
    }
  }
</script>
<template  lang="pug">
    .container
        .columns
            .column.is-5.is-offset-4
                .notification(v-bind:class="type_message")
                    slot(name="body") Has Error.


</template>

<script>
    export default {
        props:{
            type_message:{
                type:String,
                require: false
            }

        }
    }
</script>
<style lang="scss" scoped>
    .notification{
        margin: 10px;
    }
</style>```




<template lang=“pug”>
#app
pm-header

pm-notification(
    v-show="showNotification",
    v-bind:type_message="notificationData.class_css")

    p(slot="body") {{notificationData.message}}

pm-loader(v-show="isLoading")
section.section(v-show="!isLoading")
  nav.nav
    .container
      input.input.is-large(
        v-model="searchQuery",
        type="text",
        placeholder="Search songs")

      a.button.is-info.is-large(v-on:click="search") search
      a.button.is-danger.is-large &times;

  .container
      p
        small {{searchMessage}}

  .container.result
    .columns.is-multiline
      .column.is-one-quarter(v-for="t in tracks")
        pm-track(
        v-bind:class="{'is-active': t.id == selectedTrack}",
        v-bind:track="t",
        v-on:select="setSelectedTrack")



pm-footer

</template>

<script>
import trackService from ‘@/services/track’;

import PmFooter from ‘@/components/layout/Footer.vue’;
import PmHeader from ‘@/components/layout/Header.vue’;

import PmTrack from ‘@/components/Track.vue’;

import PmLoader from ‘@/components/shared/Loader.vue’;
import PmNotification from “@/components/shared/Notification.vue”

export default {
name: ‘app’,
data () {
return {
searchQuery:’’,
tracks:[],
isLoading:false,
selectedTrack:’’,
showNotification:false,
notificationData:{
class_css : ‘’,
message : ‘’
}

}

},

components:{
PmFooter,
PmHeader,
PmTrack,
PmLoader,
PmNotification
},

methods:{
search(){
if(this.searchQuery.length < 2){
return false;
}

    this.isLoading = true;
    trackService.search(this.searchQuery)
        .then((res)=>{

            if(res.tracks.total !== 0){
                this.notificationData.class_css = 'is-success';
                this.notificationData.message = `found ${res.tracks.total} total`;
            }else{
                this.notificationData.class_css = 'is-danger';
                this.notificationData.message = `Not Found Result.`;
            }

            this.showNotification = true;
            this.tracks = res.tracks.items;
            this.isLoading = false;
        })
},
setSelectedTrack(id){
    this.selectedTrack = id;
}

},
computed:{
searchMessage(){
return Found ${this.tracks.length};
}
},

watch:{
showNotification(){
if(this.showNotification){
if(this.notificationData.class_css === ‘is-success’) return;
setTimeout(()=>{
this.showNotification = false;
},3000)
}
}
}

}
</script>

<style lang=“scss”>
@import “./scss/main”;

.result{
margin-top:50px;
}

.is-active{
border: 3px solid #23d160;
}

</style>


Notifications.vue

<template lang="pug">
    .container
        .columns
            .column.is-4.is-offset-4
                .notification(:class="type_message")
                    slot(name="body") Algo anduvo mal


</template>
<script>
    export default {
        props:{
            type_message: {
                type: String,
                require: false,
            }
        }
    }
</script>
<style  lang="scss" scoped>
    .notification {
        margin: 10px;
    }
</style>

App.vue

<template lang="pug">
    .container
        pm-header
        pm-notifications(v-show='showNotification' :type_message='notificationType')
            p(slot='body') {{notificationMessage}}

        pm-loader(v-show="isLoading")
        section(v-show="!isLoading").section
            nav.nav
                .container
                    input.input.is-large(
                    v-model='searchQuery',
                    type="text",
                    placeholder="Buscar cancion"
                    )
                    a(@click="search").button.is-info.is-large Buscar
                    a.button.is-danger.is-large &times;
            .container.results
                p
                    small {{searchMessage}}
                .columns.is-multiline
                    .column.is-one-quarter(v-for ="t in tracks")
                        pm-track(
                            :class="{'is-active' : t.id === selectedTrack }",
                            :track="t",
                            @select="setSelectedTrack",

                        )


        pm-footer

</template>

<script>
    /* eslint-disable no-console */
    import PmLoader from '@/components/shared/Loader.vue'
    import trackService from '@/services/track'
    import PmHeader from '@/components/layout/Header.vue'
    import PmFooter from '@/components/layout/Footer.vue'
    import PmTrack from '@/components/Track.vue'
    import PmNotifications from '@/components/shared/Notifications.vue'
    export default {
        name: 'platzi-music',
        data(){
            return {
                searchQuery : '',
                tracks: [],
                isLoading: false,

                selectedTrack: '',
                showNotification: false,
                notificationMessage: ''

            }
        },

        components:{
            PmFooter,
            PmHeader,
            PmTrack,
            PmLoader,
            PmNotifications
        },
        computed: {
            searchMessage(){
                return 'Encontrados: '+ this.tracks.length;
            }
        },
        methods:{
            search (){
                if(!this.searchQuery ) {return}
                this.isLoading = true;
                trackService.search(this.searchQuery)
                    .then(res => {
                        console.log(res);
                        this.showNotification = true;
                        if(res.tracks.total === 0){
                            this.notificationType = 'is-danger';
                            this.notificationMessage = 'No se han encontrado resultados';
                        }else {
                            this.notificationType = 'is-success';
                            this.notificationMessage = 'Sea han encontrado ' + res.tracks.total + ' Resultados' ;
                        }
                        this.tracks = res.tracks.items;
                        this.isLoading = false;
                    })
            },
            setSelectedTrack(id){
                this.selectedTrack = id ;
            }

        },
        watch:{
            showNotification () {
                if(this.showNotification) {
                    setTimeout(() => {
                        this.showNotification = false
                    },3000)
                }
            }
        },
        mounted(){

        }
    }
</script >

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">

    .results {
        margin-top: 50px;
        columns{
            flex-wrap: wrap;
        }

        .column{
            min-width: 150px;
        }
    }
    .is-active{
        border: 3px #23d160 solid;
    }
</style>

aqui el reto quizas exita una mejor manera. Yo solo utilice el html

NOTIFICACIONES

<template lang="pug">
  .container
    .columns
      .column.is-5.is-offset-4
        .notification.is-danger(v-show="notification")
          slot(name="body") Notificacion default
        .notification.is-success(v-show="!notification")
          slot(name="body") Notificacion default
</template>

<script>
  export default {
    props:{
      notification:{
        type:Boolean,
      }
    }
  }
</script>

<style lang="scss" scoped>
  .notification{
    margin: 10px
  }
</style>



App.vue

<template lang="pug">
  #app
    PmHeader
    pm-notification(:notification="showNotification")
      p(slot="body" v-if="showNotification") no se encontraron resultados
      p(slot="body" v-else="showNotification") resultados {{serachMessage}}
  
    pm-loading(v-show="isLoading")

    section.section(v-show="!isLoading")
      nav.nav
        .container
          input.input(type="text" placeholder="Search Sons" v-model="searchQuery")
          a.is-info.button.is-large(@click="search") Search
          a.is-danger.button.is-large &times; 
      .container
      
      .container.results
        .columns.is-multiline
          .column.is-one-quarter(v-for="track in tracks")
              pm-track(
                :class="{'is-active': track.id ===selectedTrack }"
                :track="track",
                @select="setSelected"
              )
              
    PmFooter            

</template>

No creoq ue nadie responda a estas alturas, pero como funciona la parte de event bus, es decir haces $bus.emit, o lo que es lo mismo new Vue.emit, pero luego haces $bus.on o lo que es lo mismo new Vue.on, son todo el rato instancias nuevas, como es posibles que instancias nuevas y diferentes tengan guardados eventos?

Gracias.

Notification.vue

<template lang="pug">
  .container
    .columns
      .column.is-5.is-offset-4      v-bind:class="{'is-active': t.id === selectedTrack}"
        .notification.is-success(v-show="notificationType==='Success'")
          slot(name="body") Something went wrong
        .notification.is-danger(v-show="notificationType==='Failed'")
          slot(name="body") Something went wrong
</template>

<script>
export default {
  props: {
    notificationType: { // "Success", "Failed"
      type: String,
      required() {
        return true;
      },
    },
  },
};
</script>

<style lang="scss" scoped>
  .notification {
    margin: 10px;
  }
</style>

App.vue

<template lang="pug">
  #app
    pm-header

    pm-notification(v-show="showNotification" v-bind:notification-type="notificationType")
      p(slot="body") {{notificationMessage}}

    pm-loader(v-show="isLoading")
    section.section(v-show="!isLoading")
      nav.nav
        .container
          input.input.is-large(
            type="text",
            placeholder="Buscar canciones",
            v-model="searchQuery"
          )
          a.button.is-info.is-large(@click="search") Buscar
          a.button.is-danger.is-large &times;
      .container
        p
          small {{ searchMessage }}

      .container.results
        .columns.is-multiline
          .column.is-one-quarter(v-for="t in tracks")
            pm-track(
              v-bind:class="{'is-active': t.id === selectedTrack}",
              v-bind:track="t",
              v-on:select="setSelectedTrack")
    pm-footer
</template>

<script>
import trackService from './services/track';

import PmFooter from './components/layout/Footer.vue';
import PmHeader from './components/layout/Header.vue';

import PmTrack from './components/Track.vue';

import PmLoader from './components/shared/Loader.vue';
import PmNotification from './components/shared/Notification.vue';

export default {
  name: 'app',

  components: {
    PmFooter,
    PmHeader,
    PmTrack,
    PmLoader,
    PmNotification,
  },

  data() {
    return {
      searchQuery: '',
      tracks: [],
      isLoading: false,
      selectedTrack: '',
      showNotificationSuccess: false,
      showNotificationFailed: false,
    };
  },

  watch: {
    showNotification() {
      if (this.showNotification) {
        setTimeout(() => {
          this.hideNotifications();
        }, 3000);
      }
    },
  },

  computed: {
    searchMessage() {
      return `Found: ${this.tracks.length}`;
    },
    showNotification() {
      return this.showNotificationSuccess || this.showNotificationFailed;
    },
    notificationMessage() {
      let message = 'N/A';
      if (this.showNotificationSuccess) {
        message = `${this.tracks.length} tracks found.`;
      } else if (this.showNotificationFailed) {
        message = 'No tracks found.';
      }
      return message;
    },
    notificationType() {
      if (this.showNotificationSuccess) {
        return 'Success';
      }
      if (this.showNotificationFailed) {
        return 'Failed';
      }

      return 'N/A';
    },
  },

  methods: {
    search() {
      if (!this.searchQuery) { return; }
      this.isLoading = true;
      trackService.search(this.searchQuery)
        .then((res) => {
          if (res.tracks.total > 0) {
            this.showNotificationSuccess = true;
            this.showNotificationFailed = false;
          } else {
            this.showNotificationSuccess = false;
            this.showNotificationFailed = true;
          }

          this.tracks = res.tracks.items;
          this.isLoading = false;
        });
    },
    setSelectedTrack(id) {
      this.selectedTrack = id;
    },
    hideNotifications() {
      this.showNotificationSuccess = false;
      this.showNotificationFailed = false;
    },
  },
};
</script>

<style lang="scss">
  @import './scss/main.scss';

  .results {
    margin-top: 50px;
  }

  .is-active {
    border: 3px #48c774 solid;
  }
</style>

Espero haber entendido el ejercicio xD
**App.vue
**

<template lang="pug">
  #app
    pm-header
    pm-notification(v-show="showNotification", :notification="notify")
      //- p(slot="body") No se encontraron resultados!
    pm-loader(v-show="isLoading")
    section.section(v-show="!isLoading")
      nav.nav
        .container
          input.input.is-large(type="text", placeholder="Buscar canciones", v-model="searchQuery")
          a.button.is-info.is-large(@click="search") Buscar
          a.button.is-dange.is-large &times;
      .container
        p
          small {{ searchMessage }}
      .container.results
        .columns.is-multiline
          .column.is-one-quarter(v-for="t in tracks")
            pm-track(:track="t", v-on:select="setSelectedTrack", v-bind:class="{ 'is-active': t.id == selectedTrack }")
    pm-footer
</template>

<script>
import trackService from '@/services/track'

import PmFooter from '@/components/layout/Footer.vue'
import PmHeader from '@/components/layout/Header.vue'

import PmTrack from '@/components/Track.vue'

import PmLoader from '@/components/shared/Loader.vue'
import PmNotification from '@/components/shared/Notification.vue'

export default {
  name: 'app',
  components: { PmFooter, PmHeader, PmTrack, PmLoader, PmNotification },
  data () {
    return {
      searchQuery: '',
      tracks: [],
      isLoading: false,
      selectedTrack: '',
      showNotification: false,
      notify: {}
    }
  },
  computed: {
    searchMessage () {
      return `Encontrados: ${this.tracks.length}`
    }
  },
  watch: {
    showNotification () {
      if (this.showNotification) {
        setTimeout(() => {
          this.showNotification = false
        }, 3000)
      }
    }
  },
  methods: {
    search () {
      if (!this.searchQuery) { return }
      this.isLoading = true
      trackService.search(this.searchQuery)
        .then(
          res => {
            if (res.tracks.total === 0) {
              this.notify = {
                message: 'Algo anduvo mal',
                type: 'is-danger'
              }
            } else {
              this.notify = {
                message: 'Se encontraron resultados',
                type: 'is-success'
              }
            }
            console.log(this.notify)
            this.showNotification = true
            this.tracks = res.tracks.items
            this.isLoading = false
          })
    },
    setSelectedTrack (id) {
      this.selectedTrack = id
    }
  }
}
</script>

<style lang="scss">
  @import './scss/main.scss';
  .results {
    margin-top: 50px;
  }
  .is-active {
    border: 3px #23d160 solid;
  }
</style>

**Notification.vue
**

<template lang="pug">
    .container
      .columns
        .column.is-5.is-offset-4
          .notification(:class="notification.type")
            slot(name="body") {{ notification.message }}
</template>
<script>
export default {
  props: {
    notification: { type: Object, required: true }
  }
}
</script>
<style lang="scss">
  .notification {
    margin: 10px;
  }
</style>

A donde le doy me gusta, por que tambien es muy buena!

Crei que este video ya lo habiamos visto, ya tenia todo creado pero no me acuerdo a donde estaba el video

No puedo agregar mi codigo me sale un mensaje de forbbiden

App.vue

<template lang="pug">
  #app
    px-header
    px-notification(v-show="showNotification", :notificationStatus='notificationStatus')
      p(slot="body") No se encontraron resultados
    px-notification(v-show="!showNotification", :notificationStatus='notificationStatus')
      p(slot="body") Se encontraron {{ searchMessage }} resultados
    px-loader(v-show="isLoading")
    section.section(v-show="!isLoading")
      nav.nav.has-shadow
        .container.flexbox
          input.input.is-large(
            type="text"
            placeholder= "Buscar cancion"
            v-model="searchQuery" )
          a.button.is-info.is-large(@click='search') Buscar
          a.button.is-danger.is-large &times;
      .container
      .container.results
        .columns.is-multiline
          .column.is-one-quarter(v-for="t in tracks")
            px-track(
              :class="{'is-active':t.id===selectedTrack}"
              :track="t",
              @select="setSelectedTrack") //<- componente hijo - importante poner v-bind
            //- | {{ t.name }} - {{ t.artists[0].name }}
    px-footer
</template>

<script>
import pxHeader from '@/components/layout/pxHeader'
import pxFooter from '@/components/layout/pxFooter'
import trackService from '@/services/track'
import pxTrack from '@/components/layout/Track'
import pxLoader from '@/components/shared/loader'
import pxNotification from '@/components/shared/notification'
export default {
  name: 'App',
  components: { pxHeader, pxFooter, pxTrack, pxLoader, pxNotification },
  data() {
    return {
      tracks: [],
      searchQuery: '',
      isLoading: false,
      selectedTrack: '',
      showNotification: false
    }
  },
  methods: {
    search() {
      if (!this.searchQuery) {
        //Evitar errores en consola valores truly or falsy
        return
      }
      this.isLoading = true
      trackService.search(this.searchQuery).then(res => {
        this.showNotification = res.tracks.total === 0
        this.tracks = res.tracks.items
        this.isLoading = false
      })
    },
    setSelectedTrack(id) {
      this.selectedTrack = id
    }
  },
  computed: {
    searchMessage() {
      return `${this.tracks.length}`
    },
    notificationStatus() {
      if (this.showNotification) {
        return true
      } else {
        return false
      }
    }
  },
  watch: {
    showNotification() {
      if (this.showNotification) {
        setTimeout(() => {
          this.showNotification = false
        }, 3000)
      }
    }
  }
}
</script>

<style lang="scss">
@import './scss/main.scss';
.results {
  margin-top: 50px;
}
.flexbox {
  display: flex;
}
.is-active {
  border: 3px solid #23d160;
}
</style>

notification.vue

<template lang="pug">
  .container
    .columns
      .column.is-5.is-offset-4
        .notification.is-danger(v-show='notificationStatus')
          slot(name="body")
        .notification.is-success(v-show='!notificationStatus')
          slot(name="body")
</template>

<script>
export default {
  props: {
    notificationStatus: {
      required: true
    }
  }
}
</script>

<style lang="scss" scoped>
.notification {
  margin: 10px;
}
</style>

Aquí mi solución:
App.vue

<template lang="pug">
  #app
    pm-header
    pm-notification(v-show="showNotification", :foundNotification="foundNotification")
      p(slot="body", v-show="!foundNotification") No se encontraron resultados
      p(slot="body", v-show="foundNotification") Se encontraron {{totalSearch}} concidencias
    pm-loader(v-show="isLoading")
    section.section(v-show="!isLoading")
      nav.nav.has-shadow
        .container
          input.input.is-large(
            type="text",
            placeholder="Buscar canciones",
            v-model="searchQuery"
          )
          a.button.is-info.is-large(@click="search") Buscar
          a.button.is-danger.is-large &times;
      .container
        p
          small {{searchMessage}}
      .container.results
        .columns.is-multiline
          .column.is-one-quarter(v-for="t in tracks")
            pm-track(
              :track="t",
              @select="setSelectedTrack",
              :class="{'is-active' : t.id === selectedTrack}")
    pm-footer
</template>

<script>
import trackService from '@/services/track'
import PmHeader from '@/components/layout/Header.vue'
import PmFooter from '@/components/layout/Footer.vue'
import PmTrack from '@/components/Track.vue'
import PmLoader from '@/components/share/Loader.vue'
import PmNotification from '@/components/share/Notification.vue'

export default {
  name: 'app',
  data () {
    return {
      searchQuery: '',
      tracks: [],
      isLoading: false,
      selectedTrack: '',
      showNotification: '',
      totalSearch: 0
    }
  },

  components: {
    PmFooter,
    PmHeader,
    PmTrack,
    PmLoader,
    PmNotification
  },

  methods: {
    search () {
      if (!this.searchQuery) { return }

      this.isLoading = true

      trackService.search(this.searchQuery)
        .then(res => {
          this.showNotification = true
          this.tracks = res.tracks.items
          this.isLoading = false
          this.totalSearch = res.tracks.total
        })
    },
    setSelectedTrack (id) {
      this.selectedTrack = id
    }
  },

  computed: {
    searchMessage () {
      return `Encontrados: ${this.tracks.length}`
    },
    foundNotification () {
      return this.totalSearch > 0
    }
  },

  watch: {
    showNotification () {
      if (this.showNotification) {
        setTimeout(() => {
          this.showNotification = false
        }, 5000)
      }
    }
  }

}
</script>

<style lang="scss">
  @import './scss/main.scss';

  .results {
    margin-top: 50px;
  }

  .is-active{
    border: 3px solid #3298dc;
  }
</style>

Notification.vue

<template lang="pug">
    .container
        .columns
            .column.is-5.is-offset-4
                .notification(:class="{ 'is-success' : foundNotification, 'is-danger' : !foundNotification}")
                    slot(name="body") Algo anduvo mal
</template>

<script>
export default {
  props: {
    foundNotification: { type: Boolean, required: true }
  }
}
</script>

<style lang="scss" scoped>
    .notification{
        margin: 10px;
    }
</style>

Esta es mi solución en app.vue

<template lang="pug">
  #app
    pm-header

    pm-notifications(v-show="showNotifications")
      p(slot="body") No se encontro ningún resultado
    pm-numerotemas(v-show="!showNotifications")
      p(slot="nTemas") Total temas encontrados : {{ temasTotales }}

    pm-loader(v-show="isLoading")
    section.section(v-show="!isLoading")
      nav.navbar
        .container
          .control.is-expanded
            input.input.is-large(
            type="text",
            placeholder="Buscar canciones",
            v-model = "searchQuery"
            )
          a.button.is-info.is-large(@click="search") Buscar
          a.button.is-danger.is-large &times;
          p
            small {{ searchMessage }}
      .container.results
        .columns.is-multiline
          .column.is-one-quarter(v-for="t in tracks")
            pm-track(
            :class="{ 'is-active': t.id === selectedTrack }",
            :track="t",
            @select="setSelectedTrack"
            )
    pm-footer
</template>

  <script>

  import trackService from '@/services/track'
  import Tarjetas from '@/components/Tarjetas.vue'
  import Encabezado from '@/components/Encabezado.vue'
  import Menu from '@/components/Menu.vue'
  import DomTarea from '@/components/DomTarea.vue'
  import NavMusica from '@/components/NavMusica.vue'
  import PmFooter from '@/components/layout/Footer.vue'
  import PmHeader from '@/components/layout/Header.vue'
  import PmTrack from '@/components/Track.vue'
  import PmLoader from '@/components/shared/Loader.vue'
  import PmNotifications from '@/components/shared/Notifications.vue'
  import PmNumerotemas from '@/components/shared/Numerotemas.vue'


  export default {
    name: 'App',
    components: {
      Tarjetas,
      Encabezado,
      Menu,
      DomTarea,
      NavMusica,
      PmFooter,
      PmHeader,
      PmTrack,
      PmLoader,
      PmNotifications,
      PmNumerotemas
    },

    data () {
    return {
      searchQuery: '',
      tracks: [],
      isLoading: false,
      selectedTrack: '',
      temasTotales: 0,
      showNotifications: false
    }
    },
    computed: {
      searchMessage () {
        return `Encontrados: ${this.tracks.length}`
      }

    },
    watch: {
      showNotifications() {
        if (this.showNotifications) {
          setTimeout(() => {
            this.showNotifications = false
          }, 3000)
        }
      }
    },
    methods: {
      search() {
      if (!this.searchQuery){ return }
      this.isLoading = true
      trackService.search(this.searchQuery)
      .then(res => {
      console.log(res)
      this.temasTotales = res.tracks.total
      this.showNotifications = res.tracks.total === 0
      this.tracks = res.tracks.items
      this.isLoading = false
      })
      },
      setSelectedTrack(id) {
        this.selectedTrack = id
      }
    }

  }

  </script>

<style lang="scss">
  @import './scss/main.scss';

  .results {
    margin-top: 50px;
  }

  .is-active {
    border: 3px #23d160 solid;
  }
</style>

Recien ando leyendo un libro sobre vue, tocan el tema del even bus, pero al final concluye que no es recomendable su uso y si se usa es con precaución.

https://vuejs.org/v2/style-guide/#Non-flux-state-management-use-with-caution

Vuex permite solucionar esto de una manera mas eficiente usando los modules de vuex, esto igualmente se ve en el curso, recomiendo que vean el codigo y lo analizen pero yo no lo haria paso a paso porque justo para esto se desarrollo Vuex el cual es creado por el equipo de desarrollo de Vue

No se si es la mejor forma, pero pues, ahi les va. XD

App.vue

<template lang="pug">
  #app
    pm-header

    pm-notification(v-show="showNotification" :totalTracks="totalTracks")
      p(slot="body" ) {{ tracks.length === 0 ? `No hay resultados &#x1F50D;` : `Se encontraron ${totalTracks} resultados &#x2714;`}}

    pm-loader(v-show="isLoading")
    section.section(v-show="!isLoading")
      nav.nav
        .container
          input.input.is-large(
            type="text",
            placeholder="Buscar canciones",
            v-model="searchQuery"
          )
          a.button.is-info.is-large(v-on:click="search()") Buscar
          a.button.is-danger.is-large &times;
      .container
        p
          small {{ searchMessage }}

      .container.results
        .columns.is-multiline
          .column.is-one-quarter(v-for="t in tracks")
            pm-track(
              :class="{ 'is-active': t.id === selectedTrack }"
              :track="t", 
              v-on:select="setSelectedTrack")

    pm-footer

    //- h1 Ciclo de Vida
</template>

<script>
import trackService from './services/track.js'

import PmFooter from './components/layout/Footer.vue'
import PmHeader from './components/layout/Header.vue'

import PmTrack from './components/Track.vue'

import PmNotification from './components/shared/Notification.vue'

import PmLoader from './components/shared/Loader.vue'

export default {
  name: 'App',
  components: { PmFooter, PmHeader, PmTrack, PmLoader, PmNotification },
  data(){
    return {
      searchQuery: '',
      tracks: [],

      isLoading: false,
      showNotification: false,
      totalTracks: '',

      selectedTrack: ''
    }
  },
  watch: {
    showNotification () {
      if(this.showNotification){
        setTimeout(() => {
          this.showNotification = false
        }, 30000)
      }
    }
  },
  methods: {
    search () {
      if (!this.searchQuery){
        return
      }
      
      this.isLoading = true

      trackService.search(this.searchQuery)
      .then(res =>{
        this.totalTracks = res.tracks.total
        this.showNotification = true
        this.tracks = res.tracks.items
        this.isLoading = false
      })
    },
    setSelectedTrack(id){
      this.selectedTrack = id
    }
  },
  computed:{
    searchMessage () {
      return `Encontrados: ${this.tracks.length}`
    }
  }
}

</script>

<style lang="scss">
@import './scss/main.scss';

.results{
  margin-top: 50px
}

.is-active{
  border: 3px #23d160 solid;
}
</style>

Notification.vue

<template lang="pug">
    .container
      .columns
        .column.is-5.is-offset-4
          .notification(:class="{ 'is-danger' : totalTracks === 0, 'is-success': totalTracks !== 0}")
            slot(name="body") Algo anduvo mal &#x1FA79;
</template>

<script>
export default {
  props:{
    totalTracks: {
      type: Number
    }
  }
}
</script>

<style lang="scss" scoped>
  .notification {
      margin: 10px;
  }
</style>

👌

Notification.vue

<template lang="pug">
    .container
        .columns
            .column.is-5.is-offset-4
                .notification(:class='color')
                    slot(name="body") Algo anduvo mal
</template>

<script >
  export default {
    props: {
      color: { type: String, required: true }
    }
  }
</script >

App.vue

<template>

pm-notification(v-show=“showNotification” :color=‘colorNotification’)
p(slot=“body”) {{ messageNotification }}

</template

en data adicione las siguientes propiedades
colorNotification: ‘is-success’,
messageNotification: ‘’,

y en search me quedo asi
search () {
if (!this.searchQuery) { return } // esto se lee como si no existe searchQuery entonces return
this.isLoading = true
trackService.search(this.searchQuery)
.then(res => {
this.showNotification = true
this.colorNotification = 'is-danger’
this.messageNotification = ‘No se encontraron resutlados’

      if (res.tracks.total > 0) {
        this.messageNotification = `Se encontraron: ${this.tracks.length}`
        this.colorNotification = 'is-success'
      }
      this.tracks = res.tracks.items
      this.isLoading = false
    })
}