Vue component $emit firing twice - javascript

I'm recently trying to reutilize my Vue components in some real-world application to remove unnecessary duplicates and clutter with <divs>.
But I'm having trouble in doing so. After hours I managed to "accomplish" it, but now the event fires twice and I don't know exactly why.
I've made a basic setup to show the problem:
Vue.component("bs-select",{
template:
`<div class="form-group">
<label class="control-label">{{ label }}</label>
<select2
ref="select2control"
:options="options"
:value="value"
#input="chosenOption"
></select2>
</div>`,
props: ["value", "label", "options"],
methods: {
chosenOption(val) {
this.$emit("input", val);
}
}
});
Vue.component("select2",{
template:
`<select :value="value" style="width: 100%">
<option value="" selected disabled>Choose...</option>
<option v-if="!options">{{ value }}</option>
<option v-for="option in options" :value="option.value">{{ option.text }}</option>
</select>`,
props: ["value", "options"],
mounted: function() {
const vm = this;
$(vm.$el)
.select2()
.on("change", function() {
console.log("CHANGE", vm.$el.value);
vm.$emit("input", vm.$el.value);
});
},
watch: {
value: function(val) {
$(this.$el)
.val(val)
.trigger("change");
}
}
});
new Vue({
el: "#app",
data: {
test: "bug",
options: [
{
value: "hello",
text: "Hello"
},
{
value: "bug",
text: "Bug"
}
]
}
})
* {
font-family: Arial;
font-size: 10px;
}
div {
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/js/select2.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/css/select2.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="app">
<bs-select v-model="test" :options="options"></bs-select>
<br><br>
<button #click="test = 'bug'">
Set 'test' variable to 'bug' (Two-way check)
</button>
{{ test }}
</div>
<div>
Event is firing twice in console...
</div>
I also Googled a lot and came to no conclusion on why this happens and/or how to fix this issue.
Any help is greatly appreciated.

After asking some of my friends, one figured it out that the "change" trigger must be in beforeUpdate.
So, the solved code looks like this:
Vue.component("bs-select",{
template:
`<div class="form-group">
<label class="control-label">{{ label }}</label>
<select2
ref="select2control"
:options="options"
:value="value"
#input="chosenOption"
></select2>
</div>`,
props: ["value", "label", "options"],
methods: {
chosenOption(val) {
this.$emit("input", val);
}
}
});
Vue.component("select2",{
template:
`<select :value="value" style="width: 100%">
<option value="" selected disabled>Choose...</option>
<option v-if="!options">{{ value }}</option>
<option v-for="option in options" :value="option.value">{{ option.text }}</option>
</select>`,
props: ["value", "options"],
mounted: function() {
const vm = this;
$(vm.$el)
.select2()
.on("change", function() {
console.log("CHANGE", vm.$el.value);
vm.$emit("input", vm.$el.value);
});
},
beforeUpdate() {
$(this.$el).val(this.value).trigger('change.select2')
}
});
new Vue({
el: "#app",
data: {
test: "hello",
options: [
{
value: "hello",
text: "Hello"
},
{
value: "solved",
text: "Solved"
}
]
}
})
* {
font-family: Arial;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/js/select2.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.5/css/select2.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script></script>
<div id="app">
<bs-select v-model="test" :options="options"></bs-select>
<br><br>
{{ test }}
<br><br>
<button #click="test = 'solved'">
Set 'test' variable to 'solved'
</button>
</div>
It works quite nice, but he also suggested me to use this approach, which is a lot cleaner. I am currently using that now, but I leave the original answer to the question too, in case someone needs it.

I'm not sure what exactly you are trying to do here, but my guess is you were firing the onchange event twice, once when it actually changed and once in the watcher. Anyways you don't really need to use listeners like that when there are vue solutions available like this:
<div id="app">
<bs-select :value="test" v-on:change-value="test = $event" :options="options"></bs-select>
<br><br>
{{ test }}
<br><br>
<button #click="test = 'bug'">
Set 'test' variable to 'bug'
</button>
</div>
<div>
Event is firing twice in console...
</div>
component:
Vue.component("bs-select",{
template:
`<select :value="value" v-on:change="changeVal" style="width: 100%">
<option value="" selected disabled>Choose...</option>
<option v-if="!options">{{ value }}</option>
<option v-for="option in options" :value="option.value">{{ option.text }}</option>
</select>`,
props: ["value", "options"],
methods: {
changeVal: function(event) {
this.$emit('change-value', event.target.value)
}
}
});
new Vue({
el: "#app",
data: {
test: "bug",
options: [
{
value: "hello",
text: "Hello"
},
{
value: "bug",
text: "Bug"
}
]
}
})
https://jsfiddle.net/kv3bq1dw/

For anyone coming across this now, this is the proper way to get it to fire only once.
<template>
<select><slot></slot></select>
</template>
<script>
module.exports = {
props: ['options', 'value'],
mounted: function () {
console.log(this.options);
var vm = this;
$(this.$el)
// init select2
.select2({
data: this.options,
theme: 'bootstrap',
width: '100%',
placeholder: 'Select',
allowClear: true
})
.val(this.value)
.trigger('change')
// emit event on change.
.on('select2:select', function () { // bind to `select2:select` event
vm.$emit('input', this.value)
});
},
watch: {
value: function (value) {
// update value
$(this.$el)
.val(value)
.trigger('change');
},
options: function (options) {
// update options
$(this.$el).empty().select2({ data: options })
}
},
destroyed: function () {
$(this.$el).off().select2('destroy')
}
}
</script>

Related

Pass selected option <b-form-select> to axis post from child to parent

I created a form using Vue.js. All my input fields are getting passed but I can't get the value in the <b-form-select> to pass. How do I pass the value of the selected option in <b-form-select> to parent?
child:
<label>
for="source"
class="inline-3-columns--camp-wizard"
>
<span class="title__field">Network</span>
<b-form-select
id="source"
v-model="networkAudience.source"
data-vv-as="source"
name="source"
value-field="value"
text-field="label"
:options="sources"
/>
<span
class="title__field"
data-vv-as="source"
>
{{ networkAudience.source }}
<input
v-model="networkAudience.source"
name="source"
type="hidden"
data-vv-as="source"
>
</span>
</label>
Here is my script:
data()
{
return {
selectedclientId: null,
networkAudience: {
source: '',
name: '',
},
sources: [
{value: "Group1", label:"Group1"},
{value: "Group2", label:"Group2"}],
};
},
methods: {
async submit()
{
axios.post(projConfig.apiRoot + `/s/audiences/${this.audienceId}/network-audiences/`, this.networkAudience)
.then(response =>
{
this.networkAudiences = response.data;
console.log(response);
this.$emit('on-success');
})
.catch(error =>
{
console.error(error);
});
},
}
Parent:
<transition
name="fade"
#after-leave="VueEvent.$emit('close-preview-panel')"
>
<preview-panel
v-if="showAddNetwork"
:content-classes="'panel__content--camp-creation mw-1150'"
:title="'Add Network Audience'"
#close-preview="showAddNetwork = false"
>
<template #content>
<AddNetworkAudience
:audience-id="currentAudId"
#on-cancel="showAddNetwork = false"
#on-success="handleAddNetworkAudienceSuccess"
/>
</template>
</preview-panel>
</transition>
I've tried changing my submit method but it isn't working.
I get the following errors after submitting the form:
error in network
enter in console
I am not sure what issue you are facing but I created a snippet and it is working fine as per the expectation. Can you please have a look in the below code snippet and let me know what issue you are facing.
new Vue({
el: '#app',
data: {
networkAudience: {
source: '',
name: '',
},
sources: [
{
value: "Group1", label:"Group1"
}, {
value: "Group2", label:"Group2"
}
]
},
methods: {
submitForm() {
console.log(this.networkAudience);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>
<script src="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.js"></script>
<link rel="stylesheet" href="https://unpkg.com/bootstrap-vue#2.21.2/dist/bootstrap-vue.css"/>
<div id="app">
<b-form-select v-model="networkAudience.source"
:options="sources"
value-field="value"
text-field="label"
></b-form-select>
<div>Selected Option: <strong>{{ networkAudience.source }}</strong></div>
<button type="submit" #click="submitForm()">Submit</button>
</div>

Vue.js : Method not firing in parent component after emit

A child component emits an event on input change with a payload:
Vue.component('gum-option', {
inheritAttrs: false,
props: ['label'],
template: `
<label class="gum-option">
<input
type="radio"
v-bind="$attrs"
v-on:change="$emit('update', this.label)">
<span>{{ label }}</span>
</label>
`
});
Its parent component listens to this event in order to fire a method that throws an alert:
Vue.component('gum-select', {
template: `
<div v-on:update="updateValue">
<label>{{label}}</label>
<div class="selected">
{{selectedOption}}
</div>
<slot v-bind="options"></slot>
</div>
`,
data: function () {
return {
selectedOption: ''
}
},
props:['options', 'name', 'label'],
methods: {
updateValue: function(event) { // method invoked by the "update" event
alert(event); // Not firing ! Why
}
}
});
You can find the bug on this pen:
https://codepen.io/bourpie/pen/JjXdjzg?editors=1011
The listener should be put on the gum-option component in App, as well as the method updateValue. There were some other mistakes so this is the fixed demo:
<div id="myApp">
<gum-select name="options" label="Options" v-bind:options="options" >
<gum-option
v-for="option in options"
v-bind:key="option.value"
v-bind:value="option.value"
v-bind:label="option.texte"
name="province"
v-on:update="updateValue">
</gum-option>
</gum-select>
</div>
Vue.component('gum-option', {
inheritAttrs: false,
props: ['label'],
template: `
<label class="gum-option">
<input
type="radio"
v-bind="$attrs"
v-on:change="$emit('update', label)">
<span>{{ label }}</span>
</label>
`
});
Vue.component('gum-select', {
template: `
<div>
<label>{{label}}</label>
<div class="selected">
{{selectedOption}}
</div>
<slot v-bind="options"></slot>
</div>
`,
data: function () {
return {
selectedOption: ''
}
},
props:['options', 'name', 'label'],
});
new Vue({
el: '#myApp',
data: {
options: [
{value: '1', texte: 'Option 1'},
{value: '2', texte: 'Option 2'},
{value: '3', texte: 'Option 3'}
]
},
methods: {
updateValue: function(label) {
console.log(label);
}
}
});

Vue js v-if condicional rendering on a shared toggle button

//PARENT COMPONENT
<template>
....
<div class="input-wrapper">//Toggle button container
<label class="input-label">
SELECT YES OR NOT
</label>
<toggle //child component, toggle button
:options="shipping"
/>
</div>
<div
v-if="destiny[0].value"
class="input-wrapper">
<label class="input-label">
IF YES THIS CONTAINER WILL BE DISPLAYED
</label>
<toggle
:options="Options"
/>
</div>
.....
</template>
<script>
import Toggle from "....";
export default {
components: {
Toggle,
},
data: function () {
return {
destiny: [{
label: 'Yes',
value: true
},
{
label: 'No',
value: false
}
],
Options: [{
label: 'A',
value: 'a'
},
{
label: 'B',
value: 'b'
},
{
label: 'C',
value: 'c'
}]
}
}
}
</script>
///CHILD COMPONENT
<template>
<div class="toggle">
<button
v-for="option in options"
:key="option.value"
:class="{
active: option.value === value
}"
class="btn"
#click="() => toggleHandler(option.value)">{{ option.label }} .
</button>
</div>
</template>
<script>
export default {
props: {
options: {
type: Array,
required: true
}
},
data: function () {
return {
value: this.options[0].value
}
},
methods: {
toggleHandler (value) {
this.$emit('input', value)
this.value = value
}
}
}
</script>
There is toggle with to options YES or NOT, if yes is selected the child component will be rendered otherwise will keep hide.
I'm trying to use a conditional in order to display a child component into a parent component using directives v-if or v-show, but I could not find the way to send the boolean value from the child component to the parent component.
Hope this helps!!
// CHILD
Vue.component('child', {
template: '<div>TOGGLE:- <input type="checkbox" #click="emit"/></div>',
data() {
return {
checked: false
};
},
methods: {
emit: function() {
this.checked = !this.checked;
this.$emit('event_child', this.checked);
}
}
});
// PARENT
var vm = new Vue({
el: '#app',
data: function() {
return {
toggleStatus: false
}
},
methods: {
eventChild: function(checked) {
this.toggleStatus = checked;
},
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js"></script>
<div id="app">
<child v-on:event_child="eventChild"></child>
<div id="toggle">TOGGLE STATUS => {{toggleStatus}}</div>
</div>

Set initial v-model value when using v-for

I have a select that i want to populate with computed data, it works fine:
<select style="width: 175px">
<option>None</option>
<option v-for="label in labels">{{label.description}</option>
</select>
var vm = new Vue({
el: '#div',
data: {
},
computed: {
labels: function () {
//return labels based on some logic
}
}
});
However i want to bind the selected value to a property, so i change my code to:
<select style="width: 175px" v-model=selectedLabel">
<option>None</option>
<option v-for="label in labels">{{label.description}</option>
</select>
var vm = new Vue({
el: '#div',
data: {
selectedLabel: ''
},
computed: {
labels: function () {
//return labels based on some logic
}
}
});
Then i get no data at all in the select. It's obviously because i set selectedLabel to '', but there is no empty option in my select. But how can i fix this? I don't know what data that will be computed so i can't predefine selectedLabel.
Just set the default value of your selection option to '' like:
<div id="app">
<select style="width: 175px" v-model="selectedLabel">
<option value="">None</option>
<option v-for="label in labels" :value="label.value">{{label.description}}</option>
</select>
</div>
This will auto select the none option.
See this fiddle
var vm = new Vue({
el: '#app',
data: {
selectedLabel: ''
},
computed: {
labels: function () {
return [
{description:'test', value: 'test'},
{description: 'test1', value: 'test1'}
]
}
}
});
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div id="app">
<select style="width: 175px" v-model="selectedLabel">
<option value="">None</option>
<option v-for="label in labels" :value="label.value">{{label.description}}</option>
</select>
</div>

Vue JS - injecting default value to custom radio group

So i tried to inject a default value to custom radio component that i wrote
Here's the code:
<template>
<div class="custom-radio-button">
{{value}}
<div v-for= "item in localValue">
<input type="radio" :value="item.value" name=item.name #click=onSelected(item.value) >
<span>{{item.label}}</span>
</input>
</div>
</div>
<script>
import Vue from 'vue'
const name = 'CustomRadioButton'
export default {
name,
componentName: name,
props: [ 'name', 'value', 'isDefault', 'label'],
data() {
return {
localName: this.name,
localValue: this.value
}
},
methods: {
onSelected (value) {
this.$emit('clicked', value)
}
}
}
</script>
And here's how i called it:
<CustomRadioButton :value=RadioFieldData #clicked="isRadioButtonSelection" isDefault='yellow'></CustomRadioButton>
And here's the Json Data that goes with it
RadioFieldData:[
{label:'Fire', value:'red', name:'colour' },
{label:'Sun', value:'yellow', name:'colour',isDefault:'yellow'},
{label:'Water', value:'blue', name:'colour'}
]
My question is what is the best way to pass the value "yellow" to the radio buttons group?
Your issue is that props need to be represented in their kebab-case format when used in your template. To set isDefault to "yellow", you need to use
is-default="yellow"
See https://v2.vuejs.org/v2/guide/components.html#camelCase-vs-kebab-case
Once you're able to read that property correctly, you can use
:checked="item.value == isDefault"
Here's an example.
Vue.component('custom-radio-button', {
template: `<div class="custom-radio-button">
Default: {{isDefault}}
<div v-for="item in value">
<input type="radio" :value="item.value" name="item.name" #click="onSelected(item.value)" :checked="item.value == isDefault" />
<span>{{item.label}}</span>
</div></div>`,
props: ['value', 'isDefault'],
methods: {
onSelected(value) {
this.$emit('clicked', value)
}
}
})
new Vue({
el: '#app',
methods: {
isRadioButtonSelection (val) {
console.log('isRadioButtonSelection', val)
}
},
data: {
RadioFieldData: [{"label":"Fire","value":"red","name":"colour"},{"label":"Sun","value":"yellow","name":"colour","isDefault":"yellow"},{"label":"Water","value":"blue","name":"colour"}]
}
})
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<custom-radio-button :value="RadioFieldData"
#clicked="isRadioButtonSelection"
is-default="yellow">
</custom-radio-button>
</div>

Categories

Resources