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.
I have a scenario like I have to list a few hundred categories and I have to show them in a select box. Since the list is huge, skip and limit is implemented in the backend, so that it will limit the categories to 20s. My case is like when the user sees the first 20 categories, in the end, I have to add some button stating like 'Load more' so that when the user clicks on it, they can see the next 20 categories. But I have no idea how to add a button in a vuetify select. Can someone help me with this?
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
fruits: [
'Item 1',
'Item 2',
'Item 3',
'Item 4'
],
selectedFruits: [],
}),
computed: {
likesAllFruit () {
return this.selectedFruits.length === this.fruits.length
},
likesSomeFruit () {
return this.selectedFruits.length > 0 && !this.likesAllFruit
},
icon () {
if (this.likesAllFruit) return 'mdi-close-box'
if (this.likesSomeFruit) return 'mdi-minus-box'
return 'mdi-checkbox-blank-outline'
},
},
methods: {
toggle () {
this.$nextTick(() => {
if (this.likesAllFruit) {
this.selectedFruits = []
} else {
this.selectedFruits = this.fruits.slice()
}
})
},
loadMore () {
console.log('load more ...')
}
},
})
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Material+Icons" 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.2.26/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/babel-polyfill/dist/polyfill.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.2.26/dist/vuetify.min.js"></script>
<div id="app">
<v-app id="inspire">
<v-container fluid>
<v-select
v-model="selectedFruits"
:items="fruits"
label="Favorite Fruits"
multiple
>
<template v-slot:prepend-item>
<v-list-item
ripple
#click="toggle"
>
<v-list-item-action>
<v-icon :color="selectedFruits.length > 0 ? 'indigo darken-4' : ''">{{ icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>Select All</v-list-item-title>
</v-list-item-content>
</v-list-item>
<v-divider class="mt-2"></v-divider>
</template>
<template v-slot:append-item>
<v-divider class="mb-2"></v-divider>
<v-btn color="primary" #click="loadMore">
Click me for load more items.
</v-btn>
</template>
</v-select>
</v-container>
</v-app>
</div>
In Vuetify 2.x there is an virtual scroll component, but I still found the v-intersect easiest to use...
markup:
<v-autocomplete
v-model="selected"
:items="beers"
item-text="name"
item-value="id"
label="Search da beers.."
return-object
autocomplete="off"
>
<template v-slot:append-item>
<div v-intersect="onIntersect" class="pa-4 teal--text">
Loading more items ...
</div>
</template>
</v-autocomplete>
data() {
return {
beers: [],
selected: null,
perPage: 30,
page: 1,
}
},
methods: {
getBeers() {
console.log('page', this.page)
const apiUrl = `https://api.punkapi.com/v2/beers?page=${this.page}&per_page=${this.perPage}`
axios.get(apiUrl)
.then(response => {
this.beers = [
...this.beers,
...response.data
]
})
},
onIntersect () {
console.log('load more...')
this.page += 1
this.getDate()
},
},
created() {
this.getData()
}
Working Codeply
I am new to Vue.js.
list.vue:
<template>
<div class="m-products-list">
<ul #mouseover="over">
<Item
v-for="(item,idx) in parentList"
location="item.location"
:key="idx"
:meta="item"/>
</ul>
</div>
</template>
<script>
export default {
...
methods: {
over: function (e) {
let dom = e.target;
let tag = dom.tagName.toLowerCase();
if (tag === 'dd') {
console.log(dom.getAttribute('location'))
}
}
}
}
</script>
The Item is from its parent component. And I want to get item.location in over() when I mouseover an item, but console.log always returns null. Anyone have an idea?
This is technically possible (but there may be a better alternative shown in the next section) by setting a data-* attribute in the Item.
// Item.vue
<li :data-location="location" class="item" ... >
new Vue({
el: '#app',
data() {
return {
items: [
{id: 1, location: 'New York'},
{id: 2, location: 'Los Angeles'},
{id: 3, location: 'Chicago'},
]
}
},
components: {
Item: {
props: ['location'],
template: `<li :data-location="location" class="item">{{location}}</li>`,
}
},
methods: {
over(e) {
console.log(e.target.dataset.location)
}
}
})
<script src="https://unpkg.com/vue#2.6.7/dist/vue.min.js"></script>
<div id="app">
<ul #mouseover="over">
<Item v-for="item in items"
:key="item.id"
:location="item.location" />
</ul>
</div>
A better solution that doesn't require DOM manipulation would be to use the data model in Vue and to move the mouseover event listener to the Item:
Change the argument of over() to the location name (previously the event object):
methods: {
over(location) {
/* ... */
}
}
Move the #mouseover event-listener annotation from ul to the Item in the template, and pass the item.location as an argument:
<ul>
<Item v-for="item in items" #mouseover="over(item.location)" ... />
</ul>
Edit the Item's template to forward its mouseover event to the parent:
// Item.vue
<li #mouseover="$emit('mouseover', $event)" ... >
new Vue({
el: '#app',
data() {
return {
items: [
{id: 1, location: 'New York'},
{id: 2, location: 'Los Angeles'},
{id: 3, location: 'Chicago'},
]
}
},
components: {
Item: {
props: ['location'],
template: `<li #mouseover="$emit('mouseover', $event)" class="item">{{location}}</li>`,
}
},
methods: {
over(location) {
console.log(location)
}
}
})
<script src="https://unpkg.com/vue#2.6.7/dist/vue.min.js"></script>
<div id="app">
<ul>
<Item v-for="item in items"
:key="item.id"
:location="item.location"
#mouseover="over(item.location)" />
</ul>
</div>