Vue wrapper example - vue.js

There are a good examples of integrating vue with select2.
I have a question.
If we look at this part of the code:
mounted: function () {
var vm = this
$(this.$el)
// init select2
.select2({ data: this.options })
.val(this.value)
.trigger('change')
// emit event on change.
.on('change', function () {
vm.$emit('input', this.value)
})
}
I don't understand, why when we change value of select2, this.value changes too.
I expected a record like:
.on('change', function () {
this.value = $(this.$el).val()
vm.$emit('input', this.value)
})

It behaves that way because of how v-model works. What you are a looking at is a 2-way binding. Where if the value of selected in v-model="selected" changes, the value will be pushed down to the component.
When vm.$emit('input', this.value) is called, it tells the parent to update whatever variable is listening to changes, in this case selected, which it turn gets pushed back to the component such that its value gets changed.
To make it simpler to understand, this is the sequence of events:
select2's value changes
the select2 value change triggers an event emission
the parent receives the event and updates selected
the component's value gets assigned to the new value of selected
the watcher for the value gets triggered and updates select2 with the new value
Good question though.
Caveat: Doing this is understandably poor practice. It will break it sad ways when used with 1-way bindings.

After writing my previous answer, I realized I missed something important: contexts.
On line 5 in the jsfiddle, there is this line:
var vm = this, why?
This is because later on, when doing vm.$emit, this.$emit will not work; the context has been changed.
.on('change', function () {
//this = select2 element
vm.value // == 1
this.value // == 2
vm.$emit("input", this.value);
})
The value on the emit event is not that of the component, but of the select2 element.
While the value has not yet been changed on the component, the new value has been broadcasted to the parent.

Notice how select2 component used in the template:
<select2 :options="options" v-model="selected">
<option disabled value="0">Select one</option>
</select2>
In Vue.js using v-model with components could be simplified to:
<your-component
:value="selected"
#input="value => { selected = value }">
</your-component>
So every time you emit an event from your component the value gets changed at the parent component.
Source

Related

Vue3 child component does not recreating, why?

I have made some sandbox code of my problem here:
https://codesandbox.io/s/clever-zeh-kdff1z
<template>
<div v-if="started">
<HelloWorld :msg="msg" #exit="exit" #remake="remake" />
</div>
<button v-if="!started" #click="started = !started">start</button>
</template>
<script>
import HelloWorldVue from "./components/HelloWorld.vue";
export default {
name: "App",
components: {
HelloWorld: HelloWorldVue,
},
data() {
return {
started: false,
msg: "Hello Vue 3 in CodeSandbox!",
};
},
methods: {
exit() {
this.started = false;
},
remake() {
this.msg = this.msg + 1;
//this code should recreate our child but...
this.exit();
this.started = true;
// setTimeout(() => {
// this.started = true;
// });
},
},
};
</script>
So! We have 2 components parent and child. The idea is simple - we have a flag variable in our parent. We have a v-if statement for this - hide / show an element depend on the flag value "false" or "true". After we toggle the flag - the child component should be recreated. This is the idea. Simple.
In our parent we have a button which will set the flag variable to "true" and our child will be created and will appear on our page.
Ok. Now we have 2 buttons inside our child.
One button is "exit" which is emit an event so the flag variable of parent will set to "false" and the elemint will disappear from our page(It will be destroyed btw). Works as charm. Ok.
The second button "remake". It emit event so the flag variable will be just toggled (off then on). Simple. We set to "false", we set to "true". So the current child should dissapear, and then imediatly will be created new one.
But here we are facing the problem! Ok, current child is still here, there is no any recreation, it just updates current one... So in child I have checked our lifecycle hooks - created and unmounted via console.log function. And the second button dont trigger them. Start->Exit->Start != Start->Remake.
So can anyone please explain me why this is happening? I cant figure it out.
Interesting thing, if you can see there is some asynchronous code commented in my demo. If we set our flag to "true" inside the async function the child will be recreated and we will see the created hook message but it seems like crutch. We also can add a :key to our component and update it to force rerender, but it also seems like a crutch.
Any explanations on this topic how things work would be nice.
Vue re-uses elements and components whenever it can. It will also only rerender once per tick. The length of a 'tick' is not something you should worry yourself about too much, other than that it exists. In your case the this.exit() and this.started = true statements are executed within the same tick. The data stored in this.started is both true in the last tick and the current tick as it does not end the tick in between the statements, and so nothing happens to your component.
In general you should think in states in Vue rather than in lifecycles. Or in other words: What are the different situations this component must be able to handle and how do you switch between those states. Rather than determining what to do in which point in time. Using :key="keyName" is indeed generally a crutch, as is using import { nextTick } from 'vue'; and using that to get some cadence of states to happen, as is using a setTimeout to get some code to execute after the current tick. The nasty part of setTimeout is also that it can execute code on a component that is already destroyed. It can sometimes help with animations though.
In my experience when people try to use lifecycle hooks they would rather have something happen when one of the props change. For example when a prop id on the child component changes you want to load data from the api to populate some fields. To get this to work use an immediate watcher instead:
watch: {
id: {
handler(newId, oldId) {
this.populateFromApi(newId);
},
immediate: true
}
}
Now it will call the watcher on component creation, and call it afterwards when you pass a different id. It will also help you gracefully handle cases where the component is created with a undefined or null value in one of the props you expect. Instead of throwing an error you just render nothing until the prop is valid.

