How to combine Filtering, Grouping, and Sorting in Kendo UI Vue Grid (native) - vue.js

I'm trying to enable some operations on my grid such as grouping, filtering and sorting, individually they works as shown in the docs but there is no an example of those functionality working together.
By myself I was able to combine sorting and filtering but grouping does not work when i'm adding it as it shown in the docs. look at at my code
<template>
<div>
<Grid :style="{height: '100%'}"
ref="grid"
:data-items="getData"
:resizable="true"
:reorderable="true"
#columnreorder="columnReorder"
:filterable="true"
:filter="filter"
#filterchange="filterChange"
:sortable="true"
:sort= "sort"
#sortchange="sortChangeHandler"
:groupable="true"
:group= "group"
#dataStateChange="dataStateChange"
:columns="columns">
</Grid>
</div>
</template>
<script>
export default {
data() {
return {
items: [],
editID: null,
columns: [
{ field: 'AbsenceEmployeID', filterable:false, editable: false, title: '#'},
{ field: 'Employe', title: 'Employer', cell: DropDownEmployes},
{ field: 'Remarque', title: 'Remarque'},
{ field: 'Type', title: 'Type', cell: DropDownTypes},
{ field: 'CreatedDate', filter:'date', editable: false, editor: 'date', title: 'créé le', format: '{0:d}'},
{ title: 'Actions', filterable:false, cell: CommandCell}
],
filter: {
logic: "and",
filters: []
},
sort: [
{ field: 'CreatedDate', dir: 'desc' }
],
group: [],
gridData: []
}
}
mounted() {
this.loadItems()
},
computed: {
absencesList() {
return this.items.map((item) => Object.assign({ inEdit: item.AbsenceEmployeID === this.editID}, item));
},
getData() {
return orderBy(filterBy(this.absencesList, this.filter), this.sort);
},
...mapState({
absences: state => state.absences.absences
})
}
methods: {
loadItems () {
this.$store.dispatch('absences/getAbsences')
.then(resp => {
this.items = this.absences.map(item => item)
})
},
filterChange: function(ev) {
this.filter = ev.filter;
},
columnReorder: function(options) {
this.columns = options.columns;
},
sortChangeHandler: function(e) {
this.sort = e.sort;
},
// the following is for grouping but not yet used, read more
groupedData: function () {
this.gridData = process(this.getData, {group: this.group});
},
createAppState: function(dataState) {
this.group = dataState.group;
this.groupedData();
},
dataStateChange: function (event) {
this.createAppState(event.data);
},
}
}
</script>
The last three methods are not used yet, so filtering and sorting is working perfectly as of now. then in other to enable grouping I want to replace :data-items="getData" by :data-items="gridData" and run this.groupedData() method after the items are loaded but grouping doesn't work.
I think everything should be handle by the dataStateChange event and process() function but I also tried but without success

If you define the filterchange and sortchange events they are being triggered for filter and sort and you will have to updated data in their handlers. If you rather want to use datastatechage event for all the changes you have to remove the filterchange and sortchange events and the datastatechage event will be triggered instead of them. In this case you will have to update the data in its handler.
You can use the process method of #progress/kendo-data-query by passing the respective parameter each data change that is needed as in the example below:
const result = process(data, {
skip: 10,
take: 20,
group: [{
field: 'category.categoryName',
aggregates: [
{ aggregate: "sum", field: "unitPrice" },
{ aggregate: "sum", field: "unitsInStock" }
]
}],
sort: [{ field: 'productName', dir: 'desc' }],
filter: {
logic: "or",
filters: [
{ field: "discontinued", operator: "eq", value: true },
{ field: "unitPrice", operator: "lt", value: 22 }
]
}
});
Hers is a sample stackblitz example where such example is working correctly - https://stackblitz.com/edit/3ssy1k?file=index.html

