Vuetify pagination howto change page property when rowsPerPage property changes - vue.js

I'm having trouble figuring out how to change the vuetify pagination.page property back to 1, when a user changes the rowsPerPage property.
Say there are a total of 23 rows in a result set and rowsPerPage is currently set to 10. If the users goes to the 3rd (last) page and then selectes 50 rowsPerPage, vue calls my ajax query to get new data from the backend server, but it passes rowsPerPage as 50 and it passes page as 3.
Since this causes the sql offset property to be 100, which is way more than the 23 records in the table, it returns no data and so the screen re-renders with no records.
What I would like to do to fix this is, when the rowsPerPage property changes, reset the page property back to 1.
I have googled a bunch but cannot find the answer. Am I trying to solve this problem the wrong way?
Edit: Here is a sample of my rails view code:
<v-card flat>
<v-card-title class="pt-0 pb-0">
<h2>No RSP/Participating Apps</h2>
<%= render :partial => "search" %>
<%= render :partial => "rows_per_page" %>
</v-card-title>
<v-data-table
:headers="headers"
:items="results"
:pagination.sync="pagination"
hide-actions
:total-items="totalItems"
:must-sort=true
:search="pagination.search"
>
...
</v-data-table>
<div class="text-xs-center pt-2">
<v-pagination v-model="pagination.page" :length="pages"></v-pagination>
</div>
</v-card>
And my js code:
data: {
search: '',
drawer: null,
miniVariant: false,
loading: true,
totalItems: 0,
results: [],
pagination: {
rowsPerPage: 10,
},
rowsPerPageChoices: [
{ text: '2 rows per page', value: 2 },
{ text: '5 rows per page', value: 5 },
{ text: '10 rows per page', value: 10 },
{ text: '20 rows per page', value: 20 },
{ text: '30 rows per page', value: 30 }
],
},
methods: {
commonQueryParams() {
return '?sortBy=' + this.pagination.sortBy +
'&descending=' + this.pagination.descending +
'&page=' + this.pagination.page +
'&rowsPerPage=' + this.pagination.rowsPerPage +
'&onlyTotal=0' +
'&filter=' + this.search;
},
queryParams() {
return this.commonQueryParams();
},
getData() {
this.loading = true;
axios.get(this.dataApiUrl + '/' + this.dataEndPoint + this.queryParams(), {withCredentials: true})
.then(response => {
this.results = response.data.data;
this.totalItems = response.data.control_data.total;
this.loading = false;
});
},
},
computed: {
pages () {
return this.pagination.rowsPerPage ? Math.ceil(this.totalItems / this.pagination.rowsPerPage) : 0;
}
},
watch: {
pagination: {
handler () {
this.getData();
},
deep: true
},
search: _.debounce(function () {
this.getData()
}, 500),
}

So, I came up with a solution that appears to do what I want. First, I added a new data attribute:
oldRowsPerPage: 10
I set this to 10, because that is the default value for the pagination.rowsPerPage attribute.
Next, I changed the getData method to look like this:
getData() {
if (this.pagination.rowsPerPage != this.oldRowsPerPage) {
this.oldRowsPerPage = this.pagination.rowsPerPage;
this.pagination.page = 1;
}
this.loading = true;
axios.get(this.dataApiUrl + '/' + this.dataEndPoint + this.queryParams(), {withCredentials: true})
.then(response => {
this.results = response.data.data;
this.totalItems = response.data.control_data.total;
this.loading = false;
});
},
The conditional at the beginning of the method checks to see if the pagination.rowsPerPage value has been changed. If it has, we set the oldRowsPerPage attribute to the same value and then we change the pagination.page attribute to 1. This will cause the query to reset the offset so that it starts returning records from the beginning of the result set and it will also change the vuetify datatable so that it displays the first page of the result set instead of continuing to try to show whatever page the datatable was currently on when the user selected the new pagination.rowsPerPage choice.

Related

Update table when user clicked on pagination buttons

