Not able to access data variable in script but can in html - vue.js

I have populated a data variable with an array, and can access its contents by using a v-for in the html, but I can't access any of the data in the variable within the script, and I don't know why.
var result = [{
"CatalogName": "Retro Doors",
"ItemName": "French Doors",
"ItemListPrice": "$461.00",
"ItemType": "Oak",
"ItemFeatures": [{
"Features": "Door Quantity",
"QTY": 2
},
{
"Features": "Door Hinges",
"QTY": 4
},
{
"Features": "Door Knobs",
"QTY": 1
},
{
"Features": "Door Looks",
"QTY": 1
},
{
"Features": "Glass Panes",
"QTY": 2
}
]
}];
new Vue({
el: '#app',
beforeCreate: function() {
console.log("Before Created");
},
created: function() {
console.log("Created");
this.GetItemsList();
},
beforeMount: function() {
console.log("Before Mount");
},
data: {
itemPriceList: []
},
methods: {
GetItemsList() {
this.itemPriceList = result;
}
},
mounted: function() {
console.log("Mounted");
console.log(this.ItemPriceList);
}
});
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.17/dist/vue.js"></script>
<div id="app">
{{ itemPriceList[0].CatalogName }}
<div v-for="item in itemPriceList">
{{ item.ItemName }}
<div v-for="items in item.ItemFeatures">
{{ items.Features }} : {{ items.QTY }}
</div>
</div>
</div>

You have a typo in a variable name inside mounted hook - used with capital I.
In data: itemPriceList
In mounted hook: this.ItemPriceList
Should be the same as defined inside data property.

Related

How to debug select menu not selected?

I have this select menu:
<v-select
dense
outlined
item-text="name"
item-value="id"
:items="frequencies"
v-model="frequency"
label="Frequency"
></v-select>
& I kept seeing this:
If I print it out it seems to contain correct value:
{{ frequency }} {{ frequencies }}
Hourly
[
{
"id": 1,
"name": "Hourly"
},
{
"id": 2,
"name": "Daily"
},
{
"id": 3,
"name": "Weekly"
},
{
"id": 4,
"name": "Monthly"
},
{
"id": 5,
"name": "Perpetual"
}
]
Why my select didn’t select Hourly as selected value?
Here's a live, working demo using your example.
Or, for a shortened version here:
// Hide Vue dev warnings, just for the demo
Vue.config.devtools = false;
Vue.config.productionTip = false;
// Demo Vue app, using vuetify
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
frequency: null, // ID of currently selected frequenct
frequencies: [ // List of values to display in the dropdown
{ id: 1, name: "Hourly" },
{ id: 2, name: "Daily" },
{ id: 3, name: "Weekly" },
{ id: 4, name: "Monthly" },
{ id: 5, name: "Perpetual" },
{ id: "XX", name: "Broken" },
],
}),
})
p.demo { font-size: 1.5rem; text-align: center; background: yellow; }
p.demo.init { background: aqua; }
p.demo.broke { background: hotpink; }
<script src="https://unpkg.com/vue#2.x/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#2.6.5/dist/vuetify.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/vuetify#2.6.5/dist/vuetify.min.css"/>
<link rel="stylesheet" href="https://unpkg.com/#mdi/font#6.x/css/materialdesignicons.min.css"/>
<div id="app">
<v-app id="inspire">
<v-container fluid>
<!-- v-select, from your question -->
<v-select
dense
outlined
item-text="name"
item-value="id"
:items="frequencies"
v-model="frequency"
label="Frequency"
></v-select>
Selected Frequency : {{ frequency }}
<!-- Display what's selected, just for the demo -->
<p class="demo" v-if="frequency && frequencies[frequency - 1]">
Frequency: {{ frequency }} ({{ frequencies[frequency - 1].name }})
</p>
<p class="demo broke" v-else-if="frequency">
Invalid Frequency Selected ({{ frequency }})
</p>
<p class="demo init" v-else>No value selected yet</p>
</v-container>
</v-app>
</div>
I am not sure what issue you are facing but label is not showing on select any option from the <v-select>. I created a sample code snippet for the reference, Can you please have a look and let me know if any further assistance required.
Demo :
new Vue({
el: '#app',
vuetify: new Vuetify(),
data: () => ({
frequency: null,
frequencies: [
{
"id": 1,
"name": "Hourly"
},
{
"id": 2,
"name": "Daily"
},
{
"id": 3,
"name": "Weekly"
},
{
"id": 4,
"name": "Monthly"
},
{
"id": 5,
"name": "Perpetual"
}
]
}),
})
<script src="https://unpkg.com/vue#2.x/dist/vue.js"></script>
<script src="https://unpkg.com/vuetify#2.6.5/dist/vuetify.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/vuetify#2.6.5/dist/vuetify.min.css"/>
<link rel="stylesheet" href="https://unpkg.com/#mdi/font#6.x/css/materialdesignicons.min.css"/>
<div id="app">
<v-app id="inspire">
<v-container fluid>
<v-select
:items="frequencies"
item-text="name"
item-value="id"
label="Frequency"
v-model="frequency"
outlined
></v-select>
Selected Frequency : {{ frequency }}
</v-container>
</v-app>
</div>

