multi dimensional array filter in vue js - vue.js

i am trying to filter from multi dimensional array in vue js.
first i am storing response from axios in a variable like
fetchUsersDetails() {
var vm = this;
axios.get('school/api/user',{headers: getHeader()}).then(response => {
Vue.set(vm.$data, 'userList', response.data.data)
//console.log(this.userList)
})
},
on doing console.log(this.userList) iam getting
0:{
name:rajesh
city:dhanbad
state:jharkhand
student_session{
0:{
class_id:1
session_id:1
}
}
}
1:{
name:rohan
city:dhanbad
state:jharkhand
student_session{
0:{
class_id:1
session_id:1
}
}
}
2:{
name:rahul
city:dhanbad
state:jharkhand
student_session{
0:{
class_id:2
session_id:1
}
}
}
3:{
name:ramesh
city:dhanbad
state:jharkhand
student_session{
0:{
class_id:3
session_id:1
}
}
}
and so on...
now in html
<table class="table">
<tr>
<th style="display: none">Id</th>
<th>Sl. No.</th>
<th>Name</th>
</tr>
</thead>
<tfoot>
<tr>
<th style="display: none">Id</th>
<th>Sl. No.</th>
<th>Name</th>
</tr>
</tfoot>
<tbody>
<tr v-for="(studentDetails, index) in filterUserLists">
<td style="display: none">{{studentDetails.user_token}}</td>
<td>{{index+1}}</td>
<td>
<a #click="showModal(studentDetails)" data-toggle="modal" data-target="#showModal" >{{studentDetails.first_name}}</a>
</td>
</tr>
</tbody>
and i am filtering my userList
filterUserLists: function () {
if(this.userList)
{
var list= this.userList
.filter(item => item.student_session.class_id==="1" )
}
console.log(list)
},
but i am getting empty list on my console though in my userList student_session is present with all values
i am new to vue js, so please help me
thankx in advance...

you can use computed
computed: {
filterUserLists () {
var filtered = [];
for (var i = 0; i < this.userList.length; i++) {
if (this.userList[i].student_session.class_id == "1") {
filtered.push(this.userList[i]);
}
}
return filtered;
}
}

This seems to be rather a problem with your filter because you try to access the secound array directly.
For me it worked with
userList.filter(item => item.student_session[0].class_id===1 )
and
userList
.filter(item => item.student_session
.filter((item2 =>item2.class_id===1 )) )
Or just use two loops like everyone does for a two dimensional array.
for(var i =0; i < userList.length; i++){
...
for(var j=0; j < userList[i].student_session.length; j++){
if(userList[i].student_session[j].class_id===1){
...
}
}
}
If you declared filterUserList unter methods you have to use it as function in the v-for
<tr v-for="(studentDetails, index) in filterUserLists()">
You try to access the properties .user_token and .first_name but these are never declared.

Related

Sorting a vue 3 v-for table composition api

