How can I access nested data via an array with parsed json - javascript

I am creating a vuetify simple table that is going to display various data elements. The problem is, some of those elements are based on relationships and nested. Getting the top level data is fine, and if I pull the nested data as a standalone, it works fine as well.
However, what I want to do is utilize an array to avoid repetitive html code for the table. Is this possible at all?
Below is the code as constructed for the table itself.
HTML:
<v-simple-table fixed-header height="300px">
<template v-slot:default>
<thead>
<tr>
<th class="text-left">
Attribute
</th>
<th class="text-left">
Value
</th>
</tr>
</thead>
<tbody>
<tr
v-for="(serviceProperty, idx) in serviceProperties"
:key="idx">
<th>{{ serviceProperty.label }}</th>
<td>{{ service[serviceProperty.value] }}</td>
</tr>
</tbody>
</template>
</v-simple-table>
JS:
export default {
name: "Details",
data() {
return {
loading: true,
service: {},
serviceProperties: [
{
label: 'Description',
value: 'description'
},
{
label: 'Location',
value: 'organization.locations[1].streetAddress'
},
{
label: 'EIN',
value: 'organization.EIN'
}
]
};
},
props: ["serviceId"],
async created() {
this.service = await Vue.$serviceService.findOne(this.serviceId);
this.loading = false;
},
};

This seems unnecessarily complicated.
Consider using computed, like this
...
computed: {
mappedData() {
return this.service.map(item => {
Description: item.Description,
Location: item.organization.locations[1].streetAddress,
EIN: item.organization.EIN
})
}
}
...
You can then access the data in the template with:
...
<element v-for="item in mappedData">
{{item.Description}}
{{item.Location}}
{{item.EIN}}
</element>
...

Related

Call a function with customized datatable in Vuejs

I just started with Vuejs (Composition API) and trying to create my own kind of "datatable".
Managed to create the columns and the rows and all is great (event the call via ajax).
But now I have some kind of problem which I cant figure out how to fix.
I'm calling the Datatable component like this:
<DataTable :config="{
data: {
type: 'remote',
url: '/api/categories/get'
},
columns: [
{
field: 'id',
title: '#'
},
{
field: 'name',
title: 'Name'
},
{
field: 'order',
title: 'Placement'
},
{
field: 'actions',
title: 'Actions',
template: 'SOME HTML & CALL FUNCTION'
}
]
}" />
as you can see in the last column there is a new key called 'template'.
the point is to create an "html" value, like buttons and stuff like that.
On the Datatable component I'm checking to see if there is "template" key, and if it's exists I want to display it
I did something like that:
<template v-if="column.template">{{ HTML }}</template>
I managed to show the buttons/everything else. the problem that I cant make the button to call any function.
Let's say I want to add delete button, something like that:
field: "actions",
title: "Actions",
template: () => "<button #click="delete(id)" >delete</button>"
How can I make it work?
if I'm not using it's correctly, would love to hear & learn how to do it right.
Thanks in advance
You can use scoped slots to render some templates that also have access to the state of the child component.
So you would define a slot in your table that have the name of the column <slot v-bind="item" :name="column.name" /> and the v-bind="item" would allow you to access the row data in the slot definition in the parent.
<template>
<table>
<thead>
<tr>
<th v-for="column in columns">{{column.title}}</th>
</tr>
</thead>
<tbody>
<tr v-for="item in items">
<td v-for="column in columns">
<template v-if="column.name">{{item[column.name]}}</template>
<slot v-bind="item" :name="column.name"></slot>
</td>
</tr>
</tbody>
</table>
</template>
<script setup lang="ts">
defineProps<{
items: any[],
columns : {title? : string, name? : string}[]
}>()
</script>
<style>
th {
font-weight: bold;
}
</style>
And then use it like this
<script setup lang="ts">
import { ref } from 'vue';
import MyTable from './components/MyTable.vue';
const items = ref([{msg : 'Hello' }, { msg: 'World' }]);
</script>
<template>
<main>
<MyTable :items="items" :columns="[{ title : 'Message', name: 'msg' }, { name: 'delete'}]">
<template #delete="item">
<button #click="items.splice(items.findIndex(x => x.msg === item.msg), 1)">Delete</button>
</template>
</MyTable>
</main>
</template>

Are there a way to overwrite increment/decrement-handling for a input of type number?

