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

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

Related

Strange behavior in Vue 3 without any error or warning

I have the following Vue 3 component that I am using in 2 pages that are setup like this in vue.config.js:
module.exports = {
// Put this in the ASP.NET Core directory
outputDir: "../wwwroot/app",
pages: {
vehicleDetails: "src/VehicleDetails.js",
driverDetails: "src/DriverDetails.js"
}
};
Both of these pages work and share the same underlying 'Certificates' component that looks like this:
<template>
<div>
<h3 id="Vue_Certificates">Certificates</h3>
<div>
objectId: {{objectId}} test
<table class="table table-striped table-hover table-condensed2" style="clear: both;">
<thead>
<tr>
<th><b>test</b></th>
<th style="text-align: right;">
<a href="#" #click="addCertificate">
<i class="fa fa-plus-square"></i> Add
</a>
</th>
</tr>
</thead>
<tbody>
<tr v-for="certificate in certificates" v-bind:key="certificate">
<td>{{ certificate.CertificateTypeId }}</td>
<td>
<a href="#" #click="removeCertificate(index)" title="Delete" style="float: right;" class="btn btn-default">
<i class="fa fa-trash"></i>
</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import axios from 'axios';
import { onMounted, ref } from "vue";
import $ from 'jquery';
import _ from 'lodash';
export default {
components: {
},
props: {
objectId: String,
ownerIsVehicle: Boolean
},
data() {
return {
certificates: [],
types: []
}
},
created() {
const requestOne = axios.get("/api/v1.0/CertificateType/GetCertificateTypes");
const requestTwo = axios.get("/api/v1.0/Certificate/GetCertificateByObjectId", { params: { objectId: this.objectId, ownerIsVehicle: this.ownerIsVehicle } });
axios.all([requestOne, requestTwo]).then(axios.spread((...responses) => {
const responseOne = responses[0];
const responseTwo = responses[1];
alert("Axios calls completed 1");
this.types = responseOne.data;
var mappedData = responseTwo.data.map(x => ({
...x,
ValidFrom: new Date(x.ValidFrom),
ValidTill: new Date(x.ValidTill)
}));
this.certificates = mappedData;
alert("Axios calls completed 2");
console.log("succes");
})).catch(errors => {
console.log("fail");
});
},
methods: {
removeCertificate(index) {
this.certificates.splice(index, 1);
},
addCertificate() {
alert("Hello?");
}
}
}
</script>
The above code does NOT render the Vue component, but once I take out the two cells containing the anchors that call addCertificate & removeCertificate it all suddenly works.
For some reason I don't get any error in Chrome dev tools .. npm console .. visual studio console .. nowhere, so I have no clue at all what is going wrong.
Important: If I take out 1 of the 2 lines in vue.config.js it works on the page that is still added. The purpose of this component is that it can be reused.
FYI: I shrank down the code as much as possible to make sure other code bits aren't the cause.
For the record, the data property "certificates" was first a ref([]) but that doesn't seem to help at all.
Thanks in advance!

Embeded template in vue

