Expand all data table entries at once in VuetifyJS/VueJS - vuetify.js

How to expand all entries of this VuetifyJS/VueJS data table example at once and not only one at the time?
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="desserts"
hide-actions
item-key="name"
expand
>
<template slot="items" slot-scope="props">
<tr #click="props.expanded = !props.expanded">
<td>{{ props.item.name }}</td>
<td class="text-xs-right">{{ props.item.calories }}</td>
<td class="text-xs-right">{{ props.item.fat }}</td>
</tr>
</template>
<template slot="expand" slot-scope="props">
<v-card flat>
<v-card-text>Peek-a-boo!</v-card-text>
</v-card>
</template>
</v-data-table>
</v-app>
</div>
Here is an example for a single expand:
https://codepen.io/anon/pen/yEWNxE?&editors=101#

There is an open-issue with regards to this feature, make sure to follow it and get notified when it's resolved.
Temporary solution by #zikeji follows:
Add reference to the table:
<v-data-table ref="dTable">
Expand rows manually when component loads:
mounted() {
for (let i = 0; i < this.desserts.length; i += 1) {
const item = this.desserts[i];
this.$set(this.$refs.dTable.expanded, item.name, true);
}
},
Codepen

in Veutify v2.1.13
Just copied data to expanded from dessertson click.
methods: {
expandAll: function() {
console.log("All expanded.");
this.$data.expanded = this.$data.desserts;
},
collapseAll: function() {
console.log("All collapsed.");
this.$data.expanded = [];
}
},
codepen

You should extend the component instead and set the values as such.
MyVDataTable.vue
<script>
import VDataTable from 'vuetify/src/components/VDataTable'
export default {
extends: VDataTable,
props: ['deserts'],
mounted () {
for (let i = 0; i < this.desserts.length; i += 1) {
const item = this.desserts[i];
this.$set(this.expanded, item.name, true);
}
}
}
Then you would replace your VDataTable with this one.

For me the above and other solutions found on google were not working. So i created another approach.
To the row where you usually add your #click expand function you have to add a custom directive.
for example v-open
and pass your props.
<tr #click="props.expanded = !props.expanded" v-open="props">
then inside your component
directives: {
open: {
// directive definition
bind: function (el,binding) {
//only the first row
if( binding.value.index === 0){
binding.value.expanded = true
}
//or all rows
//binding.value.expanded = true
}
}
},

Related

Increasing performance of v-data-table with custom cells and async data loading

