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
Related
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">
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 this code but when the selectCategory method gets called i don’t see product.categories being updated in the vue dev console.
<li class="border hover:bg-blue-100" v-bind:class=""
v-on:click="selectCategory($event, category.id)"
>
{{category.name}}
</li>
export default {
data () {
return {
product: {
categories: []
},
}
},
methods: {
selectCategory(event, id){
this.product.categories.push(id);
},
}
This might help you as referance.
<template>
<div>
<li v-for="category in Available.categories"
class="border hover:bg-blue-100"
v-bind:class=""
v-on:click="selectCategory($event, category.id)"
>
{{category.name}}
</li>
</div>
</template>
<script>
export default{
data () {
return {
Available: {
categories: [{
"name":"Fruites",
"id":1,
},
{
"name":"Veges.",
"id":2,
}]
},
product: {
categories: []
},
}
},
methods: {
selectCategory(event, id){
this.product.categories.push(id);
console.log(this.product.categories)
},
}
}
</script>
https://codesandbox.io/s/vue-template-v2zss?fontsize=14
Struggling to sort out how to get a selected value from a Typeahead component to pass back to the parent component. I'm allowing the user to search from a variety of data to link a record to a post. Once the user clicks one of the typeahead drop-down records, I pass the item to the sendlink method - I've checked that the data passes ok. When I do the emit using the selected-link event, I'm not getting the data in the parent component.
PostList.vue
<template>
<div>
<div v-if='posts.length === 0' class="header">There are no posts yet!</div>
<form action="#" #submit.prevent="createPost()" class="publisher bt-1 border-fade bg-white" autocomplete="off">
<div class="input-group">
<input v-model="post.content" type="text" name="content" class="form-control publisher-input" placeholder="What's the lastest?" autofocus>
<span class="input-group-btn">
<button type="submit" class="btn btn-primary">Post</button>
</span>
</div>
<span class="publisher-btn file-group">
<i class="fa fa-camera file-browser"></i>
<input type="file">
</span>
</form>
<div #click="doit" v-on:selected-link="onSelectedLink">{{ modellink.name }}</div>
<typeahead
source="/api/typeahead"
placeholder="Link Post to Trip, Business, etc"
filter-key="title"
:start-at="3">
</typeahead>
<post v-for="post in posts"
:key="post.id"
:post="post"
#post-deleted="deletePost($event)">
</post>
</div>
</template>
<script>
var axios = require("axios");
import post from './PostItem.vue';
import typeahead from './Typeahead.vue';
export default {
components: {
post,
typeahead
},
props: ['postableId', 'postableType', 'model'],
data: function() {
return {
modellink: {
"name": "n/a",
"description": "",
"id": null,
"model": "n/a"
},
post: {
id: 1,
content: "",
edited: false,
created_at: new Date().toLocaleString(),
user: {
id: 1,
name: '',
}
},
posts: [
{
id: 1,
content: "",
edited: false,
created_at: new Date().toLocaleString(),
user: {
id: 1,
name: '',
}
}
]
};
},
created() {
this.fetchPostsList();
},
methods: {
onSelectedLink: function (talink) {
alert(JSON.stringify(talink, null, 4));
this.link = talink
},
doit() {
alert(JSON.stringify(this.modellink, null, 4));
},
fetchPostsList() {
if( this.postableId ) {
axios.get('/api/' + this.postableType + '/' + this.postableId + '/posts').then((res) => {
this.posts = res.data;
});
} else {
axios.get('/api/post').then((res) => {
//alert(JSON.stringify(res.data[0], null, 4));
this.posts = res.data;
});
}
},
createPost() {
axios.post('api/post', {content: this.post.content, user_id: Laravel.userId, vessel_id: Laravel.vesselId })
.then((res) => {
this.post.content = '';
// this.post.user_id = Laravel.userId;
// this.task.statuscolor = '#ff0000';
this.edit = false;
this.fetchPostsList();
})
.catch((err) => console.error(err));
},
deletePost(post) {
axios.delete('api/post/' + post.id)
.then((res) => {
this.fetchPostsList()
})
.catch((err) => console.error(err));
},
}
}
</script>
Typeahead.vue
<template>
<div>
<input
v-model="query"
#blur="reset"
type="text"
class="SearchInput"
:placeholder="placeholder">
<transition-group name="fade" tag="ul" class="Results">
<li v-for="item in items" :key="item.id">
<span #click="sendlink(item)">
<strong>{{ item.name }}</strong> - <small>{{ item.model }}</small><br>
<small>{{ item.description }}</small>
</span>
</li>
</transition-group>
<p v-show="isEmpty">Sorry, but we can't find any match for given term :( </p>
</div>
</template>
<script>
var axios = require("axios");
export default {
name: 'Typeahead',
props: {
modellink: {
type: Object,
required: false
},
source: {
type: [String, Array],
required: true
},
filterKey: {
type: String,
required: true
},
startAt: {
type: Number,
default: 3
},
placeholder: {
type: String,
default: ''
}
},
data() {
return {
items: [],
query: '',
taitem: ''
}
},
computed: {
lookup() {
if(this.query.length >= this.startAt) {
axios.get(this.source + '/' + this.query).then((res) => {
this.items = res.data;
return res.data;
});
}
},
isEmpty() {
if( typeof this.lookup === 'undefined' ) {
return false
} else {
return this.lookup.length < 1
}
}
},
methods: {
sendlink: function (taitem) {
this.$emit('selected-link', taitem);
},
reset() {
this.query = ''
}
}
}
</script>
In your PostList.vue, move the v-on:selected-link="onSelectedLink" from the div to typeahead like below. When emitting an event from child to parent, the listener on the parent needs to be on the child component tag for it to work.
<div #click="doit">{{ modellink.name }}</div>
<typeahead
source="/api/typeahead"
placeholder="Link Post to Trip, Business, etc"
filter-key="title"
:start-at="3"
v-on:selected-link="onSelectedLink">
</typeahead>