Files
raven-client/ayanova/src/components/pick-list.vue
2020-03-20 00:05:16 +00:00

349 lines
12 KiB
Vue

<template>
<div>
<v-autocomplete
v-model="selected"
return-object
:items="searchResults"
:label="label"
item-text="name"
item-value="id"
item-disabled="!active"
:error-messages="errors"
:loading="searchUnderway"
:placeholder="lt('Search by text, ..tags or both')"
:search-input.sync="searchEntry"
auto-select-first
:multiple="multiple"
:filter="customFilter"
hide-no-data
clearable
:no-filter="isTagFilter"
>
<!-- <template v-slot:no-data> hide-selected v-on:input="$emit('input', $event)" cache-items="false":no-data-text="lt('NoData')"
<v-list-item>
<v-list-item-title>
Search for your favorite
<strong>PICKLIST ITEM</strong>
some kind of hint here?
</v-list-item-title>
</v-list-item>
</template> -->
</v-autocomplete>
<!-- <div>autocomplete selected item: {{ selected }}</div>
<div>searchResults: {{ searchResults }}</div> -->
</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
import _ from "../libs/lodash.min.js";
/*
todo: double check what I want here is actually autocomplete,
becuase I'm not actually autocompleting what is typed, it's more auto-search
maybe combobox does this better?
worth it to check
todo: validation error is obscured by no-data element
- PREPEND SLOT - use the prepend slot to add custom error message box or mirror it, In addition to the help link mentioned below or maybe this is it?
- style it to look like the error slot
- maybe on validation error it should just clear the list so this will reveal the error?
- can't get this to do anything, it's very stubborn
- maybe on validation error it shows elsewhere (popup error dialog with help link?) or just goes red and a HELP icon appears to take to how to use picklist?
- also there is suffix and prefix text options to explore
- also there is separately HINT and PLACEHOLDER text, maybe HINT is what I'm after here
- NOPE: hint is useless, it just gets obscured by drop down items list just like error and is replaced by error message
todo: set actual seleted ID value from our local selected whole object so outer form just gets id
todo test multiple selection
todo: search only property that forces user to search vs just drop down and get default 100
todo: append or use slot to put at end of drop down menu a "type to search for more" when the results are from an empty query only
todo: translation keys when done
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() {
//need to add no selection object if specified
var vm = this;
//console.log("CREATED: calling getList for type ", this.ayaType);
if (vm.preFill) {
vm.searchUnderway = true;
vm.getList();
} else {
if (vm.noSelectionValid) {
window.$gz.form.addNoSelectionItem(vm.searchResults);
if (vm.value == null) {
vm.value = 0;
}
}
}
},
beforeUpdate() {
//Set the initial list items based on the record items, this only needs to be called once at init
//Not sure what this is to picklist as it came from tags
// if (!this.initialized && this.value.length > 0) {
// this.searchResults = this.value;
// this.initialized = true;
// }
},
data() {
return {
searchResults: [],
errors: [],
selected: { name: "-", id: 0 },
searchEntry: null,
searchUnderway: false,
isTagFilter: false
//,initialized: false
};
},
props: {
// value: {
// type: Number,
// default: 0
// },
ayaType: {
type: Number,
default: 0
},
includeInactive: {
type: Boolean,
default: false
},
multiple: {
type: Boolean,
default: false
},
noSelectionValid: {
type: Boolean,
default: false
},
preFill: {
type: Boolean,
default: true
},
label: { type: String, default: "" }
},
watch: {
searchEntry(val) {
var vm = this;
//clear any local errors
vm.errors = [];
// console.log("WATCH::SEARCHENTRY TRIGGERED:", val);
if (!val || vm.searchUnderway) {
return;
}
if (vm.selected != null) {
if (val == vm.selected.name) {
return;
}
}
//console.log("WATCH::SEARCHENTRY doing search now");
this.doSearch();
},
value(val) {
//this ensures the parent form gets the onchange event
//not actually sure why there are two here but it worked with the datetime picker so I replicated it here
//To answer above it appears both are necessary for proper operation
this.$emit("input", val);
this.$emit("change", val);
}
},
methods: {
lt: function(ltkey) {
return window.$gz.translation.get(ltkey);
},
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;
// 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 => {
if (res.error) {
throw res.error;
}
vm.searchResults = res.data;
if (vm.noSelectionValid) {
window.$gz.form.addNoSelectionItem(vm.searchResults);
// if (vm.value == null) {
// vm.value = 0;
// }
}
vm.searchUnderway = false;
})
.catch(err => {
window.$gz.errorHandler.handleFormError(err);
});
},
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) {
//todo: put client side localized validation error message in component
vm.errors.push("LTERROR TOO MANY TERMS");
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], "..")
) {
//todo: put client side localized validation error message in component
vm.errors.push(
"LTERROR if two terms one must be tag and one must be text"
);
return;
}
//check that both aren't non-tags
if (
!window.$gz._.startsWith(queryTerms[0], "..") &&
!window.$gz._.startsWith(queryTerms[1], "..")
) {
//todo: put client side localized validation error message in component
vm.errors.push(
"LTERROR if two terms one must be tag and one must be text"
);
return;
}
}
}
//build url
vm.searchUnderway = 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);
// console.log("dosearch: calling api.get.picklist for type ", this.ayaType);
// window.$gz.api
// .get("PickList/List" + urlParams)
// .then(res => {
// if (res.error) {
// throw res.error;
// }
// vm.searchResults = res.data;
// if (vm.noSelectionValid) {
// window.$gz.form.addNoSelectionItem(vm.searchResults);
// // if (vm.value == null) {
// // vm.value = 0;
// // }
// }
// vm.searchUnderway = false;
// })
// .catch(err => {
// window.$gz.errorHandler.handleFormError(err);
// });
//------------
}, 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>