stop playing audio when navigating to another pages, vue.js - vuejs2

I am working on a spa using laravel and vue, I want to play a background music only in home page not in other pages, i have simplified my codes to make my point
this my routes.js codes
import home from './components/main.vue';
export const routes = [
{
path: '/',
component: home,
name: 'home'
},
]
this my root.vue
<template>
<div>
<navigation></navigation>
<router-view></router-view>
</div>
</template>
<script>
import navigation from './nav';
export default {
components:{
navigation
},
}
</script>
this is nav.vue
<template>
<div>
<nav>
<i class="fas fa-volume-slash" id="mute" v-if= "this.$route.name == 'home'" v-show = "mute" #click = "play"></i>
<i class="fas fa-volume-up" id="speaker" v-if= "this.$route.name === 'home'" v-show= "!mute" #click = "play"></i>
<!-- i want music i con to be seen only in home page -->
</nav>
<audio style="display: none;" id="backMusic">
<source src="uploads/audio/music.mp3" type="audio/mpeg">
</audio>
</div>
</template>
<script>
export default {
data(){
return{
mute: true,
audio2: null,
}
},
methods:{
play(){
if(this.mute){
this.audio2.play();
this.audio2.loop = true;
this.mute = false;
}else{
this.audio2.pause();
this.mute = true;
}
},
},
mounted(){
this.audio2 = document.getElementById('backMusic');
}
}
</script>
finally this app.js
Vue.component('root-view', require('./components/root.vue').default);
import {routes} from './routes.js';
const router = new VueRouter({
routes,
mode: 'history',
});
const app = new Vue({
el: '#app',
router,
store
});
when i navigate to login page music still plays! when i navigate to anywhere music still plays
how can i stop
there is no audio tag or javascript audio variable in other pages but still it goes on playing
thank you
p.s: i didnt know how to create jsfiddle containg 2 pages(to navigate)

I read some Q&A in stackoverflow and i finally got the answer
i just add watch to my nav.vue which includes audio
in any redirect i run this code to stop audio
nav.vue
watch:{
$route (to, from){
this.audio2.pause();
}
}

You are also able to you use beforeRouteLeave
beforeRouteLeave(to, from, next) {
this.play()
next()
},

If you are using Nuxt.js you can add
beforeMount(){ initializedAudio.pause(); this.isPlaying = false; },
into your nav or header

Related

Vue2 - how to recreate component?

