No tienes acceso a esta clase

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

Convierte tus certificados en títulos universitarios en USA

Antes: $249

Currency
$209

Paga en 4 cuotas sin intereses

Paga en 4 cuotas sin intereses
Suscríbete

Termina en:

17 Días
19 Hrs
10 Min
0 Seg

Comunicacion entre Componentes Genericos - Event Bus y Plugins

30/53
Recursos

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
    })
}