I'm creating a Table component and use factory functions for all logics. In a v-for
I create a cell for each item per row.
The factory
This are the actual factories that I import in the vue page where I need it. I only added the relevant code here.
const TableData = (data) => {
const methods = {
'getRows': () => {
const result = []
for(let i = 0, end = data.length; i < end; i++) {
result.push(TableRow(methods, i))
}
return result
}
}
return methods
}
const TableRow = (parent, rowIndex) => {
const methods = {
'getCells': () => {
const result = []
for(let colIndex = 0, end = parent.getColumnCount(); colIndex < end; colIndex++) {
result.push(TableCell(parent, rowIndex, colIndex))
}
return result
}
}
return methods
}
const TableCell = (parent, rowIndex, columnIndex) => {
let active = false
const methods = {
'hover': () => {
active = !active
},
'isActive': () => {
return active
}
}
return methods
}
The component
So below the component
<template>
<div class="table-container">
<table class="table" v-if="table">
<thead>
<tr>
<th class="index-col"></ths>
<th v-for="(col, index) in columns">{{col}}</th>
</tr>
</thead>
<tbody>
<tr v-for="row in rows">
<td class="cell" v-for="cell in row.getCells()" @mouseenter="cell.hover" @mouseleave="cell.hover" :class="{active: cell.isActive()}">{{cell.getValue()}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
/* Table Data Factory */
import TableData from "~/plugins/library/table/data_new.js";
export default {
data() {
return {
table: null
};
},
methods: {
async fetch() {
/* Here I fetch data from API (fetchedData is an array) */
this.data = fetchedData
if(this.data) {
this.table = TableData(this.data)
} else {
console.error('Got no data')
}
}
},
computed: {
columns() {
return this.table.getColumns()
},
rows() {
return this.table.getRows()
}
},
mounted() {
this.fetch()
}
};
</script>
What I want to happen is that when I hover a cell in the table (which sets the cells active state to true), the class toggles as well.
:class="{active: cell.isActive()"
The class prop doesn't watch for changes in the cell factory. Which I understand, but I don't have any idea how to make it reactive. I've tried and searched for a while to find a solution, but without success.
Hope someone can help me further, thanks in advance!
looks to me like the problem is with this here => cell.isActive()
Because you're returning a function instead of a reactive variable, there is nothing to indicate a change there. Now you could force an update with forceUpdate()
, but then you'll be redrawing all the cells, which is terribly inefficient. You should try not using functions as part of rendering if possible, especially within loops, since they get triggered on every draw.
My preferred way would be to not have individual components manage their own state, but use a nested object/array state that could handle the columns', rows', and/or cells' data. But I'm assuming that's not what you're looking for.
If you could use a computed, then you can have the reactivity implemented without calling a function. You can add this with a DIY sort of way, if you're using vue 2.6+. As of Vue 2.6 the is an ability to define standalone observables, which you can use to store and mutate state, and generate a computed field.
You should also apply this to other factories, for example row.getCells()
will regenerate all the data, since it holds no state.
untested code:
const TableCell = (parent, rowIndex, columnIndex) => {
const state = Vue.Observable({
active: false
});
const computed= {
isActive: () => state.active,
}
const methods = {
hover: () => {
active = !active;
},
};
// return all merged (need to watch for overlapping names)
return {...state, ...computed, ...methods};
};
With this, you should be able to use cell.isActive
and have it react to changes.
On a side note, if you're looking to use Vue in this way, you'll probably benefit from the changes coming in Vue 3 (released in beta recently), namely API composition, which lends component composition.