I'm creating a page with v-data-table. Some content of this table is loading at mounted stage, but the data for one column should be loaded line-by-line in the background by async API calls after rendering the whole table. Table rows should also be colored based on data returned from API call.
I've already developed this page, but stuck into one issue - when the table contains composite cells that was redefined by item slot (by example, a cell with icons, tooltips or spans), table row update time significantly increases.
According to business logic, the page may contain a large amount of rows, but I can't use v-data-table pagination to reduce entries count at one page.
The question is - how can I update row (in fact, just its color and one cell value) with a little performance loss as possible?
There is a Codepen with this problem. The way of loading data into the page is completely preserved in this Codepen, but API calls was replaced by promises with fixed timeout.
The problem still exists in Codepen. By default all the requests for 100 items have passed in 12-13 seconds (there's a counter at the bottom of the page). When I comment out last td, they're passed just in 7-8 seconds. When I comment out another one td (second from the end), they're passed in 6 seconds. When I increase items count to 1000, row update time is also increases.
new Vue({
el: '#app',
vuetify: new Vuetify(),
data () {
return {
headers: [
{
text: 'Dessert (100g serving)',
value: 'name',
},
{ text: 'Second name', value: 'secondName' },
{ text: 'Fat (g)', value: 'fat' },
{ text: 'Carbs (g)', value: 'carbs' },
{ text: 'Protein (g)', value: 'protein' },
{ text: 'Max value', value: 'maxValue' },
{ text: 'Actions', value: 'id' },
],
desserts: [],
timerStart: null,
loadingTime: null,
}
},
created() {
this.generateDesserts();
},
mounted() {
this.countMaxValues(this.desserts).then(() => {
this.loadingTime = (Date.now() - this.timerStart) / 1000;
});
},
methods: {
generateDesserts() {
let dessertNames = [
'Frozen Yogurt ',
'Ice cream sandwich ',
'Eclair',
'Cupcake',
'Gingerbread',
'Jelly bean',
'Lollipop',
'Honeycomb',
'Donut',
'KitKat',
null
];
for (let i = 0; i < 100; i++) {
let dessert = {
id: i,
name: dessertNames[Math.floor(Math.random() * dessertNames.length)],
secondName: dessertNames[8 + Math.floor(Math.random() * (dessertNames.length - 8))],
fat: Math.random() * 100,
carbs: Math.floor(Math.random() * 100),
protein: Math.random() * 10
};
this.desserts.push(dessert);
}
},
async countMaxValues(array) {
this.timerStart = Date.now();
for (const item of array) {
await this.countMaxValue(item).catch(() => {
//Even when one request throws error we should not stop others
})
}
},
async countMaxValue(item) {
await new Promise(resolve => setTimeout(resolve, 50)).then(() => {
let maxVal = Math.random() * 100;
item.maxValue = maxVal < 20 ? null : maxVal;
this.desserts.splice(item.id, 1, item);
});
}
}
})
<div id="app">
<v-app id="inspire">
<v-data-table
:headers="headers"
:items="desserts"
:footer-props='{
itemsPerPageOptions: [-1],
prevIcon: null,
nextIcon: null,
}'
>
<template v-slot:item="props">
<tr :style="{
background: (props.item.maxValue !== null && (props.item.carbs < props.item.maxValue))
? '#ffcdd2'
: (
(props.item.maxValue !== null && (props.item.carbs > props.item.maxValue)
? '#ffee58'
: (
props.item.maxValue === null ? '#ef5350' : 'transparent'
)
)
)}">
<td>{{ props.item.name || '—' }}</td>
<td>{{ props.item.secondName || '—' }}</td>
<td>{{ props.item.fat }}</td>
<td>{{ props.item.carbs }}</td>
<td>{{ props.item.protein }}</td>
<td>
<span>
{{ props.item.maxValue || '—' }}
</span>
<v-btn v-if="props.item.name && props.item.maxValue" icon>
<v-icon small>mdi-refresh</v-icon>
</v-btn>
</td>
<td class="justify-center text-center" style="min-width: 100px">
<v-tooltip bottom v-if="props.item.name && props.item.secondName">
<template v-slot:activator="{ on }">
<v-icon v-on="on"
class="mr-2"
small
>
format_list_numbered_rtl
</v-icon>
</template>
<span>Some action tooltip</span>
</v-tooltip>
<v-tooltip bottom v-if="props.item.name && props.item.secondName">
<template v-slot:activator="{ on }">
<v-icon v-on="on"
class="mr-2"
small
>
edit
</v-icon>
</template>
<span>Edit action tooltip</span>
</v-tooltip>
<v-tooltip bottom v-if="props.item.name === 'KitKat'">
<template v-slot:activator="{ on }">
<v-icon v-on="on"
small
>
delete
</v-icon>
</template>
<span>Delete action tooltip</span>
</v-tooltip>
</td>
</tr>
</template>
</v-data-table>
<p>{{ "Page loading time (sec): " + (loadingTime || '...') }}</p>
</v-app>
</div>
It seems Vue can update DOM more efficient if it is wrap in component (Sorry, I don't know in detail why).
This is your original code in JSFiddle. It will use around 12-13 seconds.
Then I create a component which wrap your entire tr:
const Tr = {
props: {
item: Object
},
template: `
<tr>
... // change props.item to item
</tr>
`
}
new Vue({
el: '#app',
vuetify: new Vuetify(),
components: {
'tr-component': Tr // register Tr component
},
...
async countMaxValue(item) {
await new Promise(resolve => setTimeout(resolve, 50)).then(() => {
let maxVal = Math.random() * 100;
// update entire object instead of one property since we send it as object to Tr
let newItem = {
...item,
maxValue: maxVal < 20 ? null : maxVal
}
this.desserts.splice(newItem.id, 1, newItem);
});
}
})
And your html will looks like:
<v-data-table
:headers="headers"
:items="desserts"
:footer-props='{
itemsPerPageOptions: [-1],
prevIcon: null,
nextIcon: null,
}'>
<template v-slot:item="props">
<tr-component :item='props.item'/>
</template>
</v-data-table>
The result will use around 6-7 seconds which is only 1-2 seconds to update DOM.
Or if you find out that your function trigger very fast (In your example use 50ms which is too fast in my opinion) you could try throttle it to less update DOM.
...
methods: {
async countMaxValue(item) {
await new Promise(resolve => setTimeout(resolve, 50)).then(() => {
let maxVal = Math.random() * 100;
let newItem = {
...item,
maxValue: maxVal < 20 ? null : maxVal
}
this.changes.push(newItem) // keep newItem to change later
this.applyChanges() // try to apply changes if it already schedule it will do nothing
});
},
applyChanges () {
if (this.timeoutId) return
this.timeoutId = setTimeout(() => {
while (this.changes.length) {
let item = this.changes.pop()
this.desserts.splice(item.id, 1, item)
}
this.timeoutId = null
}, 1500)
}
}
The result will use around 5-6 seconds but as you can see it's not immediately update.
Or you may try to call your API in parallel such as 10 requests, you could reduce from to wait 100 * 50 ms to around 10 * 50 ms (mathematically).
I hope this help.