What i would like to do is manipulate the built-in html input buttons for increment and decrement of numbers. If there is a "vue-way" of doing this, that would of course be preferred.
First of all i'm working on a small vue-app that i've created for learning Vue, what i got now is a Vuex store which contains the state and methods for a shopping cart. I have bound the value item.itemCount seen in the image, to the value of the inputfield. But i would like the increment/decrement buttons to actually update the vuex-state in a proper way.
<input
class="slim"
type="number"
v-model.number="item.itemCount"
/>
I understand that i can just stop using a input-field, and create my own "count-view" + two buttons, but i'm curious if it's possible to do something like this.
UPDATE
Shoppingcart.vue
<template>
<div class="sliding-panel">
<span class="header">Shopping Cart</span>
<table>
<thead>
<th>Item Name</th>
<th>Count</th>
<th>Remove</th>
</thead>
<transition-group name="fade">
<tr v-for="item in items" :key="item.id">
<td>{{ item.name }}</td>
<td>
<input class="slim" type="number" v-model.number="item.itemCount" />
</td>
<td><button #click="removeProductFromCart(item)">Remove</button></td>
</tr>
</transition-group>
<tr>
Current sum:
{{
sum
}}
of
{{
count
}}
products.
</tr>
</table>
</div>
</template>
<script>
import { mapState, mapActions } from "vuex";
export default {
computed: mapState({
items: (state) => state.cart.items,
count: (state) => state.cart.count,
sum: (state) => state.cart.sum,
}),
methods: mapActions("cart", ["removeProductFromCart"]),
};
</script>
<style>
</style>
First you don't need to "overwrite increment/decrement handling" in any way. You have the <input> so you need to handle all user inputs changing value - be it inc/dec buttons or user typing value directly...
Proper way of updating Vuex state is by using mutations. So even it's technically possible to bind v-model to some property of object stored in Vuex (as you do), it's not correct "Vuex way"
If there is only single value, you can use computed prop like this:
computed: {
myValue: {
get() { return this.$store.state.myValue },
set(value) { this.$store.commit('changemyvalue', value )} // "changemyvalue" is defined mutation in the store
}
}
...and bind it to input
<input type="number" v-model="myValue" />
But because you are working with array of values, it is more practical to skip v-model entirely - in the end v-model is just syntactic sugar for :value="myValue" #input="myValue = $event.target.value"
In this case
<input type="number" :value="item.itemCount" min="1" #input="setItemCount({ id: item.id, count: $event.target.value})"/>
...where setItemCount is mutation created to change item count in the cart
Working example:
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
items: [
{ id: 1, name: 'Socks', itemCount: 2},
{ id: 2, name: 'Trousers', itemCount: 1}
]
},
mutations: {
setItemCount(state, { id, count }) {
const index = state.items.findIndex((item) => item.id === id);
if(index > -1) {
const item = state.items[index]
item.itemCount = count;
console.log(`Changing count of item '${item.name}' to ${count}`)
}
}
}
})
const app = new Vue({
store,
template: `
<div>
<span>Shopping Cart</span>
<table>
<thead>
<th>Item Name</th>
<th>Count</th>
<th>Remove</th>
</thead>
<transition-group name="fade">
<tr v-for="item in items" :key="item.id">
<td>{{ item.name }}</td>
<td>
<input type="number" :value="item.itemCount" min="1" #input="setItemCount({ id: item.id, count: $event.target.value})"/>
</td>
<td><button>Remove</button></td>
</tr>
</transition-group>
</table>
</div>
`,
computed: {
...Vuex.mapState({
items: (state) => state.items,
})
},
methods: {
...Vuex.mapMutations(['setItemCount'])
}
})
app.$mount("#app")
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.12/vue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vuex/3.5.1/vuex.min.js"></script>
<div id="app"> </div>

Vuetify checkboxes are prechecked when getting data from Vuex

I have a problem with Vuetify checkboxes and Vuex.
What I want: Checkboxes are not check, an when any box is checked, the person-object is pushed to the selected-array. That is how it works in the Vuetify docs.
The checkboxes are checked even though are they v-modelled to an empty arrray. The table with checkboxes are populated via a getter in a computed value and to first get data into the state, I dispatch an action in the created lifecycle.
In the beginning when I just used static data, the checkboxes and other functions worked fine, but when I started using async data from Vuex, the checkboxes started being prechecked.
If I comment out the dispatch command in the created lifecycle, the checkboxes are not prechecked. Of course the table wont have data from the getter if the state in Vuex doesnt have data. But if I go to another page where the dispatch has been issued and go back to the table page, the getter now gets the data from state and checkboxes are not prechecked.
Anyone got a clue of why this is happening?
<template>
<v-simple-table>
<template v-slot:default>
<thead>
<tr>
<th #click="selectAll">Select</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr v-for="(person, index) in getPersons" :key="index">
<td>
<v-checkbox v-model="selected" :value="person"></v-checkbox>
</td>
<td>{{ person.firstName }}</td>
</tr>
</tbody>
</template>
</v-simple-table>
</template>
<script>
export default {
created() {
this.$store.dispatch("activePersons");
},
data() {
return {
selected: []
};
},
computed: {
getPersons() {
const data = [
{
firstName: "John"
},
{
firstName: "James"
}
];
console.log(data);
let test = this.$store.getters.getList;
return test;
}
},
methods: {
selectAll() {
if (this.selected.length == this.getPersons.length) {
this.selected = [];
} else {
this.selected = this.getPersons;
}
}
}
};
</script>
<style lang="scss" scoped>
</style>

