1269 lines
43 KiB
Vue
1269 lines
43 KiB
Vue
<template>
|
|
<div>
|
|
<gz-error :errorBoxMessage="formState.errorBoxMessage"></gz-error>
|
|
<v-card>
|
|
<!-- {{ dataTablePagingOptions }}
|
|
{{ headers }} -->
|
|
<v-card-title>
|
|
<v-select
|
|
v-model="listViewId"
|
|
:items="selectLists.listViews"
|
|
item-text="name"
|
|
item-value="id"
|
|
:label="$ay.t('Filter')"
|
|
@input="listViewChanged"
|
|
data-cy="selectlistview"
|
|
>
|
|
</v-select>
|
|
<v-spacer></v-spacer>
|
|
<div>
|
|
<v-btn @click="refresh">
|
|
<v-icon data-cy="refresh">$ayiSync</v-icon>
|
|
</v-btn>
|
|
|
|
<v-btn class="ml-12" @click="editListView">
|
|
<v-icon data-cy="filter">$ayiColumns</v-icon>
|
|
</v-btn>
|
|
</div>
|
|
</v-card-title>
|
|
<div
|
|
class="headline 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"
|
|
>
|
|
<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"
|
|
v-bind:style="cellStyle(c)"
|
|
>
|
|
<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 && 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 -->
|
|
{{ c.v }}
|
|
</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-iterator
|
|
: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')"
|
|
class="elevation-1"
|
|
data-cy="datatable"
|
|
>
|
|
<template v-slot:header="props">
|
|
<!-- Mimic the full width data table select all toggle :no-data-text="$ay.t('NoData')"-->
|
|
<div
|
|
id="divSelectAll"
|
|
@click="props.toggleSelectAll(!props.everyItem)"
|
|
class="pl-2 pt-2"
|
|
>
|
|
<v-icon v-if="!props.someItems" large>$ayiSquare</v-icon>
|
|
<v-icon v-if="props.someItems && !props.everyItem" large
|
|
>$ayiMinus-square</v-icon
|
|
>
|
|
<v-icon v-if="props.everyItem" large>$ayiCheckSquare</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"
|
|
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 v-bind:style="cellStyle(c)">
|
|
<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 && 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 -->
|
|
{{ 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) 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-iterator>
|
|
</template>
|
|
</v-card>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
/* Xeslint-disable */
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
import relativeDatefilterCalculator from "../api/relative-date-filter-calculator.js";
|
|
const MAX_TEXT_COLUMN_LENGTH = 1024;
|
|
export default {
|
|
data() {
|
|
return {
|
|
loading: true,
|
|
dataTablePagingOptions: {},
|
|
lastDataTablePagingOptions: {},
|
|
listViewId: 0,
|
|
listView: undefined,
|
|
selectLists: {
|
|
listViews: []
|
|
},
|
|
headers: [],
|
|
serverColumns: [],
|
|
totalRecords: 0,
|
|
records: [],
|
|
rowsPerPageItems: [5, 10, 25, 50, 100],
|
|
selected: [],
|
|
formState: {
|
|
ready: false,
|
|
dirty: false,
|
|
valid: true,
|
|
readOnly: false,
|
|
loading: true,
|
|
errorBoxMessage: undefined,
|
|
appError: undefined,
|
|
serverError: {}
|
|
}
|
|
};
|
|
},
|
|
props: {
|
|
apiBaseUrl: {
|
|
type: String,
|
|
default: "data-list"
|
|
},
|
|
formKey: String,
|
|
dataListKey: String,
|
|
metaView: {
|
|
type: String,
|
|
default: undefined
|
|
},
|
|
clientCriteria: {
|
|
type: String,
|
|
default: undefined
|
|
},
|
|
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;
|
|
}
|
|
|
|
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 and put a pin in last paging settings for next time
|
|
this.getDataFromApi();
|
|
this.lastDataTablePagingOptions = this.dataTablePagingOptions;
|
|
},
|
|
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: {
|
|
//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 "";
|
|
},
|
|
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
|
|
sortBy.push(this.headers.find(z => z.fk == key).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();
|
|
}
|
|
/* public AyaType ObjectType { get; set; }
|
|
public long[] SelectedRowIds { get; set; }
|
|
public string DataListKey { get; set; }
|
|
public string ListView { get; set; }//optional, if null or empty will use default list view built into DataList
|
|
*/
|
|
return {
|
|
ObjectType: ayaType,
|
|
selectedRowIds: selectedRowIds,
|
|
dataListKey: vm.dataListKey,
|
|
listView: untokenizeListView(vm.listView),
|
|
clientCriteria: vm.clientCriteria
|
|
};
|
|
},
|
|
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
|
|
//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
|
|
//de-lodash
|
|
//this.$emit("selection-change", window.$gz. _.map(this.selected, "id"));
|
|
|
|
this.$emit(
|
|
"selection-change",
|
|
this.selected.map(z => z.id)
|
|
);
|
|
},
|
|
editListView() {
|
|
this.$router.push({
|
|
name: "ay-data-list-column-view",
|
|
params: {
|
|
dataListKey: this.dataListKey,
|
|
formKey: this.formKey,
|
|
listViewId: this.listViewId
|
|
}
|
|
});
|
|
},
|
|
resetListView: function() {
|
|
let vm = this;
|
|
vm.listViewId = 0;
|
|
vm.listView = undefined;
|
|
vm.dataTablePagingOptions.page = 1;
|
|
saveFormSettings(vm);
|
|
//needs to show as temp unsaved filter or overridden or something
|
|
},
|
|
|
|
listViewChanged: async function() {
|
|
let vm = this;
|
|
|
|
//If listview had changed it can only have changed *away* from the unsaved filter item if it's present so just remove the unsaved filter item if it exists
|
|
|
|
//DANGER DANGER WARNING: if using lodash to remove item it might mess with vue reactivity
|
|
//this example does remove from the array and does update a plain mustache rendition of it on the page but doesn't update the select itself
|
|
//whereas using the native javascript array splice function *does* update the select because vue wraps splice and other native methods specifically
|
|
//so it can properly update the dom
|
|
// window.$gz. _.remove(vm.selectLists.listViews, function(n) {
|
|
// return n.id == -1;
|
|
// });
|
|
|
|
//first prevent the following changes from triggering a fetch
|
|
vm.loading = true;
|
|
//always go back to page one on a change of data list view
|
|
vm.dataTablePagingOptions.page = 1;
|
|
|
|
for (let i = vm.selectLists.listViews.length - 1; i >= 0; i--) {
|
|
if (vm.selectLists.listViews[i].id === -1) {
|
|
vm.selectLists.listViews.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
if (vm.listViewId == 0) {
|
|
//default view, no saved, no cached
|
|
vm.listView = undefined;
|
|
saveFormSettings(vm);
|
|
} else if (vm.listViewId > 0) {
|
|
await fetchListView(vm);
|
|
saveFormSettings(vm);
|
|
}
|
|
|
|
//fetch data because listview has changed
|
|
vm.loading = false;
|
|
vm.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
|
|
};
|
|
|
|
//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 {
|
|
//untokenize ListView date token criteria (if there are any)
|
|
let untokenizedListView = untokenizeListView(vm.listView);
|
|
|
|
let res = await window.$gz.api.upsert(vm.apiBaseUrl, {
|
|
offset: listOptions.Offset,
|
|
limit: listOptions.Limit,
|
|
dataListKey: vm.dataListKey,
|
|
listView: untokenizedListView,
|
|
clientCriteria: vm.clientCriteria
|
|
});
|
|
|
|
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;
|
|
//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 listview 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 {
|
|
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();
|
|
},
|
|
beforeUpdate() {
|
|
if (this.clientCriteria != null && this.listViewId != 0) {
|
|
this.resetListView();
|
|
}
|
|
}
|
|
};
|
|
|
|
//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";
|
|
}
|
|
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.getBrowserTimeZoneName();
|
|
let languageName = window.$gz.locale.getBrowserLanguages();
|
|
let hour12 = window.$gz.store.state.userOptions.hour12;
|
|
let currencyName = window.$gz.store.state.userOptions.currencyName;
|
|
|
|
//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 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) + "...";
|
|
}
|
|
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) {
|
|
await populateSelectionLists(vm);
|
|
await loadFormSettings(vm);
|
|
}
|
|
|
|
////////////////////
|
|
//
|
|
async function populateSelectionLists(vm) {
|
|
//http://localhost:7575/api/v8/data-list-view/viewlist?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);
|
|
//window.$gz.errorHandler.handleFormError(res.error, vm);
|
|
} else {
|
|
vm.selectLists.listViews = res.data;
|
|
window.$gz.form.addNoSelectionItem(vm.selectLists.listViews);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////
|
|
//
|
|
// Fetch and cache list view
|
|
//
|
|
async function fetchListView(vm) {
|
|
if (!vm.listViewId) {
|
|
return;
|
|
}
|
|
let res = await window.$gz.api.get("data-list-view/" + vm.listViewId);
|
|
if (res.error) {
|
|
if (res.error.code && res.error.code == "2010") {
|
|
//list not found, probably deleted by another user
|
|
//or on another browser and this one still had it as the last used list view
|
|
vm.listViewId = 0; //indicate no list view
|
|
return;
|
|
} else {
|
|
vm.formState.serverError = res.error;
|
|
window.$gz.form.setErrorBoxErrors(vm);
|
|
}
|
|
// window.$gz.errorHandler.handleFormError(res.error, vm);
|
|
} else {
|
|
vm.listView = res.data.listView;
|
|
}
|
|
}
|
|
|
|
////////////////////
|
|
//
|
|
function saveFormSettings(vm) {
|
|
let unsavedlv = vm.listView;
|
|
let 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) {
|
|
let 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 != null) {
|
|
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 != null) {
|
|
//add UNSAVED FILTER if -1
|
|
|
|
vm.selectLists.listViews.unshift({
|
|
name: vm.$ay.t("FilterUnsaved"),
|
|
id: -1
|
|
});
|
|
|
|
vm.listView = formSettings.saved.dataTable.unsavedListView;
|
|
} 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 && 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 && formSettings.temp.cachedListView != null) {
|
|
vm.listView = formSettings.temp.cachedListView;
|
|
} else {
|
|
//fetch it and cache it
|
|
return fetchListView(vm, vm.listViewId);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*//EXAMPLE FROM INTEGRATION TEST OF BETWEEN TWO DATES
|
|
// dynamic fitem = new JObject();
|
|
// fitem.op = Util.OpGreaterThanOrEqualTo;
|
|
// fitem.value = new DateTime(2019, 3, 12, 10, 0, 0).ToOffsetAdjustedUniversalTime();
|
|
// items.Add(fitem);
|
|
|
|
// fitem = new JObject();
|
|
// fitem.op = Util.OpLessThanOrEqualTo;
|
|
// fitem.value = new DateTime(2019, 3, 12, 11, 0, 0).ToOffsetAdjustedUniversalTime();
|
|
// items.Add(fitem); */
|
|
////////////////////
|
|
//
|
|
function untokenizeListView(lvJson) {
|
|
//if it has one or more tokens
|
|
//iterate the array and build a new array with substituted tokens with the correct date and time in them
|
|
|
|
//format of a date token filter
|
|
//[{"fld":"widgetname"},{"fld":"widgetstartdate","filter":{"items":[{"op":"=","value":"*past90days*","token":true}]}},{"fld":"widgetenddate"}]
|
|
if (lvJson == null) {
|
|
return lvJson;
|
|
}
|
|
//See if it has any date tokens
|
|
if (lvJson.indexOf('"token":true') == -1) {
|
|
return lvJson;
|
|
}
|
|
|
|
//we have one or more tokens, substitute them in the filter array
|
|
let ret = [];
|
|
let lv = JSON.parse(lvJson);
|
|
|
|
//iterate the incoming and copy to the outgoing directly
|
|
//except if a date token filter then substitute our own filter object in place
|
|
for (let ilv = 0; ilv < lv.length; ilv++) {
|
|
//listview object
|
|
let lvo = lv[ilv];
|
|
//instantiate return object
|
|
let reto = {};
|
|
//copy over field name to return object
|
|
reto.fld = lvo.fld;
|
|
//sort?
|
|
if (lvo.sort) {
|
|
reto.sort = lvo.sort;
|
|
}
|
|
//does it have a filter?
|
|
if (lvo.filter) {
|
|
//yes, so copy / transform as required
|
|
|
|
//create an empty filter items array on return object
|
|
reto.filter = { items: [] };
|
|
|
|
//"any" property set?
|
|
if (lvo.filter.any) {
|
|
reto.filter.any = true;
|
|
}
|
|
|
|
//iterate the filter items in the source lvo object
|
|
for (let j = 0; j < lvo.filter.items.length; j++) {
|
|
//get this filter item
|
|
let fi = lvo.filter.items[j];
|
|
//no token shortcut
|
|
if (!fi.token) {
|
|
//just copy it out
|
|
reto.filter.items.push(fi);
|
|
} else {
|
|
//it has a date token so let's build it out
|
|
//filter item value contains the token, op is always equals
|
|
let filterDates = relativeDatefilterCalculator.tokenToDates(fi.value);
|
|
//create and add a new filter item for each not undefined value
|
|
//{ after: undefined, before: undefined }
|
|
//AFTER DATE?
|
|
if (filterDates.after) {
|
|
reto.filter.items.push({
|
|
op: ">", //was GreaterThanOrEqualTo but that doesn't make sense, it's only greater than was seeing wrong return data as relative date filter already adjusts for boundaries
|
|
value: filterDates.after
|
|
});
|
|
}
|
|
//BEFORE DATE?
|
|
if (filterDates.before) {
|
|
reto.filter.items.push({
|
|
op: "<", //was LessThanOrEqualTo but see above for after ===-----^
|
|
value: filterDates.before
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
//end of has filter if condition
|
|
}
|
|
|
|
//push the return object into the return array
|
|
ret.push(reto);
|
|
//end of iterate lv loop
|
|
}
|
|
|
|
return JSON.stringify(ret);
|
|
} //[{"fld":"widgetname"},{"fld":"widgetstartdate","filter":{"items":[{"op":"=","value":"*past90days*","token":true}]}},{"fld":"widgetenddate"}]
|
|
</script>
|