Updating a data property in Vuejs based on an input event - javascript

I have a search input that filters data. The filtered data is meant to re-render my page so that the original property(shifts) will now have the filtered data and this means any change in the search input should update my data object. The snippet below works perfectly so that when I monitor the logged variable filteredData I can see its returning the expected data. When I type on the search input the data is filtered as expected and when I clear the search, the object contains the data in the original shifts property.
The issue occurs when I assign this filtered data to my original object i.e. this.shifts=filteredData. When I do this, the shifts property will contain the filtered data but when I clear the search the previously filtered data is what will still be assigned meaning that the shifts property is not being properly updated. Not sure why this is happening.
new Vue({
el: "#app",
data: {
search_input: '',
shifts: {"John":[{"user_id":193,"shift_hours":0}],"Rose":[{"user_id":194,"shift_hours":0}]},
},
methods: {
searchInput() {
const filteredData = Object.keys(this.shifts).reduce(
(obj, key) => {
if (
key.toLowerCase().includes(this.search_input.toLowerCase())
) {
obj[key] = this.shifts[key];
}
return obj;
},
{}
);
console.log(filteredData);
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input v-model="search_input" #input.prevent="searchInput" type="text"/>
</div>
I've appended an additional snippet below to showcase the issue I'm facing:
new Vue({
el: "#app",
data: {
search_input: '',
shifts: {"John":[{"user_id":193,"shift_hours":0}],"Rose":[{"user_id":194,"shift_hours":0}]},
},
methods: {
searchInput() {
const filteredData = Object.keys(this.shifts).reduce(
(obj, key) => {
if (key.toLowerCase().includes(this.search_input.toLowerCase())) {
obj[key] = this.shifts[key];
}
return obj;
},
{}
);
this.shifts = filteredData;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input v-model="search_input" #input.prevent="searchInput" type="text" />
<pre>{{ shifts }}</pre>
</div>

Data needs to be a function, and you can use computed property for filtering:
new Vue({
el: "#app",
data() {
return {
search_input: '',
shifts: {"John": [{"user_id": 193, "shift_hours": 0}], "Rose": [{"user_id": 194, "shift_hours": 0}]},
}
},
computed: {
searchInput() {
return Object.keys(this.shifts).reduce((obj, key) => {
if (key.toLowerCase().includes(this.search_input.toLowerCase())) {
obj[key] = this.shifts[key]
}
return obj
}, {} )
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input v-model="search_input" type="text" />
<pre>{{ searchInput }}</pre>
</div>

Related

Manipulating array in vue3 using v-model [edited]

The question has three parts revolving around two files App.vue and X Array.vue :-
1.When value of input is changed, how it could be written back to the array?
2.If the value entered is empty how to remove the element from array?
3.How to show one extra input element always so that it is possible to add new values(linked with 2)?
XArray should basically be an array editor.
App.vue
<template>
<div>
<XArray v-model="myArray" />
<pre>{{ myArray }}</pre>
</div>
</template>
<script>
import XArray from './components/XArray.vue';
export default {
components: {
XArray,
},
data: () => {
return {
myArray: ['one', 'two'],
};
},
};
</script>
XArray.vue
<template>
<input
v-for="(option, index) in modelValue"
:key="index"
#input="$emit('update:modelValue', [...modelValue, `${$event.target.value}`])"
/>
</template>
<script>
export default {
props: {
modelValue: {
type: Array,
required: true,
},
};
</script>
Please take a look at following snippet:
const app = Vue.createApp({
data() {
return {
myArray: ['one', 'two'],
}
},
methods: {
addEl() {
this.myArray.push('new')
}
}
})
app.component('child', {
template: `
<div>
<input
v-for="(option, index) in modelValue"
:key="index"
#input="$emit('update:modelValue', upd(index, $event.target.value))"
:value="option"
/>
</div>
`,
props: {
modelValue: {
type: Array,
required: true,
}
},
methods: {
upd(idx, val) {
return val ? [
...this.modelValue.map((item, i) =>
i !== idx
? item
: val
),
] : this.modelValue.length > 1 ?
[ ...this.modelValue.filter((item, i) => {
if(i !== idx) return item
})] :
[ "last" ]
}
}
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<child v-model="myArray"></child>
<pre>{{ myArray }}</pre>
<button #click="addEl">add</button>
</div>

How can I get json collected data from child to parent component in Vue.js?

I have results data in child component but I should bring it to main component.
Main Component in Parent, so each time I click the button, the result should be gathered in main app
<button #click="showFinalResult">Click me</button>
Child component data here and I just need to show results in json format in parent, the results are input results
results: [],
You can emit event for child for sending data:
Vue.component('Child', {
template: `
<div class="">
</div>
`,
data() {
return {
results: [1,2,3]
}
},
mounted() {
this.$emit('update', this.results);
}
})
new Vue({
el: '#demo',
data() {
return {
res: [],
show: false
}
},
methods: {
getResult(res) {
this.res = res
},
showResults() {
this.show = !this.show
}
}
})
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="showResults">Click me</button>
<p v-if="show">{{ res }}</p>
<child #update="getResult" />
</div>

I've created multiple filters functions and each one does its own job right, however, I'm not sure how to chain them so they all work together

I'm trying to create filtering functionality which would allow me to filter cards from a collectible card game based on several criteria. I've already created 6 functions which filter based on a single criteria like card name, card cost or card rarity. These functions work fine and do their job, however, right now I can only use one of them at a time.
What I am trying to do is combine or chain these functions so that they are all taken into account before returning the final array with cards. I'm wondering if there's any easy way to do that?
Right now I have this:
<template>
<div class="cards">
<div class="cards-list">
<div class="card" v-for='card in filteredByCost' #click='specificCard(card.cardCode)'>
<div class="card-image">
<img class='responsive-image' :src='"../assets/cards/" + card.cardCode + ".png"' alt="">
</div>
</div>
</div>
</div>
</template>
<script>
import cards from '../assets/cards/set1-en_us.json'
import router from '../router'
export default {
data() {
return {
cards: cards,
search: '',
regions: ['Demacia', 'Noxus'],
cost: [7],
attack: [3, 5],
health: [4, 7],
rarity: ['Champion']
}
},
methods: {
specificCard(cardCode){
router.push({ name: 'specificCard', params: { cardCode: cardCode } })
}
},
computed: {
filteredByName(){
return this.cards.filter((card) => {
return card.name.match(this.search)
})
},
filteredByRegion(){
return this.cards.filter((card) => {
return this.regions.includes(card.region)
})
},
filteredByCost(){
return this.cards.filter((card) => {
return this.cost.includes(card.cost)
})
},
filteredByRarity(){
return this.cards.filter((card) => {
return this.rarity.includes(card.rarity)
})
},
filteredByAttack(){
return this.cards.filter((card) => {
return this.attack.includes(card.attack)
})
},
filteredByHealth(){
return this.cards.filter((card) => {
return this.health.includes(card.health)
})
},
}
}
</script>
Information
Place all your filter methods in the methods attribute on the vue instance
Create a way to enable/disable the filters
Create a computed property that looks at your #2 in this list and applies the proper filters accordingly
My rough example
new Vue({
el: "#app",
data () {
return {
filterEnabler: {
search: false,
sort: false
},
formInputs: {
searchText: ''
},
entries: [
'vue',
'react',
'angular',
'svelte'
]
}
},
computed: {
filteredEntries () {
let { entries, filterEnabler } = this
entries = entries.slice(0)
if (filterEnabler.search) entries = this.searchFilter(entries)
if (filterEnabler.sort) entries = this.sortFilter(entries)
return entries
}
},
methods: {
searchFilter (entries) {
return entries.filter(entry => entry.indexOf(this.formInputs.searchText) !== -1)
},
sortFilter (entries) {
return entries.sort()
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="filters">
<div class="search-filter">
<input type="checkbox" v-model="filterEnabler.search" /> Search Filter
<div v-if="filterEnabler.search">
<input placeholder="type here" type="text" v-model="formInputs.searchText" />
</div>
</div>
<div class="sort-filter">
<input type="checkbox" v-model="filterEnabler.sort" /> Sort Filter
</div>
</div>
<ul>
<li v-for="entry in filteredEntries" :key="entry">{{entry}}</li>
</ul>
</div>
In my example, you can see this I have 2 filters, search and sort - when one of their filterEnablers is toggeled true, it will apply all the enabled filters to the data and then return a new, separate array (very important, try not to mutate your source of truth, in my case, that's entries)
Hope this helps!

How to track changed Valued from array of objects in vuejs

I have an addRows array which contains a group of objects.
These objects are added dynamically when using click + button.
Then they are pushed to the addRows array; after that, the user fills these objects.
Every object has many input values such as price and quantity.
I need to change the price when the quantity changes, but the problem is if the value was added before, the returned changed item is empty.
This happens because the new value was added before in the old values array.
As shown in the image third value same first so the array returned empty value of third item
I tried foreach and for also map but I'm facing the same problem.
computed: {
originalDistValue(a , b) {
return this.addRows.map(i => {
return i.dist
})
}
}, // close computed
watch: {
originalDistValue(a , b) {
let newVal = a.filter( obj => {
return b.indexOf(obj) == -1;
})
let element = this.addRows.filter( i => {
return i.dist == newVal;
})
deep: true
console.log(newVal)
for(let i in element) {
element[i].cit = 1;
}
}
}
At first create a child component for each row
Pass each row to component
Handle the single object inside the component instead of handling an array of objects in parent
See this example:
Vue.component('child', {
props: {
value: { type: Object, default: () => ({}) }
},
data: function() {
return {
selfValue: null
}
},
watch: {
value: {
handler(value) {
this.selfValue = value
},
immediate: true,
deep: true
},
selfVaue: {
handler(value) {
// you can also validate value then emit it
this.$emit('input', value)
},
deep: true
},
'selfValue.quantity'() {
this.selfValue.price = 0
}
},
template: `
<div class="d-flex justify-content-between">
<label>
Title:
<input type="text" v-model="selfValue.title" class="form-control" />
</label>
<label>
Quantity:
<select v-model="selfValue.quantity" class="form-control">
<option value="A">A</option>
<option value="B">B</option>
</select>
</label>
<label>
Price:
<input type="tel" v-model="selfValue.price" class="form-control" />
</label>
</div>
`
})
new Vue({
el: '#app',
data: {
rows: []
},
methods: {
add() {
this.rows.push({ title: '', quantity: '', price: 0 })
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<div id="app" class="container">
<h2 class="mb-3">Rows:</h2>
<div v-for="row,i in rows">
<child v-model="row"></child>
<hr class="mt-1 mb-1" />
</div>
<button #click="add" class="mb-3">
ADD
</button>
<pre>{{ rows }}</pre>
</div>

Dynamics inputs, the v-model update all values in v-for

I try this following code with Vue.js 2:
<div id="app">
<div v-for="(item, index) in items">
<div>
<input type="text" v-model="items[index].message">
<input type="text" v-model="items[index].surface">
</div>
</div>
<button #click="addNewfield">Add</button>
</div>
var app = new Vue({
el: '#app',
data: {
item: {
message: 'test',
surface: 45
},
items: [],
},
mounted() {
this.items.push(this.item)
},
methods: {
addNewfield() {
this.items.push(this.item);
}
}
})
The goal is to create news input when user click on Add button. I tried different ways like :
<input type="text" v-model="item.message">
But it doesn't work. If you write in "message" input, all "message" inputs will be updated.
How can I only updated the concerned value ?
Thanks for help !
This is happening because objects in Javascript are stored by reference. This means that every time you push this.item onto the array, it's adding a reference to the exact same object as the last.
You can avoid this by creating a new object each time:
methods: {
addNewfield() {
const obj = {
message: 'test',
surface: 45
}
this.items.push(obj);
}
}
Another option would be to clone the original object each time like:
methods: {
addNewfield() {
const clone = Object.assign({}, this.item);
this.items.push(clone);
}
}

Categories