Vuejs class doesn't update after ajax request - vue.js

What I'm trying to do is to highlight a table row after the component has been created or mounted. The playingTrack value is being changed to the id of the current song but the class doesn't change.
The #click function works and changes the class to highlight but what I want is for it to happen when component is mounted taking its value from playingTrack variable.
<tr :class="{highlight:track.id == playingTrack}" #click="playingTrack = track.id" v-for="track in tracks">
<td class="align-middle">
{{track.title}} <br> <span style="color: grey;">{{track.artist}}</span>
</td>
</tr>
<script>
export default{
data(){
return{
tracks:{},
album:{},
playingTrack: undefined
}
},
beforeCreate(){
Event.$emit('requestCurrentTrack');
Event.$on('currentTrack', (data) => this.fetchAlbum(data));
},
methods:{
fetchAlbum(data){
axios.get('/api/album/'+this.id).then((response)=>{
if(data){
this.playingTrack = data.id;
}
this.tracks = response.data[0];
this.album = response.data[1][0];
});
}
}
}
</script>

You can use "Computed Property"
https://v2.vuejs.org/v2/guide/computed.html
Here is well-written documentation.

Related

Multiple v-for loops to display cities within countries?

I am using vue.js and axios within visualstudiocode I'm very new at coding in principal and so I'm sorry if my question is worded poorly. I have two arrays I have assembled from this API:
'https://docs.openaq.org/#api-_'
One is a list of countries, with data of how many cities is in the region etc, and another is a list of cities within those countries with corresponding data. I display a list of countries, in a table followed by a corresponding button in the same row using a v-for loop.
What I want to happen, is when this button is clicked for each corresponding country in the array, all the cities within that country will be displayed. I've been told this needs seperate v-for loops, but I don't know how I'd write it out.
<template>
<div>
<table>
<tr>
<th>Countries</th>
<th>Cities</th>
</tr>
<tr>
<td>
<ul>
<li v-for="countries in listofcountries" :key="countries.name">
{{countries.name}}
</li>
</ul>
</td>
<ul v-for="countries in listofcountries" :key="countries.name">
<li v-for="cities in lifeofcities" :key="cities.city"></li>
<button>{{countries.name}}</button>
</li>
</ul>
</tr>
</table>
</div>
</template>
<script>
export default {
name: 'HelloWorld',
data () {
return {
listofcountries: [],
listofcities: [],
}
},
mounted () {
const axios = require('axios');
var self = this;
// Make a request for a user with a given ID
axios.get('https://api.openaq.org/v1/cities')
.then(function (response) {
// handle success
console.log(response.data.results);
self.listofcities=response.data.results;
})
.catch(function (error) {
// handle error
console.log(error);
})
.finally(function () {
// always executed
});
axios.get('https://api.openaq.org/v1/countries')
.then(function (response) {
// handle success
console.log(response.data.results) ;
self.listofcountries=response.data.results;
})
.catch(function (error) {
// handle error
console.log(error);
})
.finally(function () {
// always executed
});
},
methods: {
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
ul {
list-style-type: none;
}
table,th,td,tr {
border: 1px solid black;
}
</style>
I'm not completely certain this output is what you want but it's a place to start:
<template>
<table>
<tr>
<th>Countries</th>
<th>Cities</th>
</tr>
<tr v-for="country in countries" :key="country.key">
<td>{{ country.name }}</td>
<td v-if="areCitiesVisible(country.code)">{{ citiesInCountry(country.code) }}</td>
<td v-else>
<button #click="onClickCountry(country.code)">show cities</button>
</td>
</tr>
</table>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
cities: [],
countries: [],
countriesWithVisibleCities: [],
};
},
mounted() {
axios
.get('https://api.openaq.org/v1/cities')
.then(response => (this.cities = response.data.results))
.catch(console.log);
axios
.get('https://api.openaq.org/v1/countries')
.then(response => (this.countries = response.data.results))
.catch(console.log);
},
methods: {
citiesInCountry(code) {
return this.cities
.filter(c => c.country === code)
.map(c => c.city)
.join(', ');
},
onClickCountry(code) {
this.countriesWithVisibleCities.push(code);
},
areCitiesVisible(code) {
return this.countriesWithVisibleCities.includes(code);
},
},
};
</script>
It renders a table with one row per country. Each row has a name and a button. If you press the button, you'll get a list of cities instead of the button. Note that the API call seems to return only the cities for the first few counties in the list.
Other notes:
Your original post had several typos that could have been caught with eslint. I recommend installing it along with prettier. I promise that will save you a lot of time in the future.
I simplified the axios calls - self isn't necessary here.

Vue.js Not updating the class css items when data changes