You need to implement the groupchange method to handle Grouping
I prefer to use process from #progress/kendo-data-query
The following is a complete example of this
<template>
<Grid :style="{height: height}"
:data-items="gridData"
:skip="skip"
:take="take"
:total="total"
:pageable="pageable"
:page-size="pageSize"
:filterable="true"
:filter="filter"
:groupable="true"
:group="group"
:sortable="true"
:sort="sort"
:columns="columns"
#sortchange="sortChangeHandler"
#pagechange="pageChangeHandler"
#filterchange="filterChangeHandler"
#groupchange="groupChangeHandler"
/>
</template>
<script>
import '#progress/kendo-theme-default/dist/all.css';
import { Grid } from '#progress/kendo-vue-grid';
import { process } from '#progress/kendo-data-query';
const sampleProducts = [
{
'ProductID': 1,
'ProductName': 'Chai',
'UnitPrice': 18,
'Discontinued': false,
},
{
'ProductID': 2,
'ProductName': 'Chang',
'UnitPrice': 19,
'Discontinued': false,
},
{
'ProductID': 3,
'ProductName': 'Aniseed Syrup',
'UnitPrice': 10,
'Discontinued': false,
},
{
'ProductID': 4,
'ProductName': "Chef Anton's Cajun Seasoning",
'UnitPrice': 22,
'Discontinued': false,
},
];
export default {
components: {
Grid,
},
data () {
return {
gridData: sampleProducts,
filter: {
logic: 'and',
filters: [],
},
skip: 0,
take: 10,
pageSize: 5,
pageable: {
buttonCount: 5,
info: true,
type: 'numeric',
pageSizes: true,
previousNext: true,
},
sort: [],
group: [],
columns: [
{ field: 'ProductID', filterable: false, title: 'Product ID', width: '130px' },
{ field: 'ProductName', title: 'Product Name' },
{ field: 'UnitPrice', filter: 'numeric', title: 'Unit Price' },
{ field: 'Discontinued', filter: 'boolean', title: 'Discontinued' },
],
};
},
computed: {
total () {
return this.gridData ? this.gridData.length : 0;
},
},
mounted () {
this.getData();
},
methods: {
getData: function () {
this.gridData = process(sampleProducts,
{
skip: this.skip,
take: this.take,
group: this.group,
sort: this.sort,
filter: this.filter,
});
},
// ------------------Sorting------------------
sortChangeHandler: function (event) {
this.sort = event.sort;
this.getData();
},
// ------------------Paging------------------
pageChangeHandler: function (event) {
this.skip = event.page.skip;
this.take = event.page.take;
this.getData();
},
// ------------------Filter------------------
filterChangeHandler: function (event) {
this.filter = event.filter;
this.getData();
},
// ------------------Grouping------------------
groupChangeHandler: function (event) {
this.group = event.group;
this.getData();
},
},
};
</script>

Related

Vue Cant render data from axios inside datatable

i am trying to get data from axios and then render it in datable component
how you can see the hardcoded data is renderd every time , but i cant implement data from axios call,
i am loading same format data from axios and then try to update my hardocded rows but nothing happens, i am not shure thath i am doing it right way
can somone help
<template>
<div>
<mdb-datatable-2 v-model="data" />
</div>
</template>
<script>
import { mdbDatatable2 } from 'mdbvue';
export default {
name: 'Datatable',
components: {
mdbDatatable2
},
data() {
return {
data: {
columns: [
{
label: 'Account',
field: 'account',
sort: true
},
{
label: 'bt_mac',
field: 'bt_mac',
sort: true
},
{
label: 'rssi',
field: 'rssi',
sort: true
},
{
label: 'time_stamp',
field: 'time_stamp',
sort: true
}
],
rows: [{
account: 'Tiger Nixon',
bt_mac: 'System Architect',
rssi: 'Edinburgh',
time_stamp: '2011/04/25',
time: '2011/04/25'
},
{
account: 'Garrett Winters',
bt_mac: 'Accountant',
rssi: 'Tokyo',
time_stamp: '2018/04/25',
time: '2011/04/25'
},
{
account: 'Ashton Cox',
bt_mac: 'unior Technical Author',
rssi: 'San Francisco',
time_stamp: '2009/01/12',
time: '2011/04/25'
},]
}
}
},
methods: {
getSnomData () {
this.axios.get('http://172.27.11.174:1818/testJson').then((response) => {
console.log(this.data);
console.log(response.data.rows); this.rows = response.data;
console.log(this.data);
return response;
})
}
},
created() {
this.getSnomData();
}
,
mounted () {
this.getSnomData();
}
}
</script>
this.data.rows = response.data.data;
that helped in my case

BootstrapVue table pagination not rendering results properly

