1336 lines
46 KiB
Vue
1336 lines
46 KiB
Vue
<template>
|
|
<div>
|
|
<gz-error :error-box-message="formState.errorBoxMessage"></gz-error>
|
|
|
|
<gz-data-table-filter
|
|
:data-list-key="dataListKey"
|
|
:active-filter-id="activeFilterId"
|
|
ref="dataTableFilter"
|
|
>
|
|
</gz-data-table-filter>
|
|
<gz-data-table-filter-manager
|
|
:data-list-key="dataListKey"
|
|
:active-filter-id="activeFilterId"
|
|
ref="dataTableFilterManager"
|
|
>
|
|
</gz-data-table-filter-manager>
|
|
<gz-data-table-mobile-filter-column-selector
|
|
:headers="headers"
|
|
ref="dataTableMobileFilterColumnSelector"
|
|
>
|
|
</gz-data-table-mobile-filter-column-selector>
|
|
<v-card>
|
|
<v-card-title>
|
|
<template v-if="preFilterMode">
|
|
<div class="my-2">
|
|
<v-btn text @click="preFilterNav()">
|
|
<v-icon data-cy="clickThru">{{ preFilterMode.icon }}</v-icon>
|
|
</v-btn>
|
|
<span @click="preFilterNav()" class="text-h5">
|
|
{{ preFilterMode.viz }}</span
|
|
>
|
|
<v-btn
|
|
v-if="preFilterMode.clearable"
|
|
text
|
|
@click="preFilterClear()"
|
|
>
|
|
<v-icon>$clear</v-icon>
|
|
</v-btn>
|
|
</div>
|
|
|
|
<!-- v-if="preFilterMode.ayatype && preFilterMode.id" {icon:null,viz:null,ayatype:null,id:null,clearable:false} -->
|
|
</template>
|
|
<template v-else>
|
|
<v-select
|
|
v-model="activeFilterId"
|
|
:items="selectLists.savedFilters"
|
|
item-text="name"
|
|
item-value="id"
|
|
:label="$ay.t('Filter')"
|
|
@input="selectedFilterChanged"
|
|
prepend-icon="$ayiEdit"
|
|
@click:prepend="editFilter()"
|
|
:append-outer-icon="clearFilterIcon()"
|
|
@click:append-outer="clearFilter()"
|
|
data-cy="selectSavedFilter"
|
|
>
|
|
</v-select>
|
|
</template>
|
|
|
|
<v-spacer></v-spacer>
|
|
<div class="my-5 my-sm-1">
|
|
<v-btn text @click="refresh">
|
|
<v-icon data-cy="refresh">$ayiSync</v-icon>
|
|
</v-btn>
|
|
|
|
<v-btn
|
|
text
|
|
v-if="$vuetify.breakpoint.xs"
|
|
class="ml-12"
|
|
@click="mobileColumnFilterSelect"
|
|
>
|
|
<v-icon data-cy="refresh">$ayiFilter</v-icon>
|
|
</v-btn>
|
|
<v-btn text class="ml-12" @click="editColumnView">
|
|
<v-icon
|
|
:large="hiddenAffectiveColumns.length > 0"
|
|
:color="hiddenAffectiveColumns.length ? 'accent' : null"
|
|
data-cy="filter"
|
|
>$ayiColumns</v-icon
|
|
>
|
|
</v-btn>
|
|
</div>
|
|
</v-card-title>
|
|
<div
|
|
class="text-h5 ml-3 accent--text"
|
|
v-if="!loading && records.length < 1"
|
|
>
|
|
{{ $ay.t("NoData") }}
|
|
</div>
|
|
|
|
<!--
|
|
################################################################################################################################################
|
|
WIDE TABLE VIEW
|
|
################################################################################################################################################
|
|
-->
|
|
<template v-if="$vuetify.breakpoint.smAndUp">
|
|
<v-data-table
|
|
:headers="headers"
|
|
:items="records"
|
|
v-model="selected"
|
|
:options.sync="dataTablePagingOptions"
|
|
:server-items-length="totalRecords"
|
|
:loading="loading"
|
|
multi-sort
|
|
:show-select="showSelect"
|
|
:single-select="singleSelect"
|
|
:footer-props="{
|
|
showCurrentPage: true,
|
|
showFirstLastPage: true,
|
|
itemsPerPageOptions: rowsPerPageItems,
|
|
itemsPerPageText: $ay.t('RowsPerPage'),
|
|
pageText: $ay.t('PageOfPageText')
|
|
}"
|
|
:loading-text="$ay.t('Loading')"
|
|
:no-data-text="$ay.t('NoData')"
|
|
class="elevation-1"
|
|
data-cy="datatable"
|
|
>
|
|
<!-- HEADER FILTERS: https://stackoverflow.com/a/58718975/8939 -->
|
|
<template v-for="h in headers" v-slot:[`header.${h.value}`]>
|
|
<span :key="h.text">
|
|
<v-btn
|
|
v-if="h.flt"
|
|
icon
|
|
@click.stop="filter(h)"
|
|
class="ml-n8 mr-1"
|
|
><v-icon :color="filterColor(h)">$ayiFilter</v-icon></v-btn
|
|
>
|
|
{{ h.text }}
|
|
</span>
|
|
</template>
|
|
|
|
<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"
|
|
hide-details
|
|
></v-checkbox>
|
|
</td>
|
|
</template>
|
|
<td
|
|
v-for="c in item.columns"
|
|
:key="c.key"
|
|
:style="cellStyle(c)"
|
|
>
|
|
<template v-if="c.t == 1">
|
|
<!-- DATETIME -->
|
|
<template v-if="c.i && c.i != 0 && !c.nopen">
|
|
<!-- openable object with an ID -->
|
|
<div
|
|
class="subtitle-1"
|
|
@click="gridCellButtonClick(c.key, c.i, c.ot)"
|
|
>
|
|
<a href="javascript:"> {{ c.v }}</a>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
{{ c.v }}
|
|
</template>
|
|
</template>
|
|
<template v-else-if="c.t == 2">
|
|
<!-- DATE -->
|
|
<template v-if="c.i && c.i != 0 && !c.nopen">
|
|
<!-- openable object with an ID -->
|
|
<div
|
|
class="subtitle-1"
|
|
@click="gridCellButtonClick(c.key, c.i, c.ot)"
|
|
>
|
|
<a href="javascript:"> {{ c.v }}</a>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
{{ c.v }}
|
|
</template>
|
|
</template>
|
|
<template v-else-if="c.t == 3">
|
|
<!-- TIME -->
|
|
<template v-if="c.i && c.i != 0 && !c.nopen">
|
|
<!-- openable object with an ID -->
|
|
<div
|
|
class="subtitle-1"
|
|
@click="gridCellButtonClick(c.key, c.i, c.ot)"
|
|
>
|
|
<a href="javascript:"> {{ c.v }}</a>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
{{ c.v }}
|
|
</template>
|
|
</template>
|
|
<template v-else-if="c.t == 4">
|
|
<!-- TEXT (also maybe openable)-->
|
|
<template v-if="c.i && c.i != 0 && !c.nopen">
|
|
<!-- openable object with an ID -->
|
|
<div
|
|
class="subtitle-1"
|
|
@click="gridCellButtonClick(c.key, c.i, c.ot)"
|
|
>
|
|
<a href="javascript:"> {{ c.v }}</a>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
{{ c.v }}
|
|
</template>
|
|
</template>
|
|
<template v-else-if="c.t == 5">
|
|
<!-- INTEGER -->
|
|
<template v-if="c.i && c.i != 0 && !c.nopen">
|
|
<!-- openable object with an ID -->
|
|
<div
|
|
class="subtitle-1"
|
|
@click="gridCellButtonClick(c.key, c.i, c.ot)"
|
|
>
|
|
<a href="javascript:"> {{ c.v }}</a>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
{{ c.v }}
|
|
</template>
|
|
</template>
|
|
<template v-else-if="c.t == 6">
|
|
<!-- BOOL -->
|
|
|
|
<v-icon v-if="c.v === false" small>$ayiSquare</v-icon>
|
|
<v-icon v-else-if="c.v === true" small
|
|
>$ayiCheckSquare</v-icon
|
|
>
|
|
<v-icon v-else small>$ayiMinus-square</v-icon>
|
|
</template>
|
|
<template v-else-if="c.t == 7">
|
|
<!-- DECIMAL -->
|
|
<template v-if="c.i && c.i != 0 && !c.nopen">
|
|
<!-- openable object with an ID -->
|
|
<div
|
|
class="subtitle-1"
|
|
@click="gridCellButtonClick(c.key, c.i, c.ot)"
|
|
>
|
|
<a href="javascript:"> {{ c.v }}</a>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
{{ c.v }}
|
|
</template>
|
|
</template>
|
|
<template v-else-if="c.t == 8">
|
|
<!-- CURRENCY -->
|
|
{{ c.v }}
|
|
</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) ALSO MAYBE OPENABLE -->
|
|
<template v-if="c.i && c.ot">
|
|
<!-- openable object with an ID -->
|
|
<div
|
|
class="subtitle-1"
|
|
@click="gridCellButtonClick(c.key, c.i, c.ot)"
|
|
>
|
|
<a href="javascript:"> {{ c.v }}</a>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
{{ c.v }}
|
|
</template>
|
|
</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-if="c.t == 14">
|
|
<!-- File / memory Size -->
|
|
{{ c.v }}
|
|
</template>
|
|
<template v-else-if="c.t == 16">
|
|
<!-- PHONE NUMBER -->
|
|
<a :href="'tel:' + c.v">{{ 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-table
|
|
:headers="headers"
|
|
:items="records"
|
|
v-model="selected"
|
|
:options.sync="dataTablePagingOptions"
|
|
:server-items-length="totalRecords"
|
|
:loading="loading"
|
|
multi-sort
|
|
:show-select="showSelect"
|
|
:single-select="singleSelect"
|
|
:footer-props="{
|
|
showCurrentPage: true,
|
|
showFirstLastPage: true,
|
|
itemsPerPageOptions: rowsPerPageItems,
|
|
itemsPerPageText: $ay.t('RowsPerPage'),
|
|
pageText: $ay.t('PageOfPageText')
|
|
}"
|
|
:header-props="{ sortByText: $ay.t('Sort') }"
|
|
:loading-text="$ay.t('Loading')"
|
|
class="elevation-1"
|
|
data-cy="datatable"
|
|
:no-data-text="$ay.t('NoData')"
|
|
>
|
|
<template v-slot:body="{ 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"
|
|
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 :style="cellStyle(c)">
|
|
<template v-if="c.t == 1">
|
|
<!-- DATETIME -->
|
|
<template v-if="c.i && c.i != 0 && !c.nopen">
|
|
<!-- openable object with an ID -->
|
|
<div
|
|
class="subtitle-1"
|
|
@click="gridCellButtonClick(c.key, c.i, c.ot)"
|
|
>
|
|
<a href="javascript:"> {{ c.v }}</a>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
{{ c.v }}
|
|
</template>
|
|
</template>
|
|
<template v-else-if="c.t == 2">
|
|
<!-- DATE -->
|
|
<template v-if="c.i && c.i != 0 && !c.nopen">
|
|
<!-- openable object with an ID -->
|
|
<div
|
|
class="subtitle-1"
|
|
@click="gridCellButtonClick(c.key, c.i, c.ot)"
|
|
>
|
|
<a href="javascript:"> {{ c.v }}</a>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
{{ c.v }}
|
|
</template>
|
|
</template>
|
|
<template v-else-if="c.t == 3">
|
|
<!-- TIME -->
|
|
<template v-if="c.i && c.i != 0 && !c.nopen">
|
|
<!-- openable object with an ID -->
|
|
<div
|
|
class="subtitle-1"
|
|
@click="gridCellButtonClick(c.key, c.i, c.ot)"
|
|
>
|
|
<a href="javascript:"> {{ c.v }}</a>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
{{ c.v }}
|
|
</template>
|
|
</template>
|
|
<template v-else-if="c.t == 4">
|
|
<!-- TEXT (also maybe openable)-->
|
|
<template v-if="c.i && c.i != 0 && !c.nopen">
|
|
<!-- openable object with an ID -->
|
|
<div
|
|
class="subtitle-1"
|
|
@click="gridCellButtonClick(c.key, c.i, c.ot)"
|
|
>
|
|
<a href="javascript:"> {{ c.v }}</a>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
{{ c.v }}
|
|
</template>
|
|
</template>
|
|
<template v-else-if="c.t == 5">
|
|
<!-- INTEGER -->
|
|
<template v-if="c.i && c.i != 0 && !c.nopen">
|
|
<!-- openable object with an ID -->
|
|
<div
|
|
class="subtitle-1"
|
|
@click="gridCellButtonClick(c.key, c.i, c.ot)"
|
|
>
|
|
<a href="javascript:"> {{ c.v }}</a>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
{{ c.v }}
|
|
</template>
|
|
</template>
|
|
<template v-else-if="c.t == 6">
|
|
<!-- BOOL -->
|
|
<div>
|
|
<v-icon v-if="c.v === false" small
|
|
>$ayiSquare</v-icon
|
|
>
|
|
<v-icon v-else-if="c.v === true" small
|
|
>$ayiCheckSquare</v-icon
|
|
>
|
|
<v-icon v-else small>$ayiMinus-square</v-icon>
|
|
</div>
|
|
</template>
|
|
<template v-else-if="c.t == 7">
|
|
<!-- DECIMAL -->
|
|
<template v-if="c.i && c.i != 0 && !c.nopen">
|
|
<!-- openable object with an ID -->
|
|
<div
|
|
class="subtitle-1"
|
|
@click="gridCellButtonClick(c.key, c.i, c.ot)"
|
|
>
|
|
<a href="javascript:"> {{ c.v }}</a>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
{{ c.v }}
|
|
</template>
|
|
</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) ALSO MAYBE OPENABLE -->
|
|
<template v-if="c.i && c.ot">
|
|
<!-- openable object with an ID -->
|
|
<div
|
|
class="subtitle-1"
|
|
@click="gridCellButtonClick(c.key, c.i, c.ot)"
|
|
>
|
|
<a href="javascript:"> {{ c.v }}</a>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
{{ c.v }}
|
|
</template>
|
|
</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-if="c.t == 14">
|
|
<!-- File / memory Size -->
|
|
{{ c.v }}
|
|
</template>
|
|
<template v-else-if="c.t == 16">
|
|
<!-- PHONE NUMBER -->
|
|
<a :href="'tel:' + c.v">{{ 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-table>
|
|
</template>
|
|
</v-card>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/* Xeslint-disable */
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
const MAX_TEXT_COLUMN_LENGTH = 1024;
|
|
export default {
|
|
data() {
|
|
return {
|
|
loading: true,
|
|
dataTablePagingOptions: {},
|
|
lastDataTablePagingOptions: {},
|
|
activeFilterId: 0, //<--0 signifies to select default as it's uninitialized
|
|
lastFetchFilterId: 0, //used to track change of filter and reset paging in getdata
|
|
selectLists: {
|
|
savedFilters: []
|
|
},
|
|
headers: [],
|
|
serverColumns: [],
|
|
serverFilter: [],
|
|
hiddenAffectiveColumns: [],
|
|
totalRecords: 0,
|
|
records: [],
|
|
rowsPerPageItems: [5, 10, 25, 50, 100],
|
|
selected: [],
|
|
timeZoneName: null,
|
|
formState: {
|
|
ready: false,
|
|
dirty: false,
|
|
valid: true,
|
|
readOnly: false,
|
|
loading: true,
|
|
errorBoxMessage: undefined,
|
|
appError: undefined,
|
|
serverError: {}
|
|
}
|
|
};
|
|
},
|
|
props: {
|
|
formKey: { type: String, default: null },
|
|
dataListKey: { type: String, default: null },
|
|
clientCriteria: {
|
|
type: String,
|
|
default: undefined
|
|
},
|
|
preFilterMode: {
|
|
//{icon:null,viz:null,ayatype:null,id:null,clearable:false}
|
|
type: Object,
|
|
default: null
|
|
},
|
|
showSelect: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
singleSelect: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
reload: {
|
|
type: Boolean,
|
|
default: false
|
|
},
|
|
ridColumnOpenable: {
|
|
type: Boolean,
|
|
default: true
|
|
}
|
|
},
|
|
watch: {
|
|
dataTablePagingOptions: {
|
|
async handler() {
|
|
//{ "page": 1, "itemsPerPage": 10, "sortBy": [], "sortDesc": [], "groupBy": [], "groupDesc": [], "mustSort": false, "multiSort": false }
|
|
//this code works around some weird bug that causes visible items to be selected in grid (only, not in actual selected array, just a visual thing)
|
|
// when breakpoint is switched between wide and narrow either way. No idea why it happens but this fixes that issue and also ensures that there are no
|
|
|
|
//spurious fetches happening just because the view has changed
|
|
//See what has changed and record it for processing
|
|
let sortHasChanged = !(
|
|
window.$gz.util.isEqualArraysOfPrimitives(
|
|
this.dataTablePagingOptions.sortBy,
|
|
this.lastDataTablePagingOptions.sortBy
|
|
) &&
|
|
window.$gz.util.isEqualArraysOfPrimitives(
|
|
this.dataTablePagingOptions.sortDesc,
|
|
this.lastDataTablePagingOptions.sortDesc
|
|
)
|
|
);
|
|
|
|
let pagingHaschanged = !(
|
|
this.lastDataTablePagingOptions.page ==
|
|
this.dataTablePagingOptions.page &&
|
|
this.lastDataTablePagingOptions.itemsPerPage ==
|
|
this.dataTablePagingOptions.itemsPerPage
|
|
);
|
|
|
|
if (!pagingHaschanged && !sortHasChanged) {
|
|
return;
|
|
}
|
|
|
|
//something changed, persist it to last
|
|
this.lastDataTablePagingOptions = this.dataTablePagingOptions;
|
|
|
|
if (sortHasChanged && !this.loading) {
|
|
//update sort at server and then allow get data
|
|
|
|
//no return data is required here
|
|
//because getdatafromapi will have the data to refresh the sort of the view anyway
|
|
//so it's fire and forget
|
|
await window.$gz.api.post("data-list-column-view/sort", {
|
|
listKey: this.dataListKey,
|
|
sortBy: this.keyArrayFromSortByArray(
|
|
this.dataTablePagingOptions.sortBy
|
|
),
|
|
sortDesc: this.dataTablePagingOptions.sortDesc
|
|
});
|
|
}
|
|
|
|
//has changed something important so refetch
|
|
await this.getDataFromApi();
|
|
},
|
|
deep: true
|
|
},
|
|
selected: function(newValue, oldValue) {
|
|
if (!newValue) {
|
|
return;
|
|
}
|
|
if (newValue.length != oldValue.length) {
|
|
this.handleSelectChange();
|
|
}
|
|
},
|
|
reload: function(newValue, oldValue) {
|
|
if (newValue != oldValue) {
|
|
this.getDataFromApi(true);
|
|
}
|
|
}
|
|
},
|
|
methods: {
|
|
preFilterNav: function() {
|
|
window.$gz.eventBus.$emit("openobject", {
|
|
type: this.preFilterMode.ayatype,
|
|
id: this.preFilterMode.id
|
|
});
|
|
},
|
|
preFilterClear: function() {
|
|
this.$emit("clear-pre-filter");
|
|
// this.preFilterMode = null;
|
|
// this.clientCriteria = null;
|
|
// getDataFromApi();
|
|
},
|
|
//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"
|
|
let columnIndex = key.split("-")[1];
|
|
let header = this.headers[columnIndex];
|
|
if (header && header.text) {
|
|
return header.text;
|
|
}
|
|
return "";
|
|
},
|
|
async filter(item) {
|
|
let res = await this.$refs.dataTableFilter.open(item);
|
|
if (res && res.refresh == true) {
|
|
this.getDataFromApi();
|
|
}
|
|
},
|
|
filterColor(item) {
|
|
let clr = "disabled";
|
|
if (this.serverFilter.findIndex(z => z.column == item.fk) > -1) {
|
|
clr = "primary";
|
|
}
|
|
return clr;
|
|
},
|
|
async clearFilter() {
|
|
//Reset back to DEFAULT filter
|
|
setActiveFilter(this); //will not trigger refresh yet
|
|
|
|
//Call api method to clear this filter
|
|
let res = await window.$gz.api.remove(
|
|
`data-list-filter/${this.activeFilterId}`
|
|
);
|
|
if (res.error) {
|
|
vm.formState.serverError = res.error;
|
|
window.$gz.form.setErrorBoxErrors(vm);
|
|
} else {
|
|
await fetchSavedFilterList(this);
|
|
await this.getDataFromApi();
|
|
}
|
|
},
|
|
clearFilterIcon() {
|
|
if (this.serverFilter && this.serverFilter.length > 0) {
|
|
return "$clear";
|
|
}
|
|
return null;
|
|
},
|
|
async editFilter() {
|
|
let res = await this.$refs.dataTableFilterManager.open();
|
|
if (res && res.refresh == true) {
|
|
if (res.newFilterId) {
|
|
//save as new filter, select it
|
|
this.activeFilterId = res.newFilterId;
|
|
}
|
|
//refresh the filter picklist and get data from api
|
|
await fetchSavedFilterList(this);
|
|
await this.getDataFromApi();
|
|
}
|
|
},
|
|
async mobileColumnFilterSelect() {
|
|
let resHeaderItem = await this.$refs.dataTableMobileFilterColumnSelector.open(
|
|
this.headers,
|
|
this.filterColor
|
|
);
|
|
if (resHeaderItem) {
|
|
await this.filter(resHeaderItem);
|
|
}
|
|
},
|
|
keyArrayFromSortByArray(sortBy) {
|
|
return sortBy.map(sortItem => {
|
|
let val = this.headers.find(z => z.value == sortItem);
|
|
if (val) {
|
|
return val.fk;
|
|
}
|
|
});
|
|
},
|
|
setSortIndicatorsFromDataListResponse(rsort) {
|
|
let sortBy = [];
|
|
let sortDesc = [];
|
|
if (rsort != null) {
|
|
Object.keys(rsort).forEach((key, index) => {
|
|
//Pull column header name "value" from "fk"matching "key" here from this.headers columns.c0 etc here from this.headers see above method
|
|
let found = this.headers.find(z => z.fk == key);
|
|
if (found != null) {
|
|
sortBy.push(found.value);
|
|
|
|
//if not null then push into the sortBy array
|
|
let sort = rsort[key];
|
|
if (sort == "-") {
|
|
sortDesc.push(true);
|
|
} else {
|
|
sortDesc.push(false);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
this.dataTablePagingOptions.sortBy = [...sortBy];
|
|
this.dataTablePagingOptions.sortDesc = [...sortDesc];
|
|
},
|
|
refresh() {
|
|
this.getDataFromApi();
|
|
},
|
|
getDataListSelection(ayaType) {
|
|
let vm = this;
|
|
//called when parent form needs the selected id's or the list view options needed to rehydrate the entire list of id's in the same order and filter
|
|
//i.e. for reporting, batch operations etc
|
|
|
|
let selectedRowIds = [];
|
|
//get selected row id's if there are selections else get all row id's
|
|
//called into from parent for things like reporting or batch ops
|
|
//selected or records both are arrays with an id property that contains the row id conveniently for this method
|
|
if (vm.selected.length > 0) {
|
|
//return row id's of selected items only
|
|
selectedRowIds = vm.selected.map(z => {
|
|
return z.id;
|
|
});
|
|
selectedRowIds.reverse();
|
|
}
|
|
return {
|
|
AType: ayaType,
|
|
selectedRowIds: selectedRowIds,
|
|
dataListKey: vm.dataListKey,
|
|
filterId: vm.activeFilterId,
|
|
clientCriteria: vm.clientCriteria,
|
|
clientTimeStamp: window.$gz.locale.clientLocalZoneTimeStamp(
|
|
vm.timeZoneName
|
|
)
|
|
};
|
|
},
|
|
handleSelectChange() {
|
|
//SINGLESELECT MODE?
|
|
//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(
|
|
"selection-change",
|
|
this.selected.map(z => z.id)
|
|
);
|
|
},
|
|
editColumnView() {
|
|
this.$router.push({
|
|
name: "ay-data-list-column-view",
|
|
params: {
|
|
dataListKey: this.dataListKey,
|
|
hiddenAffectiveColumns: this.hiddenAffectiveColumns
|
|
// ,
|
|
// formKey: this.formKey,
|
|
// activeFilterId: this.activeFilterId
|
|
}
|
|
});
|
|
},
|
|
selectedFilterChanged: async function() {
|
|
await this.getDataFromApi();
|
|
},
|
|
gridCellButtonClick(key, i, ot) {
|
|
let typeToOpen = null;
|
|
//if ot is not null or undefined then *that* is the Openable Type otherwise it's in the header data
|
|
if (ot) {
|
|
typeToOpen = ot;
|
|
} else {
|
|
//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
|
|
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 });
|
|
},
|
|
cellStyle(c) {
|
|
//does it have additional styling?
|
|
//c.cst is styling added here by data builder in the form of an object with props set for compatibility with Vue
|
|
//c.clr is a specific color coming from the server for items that support colors (reminders etc)
|
|
if (c.cst || c.clr) {
|
|
let clrBit = {};
|
|
if (c.clr) {
|
|
if (c.clr[0] != "#") {
|
|
c.clr = "#" + c.clr;
|
|
}
|
|
clrBit["border-left"] = `5px solid ${c.clr}`; //warning: don't put a semicolon after any of these styles, it will then ignore the whole bit
|
|
}
|
|
return { ...c.cst, ...clrBit };
|
|
} else {
|
|
return null;
|
|
}
|
|
},
|
|
async getDataFromApi(deSelectAll) {
|
|
let vm = this;
|
|
|
|
if (vm.loading) {
|
|
return;
|
|
}
|
|
//start with defaults
|
|
let listOptions = {
|
|
DataListKey: vm.dataListKey,
|
|
Limit: 5,
|
|
Offset: 0
|
|
};
|
|
|
|
//has filter changed?
|
|
if (vm.lastFetchFilterId != vm.activeFilterId) {
|
|
//yes, go back to page one
|
|
vm.dataTablePagingOptions.page = 1;
|
|
vm.lastFetchFilterId = vm.activeFilterId;
|
|
}
|
|
|
|
//calculate paging based on settings
|
|
const { page, itemsPerPage } = vm.dataTablePagingOptions;
|
|
if (itemsPerPage && itemsPerPage > 0) {
|
|
listOptions.Offset = (page - 1) * itemsPerPage;
|
|
listOptions.Limit = itemsPerPage;
|
|
}
|
|
|
|
vm.loading = true;
|
|
//Weird bug that causes grid to show all items selected after async method runs below to fetch data
|
|
//this puts a pin in it then resets it after the fetch is completed
|
|
let preSelected = [];
|
|
if (!deSelectAll) {
|
|
//preserve selections
|
|
preSelected = [...vm.selected];
|
|
}
|
|
|
|
try {
|
|
let res = await window.$gz.api.post("data-list", {
|
|
offset: listOptions.Offset,
|
|
limit: listOptions.Limit,
|
|
dataListKey: vm.dataListKey,
|
|
filterId: vm.activeFilterId,
|
|
clientCriteria: vm.clientCriteria,
|
|
clientTimeStamp: window.$gz.locale.clientLocalZoneTimeStamp(
|
|
vm.timeZoneName
|
|
)
|
|
});
|
|
|
|
if (res.error) {
|
|
vm.formState.serverError = res.error;
|
|
window.$gz.form.setErrorBoxErrors(vm);
|
|
} else {
|
|
//Save a copy of the server columns data for handling button clicks etc later
|
|
vm.serverColumns = res.columns;
|
|
//save a copy of the filter to show the filter symbol icons beside column headers
|
|
//and know that the return list *is* filtered
|
|
vm.serverFilter = res.filter;
|
|
|
|
//Hidden columns that affect the query and data displayed
|
|
vm.hiddenAffectiveColumns = res.hiddenAffectiveColumns;
|
|
|
|
//Make sure the translation keys are fetched
|
|
await fetchTranslatedHeaderNames(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);
|
|
|
|
//SET TABLE SORT HERE FROM RESPONSE
|
|
vm.setSortIndicatorsFromDataListResponse(res.sortBy);
|
|
|
|
//Post process data here and then set vm.records
|
|
vm.records = buildRecords(
|
|
res.data,
|
|
res.columns,
|
|
vm.ridColumnOpenable
|
|
);
|
|
|
|
vm.totalRecords = res.totalRecordCount;
|
|
//Put back selected items
|
|
vm.selected = [...preSelected];
|
|
|
|
//persist the paging options and filter stuff so user sees same page and list on refresh or leave and return scenario
|
|
saveFormSettings(vm);
|
|
}
|
|
} catch (err) {
|
|
window.$gz.errorHandler.handleFormError(err, vm);
|
|
} finally {
|
|
//Note: this needs to be called in NextTick because otherwise it triggers a re-fetch when the sort indicators were set above
|
|
vm.$nextTick(() => {
|
|
vm.loading = false;
|
|
vm.formState.ready = true;
|
|
});
|
|
}
|
|
}
|
|
},
|
|
async created() {
|
|
//get pick lists
|
|
let vm = this;
|
|
await initForm(vm);
|
|
//rehydrate last form settings
|
|
//loadFormSettings(vm);
|
|
vm.loading = false;
|
|
vm.getDataFromApi();
|
|
}
|
|
};
|
|
|
|
//Called by getDataFromApi on retrieval of list with columnData
|
|
function buildHeaders(columnData) {
|
|
//iterate columns, build headers and return
|
|
if (!columnData) {
|
|
return [];
|
|
}
|
|
let ret = [];
|
|
//iterate the columns
|
|
for (let i = 0; i < columnData.length; i++) {
|
|
let cm = columnData[i];
|
|
let h = {};
|
|
h.text = window.$gz.translation.get(cm.cm);
|
|
h.fk = cm.fk;
|
|
h.value = "columns.c" + i.toString(); //+".v";
|
|
if (i == 0) {
|
|
h.align = "start";
|
|
}
|
|
//sort and filter, note server only sends a value if a column is *not* filterable or sortable to save bandwidth since most are
|
|
//this just reverses that so that it's easier to check for
|
|
//Not filterable?
|
|
if (!cm.nf) {
|
|
//yes, is filterable
|
|
h.flt = true;
|
|
}
|
|
//Sortable?
|
|
if (cm.ns) {
|
|
h.sortable = false;
|
|
}
|
|
ret.push(h);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////
|
|
//Called by getDataFromApi on retrieval of list with columnData
|
|
//
|
|
function buildRecords(listData, columndefinitions, ridColumnOpenable) {
|
|
//iterate data, build each object keyed with index name and display set to correct translated filter and then return
|
|
|
|
let ret = [];
|
|
if (!listData) {
|
|
return ret;
|
|
}
|
|
|
|
//cache display format stuff
|
|
let timeZoneName = window.$gz.locale.getResolvedTimeZoneName();
|
|
let languageName = window.$gz.locale.getResolvedLanguage();
|
|
let hour12 = window.$gz.store.state.userOptions.hour12;
|
|
let currencyName = window.$gz.locale.getCurrencyName();
|
|
|
|
//this will cache the first time it's required (if required)
|
|
let availableRoles = null;
|
|
|
|
//comes as an array of arrays, needs to leave as an array of objects representing each row
|
|
for (let iRow = 0; iRow < listData.length; iRow++) {
|
|
let row = listData[iRow];
|
|
//iterate row and build object representing row data keyed to index
|
|
|
|
//container object for row
|
|
//id will be set later when code below encounters the id column which could be in any position (or not at all) but is identified by it's rid property
|
|
let o = { id: undefined, columns: {} };
|
|
|
|
for (let iColumn = 0; iColumn < row.length; iColumn++) {
|
|
let column = row[iColumn];
|
|
|
|
//rowId?
|
|
if (column.rid) {
|
|
o.id = column.i;
|
|
}
|
|
|
|
let dataType = columndefinitions[iColumn].dt;
|
|
let display = column.v;
|
|
let cstStyle = null; //custom additional styling here in grid e.g. negative numbers etc
|
|
|
|
/*
|
|
public enum UiFieldDataType : int
|
|
{
|
|
NoType = 0,
|
|
DateTime = 1,
|
|
Date = 2,
|
|
Time = 3,
|
|
Text = 4,
|
|
Integer = 5,
|
|
Bool = 6,
|
|
Decimal = 7,
|
|
Currency = 8,
|
|
Tags = 9,
|
|
Enum = 10,
|
|
EmailAddress = 11,
|
|
HTTP = 12,
|
|
InternalId = 13,
|
|
MemorySize=14,
|
|
TimeSpan=15,
|
|
PhoneNumber=16//this is so client can dial directly,
|
|
Roles=17
|
|
|
|
}
|
|
*/
|
|
if (display != null) {
|
|
switch (dataType) {
|
|
case 1: //datetime format to shortdatetime
|
|
display = window.$gz.locale.utcDateToShortDateAndTimeLocalized(
|
|
display,
|
|
timeZoneName,
|
|
languageName,
|
|
hour12
|
|
);
|
|
if (!window.$gz.locale.dateIsPast(column.v)) {
|
|
cstStyle = { "font-weight": "bold" }; //warning, don't put a semicolon after the style or it will not work
|
|
}
|
|
break;
|
|
case 2: //date only
|
|
display = window.$gz.locale.utcDateToShortDateLocalized(
|
|
display,
|
|
timeZoneName,
|
|
languageName
|
|
);
|
|
if (!window.$gz.locale.dateIsPast(column.v)) {
|
|
cstStyle = { "font-weight": "bold" }; //warning, don't put a semicolon after the style or it will not work
|
|
}
|
|
break;
|
|
case 3: //time only
|
|
display = window.$gz.locale.utcDateToShortTimeLocalized(
|
|
display,
|
|
timeZoneName,
|
|
languageName,
|
|
hour12
|
|
);
|
|
break;
|
|
case 4: //text
|
|
// if (display.length > MAX_TEXT_COLUMN_LENGTH) {
|
|
// display = display.substring(0, MAX_TEXT_COLUMN_LENGTH) + "...";
|
|
// }
|
|
display = window.$gz.util.truncateString(
|
|
display,
|
|
MAX_TEXT_COLUMN_LENGTH
|
|
);
|
|
break;
|
|
case 5: //Integer
|
|
//display is already set, just some styling to do
|
|
if (window.$gz.util.isNegative(column.v)) {
|
|
cstStyle = { color: "#FF0000" }; //warning, don't put a semicolon after the style or it will not work
|
|
}
|
|
break;
|
|
case 7: //decimal
|
|
display = window.$gz.locale.decimalLocalized(display, languageName);
|
|
if (window.$gz.util.isNegative(column.v)) {
|
|
cstStyle = { color: "#FF0000" }; //warning, don't put a semicolon after the style or it will not work
|
|
}
|
|
break;
|
|
case 8: //currency
|
|
display = window.$gz.locale.currencyLocalized(
|
|
display,
|
|
languageName,
|
|
currencyName
|
|
);
|
|
if (window.$gz.util.isNegative(column.v)) {
|
|
cstStyle = { color: "#FF0000" }; //warning, don't put a semicolon after the style or it will not work
|
|
}
|
|
break;
|
|
case 9: //tags
|
|
if (display && display.length > 0) {
|
|
display = display.join(", ");
|
|
} else {
|
|
display = "";
|
|
}
|
|
|
|
break;
|
|
case 10: //enum
|
|
display = window.$gz.enums.get(
|
|
columndefinitions[iColumn].et,
|
|
display
|
|
);
|
|
break;
|
|
case 14: //MemorySize (file size)
|
|
display = window.$gz.locale.humanFileSize(
|
|
display,
|
|
languageName,
|
|
false,
|
|
2
|
|
);
|
|
break;
|
|
case 17: //Authorization Roles
|
|
if (availableRoles == null) {
|
|
availableRoles = window.$gz.enums.getSelectionList(
|
|
"AuthorizationRoles"
|
|
);
|
|
}
|
|
let roles = display;
|
|
let roleNames = [];
|
|
|
|
if (roles != null && roles != 0) {
|
|
for (let i = 0; i < availableRoles.length; i++) {
|
|
let role = availableRoles[i];
|
|
if (!!(roles & role.id)) {
|
|
roleNames.push(role.name);
|
|
}
|
|
}
|
|
}
|
|
display = roleNames.join(", ");
|
|
|
|
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
|
|
let 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;
|
|
}
|
|
|
|
if (!ridColumnOpenable && column.rid) {
|
|
columnObject["nopen"] = true;
|
|
}
|
|
|
|
//Is there an overriding openable type already defined?
|
|
//(this is used by dynamic columns with different object type and id's that are openable like attachments and event logs and Review list etc)
|
|
if (column.ot) {
|
|
columnObject["ot"] = column.ot;
|
|
}
|
|
|
|
//rgba color to decorate field with
|
|
if (column.clr) {
|
|
columnObject["clr"] = column.clr;
|
|
}
|
|
|
|
//additional styling
|
|
if (cstStyle) {
|
|
columnObject["cst"] = cstStyle;
|
|
}
|
|
|
|
o.columns["c" + iColumn.toString()] = columnObject;
|
|
}
|
|
|
|
ret.push(o);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
//
|
|
// Ensures column names are present in translation table
|
|
//
|
|
async function fetchTranslatedHeaderNames(columnData) {
|
|
if (!columnData) {
|
|
return;
|
|
}
|
|
let headerKeys = [];
|
|
for (let i = 0; i < columnData.length; i++) {
|
|
let cm = columnData[i];
|
|
headerKeys.push(cm.cm);
|
|
}
|
|
//Now fetch all the keys
|
|
await window.$gz.translation.cacheTranslations(headerKeys);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
//
|
|
// Ensures column enums are present in enums list in store
|
|
//
|
|
async function fetchEnums(columnData) {
|
|
if (!columnData) {
|
|
return;
|
|
}
|
|
let headerKeys = [];
|
|
for (let i = 0; i < columnData.length; i++) {
|
|
let cm = columnData[i];
|
|
if (cm.et) {
|
|
await window.$gz.enums.fetchEnumList(cm.et);
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////
|
|
//
|
|
//
|
|
async function initForm(vm) {
|
|
vm.timeZoneName = window.$gz.locale.getResolvedTimeZoneName();
|
|
await fetchSavedFilterList(vm);
|
|
loadFormSettings(vm);
|
|
}
|
|
|
|
////////////////////
|
|
//
|
|
async function fetchSavedFilterList(vm) {
|
|
//http://localhost:7575/api/v8/data-list-filter/list?ListKey=TestWidgetDataList
|
|
let res = await window.$gz.api.get(
|
|
"data-list-filter/list?ListKey=" + vm.dataListKey
|
|
);
|
|
if (res.error) {
|
|
vm.formState.serverError = res.error;
|
|
window.$gz.form.setErrorBoxErrors(vm);
|
|
} else {
|
|
vm.selectLists.savedFilters = res.data;
|
|
//confirm we still have the current active filter id
|
|
setActiveFilter(vm, vm.activeFilterId);
|
|
}
|
|
}
|
|
|
|
////////////////////
|
|
//
|
|
function saveFormSettings(vm) {
|
|
window.$gz.form.setFormSettings(vm.formKey, {
|
|
temp: { page: vm.dataTablePagingOptions.page },
|
|
saved: {
|
|
itemsPerPage: vm.dataTablePagingOptions.itemsPerPage,
|
|
dataTable: {
|
|
activeFilterId: vm.activeFilterId
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
////////////////////
|
|
//
|
|
//
|
|
function setActiveFilter(vm, desiredId) {
|
|
//Handle a change of filter, ensure it exists, if not then try to select default and if not that then just put in a zero
|
|
//if desiredId is falsey then try to pick the default
|
|
|
|
if (desiredId) {
|
|
if (vm.selectLists.savedFilters.find(z => z.id == desiredId)) {
|
|
vm.activeFilterId = desiredId;
|
|
return;
|
|
}
|
|
}
|
|
|
|
//no specific id so attempt to set to default
|
|
let dflt = vm.selectLists.savedFilters.find(z => z.default == true);
|
|
if (dflt) {
|
|
vm.activeFilterId = dflt.id;
|
|
return;
|
|
}
|
|
vm.activeFilterId = 0;
|
|
}
|
|
|
|
////////////////////
|
|
//
|
|
function loadFormSettings(vm) {
|
|
let formSettings = window.$gz.form.getFormSettings(vm.formKey);
|
|
|
|
//process SAVED formsettings
|
|
if (formSettings.saved) {
|
|
if (formSettings.saved.itemsPerPage) {
|
|
vm.dataTablePagingOptions.itemsPerPage = formSettings.saved.itemsPerPage;
|
|
}
|
|
//have a nonzero saved filter id
|
|
if (
|
|
formSettings.saved.dataTable.activeFilterId != null &&
|
|
formSettings.saved.dataTable.activeFilterId != 0
|
|
) {
|
|
setActiveFilter(vm, formSettings.saved.dataTable.activeFilterId);
|
|
}
|
|
}
|
|
|
|
//process TEMP form settings
|
|
|
|
if (formSettings.temp && formSettings.temp.page) {
|
|
vm.dataTablePagingOptions.page = formSettings.temp.page;
|
|
}
|
|
}
|
|
</script>
|