I am having a problem updating my shown class when the data changes.
I have a servers array that calls to get the server status every 10 seconds. If the data changes, the data changes, but the class doesn't
The part that isn't changing is showing the font-awesome icon based on the status
'fas fa-exclamation-triangle critical' : 'fas fa-check ok'">
The text does change {{server.status}} just not the font-awesome class in the if statement.
Any ideas on what I need to change to get it to show correctly?
<tr v-for="server in servers">
<td>
{{server.name}}
<a v-bind:href="server.url" target="_blank">{{server.url}}</a>
</td>
<td style="min-width: 125px">
<i :class="server.status === 'CRITICAL' ? 'fas fa-exclamation-triangle critical' : 'fas fa-check ok'"></i>
{{server.status}}
</td>
<td>{{server.revision}}</td>
<td>{{server.notify}}</td>
<td>{{server.count}}</td>
</tr>
<script>
import axios from 'axios'
export default {
name: 'ServerMonitor',
data() {
return {
servers: []
}
},
created() {
this.fetchData();
},
mounted: function () {
setInterval(function () {
this.fetchData();
}.bind(this), 10000)
},
methods: {
fetchData() {
axios.get('https://SERVER/serverinfo')
.then((resp) => {
this.servers = resp.data[0].servers;
})
.catch((err) => {
console.log(err);
})
}
}
}
</script>
Also I have tried it without the :class like this:
<i v-if="server.status === 'CRITICAL'" class="fas fa-exclamation-triangle critical"></i>
<i v-if="server.status === 'OK'" class="fas fa-check ok"></i>
Vue's v-bind:class takes an object or an Array and not a string, which is probably your issue.
<td style="min-width: 125px">
<i :class="['fas', server.status === 'CRITICAL' ? 'fa-exclamation-triangle critical' : 'fa-check ok']"></i>
{{server.status}}
</td>
Updating my answer based on comments below:
You need to use the font-awesome Vue component. What's happening is that FontAwesome is converting the <i> icons to SVG once, and doesn't rerender them at any future point.
Edit 2
Alternatively you can use the v4 upgrade shim:
<script defer src="https://use.fontawesome.com/releases/v5.0.6/js/v4-shims.js"></script>
https://jsfiddle.net/6tfqp4nb/12/
If you are using font-awesome in js way, you can try this:
FontAwesomeConfig = { autoReplaceSvg: 'nest' }
doc: https://fontawesome.com/how-to-use/svg-with-js#auto-replace-svg-nest

Vue: Style list row when selected

I have a list of components called client-row and when I select one I want to change the style. I run into an issue when trying to remove the styling from the previously selected row, when selecting a new row.
Vue.component('client-row', {
template: '#client-row',
props: {
client: Object,
},
data: function() {
return {
selected: false
}
},
methods: {
select: function() {
// Does not work properly
el = document.querySelector('.chosen_row')
console.log(el)
if ( el ) {
el.className = el.className - "chosen_row"
}
this.selected = true
this.$emit('selected', this.client)
}
}
})
<script type="text/x-template" id="client-row">
<tr v-bind:class="{ 'chosen_row': selected }">
<td>{{ client.name }}</td>
<td>{{ client.location_name || 'no location found' }}</td>
<td>{{ client.email || 'no email found' }}</td>
<td><button class="btn btn-sm btn-awaken" #click="select()">Select</button></td>
</tr>
</script>
I can properly set the selected property, but cannot seem to remove it reliably.
It is generally bad practice to manually modify DOM elements in components. Instead, I recommend that you change the parent component to have a data field to keep track of which row is selected, and to pass that value into the rows. The row would then check whether its value matches the parent's selected row and apply style if true.
DOM manipulation in components is a sign you are doing things very wrong in vue.
In your case, vue and your manually DOM manipulation are battling each other. Vue is tracking whether to add the chosen_row class on the tr based on whether the child's data field selected is true or not. In your code, you are only ever setting it to true. Vue will always try to include the class for any row you have clicked. Then you are manually removing the class from all rows that were previously clicked, however Vue will still try to add the class because selected is still true in the child components that have been clicked.
You need to do a data oriented approach rather than a DOM manipulation based approach.
Child:
Vue.component('client-row', {
template: '#client-row',
props: {
client: Object,
selectedClient: Object
},
methods: {
select: function() {
this.$emit('selected', this.client);
}
}
})
<script type="text/x-template" id="client-row">
<tr v-bind:class="{ 'chosen_row': client === selectedClient }">
<!-- td's removed for brevity -->
<td><button class="btn btn-sm btn-awaken" #click="select">Select</button></td>
</tr>
</script>
Parent:
Vue.component('parent', {
template: '#parent',
data() {
return {
clients: [],
selectedClient: null
};
},
methods: {
clientSelected(client) {
this.selectedClient = client;
}
}
})
<script type="text/x-template" id="parent">
<!-- i dont know what your parent looks like, so this is as simple as i can make it -->
<div v-for="client in clients">
<client-row :client="client" :selected-client="selectedClient" #selected="clientSelected"></client-row>
</div>
</script>
In addition:
Your click event handler on the button can be shortened to #click="select" which is the recommended way of binding methods.

Data is not updated accordint to related props value