I'm using bootstrap-vue with version 2.9.0. When navigating to another page, the result does not display unless I click on to another page then the previous page will show up then immediately show the current page, which I find it really weird.
Here's a snippet:
<b-table
:items="dataTable.customerList"
:fields="dataTable.fields"
:per-page="dataTable.perPage"
:current-page="dataTable.currentPage"
:sort-by.sync="dataTable.sortBy"
:sort-desc.sync="dataTable.sortDesc"
:no-local-sorting="true"
#sort-changed="sortingChanged"
responsive
striped
hover
show-empty
empty-text="Loading..."
id="tbl-customer-list"
busy.sync="true"
>
</b-table>
<b-pagination
v-model="dataTable.currentPage"
:total-rows="dataTable.totalRows"
:per-page="dataTable.perPage"
#input="getCustomers(dataTable.currentPage)"></b-pagination>
And the JS:
export default {
page: {
title: 'Customers',
meta: [{ name: 'description', content: appConfig.description }],
},
components: {
Layout,
PageHeader,
},
methods: {
getCustomers(currentPage) {
this.busy = true;
customerList({page: currentPage}).then((response) => {
this.dataTable.customerList = response.data;
this.dataTable.totalRows = response.total;
this.dataTable.currentPage = response.current_page;
this.dataTable.perPage = response.per_page;
this.dataTable.lastPage = response.last_page;
this.busy = false;
})
},
sortingChanged(ctx) {
console.log(ctx);
this.$root.$emit('bv::table::refresh', 'tbl-customer-list')
},
},
mounted() {
this.getCustomers();
},
data() {
return {
title: 'Customers',
busy: false,
dataTable: {
customerList: [],
fields: [
{ key: 'customer_name', label: 'Full Name', sortable: true },
{ key: 'actions', sortable: false },
],
totalRows: 0,
currentPage: 1,
perPage: 0,
lastPage: 1,
sortBy: 'first_name',
sortDesc: false,
filter: null,
filterOn: [],
},
}
},
}
When I remove :current-page="dataTable.currentPage" on the b-table, it works fine but I when clicking on the row headers will always display the current page as 1. (See sortingChanged)

Trying to refresh label when click on it. Cannot read property '_meta' of undefined"

