235 lines
9.3 KiB
JavaScript
235 lines
9.3 KiB
JavaScript
|
|
const TailboneAutocomplete = {
|
|
|
|
template: '#tailbone-autocomplete-template',
|
|
|
|
props: {
|
|
|
|
// this is the "input" field name essentially. primarily is
|
|
// useful for "traditional" tailbone forms; it normally is not
|
|
// used otherwise. it is passed as-is to the buefy
|
|
// autocomplete component `name` prop
|
|
name: String,
|
|
|
|
// the url from which search results are to be obtained. the
|
|
// url should expect a GET request with a query string with a
|
|
// single `term` parameter, and return results as a JSON array
|
|
// containing objects with `value` and `label` properties.
|
|
serviceUrl: String,
|
|
|
|
// callers do not specify this directly but rather by way of
|
|
// the `v-model` directive. this component will emit `input`
|
|
// events when the value changes
|
|
value: String,
|
|
|
|
// callers may set an initial label if needed. this is useful
|
|
// in cases where the autocomplete needs to "already have a
|
|
// value" on page load. for instance when a user fills out
|
|
// the autocomplete field, but leaves other required fields
|
|
// blank and submits the form; page will re-load showing
|
|
// errors but the autocomplete field should remain "set" -
|
|
// normally it is only given a "value" (e.g. uuid) but this
|
|
// allows for the "label" to display correctly as well
|
|
initialLabel: String,
|
|
|
|
// while the `initialLabel` above is useful for setting the
|
|
// *initial* label (of course), it cannot be used to
|
|
// arbitrarily update the label during the component's life.
|
|
// if you do need to *update* the label after initial page
|
|
// load, then you should set `assignedLabel` instead. one
|
|
// place this happens is in /custorders/create page, where
|
|
// product autocomplete shows some results, and user clicks
|
|
// one, but then handler logic can forcibly "swap" the
|
|
// selection, causing *different* product data to come back
|
|
// from the server, and autocomplete label should be updated
|
|
// to match. this feels a bit awkward still but does work..
|
|
assignedLabel: String,
|
|
|
|
// simple placeholder text for the input box
|
|
placeholder: String,
|
|
|
|
// TODO: pretty sure this can be ignored..?
|
|
// (should deprecate / remove if so)
|
|
assignedValue: String,
|
|
},
|
|
|
|
data() {
|
|
|
|
// we want to track the "currently selected option" - which
|
|
// should normally be `null` to begin with, unless we were
|
|
// given a value, in which case we use `initialLabel` to
|
|
// complete the option
|
|
let selected = null
|
|
if (this.value) {
|
|
selected = {
|
|
value: this.value,
|
|
label: this.initialLabel,
|
|
}
|
|
}
|
|
|
|
return {
|
|
|
|
// this contains the search results; its contents may
|
|
// change over time as new searches happen. the
|
|
// "currently selected option" should be one of these,
|
|
// unless it is null
|
|
data: [],
|
|
|
|
// this tracks our "currently selected option" - per above
|
|
selected: selected,
|
|
|
|
// since we are wrapping a component which also makes use
|
|
// of the "value" paradigm, we must separate the concerns.
|
|
// so we use our own `value` prop to interact with the
|
|
// caller, but then we use this `buefyValue` data point to
|
|
// communicate with the buefy autocomplete component.
|
|
// note that `this.value` will always be either a uuid or
|
|
// null, whereas `this.buefyValue` may be raw text as
|
|
// entered by the user.
|
|
buefyValue: this.value,
|
|
|
|
// // TODO: we are "setting" this at the appropriate time,
|
|
// // but not clear if that actually affects anything.
|
|
// // should we just remove it?
|
|
// isFetching: false,
|
|
}
|
|
},
|
|
|
|
// watch: {
|
|
// // TODO: yikes this feels hacky. what happens is, when the
|
|
// // caller explicitly assigns a new UUID value to the tailbone
|
|
// // autocomplate component, the underlying buefy autocomplete
|
|
// // component was not getting the new value. so here we are
|
|
// // explicitly making sure it is in sync. this issue was
|
|
// // discovered on the "new vendor catalog batch" page
|
|
// value(val) {
|
|
// this.$nextTick(() => {
|
|
// if (this.buefyValue != val) {
|
|
// this.buefyValue = val
|
|
// }
|
|
// })
|
|
// },
|
|
// },
|
|
|
|
methods: {
|
|
|
|
// fetch new search results from the server. this is invoked
|
|
// via the `@typing` event from buefy autocomplete component.
|
|
// the doc at https://buefy.org/documentation/autocomplete
|
|
// mentions `debounce` as being optional. at one point i
|
|
// thought it would fix a performance bug; not sure `debounce`
|
|
// helped but figured might as well leave it
|
|
getAsyncData: debounce(function (entry) {
|
|
|
|
// since the `@typing` event from buefy component does not
|
|
// "self-regulate" in any way, we a) use `debounce` above,
|
|
// but also b) skip the search unless we have at least 3
|
|
// characters of input from user
|
|
if (entry.length < 3) {
|
|
this.data = []
|
|
return
|
|
}
|
|
|
|
// and perform the search
|
|
this.$http.get(this.serviceUrl + '?term=' + encodeURIComponent(entry))
|
|
.then(({ data }) => {
|
|
this.data = data
|
|
})
|
|
.catch((error) => {
|
|
this.data = []
|
|
throw error
|
|
})
|
|
}),
|
|
|
|
// this method is invoked via the `@select` event of the buefy
|
|
// autocomplete component. the `option` received will either
|
|
// be `null` or else a simple object with (at least) `value`
|
|
// and `label` properties
|
|
selectionMade(option) {
|
|
|
|
// we want to keep track of the "currently selected
|
|
// option" so we can display its label etc. also this
|
|
// helps control the visibility of the autocomplete input
|
|
// field vs. the button which indicates the field has a
|
|
// value
|
|
this.selected = option
|
|
|
|
// reset the internal value for buefy autocomplete
|
|
// component. note that this value will normally hold
|
|
// either the raw text entered by the user, or a uuid. we
|
|
// will not be needing either of those b/c they are not
|
|
// visible to user once selection is made, and if the
|
|
// selection is cleared we want user to start over anyway
|
|
this.buefyValue = null
|
|
|
|
// here is where we alert callers to the new value
|
|
if (option) {
|
|
this.$emit('new-label', option.label)
|
|
}
|
|
this.$emit('input', option ? option.value : null)
|
|
},
|
|
|
|
// set selection to the given option, which should a simple
|
|
// object with (at least) `value` and `label` properties
|
|
setSelection(option) {
|
|
this.$refs.autocomplete.setSelected(option)
|
|
},
|
|
|
|
// clear the field of any value, i.e. set the "currently
|
|
// selected option" to null. this is invoked when you click
|
|
// the button, which is visible while the field has a value.
|
|
// but callers can invoke it directly as well.
|
|
clearSelection(focus) {
|
|
|
|
// clear selection for the buefy autocomplete component
|
|
this.$refs.autocomplete.setSelected(null)
|
|
|
|
// maybe set focus to our (autocomplete) component
|
|
if (focus) {
|
|
this.$nextTick(function() {
|
|
this.focus()
|
|
})
|
|
}
|
|
},
|
|
|
|
// set focus to this component, which will just set focus to
|
|
// the buefy autocomplete component
|
|
focus() {
|
|
this.$refs.autocomplete.focus()
|
|
},
|
|
|
|
// this determines the "display text" for the button, which is
|
|
// shown when a selection has been made (or rather, when the
|
|
// field actually has a value)
|
|
getDisplayText() {
|
|
|
|
// always use the "assigned" label if we have one
|
|
// TODO: where is this used? what is the use case?
|
|
if (this.assignedLabel) {
|
|
return this.assignedLabel
|
|
}
|
|
|
|
// if we have a "currently selected option" then use its
|
|
// label. all search results / options have a `label`
|
|
// property as that is shown directly in the autocomplete
|
|
// dropdown. but if the option also has a `display`
|
|
// property then that is what we will show in the button.
|
|
// this way search results can show one thing in the
|
|
// search dropdown, and another in the button.
|
|
if (this.selected) {
|
|
return this.selected.display || this.selected.label
|
|
}
|
|
|
|
// we have nothing to go on here..
|
|
return ""
|
|
},
|
|
|
|
// returns the "raw" user input from the underlying buefy
|
|
// autocomplete component
|
|
getUserInput() {
|
|
return this.buefyValue
|
|
},
|
|
},
|
|
}
|
|
|
|
Vue.component('tailbone-autocomplete', TailboneAutocomplete)
|