2 way binding is not working properly in vuejs - vue.js

I have defined a vuejs component this way:
<template>
<form #submit.prevent="submit">
<textarea id="content" cols="30" rows="10" v-model="content">{{ content }}</textarea>
<button class="btn btn-lg btn-success" type="submit" #click="send()">
Send content
</button>
</form>
</template>
<script>
export default {
data() {
return {
content: '// Initial content'
}
},
methods: {
send() {
console.log('Content', this.content);
},
submit() {
return false;
}
},
mounted() {
console.log('Template init ', this.content);
}
}
</script>
When I click on send, the send method outputs the content of the textarea as expected. But when I change the content of the textarea with jquery:
$('#content').val(content);
and hit send, it doesn't update content in the template. "Send" outputs the old value.
can somebody please explain to me what's wrong here?

v-model is listening for an input event to trigger changing the value of its bound variable.
From the vue.js documentation:
v-model internally uses different properties and emits different
events for different input elements:
text and textarea elements use value property and input event;
checkboxes and radiobuttons use checked property and change event;
select fields use value as a prop and change as an event.
Using the JQuery val() method to select the element and change the value does not trigger an event that v-model is listening for, so the value of your bound variable does not change/update.
If you absolutely have to use JQuery to change the content, you could manually trigger an event that might also trigger the v-model binding to update:
$('#content').val(content).trigger('input');

Related

Vue.js this.$refs empty due to v-if

I have a simple Vue component that displays an address, but converts into a form to edit the address if the user clicks a button. The address field is an autocomplete using Google Maps API. Because the field is hidden (actually nonexistent) half the time, I have to re-instantiate the autocomplete each time the field is shown.
<template>
<div>
<div v-if="editing">
<div><input ref="autocomplete" v-model="address"></div>
<button #click="save">Save</button>
</div>
<div v-else>
<p>{{ address }}</p>
<button #click="edit">Edit</button>
</div>
</div>
</template>
<script>
export default {
data() {
editing: false,
address: ""
},
methods: {
edit() {
this.editing = true;
this.initAutocomplete();
},
save() {
this.editing = false;
}
initAutocomplete() {
this.autocomplete = new google.maps.places.Autocomplete(this.$refs.autocomplete, {});
}
},
mounted() {
this.initAutocomplete();
}
}
I was getting errors that the autocomplete reference was not a valid HTMLInputElement, and when I did console.log(this.$refs) it only produced {} even though the input field was clearly present on screen. I then realized it was trying to reference a nonexistent field, so I then tried to confine the autocomplete init to only when the input field should be visible via v-if. Even with this, initAutocomplete() is still giving errors trying to reference a nonexistent field.
How can I ensure that the reference exists first?
Maybe a solution would be to use $nextTick which will wait for your DOM to rerender.
So your code would look like :
edit() {
this.editing = true;
this.$nextTick(() => { this.initAutocomplete(); });
},
Moreover if you try to use your this.initAutocomplete(); during mounting it cannot work since the $refs.autocomplete is not existing yet but I'm not sure you need it since your v-model is already empty.
I think it's because your "refs" is plural
<input refs="autocomplete" v-model="address">
It should be:
<input ref="autocomplete" v-model="address">

What is the correct way to retrieve data from 2 or more identical components?

Evening. I've created a button which adds a component that has an input field inside. I might need to press that button few times so there would be 2-3 input fields that appear. Whenever I type the text I would like to send a request from the parent component but I don't know how to retrieve the data from every child component that has been created. Is this the time to start using vuex (never used it)?
ParentComponent.vue
<template>
<div>
<button class="btn btn-success" #click="addStep">Add step</button>
<div v-for="i in count">
<recipe-step v-bind:step-number="i"></recipe-step>
</div>
</div>
</template>
<script>
export default {
data() {
return {
count: 0
}
},
methods: {
addStep() {
this.count += 1;
}
}
}
</script>
StepComponent.vue
<template>
<div>
<div class="from-group">
<label for="step-input"></label>
<input id="step-input" v-model="text" type="text">
</div>
</div>
</template>
<script>
export default {
props: {
stepNumber: {
type: Number,
required: true
}
},
data() {
return {
step: this.stepNumber,
text: ""
}
}
}
</script>
No, you really don't need Vuex yet. As long as you are still dealing with parent-child-component communication, you should be fine. Vuex comes into play when components, spread across the hole component hierarchy, need to exchange information.
Now, you should do something like this:
Don't store the text in the child component. When the input changes, send a Custom Event right to the parent component. Note that
<input v-model="text">
is only syntax sugar for
<input :value="text" #input="text = $event">
Both have the same effect. That's way you can send the input event up to the parent, like this:
<input #input="$emit('input', $event)">
Add another prop to your child component called value which should replace text.
And now you can use v-model in the parent component:
<recipe-step v-model="text">
To store multiple values, just use an array in your data properties.
<recipe-step v-model="textArray[i]">
Vuex can help you on that, however if all you want is to get the input text value back to the parent with the minimum effort you can create a prop called value in the children and then pass it as v-model in the parent.
Since you have a v-for you could make it iterate over a list instead a counter and then pass some prop inside each item as v-model