I would like to use a local component in VueJS:
My component file (cleaned up a bit):
<template id="heroValuePair">
<td class="inner label">{{label}}</td>
<td class="inner value">{{c}}</td>
<td class="inner value">
{{t}}
<span v-if="c < t" class="more">(+{{t-c}})</span>
<span v-if="c > t" class="less">({{t-c}})</span>
</td>
</template>
<template id="hero">
<table class="hero card" border="0" cellpadding="0" cellspacing="0">
<tr>
<td>other data...</td>
</tr>
<tr>
<hvp label="Label" v-bind:c="current.level" :t="target.level" :key="hero.id"/>
</tr>
</table>
</template>
<script>
var HeroValuePair = {
template: "#heroValuePair",
props: {
label : String,
c : Number,
t : Number
},
created() {
console.log("HVP: "+this.c+" "+this.t);
}
};
Vue.component("Hero", {
template: "#hero",
props: {
heroId : String
},
components: {
"hvp" : HeroValuePair
},
data: () => ({
hero: {},
current: {},
target: {}
}),
computed: {
},
created() {
fetch("/api/hero/"+this.heroId)
.then(res => res.json())
.then(res => {
this.hero = res.hero
this.current = res.current
this.target = res.target
})
}
});
</script>
<style>
</style>
This outer Hero template is used in a list iterator:
<template id="card-list">
<table>
Card list
<div id="">
<div v-for="card in cards" class="entry">
<Hero :hero-id="card.hero.id" :key="card.hero.id"/>
</div>
</div>
</table>
</template>
<script>
Vue.component("card-list", {
template: "#card-list",
data: () => ({
cards: [],
}),
created() {
fetch("/api/cards")
.then(res => res.json())
.then(res => {
this.cards = res.heroes
})
.catch((e) => alert("Error while fetching cards: "+e));
}
});
</script>
<style>
</style>
However, when I render the card list, it only produces the list of the first td in hvp template:
When I comment out the call of hpv the page is rendered correctly with all the HTML code from Hero template.
I tried to figure out what step I left out, but can't find the clue.
One last info: I used JavalinVue to support the server side, not nodejs-based Vue CLI. I don't know if it has any impact, but may be important.
UPDATE 1
After IVO GELOV spot the problem with multiple root tags, and because I can't move to Vue3, I tried to make it as a functional template, as he suggested. I removed the template and created the render function:
var HeroValuePair = {
template: "#heroValuePair",
functional: true,
props: {
label : String,
c : Number,
t : Number
},
render(createElement, context) {
console.log("HVP: "+context.props.c+" "+context.props.t);
if (typeof context.props.c === undefined) return createElement("td" )
else return [
createElement("td", context.props.label ),
createElement("td", context.props.c ),
createElement("td", context.props.t )
]
}
}
Although the console indicated the render is called correctly, the result is the same: there is neither the rendered nodes, nor the parent Hero component displayed. I tried to move into different file, tried the functional template format, but none worked.

After Vue.delete or $delete UI is not updating

