Files
raven-client/ayanova/src/components/pick-list.vue
2020-06-08 16:49:41 +00:00

361 lines
10 KiB
Vue

<template>
<div>
<v-autocomplete
v-bind:value="value"
v-on:input="selectionMade($event)"
:readonly="readonly"
:disabled="disabled"
return-object
:items="searchResults"
:label="label"
item-text="name"
item-value="id"
item-disabled="!active"
:error-messages="errors"
:loading="fetching"
:placeholder="$ay.t('Search')"
:search-input.sync="searchEntry"
:filter="customFilter"
hide-no-data
:clearable="!readonly"
:no-filter="isTagFilter"
:append-icon="errorIcon"
@click:append="handleErrorClick"
@mousedown="dropdown"
>
<template v-slot:prepend-item v-if="hasError()">
<div class="pl-2">
<span class="error--text"> {{ errors[0] }}</span>
</div>
</template>
<template v-slot:prepend>
<v-icon @click="handleEditClick">{{ editIcon() }}</v-icon>
</template>
</v-autocomplete>
</div>
</template>
<script>
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* eslint-disable */
////////////////////////////////////////////////////////////////////////////////////////////////////////////
//NOTE: have to import lodash directly here as no combination was working with the window.$gz._
//it would not recognize window in the function call cache-items
import _ from "../libs/lodash.min.js";
export default {
created() {
this.fetchValueIfNotPresent();
},
data() {
return {
searchResults: [],
errors: [],
searchEntry: null,
lastSelection: null,
fetching: false,
isTagFilter: false,
errorIcon: null,
initialized: false
};
},
props: {
value: {
type: Number,
default: null
},
readonly: { type: Boolean, default: false },
disabled: { type: Boolean, default: false },
ayaType: {
type: Number,
default: 0
},
includeInactive: {
type: Boolean,
default: false
},
showEditIcon: {
type: Boolean,
default: false
},
label: { type: String, default: "" }
},
watch: {
value(val) {
this.fetchValueIfNotPresent();
},
searchEntry(val, oldVal) {
let vm = this;
//clear any local errors
vm.clearErrors();
//if the search entry is in the results list then it's a drop down selection not a typed search so bail
for (let i = 0; i < vm.searchResults.length; i++) {
if (vm.searchResults[i].name == val) {
return;
}
}
if (!val || vm.fetching || !vm.initialized) {
if (!vm.initialized) {
vm.$nextTick(() => {
vm.initialized = true;
});
}
return;
}
this.doSearch(val);
},
errors(val) {
if (this.hasError()) {
this.errorIcon = "fa-question-circle";
} else {
this.errorIcon = null;
}
}
},
methods: {
hasError: function() {
return this.errors.length > 0;
},
clearErrors: function() {
this.errors = [];
},
handleErrorClick: function() {
//open help nav for picklist
window.$gz.eventBus.$emit("menu-click", {
key: "app:help",
data: "ay-start-form-select-list"
});
},
editIcon: function() {
if (!this.showEditIcon) {
return null;
}
if (this.value != 0) {
return "fa-edit";
}
return "fa-plus";
},
handleEditClick: function() {
let idToOpen = 0;
if (this.lastSelection != null && this.lastSelection.id) {
idToOpen = this.lastSelection.id;
}
window.$gz.eventBus.$emit("openobject", {
type: this.ayaType,
id: idToOpen
});
},
selectionMade(e) {
this.clearErrors();
if (e == undefined) {
//this will happen when clear clicked
return;
}
if (e.id != null) {
//this is required for the control to update and parent form to detect it
this.$emit("input", e.id);
}
this.lastSelection = e;
},
fetchValueIfNotPresent() {
//is there a value that might require fetching?
let vm = this;
let val = vm.value;
if (val == null) {
return;
}
//check if it's in the list of items we have here
for (let i = 0; i < vm.searchResults.length; i++) {
if (vm.searchResults[i].id == val) {
return;
}
}
//is it the no selection item?
if (val == 0) {
window.$gz.form.addNoSelectionItem(vm.searchResults);
} else {
//Not here, better get it
let urlParams = "?ayaType=" + vm.ayaType + "&preId=" + vm.value;
vm.getList(urlParams);
}
},
replaceLastSelection() {
let vm = this;
//check if searchResults has last selection, if not then add it back in again
if (vm.lastSelection == null) {
//it might be initializing
for (let i = 0; i < vm.searchResults.length; i++) {
if (vm.searchResults[i].id == vm.value) {
vm.lastSelection = vm.searchResults[i];
return;
}
}
return;
}
for (let i = 0; i < vm.searchResults.length; i++) {
if (vm.searchResults[i].id == vm.lastSelection.id) {
return; //already there
}
}
//Not there so insert it
vm.searchResults.push(vm.lastSelection);
},
dropdown(e) {
let vm = this;
//check if we have only the initial loaded item and no selection item
if (vm.searchResults.length < 3) {
//get the default list
vm.getList();
}
},
customFilter(item, queryText, itemText) {
//NOTE: I wanted this to work with tags but all it does is highlight all of each row if tag query is present
//I guess because it later on attempts to do the highlighting and can't find all the entered query
//it's not clean so I'm just going to make it only highlight if it's a non tag query for now
//and do no filtering (highlighting) at all if it's a tag query
if (queryText.includes(" ") || queryText.startsWith("..")) {
this.isTagFilter = true;
return false;
}
if (this.$store.state.globalSettings.searchCaseSensitiveOnly == true) {
return item.name.indexOf(queryText) > -1;
} else {
//need to do a case insensitive filter and hopefully it mirrors postgres at the backend
return item.name.toLowerCase().indexOf(queryText.toLowerCase()) > -1;
}
//split out just the text search part
// let searchText = queryText;
// if (queryText.includes(" ")) {
// //get the non tag part of query if possible
// //ignore bad condition of too many terms
// let searchTerms = queryText.split(" ");
// if (searchTerms[0].includes("..")) {
// searchText = searchTerms[1];
// } else {
// searchText = searchTerms[0];
// }
// }
},
getList: function(urlParams) {
let vm = this;
if (vm.fetching) {
return;
}
vm.fetching = true;
//default params for when called on init
if (!urlParams) {
urlParams = "?ayaType=" + vm.ayaType;
if (vm.includeInactive) {
urlParams += "&inactive=true";
}
}
window.$gz.api
.get("pick-list/List" + urlParams)
.then(res => {
vm.fetching = false;
if (res.error) {
throw res.error;
}
vm.searchResults = res.data;
window.$gz.form.addNoSelectionItem(vm.searchResults);
vm.replaceLastSelection();
})
.catch(err => {
window.$gz.errorHandler.handleFormError(err);
vm.fetching = false;
});
},
doSearch: _.debounce(function(searchFor) {
//NOTE debounce with a watcher is a bit different, currently it has to be done exactly this way, nothing else will work properly
//https://vuejs.org/v2/guide/migration.html#debounce-Param-Attribute-for-v-model-removed
//-----------------
let vm = this;
let isATwoTermQuery = false;
let queryTerms = [];
//NOTE: empty query is valid; it means get the top 100 ordered by template order
let emptyQuery = false;
if (searchFor == null || searchFor == "") {
emptyQuery = true;
} else {
//Pre-process the query to validate and send conditionally
//get the discrete search terms and verify there are max two
if (searchFor.includes(" ")) {
queryTerms = searchFor.split(" ");
if (queryTerms.length > 2) {
vm.errors.push(vm.$ay.t("ErrorPickListQueryInvalid"));
return;
}
isATwoTermQuery = true;
} else {
//one term only so push it into array
queryTerms.push(searchFor);
//Marker term, will be weeded back out later
queryTerms.push("[?]");
}
//Now vet the terms
//Is user in mid entry of a second tag (space only?)
//will appear as an empty string post split
if (queryTerms[1] == "") {
//mid entry of a second term, just return
return;
}
//Is user in mid entry of tag query, just bounce back
if (
queryTerms[0] == "." ||
queryTerms[0] == ".." ||
queryTerms[1] == "." ||
queryTerms[1] == ".."
) {
return;
}
if (isATwoTermQuery) {
//check that both terms aren't tags
if (
window.$gz._.startsWith(queryTerms[0], "..") &&
window.$gz._.startsWith(queryTerms[1], "..")
) {
vm.errors.push(vm.$ay.t("ErrorPickListQueryInvalid"));
return;
}
//check that both aren't non-tags
if (
!window.$gz._.startsWith(queryTerms[0], "..") &&
!window.$gz._.startsWith(queryTerms[1], "..")
) {
vm.errors.push(vm.$ay.t("ErrorPickListQueryInvalid"));
return;
}
}
}
//build url
let urlParams = "?ayaType=" + vm.ayaType;
if (!emptyQuery) {
let query = queryTerms[0];
if (queryTerms[1] != "[?]") {
query += " " + queryTerms[1];
}
urlParams += "&query=" + query;
}
if (vm.includeInactive) {
urlParams += "&inactive=true";
}
this.getList(urlParams);
//------------
}, 300) //did some checking, 200-300ms seems to be the most common debounce time for ajax search queries
}
};
</script>