Modal form Validation errors persist when reopened

About the problem
I am using Laravel 5.6.7, Vue.js. I have modal div which being opened and closed on button click. I type something. Validation fires. I close the modal div. Then clicking button to open it. I see that the validation messages still there.
Component Template
<template>
<div>
<form role="form">
<input name="LastName" type="text" ref="LastName" v-validate
data-vv-rules="required" v-model="createForm.LastName">
<p v-if="errors.has('LastName')">{{ errors.first('LastName') }}</p>
<button v-else type="button" #click="validateBeforeSubmit()">
Create
</button>
</form>
</div>
</template>
Component Script
<script>
export default {
data() {
return {
createForm: {
LastName: ''
}
};
},
created() {
this.InitializeForm();
},
methods: {
InitializeForm() {
this.createForm.LastName = "";
},
validateBeforeSubmit() {
this.$validator.validateAll();
}
}
}
</script>
My findings
if you check the input type text above, I added ref attribute. Tried the below code to set the value to false for aria-invalid attribute.
this.$refs.LastName.setAttribute("aria-invalid", "false");
It sets the attribute value but validation errors are still there. Is there any proper way to get rid of workarounds like above?
I think, when I set the first value or I click on it...some attribute value is being set and due to that form errors occur.
Assuming that you are using vee-validate,
To clear all errors,
this.$validator.errors.clear();
To clear 1 single error only,
this.$validator.errors.remove('LastName');
Add 1 of the code above to the modal close event listener and the error would be gone the next time you opened it..

How to get the value of button when clicked using vue.js

I have several buttons on a page which are hooked to the same method webcamSendRequestButton
<button v-on:click="webcamSendRequestButton" value="0" type="button" class="webcam-send-request-button" :disabled="disabled">Verify</button>
<button v-on:click="webcamSendRequestButton" value="1" type="button" class="webcam-send-request-button" :disabled="disabled">Verify</button>
<button v-on:click="webcamSendRequestButton" value="2" type="button" class="webcam-send-request-button" :disabled="disabled">Verify</button>
<button v-on:click="webcamSendRequestButton" value="3" type="button" class="webcam-send-request-button" :disabled="disabled">Verify</button>
and I am making an ajax call when the button is clicked. In jquery or JS it is pretty straightforward to get the value of button when clicked using $(this).val();
How do I fetch the value of the button when it is clicked in vue?
var app = new Vue({
el: '#my-div',
methods: {
webcamSendRequestButton: function() {
const buttonValue = ??? // How do I fetch the value over here
$.ajax({
url: "someurl",
type: "POST",
data: {
value: buttonValue
},
success: function (data) {
// Omitted for brevity
}
});
}
}
});
A better answer than the previous ones I believe is to inject the original DOM '$event' variable into your handler call:
v-on:click="webcamSendRequestButton($event)"
And receive it in your handler:
methods: {
webcamSendRequestButton: function(e) {
const buttonValue = e.target.value;
.
.
.
}
}
This is better for two well-connected reasons.
First, the code now has the same reasoning behind the initial intention: When a button is clicked, the handler should be able to read the value of the button that initiated the event. Other solutions that pass the value statically to the handler only mimic this, thus being more or less a hack.
Second, because the code now exactly matches the initial intention, the code becomes more maintainable. For example, if the value of each button gets to change dynamically by being bound to a variable value instead of a static value, the handler needs not to be changed at all. If the buttons grow or shrink in number, there is no need to change the handler code or define extra references or change the syntax of the handler call.
You can simply give it as an argument to the function.
<button v-on:click="webcamSendRequestButton(0)" type="button" class="webcam-send-request-button" :disabled="disabled">Verify</button>
JS
...
methods: {
webcamSendRequestButton(value){
//"value" is the clicked button value
}
}
...
You can give your button a reference that Vue can access by giving it a tag ref="yourRef". Then you can access the value inside of your called function with this.$refs.yourRef.value.
Works for other Input elements as well ;)
I have a button group, and three buttons in it. I wanted to get the string from each button button when there is a change, and change my attribute to that string value when there is a change in button. I used #change event of vuejs and it worked for me. I will be pleased if it will be helpful for someone else too.
In template:
<div class="btn-group">
<button
type="button"
class="btn btn-md light btn__border"
v-on:change="latestType = 'week'"
>Week</button>
<button
type="button"
class="btn btn-md light btn__border"
v-on:change="latestType = 'month'"
>Month</button>
<button
type="button"
class="btn btn-md light btn__border"
v-on:change="latestType = ''"
>All</button>
</div>
In Js (script tag):
...
data() {
return {
latestType:''
}
}
...
One improvement over the accepted answer by reinarg is to access the button's value directly using event.srcElement. This way you don't have to pass an argument into webcamSendRequestButton in the template. In fact, the OP wouldn't have to change the template at all and would have to change only one thing in the method:
webcamSendRequestButton: function() {
const buttonValue = event.srcElement.value // This provides the button's value
$.ajax({
url: "someurl",
type: "POST",
data: {
value: buttonValue
},
success: function (data) {
// Omitted for brevity
}
});
}
This also avoids having to enter the same value in two places for each button (once in the value attribute and again as an argument for the webcamSendRequestButton method).

