Select Git revision
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
InputCombobox.vue 8.26 KiB
<template>
<MultiSelect
:model-value="selectedOption"
:options="selectableOptions"
label="name"
track-by="name"
:placeholder="t('selectPlaceholder')"
:required="required"
:disabled="disabledMode || locked"
select-label=""
:loading="isLoadingSearchResults"
selected-label=""
deselect-label=""
@update:model-value="updateMetadataValue"
@search-change="triggerFetchOptions"
>
<template #afterList>
<div
v-if="selectableOptions.length && !allResultsLoaded"
v-observe-visibility="reachedEndOfList"
>
<div class="d-flex justify-content-left my-3 ps-3">
<b-spinner v-if="resultsLoading" small />
</div>
</div>
</template>
</MultiSelect>
</template>
<script lang="ts">
import i18n from '@/plugins/i18n';
import MultiSelect from '@/plugins/vue-multiselect';
import { ObserveVisibility } from 'vue-observe-visibility';
import type { Label } from '@/types/labels';
import type { NamedNode, Quad_Object } from '@rdfjs/types';
import factory from 'rdf-ext';
import { retrieveChildren, retrieveInstance } from '@/util/linkedData';
export default defineComponent({
name: 'InputCombobox',
components: {
MultiSelect,
},
directives: {
'observe-visibility': ObserveVisibility,
},
props: {
locked: {
default: false,
type: Boolean,
},
required: {
default: false,
type: Boolean,
},
entry: {
required: true,
type: Object as PropType<Quad_Object>,
},
disabledMode: {
default: false,
type: Boolean,
},
languageCode: {
default: 'en',
type: String,
},
classObject: {
required: true,
type: Object as PropType<NamedNode<string>>,
},
classReceiver: {
default: () => retrieveChildren,
type: Function as PropType<typeof retrieveChildren>,
},
instanceReceiver: {
default: () => retrieveInstance,
type: Function as PropType<typeof retrieveInstance>,
},
},
setup() {
return { locale: i18n.global.locale, t: i18n.global.t };
},
data() {
return {
resultList: [] as Label[],
searchResultList: [] as Label[],
object: factory.namedNode('') as NamedNode<string>,
pageSize: 10,
pageNumber: 1,
searchTerm: undefined as undefined | string,
queryTimer: 0,
allResultsLoaded: false,
resultsLoading: false,
hasNextDeInstances: true,
hasNextEnInstances: true,
isVisible: true,
isLoadingSearchResults: false,
};
},
computed: {
selectableOptions(): Label[] {
return [...this.resultList].sort((item1, item2) =>
item1.name && item2.name ? item1.name.localeCompare(item2?.name) : 0,
);
},
selectedOption(): Label | undefined {
if (this.object.value !== '') {
const foundOption = this.resultList.find(
(option) => option.value === this.object.value,
);
return {
name: foundOption?.name,
value: this.object.value,
};
}
return undefined;
},
},
watch: {
entry() {
this.loadData();
},
},
mounted() {
if (this.languageCode === 'en' || this.languageCode === 'de') {
this.locale = this.languageCode;
} else {
this.locale = 'en';
}
this.retrieveLabels();
this.loadData();
},
methods: {
updateMetadataValue(label: Label | null) {
if (label?.value) {
this.object.value = label.value;
} else {
this.object.value = '';
}
this.$emit('update:metadata-value', this.object);
},
async loadData() {
if (this.object.value !== this.entry.value) {
this.object.value = this.entry.value;
const foundOption = this.resultList.find(
(option) => option.value === this.object.value,
);
if (!foundOption) {
let instanceReceiver:
| typeof retrieveInstance
| (() => typeof retrieveInstance) = this.instanceReceiver;
// Deal with weird behavior during dev builds
try {
// If the function has no arguments, it's not the retrieveChildren one
if (instanceReceiver.length === 0) {
instanceReceiver = (
instanceReceiver as unknown as () => typeof retrieveInstance
)();
}
} catch (e) {
// Ignore
}
const instanceLabels = await instanceReceiver(
this.object.value,
this.classObject.value,
);
// List to be extended
let newEntriesToAppend: Label;
if (this.languageCode === 'en' && instanceLabels.en) {
newEntriesToAppend = instanceLabels.en;
} else if (this.languageCode === 'de' && instanceLabels.de) {
newEntriesToAppend = instanceLabels.de;
} else {
newEntriesToAppend = {
name: this.object.value,
value: this.object.value,
};
}
this.resultList.push(newEntriesToAppend);
}
}
},
async reachedEndOfList(isVisible: boolean) {
if (!isVisible) {
return;
}
this.isVisible = isVisible;
if (this.hasNextDeInstances || this.hasNextEnInstances) {
//fetch vocabularies
await this.retrieveLabels();
}
},
async retrieveLabels() {
const selectedSearchOption = this.selectedOption;
this.resultsLoading = true;
let classReceiver:
| typeof retrieveChildren
| (() => typeof retrieveChildren) = this.classReceiver;
// Deal with weird behavior during dev builds
try {
// If the function has no arguments, it's not the retrieveChildren one
if (classReceiver.length === 0) {
classReceiver = (
classReceiver as unknown as () => typeof retrieveChildren
)();
}
} catch (e) {
// Ignore
}
try {
const bilingualLabels = await classReceiver(
this.classObject.value,
this.pageNumber,
this.pageSize,
this.searchTerm,
);
// List to be extended
let newEntriesToAppend: Label[] = [];
if (this.languageCode === 'en' && bilingualLabels.en) {
newEntriesToAppend = bilingualLabels.en;
} else if (this.languageCode === 'de' && bilingualLabels.de) {
newEntriesToAppend = bilingualLabels.de;
} else if (bilingualLabels.en && bilingualLabels.en.length) {
newEntriesToAppend = bilingualLabels.en;
} else if (bilingualLabels.de && bilingualLabels.de.length) {
newEntriesToAppend = bilingualLabels.de;
}
this.hasNextDeInstances =
bilingualLabels.dePagination?.hasNext === true;
this.hasNextEnInstances =
bilingualLabels.enPagination?.hasNext === true;
// Avoid duplicates
this.resultList = this.resultList.concat(
newEntriesToAppend.filter(
(entry) =>
!this.resultList.some(
(otherEntry) => entry.value === otherEntry.value,
),
),
);
if (
selectedSearchOption &&
!this.resultList.some(
(entry) => entry.value === selectedSearchOption?.value,
)
) {
this.resultList.push(selectedSearchOption);
}
this.loadData();
this.allResultsLoaded =
!this.hasNextDeInstances || !this.hasNextEnInstances;
// Increment the page number for next iteration
this.pageNumber++;
} catch (e) {
// Method not available
console.error(e);
}
this.resultsLoading = false;
},
triggerFetchOptions(searchTerm: string) {
clearTimeout(this.queryTimer);
this.isLoadingSearchResults = true; // Set the flag here, to avoid delayed loading indication
// Set the search term
this.searchTerm = searchTerm;
// Reset the page number to show the first result
this.pageNumber = 1;
this.resultList = this.selectedOption ? [this.selectedOption] : [];
this.queryTimer = window.setTimeout(() => this.retrieveLabels(), 500);
this.isLoadingSearchResults = false;
},
},
});
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped></style>