I have successfully created a table of my array of objects using the code below:
<div class="table-responsive">
<table ref="tbl" border="1" class="table">
<thead>
<tr>
<th scope="col" #click="orderby('b.property')">Property</th>
<th scope="col"> Price </th>
<th scope="col"> Checkin Date </th>
<th scope="col"> Checkout Date </th>
<th scope="col" > Beds </th>
</tr>
</thead>
<tbody>
<tr scope="row" class="table-bordered table-striped" v-for="(b, index) in properties" :key="index">
<td> {{b.property}} </td>
<td> {{b.pricePerNight}}</td>
<td> {{b.bookingStartDate}} </td>
<td> {{b.bookingEndDate}} <br> {{b.differenceInDays}} night(s) </td>
<td> {{b.beds}} </td>
</tr>
</tbody>
</table>
</div>
<script>
import {ref} from "vue";
import { projectDatabase, projectAuth, projectFunctions} from '../../firebase/config'
import ImagePreview from "../../components/ImagePreview.vue"
export default {
components: {
ImagePreview
},
setup() {
const properties = ref([]);
//reference from firebase for confirmed bookings
const Ref = projectDatabase .ref("aref").child("Accepted Bookings");
Ref.on("value", (snapshot) => {
properties.value = snapshot.val();
});
//sort table columns
const orderby = (so) =>{
desc.value = (sortKey.value == so)
sortKey.value = so
}
return {
properties,
orderby
};
},
};
</script>
Is there a way to have each column sortable alphabetically (or numerically for the numbers or dates)? I tried a simple #click function that would sort by property but that didn't work
you can create a computed property and return the sorted array.
It's just a quick demo, to give you an example.
Vue.createApp({
data() {
return {
headers: ['name', 'price'],
properties: [
{
name: 'one',
price: 21
},
{
name: 'two',
price: 3
},
{
name: 'three',
price: 5
},
{
name: 'four',
price: 120
}
],
sortDirection: 1,
sortBy: 'name'
}
},
computed: {
sortedProperties() {
const type = this.sortBy === 'name' ? 'String' : 'Number'
const direction = this.sortDirection
const head = this.sortBy
// here is the magic
return this.properties.sort(this.sortMethods(type, head, direction))
}
},
methods: {
sort(head) {
this.sortBy = head
this.sortDirection *= -1
},
sortMethods(type, head, direction) {
switch (type) {
case 'String': {
return direction === 1 ?
(a, b) => b[head] > a[head] ? -1 : a[head] > b[head] ? 1 : 0 :
(a, b) => a[head] > b[head] ? -1 : b[head] > a[head] ? 1 : 0
}
case 'Number': {
return direction === 1 ?
(a, b) => Number(b[head]) - Number(a[head]) :
(a, b) => Number(a[head]) - Number(b[head])
}
}
}
}
}).mount('#app')
th {
cursor: pointer;
}
<script src="https://unpkg.com/vue#next"></script>
<div id="app">
<table>
<thead>
<tr>
<th v-for="head in headers" #click="sort(head)">
{{ head }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(data, i) in sortedProperties" :key="data.id">
<td v-for="(head, idx) in headers" :key="head.id">
{{ data[head] }}
</td>
</tr>
</tbody>
</table>
</div>
For any one else who is stuck this is how i solved the problem from https://www.w3schools.com/howto/tryit.asp?filename=tryhow_js_sort_table_desc:
//sort table columns
const sortTable = (n) =>{
var table, rows, switching, i, x, y, shouldSwitch, dir, switchcount = 0;
table = document.getElementById("myTable");
switching = true;
//Set the sorting direction to ascending:
dir = "asc";
/*Make a loop that will continue until
no switching has been done:*/
while (switching) {
//start by saying: no switching is done:
switching = false;
rows = table.rows;
/*Loop through all table rows (except the
first, which contains table headers):*/
for (i = 1; i < (rows.length - 1); i++) {
//start by saying there should be no switching:
shouldSwitch = false;
/*Get the two elements you want to compare,
one from current row and one from the next:*/
x = rows[i].getElementsByTagName("TD")[n];
y = rows[i + 1].getElementsByTagName("TD")[n];
/*check if the two rows should switch place,
based on the direction, asc or desc:*/
if (dir == "asc") {
if (x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase()) {
//if so, mark as a switch and break the loop:
shouldSwitch= true;
break;
}
} else if (dir == "desc") {
if (x.innerHTML.toLowerCase() < y.innerHTML.toLowerCase()) {
//if so, mark as a switch and break the loop:
shouldSwitch = true;
break;
}
}
}
if (shouldSwitch) {
/*If a switch has been marked, make the switch
and mark that a switch has been done:*/
rows[i].parentNode.insertBefore(rows[i + 1], rows[i]);
switching = true;
//Each time a switch is done, increase this count by 1:
switchcount ++;
} else {
/*If no switching has been done AND the direction is "asc",
set the direction to "desc" and run the while loop again.*/
if (switchcount == 0 && dir == "asc") {
dir = "desc";
switching = true;
}
}
}
}

How can I get a result from a POST request into a v-for?

I have something like this:
<table class="table">
<tbody>
<tr v-for="(option, index) in Weapons">
<td>Primary</td>
<td>[[ getWeaponType(option.WeaponType) ]]</td>
</tr>
</tbody>
</table>
In my Vue object, in methods, I have this:
getWeaponType: function(weaponTypeNumber){
axios.get('/path/to/api')
.then(response => {
return response.data
})
}
I send an ID and it returns the name for that ID. But I need for it to show in my table whose rows are being generated by the v-for. This isn't working since it is a Promise and the values are not showing. Is there any way I can achieve getting that value to show in the table? I didn't want to do it server side so I'm trying to see if I have any options before I do that.
May I suggest an alternative method?
data() {
return {
weaponsMappedWithWeaponTypes: [];
}
}
mounted() { // I am assuming the weapons array is populated when the component is mounted
Promise.all(this.weapons.map(weapon => {
return axios.get(`/path/to/api...${weapon.weaponType}`)
.then(response => {
return {
weapon,
weaponType: response.data
}
})
).then((values) => {
this.weaponsMappedWithWeaponTypes = values
})
}
computed: {
weaponsAndTheirWeaponTypes: function () {
return this.weaponsMappedWithWeaponTypes
}
}
And then in your template
<table class="table">
<tbody>
<tr v-for="(option, index) in weaponsAndTheirWeaponTypes">
<td>Primary</td>
<td>option.weaponType</td>
</tr>
</tbody>
</table>

sorting does not works with Virtual scrolling custom data source

Angular 8 brings really cool feature to implement virtual scroll. They have provide example here regarding usage of it. Although, its really difficult implement with mat-table.
I have been working on to implement virtual scroll on mat-table and found that, we need to have our own custom data source for that. I have create new data source by implementing DataSource class but what I found is that, I loose the sort and other features due to custom data source.
app.component.html
<cdk-virtual-scroll-viewport class="example-viewport">
<table class="table" mat-table #table matSort [dataSource]="dataSource" [multiTemplateDataRows]="true">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header> id </th>
<td mat-cell *matCellDef="let element"> {{element.id}} </td>
</ng-container>
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Name</th>
<td mat-cell *matCellDef="let element"> {{element.name}} </td>
</ng-container>
<ng-container matColumnDef="age">
<th mat-header-cell *matHeaderCellDef> Age</th>
<td mat-cell *matCellDef="let element"> {{element.age}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<!-- cdkVirtualScrollViewport uses transform: translateY to correct for all elements that are removed.
We will use a plcaholder row in the table instead because the translate causes problems with the sticky header -->
<tr [style.height.px]="placeholderHeight" mat-row *matRowDef="let row; let index = index; columns: []; when: placeholderWhen"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
</table>
</cdk-virtual-scroll-viewport>
app.component.ts
import { Component, OnInit, ViewChild, AfterViewInit } from "#angular/core";
import {
CdkVirtualScrollViewport,
FixedSizeVirtualScrollStrategy,
VIRTUAL_SCROLL_STRATEGY
} from "#angular/cdk/scrolling";
import { CollectionViewer } from "#angular/cdk/collections";
import { MatSort } from "#angular/material";
// import { GridTableDataSource } from './gridTable.datasource'
import { GridTableDataSource } from "./gridTableM.datasource";
const ROW_HEIGHT = 48;
/**
* Virtual Scroll Strategy
*/
export class CustomVirtualScrollStrategy extends FixedSizeVirtualScrollStrategy {
constructor() {
super(ROW_HEIGHT, 1000, 2000);
}
attach(viewport: CdkVirtualScrollViewport): void {
this.onDataLengthChanged();
}
}
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
providers: [
{ provide: VIRTUAL_SCROLL_STRATEGY, useClass: CustomVirtualScrollStrategy }
]
})
export class AppComponent implements OnInit, AfterViewInit {
placeholderHeight = 0;
displayedColumns: string[] = ["id", "name", "age"];
dataSource: GridTableDataSource<any>;
rows = Array(200)
.fill(0)
.map((x, i) => {
return { name: "name" + i, id: i, age: 27 };
});
itemSize = 48;
#ViewChild(CdkVirtualScrollViewport, { static: true })
viewport: CdkVirtualScrollViewport;
#ViewChild(MatSort, { static: false }) sort: MatSort;
constructor() {}
ngOnInit() {
this.dataSource = new GridTableDataSource(
this.rows,
this.viewport,
this.itemSize
);
this.dataSource.offsetChange.subscribe(offset => {
this.placeholderHeight = offset;
});
this.dataSource.data = this.rows;
// debugger;
// this.dataSource.sort = this.sort; // this.sort is null here
}
ngAfterViewInit() {
debugger;
this.dataSource.sort = this.sort; // this.sort is null here as well
}
placeholderWhen(index: number, _: any) {
return index == 0;
}
}
To implement sorting and other feature, I just copied code from MatTableDataSource. but somehow, this.sort is always null in any method. I couldn't find reason for this. Can anyone explain this behavior?
Here is stackblitz to play with.
it doesn't work.
angular team doesn't support this
https://github.com/angular/components/issues/19003

Sorting table doesnt working Vue

I found this pen https://codepen.io/cfjedimaster/pen/BYpJgj But it doesn't work in my implementation. I can't understand what i am doing wrong
<table>
<thead>
<tr class="header">
<th #click="sort('campaign')">Campaign</th> // click heandler
<th #click="sort('time')">Time</th>
<th #click="sort('views')">Views</th>
<th #click="sort('visitors')">Visitors</th>
<th #click="sort('ctr')">CTR</th>
<th #click="sort('cpc')">CPC</th>
<th #click="sort('cpv')">CPV</th>
<th #click="sort('cpm')">CPM</th>
<th #click="sort('status')">Status</th>
</tr>
</thead>
<tbody> //target table
<tr v-for="item in data" :key="item.id"> // loop
<td>{{item.name}}</td> //row
<td>{{item.time}}</td>
<td>{{item.views}}</td>
<td>{{item.visitors}}</td>
<td>{{item.ctr}}</td>
<td>{{item.cpc}}</td>
<td>{{item.cpv}}</td>
<td>{{item.cpm}}</td>
<td>{{item.status}}</td>
</tr>
</tbody>
</table>
data(){
return{
data: [], // data
currentSort:'campaign', // dynamic property
currentSortDir:'asc'
}
},
methods:{ // methods
getData(){
this.data = this.$store.state.graphArr // get data from store
},
sort(s) { // sort function
//if s == current sort, reverse
if(s === this.currentSort) { // if statement
this.currentSortDir = this.currentSortDir==='asc'?'desc':'asc'; }
this.currentSort = s; // setting currentSortDir
}
},
computed:{ // computed
sortedCats() { // where this function should be called
return this.data.sort((a,b) => { // ES Lint highlits "this". Unexpetced side effect
let modifier = 1;
if(this.currentSortDir === 'desc') modifier = -1;
if(a[this.currentSort] < b[this.currentSort]) return -1 * modifier;
if(a[this.currentSort] > b[this.currentSort]) return 1 * modifier;
return 0;
});
}
}
Maybe i should somewhere call sortedCats function? And the role of computed property at all in this case?
You need to loop through sortedCats array to get the desired result instead of data as sortedCats returns new value every time sort is clicked through computed property.
<tr v-for="item in sortedCats" :key="item.id">

Vue - how to bind table column to a data object?

I'm new to Vue, so go easy on me! Here's the situation. There must be a better way than what I'm doing here.
I have a simple 2 column HTML table:
<table id="contacts">
<tbody>
<tr>
<th class="column-1">
Contact Id
</th>
<th class="column-2">
Applications assigned count
</th>
</tr>
<tr class="odd" id="contacts_tr_1">
<td class="column-1">
1
</td>
<td class="column-2">
247
</tr>
<tr class="even last" id="contacts_tr_2">
<td class="column-1">
2
</td>
<td class="column-2">
0
</td>
</tr>
<tr class="even last" id="contacts_tr_2">
<td class="column-1">
3
</td>
<td class="column-2">
44
</td>
</tr>
<tr class="even last" id="contacts_tr_2">
<td class="column-1">
.........
</td>
<td class="column-2">
.........
</td>
</tr>
<tr class="even last" id="contacts_tr_2">
<td class="column-1">
10
</td>
<td class="column-2">
76
</td>
</tr>
</tbody>
</table>
I want to update the "Applications assigned count" column (but only for certain rows), as determined by the result of an AJAX call. So assuming the table has 10 rows, the AJAX call might say that the value of the "Applications assigned count" column of rows 1, 4 and 7 need to be updated, to e.g. 247, 80 and 356 respectively. I'm thinking of using a JSON object like this as my Vue data object, because the AJAX response will look like this.
data: {
num_of_applications_assigned: [
{
"party_id": "1",
"num": "247"},
{
"party_id": "4",
"num": "80"},
{
"party_id": "7",
"num": "356"}
]
},
I thought there might be a way to bind the "Applications assigned count" column to the Vue data object that gets updated by the AJAX call, but I don't see a way to do this other than adding a unique v-text to each individual <TD> cell e.g.
<div v-text="num_of_applications_assigned_1"></div>
<div v-text="num_of_applications_assigned_2"></div>
etc
However, this has lead me to writing some very convoluted code when updating those v-texts with the results of the AJAX response, as I have to dynamically construct the references:
let vm = this;
jQuery.ajax({
url: myurl
}).then(function(response) {
for (var i = 0, len = vm.num_of_applications_assigned.length; i < len; i++) {
var party_id = vm.num_of_applications_assigned[i].party_id;
var dref = 'vm.num_of_applications_assigned_'+party_id;
var dnum = vm.num_of_applications_assigned[i].num;
eval(dref + ' = ' + dnum + ';');
}
});
Yes, I know eval is bad - that's why I'm here asking for help! What is a better way of doing this, or is Vue not a good match for this situation?
I can't use v-for, as the table and its rows are all generated server side
If you cannot use v-for you can still use Vue to render your data, if you decide to do some additional work on the server-side, and you mould your data a little differently. It's less elegant than v-for but it should be straightforward.
For example, if you wanted to create a two-column table where Vue would render/update the second column's cell values, you could generate something like this on the server side:
<table id="app">
<tr>
<td>1</td>
<td>{{ applications.party_1.num }}</td>
</tr>
<tr>
<td>2</td>
<td>{{ applications.party_2.num }}</td>
</tr>
</table>
Where you use your favourite server-side language to generate values party_1, party_2, party_3 dynamically.
This implies that an underlying data structure like so:
applications: {
party_1: { num: 1 },
party_2: { num: 2 }
}
This should be straightforward to create that structure dynamically on the server-side. Then, just create a Vue instance and populate its initial data object with that data structure:
new Vue({
el: '#app',
data: {
applications: {
party_1: { num: 1 },
party_2: { num: 2 }
}
}
});
When the HTML is rendered in a browser, the Vue instance mounts and it will update the bound cell values from its data object. These values are reactive, so any subsequent changes to the Vue instance's underlying data will be rendered automatically.
Hope this helps :)
The idea in Vue is to have all your data ready, and let Vue.JS do the rendering. So, your data should probably look like this:
data: {
assignedApplications: [
{ party_id: 1, num: 247 },
{ party_id: 2, num: 0 },
{ party_id: 3, num: 44 },
{ party_id: 4, num: 76 },
{ party_id: 5, num: 9 },
]
},
}
Then, you can let Vue render it:
<table>
<tr>
<th class="column-1">Contact Id</th>
<th class="column-2">Applications assigned count</th>
</tr>
<tr v-for="a in assignedApplications">
<td>{{a.party_id}}</td>
<td>{{a.num}}</td>
</tr>
</table>
Remains the problem, how to update it. After getting the new data, you have to modify the array this.assignedApplications, and then Vue will re-render the table correctly. If your rows have a unique id, you could make assignedApplications instead of an array a map-like data structure, so you can easy access specific rows by their id and change the value. If not, you have to search through the whole array for every change and adapt it:
mergeInNewData(newData) {
for (let i = 0; i < newData.length; i++) {
let changedData = newData[i];
for (let j = 0; j < this.assignedApplications.length; j++) {
if (this.assignedApplications[j].party_id === changedData.party_id) {
this.assignedApplications[j].num = changedData.num;
}
}
}
}
All together, an example could look like this:
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/vue"></script>
</head>
<body>
<div id="app">
<table>
<tr>
<th class="column-1">Contact Id</th>
<th class="column-2">Applications assigned count</th>
</tr>
<tr v-for="a in assignedApplications">
<td>{{a.party_id}}</td>
<td>{{a.num}}</td>
</tr>
</table>
Update
</div>
<script>
var app = new Vue({
el: '#app',
data: {
assignedApplications: [
{ party_id: 1, num: 247 },
{ party_id: 2, num: 0 },
{ party_id: 3, num: 44 },
{ party_id: 4, num: 76 },
{ party_id: 5, num: 9 },
]
},
methods: {
update: function() {
let newData = [
{ party_id: 1, num: 243},
{ party_id: 2, num: 80},
{ party_id: 4, num: 0},
];
this.mergeInNewData(newData);
},
mergeInNewData(newData) {
for (let i = 0; i < newData.length; i++) {
let changedData = newData[i];
for (let j = 0; j < this.assignedApplications.length; j++) {
if (this.assignedApplications[j].party_id === changedData.party_id) {
this.assignedApplications[j].num = changedData.num;
}
}
}
}
}
})
</script>
</body>
</html>