Vue watch on objects - vue.js

I'm using Vue2 and I have a problem to compare objects. But when compare "newVal" and "oldVal" values are always the same.
Child componenet:
name: "List",
components: {
Pagination, UserDetails
},
props: {
filters: Object
},
watch: {
filters: {
handler(val, oldVal) {
console.log(JSON.stringify(val));
console.log(JSON.stringify(oldVal));
},
deep: true,
}
},
Parent Componenet:
<template>
<div>
<b-form inline>
<label class="sr-only" for="inline-form-input-name">Name</label>
<b-form-input
id="inline-form-input-name"
class="mb-2 mr-sm-2 mb-sm-0"
v-model="filters.username"
placeholder="Name"
></b-form-input>
<label class="sr-only" for="inline-form-input-username">Email</label>
<b-input-group class="mb-2 mr-sm-2 mb-sm-0">
<b-form-input
id="inline-form-input-username"
v-model="filters.email"
placeholder="Email"></b-form-input>
</b-input-group>
<b-button variant="danger">Clear</b-button>
</b-form>
<List :filters="filters"/>
</div>
</template>
<script>
import List from "./List";
export default {
name: "AdminUserList",
components: {
List,
},
data() {
return {
filters: {
username: '',
email: ''
},
}
},
}
</script>
<style lang="scss">
</style>
Result:
{"username":"d","email":""}
{"username":"d","email":""}

from the official doc you will find:
Note: when mutating (rather than replacing) an Object or an Array, the old value will be the same as new value because they reference the same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.
vue js watcher DOC
one thing that you can do is to watch the object's property directly like:
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
vuetify: new Vuetify(),
data() {
return {
filters: {
name: '',
email: '',
},
};
},
watch: {
'filters.name' (newVal, oldVal) {
console.log(`new name: ${newVal}`, `, old name: ${oldVal}`);
},
'filters.email' (newVal, oldVal) {
console.log(`new email: ${newVal}`, `, old email: ${oldVal}`);
},
},
});
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-container>
<v-row class="px-5">
<v-text-field v-model="filters.name" outlined label="name"></v-text-field>
</v-row>
<v-row class="px-5">
<v-text-field v-model="filters.email" outlined label="email"></v-text-field>
</v-row>
</v-container>
</v-app>
</div>

Try to adjust watcher accordingly
watch: {
filters: {
handler: function(val, oldVal) {
console.log(JSON.stringify(val));
console.log(JSON.stringify(oldVal));
},
deep: true,
}
},
I have just followed documentation from the official website

Related

How to collect selected BootstrapVue (b-form-checkbox) check boxes?