How to get Vuetify checkbox event.target.checked and event.target.value?

How to get Vuetify checkbox event.target.checked and event.target.value?
I'm using Vuetify's checkbox in my app. When the user checks/unchecks it, I want to get the checked and the value properties.
I realize that it is not a native checkbox element, so I don't have access to event.target.checked or event.target.value, but surely there must be a way to do that. I tried the following:
<p v-for="(linkType, index) in linkTypes" v-if='linksLoaded'>
<v-checkbox
color="info"
:label="linkType"
:value="linkType"
v-model="checkedLinks"
#click="onCheckboxClicked($event)"></v-checkbox>
</p>
...
onCheckboxClicked: function(e) {
console.log(e);
},
For some reason it printed a mouse event TWICE and the checkbox itself didn't change (the check mark wasn't unchecked).
#click.native printed the same mouse event, but once.
I tried #change="onCheckboxClicked" - that printed the v-model.
So is there a way to do that?
I see that you are looping without binding a key, and inside you have v-model which is hooked to a single variable. So, whenever some checkbox is changed all others will update simultaneously. So you need new v-model for each checkbox. Why not add another property in linkTypes so you can v-model="linkType.checked".
change is the name of the event which gets triggered whenever checkbox value is changed and value is passed as a parameter.
<v-checkbox
#change="checkboxUpdated"
color="info"
:label="linkType"
:value="linkType"
v-model="checkedLinks"
#click="onCheckboxClicked($event)"></v-checkbox>
and in methods you need
checkboxUpdated(newValue){
console.log(newValue)
}
The easy way to access whether the checkbox is checked or not after clicking is to check it value. If it is null or empty array, then you can assume that its not checked. It depends on how you initialised the variable. This is the safest way to get what you want.
<v-checkbox
v-for="(el, index) in checkboxes"
:key="index"
v-model="checkbox[index]"
:value="el"
:label="`Checkbox ${index}`"
#change="valueChanged($event, index)"
></v-checkbox>
new Vue({
el: '#app',
data () {
return {
checkboxes: ['Opt 1', 'Opt 2', 'Opt 3', 'Opt 4'],
checkbox: [],
}
},
methods: {
onChange(val, i) {
console.log(val, i, this.checkbox)
if (val === null || val.length === 0) { // Custom checks in this
console.log('Unchecked')
} else {
console.log('Checked')
}
}
}
})
If you really need access to the element, then you can use ref to get the component. Then try to find the input element inside the component. And then find the checked value of that input element. But depending on how the library is implemented, you might not get the right value for $refs.checkbox.$el.target.checked.
You are lookin for a event. If you want to know if your checkbox is checked or not, you should use this:
onCheckboxClicked: function(e) {
console.log(e.target.checked)
},
As you've already noticed, Vuetify checkbox is not a native checkbox element. Therefore, event.target.checked and event.target.value do not exist. To fix this, one needs to do 2 things:
Disable the ripple effect of the v-checkbox. Otherwise, there will be a div on top of the input checkbox tag. Then, event.target is the one we expected.
However, when the user clicks on the label, it also affects the checkbox. In this case, we need to access the checkbox via event.target.control.
The checkbox in your question should like this:
<v-checkbox
color="info"
:ripple="false"
:label="linkType"
:value="linkType"
v-model="checkedLinks"
#click.native="onCheckboxClicked"
/>
Then, in the onCheckboxClicked method:
const onCheckboxClicked = (event) => {
const target = event.target.control ?? event.target;
const isChecked = target.checked;
const value = target.value;
// Do something here...
};
Notice that we use .native modifier for the click event. Otherwise, the event.target.control.checked will give opposite values (false when the checkbox is checked and vice versa).
And small note: you should always bind the key value when using v-for.