I'm trying to use vuejs to have a modal window and hide it on putton pressed.
Here is some code:
index.html:
<table class="table">
<tr v-for = "dream in dreams">
...
<th>
<button id="show-modal" #click="showModal = true">New Post</button>
</th>
</tr>
</table>
...
<editdream v-bind:show.sync="showModal"></editdream>
in editdream.vue file I have:
<template>
<div v-show="isShown" transition="modal">
...
</div>
</template>
<script>
export default {
props: ['show'],
methods: {
close: function () {
this.isShown = false;
},
savePost: function () {
this.close();
}
},
data: function () {
return { isShown: this.show }
}
}
I supposed that when I press button then 'show' props will be updated for modal window and corresponding 'isShown' data will be udpdated as well. But I can only see that prop is becoming true, but isShown is still false when I press button. Could you please explain why?
Inside of editdream.vue, you've defined isShown in under data.
data is set once prior to the creation of the component, so it doesn't update when you click your show-modal button in index.html. isShown stays at whatever your prop show was set to on creation, prior to the button being clicked (presumably false).
A simple fix would be make isShown a computed property:
<template>
<div v-show="isShown" transition="modal">
...
</div>
</template>
<script>
export default {
props: ['show'],
methods: {
close: function () {
this.isShown = false;
},
savePost: function () {
this.close();
}
},
computed: {
isShown: function() return {this.show};
}
}
computed properties actively watch the data on this, like your show prop, so it should update whenever show does.

How to amend data in a different component?

I am new to Vue js and trying to build a simple CRUD example for myself.
Following the documentation on non parent child communication I would like to amend the heading value in the data of one component but from another component.
I set up a fiddle to showcase the relative functionality as I currently understand it and we have here the HTML:
<div id="app" v-cloak>
<person-add></person-add>
<person-list :list="people"></person-list>
</div>
<template id="person-add-template">
<div>
<h2>
<span>{{ heading }}</span>
Person
</h2>
<form #submit.prevent="handleFormSubmit">
<input type="text" placeholder="Enter persons name" v-model="name" />
<button type="submit" v-show="name">
Add Person
</button>
</form>
</div>
</template>
<template id="person-list-template">
<div>
<h2>People</h2>
<table border="1">
<tr>
<th>Person</th>
<th>Edit</th>
</tr>
<tr v-for="(person, key) in list">
<td>{{ person.name }}</td>
<td><button type="button" #click="editPerson(key)">Edit</button></td>
</tr>
</table>
</div>
</template>
And the JS:
// https://v2.vuejs.org/v2/guide/components.html#Non-Parent-Child-Communication
var bus = new Vue();
// Add
Vue.component('person-add', {
template: '#person-add-template',
props: ['list'],
data: function () {
return {
heading: 'Add',
name: ''
}
},
created: function () {
bus.$on('toggle-heading', function (newHeading) {
console.log(newHeading);
this.heading = newHeading;
});
}
});
// List
Vue.component('person-list', {
template: '#person-list-template',
props: ['list'],
methods: {
editPerson: function (key) {
console.log('fired');
bus.$emit('toggle-heading', 'Edit');
}
}
});
// Vue
new Vue({
el: '#app',
data: {
people: [
{ name: 'Bob' },
{ name: 'Frank' },
{ name: 'Mary' }
]
}
});
As you can see, it presents a simple form that starts with "Add Person" and lists some people along with an edit button for each:
What I would like to happen is that when I click on edit next to a persons name then it will change the heading in the other component to say "Edit Person" as opposed to the default "Add Person".
In the method in component A I have:
editPerson: function (key) {
console.log('fired');
bus.$emit('toggle-heading', 'Edit');
}
And in the created hook within component B I have:
created: function () {
bus.$on('toggle-heading', function (newHeading) {
console.log(newHeading);
this.heading = newHeading;
});
}
When I click edit, in the console I see the logs fired and then Edit so the event seems to follow through to the person-add component but where I have tried to assign the new heading this.heading = newHeading;, the heading does not change and I am battling to understand why.
If anyone could suggest why this is happening, where I have gone wrong with this or how things should be done if this is not the right way then it would be greatly appreciated.
Many thanks in advance!
Your problem is actually to do with scope, not a lack of understanding of Vue. Your code is correct, except you are trying to access this from inside a function creating it's own this context.
Whenever you create a new function this way, it creates it's own this so when you do:
bus.$on('toggle-heading', function(newHeading) {
console.log(newHeading);
// this refers to this anonymous function only
this.heading = newHeading;
});
this only refers to the function itself, not the the Vue instance.
The way to get around this is to use an arrow function, which do not create their own this:
bus.$on('toggle-heading', (newHeading) => {
console.log(newHeading);
// No new 'this' context is created in an arrow function
this.heading = newHeading;
});
Or if you are not using ECMAScript 2015 you will need to set a reference to this outside the function:
var self = this; // set a reference to "this"
bus.$on('toggle-heading', function(newHeading) {
console.log(newHeading);
// Now self refers to the view models `this`
self.heading = newHeading;
});
I've updated your fiddle to show you the two methods:
Arrow Function: https://jsfiddle.net/abtgmx47/3/
Using var self=this reference: https://jsfiddle.net/abtgmx47/4/