Rendering items via props after handling some processing using Vue.js and Vue CLI 3

I have a main component called App.vue and a child one MyTable.vue which wraps a table of data and showing only the 10 first rows, i'm working with vue cli 3 and when i ran the npm run serve command and go to the given address, it renders only the head of my table, but when i add some code in the mounted() function inside MyTable.vue like console.log() it renders also the body of my table, the problem comes back when i refresh my page, how can i deal with that ?
these is my components
App.vue
<template>
<div class="main-page">
<my-table title="todos" :cols="todo_attr" :rows_data="todo_data"></my-table>
</div>
</template>
<script>
import MyTable from './components/MyTable.vue'
import todos from './assets/todos.json'
export default {
name: 'app',
data(){
return{
todo_attr:[
"todoId","id","title","completed"
],
todo_data:[]
}
},
components: {
MyTable
},
mounted(){this.todo_data=todos;}
}
</script>
MyTable.vue
<template>
<div class="vet-container">
<table>
<thead>
<th class="tab-head-cell" v-for="col in cols" :key="col">{{col}}</th>
</thead>
<tbody>
<tr class="tab-rows_data-row" v-for="row in currentPageData" :key="row.id">
<td class="tab-rows_data-cell" v-for="(cell,key,index) in row" :key="key+index" > {{cell}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'my-table',
props: {
title: String,
cols: {},
rows_data: {}
},
data() {
return {
currentPageData: {}
};
},
methods:{
createFirstPage(){
this.currentPageData = this.rows_data.slice(0, 10);
}
}
,
mounted() {
this.createFirstPage();
}
}
</script>
First, you declared cols and rows_data as objects in MyTable.vue but you declared them as arrays in App.vue. You also declared currentPageData as an object instead of an array. It may cause some errors.
Second, you should prefer do this:
<template>
<div class="vet-container">
<table>
<thead>
<th class="tab-head-cell" v-for="col in cols" :key="col">{{col}}</th>
</thead>
<tbody>
<tr class="tab-rows_data-row" v-for="row in currentPageData" :key="row.id">
<td
class="tab-rows_data-cell"
v-for="(cell,key,index) in row"
:key="key+index" >{{cell}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
name: 'my-table',
props: {
title: String,
cols: Array,
rows_data: Array,
},
data() {
return {
index: 0,
size: 10,
};
},
computed: {
currentPageData() {
const start = this.index * this.size;
const end = start + this.size;
return this.rows_data.slice(start, end);
},
},
};
</script>
You could then pass index in props and change it on parent on click on buttons.
Little explanation of the computed property: this property act like calculated data. You can use it just like any other data or props and you can calculate its content based on other stuff, like here, with the current index and the size of page.

Using an ngFor to traverse a 2 dimensional array

I've been beating my head up against the wall on this one for a while but I finally feel close. What I'm trying to do is read my test data, which goes to a two dimensional array, and print its contents to a table in the html, but I can't figure out how to use an ngfor to loop though that dataset
Here is my typescript file
import { Component } from '#angular/core';
import { Http } from '#angular/http';
#Component({
selector: 'fetchdata',
template: require('./fetchdata.component.html')
})
export class FetchDataComponent {
public tableData: any[][];
constructor(http: Http) {
http.get('/api/SampleData/DatatableData').subscribe(result => {
//This is test data only, could dynamically change
var arr = [
{ ID: 1, Name: "foo", Email: "foo#foo.com" },
{ ID: 2, Name: "bar", Email: "bar#bar.com" },
{ ID: 3, Name: "bar", Email: "bar#bar.com" }
]
var res = arr.map(function (obj) {
return Object.keys(obj).map(function (key) {
return obj[key];
});
});
this.tableData = res;
console.log("Table Data")
console.log(this.tableData)
});
}
}
Here is my html which does not work at the moment
<p *ngIf="!tableData"><em>Loading...</em></p>
<table class='table' *ngIf="tableData">
<tbody>
<tr *ngFor="let data of tableData; let i = index">
<td>
{{ tableData[data][i] }}
</td>
</tr>
</tbody>
</table>
Here is the output from my console.log(this.tableData)
My goal is to have it formatted like this in the table
1 | foo | bar#foo.com
2 | bar | foo#bar.com
Preferably I'd like to not use a model or an interface because the data is dynamic, it could change at any time. Does anyone know how to use the ngfor to loop through a two dimensional array and print its contents in the table?
Like Marco Luzzara said, you have to use another *ngFor for the nested arrays.
I answer this just to give you a code example:
<table class='table' *ngIf="tableData">
<tbody>
<tr *ngFor="let data of tableData; let i = index">
<td *ngFor="let cell of data">
{{ cell }}
</td>
</tr>
</tbody>
</table>

Categories

Resources