Is it possible to set actions call a function at default pagination of v-data-table?

I would like to call a function when I click the previous button, next button and the rowsPerPage numbers are changed.
Is it possible to use default v-data-table pagination when I use actions-prepend slot?
I tried but it seems not working.
<template>
<v-data-table
:headers="headers"
:items="alertList"
:pagination.sync="pagination"
:rows-per-page-items="pagination.rowsPerPageItems"
:total-items="pagination.totalCount"
:loading="paginationLoading"
class="elevation-1 block_display"
>
<template v-slot:items="props">
<td class="text-xs-left">{{ props.item.id }}</td>
...
</template>
<template v-slot:actions-prepend> // add another button before the default pagination
<v-btn #click='goPrev'> < </btn>
</template>
</v-data-table>
</template>
<script>
export default {
data() {
....
},
methods: {
goPrev() {
// want to call vuex actions
}
}
}
</script>
I got an answer which would be better or not.
<template>
<v-data-table
:headers="headers"
:items="alertList"
:pagination.sync="cstPagination"
:rows-per-page-items="pagination.rowsPerPageItems"
:total-items="pagination.totalCount"
:loading="paginationLoading"
class="elevation-1 block_display"
>
<template v-slot:items="props">
<td class="text-xs-left">{{ props.item.id }}</td>
...
</template>
</v-data-table>
</template>
<script>
export default {
data() {
....
},
computed: {
get() {
return this.pagination // props
},
set(val) {
// val: changed pagination value
}
}
}
</script>

how to add filters and search in tables in vuejs?