I'm trying that the percentages in a pie-chart refresh when clicking in a legend to hide data.
So far, I can display the chart with percentages, but they don't change if I hide one of the legends.
This is the chart: initial chart
This is how looks after the click: after click
We expect that instead 55.6%, it shows 100%.
This is my code so far:
<script>
import {Pie} from "vue-chartjs";
import ChartJsPluginDataLabels from 'chartjs-plugin-datalabels';
export default {
extends: Pie,
ChartJsPluginDataLabels,
props: {
data: Array,
bg: Array,
labels: Array
},
data() {
return {
}
},
computed: {
chartData() {
return this.data
},
bgData() {
return this.bg
},
total() {
return this.data.reduce((a, b) => a + (b || 0), 0)
}
},
methods: {
renderPieChart() {
this.renderChart({
labels: this.labels,
datasets: [
{
label: "Data One",
backgroundColor: this.bgData,
data: this.chartData,
hoverBackgroundColor: "#f78733"
}
]
}, {
responsive: true,
plugins: {
datalabels: {
formatter: (value) => {
let sum = this
.$refs.canvas.getContext('2d').dataset._meta[1].total; //use this.total to fix percentages
let percentage = (value * 100 / sum).toFixed(1) + "%";
return percentage;
},
color: '#fff'
}
}
})
console.log()
},
updateSelected(point, event) {
const item = event[0]
this.selected = {
index: item._index,
value: this
.chartData
.datasets[0]
.data[item._index]
}
}
},
watch: {
bg: function () {
this.renderPieChart();
},
data: function () {
this.renderPieChart();
}
},
}
</script>
In order to obtain the expected result, you should define plugins.datalabels.formatter as follows:
formatter: (value, context) => {
return (value * 100 / context.dataset._meta[0].total).toFixed(1) + "%";
}
new Chart(document.getElementById("myChart"), {
type: "pie",
data: {
labels: ['Savings', 'House'],
datasets: [{
label: "Data One",
backgroundColor: ['#4e9258', '#64e986'],
data: [9, 7],
hoverBackgroundColor: "#f78733"
}]
},
options: {
plugins: {
datalabels: {
formatter: (value, context) => {
return (value * 100 / context.dataset._meta[0].total).toFixed(1) + "%";
},
color: '#fff'
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels"></script>
<canvas id="myChart" height="100"></canvas>

vue good table - 3 requests to the service

I use vue-good-table object to render table in Vue.js. I use paging and sorting serverside.
My code:
<vue-good-table v-if="authorizations"
id="AuthorizationsTable"
ref="refToAuthorizationsTable"
#on-page-change="onPageChange"
#on-sort-change="onSortChange"
#on-column-filter="onColumnFilter"
#on-per-page-change="onPerPageChange"
mode="remote"
:columns="columns"
:rows="authorizations"
:totalRows="totalRecords"
:pagination-options="{
enabled: true,
mode: 'pages',
nextLabel: 'następna',
prevLabel: 'poprzednia',
ofLabel: 'z',
pageLabel: 'strona',
rowsPerPageLabel: 'wierszy na stronie',
allLabel: 'wszystko',
dropdownAllowAll: false
}"
:sort-options="{
enabled: true,
initialSortBy: {
field: 'id',
type: 'asc'
}
}">
(...)
export default {
name: 'AuthoritiesAdministration',
components: {},
data() {
return {
totalRecords: 0,
serverParams: {
columnFilters: {},
sort: {
field: 'id',
type: 'asc'
},
page: 1,
perPage: 10
},
rows: [],
columns: [
{
label: 'ID',
field: 'id',
type: 'number',
tdClass: 'vue-good-table-col-100'
},
{
label: 'Data wystawienia',
field: 'issueDate',
formatFn: this.formatDate,
tdClass: 'vue-good-table-col-200',
},
{
label: 'Nazwa operatora',
field: 'operator',
sortable: true,
formatFn: this.formatOperatorName,
},
{
label: 'Login',
field: 'operator.login'
},
{
label: 'Spółka',
field: 'company.description',
type: 'text',
},
{
label: 'Data ważności',
field: 'endDate',
type: 'text',
formatFn: this.formatDate,
},
{
label: 'Akcje',
field: 'btn',
tdClass: 'vue-good-table-col-250',
sortable: false
}
],
}
},
(...)
methods: {
updateParams(newProps) {
this.serverParams = Object.assign({}, this.serverParams, newProps);
},
onPageChange(params) {
this.updateParams({page: params.currentPage});
this.loadAuthorizations();
},
onPerPageChange(params) {
this.updateParams({
perPage: params.currentPerPage
});
this.loadAuthorizations();
},
onSortChange(params) {
this.updateParams({
sort: {
type: params[0].type,
field: params[0].field
}
});
this.loadAuthorizations();
},
onColumnFilter(params) {
this.updateParams(params);
this.loadAuthorizations();
},
loadAuthorizations() {
getAllAuthorizationsLightWithPagination(this.$store.getters.loggedUser.token, this.serverParams).then(response => {
this.totalRecords = response.data.totalRecords;
this.rows = response.data.authorizations;
}).catch(err => {
this.$showError(err, true);
});
},
I have noticed that there are sent 3 requests to the server instead of 1: there are called methods like onPageChange, onPerPageChange and onSortChange but only the first time when my page is loaded. It is unnecessary. I have one method in "mounted" section where I load the first 10 results (sorting and paging by default). It's common, well-known problem with vue-good-table? Or maybe should I add an additional flag to not to invoke these 3 methods unnecessarily when the page is loaded?
Your onSortChange method is called at the table loading because you made a initialSortBy with specific values. To remove this calling juste remove
initialSortBy: {
field: 'id',
type: 'asc'
}
from you table configuration, but your sort will not be set, so I think this should be a legit function call.
The onPerPageChange and onPageChange are triggered by the config below
:pagination-options="{
enabled: true,
...
}
just remove the colon before pagination-options to have a config like this
pagination-options="{
enabled: true,
...
}

Restricting a Rally chart snapshot store to a date period

I want to show some data from Rally using snapshot sotre passed to teh chart like this:
storeConfig: {
find: {
_ItemHierarchy: 15312401235, //PI Object ID
//Release: 9045474054,
_TypeHierarchy: 'HierarchicalRequirement', //Burn on stories
Children: null, //Only include leaf stories,
_ValidTo: { $gte: me._startDateField.value },
_ValidFrom: { $lte: me._endDateField.value }
},
fetch: ['ScheduleState', 'PlanEstimate'],
hydrate: ['ScheduleState'],
sort: {
'_ValidFrom': 1
}
}
The idea is that I want the chart to show only yhe period between Start Date and End Date specified in me._startDateField.value and me._endDateField.value. What is the way of achieving this? Because now the chart displays the data starting from January and not from Start Date.
This example restricts the end date to a selection in the second rallydatepicker instead of defaulting to today's date. See Readme here.
Ext.define('CustomApp', {
extend: 'Rally.app.App',
componentCls: 'app',
launch: function() {
var that = this;
var minDate = new Date(new Date() - 86400000*90); //milliseconds in day = 86400000
var datePicker = Ext.create('Ext.panel.Panel', {
title: 'Choose start and end dates:',
bodyPadding: 10,
renderTo: Ext.getBody(),
layout: 'hbox',
items: [{
xtype: 'rallydatepicker',
itemId: 'from',
minDate: minDate,
handler: function(picker, date) {
that.onStartDateSelected(date);
}
},
{
xtype: 'rallydatepicker',
itemId: 'to',
minDate: minDate,
handler: function(picker, date) {
that.onEndDateSelected(date);
}
}]
});
this.add(datePicker);
var panel = Ext.create('Ext.panel.Panel', {
id:'infoPanel',
componentCls: 'panel'
});
this.add(panel);
},
onStartDateSelected:function(date){
console.log(date);
this._startDate = date;
},
onEndDateSelected:function(date){
this._endDate = date;
console.log(date);
Ext.getCmp('infoPanel').update('showing data between ' + this._startDate + ' and ' + this._endDate);
this.defineCalculator();
this.makeChart();
},
defineCalculator: function(){
var that = this;
Ext.define("MyDefectCalculator", {
extend: "Rally.data.lookback.calculator.TimeSeriesCalculator",
getMetrics: function () {
var metrics = [
{
field: "State",
as: "Open",
display: "column",
f: "filteredCount",
filterField: "State",
filterValues: ["Submitted","Open"]
},
{
field: "State",
as: "Closed",
display: "column",
f: "filteredCount",
filterField: "State",
filterValues: ["Fixed","Closed"]
}
];
return metrics;
}
});
},
makeChart: function(){
if (this.down('#myChart')) {
this.remove('myChart');
}
var timePeriod = new Date(this._endDate - this._startDate);
var project = this.getContext().getProject().ObjectID;
var storeConfig = this.createStoreConfig(project, timePeriod);
this.chartConfig.calculatorConfig.startDate = Rally.util.DateTime.format(new Date(this._startDate), 'Y-m-d');
this.chartConfig.calculatorConfig.endDate = Rally.util.DateTime.format(new Date(this._endDate), 'Y-m-d');
this.chartConfig.storeConfig = storeConfig;
this.add(this.chartConfig);
},
createStoreConfig : function(project, interval ) {
return {
listeners : {
load : function(store,data) {
console.log("data",data.length);
}
},
filters: [
{
property: '_ProjectHierarchy',
operator : 'in',
value : [project]
},
{
property: '_TypeHierarchy',
operator: 'in',
value: ['Defect']
},
{
property: '_ValidFrom',
operator: '>=',
value: interval
}
],
autoLoad : true,
limit: Infinity,
fetch: ['State'],
hydrate: ['State']
};
},
chartConfig: {
xtype: 'rallychart',
itemId : 'myChart',
chartColors: ['Red', 'Green'],
storeConfig: { },
calculatorType: 'MyDefectCalculator',
calculatorConfig: {
},
chartConfig: {
plotOptions: {
column: { stacking: 'normal'}
},
chart: { },
title: { text: 'Open/Closed Defects'},
xAxis: {
tickInterval: 1,
labels: {
formatter: function() {
var d = new Date(this.value);
return ""+(d.getMonth()+1)+"/"+d.getDate();
}
},
title: {
text: 'Date'
}
},
yAxis: [
{
title: {
text: 'Count'
}
}
]
}
}
});