Conditionally rendering icons only one time depending on property - vue.js

I am trying to write a template that displays either Icon AAA or Icon BBB, depending on whether or not the item in the current iteration has a specific flag. Here is my code:
<div v-for="(item, itemIndex) in items">
<div v-if="item.hasUnreadComments">
<span>Display Icon AAA</span>
</div>
<div v-else>
<span>Display Icon BBB</span>
</div>
</div>
The issue here is that I need either icon displayed ONCE. If more than one item has it set to item.hasUnreadComments === true, Icon AAA will be displayed equally as many times, which is not what I want. Couldnt find anything in the docs and I dont want to bind it to a v-model.
Can this be done in Vue without a third data variable used as a flag?

You will have to do some sort of intermediate transformation. v-if is just a flexible, low-level directive that will hide or show an element based on a condition. It won't be able to deal directly with how you expect the data to come out.
If I'm understanding what you're asking for, you want an icon to only be visible once ever in a list. You can prefilter and use an extra "else" condition. This sounds like a use case for computed properties. You define a function that can provide a transformed version of data when you need it.
This example could be improved upon by finding a way to boil down the nested if/elses but I think this covers your use case right now:
const app = new Vue({
el: "#app",
data() {
return {
items: [{
hasUnreadComments: true
},
{
hasUnreadComments: true
},
{
hasUnreadComments: false
},
{
hasUnreadComments: true
},
{
hasUnreadComments: false
},
]
}
},
computed: {
filteredItems() {
let firstIconSeen = false;
let secondIconSeen = false;
return this.items.map(item => {
if (item.hasUnreadComments) {
if (!firstIconSeen) {
item.firstA = true;
firstIconSeen = true;
}
} else {
if (!secondIconSeen) {
item.firstB = true;
secondIconSeen = true;
}
secondIconSeen = true;
}
return item;
});
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="(item, itemIndex) in filteredItems">
<div v-if="item.firstA">
<span>Display Icon AAA</span>
</div>
<div v-else-if="item.firstB">
<span>Display Icon BBB</span>
</div>
<div v-else>
<span>Display no icon</span>
</div>
</div>
</div>

Related

How to fire an event in mount in Vuejs

I have a sidebar that you can see below:
<template>
<section>
<div class="sidebar">
<router-link v-for="(element, index) in sidebar" :key="index" :to="{ name: routes[index] }" :class='{active : (index==currentIndex) }'>{{ element }}</router-link>
</div>
<div class="sidebar-content">
<div v-if="currentIndex === 0">
Profile
</div>
<div v-if="currentIndex === 1">
Meine Tickets
</div>
</div>
</section>
</template>
<script>
export default {
mounted() {
EventBus.$on(GENERAL_APP_CONSTANTS.Events.CheckAuthentication, () => {
this.authenticated = authHelper.validAuthentication();
});
console.log()
this.checkRouter();
},
data(){
return {
currentIndex:0,
isActive: false,
sidebar: ["Profile", "Meine Tickets"],
routes: ["profile", "my-tickets"],
authenticated: authHelper.validAuthentication(),
}
},
computed: {
getUser() {
return this.$store.state.user;
},
},
methods: {
changeSidebar(index) {
this.object = this.sidebar[index].products;
this.currentIndex=index;
},
checkRouter() {
let router = this.$router.currentRoute.name;
console.log(router);
if(router == 'profile') {
this.currentIndex = 0;
} else if(router == 'my-tickets') {
this.currentIndex = 1;
}
},
},
}
</script>
So when the link is clicked in the sidebar, the route is being changed to 'http://.../my-account/profile' or 'http://.../my-account/my-tickets'. But the problem is currentIndex doesn't change therefore, the content doesn't change and also I cannot add active class into the links. So how do you think I can change the currentIndex, according to the routes. Should I fire an event, could you help me with this also because I dont know how to do it in Vue. I tried to write a function like checkRouter() but it didn't work out. Why do you think it is happening? All solutions will be appreciated.
So if I understand correctly, you want currentIndex to be a value that's based on the current active route? You could create it as a computed property:
currentIndex: function(){
let route = this.$router.currentRoute.name;
if(router == 'profile') {
return 0;
} else if(router == 'my-tickets') {
return 1;
}
}
I think you could leverage Vue's reactivity a lot more than you are doing now, there's no need for multiple copies of the same element, you can just have the properties be reactive.
<div class="sidebar-content">
{{ sidebar[currentIndex] }}
</div>
Also, you might consider having object be a computed property, something like this:
computed: {
getUser() {
return this.$store.state.user;
},
object() {
return this.sidebar[currentIndex].products;
}
},
Just use this.$route inside of any component template. Docs .You can do it simple without your custom logic checkRouter() currentIndex. See simple example:
<div class="sidebar-content">
<div v-if="$route.name === 'profile'">
Profile
</div>
<div v-if="$route.name === 'my-tickets'">
Meine Tickets
</div>
</div>

Hide one item from input list that is dynamically generated unless other input from list has value in vue / vuetify

So the process is like this. I have a form that is created from api. I want to show all the inputs except for one. That one will only be shown if the user adds value to another specific input from the form.
Something like this is the idea.
<Form>
<div-for="formItem in state.formItemData">
<template v-if="formItem.one !== ''">
<form-input
v-model="invoiceForm.two"
:key="formItem.id"
ref="two"
></form-input>
</template>
</div>
</Form>
const invoiceForm = computed({
get: () => state.forms.formData,
set: (value) => {
state.forms.formData= value
}
})
The concept in your problem is very simple, you just must make use of a computed property that evaluates if there are values in input 1 and it will rerender the component showing input2.
new Vue({
el: '#app',
data: {
input1: '',
input2: ''
},
methods: {
verification() {
console.log(this.input1);
}
},
computed: {
notEmpty() {
return this.input1 !== '' && this.input1.length > 3;
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input #change="verification" type="text" v-model="input1"> <input v-if="notEmpty" type="text" v-model="input2">
</div>
You should use a v-show if you're eventually going to show/hide it when rendering, especially if it's going to be multiple times. The Vue documentation says that the v-if will not be rendered when the initial condition is false.
Source: https://v3.vuejs.org/guide/conditional.html#v-if-vs-v-show

Why v-model is not updating in text

<template>
<div class="card-deck mb-3 text-center">
<div class="card mb-3 box-shadow">
<div class="card-header">
Numbers Checked
</div>
<div class="card-body card-info color-accent" v-model="numbers_checked" v-text="numbers_checked">
</div>
</div>
</div>
</template>
<script>
export default {
props:
[
'overviewAnalytics',
],
data() {
return {
numbers_checked: this.overviewAnalytics.numbers_checked
};
},
created() {
this.channelTemperatureReading.listen('TemperatureReadingUpdate', reading => {
axios.get('/home/get-overview-analytics').then(resp => {
this.numbers_checked = 12; //resp.data.numbers_checked + 100;
});
});
},
computed: {
channelTemperatureReading() {
return window.Echo.channel('temperature-reading');
},
},
};
</script>
I've tried everything but text is not updating. Confirmed from every aspect that data does change.
Changes from AXIOS are coming just ok. I even tried to put custom value but no avail.
I don't what is issue here.
v-model only works on input, textarea, and select elements
You appear to be misusing computed properties which rely on reactive dependencies to execute however yours is wrapping window.Echo.channel('temperature-reading') which Vue knows nothing about.
I suggest you remove the computed property and use something like this
created() {
const channel = window.Echo.channel('temperature-reading')
channel.listen('TemperatureReadingUpdate', reading => {
axios.get('/home/get-overview-analytics').then(({ data }) => {
// console.log('get-overview-analytics', data.numbers_checked)
this.numbers_checked = data.numbers_checked + 100
})
})
}
As others have mentioned, v-model is not appropriate here so you should also remove that.
Don't use v-model with div, it's for inputs.
<div v-text="numbers_checked"></div>
From the documentation on v-model:
Usage: Create a two-way binding on a form input element or a
component.

How to binding only single class when using several v-on:click?

I'm newbie and studying Vue.js
I create v-on:click function and element toggle class when I click the button.
I am not good at English, I think it will be fast to show the code.
<button #click="bindA = !bindA">A</button>
<button #click="bindB = !bindB">B</button>
<span :class="[{ classA:bindA }, { classB:bindB }]"></span>
data: function() {
return {
bindA: true, // default
bindB: false
}
it's now. clicked bindA and B.
// browser
<span class="classA classB"></span>
but I want
// bindA click , remove classB
<span class="classA"></span>
// bindB click , remove classA
<span class="classB"></span>
It's simple in jquery, but difficult in vue.
It's very simple in vue as well.
Bind the class according to the conditions you want to see the data
:class="{'classA': (bindA== true), 'classB':(bindA== false)}"
Try adding a method to the #click - then you can build up a more complex logic, than simple "toggle".
new Vue({
el: "#app",
data: {
bindA: true,
bindB: false
},
methods: {
bind(btn) {
if ((btn === 'A' && !this.bindA) || (btn === 'B' && !this.bindB)) {
this.bindA = !this.bindA
this.bindB = !this.bindB
}
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="bind('A')">A</button>
<button #click="bind('B')">B</button>
<span :class="{ classA:bindA, classB:bindB }"></span>
</div>

Vue data property not reactive

Considering the code below, I can not make the active data property reactive. In this case I would like to show a div only on mouseover on an image.
<template>
<div>
<img #mouseover="showInfo" class="cursor-pointer w-full" :src="project.images[0].url" width="100%">
<div v-show="active" class="bg-red h-12">
Info about the image
</div>
</div>
</template>
<script>
export default {
props: ['project'],
data: function () {
return {
active: false
}
},
methods: {
showInfo: () => {
this.active = !this.active;
}
}
}
</script>
I have tried using v-if instead and printing active but to no effect. What am I doing wrong?
Don't use arrow functions to define methods, this will not work.
Just replace
showInfo: () => {
this.active = !this.active;
}
With:
showInfo() {
this.active = !this.active;
}
This can be simpler if you just change the value in HTML and you don't require a separate method for that.
#mouseover="active = !active"
It will look like this,
<div>
<img #mouseover="active = !active" class="cursor-pointer w-full" :src="project.images[0].url" width="100%">
<div v-show="active" class="bg-red h-12">
Info about the image
</div>
</div>
data() {
return {
active: false
}
},
Update your data like the one above.
Also I'd rename your function to toggleInfo as it can also hide it on mouse out.
And remove the arrow.
toggleInfo() {
this.active = !this.active;
}