this is my first question on stackoverflow.
So, I try to delete a item from array, I see, that in Vue Dev Tools it was deleted, but UI not updating.
I become this array as response from Laravel API and send dynamic to Vue Component like this
...
<admin-panel :jurisdictions="{{ $jurisdictions }}"></admin-panel>
...
then in my AdminComponent I redirect to AdminHomeComponent with props like this
<template>
<router-view :jurisdictions="jurisdictions"></router-view>
</template>
...
props: ['jurisdictions'],
...
created() {
this.$router.push({ name: "AdminHomeComponent" }).catch(err => {});
},
...
In AdminHomeComponent I have props too and router link to another component JurisdictionsComponent like this
<template>
...
<router-link :to="{name: 'JurisdictionsComponent'}"> Jurisdictions</router-link>
...
</template>
<script>
...
props: ["jurisdictions"]
...
</script>
And then will fun, wenn in JurisdictionsComponent I add a new one, or editing old one it works, there are reactive, but if I try to delete one, it still be reactive and I see this in Vue Dev Tools, but I cann't unterstand, why UI not updating..
JurisdictionsComponent
<template>
<div class="w-100">
<div id="jurisdictionsContainer" ref="jurisdictionsContainer">
<div class="panel-heading d-flex justify-content-between">
<h3 class="panel-title">Jurisdictions</h3>
<div class="pull-right">
<button #click.prevent="$modal.show('create-edit-jurisdiction', {'action' : 'create'})">
<i class="fas fa-plus-square"/> Create new
</button>
</div>
</div>
<table class="table table-hover mt-2 rounded" id="jurisdictions-table">
<thead class="thead-dark ">
<tr>
<th>Title</th>
<th>Actions</th>
</tr>
</thead>
<tbody id="jurisdictions-table-body">
here I make v-for
<tr v-if="jurisdictions !== null" v-for="(jurisdiction, index) in this.jurisdictions" v-bind:key="jurisdiction.id"
class="result clickable-row"
#click="show($event, jurisdiction)">
<td class="title">
{{ jurisdiction.title }}
</td>
<td class="position-relative">
<button #click="$modal.show('create-edit-jurisdiction', {'jurisdiction': jurisdiction, 'index': index, 'action' : 'edit'})">
<div class="not-clickable">Edit</div>
here a show a delete modal window, use can deside delete or not, my code from ModalDeleteComponent see below
</button>
<button #click="$modal.show('delete-jurisdiction', {'jurisdiction': jurisdiction, 'index': index})">
<div class="not-clickable">Delete</div>
<i class="fas fa-trash-alt not-clickable"/>
</button>
</tr>
</tbody>
<delete-jurisdiction #onDeleted="onClickDelete"/>
<create-edit-jurisdiction #create="onClickCreate" #edit="onClickEdit":errors="this.errors.createEdit"/>
</div>
</div>
</template>
<script>
export default {
name: "JurisdictionsComponent",
props: ["jurisdictions"],
data() {
return {
isAllSelected: false,
errors: {
createEdit: {},
addEvent: {}
},
}
},
methods: {
/**
* Create a new jurisdiction
*
* #param data form
*/
onClickCreate(data) {
axios.post("/admin-dashboard/jurisdictions", data.form)
.then(response => {
response.data.image === undefined ? response.data.image = null : response.data.image;
response.data.selected = false;
this.jurisdictions.push(response.data);
this.$modal.hide("create-edit-jurisdiction");
this.errors.createEdit = {}
})
.catch(errors => {
this.errors.createEdit = errors.response.data.errors;
});
Here a try to delete jurisdiction, it deletes from database, from props in Vue Dev Tools but not from UI
/**
* Delete jurisdiction request
*
* #param index
*/
onClickDelete(index) {
axios.delete("/admin-dashboard/jurisdictions/" + this.jurisdictions[index].id)
.then(() => {
this.$delete(this.jurisdictions, index);
this.$modal.hide("delete-jurisdiction");
})
.catch(errors => {
console.log(errors)
});
},
/**
* Edit a jurisdiction
*
* #param data form
*/
onClickEdit(data) {
axios.patch(this.jurisdictions[data.index].path, data.form)
.then(response => {
this.$set(this.jurisdictions, data.index, response.data);
this.$modal.hide("create-edit-jurisdiction");
this.errors.createEdit = {}
})
.catch(errors => {
this.errors.createEdit = errors.response.data.errors;
})
},
}
</script>
ModalDeleteComponent
<template>
<modal name="delete-jurisdiction" #before-open="beforeOpen" height="200" #before-close="beforeClose">
<div class="h-100">
<div v-if="jurisdiction !== null" class="p-4 mt-2">
<h3>Do you want really delete
<a :href="'/admin-dashboard/jurisdictions/'+jurisdiction.id"><strong>{{ jurisdiction.title }}</strong></a>
<span v-if="jurisdiction.events.length > 0">
with {{ jurisdiction.events.length }} {{ jurisdiction.events.length === 1 ? 'event' : "events"}}
</span>?
</h3>
</div>
<div class="bg-dark d-flex justify-content-around p-2 position-absolute w-100" style="bottom: 0">
<button class="btn btn-danger" #click="submitDelete">Delete</button>
<button class="btn btn-secondary" #click="$modal.hide('delete-jurisdiction')">Cancel</button>
</div>
</div>
</modal>
</template>
<script>
export default {
name: "ModalDeleteJurisdictionComponent",
data() {
return {
jurisdiction: null,
index: ""
}
},
methods: {
submitDelete() {
this.$emit('onDeleted', this.index);
},
beforeOpen (event) {
this.jurisdiction = event.params.jurisdiction;
this.index = event.params.index;
},
beforeClose(event) {
this.jurisdiction = null;
this.index = "";
}
}
}
</script>
I know, my question is too long, but if anyone tries to answer this, I will very happy))
I'm open to any contra questions. Sorry for my English
So, thanks oshell for a tipp. Ich have renamed in jurisdictions to dataJurisdictions and init in created() {this.dataJurisdictions = this.jurisdictions} as well. First of all I want to avoid duplication of data in components and work only with props, but nevertheless it works. Thanks a lot!
You are adding to jurisdictions, which is a prop.
this.jurisdictions.push(response.data);
However, you should either update the prop in the parent component, to trigger a prop change and re-render or assign the prop to the components data as initial value and then update data.
Changing prop in parent component can be done using $emit or by using Vuex.
Assigning data locally just needs a different value name.
this.localJurisdictions = this.jurisdictions
And for updating then use this new data value. (Use accordingly in template.)
this.localJurisdictions.push(response.data);