How can I recreate a whole component after button click.
Let say I'm in Component "UserPanel" and there is a button call "Refresh".
When I click that button I would like to destroy component and create it from the scratch. I don't wont to use option like "vm.$forceUpdate()" because it doesn't help in my case.
Is it any way to do it?
My app code:
App.vue:
<template>
<div id="main-cont">
<NavBar></NavBar>
<router-view></router-view>
</div>
</template>
<script>
import NavBar from './components/TopBar/NavBar';
import {mapActions,mapGetters} from 'vuex';
import axios from 'axios';
export default {
name: 'App',
components: {
NavBar,
},
computed:{
...mapGetters(['isLoggedIn'])
},
methods:{
...mapActions(['loadLanguage','setToken','setUserLogged','loadUserProfile'])
},
created(){
this.loadLanguage();
this.setToken();
let userLoggedIn = document.head.querySelector('meta[name="logged"]').content;
if(userLoggedIn){
this.setUserLogged();
this.loadUserProfile();
}
}
}
</script>
<style scoped>
#main-cont{
height: 100%;
}
</style>
main.js:
import Vue from 'vue';
import VueRouter from 'vue-router';
import VueCookie from 'vue-cookie';
import store from './store';
import App from './App';
//Components
import Main from './components/main/Main';
import UserRegister from './components/user/UserRegister';
import ResetPassword from './components/user/ResetPassword';
import UserEdit from './components/user/UserEdit';
import UserView from './components/user/UserView.vue';
import GameMain from './components/game/GameMain';
import GamesList from './components/main/GameList';
import Hall from './components/main/Hall';
import Language from './components/main/Language';
import GameCreate from './components/game/GameCreate';
//Plugins
import langPlugin from './langPlugin';
import VTooltip from 'v-tooltip';
Vue.use(VueRouter);
Vue.use(VueCookie);
Vue.use(langPlugin);
export const router = new VueRouter({
mode: 'history',
routes: [
{path: '/', component: Main},
{path: '/user-register', component: UserRegister},
{path: '/user-edit', component: UserEdit},
{path: '/password-reset', component: ResetPassword},
{path: '/user', component: UserView},
{path: '/game', component: GameMain},
{path: '/game-create', component: GameCreate},
{path: '/games-list', component: GamesList},
{path: '/hall-of-fame', component: Hall},
{path: '/language', component: Language},
]
});
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app');
Component to Reload.
GameCreate:
<Template>
<div>
<button #click="reloadThisComponent"></button>
</div>
</Template>
<script>
export default{
name: 'GameCreate',
methods:{
reloadThisComponent(){
}
}
}
</script>
Thank you.
EDIT (with the new question details) : Since you're using view router and your component is registred as a route, juste simply add the following to your rebuild method in your Game component this should works fine
this.$router.go(this.$router.currentRoute)
const router = new VueRouter({
mode: 'history',
})
new Vue({
router,
el: '#app',
methods: {
reload: function() {
this.$router.go(this.$router.currentRoute)
}
},
created() {
console.log("Hey")
}
})
<script src="https://npmcdn.com/vue/dist/vue.js"></script>
<script src="https://npmcdn.com/vue-router/dist/vue-router.js"></script>
<div id="app">
<button #click="reload">Reload</button>
<router-view></router-view>
</div>
The simple way to get to the goal is to set a boolean in v-if on your component. Then switch true/false the boolean. When v-if is false the component is destroyed and will be reinstanciate after.
To do this, there is two way. What we want is to change de state of the parent component that will say if we print our component or not. The first way to do it is by using a State Management plugin like VueX, but it's a little bit too much for what we simply want to do. To be simpliest, we have to trigger an event from your component, that will trigger the state change on the parent.
In the exemple bellow, when you click on the reset button inside MyComponent,custom event named "reset" is emitted. In the parent component, we have a showMyComponent boolean on our MyComponent tag and a listener #reset that will trigger the method named "resetMyComponent" when the event "reset" is emmited by our MyComponent.
Here is a few resources :
What is VueX and State Management if you want know more about the first way to do it
Documentation about Custom Events in VueJS
Doc about Reactivity in Deep, not important here but it's for the explaination of this.nexttick usage here
Hope it's more clear now
var MyComponent = Vue.component('my-component', {
name : "my-component",
template : "#my-component-template",
data(){
return {
interval : null,
count : 0
}
},
created() {
console.log("MyComponent is created")
this.interval = setInterval(() => {
this.count++
},1000)
},
destroyed() {
console.log("MyComponent is destroyed")
clearInterval(this.interval)
}
});
new Vue({
el: "#app",
components : {
MyComponent
},
data: {
showMyComponent : true
},
methods : {
resetMyComponent() {
this.showMyComponent = false;
Vue.nextTick(() => {
this.showMyComponent = true;
});
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<my-component v-if="showMyComponent" #reset="resetMyComponent"></my-component>
</div>
<script type="text/x-template" id="my-component-template">
<div style="padding: 15px">
<p>My component is created since {{count}} seconds</p>
<button #click="$emit('reset')">Reset my component</button>
</div>
</script>
There are multiple ways of recreating components. The most efficient way is to change the component key. What we do here is we will supply a key attribute so Vue knows that a specific component is coupled or tied to a specific piece of data. If the key stays the same, it won't change the component, but if the key changes, Vue knows that it should get rid of the old component and re-create a new one.
Here is a very basic way of doing it:
<template>
<component-to-re-render :key="componentKey" />
</template>
export default {
data() {
return {
componentKey: 0,
};
},
methods: {
forceRerender() {
this.componentKey += 1;
}
}
}
Every time that forceRerender is called, our prop componentKey will change. When this happens, Vue will know that it has to destroy the component and create a new one. What you get is a child component that will re-initialize itself and “reset” its state. this simple and elegant way is solving the most common challenge we face in the Vue app development!
You can also check other possible ways od doing this: https://medium.com/emblatech/ways-to-force-vue-to-re-render-a-component-df866fbacf47

Vue.js - How to capture a URL parameter as a hidden input value

I am writing a password reset function in Vue.js. As part of the process I generate a password key that is part of the reset url emailed to the user. It is very well possible that user may request a reset on one device but then go to another to do the reset. Therefore, storing it in the local storage is not an option. How to I pass the password key along with the new password. Please advise.
The way you can do this with Vue is using vue-router and accessing the $route.query object.
For instance, when the URL has ?key=somekey, the $route.query object is `{key: "somekey"}.
Here's a demo CodeSandbox showing how you could use it.
Relevant code:
// main.js
import Vue from "vue";
import VueRouter from "vue-router";
import PasswordReset from "./PasswordReset";
Vue.use(VueRouter);
var router = new VueRouter({
mode: 'history',
routes: [{
path: '/',
component: {
template: `<div>
You are at '/'.<br>
Click to simulate an external link user:
<a href="https://lrrwkyv7k7.codesandbox.io/passwordreset?key=somekey">
https://lrrwkyv7k7.codesandbox.io/passwordreset?key=somekey
</a>
</div>`
}
}, {
path: '/passwordreset',
component: PasswordReset
}]
});
new Vue({
el: "#app",
router: router,
template: '<router-view></router-view>'
});
// PasswordReset.vue
<template>
<div id="app">
<p>Hello, user.</p>
<form action="" #submit="submit">
Type some new password: <input type="text" name="newpass" v-model="newPass">
<input type="hidden" name="key" :value="hiddenKey">
<button>Send</button>
</form>
<hr>
<div class="debug">
Object that will be sent:
<pre>{{ $data }}</pre>
click here to get back to home
</div>
</div>
</template>
<script>
export default {
name: "PasswordReset",
data() {
return {
newPass: "",
hiddenKey: ""
};
},
mounted() {
this.hiddenKey = this.$route.query.key;
},
methods: {
submit(event) {
alert('We would have sent:\n'+JSON.stringify(this.$data, null, 2));
event.preventDefault(); // don't submit now, just testing...
}
}
};
</script>

Vue.js 2 - Sharing XHR Data between Views WITHOUT using Vuex?

I'm teaching myself Vue.js 2. My task is to pull a list of posts from the Hacker News API. Upon clicking a list post, a new view is to display some of the data from that specific post.
I'm having a very tough time understanding how to get the REST API data populated in the 2nd view upon routing to the 2nd view, from the 1st view.
(I'm using vue-router and vue-resource(?), and not Vuex (it's such a small application).)
Below are the Item.vue and List.vue. From the List.vue, I'm trying to route to the Item.vue by clicking on a list item. For example, click on a list item called "Guy Has Tough Time with Vue", then open a 2nd view to display a title, score, URL, and comments of the post "Guy Has Tough Time with Vue".
List.vue (creates list, XHR request)
<template>
<div class="list-container">
<h1>List Vue</h1>
<ul class="item-list" v-for="(item, index) in this.items">
<li>
<router-link class="list-item" :to="/views">
{{index + 1}}. {{item.title}}
<div class="points">
Points: {{item.points}}
</div>
</router-link>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'List',
props:[]
data(){
return {
items: []
}
},
mounted: function(){
console.log("created");
this.fetchList();
},
methods: {
fetchList(){
this.$http.get('http://hn.algolia.com/api/v1/search?query=javascript&hitsPerPage=25').then((response) => {
this.items = response.data.hits;
})
}
}
}
Item.vue (Receives item-specific data from List.vue)
<template>
<video id="bgvid" playsinline autoplay loop>
<source src="./src/assets/rad.mp4" type="video/mp4">
<div class="item-container">
<h1>Item Vue</h1>
<div class="post-title"></div>
<div class="post-score"></div>
<div class="post-url"></div>
<ul class="post-comments">
<li class="sngl-comment">
</li>
</ul>
</div>
</video>
</template>
<script>
export default {
name: 'Item',
data(){
return {
item: {}
}
},
mounted: function(){
console.log("created");
},
methods: {
}
}
</script>
Thanks in advance!
The problem is that the first view isn't a direct descendant of the second view so you can't pass data to it through props. I actually wouldn't be using vuex for this, instead I would pass an id through the route for the specific list item and fetch that individual item by the id, as an example:
const View1 = {
template: `<div>
<ul>
<li v-for="item in list"><router-link :to="'view2/'+item.id">{{item.name}}</router-link></li>
</ul>
</div>`,
data() {
return {
list: [{
id: 1,
name: 'foo'
}, {
id: 2,
name: 'bar'
}, {
id: 3,
name: 'baz'
}]
}
}
}
const View2 = {
template: `<div>Fetch item {{id}} <br /><router-link to="/">back</router-link></div>`,
created(){
console.log('Fetch data for item '+ this.id);
},
computed:{
id(){
return this.$route.params.id
}
}
}
const routes = [{
path: '/',
component: View1
}, {
path: '/view2/:id',
component: View2
}]
const router = new VueRouter({
routes // short for `routes: routes`
})
var app = new Vue({
router
}).$mount('#app')
Here, I've set the route on View2 to be: view2/:id (see: Dynamic route matching), now in the View2 component I can access that id via this.$route.params.id (which I've put in a computed). Once I've got that id I can simply make an ajax request for the data for the specific item.
And here's the JSFiddle: https://jsfiddle.net/craig_h_411/3hwr6mcd/
Using Vuex
If you are unable to retrieve a record by the specific id for some reason and you don't want to duplicate the call, you will need to share state between non-descendant components and that's where Vuex comes in.
There is a misconception with vuex that it's complicated, but if you only want to share state between a couple of components, it's really quite simple (and it's less than 10kb in size) so there really isn't much use trying to avoid it, all you need to add to your project is:
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = {
state:{
item: {}
},
mutations:{
setItem(state, value){
state.item = value;
}
}
}
export default new Vuex.Store(store)
Then in your app.js (or wherever you have you main Vue instance) you would do something like:
import Vue from 'vue'
import store from './store'
import App from './components/App.vue'
new Vue({
store, // this will be available in components via this.$store
render: h => h(App)
}).$mount('#app')
You will also have to make a few changes, such as committing the state before pushing to the route, adding a computed to get the state from the store and removing the id from the route as it's no longer needed.
I've updated the JSFiddle to show you how that would work: https://jsfiddle.net/craig_h_411/bvswc0kb/

VueJS - trigger Modal from materializecss

I am trying to trigger a modal from the materializecss framework within a VueJS-instance.
Both, VueJS and Materializecss, are implemented correct. On their own both frameworks work fine.
Clicking the open-button results in an error:
Uncaught TypeError: data[option] is not a function
at HTMLDivElement. (adminarea.js:24562)
at Function.each (adminarea.js:10567)
at jQuery.fn.init.each (adminarea.js:10356)
at jQuery.fn.init.Plugin [as modal] (adminarea.js:24556)
at Vue$3.showLoader (adminarea.js:21396)
at boundFn (adminarea.js:54956)
at HTMLButtonElement.invoker (adminarea.js:56467)
This is my Vue-Instance:
const app = new Vue({
el: '#app',
data: {
activeUser: {
username: '',
email: ''
},
},
methods: {
showLoader(){
$('#loaderModal').modal('open');
},
closeLoader(){
$('#loaderModal').modal('close');
}
},
mounted() {
// Get current User
axios.get('/api/currentUser')
.then(response => {
this.activeUser.username = response.data.username;
this.activeUser.email = response.data.email;
});
},
components: {
Admindashboard
}
});
And here is the part of my html-file with the modal structure:
<!-- Modal Structure -->
<div id="loaderModal" class="modal">
<div class="modal-content">
<h4>Fetching data..</h4>
<div class="progress">
<div class="indeterminate"></div>
</div>
</div>
</div>
<button class="btn cyan waves-effect waves-cyan" v-on:click="showLoader">Open</button>
Any ideas? Thanks!
It seems I found an solution:
Nice to know for Laravel-users: for my current project I use Laravel 5.5 with Materializecss, VueJS and VueRouter but I think the solution is universal. Materializecss was installed via npm and has to be included into your application. I've required the css-framework within my ressources/assets/js/bootstrap.js:
...// more code
try {
window.$ = window.jQuery = require('jquery');
window.materialize = require('materialize-css');
} catch (e) {
console.log(e);
}
...// more code
Now you have to initialize the Modal-function on the mounted-event of your wrapping Vue-instance:
const app = new Vue({
router,
data: {
...
},
methods: {
testClick: function(){
console.log('Testklick-Call');
$('#modal1').modal('open');
}
},
mounted: function(){
console.log('Instance mounted');
$('.modal').modal();
}
}).$mount('#app');
The code above is placed within my ressources/assets/js/app.js and is packed by default by Laravel Mix but I think this is universal and also usable without Laravel Mix/Webpack etc.
Now you can call every modal programmatically from where ever you want. I've tested it in my main instance on a click-event. Function is placed in my Vue-instance (see above). HTML-Code see below:
<button v-on:click="testClick">Open Modal</button>
But you can also make use of the modal within a mounted-function or any other function of any component:
<template>
<div>
<p>I am an component!</p>
</div>
</template>
<script>
export default {
mounted() {
console.log('Component mounted!');
$('#modal1').modal('open');
}
}
</script>
This also works, if the component becomes only visible after clicked on a link (using VueRouter).
Hopefully this helps someone except me :)
As suggested here, you need to add following code in the mounted block:
mounted() {
$('#loaderModal').modal(); //New line to be added
// Get current User
axios.get('/api/currentUser')
.then(response => {
this.activeUser.username = response.data.username;
this.activeUser.email = response.data.email;
});
},

<router-view> not funcitoning on upgrade to vue.js 2.0

I am completely stuck on getting a component to render through router-view using the vue.js 2.0 and vue-router.
I have vue devtools installed and I do see the "fragment" label next to router-view, but no other details.
Might be important to note that I am using laravel-elixir-vueify and browserify.
App.js
var Vue = require('Vue');
var VueRouter = require('vue-router');
import dashboard from './components/dashboard.vue'
Vue.component('dashboard', dashboard);
Vue.use(VueRouter);
const router = new VueRouter({
routes: [
{ path: '/', component: dashboard }
]
const app = new Vue({
router
}).$mount('#vueapp')
dashboard.blade.php
<div id="vueapp">
//other code removed for space
<router-view></router-view>
</div>
dashboard.vue
<template>
<div class="col-md-12 col-lg-12">
<div class="block">
<div id="showCalendar"></div>
</div>
</div>
</template>
//Note: I tried adding an extra <div></div> within the template, but that didn't make a difference.
<script>
export default{
mounted: function() {
this.CompCalendar();
},
methods: {
CompCalendar: function() {
/* Initialize FullCalendar */
var date = new Date();
var d = date.getDate();
var m = date.getMonth();
var y = date.getFullYear();
this.$nextTick(function() {
var events = this.$http.get('/events/local/index')
.then(function(response){
$('#showCalendar').fullCalendar({
header: {
//unimportant options
},
//unimportant options
eventClick: function(calEvent, jsEvent, view){
var eventId = calEvent.id;
router.push({
path: 'details/'+eventId
});
},
});
})
});
}
}
};
</script>
So the first thing, you dont need to register Vue.component('dashboard', dashboard)
The Reference to in the routes is enough.
Everything else looks good for me. Do you have any Errors in your console?
Try routes = [ { path: '', component ..
It seems like you haven't registered any component for this action
router.push({
path: 'details/'+eventId
});
It seems like you need something like that in your router:
{ path: '/details', component: Details }