263 lines
6.8 KiB
Vue
263 lines
6.8 KiB
Vue
<template>
|
|
<div class="belongs-to-dropdown">
|
|
<label
|
|
:for="name"
|
|
class="col-form-label"
|
|
>
|
|
<b>{{ label }} {{ required ? "*" : "" }}</b>
|
|
</label>
|
|
<Transition name="fade">
|
|
<p
|
|
v-if="message"
|
|
class="message"
|
|
>
|
|
{{ message }}
|
|
</p>
|
|
</Transition>
|
|
<div class="d-inline-block tagInput w-100">
|
|
<VueTagsInputCls
|
|
v-model="item"
|
|
:tags="selectedItem"
|
|
:autocomplete-items="filteredItems"
|
|
:autocomplete-min-length="0"
|
|
:max-tags="1"
|
|
:placeholder="placeholder"
|
|
@before-adding-tag="beforeTagSave"
|
|
@tags-changed="changedTags"
|
|
/>
|
|
</div>
|
|
<input
|
|
v-if="!selectedItem.length"
|
|
:name="name"
|
|
type="hidden"
|
|
value=""
|
|
>
|
|
<template
|
|
v-for="(sItem, index) in selectedItem"
|
|
:key="index"
|
|
>
|
|
<p class="text-danger m-0">
|
|
{{ parseValidationInput(`${name}`, allErrors) }}
|
|
</p>
|
|
<input
|
|
:name="`${name}`"
|
|
type="hidden"
|
|
:value="sItem.id"
|
|
>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import type VueTagsInput from "@sipec/vue3-tags-input";
|
|
import { VueTagsInput as VueTagsInputCls } from "@sipec/vue3-tags-input";
|
|
import axios from "axios";
|
|
import slugify from "slugify";
|
|
import { defineComponent } from "vue";
|
|
import parseLaravelErrorMessages from "./../modules/parseLaravelErrorMessages";
|
|
import route from "./../modules/route";
|
|
import trans from "./../modules/trans";
|
|
|
|
export default defineComponent({
|
|
name:"BelongsToDropdown",
|
|
mixins: [route, trans, parseLaravelErrorMessages],
|
|
components: {
|
|
VueTagsInputCls,
|
|
},
|
|
beforeMount() {
|
|
if (!this.$attrs.json) {
|
|
throw new Error("this component requires json");
|
|
}
|
|
},
|
|
mounted() {
|
|
const json = String(this.$attrs.json ?? "{}"),
|
|
data = JSON.parse(json);
|
|
this.name = data.name;
|
|
this.labelField = data.labelField;
|
|
this.label = data.label;
|
|
this.required = data.require;
|
|
this.allItems = (data.itemsList as CustomTag[]).map(item => {item.text = item[this.labelField] as string; return item;});
|
|
this.selectedItem = this.parseTags([data.value]);
|
|
this.intStoreRoute = data.storeRoute ?? null;
|
|
this.placeholder = data.placeholder;
|
|
},
|
|
data(): {
|
|
selectedItem: CustomTag[];
|
|
allItems: CustomTag[];
|
|
name: string;
|
|
item: string;
|
|
message: string | null;
|
|
intStoreRoute: string | null;
|
|
placeholder: string;
|
|
label: string;
|
|
required: boolean;
|
|
labelField: string;
|
|
} {
|
|
return {
|
|
allItems: [],
|
|
selectedItem: [],
|
|
item: "",
|
|
name: "",
|
|
message: null,
|
|
intStoreRoute: null,
|
|
placeholder: "",
|
|
label: "",
|
|
required: false,
|
|
labelField: "",
|
|
};
|
|
},
|
|
methods: {
|
|
parseTags(intSelectedItem: number[]): CustomTag[] {
|
|
const out: CustomTag[] = [];
|
|
if (intSelectedItem[0] !== null) {
|
|
intSelectedItem.forEach((intItem: number) => {
|
|
const selectedItem = this.allItems.find(item => String(item.id) === String(intItem));
|
|
if (!selectedItem) {
|
|
console.debug(intItem);
|
|
throw new Error(`Could not find enum ${intItem}`);
|
|
}
|
|
out.push({
|
|
id: String(selectedItem.id),
|
|
value: String(selectedItem.id),
|
|
text: String(selectedItem[`${this.labelField}`])
|
|
} as LaravelModel as CustomTag);
|
|
});
|
|
}
|
|
return out;
|
|
},
|
|
parseNewlyCreated(newModel: LaravelModel): CustomTag {
|
|
let newOut: CustomTag;
|
|
newOut = {
|
|
id: String(newModel.id),
|
|
value: String(newModel.id),
|
|
text: String(newModel[`${this.labelField}`])
|
|
} as LaravelModel as CustomTag;
|
|
return newOut;
|
|
},
|
|
/**
|
|
* Add item
|
|
* @param obj
|
|
*/
|
|
beforeTagSave(obj: VueTagsInput.IAddArgs): void {
|
|
if (this.selectedItem.length < 1) {
|
|
let customTag = this.tagExists(this.allItems, obj.tag);
|
|
if (customTag) {
|
|
obj.tag = customTag;
|
|
this.selectedItem.push(customTag);
|
|
this.item = "";
|
|
return;
|
|
} else if (this.intStoreRoute !== null) {
|
|
const storeRoute = route.methods.route(this.intStoreRoute);
|
|
if (!storeRoute) {
|
|
throw new Error("Undefined route " + this.intStoreRoute);
|
|
}
|
|
axios
|
|
.post(storeRoute, {
|
|
name: obj.tag.text
|
|
})
|
|
.then((response) => {
|
|
if (response.data.status === true) {
|
|
this.item = "";
|
|
const brandItem = this.parseNewlyCreated(response.data.brand);
|
|
this.allItems.push(brandItem);
|
|
this.selectedItem.push(brandItem);
|
|
}
|
|
})
|
|
.catch((error: any) => {
|
|
console.error(error);
|
|
});
|
|
}
|
|
} else {
|
|
this.message = this.__("Nombre d'élements maximal atteint.");
|
|
setTimeout(() => (this.message = null), 1500);
|
|
}
|
|
},
|
|
tagExists(
|
|
items: Array<CustomTag>,
|
|
tagToCheck: VueTagsInput.ITag
|
|
): CustomTag | null {
|
|
let customTag: CustomTag | null = null;
|
|
items.filter((item: CustomTag) => {
|
|
if (
|
|
item.id === (tagToCheck as CustomTag).id ||
|
|
item.slug === slugify(tagToCheck.text)
|
|
) {
|
|
customTag = item;
|
|
}
|
|
});
|
|
return customTag;
|
|
},
|
|
/**
|
|
* Delete item.
|
|
* @param newItems
|
|
*/
|
|
changedTags(newItems: Array<CustomTag>): void {
|
|
this.selectedItem = newItems as Array<CustomTag>;
|
|
},
|
|
},
|
|
computed: {
|
|
filteredItems() {
|
|
return this.allItems.filter((i) => {
|
|
return i.text.toLowerCase().indexOf(this.item.toLowerCase()) !== -1;
|
|
});
|
|
},
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.vue-tags-input {
|
|
max-width: 100% !important;
|
|
.ti- {
|
|
&autocomplete {
|
|
max-height: 10rem !important;
|
|
border-radius: 0.375rem;
|
|
box-shadow: 0 0 4px var(--bs-gray-400);
|
|
overflow-y: auto !important;
|
|
ul {
|
|
padding: 0.25rem 0 !important;
|
|
}
|
|
}
|
|
&input {
|
|
padding: 0.375rem !important;
|
|
border-radius: 0.375rem;
|
|
}
|
|
&tags {
|
|
line-height: inherit !important;
|
|
}
|
|
&tag,
|
|
&new-tag-input-wrapper {
|
|
padding: 0.1rem !important;
|
|
margin: 0.1rem !important;
|
|
}
|
|
&new-tag-input-wrapper {
|
|
font-size: 1em !important;
|
|
input {
|
|
min-width: 165px !important;
|
|
}
|
|
}
|
|
&tag {
|
|
padding: 0.1rem 0.3rem !important;
|
|
background-color: var(--bs-blue) !important;
|
|
border-radius: 0.375rem !important;
|
|
}
|
|
&selected-item {
|
|
background-color: var(--bs-gray-300) !important;
|
|
color: var(--bs-gray-900) !important;
|
|
}
|
|
}
|
|
|
|
.message {
|
|
color: #ff0000;
|
|
}
|
|
.fade-enter-active,
|
|
.fade-leave-active {
|
|
transition: opacity 0.5s ease;
|
|
}
|
|
|
|
.fade-enter-from,
|
|
.fade-leave-to {
|
|
opacity: 0;
|
|
}
|
|
}
|
|
</style>
|