Trouble with collecting selected items from QTable
-
OK I lied, but not.
I have a QTable with multi select, it works fine. but the project was getting a bit too big so I decided to separate into modules. But now I would need to somehow pass the selected data into the module(s).
I decided to use Vuex for this.
The Problem:
The Qtable does not have an API for detecting what items are selected or deselected. There are 2 API Events for Selections:
-
@selection -> function(details)
This one is not very useful because it only detects what item got selected or deselected. It triggers before the array of selection is modified. I am not able to get the modified array. (maybe I could with some workaround but is this the way?) -
@update:selected -> function(newSelected)
This one is the one QTable uses for updating the selected item’s array. The problem with t his one is that it does not triggers when the array is empty.
My current template is using
@selection -> function(details)
method to update the selected array in vuex store but there is a delay of 1 change because the method triggers before the array is modified.<template> <div> <q-table ref="mainTable" class="my-sticky-virtscroll-table" style="height: 800px" :data="tableData" :columns="columns" row-key="CODIGO" :selected-rows-label="getSelectedString" selection="multiple" :selected.sync="selected" virtual-scroll :pagination.sync="pagination" :rows-per-page-options="[0]" :virtual-scroll-sticky-size-start="48" :filter="term" flat bordered @focusin.native="activateNavigation" @focusout.native="deactivateNavigation" @keydown.native="onKey" @selection="onSelect" > <template v-slot:top-left> <q-input ref="mainSearchInput" debounce="0" v-model="term" label="Búsqueda" filled bottom-slots clearable="" style="width:500px" @input="updateSearchTerm(term)" > <template v-slot:hint >Puede hacer búsquedas online con commandos.</template > </q-input> </template> <template v-slot:body-cell-qty="props"> <q-td :props="props"> <q-badge :class="{ 'bg-black': props.value <= 0, 'bg-red': props.value > 0, 'bg-orange': props.value >= 100 && props.value < 500, 'bg-green': props.value >= 500, 'text-body1': true }" :label="props.value" /> </q-td> </template> <template v-slot:body-cell-code="props"> <q-td :props="props"> <div class="tableCell">{{ props.value }}</div> </q-td> </template> <template v-slot:body-cell-description="props"> <q-td :props="props" :style="{ width: '50px', whiteSpace: 'normal' }"> <div class="tableCell"> {{ props.value }} </div> </q-td> </template> <!-- <template v-slot:body-cell-qty="props"> <q-td :props="props"> <div class="tableCell">{{ props.value }}</div> </q-td> </template> --> <template v-slot:body-cell-codAlt="props"> <q-td :props="props" :style="{ width: '50px', whiteSpace: 'normal' }"> <div class="tableCell">{{ props.value }}</div> </q-td> </template> <template v-slot:body-cell-desAlt="props"> <q-td :props="props" :style="{ width: '50px', whiteSpace: 'normal' }"> <div class="tableCell">{{ props.value }}</div> </q-td> </template> <template v-slot:body-cell-group="props"> <q-td :props="props"> <div class="tableCell">{{ props.value }}</div> </q-td> </template> <template v-slot:body-cell-price="props"> <q-td :props="props"> <div class="tableCell">{{ props.value }}</div> </q-td> </template> <template v-slot:body-cell-discount20="props"> <q-td :props="props"> <div class="tableCell">{{ props.value }}</div> </q-td> </template> </q-table> <!-- <div class="q-mt-md">Selected: {{ JSON.stringify(selected) }}</div> --> <chipDetails></chipDetails> </div> </template> <script> import path from "path"; import { remote } from "electron"; import { Platform } from "quasar"; import { mapGetters, mapActions } from "vuex"; export default { computed: { ...mapGetters("central", ["searchTerm", "selectedTableData"]) }, components: { chipDetails: require("components/chipDetails.vue").default }, data() { return { term: "", tableData: [], selected: [], selectedTableData: [], navigationActive: false, pagination: { rowsPerPage: 300 }, columns: [ { name: "code", required: true, label: "Código", align: "left", // field: row => row.name, field: "CODIGO", format: val => `${val}`, sortable: true }, { name: "description", align: "left", label: "Descipción", field: "DESCRIPCION", sortable: true }, { name: "qty", label: "Qty", field: "INVENT", sortable: true, align: "right" }, { name: "codAlt", label: "Cod Alt", field: "COD.ALT.", align: "left" }, { name: "desAlt", label: "Desc Alt", field: "DESC.ALT.", align: "left" }, { name: "group", label: "Grupo", field: "GRUPO", align: "left" }, { name: "price", label: "Precio", field: "PRECIO", align: "right" }, { name: "discount20", label: "20 %", field: "PRECIO", align: "right", sort: (a, b) => parseInt(a, 10) - parseInt(b, 10) } ] }; }, methods: { ...mapActions("central", ["updateSearchTerm", "updateSelectedTableData"]), getSelectedString() { return ( this.selected.length + " selecionado" + (this.selected.length > 1 ? "s" : "") + " de " + this.tableData.length ); }, activateNavigation() { this.navigationActive = true; }, deactivateNavigation() { this.navigationActive = false; }, onSelect(evt) { console.log(evt); this.updateSelectedTableData(this.selected); console.log(this.selected); }, onKey(evt) { if ( this.navigationActive !== true || [33, 34, 35, 36, 38, 40].indexOf(evt.keyCode) === -1 || this.$refs.mainTable === void 0 ) { return; } evt.preventDefault(); switch (evt.keyCode) { case 36: // Home page = 1; index = 0; break; case 35: // End page = lastPage; index = rowsPerPage - 1; break; case 33: // PageUp page = currentPage <= 1 ? lastPage : currentPage - 1; if (index < 0) { index = 0; } break; case 34: // PageDown page = currentPage >= lastPage ? 1 : currentPage + 1; if (index < 0) { index = rowsPerPage - 1; } break; case 38: // ArrowUp if (currentIndex <= 0) { page = currentPage <= 1 ? lastPage : currentPage - 1; index = rowsPerPage - 1; } else { index = currentIndex - 1; } break; case 40: // ArrowDown if (currentIndex >= lastIndex) { page = currentPage >= lastPage ? 1 : currentPage + 1; index = 0; } else { index = currentIndex + 1; } break; } if (page !== this.pagination.page) { this.pagination = { ...this.pagination, page }; this.$nextTick(() => { const { computedRows } = this.$refs.mainTable; this.selected = [ computedRows[Math.min(index, computedRows.length - 1)] ]; }); } else { this.selected = [computedRows[index]]; } } }, mounted() { if (Platform.is.electron) { // get file path const filePath = path.join( remote.app.getPath("home"), "/Dropbox/Sync/inventarioHL.json" ); // load the File System to execute our common tasks (CRUD) var fs = require("fs"); // read file content fs.readFile(filePath, "utf-8", (err, getData) => { if (err) { alert("An error ocurred reading the file :" + err.message); } // parse text into json var jsonData = JSON.parse(getData); // add entire data to table this.tableData = jsonData; // console.log(jsonData[0]); // focus on q-input this.$refs.mainSearchInput.$el.focus(); }); } // update the q-input with searchTerm this.term = this.searchTerm; } }; </script> <style lang="sass"> .my-sticky-virtscroll-table /* height or max-height is important */ height: 310px /* specifying max-width so the example can highlight the sticky column on any browser window */ max-width: 1300px td:first-child /* bg color is important for td; just specify one */ background-color: #fafafa !important tr th position: sticky /* higher than z-index for td below */ z-index: 2 /* bg color is important; just specify one */ background: #fafafa /* this will be the loading indicator */ thead tr:last-child th /* height of all previous header rows */ top: 48px /* highest z-index */ z-index: 3 thead tr:first-child th top: 0 z-index: 1 tr:first-child th:first-child /* highest z-index */ z-index: 3 td:first-child z-index: 1 td:first-child, th:first-child position: sticky left: 0 </style> <style> .tableCell { font-size: 18px; } </style>
This is the component I am using to access the selected array
<template> <div class="q-pt-sm"> <q-chip color="secondary" text-color="white" v-for="(sel, index) in selectedTableData" :key="index" :label="sel['CODIGO']" removable @remove="removeSelectedChip(index)" clickable @click="fixed = true" /> <q-dialog v-model="fixed"> <q-card> <q-card-section> <div class="text-h6">Terms of Agreement</div> </q-card-section> <q-separator /> <q-card-section style="max-height: 50vh" class="scroll"> <p v-for="n in 15" :key="n"> Lorem ipsum dolor sit amet consectetur adipisicing elit. Rerum repellendus sit voluptate voluptas eveniet porro. Rerum blanditiis perferendis totam, ea at omnis vel numquam exercitationem aut, natus minima, porro labore. </p> </q-card-section> <q-separator /> <q-card-actions align="right"> <q-btn flat label="..." color="primary" v-close-popup /> <q-btn flat label="OK" color="primary" v-close-popup /> </q-card-actions> </q-card> </q-dialog> </div> </template> <script> import { mapGetters } from "vuex"; export default { data() { return { fixed: false }; }, computed: { ...mapGetters("central", ["selectedTableData"]) } }; </script>
Finally this is the vuex store
const state = { searchTerm: "", selectedTableData: [] }; const mutations = { updateSearchTerm(state, term) { state.searchTerm = term; }, updateSelectedTableData(state, data) { state.selectedTableData = data; } }; const actions = { updateSearchTerm({ commit }, term) { commit("updateSearchTerm", term); }, updateSelectedTableData({ commit }, data) { commit("updateSelectedTableData", data); } }; const getters = { searchTerm: state => { return state.searchTerm; }, selectedTableData: state => { return state.selectedTableData; } }; export default { namespaced: true, state, mutations, actions, getters };
-
-
You can use a
2-way computed
property for yourselected
array :computed: { selected: { get() { return this.$store.state.central.selectedTableData }, set(val) { this.$store.commit('central/updateSelectedTableData', val) } }
And of course, remove
selected
property indata()
This way, your vuex state will always be in sync with your selection.You can also have a look to vuex-map-fields library, which will create getters&setters for you
-
@tof06 I thought this
computed: { ...mapGetters("central", ["searchTerm", "selectedTableData"]) },
already did the job.
I guess I will use
2-way-computed
thanks for the info