Is it possible to access event listeners from component in Vue.js 2

I have a custom component in Vue.js(2) as:
Vue.component("modal-panel", {
props: {
id: {
required: true
},
title: {} ,
size: {
validator: function(value) {
return !value || value=="lg" || value=="sm";
}
},
confirmLabel: {
"default": "Yes",
},
closeLabel: {
"default": "No"
},
confirm: {
type: Function
}
},
//...
template: `
//...
<button type="button" class="btn btn-primary confirm" data-dismiss="modal" v-on:click="$emit('confirm')" v-if="confirm">{{confirmLabel}}</button>
//...
`
}
And this is the code using component
<modal-panel title="New User" id="userModal" #confirm="doSomething">...</modal-panel>
As seen from the component code, confirm has been inserted into the props and on the button code in the template there is a conditional rendering according to whether confirm listener attached or not. However, button is not rendered. I checked component dom and properties, but there is not such an info.
Is it possible to make conditional rendering according to whether a specific listener attached to component in vue.js?
Thanks.
Since Vue 2.4, Vue components have a $listeners property.
Contains parent-scope v-on event listeners (without .native
modifiers).
This is documented here. You can determine whether or not the parent is listening to a particular event by examining the $listeners property.
**Original Answer**
It's not generally good practice for a component to reach out of itself to determine things.
I would recommend instead that you have a confirm callback property. You can pass a function in as a property. Then you can decide to show/hide the button on whether you received the callback or not.
Vue.component("modal",{
props:["confirm"],
template: `
<div>
<h1>Modal</h1>
<button v-if="confirm" #click="confirm">Confirm</button>
</div>
`
})
Example.
Edit
You can determine if there is a handler defined on a component for a given event, but it requires examining an internal Vue property, and you should only use this at your own risk.
Vue.component("modal",{
template: `
<div>
<h1>Modal</h1>
<button v-if="hasConfirmHandler" #click="$emit('confirm')">Confirm</button>
</div>
`,
computed:{
hasConfirmHandler(){
return !!this._events["confirm"]
}
}
})
The _events property of the component will contain the handler, if a handler is defined from the parent.
Example.
You need to bind your function with v-bind or : instead of just passing it as a string. So use :confirm syntax:
<modal-panel title="New User" id="userModal" :confirm="doSomething"></modal-panel>
Then in component template simply use v-on:click="confirm()":
<button type="button" class="btn btn-primary confirm" data-dismiss="modal"
v-on:click="confirm()"
v-if="confirm">
{{confirmLabel}}
</button>