Files
raven-client/ayanova/src/components/pick-list.vue
2020-03-24 19:02:26 +00:00

401 lines
13 KiB
Vue

<template>
<div>
<v-autocomplete
v-bind:value="value"
v-on:input="selectionMade($event)"
return-object
:items="searchResults"
:label="label"
item-text="name"
item-value="id"
item-disabled="!active"
:error-messages="errors"
:loading="fetching"
:placeholder="lt('Search')"
:search-input.sync="searchEntry"
:multiple="multiple"
:filter="customFilter"
hide-no-data
clearable
:no-filter="isTagFilter"
:prepend-icon="errorIcon"
@click:prepend="handleErrorClick"
@mousedown="dropdown"
>
<template v-slot:prepend-item v-if="hasError()">
<div class="pl-2">
<span class="error--text"> {{ errors[0] }}</span>
</div>
</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";
/*
States
1) Open form have a preselected Value
init: load preselected value only
change:
DROP DOWN need to drop down and have it populate with top 100
EDIT search as normal but keep preselected value until changed
2) Open form have no value
init: ALWAYS starts with no selection if no value, up to server routes to deal with 0 id selection if they need to
change
drop down, get top 100
edit and search as normal
NO VALUE
if not valid then there should be a rule enforcing that, not the control's issue
Control always has a value, either it's NO VALUE or it's a selection
DROP DOWN
if no items already loaded then fetches top 100 IN ADDITION to the preselect if present
if items already loaded then just shows those items as they are likely from a prior search and user can just search again
if user clears then drops down it acts as a fresh load of 100 items
PRE-FILL
NO prefill, fills only on user action or with defaults to save bandwidth. User must drop down to initiate action or type search text
todo: search entry, should preserve preset value until it's changed or maybe keep until the form is reloaded so can be inserted back again in search results
todo: set actual seleted ID value from our local selected whole object so outer form just gets id
todo: server sends whole on drop down of empty combo?
todo: needs to fill in the selected value when the form opens regardless of what any other setting is,
in other words it needs to ensure the pre-selectedvalue is in the list
Maybe an init property sent to server with query which is used to fetch teh pre-select value
Actually, traffic wise it might be best as an alternate route so that the form doesn't need to load any extras when there is already a selection
because, in general if already selected probably don't need a list of anything else
todo multiple selection
- bind value would be an array in this case, should I just change it to always be an array or maybe alternate control entirely for multiple since that's rare
todo: option to display icon to open the record selected, (we have the type and the id and in v7 you could click on most titles to navigate to that record)
if I add that then maybe need a "new" option on edit forms because it's a two step way to get to adding a new one of whatever it is without having to go
completely out of the page and hunt around lists and shit!!!
or consider a direct NEW button right there, (this might be a winner)
or maybe combine the two ideas, if no selection or empty selection then the button acts as new, puts any entered search text into the Name field
or, if selection then it acts as open
*/
export default {
created() {
var vm = this;
// console.log("CREATED:value is ", this.value);
//need to add no selection object always, if it's not valid then that's a rule for the form, not the control
window.$gz.form.addNoSelectionItem(vm.searchResults);
// debugger;
//TODO: this may be needed to force new records to have a zero in their no selection valid fields rather than null
// however it could overwrite a valid value maybe so needs further testing
//this.$emit("input", 0);
//set initial value in control if selected
if (vm.value != null && vm.value != 0) {
//It has a prior non empty selection that needs to be fetched
//note that by default this will just fetch the selected record instead of the prefill list
// console.log("STUB: created: has value, sb fetched");
var urlParams = "?ayaType=" + vm.ayaType + "&preId=" + vm.value;
vm.getList(urlParams);
}
},
data() {
return {
searchResults: [],
errors: [],
searchEntry: null,
lastSelection: null,
fetching: false,
isTagFilter: false,
errorIcon: null,
initialized: false
};
},
props: {
value: {
type: Number,
default: null
},
ayaType: {
type: Number,
default: 0
},
includeInactive: {
type: Boolean,
default: false
},
multiple: {
type: Boolean,
default: false
},
label: { type: String, default: "" }
},
watch: {
searchEntry(val, oldVal) {
var 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 (var i = 0; i < vm.searchResults.length; i++) {
if (vm.searchResults[i].name == val) {
return;
}
}
// console.log(
// "watch:searchentry cleared check to see if it's a selected list item, doing search for ",
// val
// );
// console.log("search results:", vm.searchResults);
if (!val || vm.fetching || !vm.initialized) {
if (!vm.initialized) {
vm.$nextTick(() => {
vm.initialized = true;
});
}
return;
}
this.doSearch();
},
errors(val) {
if (this.hasError()) {
this.errorIcon = "fa-question-circle";
} else {
this.errorIcon = null;
}
}
},
methods: {
lt: function(ltkey) {
return window.$gz.translation.get(ltkey);
},
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-picklist"
});
},
selectionMade(e) {
this.clearErrors();
if (e == undefined) {
//this will happen when clear clicked
return;
}
if (e.id != null) {
this.$emit("input", e.id);
}
this.lastSelection = e;
},
replaceLastSelection() {
// console.log("replace last selection top");
var 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 (var i = 0; i < vm.searchResults.length; i++) {
if (vm.searchResults[i].id == vm.value) {
// console.log(
// "rpl last selection null but found in list so setting last selection"
// );
vm.lastSelection = vm.searchResults[i];
return;
}
}
// console.log("RPL bailing as last selection is null and not in list");
return;
}
for (var i = 0; i < vm.searchResults.length; i++) {
if (vm.searchResults[i].id == vm.lastSelection.id) {
//console.log("replacelastselection bailing, it's in list already");
return;
}
}
vm.searchResults.push(vm.lastSelection);
// console.log(
// "replacelastselection bottom, search results now:",
// vm.searchResults
// );
},
dropdown(e) {
var 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;
}
return item.name.indexOf(queryText) > -1;
//split out just the text search part
// var searchText = queryText;
// if (queryText.includes(" ")) {
// //get the non tag part of query if possible
// //ignore bad condition of too many terms
// var searchTerms = queryText.split(" ");
// if (searchTerms[0].includes("..")) {
// searchText = searchTerms[1];
// } else {
// searchText = searchTerms[0];
// }
// }
},
getList: function(urlParams) {
var vm = this;
if (vm.fetching) {
return;
}
vm.fetching = true;
// console.log("getlist: calling api.get.picklist for type ", vm.ayaType);
//default params for when called on init
if (!urlParams) {
urlParams = "?ayaType=" + vm.ayaType;
if (vm.includeInactive) {
urlParams += "&inactive=true";
}
}
window.$gz.api
.get("PickList/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() {
//NOTE debounce with a watcher is a bit different
//https://vuejs.org/v2/guide/migration.html#debounce-Param-Attribute-for-v-model-removed
//-----------------
var vm = this;
//NOTE: empty query is perfectly valid; it means get the top 100 ordered by template order
var emptyQuery = false;
if (this.searchEntry == null || this.searchEntry == "") {
emptyQuery = true;
} else {
//Pre-process the query to validate and send conditionally
var val = this.searchEntry;
//get the discrete search terms and verify there are max two
var isATwoTermQuery = false;
var queryTerms = [];
if (val.includes(" ")) {
queryTerms = val.split(" ");
if (queryTerms.length > 2) {
vm.errors.push(vm.lt("ErrorPickListQueryInvalid"));
return;
}
isATwoTermQuery = true;
} else {
//one term only so push it into array
queryTerms.push(val);
//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.lt("ErrorPickListQueryInvalid"));
return;
}
//check that both aren't non-tags
if (
!window.$gz._.startsWith(queryTerms[0], "..") &&
!window.$gz._.startsWith(queryTerms[1], "..")
) {
vm.errors.push(vm.lt("ErrorPickListQueryInvalid"));
return;
}
}
}
//build url
//vm.fetching = true;
var urlParams = "?ayaType=" + vm.ayaType;
if (!emptyQuery) {
var 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
},
beforeCreate() {
//check pre-requisites exist just in case
if (window.$gz.errorHandler.devMode()) {
// if (!window.$gz._) {
// throw "tag-picker: $gz._ (lodash) is required and missing";
// }
if (!window.$gz.translation) {
throw "tag-picker: $gz.translation is required and missing";
}
}
}
};
</script>