I work on Nuxt.js (Vue.js) application and modal dialog looks as following:
The code of Basket.vue component looks as following:
<template lang="pug">
b-container(v-if='products.length > 0')
b-row
b-col(:cols="8")
b-container
b-row
b-list-group
b-list-group-item.flex-column.align-items-start(v-for="(product, index) in products" :key="index")
b-container
b-row
b-col(:cols="1").d-flex.align-items-center.justify-content-center
b-form-checkbox {{ index + 1 }}
b-col(:cols="11")
basket-item(:product="product")
b-row.row-middle-style
b-button.close(type='button' aria-label='Close' #click="onRemoveItem")
span(aria-hidden='true') ×
u Remove selected items
b-button(type='button' #click="onContinue()").btn.btn-lg.buy__button Продолжить покупки
b-row
div
hr
| * доставка будет осуществлена из Санкт-Петербурга;
br
| * наш менеджер свяжется с вами сразу после обработки заказа;
...
</template>
<script lang="ts">
import Vue from 'vue'
import BasketItem from './BasketItem.vue'
import { mapState, mapActions } from 'vuex'
export default Vue.extend({
components: {
BasketItem,
},
data() {
return {
}
},
computed: {
...mapState(['products']),
},
methods: {
...mapActions({
onRemoveItem: 'removeItem',
}),
},
})
</script>
<style lang="sass">
...
</style>
The code of BasketItem.vue component looks as following:
<template lang="pug">
b-container
b-row.style-item-row
b-col(:cols="3")
img(:src="product.image" :alt="product.alt")
b-col(:cols="4")
div {{ product.name }}
div {{ product.description }}
b-col(:cols="4")
div Цена {{ product.price }}
div Количество {{ product.quantity }}
b-col(:cols="1")
b-button.close(type='button' aria-label='Close' #click="onRemoveItem(product.id)")
span(aria-hidden='true') ×
</template>
<script lang="ts">
import Vue from 'vue'
import { mapActions } from 'vuex'
export default Vue.extend({
props: {
product: {
type: Object,
},
},
data() {
return {
}
},
methods: {
...mapActions({
onRemoveItem: 'removeItem',
}),
},
})
</script>
<style lang="sass">
...
</style>
The Vuex code looks as following:
import * as mutationTypes from './mutation_types'
export const state = () => ({
products: [
{
id: 1,
image: 'https://licota.ru/system/product_images/attachments/5d9b/1781/6332/3406/9d00/2a31/small/8bfa7c2c-c7c7-11e4-80f4-002590d99cf6.jpg?1570445184',
alt: 'Товар 1',
name: 'Товар 1',
description: 'Описание 1',
price: 100,
quantity: 4
},
{
id: 2,
image: 'https://licota.ru/system/product_images/attachments/5d9b/329f/6332/3406/9d00/9336/small/e6a69bba-3450-11e9-812c-002590d99cf6.jpg?1570452124',
alt: 'Товар 2',
name: 'Товар 2',
description: 'Описание 2',
price: 200,
quantity: 7
}
]
})
export const getters = {
}
export const actions = {
removeItem({ commit }, id) {
commit(mutationTypes.REMOVE_ITEM, id)
}
}
export const mutations = {
[mutationTypes.REMOVE_ITEM] (state, id) {
state.products = state.products.filter(x => {
return x.id != id
})
}
}
As you can see I have a list of items (there are two). In front of each of them is BootstrapVue (b-form-checkbox) check box. I can select any of them and click "Remove selected items". After that they will be removed. The code for that action doesn't exist at this moment. The Vuex code you can see refers to clicking "x" in upper-right corner of items.
My issue is how to collect selected items that is check boxes that are checked in order to remove appropriate items?
UPDATE:
I've tried you'd suggested, but it doesn't work. The most likely I did something wrong. Actually, when I take a look at Vue section in DevTools, isChecked computed property doesn't change after clicking check boxes. The code looks as following:
Basket.vue:
<template lang="pug">
b-container(v-if='products.length > 0')
b-row
b-col(:cols="8")
b-container
b-row
b-list-group
b-list-group-item.flex-column.align-items-start(
v-for="(product, index) in products"
:key="product.id"
v-bind="{...product}"
v-on="{'update:checked': (data) => handleSetChecked(data)}"
)
b-container.set_pad
b-row
b-col(:cols="12").d-flex.align-items-center.justify-content-center
basket-item(:product="product" :productIndex="index")
b-row.row-middle-style
b-button.close(type='button' aria-label='Close' #click="onRemoveSelectedItems")
span(aria-hidden='true') ×
u Удалить выбранное
b-button(type='button' #click="onContinue()").btn.btn-lg.buy__button Продолжить покупки
b-row
div
hr
| * доставка будет осуществлена из Санкт-Петербурга;
br
| * наш менеджер свяжется с вами сразу после обработки заказа;
...
</template>
<script>
import Vue from 'vue'
import BasketItem from './BasketItem.vue'
import { mapActions } from 'vuex'
export default Vue.extend({
components: {
BasketItem,
},
data() {
return {}
},
computed: {
products() {
return this.$store.getters.getProducts
},
toRemove() {
return this.$store.getters.getProductsToRemove
},
},
methods: {
...mapActions({
onRemoveItem: 'removeItem',
}),
handleSetChecked(data) {
this.$store.dispatch("setToRemove", data)
},
handleRemoveItems() {
this.$store.dispatch("removeSelected")
},
onRemoveSelectedItems() {
this.handleRemoveItems()
},
},
})
</script>
BasketItem.vue:
<template lang="pug">
b-container
b-row.style-item-row
b-col(:cols="1").d-flex.align-items-center.justify-content-center
b-form-checkbox(v-model="isChecked") {{ productIndex + 1 }}
b-col(:cols="3")
img(:src="product.image" :alt="product.alt")
b-col(:cols="3")
div {{ product.title }}
div {{ product.description }}
b-col(:cols="4")
div Цена {{ product.price }}
div Количество {{ product.quantity }}
b-col(:cols="1")
b-button.close(type='button' aria-label='Close' #click="onRemoveItem(product.id)")
span(aria-hidden='true') ×
</template>
<script>
import Vue from 'vue'
import { mapActions } from 'vuex'
export default Vue.extend({
props: {
product: {
type: Object,
},
productIndex: {
type: Number,
},
},
data() {
return {}
},
computed: {
isChecked: {
get() {
return this.product.checked
},
set(val) {
this.$emit("update:checked", {
id: this.product.id,
checked: val,
})
}
},
},
methods: {
...mapActions({
onRemoveItem: 'removeItem',
}),
},
})
</script>
Vuex:
import * as mutationTypes from './mutation_types'
export const state = () => ({
products: [
{
id: 1,
image: 'https://licota.ru/system/product_images/attachments/5d9b/1781/6332/3406/9d00/2a31/small/8bfa7c2c-c7c7-11e4-80f4-002590d99cf6.jpg?1570445184',
alt: 'Товар 1',
title: 'Товар 1',
description: 'Описание 1',
price: 100,
quantity: 4,
checked: false
},
{
id: 2,
image: 'https://licota.ru/system/product_images/attachments/5d9b/329f/6332/3406/9d00/9336/small/e6a69bba-3450-11e9-812c-002590d99cf6.jpg?1570452124',
alt: 'Товар 2',
title: 'Товар 2',
description: 'Описание 2',
price: 200,
quantity: 7,
checked: false
}
]
})
export const getters = {
getProducts: state => state.products,
getProductsToRemove: state => state.products.filter(({
checked
}) => checked),
}
export const actions = {
setToRemove({
commit
}, data) {
commit("SET_TO_REMOVE", data)
},
removeSelected({
commit
}) {
commit("REMOVE_SELECTED")
},
removeItem({ commit }, id) {
commit(mutationTypes.REMOVE_ITEM, id)
},
}
export const mutations = {
SET_TO_REMOVE(state, {
id,
checked
}) {
state.products = state.products.map(product => {
if (product.id === id) {
return {
...product,
checked,
}
} else {
return product
}
})
},
REMOVE_SELECTED(state) {
state.products = state.products.filter(({
checked
}) => !checked)
},
[mutationTypes.REMOVE_ITEM] (state, id) {
state.products = state.products.filter(x => {
return x.id != id
})
},
}
The reference to "Form Checkbox Inputs":
https://bootstrap-vue.org/docs/components/form-checkbox
I advise you to think through your code, and have the logic steps ready:
You put out a list of UI items, based on a list in the Vuex store
Each UI item has a toggle (checkbox) that sets some part of the state of the item it displays (should it be removed or not)
By clicking the toggle (checkbox), the state of the item should change
By clicking the button (remove), the Vuex state should change: remove every item whose state fulfills a condition (it's toRemove state is true)
That's all. More simply put: don't work by selecting some UI element. Work by updating the state of the data that the UI element is based upon. (And let Vue's reactive capability do the rest.)
The first snippet is an easy one, not using Vuex:
Vue.component('RowWithCb', {
props: ["id", "title", "checked"],
computed: {
isChecked: {
get() {
return this.checked
},
set(val) {
this.$emit("update:checked", val)
}
},
},
template: `
<b-row>
<b-col
class="d-flex"
>
<div>
<b-checkbox
v-model="isChecked"
/>
</div>
<div>
{{ title }}
</div>
</b-col>
</b-row>
`
})
new Vue({
el: "#app",
computed: {
toRemove() {
return this.rows.filter(({
checked
}) => checked)
},
toKeep() {
return this.rows.filter(({
checked
}) => !checked)
},
},
data() {
return {
rows: [{
id: 1,
title: "Row 1",
checked: false,
},
{
id: 2,
title: "Row 2",
checked: false,
}
]
}
},
template: `
<b-container>
<b-row>
<b-col>
<h3>Items:</h3>
<row-with-cb
v-for="row in rows"
:key="row.id"
v-bind="{
...row
}"
:checked.sync="row.checked"
/>
</b-col>
</b-row>
<hr />
<b-row>
<b-col>
<h4>To keep:</h4>
{{ toKeep }}
</b-col>
<b-col>
<h4>To remove:</h4>
{{ toRemove }}
</b-col>
</b-row>
</b-container>
`
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<div id="app"></div>
NOW WITH VUEX
const initialState = () => ({
rows: [{
id: 1,
title: "Row 1",
checked: false,
},
{
id: 2,
title: "Row 2",
checked: false,
}
]
})
const store = new Vuex.Store({
state: initialState(),
mutations: {
RESET(state) {
const init = initialState()
for (key in init) {
state[key] = init[key]
}
},
SET_TO_REMOVE(state, {
id,
checked
}) {
state.rows = state.rows.map(row => {
if (row.id === id) {
return {
...row,
checked,
}
} else {
return row
}
})
},
REMOVE_SELECTED(state) {
state.rows = state.rows.filter(({
checked
}) => !checked)
},
},
actions: {
reset({
commit
}) {
commit("RESET")
},
setToRemove({
commit
}, data) {
commit("SET_TO_REMOVE", data)
},
removeSelected({
commit
}) {
commit("REMOVE_SELECTED")
},
},
getters: {
getRows: state => state.rows,
getRowsToRemove: state => state.rows.filter(({
checked
}) => checked),
getRowsToKeep: state => state.rows.filter(({
checked
}) => !checked),
}
})
Vue.component('RowWithCb', {
props: ["id", "title", "checked"],
computed: {
isChecked: {
get() {
return this.checked
},
set(val) {
this.$emit("update:checked", {
id: this.id,
checked: val,
})
}
},
},
template: `
<b-row>
<b-col
class="d-flex"
>
<div>
<b-checkbox
v-model="isChecked"
/>
</div>
<div>
{{ title }}
</div>
</b-col>
</b-row>
`
})
new Vue({
el: "#app",
store,
computed: {
rows() {
return this.$store.getters.getRows
},
toRemove() {
return this.$store.getters.getRowsToRemove
},
toKeep() {
return this.$store.getters.getRowsToKeep
},
},
methods: {
handleSetChecked(data) {
this.$store.dispatch("setToRemove", data)
},
handleRemoveItems() {
this.$store.dispatch("removeSelected")
},
handleResetStore() {
this.$store.dispatch("reset")
},
},
template: `
<b-container>
<b-row>
<b-col>
<h3>Items:</h3>
<row-with-cb
v-for="row in rows"
:key="row.id"
v-bind="{
...row
}"
v-on="{
'update:checked': (data) => handleSetChecked(data)
}"
/>
</b-col>
</b-row>
<hr />
<b-row>
<b-col>
<h4>To keep:</h4>
{{ toKeep }}
</b-col>
<b-col>
<h4>To remove:</h4>
{{ toRemove }}
</b-col>
</b-row>
<hr />
<b-row>
<b-col>
<b-button
#click="handleRemoveItems"
>
REMOVE
</b-button>
<b-button
#click="handleResetStore"
>
RESET
</b-button>
</b-col>
</b-row>
</b-container>
`
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app"></div>
You can see that I did not mess with any checkboxes or UI elements (the only difference is that the id is also emitted from the UI component). All the functions were moved to the Vuex store & that's all. (A bit more verbose than the first snippet, as I added the remove & the reset functionality.)
LET'S BREAK IT DOWN
1. You need a component that lists items. In this case, let's use the root component for this:
new Vue({
el: "#app",
data() {
return {
baseItems: [{
id: "item_1",
name: "Item 1",
},
{
id: "item_2",
name: "Item 2",
},
]
}
},
computed: {
items() {
return this.baseItems
},
},
template: `
<ul>
<li
v-for="item in items"
:key="item.id"
>
{{ item.name }}
</li>
</ul>
`
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<div id="app"></div>
I deliberately put items as computed - it's going to be ease to switch to Vuex.
2. You know that your listed items are going to be a bit more complex than this, so let's create a component that can handle their upcoming complexity:
Vue.component('ListItem', {
props: ['id', 'name'],
template: `
<li>
{{ name }}
</li>
`
})
new Vue({
el: "#app",
data() {
return {
baseItems: [{
id: "item_1",
name: "Item 1",
},
{
id: "item_2",
name: "Item 2",
},
]
}
},
computed: {
items() {
return this.baseItems
},
},
template: `
<ul>
<list-item
v-for="item in items"
:key="item.id"
v-bind="{
...item,
}"
/>
</ul>
`
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<div id="app"></div>
3. Let's make them removable - one-by-one or in a bunch:
Vue.component('ListItem', {
props: ['id', 'name', 'toRemove'],
computed: {
selected: {
get() {
return this.toRemove
},
set(val) {
this.$emit('update:to-remove', val)
}
},
},
template: `
<li>
<input
type="checkbox"
v-model="selected"
/>
{{ name }}
</li>
`
})
new Vue({
el: "#app",
data() {
return {
baseItems: [{
id: "item_1",
name: "Item 1",
toRemove: false,
},
{
id: "item_2",
name: "Item 2",
toRemove: false,
},
]
}
},
computed: {
items() {
return this.baseItems
},
},
methods: {
handleRemoveSelected() {
this.baseItems = this.baseItems.filter(item => !item.toRemove)
},
},
template: `
<div>
So, it's easier to follow:
{{ items }}
<hr />
<ul>
<list-item
v-for="item in items"
:key="item.id"
v-bind="{
...item,
}"
:toRemove.sync="item.toRemove"
/>
</ul>
<b-button
#click="handleRemoveSelected"
>
REMOVE SELECTED
</b-button>
</div>
`
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<div id="app"></div>
OK, now there is the function you were after - without Vuex
4. Let's mix in Vuex:
move the list to a Vuex state
move the selection to a Vuex action
move the removal to a Vuex action
// initial state is a function per Vuex best practices
const initialState = () => ({
items: [{
id: "item_1",
name: "Item 1",
toRemove: false,
},
{
id: "item_2",
name: "Item 2",
toRemove: false,
}
]
})
const store = new Vuex.Store({
state: initialState(),
mutations: {
// so you are able to reset the store to
// initial state
RESET(state) {
const init = initialState()
for (key in init) {
state[key] = init[key]
}
},
// this handles the "checked" state if each item
SET_TO_REMOVE(state, {
id,
toRemove,
}) {
// returning a new, modified array
// per FLUX process
state.items = state.items.map(item => {
if (item.id === id) {
return {
...item,
toRemove,
}
} else {
return item
}
})
},
// the same function that was in the app before
REMOVE_SELECTED(state) {
state.items = state.items.filter(({
toRemove
}) => !toRemove)
},
},
actions: {
reset({
commit
}) {
commit("RESET")
},
// action to commit the mutation: select item for removal
setToRemove({
commit
}, data) {
commit("SET_TO_REMOVE", data)
},
// action to commit the mutation: remove items
removeSelected({
commit
}) {
commit("REMOVE_SELECTED")
},
},
getters: {
getItems: state => state.items,
}
})
Vue.component('ListItem', {
props: ['id', 'name', 'toRemove'],
computed: {
selected: {
get() {
return this.toRemove
},
set(val) {
this.$emit('update:to-remove', val)
}
},
},
template: `
<li>
<input
type="checkbox"
v-model="selected"
/>
{{ name }}
</li>
`
})
new Vue({
el: "#app",
store,
data() {
return {
baseItems: [{
id: "item_1",
name: "Item 1",
toRemove: false,
},
{
id: "item_2",
name: "Item 2",
toRemove: false,
},
]
}
},
computed: {
items() {
return this.$store.getters.getItems
},
},
methods: {
handleSetToRemove(data) {
// data should be: { id, toRemove }
this.$store.dispatch("setToRemove", data)
},
handleRemoveSelected() {
this.$store.dispatch("removeSelected")
},
handleResetStore() {
this.$store.dispatch("reset")
},
},
template: `
<div>
So, it's easier to follow:
{{ items }}
<hr />
<ul>
<list-item
v-for="item in items"
:key="item.id"
v-bind="{
...item,
}"
#update:to-remove="(toRemove) => handleSetToRemove({
id: item.id,
toRemove,
})"
/>
</ul>
<b-button
#click="handleRemoveSelected"
>
REMOVE SELECTED
</b-button>
<b-button
#click="handleResetStore"
>
RESET
</b-button>
</div>
`
})
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap/dist/css/bootstrap.min.css" />
<link type="text/css" rel="stylesheet" href="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.css" />
<script src="//polyfill.io/v3/polyfill.min.js?features=es2015%2CIntersectionObserver" crossorigin="anonymous"></script>
<script src="//unpkg.com/vue#latest/dist/vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue.min.js"></script>
<script src="//unpkg.com/bootstrap-vue#latest/dist/bootstrap-vue-icons.min.js"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app"></div>
This is all there's to it.
You handle the selection in the small component - but the change of the selected state is NOT HANDLED inside this component, but emitted to the parent, which in turn handles this by calling a Vuex action. This changes the state in the Vuex store & that "flows" back through the props.
If you click on REMOVE SELECTED, that doesn't look at the components (if they are selected), but calls another Vuex action, that removes all selected items (by filtering) from the state of the Vuex store. As all items are put out from a getter of the Vuex store (and getters update if the underlying state changes), the new state appears in the list.
So, in this case, using Vuex might seem a bit overkill, but if you plan on a more complex state for the listed items, then it may be reasonable.

Vue resets child Component data on props update

I have child component, that has some internal data, that should not be changed from outside. But when I update prop from parent component, this internal data is reset.
Basically in example below, when we change title from outside, value is set back to empty ''. How can I make value persistent with Child component props update?
Child.vue
<template>
<div class="child">
<h2>{{title}}</h2>
<input type="text" :value="value" v-on:change="$emit('change', $event.target.value)">
</div>
</template>
<script>
export default {
props: {
title: {
default: 'Just title'
}
},
data() {
return {
value: ''
}
}
}
</script>
Parent.vue
<template>
<div class="parent">
<Child :title="title" v-on:change="processTitle($event)"></Child>
</div>
</template>
<script>
import Child from './Child';
export default {
data() {
return {
title: 'Title from parent'
}
},
methods: {
processTitle(value) {
this.title = value.reverse();
}
}
}
</script>
You are not setting the value data attribute, :value=value means that "if value changes, the input value should pick up that change". But value doesn't change. Use v-model instead if you want to keep it simple.
Vue.component("Child", {
props: {
title: {
default: 'Just title'
}
},
data() {
return {
value: ''
}
},
template: `
<div class="child">
<h2>{{title}}</h2>
<input type="text" v-model="value" v-on:change="$emit('change', $event.target.value)">
</div>
`
})
new Vue({
el: "#app",
data() {
return {
title: 'Title from parent'
}
},
methods: {
processTitle(value) {
this.title = value.split("").reverse().join("");
}
},
template: `
<div class="parent">
<child :title="title" v-on:change="processTitle($event)"></child>
</div>
`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>
EDIT
Also, if you want a continuous effect, don't use #change - use #input instead:
Vue.component("Child", {
props: {
title: {
default: 'Just title'
}
},
data() {
return {
value: ''
}
},
template: `
<div class="child">
<h2>{{title}}</h2>
<input type="text" v-model="value" v-on:input="$emit('change', $event.target.value)">
</div>
`
})
new Vue({
el: "#app",
data() {
return {
title: 'Title from parent'
}
},
methods: {
processTitle(value) {
this.title = value.split("").reverse().join("");
}
},
template: `
<div class="parent">
<child :title="title" v-on:change="processTitle($event)"></child>
</div>
`
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

How to use v-model for both form and populate the dropdown

I want to do two tasks using a v-select which is having dynamic items to load.
1. want to bind item-text with form to insert.
2. want to get item-id while select the option.
My code :
<v-select
:items="titles"
item-text="ocmtitle"
item-value="id"
v-model="ocmform.title"
label="Ocm Title"
#change="getOcmsubtitles()"
outline
></v-select>
You should use multi-select for v-model the element. Here is an example.
new Vue({
components: {
Multiselect: window.VueMultiselect.default
},
data: {
value: '',
options: [{
name: 'Vue.js',
language: 'JavaScript'
},
{
name: 'Rails',
language: 'Ruby'
},
{
name: 'Sinatra',
language: 'Ruby'
},
{
name: 'Laravel',
language: 'PHP',
$isDisabled: true
}
]
},
methods: {}
}).$mount('#app')
* {
font-family: 'Lato', 'Avenir', sans-serif;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.min.js"></script>
<script src="https://unpkg.com/vue-multiselect#2.1.0"></script>
<link rel="stylesheet" href="https://unpkg.com/vue-multiselect#2.1.0/dist/vue-multiselect.min.css">
<div id="app">
<multiselect v-model="value" :options="options" placeholder="Pick some" label="name" track-by="name" id="example">
</multiselect>
<pre class="language-json"><code>{{ value }}</code></pre>
</div>

How to set an attribute on a v-select option?

Just starting with Vue and need some advice on best practices.
What I'm trying to achieve: set makeId equal to the makes option id, based on what option is selected. So, for example, when I select "bmw" from the dropdown, I want makeId to equal 2.
Without Vuetify, I cloud set a data attribute to each option during the v-for loop, and then #change just grab that attribute and assign the value to the makeid.
How can I achieve what I'm trying to do with Vuetify?
<v-select v-model="car.make" :items="makes" item-value="name" item-text="name"> </v-select>
data: function() {
return {
car: { make: "", makeId: "" },
makes: [{ name: "audi", id: "1" }, { name: "bmw", id: "2" }]
};
}
UPDATED:
you can bind the id as the item-value and on the v-model you need to assign car.madeIdinstead of car.make if you need a single value:
<v-select v-model="car.makeId" :items="makes" item-value="id" item-text="name"> </v-select>
Or you can use an extra method for object binding :
here is a fork for it: codepen
The main difficulty here is that you've got two different formats for your objects. Having to map between id/name and make/makeId complicates things.
This would be easy without the property mapping, you could just set return-object on the v-select.
Assuming that isn't possible, one alternative would be to have car as a computed property based on the selected value to perform the property renaming. This would look like this:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
selectedCar: null,
makes: [{name: 'audi', id: '1'}, {name: 'bmw', id: '2'}]
}
},
computed: {
car () {
const selectedCar = this.selectedCar
if (selectedCar) {
return {
make: selectedCar.name,
makeId: selectedCar.id
}
}
}
}
})
#app {
padding: 10px;
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://unpkg.com/mdi#2.2.43/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://unpkg.com/vuetify#2.0.5/dist/vuetify.min.css" rel="stylesheet">
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#2.0.5/dist/vuetify.min.js"></script>
<div id="app">
<v-app>
<p>{{ car }}</p>
<v-select v-model="selectedCar" :items="makes" item-value="name" item-text="name" return-object> </v-select>
</v-app>
</div>
We could instead flip around the relationship between car and selectedCar so that car is in data and selectedCar is the computed property. For that we'd need to implement a getter and a setter:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
car: {make: '', makeId: ''},
makes: [{name: 'audi', id: '1'}, {name: 'bmw', id: '2'}]
}
},
computed: {
selectedCar: {
get () {
return this.makes.find(make => make.id === this.car.makeId)
},
set (selectedCar) {
this.car = {
make: selectedCar.name,
makeId: selectedCar.id
}
}
}
}
})
#app {
padding: 10px;
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://unpkg.com/mdi#2.2.43/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://unpkg.com/vuetify#2.0.5/dist/vuetify.min.css" rel="stylesheet">
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#2.0.5/dist/vuetify.min.js"></script>
<div id="app">
<v-app>
<p>{{ car }}</p>
<v-select v-model="selectedCar" :items="makes" item-value="name" item-text="name" return-object> </v-select>
</v-app>
</div>
A third alternative is to ditch v-model altogether and just use the :value/#input pair directly:
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
car: {make: '', makeId: ''},
makes: [{name: 'audi', id: '1'}, {name: 'bmw', id: '2'}]
}
},
methods: {
onInput (id) {
const make = this.makes.find(make => make.id === id)
this.car = {
make: make.name,
makeId: make.id
}
}
}
})
#app {
padding: 10px;
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://unpkg.com/mdi#2.2.43/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://unpkg.com/vuetify#2.0.5/dist/vuetify.min.css" rel="stylesheet">
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#2.0.5/dist/vuetify.min.js"></script>
<div id="app">
<v-app>
<p>{{ car }}</p>
<v-select :value="car.makeId" :items="makes" item-value="id" item-text="name" #input="onInput"> </v-select>
</v-app>
</div>

Vue.js and Multiselect - The name 'input' does not exist in the current context

I'm trying to start a method after the value of the component Vue Multiselect has been changed using #input, but I'm getting the following compilation error:
CS0103: The name 'input' does not exist in the current context
Here's my multiselect:
<multiselect v-model="Instalacao.value" label="Serie" track-by="Serie" placeholder="Nº de série" :options="Instalacoes"
:multiple="false" :searchable="true" :allow-empty="false" :disabled="Editando" #input="getTecnicosByRepresentante">
<span slot="noResult">Nenhum técnico encontrado</span>
</multiselect>
This example works as expected: both the watch and the #input handler execute when you select a value. Your problem is probably not in the code that you included here.
new Vue({
el: '#app',
components: {
Multiselect: window.VueMultiselect.default
},
data: {
Instalacao: {
value: null
},
Instalacoes: [{
Serie: 'one',
value: 'Vue.js'
},
{
Serie: 'two',
value: 'Vue-Multiselect'
},
{
Serie: 'three',
value: 'Vuelidate'
}
]
},
watch: {
'Instalacao.value': function(newValue) {
console.log('Updated', newValue);
}
},
methods: {
getTecnicosByRepresentante() {
console.log("Input detected, too");
}
}
})
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<link href="https://unpkg.com/vue-multiselect#latest/dist/vue-multiselect.min.css" rel="stylesheet" />
<script src="https://unpkg.com/vue-multiselect#latest/dist/vue-multiselect.min.js"></script>
<div id="app">
<multiselect v-model="Instalacao.value" label="Serie" track-by="Serie" placeholder="Nº de série" :options="Instalacoes" :multiple="false" :searchable="true" :allow-empty="false" #input="getTecnicosByRepresentante">
<span slot="noResult">Nenhum técnico encontrado</span>
</multiselect>
<pre>{{ Instalacao.value }}</pre>
</div>