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.
Related
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?
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.
I'm trying to implement a datatable with mdbootstrap in vue.js.
I would like to update table data on events and when initialized but it does not work.
Template;
<div class="col-md-12">
<mdb-datatable
:data="data"
striped
bordered
/>
</div>
Script;
import { mdbDatatable } from 'mdbvue';
export default {
name: 'userManagement',
components: {
mdbDatatable
},
data() {
return {
className:"",
classList: [],
data: {
columns: [
{
label: 'Name',
field: 'className',
sort: 'asc'
}, {
label: 'ID',
field: 'id',
sort: 'asc'
}
],
rows: [
{
className: 'Tiger Nixon',
id:1
},
{
className: 'Garrett Winters',
id:2
}
]
}
}
},
methods: {
getClassList(){
var _this = this;
this.$axios.get('my_url/admin/classes').then(function (response) {
if (response.status === 200) {
_this.data.rows = [];
response.data.forEach(function (obj) {
let item = {
className: obj.className,
id: obj.id
};
_this.data.rows.push(item);
});
}
}).catch(function (err) {
alert("" + err);
});
}
},
mounted(){
this.getClassList();
},
It always shows default values, I check the data rows from console the value seems to be updated but no change on the datatable.
Any help would be appreciated.
We've found the solution for Your issue.
The new code is available here: https://mdbootstrap.com/docs/vue/tables/datatables/#external-api
Also to make sure the data is reactive it's necessary to add the following code to the Datatable component in our package:
watch: {
data(newVal) {
this.columns = newVal.columns;
},
(...)
}
It will be fixed in the next MDB Vue release.
I installed mdbvue 5.5.0 which includes the change that mikolaj described. This caused the table columns to update when changed but in order to get the rows to update too I had to add to the watch method in Datatable.vue as follows:
watch: {
data(newVal) {
this.columns = newVal.columns;
this.rows = newVal.rows;
},
I'm using the vue-table-2 component : https://github.com/matfish2/vue-tables-2 and I'm struggling to make it work as I want.
I have an API (using API Platform) which is already making all the pagination works. So when I fetch for the first time my list of companies it gives the first ten results + the total rows. I store all of this in my vuex store and I am able to display the list in my table (with useVuex false or true so I don't really understand how this parameter works). The issue is I cannot paginate because I only got ten results and can't get the total rows count to change so I do not get the pagination element at the bottom and can't bind something to it to fetch the other pages later.
Since I'm pretty new to VueJs I can't figure out how this should work with my API. Here is my code so far:
My DataTable element :
<v-client-table name="company" :columns="columns" :data="companies" :options="options" :theme="theme" id="dataTable">
<b-button slot="actions" slot-scope="props" variant="secondary" size="sm" class="btn-pill">Edit</b-button>
</v-client-table>
And my script :
<script>
import Vue from 'vue'
import { ClientTable, Event } from 'vue-tables-2'
import { mapGetters } from 'vuex'
Vue.use(ClientTable)
export default {
name: 'DataTable',
components: {
ClientTable,
Event,
},
data: function() {
return {
columns: ['name', 'actions'],
options: {
headings: {
name: 'Name',
actions: 'Actions',
},
sortable: ['name'],
filterable: ['name'],
sortIcon: {
base: 'fa',
up: 'fa-sort-asc',
down: 'fa-sort-desc',
is: 'fa-sort',
},
pagination: {
chunk: 5,
edge: false,
nav: 'scroll',
},
},
useVuex: true,
theme: 'bootstrap4',
template: 'default',
}
},
computed: {
...mapGetters({
companies: 'companyModule/companies',
totalCompanies: 'companyModule/totalCompanies',
}),
},
}
</script>
This is in my component loading the data where I specify how many items per page I want my api to send me and the page I want:
created() {
this.$store.dispatch('companyModule/FETCH_COMPANIES', {
page: 1,
nbItemPerPage: 10,
})
},
My store looks like this:
import ApiService from '#/services/APIService'
export const companyModule = {
strict: true,
namespaced: true,
state: {
companies: [],
totalCompanies: 0,
},
getters: {
companies: state => state.companies,
totalCompanies: state => state.totalCompanies,
},
mutations: {
SET_COMPANIES(state, data) {
state.companies = data.companies
state.totalCompanies = data.totalCompanies
},
},
actions: {
FETCH_COMPANIES(context, payload) {
payload.entity = 'companies'
return ApiService.get(payload).then(data => {
context.commit('SET_COMPANIES', data)
})
},
},
}
When I received my data, I stored everything in my companies state and for now I'm storing everything I'm getting from my API and this looks like this :
{
"#id": "/api/admin/companies/1",
"#type": "Company",
"id": 1,
"name": "Test Company",
}
Thanks in advance for your help !
watch: {
'companies': {
handler (newValue, oldValue) {
this.totalRows=this.companies.length;
},
deep: true
}
},
Vue “watch” is a powerful reactive option attribute to the vue framework that helps front-end developers listen to changes made on a value and then react to it.
So, once the totalRows is set, you can assign it to the table attributes and the table will change accordingly.
For my case, I used it for bootstrap table as follows:
<b-pagination
:total-rows="totalRows"
:per-page="perPage"
></b-pagination>
i'm using vue-slick to show my images..
i've tried every solution that i found.. but none is working.
here is my template:
<slick ref="slick" :options="slickOptions">
<img v-for="(item) in categories" :src="'/images/category/'+item.image_url" alt="" class="img-fluid" >
</slick>
and here is my scripts:
data () {
return {
categories:'',
slickOptions: {
dots: true,
infinite: false,
autoplay: false,
arrows : false,
draggable:true,
speed: 1000,
slidesToShow: 1,
slidesToScroll: 1,
},
}
},
mounted() {
let _this = this;
axios({
method: 'post',
url: '/api/category',
data : {'name' : _this.name}
}).then( (response)=> {
console.log(response.data.data);
_this.categories = response.data.data;
}).catch((error) => {
console.log(error.response)
});
},
methods:{
next() {
this.$refs.slick.next();
},
prev() {
this.$refs.slick.prev();
},
reInit() {
this.$refs.slick.reSlick()
}
},
and only loading the image, and the slick is not working...!!?
I have faced the same issue, and what I did to solve this is to put the
v-if="categories.length > 0" on the <slick> tag.
It make the slick won't be created before the data that we want to display contains the data first.
Use below code to reinit slick, and call on success function of response
reInit() {
let currIndex = this.$refs.slick.currentSlide()
this.$refs.slick.destroy()
this.$nextTick(() => {
this.$refs.slick.create()
this.$refs.slick.goTo(currIndex, true)
})
}
I'm assuming your Axios is returning data with the structure you are looking for.
I'm also assuming you are using the vue-slick component and not slick.
You should iterate through a DIV like stated in the documentation. Without Axios, I did this:
In template:
<slick ref="slick" :options="slickOptions">
<div>Escolhe uma configuração...</div>
<div v-for="d in data1"><a class="inline" :href="d.image"><img :src="d.image" alt="">{{ d.text }}</a></div>
</slick>
In Javascript:
data: function() {
return {
data1: [
{ image: 'http://placehold.it/100x100', text: 'Config1' },
{ image: 'http://placehold.it/100x100', text: 'Config2' },
{ image: 'http://placehold.it/100x100', text: 'Config3' },
{ image: 'http://placehold.it/100x100', text: 'Config4' }
]
}