how do i modify, then filter my object property in vuejs

I have an object that comes into my model. I want break the properties up into different sections into the model- so that i can split up the content. I never worked with an object before in this context, but arrays so not sure what is the best way to approach this. So i just want to strip the returned data to just display "ab1e0yeeieieiddk", BUT inside of the model in its designated area.
new Vue({
el: "#app",
data: {
ticket:{"token_type":"Bearer","expires_in":"86399","not_before":"1649632168","expires_on":"1649718868","resource":"00000003-0000-0ff1-ce00-000000000000/contesto.sharepoint.com#ab1e0yeeieieiddk","access_token":"eydsdsadasddefdfdfjfkjnfkflklffjdslnkdsfdisfnlkfmdsjfkfmkdsfjnfjdsfn"},
bearer:"",//taken from all the characters after the #//should be ab1e0yeeieieiddk
access:""//taken from access token. Should be eydsdsadasddefdfdfjfkjnfkflklffjdslnkdsfdisfnlkfmdsjfkfmkdsfjnfjdsfn
},
methods: {
toggle: function(todo){
todo.done = !todo.done
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Ticket Status:</h2>
<p v-if="ticket.resource">{{ticket.resource}}
</p>
{{bearer}}<p v-if="ticket.access_token">{{ticket.access_token}}
</p><br>
{{access}}
</div>
You can split the text simply in the template section like this:
<p v-if="ticket.resource">{{ticket.resource.split('#')[1]}}</p>
new Vue({
el: "#app",
data: {
ticket: {
"token_type": "Bearer",
"expires_in": "86399",
"not_before": "1649632168",
"expires_on": "1649718868",
"resource": "00000003-0000-0ff1-ce00-000000000000/contesto.sharepoint.com#ab1e0yeeieieiddk",
"access_token": "eydsdsadasddefdfdfjfkjnfkflklffjdslnkdsfdisfnlkfmdsjfkfmkdsfjnfjdsfn"
},
bearer: "", //taken from all the characters after the #//should be ab1e0yeeieieiddk
access: "" //taken from access token. Should be eydsdsadasddefdfdfjfkjnfkflklffjdslnkdsfdisfnlkfmdsjfkfmkdsfjnfjdsfn
},
methods: {
toggle: function(todo) {
todo.done = !todo.done
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Ticket Status:</h2>
<p v-if="ticket.resource">{{ticket.resource.split('#')[1]}}</p>
{{bearer}}
<p v-if="ticket.access_token">{{ticket.access_token}}
</p><br> {{access}}
</div>
Or you can create a computed property and convert your data to anything you want and use it in the template:
<p v-if="ticket.resource">{{resourceToShow}}</p>
computed: {
resourceToShow() {
return this.ticket.resource.split('#')[1];
},
},
new Vue({
el: "#app",
data: {
ticket: {
"token_type": "Bearer",
"expires_in": "86399",
"not_before": "1649632168",
"expires_on": "1649718868",
"resource": "00000003-0000-0ff1-ce00-000000000000/contesto.sharepoint.com#ab1e0yeeieieiddk",
"access_token": "eydsdsadasddefdfdfjfkjnfkflklffjdslnkdsfdisfnlkfmdsjfkfmkdsfjnfjdsfn"
},
bearer: "", //taken from all the characters after the #//should be ab1e0yeeieieiddk
access: "" //taken from access token. Should be eydsdsadasddefdfdfjfkjnfkflklffjdslnkdsfdisfnlkfmdsjfkfmkdsfjnfjdsfn
},
computed: {
resourceToShow() {
return this.ticket.resource.split('#')[1];
},
},
methods: {
toggle: function(todo) {
todo.done = !todo.done
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Ticket Status:</h2>
<p v-if="ticket.resource">{{resourceToShow}}</p>
{{bearer}}
<p v-if="ticket.access_token">{{ticket.access_token}}
</p><br> {{access}}
</div>

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.

VueJS click events in compoments

I've made a component (is that overkill?) to call recursively in order to render a tree, but it seems to be impossible to handle click events because stuff rendered inside the component can't see the method I want in the Vue's methods; this appears to be some monstrous complication about components and events.
How can I handle these click events?
Does it really have to be so difficult?
<script type="text/html" id="template-tree">
<ul>
<c-tree-item v-for="item in items" v-bind:item="item"></c-tree-item>
</ul>
</script>
<script type="text/html" id="template-tree-item">
<li v-on:click="clickTreeItem"> <!-- click doesn't work :( -->
<p> {{ item.Name }}</p>
<ul v-if="item.Items.length > 0">
<c-tree-item v-for="i in item.Items" v-bind:item="i"></c-tree-item>
</ul>
</li>
</script>
<script type="text/javascript">
var vueApp = undefined;
var ct = Vue.component('c-tree', {
template: document.getElementById('template-tree').innerHTML,
props: ['items']
});
var cti = Vue.component('c-tree-item', {
template: document.getElementById('template-tree-item').innerHTML,
props: ['item']
});
$(document).ready(function () {
var router = new VueRouter({
mode: 'history',
routes: []
});
vueApp = new Vue({
router,
el: '#vue',
data: {
tree: JSON.parse(document.getElementById('data').innerHTML)
},
methods: {
clickTreeItem: function(event) {
console.log('click');
console.log(JSON.stringify(event));
}
}
});
});
</script>
<div id="vue">
<c-tree v-bind:items="tree"></c-tree>
</div>
A component can only raise events -- which may have data associated with them.
A component has to listen to it's sub-components' events and may, in turn, pass them upwards as its own events.
Example: https://jsfiddle.net/gkch7bnr/1/
StackOverflow is stroppy about JSFiddle so here is some code:
<head>
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo="
crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.11/vue.common.dev.js" integrity="sha256-soI/D3XnqcarOMK229d8GWs8P+gYViEsbWBeMaRoSPk=" crossorigin="anonymous"></script>
<script type="application/json" id="data">
[
{"Id": 1, "Name": "Name1", "Items": []},
{"Id": 2, "Name": "Name2", "Items": []},
{"Id": 3, "Name": "Name3", "Items": [
{"Id": 31, "Name": "Name31"},
{"Id": 32, "Name": "Name32"},
{"Id": 33, "Name": "Name33"}
]}
]
</script>
<script type="text/html" id="template-tree">
<ul>
<c-tree-item v-for="item in items" v-bind:item="item" v-on:click="$emit('click', $event)"></c-tree-item>
</ul>
</script>
<script type="text/html" id="template-tree-item">
<li>
<p v-on:click="$emit('click', item)" > {{ item.Name }}</p>
<ul v-if="item.Items && item.Items.length > 0">
<c-tree-item v-for="i in item.Items" v-bind:item="i" v-on:click="$emit('click', $event)"></c-tree-item>
</ul>
</li>
</script>
</head>
<body>
<div id="vue">
<c-tree v-bind:items="tree" v-on:click="clickTreeItem"></c-tree>
</div>
</body>
var vueApp = undefined;
var ct = Vue.component('c-tree', {
template: document.getElementById('template-tree').innerHTML,
props: ['items'],
methods: {
clickTI: function(e) { console.log('clickTI: ' + e.Name);}
}
});
var cti = Vue.component('c-tree-item', {
template: document.getElementById('template-tree-item').innerHTML,
props: ['item']
});
$(document).ready(function () {
vueApp = new Vue({
el: '#vue',
data: {
tree: JSON.parse(document.getElementById('data').innerHTML)
},
methods: {
clickTreeItem: function(event) {
console.log('clickTreeItem: ' + event.Name);
}
}
});
});

How to iterate nested nested json array?

I am trying to iterate nested json array, but I can't figure out how to do it. Here is my code:
<script src="https://unpkg.com/vue"></script>
<div id="app">
<div v-for="agreg in mydata.aggregated">
<p>
<!-- I need to iterate over nested i.e: s_inn, s_kpp -->
</p>
</div>
</div>
App code:
new Vue({
el: '#app',
data: {
mydata: [{
"t_registration_number": "0177200000918001354",
"aggregated": [{
"aaa": "8414440",
"bbb": "95101",
"nested": [{
"s_inn": "1111111",
"s_kpp": "2222222"
}
]
}, {
"aaa": "45770520",
"bbb": "04641",
"nested": [{
"s_inn": "3333333",
"s_kpp": "4444444"
}
]
}
]
}
]
}
})
https://jsfiddle.net/k37e28sp/1/
mydata itself is an array, it doesn't have a property called aggregated, it has a list of objects which contain aggregated. So you have to first loop over mydata.
Js Fiddle: https://jsfiddle.net/3r9aefqu/
Template
<script src="https://unpkg.com/vue"></script>
<div id="app">
<!-- For each object in my mydata -->
<div v-for="data in mydata">
{{ data.t_registration_number }}
<!-- For each aggregated inside each of mydata -->
<div v-for="agg in data.aggregated">
aaa :{{ agg.aaa }} bbb: {{ agg.bbb }}
<div v-for="n in agg.nested">
s_inn : {{n.s_inn}} s_kpp: {{ n.s_kpp }}
</div>
=======================
</div>
</div>
</div>
Vue
new Vue({
el: '#app',
data: {
mydata: [{
"t_registration_number": "0177200000918001354",
"aggregated": [{
"aaa": "8414440",
"bbb": "95101",
"nested": [{
"s_inn": "1111111",
"s_kpp": "2222222"
}]
}, {
"aaa": "45770520",
"bbb": "04641",
"nested": [{
"s_inn": "3333333",
"s_kpp": "4444444"
}]
}]
}]
}
})
Output
0177200000918001354
aaa :8414440 bbb: 95101
s_inn : 1111111 s_kpp: 2222222
=======================
aaa :45770520 bbb: 04641
s_inn : 3333333 s_kpp: 4444444
=======================