Sorting table doesnt working Vue - vue.js

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">

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>

Calling a vue component into v-for

I have the following construction:
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Наименование</th>
<th scope="col">API ключ</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr v-for="adv in advertisers">
<th scope="row">{{ adv.id }}</th>
<td>{{ adv.name }}</td>
<td>{{ adv.api_key }}</td>
<td>
<advertiser-delete-component :advertiser-id="adv.id"></advertiser-delete-component>
<advertiser-edit-component :advertiser-id="adv.id" :advertiser-name="adv.name"></advertiser-edit-component>
</td>
</tr>
</tbody>
</table>
Array "advertisers" keeps data from the server. Data is correct. But I see that all 'advertiser-delete-component' and 'advertiser-edit-component' have the first item of advertisers array in component props.
Here is the code of advertiser-edit-component:
<script>
import { EventBus } from '../../app.js';
export default {
mounted() {
},
props: ['advertiserId', 'advertiserName'],
data: function() {
return {
formFields: {
advertiserName: '',
advertiserId: this.advertiserId,
},
errors: []
}
},
methods: {
submit: function (e) {
window.axios.post('/advertiser/edit', this.formFields).then(response => {
this.formFields.advertiserName = '';
EventBus.$emit('reloadAdvertisersTable');
$('#addAdvertiser').modal('hide');
}).catch(error => {
if (error.response.status === 422) {
this.errors = error.response.data.errors || [];
}
});
}
}
}
The props props: ['advertiserId', 'advertiserName'] are the same for each component call with my code. I want them to be dynamic where they take corresponding elements from the array as the loop goes through it one by one.
What did I do wrong?
UPDATE:
Here is full code of table component:
<template>
<div>
<table class="table">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Наименование</th>
<th scope="col">API ключ</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr v-for="adv in advertisers">
<th scope="row">{{ adv.id }}</th>
<td>{{ adv.name }}</td>
<td>{{ adv.api_key }}</td>
<td>
<advertiser-delete-component :advertiser-id="adv.id"></advertiser-delete-component>
<advertiser-edit-component :advertiser-id="adv.id"
:advertiser-name="adv.name"></advertiser-edit-component>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import {EventBus} from '../../app.js';
export default {
mounted() {
this.getAdvertisersTable();
EventBus.$on('reloadAdvertisersTable', this.getAdvertisersTable)
},
props: ['totalCountOfAdvertisers'],
data: function () {
return {
advertisers: [],
}
},
methods: {
getAdvertisersTable: function () {
window.axios.get('/advertisers/all')
.then(r => {
this.advertisers = r.data.data;
})
.catch(e => {
console.log(e.response.data.errors);
})
}
}
}
</script>
I think formFields should be array of objects like:
formFields: [{
advertiserName: '',
advertiserId: this.advertiserId,
}],

multi dimensional array filter in 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.

Is it possible to use a component property as a data variable?

I've copied my current code below. I'm trying to dynamically generate table headers depending on what type I pass as a prop to my tables component (standings and status are what I have as the data arrays in my example).
I've accomplished this by using the if statement in the header computed value to return the proper list.
However I'd like to not have to add additional if statements for every type.
Is there a way that I can leverage the type prop to bind directly to the matching data?
<div id="root" class="container">
<tables type="stats">
</tables>
</div>
Vue.component('tables', {
template: `
<table class="table">
<thead>
<tr>
<th v-for="headers in header">{{ headers }}</th>
</tr>
</thead>
<tfoot>
<tr>
<th v-for="footers in header">{{ footers }}</th>
</tr>
</tfoot>
<tbody>
<tr>
<th>1</th>
<td>Leicester City <strong>(C)</strong>
<slot></slot>
</tr>
</tbody>
</table>
`,
data () {
return {
standings: ['Rank', 'Team', 'Wins', 'Losses'],
stats: ['Number', 'Player', 'Position', 'RBI', 'HR']
};
},
computed: {
header() {
if (this.type == 'standings') {
return this.standings;
} else {
return this.stats;
}
}
},
props: {
type: { required: true }
}
});
If you change up your data structure slightly, you can remove your if statements.
data () {
return {
types:{
standings: ['Rank', 'Team', 'Wins', 'Losses'],
stats: ['Number', 'Player', 'Position', 'RBI', 'HR']
}
};
},
computed: {
header() {
return this.types[this.type]
}
},
props: {
type: { required: true }
}
Here is an example.
Vue.component('tables', {
template: `
<table class="table">
<thead>
<tr>
<th v-for="headers in header">{{ headers }}</th>
</tr>
</thead>
<tfoot>
<tr>
<th v-for="footers in header">{{ footers }}</th>
</tr>
</tfoot>
<tbody>
<tr>
<th>1</th>
<td>Leicester City <strong>(C)</strong>
<slot></slot>
</td>
</tr>
</tbody>
</table>
`,
data () {
return {
types:{
standings: ['Rank', 'Team', 'Wins', 'Losses'],
stats: ['Number', 'Player', 'Position', 'RBI', 'HR']
}
};
},
computed: {
header() {
return this.types[this.type]
}
},
props: {
type: { required: true }
}
});
new Vue({
el: "#root"
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="root" class="container">
<tables type="stats">
</tables>
</div>