v-select on close event or remove validation error timer?

Final Edit/Solution: https://jsfiddle.net/up9xkhsm/1/
Is there such an event for v-select that I can key on when it is closed? Or some sort of a 'timer' I can set to remove validation errors after they occur?
This is the v-select I am using:
https://vuetifyjs.com/en/components/selects
Edit: this outlines the issue:
https://jsfiddle.net/96vnLm7g/
I want to know when a user clicked on the v-select but did not select anything. It has to be possible, obviously, since the validation can pick up on this..
Use an onChange prop to add your callback function, so you can check for the v-model assigned to v-select if has changed, to clear the validation errors. Or watch the v-model assigned to v-select for changes.
Using onChange:
<v-select :options="options" :on-change="cleanUpValidation" v-model="selectModel" name="some-select"></v-select>
And in VueJS
methods: {
cleanUpValidation(){
//do the cleanup
}
}
By default, onChange emits input event with value of selected option:
default: function (val) {
this.$emit('input', val)
}
So you can use it also to catch the input event:
<v-select :options="options" #input="cleanUpValidation" v-model="selectModel" name="some-select"></v-select>
In VueJS
methods: {
cleanUpValidation(val){
//do something with selected option value or cleanup error
}
}
Or you can watch the model assigned to v-select:
watch: {
'selectModel' : function(){
//do the cleanup or something with this.selectModel
}
}
For onChange and other props see:
https://sagalbot.github.io/vue-select/docs/Api/Props.html
Same thing would apply for VuetifyJS's v-select.
Edit:
Main goal was to clear validation errors when v-select is actually clicked.
v-select uses focus event within its onClick() method, to tell the VueJS that component is clicked, so that can be used to catch the click event:
<v-select
#input="inputChanged"
v-on:change="changeChanged"
label="Select Item"
:items="myItems"
required
:rules="rules.requiredField"
#focus="focusChanged"
>
</v-select>
And in js:
methods:{
focusChanged(){
console.log('focusChanged ');
},
}
For last example: https://jsfiddle.net/c5moqweu/
And see https://github.com/vuetifyjs/vuetify/blob/master/packages/vuetify/src/components/VSelect/VSelect.js
onClick
Method

Why doesn't Vue run/update a computed property with a reference to this inside a filter function?

I have a select box connected to a Vue computed property. I'm wondering why one of my computed property attempts works and the other doesn't.
<select>
<option v-for="option in filteredItems">{{option.description}}</option>
</select>
filteredItems is a computed property. This code works:
vInstance = new Vue({
...
computed: {
filteredItems: function(){
let someID = this.filterID;
return this.allItems.filter(function(item){
return item.id === someID;
})
}
}
})
This version does not
computed: {
filteredItems: function(){
return this.allItems.filter(function(item){
return item.id === this.filterID;
})
}
}
The two functions are almost identical, other than that the first version sets this.filterID to a different varaible to be used in the filter. Why does that work, and the other doesn't?
It's nothing related with Vue itself. It's how this works in JS. In the second code block, this will probably the window and therefore this.filterID will be undefined. However in the first code example, this will be Vue instance therefore this.filterID will be defined. Take a look at this link to read more about this scope in JS.

