Files
recipes/vue/src/components/GenericMultiselect.vue
2023-02-11 16:27:41 +01:00

212 lines
7.3 KiB
Vue

<template>
<multiselect
:id="id"
v-model="selected_objects"
:options="objects"
:close-on-select="true"
:clear-on-select="multiple"
:hide-selected="multiple"
:preserve-search="true"
:internal-search="false"
:limit="limit"
:placeholder="lookupPlaceholder"
:label="label"
track-by="id"
:multiple="multiple"
:taggable="allow_create"
:tag-placeholder="create_placeholder"
:loading="loading"
@search-change="search"
@input="selectionChanged"
@tag="addNew"
@open="selectOpened()"
>
</multiselect>
</template>
<script>
import Vue from "vue"
import Multiselect from "vue-multiselect"
import {ApiMixin, StandardToasts} from "@/utils/utils"
export default {
name: "GenericMultiselect",
components: {Multiselect},
mixins: [ApiMixin],
data() {
return {
// this.Models and this.Actions inherited from ApiMixin
id: undefined,
loading: false,
objects: [],
selected_objects: undefined,
}
},
props: {
placeholder: {type: String, default: undefined},
model: {
type: Object,
default() {
return {}
},
},
label: {type: String, default: "name"},
parent_variable: {type: String, default: undefined},
limit: {type: Number, default: 25},
sticky_options: {
type: Array,
default() {
return []
},
},
initial_selection: {
type: Array,
default() {
return []
},
},
initial_single_selection: {
type: Object,
default: undefined,
},
search_on_load: {type: Boolean, default: true},
multiple: {type: Boolean, default: true},
allow_create: {type: Boolean, default: false},
create_placeholder: {type: String, default: "You Forgot to Add a Tag Placeholder"},
clear: {type: Number},
},
watch: {
initial_selection: function (newVal, oldVal) {
// watch it
this.selected_objects = newVal
let get_details = []
let empty = {}
empty[this.label] = `..${this.$t("loading")}..`
this.selected_objects.forEach((x) => {
if (typeof x !== "object") {
this.selected_objects[this.selected_objects.indexOf(x)] = {...empty, id: x}
get_details.push(x)
}
})
get_details.forEach((x) => {
this.genericAPI(this.model, this.Actions.FETCH, {id: x})
.then((result) => {
// this.selected_objects[this.selected_objects.map((y) => y.id).indexOf(x)] = result.data
Vue.set(this.selected_objects, this.selected_objects.map((y) => y.id).indexOf(x), result.data)
})
.catch((err) => {
this.selected_objects = this.selected_objects.filter((y) => y.id !== x)
})
})
},
initial_single_selection: function (newVal, oldVal) {
// watch it
this.selected_objects = newVal
if (typeof this.selected_objects !== "object") {
let empty = {}
empty[this.label] = `..${this.$t("loading")}..`
this.selected_objects = {...empty, id: this.selected_objects}
this.genericAPI(this.model, this.Actions.FETCH, {id: this.selected_objects})
.then((result) => {
this.selected_objects = result.data
})
.catch((err) => {
this.selected_objects = undefined
})
}
},
clear: function (newVal, oldVal) {
if (this.multiple || !this.initial_single_selection) {
this.selected_objects = []
} else {
this.selected_objects = undefined
}
},
},
mounted() {
this.id = Math.random()
if (this.search_on_load) {
this.search("")
}
if (this.multiple || !this.initial_single_selection) {
this.selected_objects = this.initial_selection
} else {
this.selected_objects = this.initial_single_selection
}
},
computed: {
lookupPlaceholder() {
return this.placeholder || this.model.name || this.$t("Search")
},
nothingSelected() {
if (this.multiple || !this.initial_single_selection) {
return this.selected_objects?.length === 0 && this.initial_selection?.length === 0
} else {
return !this.selected_objects && !this.initial_single_selection
}
},
},
methods: {
// this.genericAPI inherited from ApiMixin
search: function (query) {
let options = {
page: 1,
pageSize: this.limit,
query: query,
limit: this.limit,
options: {query: {simple: 1}}, // for API endpoints that support a simple view
}
this.genericAPI(this.model, this.Actions.LIST, options).then((result) => {
this.objects = this.sticky_options.concat(result.data?.results ?? result.data)
if (this.nothingSelected && this.objects.length > 0) {
this.objects.forEach((item) => {
// select default items when present in object
if ("default" in item) {
if (item.default) {
if (this.multiple) {
this.selected_objects = [item]
} else {
this.selected_objects = item
}
this.selectionChanged()
}
}
})
}
})
},
selectOpened: function () {
if (this.objects.length < 1) {
this.search("")
}
},
selectionChanged: function () {
this.$emit("change", {var: this.parent_variable, val: this.selected_objects})
},
addNew(e) {
//TODO add ability to choose field name other than "name"
console.log('CREATEING NEW with -> ', e)
this.genericAPI(this.model, this.Actions.CREATE, {name: e}).then(result => {
let createdObj = result.data?.results ?? result.data
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_CREATE)
if (this.multiple) {
this.selected_objects.push(createdObj)
} else {
this.selected_objects = createdObj
}
this.objects.push(createdObj)
this.selectionChanged()
}).catch((r, err) => {
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE)
})
},
},
}
</script>
<style src="vue-multiselect/dist/vue-multiselect.min.css"></style>
<style scoped></style>