Vue event on element destruction - vue.js

Is there a vue event when a v-if condition ceases to be true?
For example if I want to set y when div id foo is removed for whatever reason.
What should go in place of #whenDivGoes?
<div v-if="z">
<div id="foo"
v-if="x"
#whenDivGoes="y = true"
>
Hello
</div>
<div>
<div v-if="!x">Not X</div>
<div v-if="y">Bye</div>

If I understand correctly you want to set value of 'y' to be true when the foo div cease to exist.
Your foo div is shown when your 'x' is true, and hidden when x is false, so you can declare 'y' as a computed property that depends on x.
computed:{
y(){
return !x;
}
}
then
<div v-if="z">
<div id="foo"
v-if="x">
Hello
</div>
<div>
<div v-if="!x">Not X</div>
<div v-if="y">Bye</div>
note: declaring 'y' as computed property would prevent you from modifying/assigning value to it directly. if you want to 'do something' when 'y' value is changed you can use watch on 'y'.

With v-if, is the node is taken directly from the virtual DOM, you would need to watch and create your own callback.
If you are looking for a quick solution similar to your example, i have written a quick directive to you put directly into the element you want a callback on, but it would require you to use v-show as apposed to v-if (as it only changes the css display class)
Simply change the div you want a callback on when its not longer displayed, and add v-show-event:
<div id="foo"
v-show="x"
v-show-event="() => y = true"
>
So basically what's happening, is the directive checks if the element has style.display set to none
Vue.directive('show-event', {
update: function(el, binding, vnode) {
if(el.style.display === "none") {
if(typeof binding.value==='function') {
binding.value.bind(vnode.context)(event)
}
}
}
});
new Vue({
el: "#app",
data: {
done: false,
newEv: "this is some text"
},
methods: {
toggle: function(){
this.done = !this.done
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2
v-show="!done"
v-show-event="() => newEv = 'show event was hidden'">
v-show-event directive
</h2>
<button #click="toggle()">
Toggle
</button>
<div v-text="newEv"></div>
</div>

Related

Changing one Vuejs v-model value is changing all other values

I have a Nuxtjs/Vuejs application within which I am creating multiple Nodes. These Nodes have the Radio button for which I have assigned v-model. However, when I change the value of one Vuejs v-model is affecting all other Node Values. Following is the code sample that I have created for the Node. The ID value is unique for each Node.
<template>
<div ref="el">
<div class="header">
Node: {{ ID }}
</div>
<div>
Syntax:
<input
id="identifierTypeURN"
v-model="identifierSyntax"
type="radio"
value="URN"
name="instanceIdentifierURN"
#change="instanceIdentifiersSyntaxChange('URN')"
>
<label for="identifierTypeURN">URN</label>
<input
id="identifierTypeWebURI"
v-model="identifierSyntax"
type="radio"
value="WebURI"
name="instanceIdentifierWebURI"
#change="instanceIdentifiersSyntaxChange('WebURI')"
>
<label for="identifierTypeWebURI">WebURI</label>
</div>
</div>
</template>
I am aware that this is happening because I am using the same v-model name for all the Nodes so I changed to something like this. But still the issue persists:
<template>
<div ref="el">
<div class="header">
Identifiers
Node: {{ ID }}
</div>
<div>
Syntax:
<div v-for="node in allNodeInfo" :key="node.identifiersId">
<div v-if="node.identifiersId === ID">
<input
id="identifierTypeURN"
v-model="node.identifierSyntax"
type="radio"
value="URN"
name="instanceIdentifierURN"
#change="instanceIdentifiersSyntaxChange('URN')"
>
<label for="identifierTypeURN">URN</label>
<input
id="identifierTypeWebURI"
v-model="node.identifierSyntax"
type="radio"
value="WebURI"
name="instanceIdentifierWebURI"
#change="instanceIdentifiersSyntaxChange('WebURI')"
>
<label for="identifierTypeWebURI">WebURI</label>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
ID: '',
nodeId: '',
eventCount: '',
bizStep: '',
allNodeInfo: [],
instanceIdentifierSyntax: ''
}
},
mounted () {
this.$nextTick(() => {
const id = this.$el.parentElement.parentElement.id
const data = this.$df.getNodeFromId(id.slice(5))
this.ID = data.data.ID
this.nodeId = data.data.nodeId
this.allNodeInfo = JSON.parse(JSON.stringify(this.$store.state.modules.ConfigureIdentifiersInfoStore.identifiersArray, null, 4))
const identifiersNode = this.allNodeInfo.find(node => node.identifiersId === this.nodeId)
this.instanceIdentifierSyntax = identifiersNode.identifierSyntax
console.log(JSON.stringify(this.allNodeInfo, null, 4))
})
},
methods: {
// On change of the IdentifierSyntax change, change the value in the respective node info
instanceIdentifiersSyntaxChange (syntaxValue) {
// Change the value of the respective syntax within the Node information in IdentifiersNode array
console.log(this.ID + " --- " + syntaxValue)
}
}
}
</script>
<style>
</style>
I know I am making some small mistake where I need to differentiate each Nodes V-model but nothing is clicking me. Can someone please help me.
You have to pass your node and your v-model also to your methods within this event.. like this, because for every loop in your v-for there will be a "new created node" which includes your v-model and than you can refer to this:
#change="instanceIdentifiersSyntaxChange('URN', node, identifierSyntax)"
In your methods you just do everything based on your node.identifierSyntax = HERE WHAT YOU WANT..
Hopefully I understood the question correct and helped you out!
EDIT: (Standard procedure)
Normally it looks like this when you add a single "input" to your v-for.
Template:
<div v-for="node in inputs" :key="node.id">
<input v-model="node.someDefinition" :value="node.someDefinition"/>
<div #change="another_Function(node, someDefinition)></div>
</div>
<button #click="add_new_Input>ADD</button>
Script:
data() {
return {
id: 0,
inputs: [{ //this is representing the first input when side will be loaded
id: this.id,
//your stuff in here as well
}]
}
}
methods: {
add_new_Input() {
this.inputs.push({
id: this.id += 1
})
}
}
This should be enough.. So in your template you have a v-for where your are looping over an array (or something else but in my case it's an array) with all inputs - be aware every input of me gets an unique ID when it will be created or added.
Also you have - in my case here - an input-tag where you can get the v-model or set the :value. It's binded to my unique node which I have created in my methods.
If you pass your #change you also have to pass the current node and the v-model / value that the correct one will be changed.
Hopefully it now helps you out!

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

Conditionally rendering icons only one time depending on property

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>

Show child of this parent click vue

I want to show the immediate child after clicking on its parent. Its easy on jquery, hard on vue, how can I do it?
template:
<boxes inline-template>
<div class="white-box" #click="toggleTick">Purchase only
<div v-if="showTick"><i class="fas fa-check"></i></div>
</div>
</boxes>
js
Vue.component('boxes', {
data: function () {
return {
showTick: false
}
},
methods: {
toggleTick () {
this.showTick = !this.showTick
}
}
})
var app = new Vue({
el: '#app',
data: {
}
})
At the moment I have multiple "white-box" div, it shows the child div for all of them, I just want to show the div for the clicked parent's child.
You should have behavior that you expect :
Vue.component('boxes', {
data: function () {
return {
showTick: false
}
}
})
var app = new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<boxes inline-template>
<div class="white-box" #click="showTick = !showTick">
<span>
Purchase only
</span>
<div v-if="showTick">Component 1</div>
</div>
</boxes>
<boxes inline-template>
<div class="white-box" #click="showTick = !showTick">
<span>
Purchase only
</span>
<div v-if="showTick">Component 2</div>
</div>
</boxes>
</div>
Your white-boxes are sharing the same showTick variable, so clicking on one fo them changes the value for all of them.
There are 2 available solutions to this:
Have multiple boxes components and not multiple white-boxes under the same boxes component. Something take ends up looking like this in the DOM
<boxes>...</boxes>
<boxes>...</boxes>
<boxes>...</boxes>
Use an array for showTick and use an index when calling toggleClick. Also note that for the changes in the array to be reactive you need to use Vue.set.
I recommend the former solution.

Vue.js Checkbox issue in Firefox, works file in Chrome and IE

I am using a checkbox.
<template v-for="(item,index) in items">
<div >
<input type="checkbox"
v-model="item.checked"
#click="selectionCheckboxClicked(index,item.checked)"
/>
</div>
.....
And this is the JS code
selectionCheckboxClicked: function selectionCheckboxClicked(index,checked) {
console.log(this.items[index].checked);
console.log(checked);
....
},
Initial value of item.checked is false. When I click the checkbox in Chrome or IE, it checks the checkbox and displays 'true' in console log. However, when I run the code in Firefox, though it does change the state, console log displays false in selectionCheckboxClicked(). I need to take some action based on the current state of the checkbox in selectionCheckboxClicked(), which I ma finding difficult to implement in the current situation.
Shall appreciate any suggestions to fix the issue.
Because for checkbox, v-model bind #change not #input (check Vue Github: source codes for v-model). Then #change will be fired after lose focus.
But you should not rely on the order either #click or #change will be executed first (check this answer for more details).
So one solution is uses #change=handler($event) instead, because v-model uses addHandler with one parameter named important=true to make sure it will be fired first than your event handler.
new Vue({
el: '#app',
data() {
return {
testValues: false
}
},
methods: {
selectionCheckboxClicked: function(ev) {
console.log(this.testValues);
console.log(ev.target.checked);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div>
<input type="checkbox" v-model="testValues" #change="selectionCheckboxClicked($event)" />
</div>
</div>
Or another solution is uses #input but you should rely on what is checked or not on the input Dom element.
new Vue({
el: '#app',
data() {
return {
testValues: false
}
},
methods: {
selectionCheckboxClicked: function(ev) {
console.log('v-model:', this.testValues);
console.log('inputCheckedAtDom:', ev.target.checked);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div>
<input type="checkbox" v-model="testValues" #input="selectionCheckboxClicked($event)" />
</div>
</div>
If you still like to use #click, one solution actually is same as #input. Uses one ref to access the input Dom element.
new Vue({
el: '#app',
data() {
return {
testValues: false
}
},
methods: {
selectionCheckboxClicked: function(ev) {
console.log('v-model:', this.testValues);
console.log('inputCheckedAtDom:', this.$refs.test.checked);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<div id="app">
<div>
<input ref="test" type="checkbox" v-model="testValues" #click="selectionCheckboxClicked($event)" />
</div>
</div>
setTimeout will work because it will be executed after current task finished and re-render. Even you uses setTimeout(()=>{}, 0)(delay 0 seconds), it will still work.
What seems to be happening is that Firefox calls the click function immediately even before the v-model = "item.checked" has changes. When I check the value of this.items[index].checked after some delay (say 100 ms), it displays true.
console.log(this.items[index].checked);
console.log(checked);
var self = this;
setTimeout(function()
{ if (self.items[index].checked)
self.selectedContactsCount++;
else
self.selectedContactsCount--;
},
100);