Improved autocomplete widget

This commit is contained in:
Jiang Yio 2023-04-24 21:30:14 -04:00
parent 1d68749525
commit 6c263b00dc
2 changed files with 41 additions and 36 deletions

View File

@ -1,44 +1,36 @@
<template> <template>
<div class="autocomplete"> <div class="dropdown" :class="{ 'form-floating': label }">
<input type="text" @input="option_open" v-model="xvalue" @keydown.down="option_down" @keydown.up="option_up" @keydown.enter="option_enter" /> <input type="text" class="form-control" placeholder=" " @focus="option_open" @input="option_filter" v-model="x_modelValue" @keydown.down="option_down" @keydown.up="option_up" @keydown.enter="option_enter" />
<ul id="autocomplete-results" v-show="open" class="autocomplete-results"> <ul class="dropdown-menu shadow" :class="{ show: (open) && (results.length > 0) }">
<li class="loading" v-if="!items">Loading results...</li> <li class="loading" v-if="!items">Loading results...</li>
<li v-else v-for="(result, i) in results" :key="i" @click="option_click(result)" class="autocomplete-result" :class="{ 'is-active': i === index }">{{ result }}</li> <li v-else v-for="(result, i) in results" :key="i" @click="option_click(result)" class="dropdown-item" :class="{ 'is-active': i === index }">{{ result }}</li>
</ul> </ul>
<label v-if="label">{{label}}</label>
</div> </div>
</template> </template>
<style scoped> <style scoped>
.autocomplete { .dropdown-menu {
position: relative; width: 100%;
} max-height: 10rem;
.autocomplete-results {
padding: 0;
margin: 0;
border: 1px solid #eeeeee;
height: 120px;
overflow: auto; overflow: auto;
} }
.autocomplete-result { .dropdown-item {
list-style: none; cursor: default;
text-align: left;
padding: 4px 2px;
cursor: pointer;
} }
.autocomplete-result.is-active, .dropdown-item.is-active,
.autocomplete-result:hover { .dropdown-item:hover {
background-color: #4AAE9B; background-color: var(--bs-primary);
color: white; color: var(--bs-body-bg);
} }
</style> </style>
<script> <script>
export default { export default {
props: { props: {
value: { modelValue: {
type: String, type: String,
default: '' default: ''
}, },
@ -46,26 +38,30 @@
type: Array, type: Array,
required: false, required: false,
default: () => [], default: () => [],
} },
label: String
}, },
emits: [
'update:modelValue'
],
data() { data() {
return { return {
xvalue: '', x_modelValue: this.modelValue,
results: [], results: [],
open: false, open: false,
index: -1, index: -1,
}; };
}, },
watch: { watch: {
value(val) { modelValue(val) {
this.xvalue = val; this.x_modelValue = val;
}, },
xvalue(val) { x_modelValue(val) {
this.$emit('update:value', val); this.$emit('update:modelValue', val);
} }
}, },
mounted() { mounted() {
this.xvalue = this.value; this.x_modelValue = this.modelValue;
document.addEventListener('click', this.option_close) document.addEventListener('click', this.option_close)
}, },
destroyed() { destroyed() {
@ -74,7 +70,16 @@
methods: { methods: {
option_open() { option_open() {
if(this.items) { if(this.items) {
this.results = this.items.filter((item) => item.toLowerCase().indexOf(this.xvalue.toLowerCase()) > -1); this.results = this.items;
this.open = true;
}
},
option_filter() {
if(this.items) {
if(this.x_modelValue) {
var selection = this.x_modelValue.toLowerCase();
this.results = this.items.filter((item) => item.toLowerCase().indexOf(selection) >= 0);
} else this.results = this.items;
this.open = true; this.open = true;
} }
}, },
@ -85,12 +90,12 @@
if(this.index > 0) this.index--; if(this.index > 0) this.index--;
}, },
option_enter() { option_enter() {
this.xvalue = this.results[this.index]; this.x_modelValue = this.results[this.index];
this.open = false; this.open = false;
this.index = -1; this.index = -1;
}, },
option_click(result) { option_click(result) {
this.xvalue = result; this.x_modelValue = result;
this.open = false; this.open = false;
}, },
option_close(evt) { option_close(evt) {

View File

@ -1,7 +1,7 @@
<template> <template>
<table class="table" style="font-family: monospace;" v-if="appointments && appointments.length > 0"> <table class="table" style="font-family: monospace;" v-if="appointments && appointments.length > 0">
<thead> <thead>
<tr><th>Time</th><th>Clinic</th><th>Patient</th><th>Note</th><th>Assignee</th></tr> <tr><th>Time</th><th>Clinic</th><th>Patient</th><th>Note</th><th style="width: 16rem;">Assignee</th></tr>
</thead> </thead>
<tbody> <tbody>
<tr v-for="row in appointments" :style="{ backgroundColor: strHashHSL(row.Clinic, '90%') }"> <tr v-for="row in appointments" :style="{ backgroundColor: strHashHSL(row.Clinic, '90%') }">
@ -10,7 +10,7 @@
<td v-if="production"><router-link :to="'/patient/$' + row.HRN">{{row.Name}} <span :title="row.HRN">{{row.HRN.slice(-4)}}</span></router-link></td> <td v-if="production"><router-link :to="'/patient/$' + row.HRN">{{row.Name}} <span :title="row.HRN">{{row.HRN.slice(-4)}}</span></router-link></td>
<td v-else><router-link :title="strtr_unscramble(row.Name)" :to="'/patient/$' + row.Name.charAt(0) + row.HRN.slice(-4) + '?name=' + row.Name">{{row.Name}} ${{row.HRN}}</router-link></td> <td v-else><router-link :title="strtr_unscramble(row.Name)" :to="'/patient/$' + row.Name.charAt(0) + row.HRN.slice(-4) + '?name=' + row.Name">{{row.Name}} ${{row.HRN}}</router-link></td>
<td>{{row.NOTE}} [{{row.APPT_MADE_BY}} on {{row.DATE_APPT_MADE}}]</td> <td>{{row.NOTE}} [{{row.APPT_MADE_BY}} on {{row.DATE_APPT_MADE}}]</td>
<td><Autocomplete :value="practitioner[row.Name]" @update:value="x => practitioner[row.Name] = x" :items="practitioner_list" /></td> <td><Autocomplete :modelValue="practitioner[row.Name]" @update:modelValue="x => practitioner[row.Name] = x" :items="practitioner_list" /></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>
@ -48,7 +48,7 @@
return this.client.remotestate.practitioner || (this.client.remotestate.practitioner = {}); return this.client.remotestate.practitioner || (this.client.remotestate.practitioner = {});
}, },
practitioner_list() { practitioner_list() {
return this.practitioner ? uniq(Object.values(this.practitioner)).sort() : []; return this.practitioner ? uniq(Object.values(this.practitioner).filter(x => x)).sort() : [];
} }
}, },
watch: { watch: {