V-model populated in a method not updating the DOM

I am newbie in VueJs.(vue 2). I have a problem here. I have a table where I am dynamically populating data like this.
<tbody>
<tr v-bind:key="queProduct.value" v-for="queProduct in queueProducts">
<td class="has-text-centered">
<figure class="image is-48x48">
<img :src="queProduct.image" alt="Placeholder image">
</figure>
</td>
<td><span>{{queProduct.title}}</span></td>
<td class="has-text-centered"><a class="has-text-link">
<span class="icon is-size-4" #click="openModalPopup(queProduct.id)">
<i class="fa fa-edit" />
</span>
</a>
</td>
<td class="has-text-centered"><a class="has-text-link">
<span class="icon is-size-4" #click="openModalPopup(queProduct.id)">
<img :src="queProduct.indicatorImg" />
</span>
</a>
</td>
<td class="has-text-centered"><a class="delete is-large has-background-link" #click="removeFromQueue(queProduct.id)"></a></td>
</tr>
</tbody>
methods:{
loadQueue(){
const indicators = store.get('productIndicators');
if(indicators === undefined){
store.set('productIndicators', []);
} else {
this.savedProprogressIndicators = indicators;
}
this.queueProducts.forEach(product => {
product.indicatorImg = indicatorImgBaseUrl +'Level-0.png';
this.savedProprogressIndicators.forEach(indicator => {
if(indicator.id === product.id){
product.indicatorImg = indicatorImgBaseUrl +indicator.image;
}
})
})
}
}
When I console.log the product, I see the product object being updated with the new value. But the dom isnt getting updated. Like,
this.product looks like this.
{
id: "d6dd8228-e0a6-4cb7-ab83-50ca5a937d45"
image: "https://zuomod.ca/image/cache/catalog/products/2018/Single/medium/50105-1-800x800.jpg"
inQueue: false
indicatorImg: "https://cdn.shopify.com/s/files/1/0003/9252/7936/files/Level-2.png"
saved: false
sku: "50105"
title: "Interstellar Ceiling Lamp"
}
But in the DOM, it looks like this
{
id: "d6dd8228-e0a6-4cb7-ab83-50ca5a937d45"
image: "https://zuomod.ca/image/cache/catalog/products/2018/Single/medium/50105-1-800x800.jpg"
inQueue: false
indicatorImg: "https://cdn.shopify.com/s/files/1/0003/9252/7936/files/Level-0.png"
saved: false
sku: "50105"
title: "Interstellar Ceiling Lamp"
}
Can you please help me resolve this?
Thanks,
Vandanaa
As you use Vuex, you should get your products directly from you store like in computed property in your vue definition. This will refresh the data directly from store without any action from vue side.
{
...
computed:{
...mapGetters({
queueProducts : 'queueProducts'
})
}
...
}
Furthermore, if your are using vuex, try to keep your logic inside your store. You vue should only display data.
Hava a look to vuex documentation to know when and where you should use
Getters, Mutations and Actions.
Hope this help.
this.queueProducts.forEach(product => {
...
...
...
}
this.$forceUpdate(); // Trying to add this code
I guessed your product.indicatorImg was not been watch by Vue, so it will not update the DOM. Trying to add this.$forceUpdate() in the end. It will force Vue to update DOM.

Vuejs class doesn't update after ajax request

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.