I am learning vue js and building a SPA i want to know how do I add filters and search using an input tag. I also want to add a feature that when i click on a particular name on the table it should open the profile of that person also a select all functionality
<template>
<div class="animated fadeIn">
<input type="text" placeholder="Enter Name" v-model="searchText">
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>College Name</th>
<th>College City</th>
<th>Level Name</th>
<th>Submitted</th>
<th>Pending</th>
<th>Completed</th>
<th>Approved</th>
<th>Rejected</th>
</tr>
</thead>
<tbody>
<tr v-for="profile in profilesdata">
<td>{{profile.first_name}}</td>
<td>{{profile.college_name}}</td>
<td>{{profile.college_city}}</td>
<td>{{profile.level_name}}</td>
<td>{{profile.submitted}}</td>
<td>{{profile.pending}}</td>
<td>{{profile.complete}}</td>
<td>{{profile.approve}}</td>
<td>{{profile.rejected}}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default{
name: 'profiles',
data () {
return{
profilesdata: []
}
},
created:function(){
this.loadlike()
},
methods:{
loadlike:function(){
this.$http.get('/api/profiles').then(function (res) {
this.profilesdata = res.body
console.log(53+this.profiles)
})}}}
</script>
you could probably return computed list instead of profilesData
<template>
...
<input type="text" placeholder="Enter Name" v-model="searchText">
...
<tr v-for="profile in computedProfilesData">
...
</template>
<script>
export default{
...
data () {
return {
...
// - you need info for searchText
searchText: ''
}
}
...
computed: {
computedProfilesData(){
let searchString = this.searchText;
return this.profilesdata.filter((profile) => {
// example
profile.first_name.indexOf(searchString) !== -1;
})
}
}
</script>
there is a lot of different ways to do this, this is just one of them.
you can pass that searchString to API and return result list, it all comes to that
what do you really need.
You can make a computed for the filtered data:
computed: {
filteredProfiles() {
return this.profilesdata.filter(profile => {
// TODO filter profile with this.searchText
})
}
}
And then change the v-for to loop over the filtered data: <tr v-for="profile in filteredProfiles">

Vue not rendering my data

I have a component that I get data on create with ajax and I show this in a Template. Basically I have this:
my Vue code:
Vue.component('feedback-table', {
template: '#grid-template',
data: function () {
return {
chancesOfApproval:[],
}
},
created:function(){
$.getJSON('/getFeedbackQuestionsAndChances',function(data){
this.chancesOfApproval = data.chancesOfApproval;
}.bind(this));
},
});
new Vue({
el: '#feedback-wrapper',
});
And here is my template:
<template id="grid-template">
<table class="table table-bordered table-responsive table-striped">
<tr v-for="entry in chancesOfApproval">
<td>#{{ entry.position }}</td>
</tr>
</table>
</template>
<div id="feedback-wrapper">
<feedback-table></feedback-table>
</div>
The data is being getted from the jquery because if I do console.log(this.chanceOfApproval) for example, it appears fine in the console. And I do not get any error in my console, so I have no idea why it does not work.
It behaves as expected here, where I've switched out the getJSON for a setTimeout doing the same assignment.
Vue.component('feedback-table', {
template: '#grid-template',
data: function() {
return {
chancesOfApproval: [],
}
},
created: function() {
/*
$.getJSON('/getFeedbackQuestionsAndChances', function(data) {
this.chancesOfApproval = data.chancesOfApproval;
}.bind(this));
*/
setTimeout(() => {
this.chancesOfApproval = [{
position: 1
}, {
position: 2
}];
}, 500);
},
});
new Vue({
el: '#feedback-wrapper',
});
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.min.js"></script>
<template id="grid-template">
<table class="table table-bordered table-responsive table-striped">
<tr v-for="entry in chancesOfApproval">
<td>#{{ entry.position }}</td>
</tr>
</table>
</template>
<div id="feedback-wrapper">
<feedback-table></feedback-table>
</div>
You are using function.bind(this) to bring this from outer scope to the function scope, in your $http success handler.
Have you checked if it is not creating any issues? I am not aware of how bind is implemented. Maybe it is not allowing Vue to trigger its Reactivity System.
If you see values in console as a plain JSON object, then it is probably wrong. Vue.js modifies object params to getters / setters / observers so that it can bind to the DOM.
Alternatively, why dont you try the arrow function or using a self variable, just to see if it solves the issue?
Arrow function (ES6 and above):
created:function(){
$.getJSON('/getFeedbackQuestionsAndChances',data => {
this.chancesOfApproval = data.chancesOfApproval;
});
}
Or use a local variable:
created:function(){
var self = this; // self points to this of Vue component
$.getJSON('/getFeedbackQuestionsAndChances',function(data){
self.chancesOfApproval = data.chancesOfApproval;
});
}
Another note: instead of setTimeout(), you also have the option of Vue.nextTick() as follows:
Vue.nextTick(function () {
// DOM updated
})
or in your case:
this.$nextTick(function () {
// DOM updated
})

Sorting and Filtering ajax data using Laravel and VueJs

Current code is sorting and filtering data using vue.js. It is working fine but data is dummy, it is hardcoded. I need to get data dynamically from table using vue js and laravel. How can I get dynamic data in gridData?
JS
Vue.component('demo-grid', {
template: '#grid-template',
props: {
data: Array,
columns: Array,
filterKey: String
},
data: function () {
var sortOrders = {}
this.columns.forEach(function (key) {
sortOrders[key] = 1
})
return {
sortKey: '',
sortOrders: sortOrders
}
},
methods: {
sortBy: function (key) {
this.sortKey = key
this.sortOrders[key] = this.sortOrders[key] * -1
}
}
})
// bootstrap the demo
var demo = new Vue({
el: '#app',
data: {
searchQuery: '',
gridColumns: ['name', 'power'],
gridData: [
{ name: 'Chuck Norris', power: Infinity },
{ name: 'Bruce Lee', power: 9000 },
{ name: 'Jackie Chan', power: 7000 },
{ name: 'Jet Li', power: 8000 }
]
}
})
laravel.blade.php
#extends('layouts.app')
#section('title', 'Customers List')
#section('styles')
#endsection
#section('content')
<div class="container">
<div class="row">
<div class="col-md-10 col-md-offset-1">
<div class="panel panel-default">
<div class="panel-heading">Customers List</div>
<div class="panel-body">
<script type="text/x-template" id="grid-template">
<table class="table table-hover table-bordered">
<thead>
<tr>
<th v-for="key in columns" #click="sortBy(key)" :class="{active: sortKey == key}">#{{key | capitalize}}<span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'"></span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="entry in data | filterBy filterKey | orderBy sortKey sortOrders[sortKey]">
<td v-for="key in columns">
#{{entry[key]}}
</td>
</tr>
</tbody>
</table>
</script>
<div id="app">
<form id="search">
Search <input name="query" v-model="searchQuery">
</form>
<demo-grid :data="gridData" :columns="gridColumns" :filter-key="searchQuery"></demo-grid>
</div>
</div>
</div>
</div>
</div>
</div>
#endsection
#section('scripts')
<script src="/js/vue.js"></script>
<script src="/js/vue-resource.min.js"></script>
<script src="/js/customers.js"></script>
#endsection
You will need to do a few things.
First, in Laravel, create a new route in your routes.php file, for ex.:
Route::get('/api/fighters', 'SomeController#index');
Then in your controller (somecontroller.php), you will have a method index which will query your database table and return it as JSON data.
public function index() {
//query your database any way you like. ex:
$fighters = Fighter::all();
//assuming here that $fighters will be a collection or an array of fighters with their names and power
//when you just return this, Laravel will automatically send it out as JSON.
return $fighters;
}
Now, in Vue, your can call this route and grab the data. Using AJAX. You can use any AJAX library that you like, even jQuery. I currently use Superagent.js. Vue will work just fine with any.
So, in your Vue code, create a new method to get your data.:
methods: {
getDataFromLaravel: function() {
//assign `this` to a new variable. we will use it to access vue's data properties and methods inside our ajax callback
var self = this;
//build your ajax call here. for example with superagent.js
request.get('/api/fighters')
.end(function(err,response) {
if (response.ok) {
self.gridData = response.body;
}
else {
alert('Oh no! We have a problem!');
}
}
}
}
Then you can just call this new method using a button or anyway you like. For example, using a button click event:
<button type="button" #click="getDataFromLaravel">Get Data</button>
Or you can even just have the data loaded automatically using Vue's ready function:
// bootstrap the demo
var demo = new Vue({
el: '#app',
data: {
.... }
ready: function () {
this.getDataFromLaravel();
},
methods: {
.... }
});
Done! You now have your database data assigned to Vue's data property gridData.

Resources