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

637 lines
23 KiB
Vue

<template>
<div>
<!-- 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="btnClick(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="btnClick(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>
<!-- <hr />
<div>Selected: {{ selected }}</div>
<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 {
data() {
return {
loading: true,
dataTablePagingOptions: {},
headers: [],
serverColumns: [],
totalRecords: 0,
records: [],
rowsPerPageItems: [5, 10, 25, 50, 100],
selected: [],
narrowFormat: this.$vuetify.breakpoint.xs
};
},
props: {
apiBaseUrl: {
type: String,
default: "DataList/List"
},
formKey: String,
dataListKey: String,
dataFilterId: {
type: Number,
default: 0
},
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 that 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"));
},
btnClick(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 that record so we have all we need to open that object
window.$gz.eventBus.$emit("openobject", { type: typeToOpen, id: i });
},
lt(ltKey) {
return window.$gz.locale.get(ltKey);
},
getDataFromApi() {
var that = this;
//start with defaults
var listOptions = {
DataListKey: that.dataListKey,
Limit: 5,
Offset: 0
};
//calculate paging based on settings
const { page, itemsPerPage } = that.dataTablePagingOptions;
if (itemsPerPage && itemsPerPage > 0) {
listOptions.Offset = (page - 1) * itemsPerPage;
listOptions.Limit = itemsPerPage;
}
//is there a filter?
if (that.dataFilterId != 0) {
listOptions["DataFilterID"] = that.dataFilterId;
}
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() {
//Save a copy of the server columns data for handling button clicks etc later
that.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 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 //this is the VUE filters collection, nothing to do with the local data structure
);
that.loading = false;
that.totalRecords = res.paging.count;
//persist the paging options so user sees same page and list on refresh or leave and return scenario
window.$gz.form.setFormSettings(that.formKey, {
temp: { page: that.dataTablePagingOptions.page },
saved: {
itemsPerPage: that.dataTablePagingOptions.itemsPerPage
}
});
//////////
})();
});
}
},
created() {
//rehydrate last form settings
var formSettings = window.$gz.form.getFormSettings(this.formKey);
if (formSettings.saved && formSettings.saved.itemsPerPage) {
this.dataTablePagingOptions.itemsPerPage =
formSettings.saved.itemsPerPage;
}
if (formSettings.temp && formSettings.temp.page) {
this.dataTablePagingOptions.page = formSettings.temp.page;
}
}
};
//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, 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 = [];
//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 6: //bool
// change to checkboxes??
// 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
//TODO: Need enum translator
//AuthorizationRoles.128
//window.$gz.local.enumLocalized(enumType, enumValue)
//(in locale) check if have that type already featched, if not fetches the whole list and caches it locally
//also a method to return the list of enumerated types in ID order with localized names
//window.$gz.local.enumList(enumType) - returns a list usable on edit forms
/* enums: {
AuthorizationRoles0:"No role",
AuthorizationRoles1:"Business admin limited"
etc
to get all authorization roles iterate list looking for start of key that is AuthorizationRoles
To get individual one same but only need to fetch actual value i.e. AuthorizationRoles1
if not one item starts with AuthorizationRoles then list needs to be fetched, maybe can use the enumpicklist route for that
This way entirely bypasses locale stuff
//big object so maybe it's own thing, not part of locale at all or locale fronts for it??
*/
// window.$gz.enums.getEnumList(columndefinitions[iColumn].et);
//display = columndefinitions[iColumn].et + "." + display;
display = window.$gz.enums.get(
columndefinitions[iColumn].et,
display
);
break;
default:
//do nothing, allow it to stay as is
}
//build the row column object that 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 that triggers a function with the column index and the id and that in turn will bubble up the event to open that
//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 that 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 => {
that.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);
}
}
}
//CURRENTLY THINKING NOT TO CACHE THIS AS
//users might only ever view the list and
//often it might not contain the whole range of options
//so would be unnecessary fetching
// `//////////////////////
// //
// //
// function preFetchEnumListNames(vm, enumListTypes) {
// //enumlisttypes is an array of enum lists to fetch names for
// return window.$gz.api
// .get("EnumPickList/list/authorizationroles")
// .then(res => {
// if (res.error) {
// throw res.error;
// }
// vm.pickLists.roles = res.data;
// });
// }`
</script>