I have a vuetify table. Right now, I call API for all records and load all records into my table. I might not see the performance differences now because it's less than 1000. When I have 10,000,000 this might not work anymore. I need to only query for the first 10 on page loads, and my BE API already supports that. Then, I will make calls to API to get the next/previous 10 only when the user clicked on the pagination buttons.
data() {
return {
headers: [
{
text: 'Name',
align: 'start',
sortable: false,
value: 'name',
width: '20%'
},
{ text: 'Link Count', value: 'count', width: '10%' },
{ text: 'URL', value: 'details', width: '50%' },
{ text: 'Actions', value: 'id', width: '20%' }
],
items: []
}
},
and I set this.items to the response from API below :
axios.defaults.headers['Content-Type'] = 'application/json'
axios
.post(window.MTS_URL, vc_url_group)
.then((response) => {
this.items = response.data.groups
})
Can someone please get me started?
It's highly depends on your backend implementation, but there are some common points:
You should define v-data-table this way:
<v-data-table
:headers="headers"
:items="desserts"
:items-per-page="10"
:server-items-length="totalDesserts"
:options.sync="options"
></v-data-table>
So you need to operate with three variables:
desserts as a [1..10] rows on your current page,
totalDesserts as a total amount of rows (10,000,000 in your case)
options as a synchronize point of your pagination
These variables comes from an example of official docs.
After that, in order to track user click on pagination buttons, you need to define a deep watcher (it should be deep in order to react to changes in nested props):
watch: {
options: {
handler() {
this.getDessertsFromApi();
},
deep: true
},
},
And then - your API call method:
methods: {
async getDessertsFromApi() {
this.loading = true;
const { sortBy, sortDesc, page, itemsPerPage } = this.options;
// Your axios call should be placed instead
await this.simulateApiCall({
size: itemsPerPage,
page: page - 1,
})
.then((response) => {
this.desserts = response.data.content;
this.totalDesserts = response.data.totalElements;
})
.finally(() => {
this.loading = false;
});
},
}
Test this at CodePen with simulated API calls.

call child's computed method in VUE

