Can anyone help with this problem? How can I emit two properties from child component to parent on select input change? I can submit the value, see below, but would like to emit the value and the name property of segmentLocations object. This is the child component:
<template>
<div class="container">
<div>
<select v-model="selectedSegmentValue" v-on:change="$emit('selectLocation', $event.target.value)">
<option selected value="">Choose your location...</option>
<option v-for="segmentLocation in segmentLocations"
:value="segmentLocation.value"
:key="segmentLocation.value">
{{ segmentLocation.name }}>
</option>
</select>
</div>
</div>
</template>
<script>
export default {
data() {
return {
segmentLocations: [
{ value: "Residential", name: 'Residential building' },
{ value: "Workplace", name: 'Workplace' },
{ value: "Hospitality", name: 'Hospitality or Retail' },
{ value: "Real Estate", name: 'Real Estate' },
{ value: "Commercial Parking", name: 'Commercial Parking' },
{ value: "Fleets", name: 'Fleets' },
{ value: "Cities & Governments", name: 'Cities & Governments' },
{ value: "Corridor", name: 'Highway, Corridor or Petrol Station' }
],
}
}
};
</script>
And this is the parent:
<template>
<Segments
v-on:selectLocation="quote.selectedSegmentValue = $event"
:selectedValue="quote.selectedSegmentValue">
</Segments>
</template>
<script>
export default {
data() {
return {
quote: {
selectedSegmentValue: "",
selectedSegmentName: ""
},
};
},
</script>
I think the existing answers and mine share a similar technique, but I created a couple of simplified sample components based on your components.
Child component:
<template>
<div class="emit-two-properties">
<div class="form-group">
<label for="segment-location">Segment Location</label>
<select class="form-control" id="segment-location"
v-model="segmentLocation" #change="selectSegmentLocation">
<option v-for="(segLoc, index) in segmentLocations" :key="index"
:value="segLoc">{{ segLoc.name }}</option>
</select>
</div>
</div>
</template>
<script>
export default {
data() {
return {
segmentLocation: {},
segmentLocations: [
{ value: "Residential", name: 'Residential building' },
{ value: "Workplace", name: 'Workplace' },
{ value: "Hospitality", name: 'Hospitality or Retail' },
{ value: "Real Estate", name: 'Real Estate' },
{ value: "Commercial Parking", name: 'Commercial Parking' },
{ value: "Fleets", name: 'Fleets' },
{ value: "Cities & Governments", name: 'Cities & Governments' },
{ value: "Corridor", name: 'Highway, Corridor or Petrol Station' }
],
}
},
methods: {
selectSegmentLocation() {
this.$emit('select-segment-location-event', this.segmentLocation);
}
}
}
</script>
Parent component:
<template>
<div class="parent">
<h4>Parent.vue</h4>
<div class="row">
<div class="col-md-6">
<form #submit.prevent="submitForm">
<emit-two-properties #select-segment-location-event="updateSegmentLocation" />
<button class="btn btn-secondary">Submit</button>
</form>
<p><span>Selected Segment Location Value:</span>{{ segmentLocation.value }}</p>
<p><span>Selected Segment Location Name:</span>{{ segmentLocation.name }}</p>
</div>
</div>
</div>
</template>
<script>
import EmitTwoProperties from './EmitTwoProperties'
export default {
components: {
EmitTwoProperties
},
data() {
return {
segmentLocation: {}
}
},
methods: {
updateSegmentLocation(segLoc) {
this.segmentLocation = segLoc;
}
}
}
</script>
you can create a method to get name and value from event.target (remove value from the end of child emit):
changeSelectedSegment(selected){
this.selectedSegmentName = selected.name
this.selectedSegmentValue = selected.value
}
in the parent change v-on:selectLocation to v-on:selectLocation="changeSelectedSegment($event)"
you can define a method like this (this method emit an object with name and value properties to parent
)
methods: {
selectLocation(event){
if(event.target.value !== ''){
const item = this.segmentLocations.find( item => item.value === event.target.value)
this.$emit('selectLocation', {
name: item.name,
value: event.target.value
})
}
}
},
and change this line :
<select v-model="selectedSegmentValue" v-on:change="$emit('selectLocation', $event.target.value)">
to this:
<select v-model="selectedSegmentValue" v-on:change="selectLocation">
Related
I have a dropdown that I want to give a validation.
So here is my dropdown component:
<template>
<div class="custom-select" :tabindex="tabindex" #blur="open = false">
<div class="selected" :class="{ open: open }" #click="open = !open">
{{ selected.name }}
</div>
<div class="items" :class="{ selectHide: !open }">
<div v-if="defaultValue != ''">{{defaultValue}}</div>
<div v-for="(option, i) of options" :key="i" #click=" selected = option; open = false; $emit('input', option);">
{{ option.name }}
</div>
</div>
</div>
</template>
<script>
export default {
props: {
options: {
type: Array,
required: true,
},
defaultValue: {
type: String,
required: false,
default: "Choose an option",
},
tabindex: {
type: Number,
required: false,
default: 0,
},
},
data() {
return {
open: false,
selected: this.setDefaultValue(),
};
},
mounted() {
this.$emit("input", this.selected);
},
methods: {
setDefaultValue () {
if (this.defaultValue == '' && this.options.length > 0) {
return this.options[0];
}
return { name: this.defaultValue};
}
}
};
</script>
and in my parent component, I am using this dropdown, the fetched value from API call and filled with variations. So what I am trying to do is if the value is not selected (default: "Choose an option"), I want to give an error message which is saying, the dropdown is mandatory.
<Dropdown
:options="getVariations"
class="select"
#input="getSelected"
/>
<script>
import Dropdown from "../components/Dropdown";
import apiHelper from "../helpers/apiHelper";
export default {
components: {Dropdown},
data() {
return {
selected: "",
variationId: "",
selectedObject: null
};
},
computed: {
getVariations() {
return this.product.attributes.variations
}
},
methods: {
getSelected(opt) {
this.selectedObject = opt;
this.selected = opt.description;
this.variationId = opt.id;
}
},
};
</script>
This is a simple example of what you are asking for:
// v-model property
selectedVModel: null
// html
<select id="testselect" v-model="selectedVModel" required>
<option :value="null">Default</option>
<option value="bar">Option 1</option>
<option value="foo">Option 2</option>
<option value="baz">Option 3</option>
</select>
<div v-if="!selectedVModel">Error! Choose something!</div>
You can handle the error inside of your child component. In this example, if the v-model as selectedVModel is empty, the <div> with v-if="!selectedVModel" will be shown. As selectedVModel is null it will automatically select <option :value="null">Default</option>. If you chosse one of the options selectedVModel get´s a value so v-if="!selectedVModel" results into false, no error then. If you select Default again, the value turns to null again which results into true at v-if="!selectedVModel", so the error will be visible again.
Combine this simple example with your code and let me know if it helped you.
If you want to validate in your parent component, try to check selectedObject.id :
Vue.component('Dropdown', {
template: `
<div class="custom-select" :tabindex="tabindex" #blur="open = false">
<div class="selected" :class="{ open: open }" #click="open = !open">
{{ selected.name }}
</div>
<div class="items" :class="{ selectHide: !open }">
<div v-if="defaultValue != ''">{{defaultValue}}</div>
<div v-for="(option, i) of options" :key="i" #click=" selected = option; open = false; $emit('input', option);">
{{ option.name }}
</div>
</div>
</div>
`,
props: {
options: {
type: Array,
required: true,
},
defaultValue: {
type: String,
required: false,
default: "Choose an option",
},
tabindex: {
type: Number,
required: false,
default: 0,
},
},
data() {
return {
open: false,
selected: this.setDefaultValue(),
};
},
mounted() {
this.$emit("input", this.selected);
},
methods: {
setDefaultValue () {
if (this.defaultValue == '' && this.options.length > 0) {
return this.options[0];
}
return { name: this.defaultValue};
}
}
})
new Vue({
el: '#demo',
data() {
return {
selected: "",
variationId: "",
selectedObject: null,
product: {
attributes: {
variations: [{id: 1, name: 'name1', description: 'desc1'}, {id: 2, name: 'name2', description: 'desc2'},{id: 3, name: 'name3', description: 'desc3'},]
}
}
};
},
computed: {
getVariations() {
return this.product.attributes.variations
}
},
methods: {
getSelected(opt) {
this.selectedObject = opt;
this.selected = opt.description;
this.variationId = opt.id;
},
submit() {
if (!this.selectedObject.id) alert('pls select option')
else console.log(this.selectedObject)
}
},
})
Vue.config.productionTip = false
Vue.config.devtools = false
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="demo">
<button #click="submit">submit</button>
<Dropdown
:options="getVariations"
class="select"
#input="getSelected"
/>
</div>
In my component, i have multiselect that should get options selected on matched value passed:
<select class="custom-select" multiple="" v-model="items">
<option v-for="item in objects_list" :selected="itemSelected(item)" :key="item.id" :value="item.id">
{{item.name}}
</option>
</select>
...
methods() {
itemSelected(item) {
let selected;
let object_selected = this.value.filter(object => {
console.log(object)
if(item.id == object) {
return true;
}
});
}
the selected binding doesn't seems to work, how to correctly implement multiselect to bind selected options ?
The best way to do this is to use the CREATED Function from Vue.js
<template>
<div>
<select class="ur-CSS-class" multiple v-model="selected">
<option v-for="item in objects_list" :key="item.id" :value="item.id">
{{ item.name }}
</option>
</select>
<br />
<span>Selected : {{ selected }}</span>
</div>
</template>
<script>
var your_list = [
{ name: "one", id: 13 },
{ name: "two", id: 18 },
{ name: "three", id: 11 },
{ name: "four", id: 7 },
{ name: "five", id: 1 },
{ name: "six", id: 2 },
];
export default {
name: "App",
data() {
return {
objects_list: your_list,
selected: []
};
},
props:{
inputData: {
type: Object,
required: true
}
},
computed: {
selected() {
let selected = this.objects_list.filter(item => {
return this.inputData.find(i => i == item.id);
});
return selected;
}
}
};
</script>
Scenario
I am using Vuex, to store some data in it, and in my case the ticket details.
Initially, I have a ticket which has an array of discounts, to be empty.
Once I hit the button "Add discount" I mount the component called "testDiscount" which in the mounted hook pushes the first object ({"code": "Foo", "value":"Boo"}) in the discounts array of a specific ticket in the store.
The problem arise when I try to type in the input boxes (changing its state) in this component where I get the error "do not mutate Vuex store state outside mutation handlers.". How could I best handle this?
Test.vue
<template>
<div>
<test-component v-for="(t, key) in tickets" :key="key" :ticket-key="key" :tid="t.id"></test-component>
</div>
</template>
<script>
import TestComponent from "~/components/testComponent.vue";
export default {
layout: "noFooter",
components: {
"test-component": TestComponent,
},
data() {
return {
tickets: this.$store.state.ticketDiscount.tickets,
};
},
mounted() {
if (this.tickets.length == 0) {
this.$store.commit("ticketDiscount/addTicket", {
id:
this.$store.state.ticketDiscount.tickets.length == 0
? 0
: this.$store.state.ticketDiscount.tickets[
this.$store.state.ticketDiscount.tickets.length - 1
].id + 1,
discount: [],
});
}
},
};
</script>
ticketDiscount.js
export const state = () => ({
tickets: []
});
export const mutations = {
addTicket(state, ticket) {
state.tickets.push(ticket);
},
addDiscount(state, property) {
state.tickets.find(ticket => ticket.id == property.id)[property.name].push(property.value);
}
testComponent.vue
<template>
<div>
<h3>Ticket number: {{ticketKey + 1}}</h3>
<button #click="showDiscount = true">Add discount</button>
<test-discount v-model="discount_" v-if="showDiscount" :tid="tid"></test-discount>
</div>
</template>
<script>
import testDiscount from "~/components/test-discount.vue";
export default {
components: {
testDiscount,
},
data() {
return {
showDiscount: false,
tid_: this.tid,
};
},
props: {
tickets: Array,
ticketKey: { type: Number },
tid: { type: Number, default: 0 },
},
methods: {
updateTicket() {
this.$emit("updateTicket", {
id: this.tid_,
value: {
discount: this.discount_,
},
});
},
},
mounted() {
this.$watch(
this.$watch((vm) => (vm.discount_, Date.now()), this.updateTicket)
);
},
computed: {
discount_: {
get() {
return this.$store.state.ticketDiscount.tickets.find(
(ticket) => ticket.id == this.tid
)["discount"];
},
set(value) {
// set discount
},
},
},
};
</script>
testDiscount.vue
<template>
<div class="container">
<div class="title">
<img src="~/assets/svgs/price_tag.svg" />
<span>Discount code</span>
{{ discounts }}
</div>
<div class="discount-container">
<div v-for="(c,idx) in discounts" class="discounts" :key="idx">
<div class="perc-input">
<input style="max-width: 50px;" v-model.number="c.discount" type="number" min="1" max="100" step="1" placeholder="10">
<div>%</div>
</div>
<input class="code-input" v-model="c.code" placeholder="Code">
<img src="~/assets/svgs/bin.svg" title="Delete code" #click="deleteCode(idx)" v-if="discounts.length > 1"/>
</div>
</div>
<span #click="newDiscount" class="add-another">+ Add another discount</span>
</div>
</template>
<script>
export default {
props: {
value: {
type: Array,
},
tid: { type: Number, default: 0 },
},
data() {
return {
discounts: this.value,
}
},
mounted() {
if (this.discounts.length == 0) {
this.newDiscount();
}
},
methods: {
newDiscount() {
this.$store.commit('ticketDiscount/addDiscount',
{
"id": this.tid,
"name": "discount",
"value": { code: null,discount: null }
});
},
deleteCode(index) {
this.discounts.splice(index, 1);
}
},
watch: {
discounts() {
this.$emit('input', this.discounts)
}
},
beforeDestroy() {
this.$emit('input', []);
}
}
</script>
you shouldn't use v-model in this case.
<input style="max-width: 50px;" v-model.number="c.discount" .../>
you could just set the value
<input style="max-width: 50px;" :value="c.discount" #change="handleValueChange" .../>
and then in handleValueChange function to commit the action to update just for that value.
[tex]It shows me on the console the following error message " Cannot read property 'name' of undefined". I cant reach out to the name in my Data even that is structured same as in my validation function.**emphasized text*
<template>
<component v-bind:validationsRule="validations" v-bind:dataFields="dataFields" v-bind:is="currentStep.details"></component>
<button class="btn" v-on:click="backStep" id="back">Back</button>
<div v-show="$v.dataFields.name.$error">this has an error</div>
<button class="btn" v-on:click="nextStep" id="next">Next</button>
</div>
</template>
<script>
import DetailsForm from './DetailsForm'
import DetailsForm1 from './DetailsForm1'
import { required } from 'vuelidate/lib/validators'
export default {
name: 'ProductDetails',
props: {
step: {
type: Array
}
},
data: function () {
return {
items: [
{ stepTitle: 'Completed step', details: DetailsForm },
{ stepTitle: 'Step Two', details: DetailsForm1 },
{ stepTitle: 'Step Three', details: DetailsForm },
{ stepTitle: 'Step Four', details: DetailsForm }
],
activeNumber: 0,
dataFields: {
id: null,
hasDescription: false,
name: '',
description: ''
},
validations: function () {
if (!this.dataFields.hasDescription) {
return {
name: {
required
}
}
} else {
return {
name: {
required
},
description: {
required
}
}
}
}
}
},
<--- DetailsForm.vue --->
Here is my other part of Code from the other file that I am using as component on this file
<template>
<div>
<div class="form-group" :class="{ 'form-group--error': $v.dataFields.name.$error}">
<label class="form__label">Name</label>
<input class="form__input" v-model.trim="$v.dataFields.name.$model"/>
<div v-show="$v.dataFields.name.$error">This has an error</div>
</div>
<div class="form-group">
<label class="form__label" for="hasDesc">Has description?</label>
<div class="toggle">
<input id="hasDesc" type="checkbox" v-model="hasDescription"/>
<label for="hasDesc">
<div class="toggle__switch"></div>
</label>
</div>
</div>
<div class="form-group" v-if="hasDescription" :class="{ 'form-group--error': $v.dataFields.description.$error}">
<label class="form__label">Description</label>
<input class="form__input" v-model.trim="$v.dataFields.description.$model"/>
</div>
<tree-view :data="$v" :options="{rootObjectKey: '$v', maxDepth: 2}"></tree-view>
</div>
</template>
<script>
export default {
name: 'DetailsForm',
data () {
return {
}
},
props: {
validationsRule: {
type: Function,
default: () => {
}
},
dataFields: {
type: Object
}
},
validations () {
return this.validationsRule()
}
}
</script>
Your validation rules do not contain a property dataFields, but you're calling $v.dataFields.name in your template. Since dataFields is not defined, the error Cannot read property 'name' of undefined makes sense.
Untested, but if you changed your validations function to return something like this, it should work:
validations: function () {
var validations = {
dataFields: {
name: {
required
}
}
};
if (this.dataFields.hasDescription)
validations.dataFields.description = { required };
return validations;
}
I have an array of names that I loop over using v-for I'm trying to filter these results when a user starts typing in a search box.
I've added my code below for reference if I do my loop as v-for="entry in entries" then it output the array but doesn't work with the computed and filteredList function
<template>
<div class="container-flex">
<div class="entries">
<div class="entries__header">
<div class="entries__header__title">
<p>Entries</p>
</div>
<div class="entries__header__search">
<input
type="text"
name="Search"
class="input input--search"
placeholder="Search..."
v-model="search">
</div>
</div>
<div class="entries__content">
<ul class="entries__content__list">
<li v-for="entry in filteredList">
{{ entry.name }}
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import addEntry from '#/components/add-entry.vue'
export default {
name: 'entry-list',
search: '',
components: {
addEntry
},
data: function() {
return {
entries: [
{
name: 'Paul'
},
{
name: 'Barry'
},
{
name: 'Craig'
},
{
name: 'Zoe'
}
]
}
},
computed: {
filteredList() {
return this.entries.filter(entry => {
return entry.name.toLowerCase().includes(this.search.toLowerCase())
})
}
}
}
Try to move the search prop in to data option like this:
export default {
name: 'entry-list',
components: {
addEntry
},
data: function() {
return {
search: '',
entries: [
{
name: 'Paul'
},
{
name: 'Barry'
},
{
name: 'Craig'
},
{
name: 'Zoe'
}
]
}
},
computed: {
filteredList() {
if(this.search === '') return this.entries
return this.entries.filter(entry => {
return entry.name.toLowerCase().includes(this.search.toLowerCase())
})
}
}
}
Also add a check if the search prop is empty to return the full entries list.
Demo fiddle