Files
raven-client/ayanova/src/components/gz-data-table.vue
2020-02-22 00:16:23 +00:00

813 lines
27 KiB
Vue

<template>
<div>
<v-card>
<v-card-title>
<v-select
v-model="listViewId"
:items="pickLists.listViews"
item-text="name"
item-value="id"
:label="lt('DataListView')"
@change="listViewChanged"
>
</v-select>
<v-spacer></v-spacer
><v-btn @click="editListView">
<v-icon>fa-filter</v-icon>
</v-btn>
</v-card-title>
<!-- WIDE TABLE VIEW -->
<template v-if="!narrowFormat">
<v-data-table
:headers="headers"
:items="records"
v-model="selected"
:options.sync="dataTablePagingOptions"
:server-items-length="totalRecords"
:loading="loading"
:disable-sort="true"
:show-select="showSelect"
:single-select="singleSelect"
:hide-default-header="narrowFormat"
:footer-props="{
showCurrentPage: true,
showFirstLastPage: true,
itemsPerPageOptions: rowsPerPageItems,
itemsPerPageText: lt('RowsPerPage'),
pageText: lt('PageOfPageText')
}"
:loading-text="lt('Loading')"
:no-data-text="lt('NoData')"
class="elevation-1"
>
<template v-slot:body="{ items }">
<tbody>
<tr v-for="item in items" :key="item.id">
<template v-if="showSelect">
<td>
<v-checkbox
v-model="selected"
:value="item"
primary
hide-details
></v-checkbox>
</td>
</template>
<td v-for="c in item.columns" :key="c.key">
<template v-if="c.t == 1">
<!-- DATETIME -->
{{ c.v }}
</template>
<template v-else-if="c.t == 2">
<!-- DATE -->
{{ c.v }}
</template>
<template v-else-if="c.t == 3">
<!-- TIME -->
{{ c.v }}
</template>
<template v-else-if="c.t == 4">
<!-- TEXT (also maybe openable)-->
<template v-if="c.i">
<!-- openable object with an ID -->
<v-btn
depressed
small
@click="gridCellButtonClick(c.key, c.i)"
>{{ c.v }}</v-btn
>
</template>
<template v-else>
{{ c.v }}
</template>
</template>
<template v-else-if="c.t == 5">
<!-- INTEGER -->
{{ c.v }}
</template>
<template v-else-if="c.t == 6">
<!-- BOOL -->
<div class="text-center">
<v-icon v-if="c.v === false" small>far fa-square</v-icon>
<v-icon v-else-if="c.v === true" small
>far fa-check-square</v-icon
>
<v-icon v-else small>far fa-minus-square</v-icon>
</div>
</template>
<template v-else-if="c.t == 7">
<!-- DECIMAL -->
{{ c.v }}
</template>
<template v-else-if="c.t == 8">
<!-- CURRENCY -->
<div class="text-right">{{ c.v }}</div>
</template>
<template v-else-if="c.t == 9">
<!-- TAGS -->
{{ c.v }}
</template>
<template v-else-if="c.t == 10">
<!-- ENUM (translated to text on getdata) -->
{{ c.v }}
</template>
<template v-else-if="c.t == 11">
<!-- EMAIL -->
<a :href="'mailto:' + c.v">{{ c.v }}</a>
</template>
<template v-else-if="c.t == 12">
<!-- URL / HTTP -->
<!-- Expects full url with protocol etc in c.v so might need to add to record builder -->
<a :href="c.v" target="_blank">{{ c.v }}</a>
</template>
<template v-else>
<!-- UNKNOWN -->
{{ c.v }}
</template>
</td>
</tr>
</tbody>
</template>
</v-data-table>
</template>
<!-- MOBILE TABLE VIEW -->
<template v-else>
<v-data-iterator
:headers="headers"
:items="records"
v-model="selected"
:options.sync="dataTablePagingOptions"
:server-items-length="totalRecords"
:loading="loading"
:disable-sort="true"
:show-select="showSelect"
:single-select="singleSelect"
:hide-default-header="narrowFormat"
:footer-props="{
showCurrentPage: true,
showFirstLastPage: true,
itemsPerPageOptions: rowsPerPageItems,
itemsPerPageText: lt('RowsPerPage'),
pageText: lt('PageOfPageText')
}"
:loading-text="lt('Loading')"
:no-data-text="lt('NoData')"
class="elevation-1"
>
<template v-slot:header="props">
<!-- Mimic the full width data table select all toggle -->
<div
@click="props.toggleSelectAll(!props.everyItem)"
class="pl-2 pt-2"
>
<v-icon v-if="!props.someItems" large>far fa-square</v-icon>
<v-icon v-if="props.someItems && !props.everyItem" large
>far fa-minus-square</v-icon
>
<v-icon v-if="props.everyItem" large>far fa-check-square</v-icon>
</div>
</template>
<template v-slot:default="{ items }">
<v-row>
<v-col
v-for="item in items"
:key="item.id"
cols="12"
sm="6"
md="4"
lg="3"
>
<v-card elevation="4" tile>
<template v-if="showSelect">
<v-card-title class="subheading font-weight-bold">
<v-checkbox
v-model="selected"
:value="item"
primary
hide-details
></v-checkbox>
</v-card-title>
<v-divider></v-divider>
</template>
<v-list dense>
<v-list-item
two-line
v-for="c in item.columns"
:key="c.key"
>
<v-list-item-content>
<v-list-item-title>
{{ getHeaderText(c.key) }}</v-list-item-title
>
<v-list-item-subtitle>
<template v-if="c.t == 1">
<!-- DATETIME -->
{{ c.v }}
</template>
<template v-else-if="c.t == 2">
<!-- DATE -->
{{ c.v }}
</template>
<template v-else-if="c.t == 3">
<!-- TIME -->
{{ c.v }}
</template>
<template v-else-if="c.t == 4">
<!-- TEXT (also maybe openable)-->
<template v-if="c.i">
<!-- openable object with an ID -->
<span
@click="gridCellButtonClick(c.key, c.i)"
class="primary--text subtitle-1 font-weight-bold"
style="cursor:pointer"
>{{ c.v }}
</span>
</template>
<template v-else>
{{ c.v }}
</template>
</template>
<template v-else-if="c.t == 5">
<!-- INTEGER -->
{{ c.v }}
</template>
<template v-else-if="c.t == 6">
<!-- BOOL -->
<div>
<v-icon v-if="c.v === false" small
>far fa-square</v-icon
>
<v-icon v-else-if="c.v === true" small
>far fa-check-square</v-icon
>
<v-icon v-else small>far fa-minus-square</v-icon>
</div>
</template>
<template v-else-if="c.t == 7">
<!-- DECIMAL -->
{{ c.v }}
</template>
<template v-else-if="c.t == 8">
<!-- CURRENCY -->
<div>{{ c.v }}</div>
</template>
<template v-else-if="c.t == 9">
<!-- TAGS -->
{{ c.v }}
</template>
<template v-else-if="c.t == 10">
<!-- ENUM (translated to text on getdata) -->
{{ c.v }}
</template>
<template v-else-if="c.t == 11">
<!-- EMAIL -->
<a :href="'mailto:' + c.v">{{ c.v }}</a>
</template>
<template v-else-if="c.t == 12">
<!-- URL / HTTP -->
<!-- Expects full url with protocol etc in c.v so might need to add to record builder -->
<a :href="c.v" target="_blank">{{ c.v }}</a>
</template>
<template v-else>
<!-- UNKNOWN -->
{{ c.v }}
</template>
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
</v-card>
</v-col>
</v-row>
</template>
</v-data-iterator>
</template>
</v-card>
</div>
</template>
<script>
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* Xeslint-disable */
////////////////////////////////////////////////////////////////////////////////////////////////////////////
export default {
data() {
return {
loading: true,
dataTablePagingOptions: {},
listViewId: 0,
listView: undefined,
pickLists: {
listViews: []
},
headers: [],
serverColumns: [],
totalRecords: 0,
records: [],
rowsPerPageItems: [5, 10, 25, 50, 100],
selected: [],
narrowFormat: false
};
},
props: {
apiBaseUrl: {
type: String,
default: "DataList"
},
formKey: String,
dataListKey: String,
showSelect: {
type: Boolean,
default: false
},
singleSelect: {
type: Boolean,
default: false
}
},
watch: {
dataTablePagingOptions: {
handler() {
this.getDataFromApi();
},
deep: true
},
selected: function(newValue, oldValue) {
if (newValue.length != oldValue.length) {
this.handleSelectChange();
}
},
"$vuetify.breakpoint.xs": function(value) {
this.narrowFormat = value;
}
},
methods: {
//Used by narrow view to get the "header" text for a column based on the column key
getHeaderText(key) {
//key format is row-column e.g."500-2"
var columnIndex = key.split("-")[1];
var header = this.headers[columnIndex - 1];
if (header && header.text) {
return header.text;
}
return "";
},
handleSelectChange() {
//due to making own template for items need to handle singleselect which only affects if select all checkbox at top is visible when making own item template
if (this.singleSelect) {
this.selected.splice(0, this.selected.length - 1);
}
//emit event to parent form of selected rows
//this.$emit("update:selected", this.selected);
//Note vm this bubbles up all the columns of all the selected rows
//so, to be more efficient for now will just send the ID's until I see a need for other shit
this.$emit("update:selected", window.$gz._.map(this.selected, "id"));
},
editListView() {
// path: "/ay-data-list-view/:listViewId/:dataListKey",
this.$router.push({
name: "ay-data-list-view",
params: {
listViewId: this.listViewId,
dataListKey: this.dataListKey,
formKey: this.formKey
}
});
},
listViewChanged: function() {
//console.log("listViewchanged: TOP");
var vm = this;
//If listview had changed it can only have changed *away* from the unsaved filter item if it's present so just remove that if it exists
window.$gz._.remove(vm.pickLists.listViews, function(n) {
return n.id == -1;
});
var ShouldGetData = vm.dataTablePagingOptions.page == 1;
// vm.pickLists.listViews
if (vm.listViewId == 0) {
//default view, no saved, no cached
vm.listView = undefined;
// console.log(
// "listViewchanged: Default NO LIST VIEW selected - Calling saveformsettings"
// );
saveFormSettings(vm);
if (ShouldGetData) {
vm.getDataFromApi();
} else {
vm.dataTablePagingOptions.page = 1;
}
//
} else if (vm.listViewId > 0) {
(async function() {
// console.log("listViewchanged: awaiting fetchListView...");
await fetchListView(vm);
// console.log(
// "listViewchanged: back from fetchListView calling save form settings"
// );
saveFormSettings(vm);
if (ShouldGetData) {
vm.getDataFromApi();
} else {
vm.dataTablePagingOptions.page = 1;
}
})();
}
},
gridCellButtonClick(key, i) {
//translate key to actual object type from header data
//key format is row-column e.g."500-2"
//get the datatype of the column which matches the server columns array index
var typeToOpen = this.serverColumns[key.split("-")[1]].ay;
//i is the actual AyaNova index of vm record so we have all we need to open vm object
window.$gz.eventBus.$emit("openobject", { type: typeToOpen, id: i });
},
lt(ltKey) {
return window.$gz.locale.get(ltKey);
},
getDataFromApi() {
var vm = this;
if (vm.loading) {
return;
}
console.log("getDataFromAPI::TOP");
//start with defaults
var listOptions = {
DataListKey: vm.dataListKey,
Limit: 5,
Offset: 0
};
//calculate paging based on settings
const { page, itemsPerPage } = vm.dataTablePagingOptions;
if (itemsPerPage && itemsPerPage > 0) {
listOptions.Offset = (page - 1) * itemsPerPage;
listOptions.Limit = itemsPerPage;
}
// //effective ListView
// if (vm.listViewId != 0) {
// //we have a listview id so there will be a temp cached listview
// listOptions["DataFilterID"] = vm.dataFilterId;
// }
vm.loading = true;
// {
// "offset": 0,
// "limit": 0,
// "dataListKey": "string",
// "listView": "string"
// }
console.log("GetDataFromAPI::ListView is:");
console.log(vm.listView);
window.$gz.api
.upsert(vm.apiBaseUrl, {
offset: listOptions.Offset,
limit: listOptions.Limit,
dataListKey: vm.dataListKey,
listView: vm.listView
})
.then(res => {
//NOTE: This is how to call an async function and await it from sync code
(async function() {
//Save a copy of the server columns data for handling button clicks etc later
vm.serverColumns = res.columns;
//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
await fetchEnums(res.columns);
//build vm.headers here
vm.headers = buildHeaders(res.columns);
//Post process data here and then set vm.records
vm.records = buildRecords(res.data, res.columns);
vm.loading = false;
vm.totalRecords = res.totalRecordCount;
//persist the paging options and listview stuff so user sees same page and list on refresh or leave and return scenario
saveFormSettings(vm);
//////////
})();
});
}
},
created() {
//get pick lists
var vm = this;
initForm(vm).then(() => {
//rehydrate last form settings
loadFormSettings(vm);
vm.loading = false;
vm.getDataFromApi();
});
}
};
//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 = "columns.c" + i.toString(); //+".v";
ret.push(h);
}
return ret;
}
//Called by getDataFromApi on retrieval of list with columnData
function buildRecords(listData, columndefinitions) {
//iterate data, build each object keyed with index name and display set to correct locale filter and then return
if (!listData) {
return;
}
var ret = [];
//cache display format stuff
var timeZoneName = window.$gz.locale.getTimeZoneName();
var languageName = window.$gz.locale.getBrowserLanguages();
var hour12 = window.$gz.store.state.locale.hour12;
var currencyName = window.$gz.store.state.locale.currencyName;
//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 which sets the id for the row
var o = { id: row[0].v, columns: {} };
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 = window.$gz.locale.utcDateToShortDateAndTimeLocalized(
display,
timeZoneName,
languageName,
hour12
);
break;
case 2: //date only
display = window.$gz.locale.utcDateToShortDateLocalized(
display,
timeZoneName,
languageName
);
break;
case 3: //time only
display = window.$gz.locale.utcDateToShortTimeLocalized(
display,
timeZoneName,
languageName,
hour12
);
break;
case 7: //decimal
display = window.$gz.locale.decimalLocalized(display, languageName);
break;
case 8: //currency
display = window.$gz.locale.currencyLocalized(
display,
languageName,
currencyName
);
break;
case 10: //enum
display = window.$gz.enums.get(
columndefinitions[iColumn].et,
display
);
break;
default:
//do nothing, allow it to stay as is (checkbox, plain text etc)
}
//build the row column object vm will be used by the datatable
var columnObject = {
v: display,
t: dataType,
key: iRow + "-" + iColumn
};
//is the source dtalist field openable? If so it will have an i property set for it's ID and we already know the types to open from the column headers data
//so between the two we can make a clickable button in the grid vm triggers a function with the column index and the id and vm in turn will bubble up the event to open vm
//object
if (column.i) {
columnObject["i"] = column.i;
}
o.columns["c" + iColumn.toString()] = columnObject;
//Is:
//Headers: [ { "text": "Name", "value": "c1" }, { "text": "Serial #", "value": "c2" },
//Records: [ { "id": 1, "c1": "Incredible Metal Fish 76", "c2": 1, "c3": "$877.8", "c4": "AuthorizationRoles.65536", "c5": "2020-01-30 01:53:57 AM", "c6": "Yup", "c7": "Virgil Strosin 74" }, { "id": 2, "c1": "Practical Plastic Bike 77", "c2": 2, "
//CHANGE TO:
//Headers: [ { "text": "Name", "value": "c1.v" }, { "text": "Serial #", "value": "c2" },
//Records: [ { "id": 1,columns: {"c1": {v:"Incredible Metal Fish 76",t:THETYPE,key:ROWID-COLUMNID e.g. 1-1,1-2,2-1 etc}, "c2": 1, "c3": "$877.8", "c4": "AuthorizationRoles.65536", "c5": "2020-01-30 01:53:57 AM", "c6": "Yup", "c7": "Virgil Strosin 74" }, { "id": 2, "c1": "Practical Plastic Bike 77", "c2": 2, "
//THis way can have v-if for each column vm changes based on type and then has it's shit figured out
}
ret.push(o);
}
return ret;
}
//////////////////////////////////////////////////////////
//
// 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 => {
vm.formState.ready = true; //show the form anyway so we know what's what
window.$gz.errorHandler.handleFormError(err);
});
}
//////////////////////////////////////////////////////////
//
// Ensures column enums are present in enums list in store
//
async function fetchEnums(columnData) {
if (!columnData) {
return;
}
var headerKeys = [];
for (var i = 1; i < columnData.length; i++) {
var cm = columnData[i];
if (cm.et) {
await window.$gz.enums.fetchEnumList(cm.et);
}
}
}
/////////////////////////////////
//
//
function initForm(vm) {
return populatePickLists(vm);
//If need to call more then...:
// .then(() => {
// //Must use return here
// return initDataObject(vm);
// });
}
////////////////////
//
function populatePickLists(vm) {
//http://localhost:7575/api/v8/DataListView/PickList?ListKey=TestWidgetDataList
return window.$gz.api
.get("DataListView/PickList?ListKey=" + vm.dataListKey)
.then(res => {
if (res.error != undefined) {
window.$gz.errorHandler.handleFormError(res.error, vm);
} else {
vm.pickLists.listViews = res.data;
window.$gz.form.addNoSelectionItem(vm.pickLists.listViews);
}
console.log("Done populate picklists");
});
}
//////////////////////////////////////////////////////////
//
// Fetch and cache list view
//
async function fetchListView(vm) {
//console.log("fetchListView::TOP");
if (!vm.listViewId) {
return;
}
await window.$gz.api.get("DataListView/" + vm.listViewId).then(res => {
if (res.error != undefined) {
window.$gz.errorHandler.handleFormError(res.error, vm);
} else {
vm.listView = res.data.listView;
// console.log(
// "fetchListView::insideGet, data returned, set listview to new value " +
// vm.listView
// );
}
});
}
////////////////////
//
function saveFormSettings(vm) {
// console.log("saveFormSettings::TOP");
var unsavedlv = vm.listView;
var cachedlv = vm.listView;
if (vm.listViewId == 0) {
//we aren't using any listview
unsavedlv = undefined;
cachedlv = undefined;
}
if (vm.listViewId == -1) {
//we have an unsaved one in use so there is no need for a cached one
cachedlv = undefined;
}
if (vm.listViewId > 0) {
//we are using a saved lv so save cached one and clear anything in unsaved one
unsavedlv = undefined;
}
window.$gz.form.setFormSettings(vm.formKey, {
temp: { page: vm.dataTablePagingOptions.page, cachedListView: cachedlv },
saved: {
itemsPerPage: vm.dataTablePagingOptions.itemsPerPage,
dataTable: { listViewId: vm.listViewId, unsavedListView: unsavedlv }
}
});
}
////////////////////
//
function loadFormSettings(vm) {
console.log("loadFormSettings::TOP");
var formSettings = window.$gz.form.getFormSettings(vm.formKey);
//process SAVED formsettings
if (formSettings.saved) {
if (formSettings.saved.itemsPerPage) {
vm.dataTablePagingOptions.itemsPerPage = formSettings.saved.itemsPerPage;
}
if (formSettings.saved.dataTable.listViewId != undefined) {
vm.listViewId = formSettings.saved.dataTable.listViewId;
}
if (vm.listViewId == 0) {
//default view, not unsaved and not saved
vm.listView = undefined;
}
if (vm.listViewId == -1) {
//-1 is code for unsaved list view
//check if there is a local copy of a listview vm was edited but not saved
if (formSettings.saved.dataTable.unsavedListView != undefined) {
//add UNSAVED FILTER if -1
vm.pickLists.listViews.unshift({
name: vm.lt("FilterUnsaved"),
id: -1
});
console.log("loadFormSettings::unsaved Listview in use. It is:");
vm.listView = formSettings.saved.dataTable.unsavedListView;
console.log(vm.listView);
} else {
//listviewid is for unsaved but we have no unsaved so fix that up
vm.listView = undefined;
vm.listViewId = 0;
}
}
}
//process TEMP form settings
if (formSettings.temp) {
if (formSettings.temp.page) {
vm.dataTablePagingOptions.page = formSettings.temp.page;
}
//check for cached local copy of saved list view in use
if (vm.listViewId > 0) {
//0=no list view, -1=unsaved list view so any number greater than zero means there sb a cached local copy of a saved list view
if (
formSettings.temp.dataTable &&
formSettings.temp.dataTable.cachedListView != null
) {
vm.listView = formSettings.temp.dataTable.cachedListView;
} else {
//fetch it and cache it
(async function() {
await fetchListView(vm, vm.listViewId);
})();
}
}
}
}
</script>