I got three VUE components with a structure like this:
Table->Modal->Form. When user selects a record, the modal will be triggered and shown.
The Form component contains a computed method method1 for computing a property that is not stored by the database. I want to add a column in the Table component and display the return value of method1 for each record.
So something like this:
Vue.component('Form`, {
computed: {
method1: function() {
// ...some calculations
// No parameter cuz Form contains the selected record's data model
return a_calculated_value
}
}
}
And I want to call method1 on each of the records in Table
In addition, if possible I do not want to store this calculated value in the database.
Is this possible?
look at my example maybe it can help
<template>
<marker-table
ref="dt"
v-model="showTable"
:raw-table-data="tableData"
/>
<v-btn #click="showGasTable">
Show GAS Table
</v-btn>
<v-btn #click="shoeElecTable">
Show Electricity Table
</v-btn>
</template>
<script>
import markerTable from './components/markerTable.vue'
methods:{
showGasTable () {
this.tableData = this.gas
this.$refs.dt.popHeaders('gas')
},
showElecTable () {
this.tableData = this.elec
this.$refs.dt.popHeaders('electricity')
},
}
</script>
Component markerTable.vue
<template>
<v-data-table
:headers="headers"
:items="tableData"
>
</v-data-table>
</template>
<script>
export default { // eslint-disable-next-line
props: ['rawTableData'],
data () {
return {
headers: [],
title: '',
rawData: '',
tableData: ''
}
},
computed: {
dataUpdate () {
this.rawData = this.rawTableData
return this.rawData
}
},
methods: {
popHeaders (value) {
if (value === 'gas') {
this.headers =
[{ text: 'Volume', value: 'Vm' },
{ text: 'Day', value: 'day' }]
this.title = 'GAS Supply'
} else if (value === 'electricity') {
this.headers =
[{ text: 'Units', value: 'units' },
{ text: 'Watts', value: 'watt' },
{ text: 'Time', value: 'time' }]
this.title = 'Electric Supply'
}
}
}
You can access the method popHeader from the child component with sets of data and can process them in your child and it will be returned to your main page.

Get tiptap-vuetify to work with "Mentions" in a Vue Nuxt app

so my problem is that I can't seem to make use of the "Mentions" functionality of tiptap inside a vuetify-nuxt project.
The original example can be found here
More useful info:
Example implementation from the tiptap github here
Similar question asked and not answered fully here
Similar question asked in the vuetify integration library here
To do that I'm trying to combine documentation examples.
I guess it goes wrong around the time I try to use "tippy", which is the css library used to display the popup that actually lists users to pick from (after the #), but I can't seem to understand the real issue.
So when I type # the keydown/up event listeners are functioning, but the tippy seems to not bind the popup successfully (it's not displayed), and the following error occurs:
Editor.vue?6cd8:204 Uncaught TypeError: Cannot read property '0' of undefined
at VueComponent.enterHandler (Editor.vue?6cd8:204)
at onKeyDown (Editor.vue?6cd8:175)
at Plugin.handleKeyDown (extensions.esm.js?f23d:788)
at eval (index.es.js?f904:3298)
at EditorView.someProp (index.es.js?f904:4766)
at editHandlers.keydown (index.es.js?f904:3298)
This is my tippy.js nuxt plugin:
import Vue from "vue";
import VueTippy, { TippyComponent } from "vue-tippy";
Vue.use(VueTippy, {
interactive: true,
theme: "light",
animateFill: false,
arrow: true,
arrowType: "round",
placement: "bottom",
trigger: "click",
// appendTo: () => document.getElementById("app")
});
Vue.component("tippy", TippyComponent);
This is the component in which I'm trying to show the editor and the suggestions/mentiosn functionality:
<template>
<div>
<div class="popup">
aaaa
</div>
<editor-menu-bar v-slot="{ commands }" :editor="editor">
<div class="menubar">
<v-btn class="menubar__button" #click="commands.mention({ id: 1, label: 'Fred Kühn' })">
<v-icon left>#</v-icon>
<span>Mention</span>
</v-btn>
</div>
</editor-menu-bar>
<tiptap-vuetify v-model="localValue" :extensions="extensions" :native-extensions="nativeExtensions" :toolbar-attributes="{ color: 'grey' }" #init="onInit" />
</div>
</template>
<script>
// import the component and the necessary extensions
import {
TiptapVuetify,
Heading,
Bold,
Italic,
Strike,
Underline,
Code,
CodeBlock,
Image,
Paragraph,
BulletList,
OrderedList,
ListItem,
Link,
Blockquote,
HardBreak,
HorizontalRule,
History,
} from "tiptap-vuetify";
// TESTING
import { EditorMenuBar, Editor } from "tiptap";
import { Mention } from "tiptap-extensions";
import tippy, { sticky } from "tippy.js";
export default {
components: { TiptapVuetify, EditorMenuBar },
props: {
value: {
type: String,
default: "",
},
},
data: () => ({
editor: null,
extensions: null,
nativeExtensions: null,
// TESTING
query: null,
suggestionRange: null,
filteredUsers: [],
navigatedUserIndex: 0,
insertMention: () => {},
popup: null,
}),
computed: {
localValue: {
get() {
return this.value;
},
set(value) {
this.$emit("input", value);
},
},
// TESTING
hasResults() {
return this.filteredUsers.length;
},
showSuggestions() {
return this.query || this.hasResults;
},
},
created() {
this.extensions = [
History,
Blockquote,
Link,
Underline,
Strike,
Italic,
ListItem,
BulletList,
OrderedList,
[
Heading,
{
options: {
levels: [1, 2, 3],
},
},
],
Bold,
Link,
Code,
CodeBlock,
Image,
HorizontalRule,
Paragraph,
HardBreak,
];
this.nativeExtensions = [
// https://github.com/ueberdosis/tiptap/blob/main/examples/Components/Routes/Suggestions/index.vue
new Mention({
// a list of all suggested items
items: async () => {
await new Promise((resolve) => {
setTimeout(resolve, 500);
});
return [
{ id: 1, name: "Sven Adlung" },
{ id: 2, name: "Patrick Baber" },
{ id: 3, name: "Nick Hirche" },
{ id: 4, name: "Philip Isik" },
{ id: 5, name: "Timo Isik" },
{ id: 6, name: "Philipp Kühn" },
{ id: 7, name: "Hans Pagel" },
{ id: 8, name: "Sebastian Schrama" },
];
},
// When # is pressed, we enter here
onEnter: ({ items, query, range, command, virtualNode }) => {
this.query = query; // the field that the # queries? currently empty
this.filteredUsers = items;
this.suggestionRange = range;
this.renderPopup(virtualNode); // render popup - failing
this.insertMention = command; // this is saved to be able to call it from within the popup
},
// probably when value after # is changed
onChange: ({ items, query, range, virtualNode }) => {
this.query = query;
this.filteredUsers = items;
this.suggestionRange = range;
this.navigatedUserIndex = 0;
this.renderPopup(virtualNode);
},
// mention canceled
onExit: () => {
// reset all saved values
this.query = null;
this.filteredUsers = [];
this.suggestionRange = null;
this.navigatedUserIndex = 0;
this.destroyPopup();
},
// any key down during mention typing
onKeyDown: ({ event }) => {
if (event.key === "ArrowUp") {
this.upHandler();
return true;
}
if (event.key === "ArrowDown") {
this.downHandler();
return true;
}
if (event.key === "Enter") {
this.enterHandler();
return true;
}
return false;
},
// there may be built-in filtering, not sure
onFilter: async (items, query) => {
await console.log("on filter");
},
}),
];
},
methods: {
// TESTING
// navigate to the previous item
// if it's the first item, navigate to the last one
upHandler() {
this.navigatedUserIndex =
(this.navigatedUserIndex + this.filteredUsers.length - 1) %
this.filteredUsers.length;
},
// navigate to the next item
// if it's the last item, navigate to the first one
downHandler() {
this.navigatedUserIndex =
(this.navigatedUserIndex + 1) % this.filteredUsers.length;
},
enterHandler() {
const user = this.filteredUsers[this.navigatedUserIndex];
if (user) {
this.selectUser(user);
}
},
// we have to replace our suggestion text with a mention
// so it's important to pass also the position of your suggestion text
selectUser(user) {
this.insertMention({
range: this.suggestionRange,
attrs: {
id: user.id,
label: user.name,
},
});
this.editor.focus();
},
renderPopup(node) {
if (this.popup) {
return;
}
// ref: https://atomiks.github.io/tippyjs/v6/all-props/
this.popup = tippy(".page", {
getReferenceClientRect: node.getBoundingClientRect, // input location
appendTo: () => document.body, // must be issue
interactive: true,
sticky: true, // make sure position of tippy is updated when content changes
plugins: [sticky],
content: this.$refs.suggestions,
trigger: "mouseenter", // manual
showOnCreate: true,
theme: "dark",
placement: "top-start",
inertia: true,
duration: [400, 200],
});
},
destroyPopup() {
if (this.popup) {
this.popup[0].destroy();
this.popup = null;
}
},
beforeDestroy() {
this.destroyPopup();
},
/**
* NOTE: destructure the editor!
*/
onInit({ editor }) {
this.editor = editor;
},
},
};
</script>
How can I get the "suggestions" item display in the aforementioned setting?

How to generate dynamic data table through api call in vuetify in vuejs

I am trying to generate dynamic data table through Vuetify ,in vuejs but dont see any example in vuetify official documentation,does anyone mind sharing an example
By "dynamic" I'm guessing you mean data loads asynchronously from an API. The Vuetify docs have numerous examples of data tables including one that simulates requesting data from an API. A very basic implementation looks something like (note: this example uses the random user generator API:
<template>
<v-data-table
:headers="headers"
:items="people"
item-key="login.uuid"
:loading="loading"
:options.sync="options"
:server-items-length="totalPeople"
#pagination="updatePage"
#update:options="customSort"
></v-data-table>
</template>
<script>
import axios from 'axios'
export default {
data: () => ({
apiURL: 'https://randomuser.me/api/',
headers: [
{ text: 'Name', value: 'name', align: 'start' },
{ text: 'Country', value: 'country' },
{ text: 'DOB (Age)', value: 'dob' },
{ text: 'Contacts', value: 'contacts' },
],
loading: false,
options: {
page: 1,
itemsPerPage: 10,
sortBy: ['name'],
sortDesc: [true],
},
people: [],
}),
mounted () {
this.getPeople()
},
methods: {
async getPeople (page = 1, results = 10, seed = 'example') {
this.loading = true
const params = { params: { page, results, seed } }
try {
this.people = (await axios.get(this.apiURL, params)).data.results
this.loading = false
} catch (error) {
console.log(error)
this.loading = false
}
},
updatePage (pagination) {
const { itemsPerPage: results, page } = pagination
this.pagination = pagination
this.getPeople({ page, results })
},
customSort (options) {
// The Random User API does NOT support sorting, but if it did, you
// would need to make an API call that returned a sorted
// list of results based on the sort parameter(s)
console.log(options)
},
},
}
</script>
For a more complex example that uses more of Vuetify's features, check out this codepen.

Implement Datatables responsive details display with Ajax data

I want to implement Datatable's responsive details with Bootstrap modal but it doesn't work for me. The first thing I noticed is that the "plus-sign" does not appear on the first column of my table. I'm not sure if this is because I'm using ajax data and need additional parameters that the example doesn't show or because I've added an auto number as my first column.
Here is the table HTML:
<table id="users" class="table table-striped table-bordered nowrap" data-conf="#Model.ExtraVM.DialogMsg" data-title="#Model.ExtraVM.DialogTitle" data-btnok="#Model.ExtraVM.Button1" data-btncancel="#Model.ExtraVM.Button2">
<thead>
<tr>
<th>#Model.HeadingVM.Col1</th>
<th>#Model.HeadingVM.Col2</th>
<th>#Model.HeadingVM.Col3</th>
<th>#Model.HeadingVM.Col4</th>
<th>#Model.HeadingVM.Col5</th>
</tr>
</thead>
<tbody></tbody>
</table>
Here is the jquery code:
<script>
$(document).ready(function () {
var t = $("#users").DataTable({
responsive: {
details: {
display: $.fn.dataTable.Responsive.display.modal({
header: function (row) {
var data = row.data();
return 'Details for ' + data[0] + ' ' + data[1];
}
}),
renderer: $.fn.dataTable.Responsive.renderer.tableAll({
tableClass: 'table'
})
}
},
columnDefs: [{
"searchable": false,
"orderable": false,
"targets": 0
}],
order: [[1, 'asc']],
ajax: {
url: "/api/users",
dataSrc: ""
},
columns: [
{
data: "id"
},
{
data: "firstName"
},
{
data: "lastName"
},
{
data: "userName"
},
{
data: "id",
render: function (data) {
return "<a href='#'><i class='fa fa-eye' data-id='" + data + "' data-toggle='modal' data-target='#dataPopup'></i></a> | <a href='#'><i class='fa fa-pencil js-edit' data-id='" + data + "'></i></a> | <a href='#'><i class='fa fa-trash js-delete' data-id='" + data + "'></i></a>";
}
}
]
});
t.on('order.dt search.dt', function () {
t.column(0, { search: 'applied', order: 'applied' }).nodes().each(function (cell, i) {
cell.innerHTML = i + 1;
});
}).draw();
$("#users").on("click", ".js-delete", function () {
var btn = $(this);
var confirm = btn.parents("table").attr("data-conf");
var dialogTitle = btn.parents("table").attr("data-title");
var btnOK = btn.parents("table").attr("data-btnOk");
var btnCancel = btn.parents("table").attr("data-btnCancel");
bootbox.dialog({
message: confirm,
title: dialogTitle,
buttons: {
main: {
label: btnCancel,
className: "btn-default",
callback: function () {
var result = "false";
}
},
success: {
label: btnOK,
className: "btn-primary",
callback: function () {
$.ajax({
url: "/api/users/" + btn.attr("data-id"),
method: "DELETE",
success: function () {
btn.parents("tr").remove();
}
});
}
}
}
});
});
});
</script>
}
This is how my tables looks (as you can see, the plus-sign is missing):
Your code seems to be fine. Plus sign only appears when viewing area is small enough and one of the columns become hidden.
There is no setting to force (+) sign to appear but you can use a trick with extra empty column and class none on it which will force column to always be hidden. See Class logic for more details.
See this jsFiddle for code and demonstration.