614 lines
18 KiB
Vue
614 lines
18 KiB
Vue
<template>
|
|
<div>
|
|
<v-data-table
|
|
:caption="caption"
|
|
:headers="headers"
|
|
:items="records"
|
|
:options.sync="options"
|
|
:server-items-length="totalRecords"
|
|
:loading="loading"
|
|
:disable-sort="true"
|
|
:footer-props="{
|
|
showCurrentPage: true,
|
|
showFirstLastPage: true,
|
|
itemsPerPageOptions: rowsPerPageItems,
|
|
itemsPerPageText: lt('RowsPerPage'),
|
|
pageText: lt('PageOfPageText')
|
|
}"
|
|
:loading-text="lt('Loading')"
|
|
class="elevation-1"
|
|
></v-data-table>
|
|
<!-- <hr />
|
|
<div>Headers: {{ headers }}</div>
|
|
<div>Records: {{ records }}</div>
|
|
<div>TotalRecords: {{ totalRecords }}</div>
|
|
<div>caption: {{ caption }}</div>
|
|
<div>apiBaseUrl: {{ apiBaseUrl }}</div>
|
|
<div>formKey: {{ formKey }}</div>
|
|
<div>dataListKey: {{ dataListKey }}</div>
|
|
<div>dataFilterId: {{ dataFilterId }}</div>
|
|
<div>viewPort is XS: {{ mini }}</div> -->
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/* Xeslint-disable */
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
export default {
|
|
created() {
|
|
//console.log("gz-data-table viewport is XS = " + this.mini());
|
|
},
|
|
data() {
|
|
return {
|
|
loading: true,
|
|
options: {},
|
|
headers: [],
|
|
totalRecords: 0,
|
|
records: [],
|
|
rowsPerPageItems: [5, 10, 25, 50, 100]
|
|
};
|
|
},
|
|
props: {
|
|
apiBaseUrl: {
|
|
type: String,
|
|
default: "DataList/List"
|
|
},
|
|
formKey: String,
|
|
dataListKey: String,
|
|
caption: String,
|
|
dataFilterId: {
|
|
type: Number,
|
|
default: 0
|
|
}
|
|
},
|
|
watch: {
|
|
options: {
|
|
handler() {
|
|
this.getDataFromApi();
|
|
},
|
|
deep: true
|
|
},
|
|
"$vuetify.breakpoint.xs": function(value) {
|
|
//for now, not going with my own mini mode but may revisit so keeping this for now
|
|
// this.getDataFromApi();
|
|
}
|
|
},
|
|
methods: {
|
|
lt(ltKey) {
|
|
return window.$gz.locale.get(ltKey);
|
|
},
|
|
ltFormat() {
|
|
return window.$gz.locale.format();
|
|
},
|
|
// mini() {
|
|
// //https://vuetifyjs.com/en/customization/breakpoints#breakpoint-service-object
|
|
// return this.$vuetify.breakpoint.xs;
|
|
// },
|
|
getDataFromApi() {
|
|
//debugger;
|
|
var that = this;
|
|
|
|
// //set the list settings in the store since we were successful at retrieval
|
|
// if (
|
|
// that.localFormSettings &&
|
|
// that.localFormSettings.pagination &&
|
|
// that.localFormSettings.pagination.itemsPerPage
|
|
// ) {
|
|
// window.$gz.form.setFormSettings(that.formKey, {
|
|
// temp: { page: that.localFormSettings.pagination.page },
|
|
// saved: {
|
|
// itemsPerPage: that.localFormSettings.pagination.itemsPerPage,
|
|
// sortBy: that.localFormSettings.pagination.sortBy,
|
|
// descending: that.localFormSettings.pagination.descending
|
|
// }
|
|
// });
|
|
// }
|
|
|
|
//DataList/list?DataListKey=TestWidgetDataList&Offset=0&Limit=999&DataFilterId=1&mini=true
|
|
|
|
//start with defaults
|
|
var listOptions = {
|
|
DataListKey: that.dataListKey,
|
|
Limit: 100,
|
|
Offset: 0
|
|
};
|
|
//calculate paging based on settings
|
|
const { page, itemsPerPage } = that.options;
|
|
if (itemsPerPage && itemsPerPage > 0) {
|
|
listOptions.Offset = (page - 1) * itemsPerPage;
|
|
listOptions.Limit = itemsPerPage;
|
|
}
|
|
|
|
//is there a filter?
|
|
if (that.dataFilterId != 0) {
|
|
listOptions["DataFilterID"] = that.dataFilterId;
|
|
}
|
|
//Mini?
|
|
//OK, seems as though the datatable has a built in ability to handle small viewports by displaying each record vertically instead so maybe I only need
|
|
//mini for picklists
|
|
// if (that.$vuetify.breakpoint.xs) {
|
|
// listOptions["Mini"] = true;
|
|
// }
|
|
|
|
that.loading = true;
|
|
var listUrl =
|
|
that.apiBaseUrl + "?" + window.$gz.api.buildQuery(listOptions);
|
|
window.$gz.api.get(listUrl).then(res => {
|
|
//NOTE: This is how to call an async function and await it from sync code
|
|
(async function() {
|
|
//Make sure the locale keys are fetched
|
|
await fetchLocalizedHeaderNames(res.columns); //Note can use await here because it's wrapped inside an async function call, it will wait then resume next stuff below
|
|
//build that.headers here
|
|
that.headers = buildHeaders(res.columns);
|
|
//Post process data here and then set that.records
|
|
that.records = buildRecords(
|
|
res.data,
|
|
res.columns,
|
|
that.$options.filters
|
|
);
|
|
that.loading = false;
|
|
that.totalRecords = res.paging.count;
|
|
})();
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
//Called by getDataFromApi on retrieval of list with columnData
|
|
function buildHeaders(columnData) {
|
|
//debugger;
|
|
//iterate columns, build headers and return
|
|
if (!columnData) {
|
|
return [];
|
|
}
|
|
var ret = [];
|
|
//iterate the columns, skip over the first one as it's the df column and not for display
|
|
for (var i = 1; i < columnData.length; i++) {
|
|
var cm = columnData[i];
|
|
var h = {};
|
|
h.text = window.$gz.locale.get(cm.cm);
|
|
h.value = "c" + i.toString();
|
|
ret.push(h);
|
|
}
|
|
/*
|
|
{https://vuetifyjs.com/en/components/data-tables#api see headers property for this info:
|
|
text: string
|
|
value: string
|
|
align?: 'start' | 'center' | 'end'
|
|
sortable?: boolean
|
|
filterable?: boolean
|
|
divider?: boolean
|
|
class?: string | string[]
|
|
width?: string | number
|
|
filter?: (value: any, search: string, item: any) => boolean
|
|
sort?: (a: any, b: any) => number
|
|
}
|
|
|
|
*/
|
|
return ret;
|
|
}
|
|
|
|
//Called by getDataFromApi on retrieval of list with columnData
|
|
function buildRecords(listData, columndefinitions, filters) {
|
|
//iterate data, build each object keyed with index name and display set to correct locale filter and then return
|
|
if (!listData) {
|
|
return;
|
|
}
|
|
var ret = [];
|
|
|
|
//comes as an array of arrays, needs to leave as an array of objects representing each row
|
|
for (var iRow = 0; iRow < listData.length; iRow++) {
|
|
var row = listData[iRow];
|
|
//iterate row and build object representing row data keyed to index
|
|
//first column is the default column so skip over it for now
|
|
var o = {};
|
|
for (var iColumn = 1; iColumn < row.length; iColumn++) {
|
|
var column = row[iColumn];
|
|
var dataType = columndefinitions[iColumn].dt;
|
|
var display = column.v;
|
|
switch (dataType) {
|
|
case 1: //datetime format to shortdatetime
|
|
display = filters.shortdatelocalized(display);
|
|
break;
|
|
case 2: //date only
|
|
display = filters.shortdateonlylocalized(display);
|
|
break;
|
|
case 3: //time only
|
|
display = filters.shorttimeonlylocalized(display);
|
|
break;
|
|
case 6: //bool
|
|
display = filters.boolastext(display);
|
|
break;
|
|
case 7: //decimal
|
|
display = filters.decimal(display);
|
|
break;
|
|
case 8: //currency
|
|
display = filters.currency(display);
|
|
break;
|
|
case 10: //enum
|
|
display = filters.enum(display, columndefinitions[iColumn].et);
|
|
break;
|
|
default:
|
|
//do nothing, allow it to stay as is
|
|
}
|
|
o["c" + iColumn.toString()] = display;
|
|
}
|
|
ret.push(o);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/*UiDataTypes
|
|
NoType = 0,
|
|
DateTime = 1,
|
|
Date = 2,
|
|
Time = 3,
|
|
Text = 4,
|
|
Integer = 5,
|
|
Bool = 6,
|
|
Decimal = 7,
|
|
Currency = 8,
|
|
Tags = 9,
|
|
Enum = 10,
|
|
EmailAddress = 11
|
|
*/
|
|
//////////////////////////////////////////////////////////
|
|
//
|
|
// Ensures column names are present in locale table
|
|
//
|
|
async function fetchLocalizedHeaderNames(columnData) {
|
|
if (!columnData) {
|
|
return;
|
|
}
|
|
var headerKeys = [];
|
|
for (var i = 1; i < columnData.length; i++) {
|
|
var cm = columnData[i];
|
|
headerKeys.push(cm.cm);
|
|
}
|
|
//Now fetch all the keys and await the response before returning
|
|
await window.$gz.locale
|
|
.fetch(headerKeys)
|
|
.then(() => {
|
|
return;
|
|
})
|
|
.catch(err => {
|
|
that.formState.ready = true; //show the form anyway so we know what's what
|
|
window.$gz.errorHandler.handleFormError(err);
|
|
});
|
|
}
|
|
|
|
//DataTable component
|
|
//https://vuetifyjs.com/en/components/data-tables#paginate-and-sort-server-side
|
|
/*
|
|
TODO:
|
|
This handles fetching and displaying the data, the parent form handles all other aspects:
|
|
- error message display
|
|
- opening items
|
|
- selecting filters and adding editing templates
|
|
|
|
What is required to be sent to server:
|
|
- example full request: DataList/list?DataListKey=TestWidgetDataList&Offset=0&Limit=999&DataFilterId=1
|
|
- example mini request: DataList/list?DataListKey=TestWidgetDataList&Offset=0&Limit=999&DataFilterId=1&mini=true
|
|
- Offset
|
|
- Limit
|
|
- DatafilterId
|
|
- mini=true if XS otherwise nothing
|
|
|
|
|
|
|
|
What it needs from it's parent form:
|
|
- form key for saving / getting persistent grid settings
|
|
- api list url (maybe, or if all from datalist then no)
|
|
- DataListKey value
|
|
- filter id
|
|
- refresh command
|
|
- rows per page
|
|
|
|
What it needs to provide TO it's parent form
|
|
- Custom event for this: https://vuejs.org/v2/guide/components-custom-events.html#sync-Modifier
|
|
- Item click info to trigger opening edit form
|
|
- Selected items list for mass ops
|
|
- error event and message in case of server error or other error so parent can handle it
|
|
- Grid is responsible ONLY for showing data, nothing else
|
|
|
|
|
|
What will be delivered by the server:
|
|
Example request: http://localhost:7575/api/v8/DataList/List?Offset=2&Limit=2&DataListKey=TestWidgetDataList
|
|
Full response object:
|
|
{
|
|
"data": [
|
|
[
|
|
{
|
|
"v": 3
|
|
},
|
|
{
|
|
"v": "Handcrafted Fresh Keyboard 27",
|
|
"i": 3
|
|
},
|
|
{
|
|
"v": 3
|
|
},
|
|
{
|
|
"v": 697.34
|
|
},
|
|
{
|
|
"v": 128
|
|
},
|
|
{
|
|
"v": "2020-01-28T12:10:46.212435Z"
|
|
},
|
|
{
|
|
"v": true
|
|
},
|
|
{
|
|
"v": "Doug Effertz 17 - OpsAdminFull",
|
|
"i": 18
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"v": 4
|
|
},
|
|
{
|
|
"v": "Unbranded Fresh Shoes 28",
|
|
"i": 4
|
|
},
|
|
{
|
|
"v": 4
|
|
},
|
|
{
|
|
"v": 219.09
|
|
},
|
|
{
|
|
"v": 16
|
|
},
|
|
{
|
|
"v": "2020-01-28T12:33:14.347243Z"
|
|
},
|
|
{
|
|
"v": true
|
|
},
|
|
{
|
|
"v": "Samuel Powlowski 14 - CustomerLimited",
|
|
"i": 15
|
|
}
|
|
]
|
|
],
|
|
"paging": {
|
|
"count": 100,
|
|
"offset": 2,
|
|
"limit": 2,
|
|
"first": "http://localhost:7575/api/v8/DataList/List?DataListKey=TestWidgetDataList&pageNo=1&pageSize=2",
|
|
"previous": "http://localhost:7575/api/v8/DataList/List?DataListKey=TestWidgetDataList&pageNo=1&pageSize=2",
|
|
"next": "http://localhost:7575/api/v8/DataList/List?DataListKey=TestWidgetDataList&pageNo=3&pageSize=2",
|
|
"last": "http://localhost:7575/api/v8/DataList/List?DataListKey=TestWidgetDataList&pageNo=50&pageSize=2"
|
|
},
|
|
"columns": [
|
|
{
|
|
"cm": "df",
|
|
"dt": 0,
|
|
"ay": 2
|
|
},
|
|
{
|
|
"cm": "WidgetName",
|
|
"dt": 4,
|
|
"ay": 2
|
|
},
|
|
{
|
|
"cm": "WidgetSerial",
|
|
"dt": 5
|
|
},
|
|
{
|
|
"cm": "WidgetDollarAmount",
|
|
"dt": 8
|
|
},
|
|
{
|
|
"cm": "WidgetRoles",
|
|
"dt": 10
|
|
},
|
|
{
|
|
"cm": "WidgetStartDate",
|
|
"dt": 1
|
|
},
|
|
{
|
|
"cm": "Active",
|
|
"dt": 6
|
|
},
|
|
{
|
|
"cm": "User",
|
|
"dt": 4,
|
|
"ay": 3
|
|
}
|
|
]
|
|
}
|
|
|
|
MINI Response object:
|
|
{
|
|
"data": [
|
|
[
|
|
{
|
|
"v": 3
|
|
},
|
|
{
|
|
"v": "Handmade Soft Towels 27",
|
|
"i": 3
|
|
},
|
|
{
|
|
"v": 3
|
|
}
|
|
],
|
|
[
|
|
{
|
|
"v": 4
|
|
},
|
|
{
|
|
"v": "Handcrafted Cotton Cheese 28",
|
|
"i": 4
|
|
},
|
|
{
|
|
"v": 4
|
|
}
|
|
]
|
|
],
|
|
"paging": {
|
|
"count": 100,
|
|
"offset": 2,
|
|
"limit": 2,
|
|
"first": "http://localhost:7575/api/v8/DataList/List?DataListKey=TestWidgetDataList&Mini=True&pageNo=1&pageSize=2",
|
|
"previous": "http://localhost:7575/api/v8/DataList/List?DataListKey=TestWidgetDataList&Mini=True&pageNo=1&pageSize=2",
|
|
"next": "http://localhost:7575/api/v8/DataList/List?DataListKey=TestWidgetDataList&Mini=True&pageNo=3&pageSize=2",
|
|
"last": "http://localhost:7575/api/v8/DataList/List?DataListKey=TestWidgetDataList&Mini=True&pageNo=50&pageSize=2"
|
|
},
|
|
"columns": [
|
|
{
|
|
"cm": "df",
|
|
"dt": 0,
|
|
"ay": 2
|
|
},
|
|
{
|
|
"cm": "Widget",
|
|
"dt": 4,
|
|
"ay": 2
|
|
}
|
|
]
|
|
}
|
|
|
|
What this grid needs to do:
|
|
- On created or refresh or change of filter
|
|
- set Loading property to true on data object
|
|
- send the get request to the server based on filter, viewport etc
|
|
- On receipt of successful response
|
|
- update the paging values
|
|
- update the persistent form settings with the current grid selections for paging etc
|
|
- generate the column headers
|
|
- They will have locale keys so this grid fetches the keys as necessary at this point as it could change dynamically almost
|
|
- Generate the Item slot template (which I've never done in code before but only in the template itself so needs research)
|
|
- set the property names in the incoming data to the correct matching headers
|
|
- set display filters based on locale settings (i.e. currency, datetime filter which converts to local time offset etc)
|
|
- fill the rows
|
|
- format?
|
|
- On fail
|
|
- Do something
|
|
- set loading property to false
|
|
|
|
|
|
|
|
TODO:
|
|
- Start coding grid here with vue component parameters and handlers first and in conjunction with test-widgets.vue
|
|
- Once it's parent / component properties are set then get into the details
|
|
|
|
|
|
|
|
|
|
Headers: [
|
|
{
|
|
"text": "Name",
|
|
"value": "c1" }, {
|
|
"text": "Serial #",
|
|
"value": "c2" }, { "text": "Price", "value": "c3" }, { "text": "Roles", "value": "c4" }, { "text": "Start", "value": "c5" }, { "text": "Active", "value": "c6" }, { "text": "User", "value": "c7" } ]
|
|
Records: [ { "c1": "Awesome Steel Table 25", "c2": 1, "c3": 676.64, "c4": 0, "c5": "2020-01-28T16:50:29.371098Z", "c6": true, "c7": "Cameron Marvin 1 - BizAdminLimited" }, { "c1": "Fantastic Concrete Soap 26", "c2": 2, "c3": 310.6, "c4": 32768, "c5": "2020-01-28T15:56:47.362545Z", "c6": true, "c7": "Becky Parker 9 - TechFull" }, { "c1": "Handmade Soft Towels 27", "c2": 3, "c3": 463.7, "c4": 8, "c5": "2020-01-28T16:55:51.487005Z", "c6": true, "c7": "Zachary Renner 14 - CustomerLimited" }, { "c1": "Handcrafted Cotton Cheese 28", "c2": 4, "c3": 934.95, "c4": 1, "c5": "2020-01-28T16:55:23.324561Z", "c6": true, "c7": "Zachary Renner 14 - CustomerLimited" }, { "c1": "Refined Concrete Salad 29", "c2": 5, "c3": 758.3, "c4": 32, "c5": "2020-01-28T16:00:46.703856Z", "c6": true, "c7": "Terri Paucek 2 - BizAdminFull" }, { "c1": "Licensed Concrete Chips 30", "c2": 6, "c3": 580.73, "c4": 2048, "c5": "2020-01-28T16:44:29.601375Z", "c6": true, "c7": "Virginia Friesen 17 - OpsAdminFull" }, { "c1": "Handmade Cotton Pants 31", "c2": 7, "c3": 880.37, "c4": 1024, "c5": "2020-01-28T16:43:44.380156Z", "c6": true, "c7": "Bernadette Denesik 12 - SubContractorLimited" }, { "c1": "Refined Cotton Sausages 32", "c2": 8, "c3": 615.32, "c4": 1, "c5": "2020-01-28T16:33:10.667606Z", "c6": true, "c7": "Alberta Gleichner 22 - fr" }, { "c1": "Tasty Soft Hat 33", "c2": 9, "c3": 273.68, "c4": 512, "c5": "2020-01-28T16:19:12.318658Z", "c6": true, "c7": "Sadie Stehr 15 - CustomerFull" }, { "c1": "Refined Frozen Bike 35", "c2": 11, "c3": 538.56, "c4": 32, "c5": "2020-01-28T16:46:25.906627Z", "c6": true, "c7": "Sadie Stehr 15 - CustomerFull" } ]
|
|
|
|
headers: [
|
|
{
|
|
text: 'Dessert (100g serving)',
|
|
align: 'left',
|
|
sortable: false,
|
|
value: 'name',
|
|
},
|
|
{ text: 'c2', value: 'c2' },
|
|
{ text: 'c3 (g)', value: 'c3' },
|
|
{ text: 'c4 (g)', value: 'c4' },
|
|
{ text: 'c5 (g)', value: 'c5' },
|
|
{ text: 'Iron (%)', value: 'c6' },
|
|
],
|
|
|
|
[
|
|
{
|
|
name: 'Frozen Yogurt',
|
|
c2: 159,
|
|
c3: 6.0,
|
|
c4: 24,
|
|
c5: 4.0,
|
|
c6: '1%',
|
|
},
|
|
{
|
|
name: 'Ice cream sandwich',
|
|
c2: 237,
|
|
c3: 9.0,
|
|
c4: 37,
|
|
c5: 4.3,
|
|
c6: '1%',
|
|
},
|
|
{
|
|
name: 'Eclair',
|
|
c2: 262,
|
|
c3: 16.0,
|
|
c4: 23,
|
|
c5: 6.0,
|
|
c6: '7%',
|
|
},
|
|
{
|
|
name: 'Cupcake',
|
|
c2: 305,
|
|
c3: 3.7,
|
|
c4: 67,
|
|
c5: 4.3,
|
|
c6: '8%',
|
|
},
|
|
{
|
|
name: 'Gingerbread',
|
|
c2: 356,
|
|
c3: 16.0,
|
|
c4: 49,
|
|
c5: 3.9,
|
|
c6: '16%',
|
|
},
|
|
{
|
|
name: 'Jelly bean',
|
|
c2: 375,
|
|
c3: 0.0,
|
|
c4: 94,
|
|
c5: 0.0,
|
|
c6: '0%',
|
|
},
|
|
{
|
|
name: 'Lollipop',
|
|
c2: 392,
|
|
c3: 0.2,
|
|
c4: 98,
|
|
c5: 0,
|
|
c6: '2%',
|
|
},
|
|
{
|
|
name: 'Honeycomb',
|
|
c2: 408,
|
|
c3: 3.2,
|
|
c4: 87,
|
|
c5: 6.5,
|
|
c6: '45%',
|
|
},
|
|
{
|
|
name: 'Donut',
|
|
c2: 452,
|
|
c3: 25.0,
|
|
c4: 51,
|
|
c5: 4.9,
|
|
c6: '22%',
|
|
},
|
|
{
|
|
name: 'KitKat',
|
|
c2: 518,
|
|
c3: 26.0,
|
|
c4: 65,
|
|
c5: 7,
|
|
c6: '6%',
|
|
},
|
|
]
|
|
},
|
|
},
|
|
}
|
|
|
|
*/
|
|
</script>
|