How to trigger event in all sibling components except current component in vuejs?

I have a reusable component that does inline editing for data.
So a page has 10 fields that can be edited inline,
<editfield :value="field" v-for="field in fieldslist"></editfield>
Each of them have a data field called "editing" that sets as true or false as the user clicks on it. Everytime a field is set to editing an event editing-another-field is emitted using event bus.
edit(){
this.editing = true;
EventBus.$emit('editing-another-field');
}
I added the event listener when the component is created
created(){
EventBus.$on('editing-another-field', ()=>{ this.editing = false;});
}
The problem I am facing is it is triggering the event even in the currennt component being edited.
How can I mention that updated value of editing in all the other sibling components except the current component.
Why not pass the current component as an event argument and use that to check if the event originated from this component or another one.
edit() {
this.editing = true;
EventBus.$emit('editing-another-field', this);
}
created() {
EventBus.$on('editing-another-field', source => {
if (source !== this) {
this.editing = false;
}
});
}
Or you can do it like this (it is important to unregister the event listener when the component is destroyed to avoid a memory leak):
edit() {
EventBus.$emit('editing-field', this);
}
created() {
this.editingFieldHandler = vm => {
this.editing = vm === this;
};
EventBus.$on('editing-field', this.editingFieldHandler);
}
destroyed() {
EventBus.$off('editing-field', this.editingFieldHandler);
}
Otherwise you can emit the event first and then set this.editing to true.
Are you sure you want an event bus? This brings up bad memories of JQuery ;-) I think it would be cleaner to limit yourself to a tree of parents and children. Thinking MVVM, formLockedBy is a perfectly valid and sensible property to store on the parent and pass to the children.
The solution below, running here, shows a form with two fields. The fields are both instances of modal-component. The parent manages the formLockedBy property. The child fields look at this property to know to disable themselves. When the user starts typing in a field, the field emits an editing event and formLockedBy gets set. Similarly, when a field emits a save or cancel event, the parent clears formLockedBy and the other input(s) spring back to life.
Note the advantages...
Only the parent listens for events.
The identifier stored in formLockedBy is just the string name of the field. This is much safer than passing and storing a reference to the Vue component. If you don't like this, you might consider adding a safe id to the object proto.
No surprises. The full list of events the parent will react to is declared in the tag instantiating the child. The child specifies in props everything it needs from the parent.
HTML
<div id="example">
<modal-input name='First Name'
:form-locked-by='this.formLockedBy'
v-on:save='formLockedBy = null'
v-on:cancel='formLockedBy = null'
v-on:editing='fieldActive'
></modal-input>
<modal-input name='Address'
:form-locked-by='this.formLockedBy'
v-on:save='formLockedBy = null'
v-on:cancel='formLockedBy = null'
v-on:editing='fieldActive'
></modal-input>
</div>
JS
Vue.component('modal-input', {
template: `<div>
{{name}} :
<input :name='name' type="text" v-on:keydown="active" :disabled="formLockedBy && formLockedBy != name"/>
<span v-if="editing && formLockedBy == name">
<input type="button" value="Save" v-on:click="$emit('save');editing=false;"></input>
<input type="button" value="Cancel" v-on:click="$emit('cancel');editing=false;"></input>
</span>
</div>`,
data : function(){
return {editing:false};
},
props: ['name','formLockedBy'],
methods : {
active : function(event){
if(!this.editing){
this.editing = true;
this.$emit('editing',{field:this.name})
}
return true;
}
}
});
// create a root instance
new Vue({
el: '#example',
data: {
formLockedBy : null
},
methods : {
fieldActive : function(args){
this.formLockedBy = args.field;
}
}
})