mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-08-05 12:07:22 -04:00
136 lines
4.5 KiB
Vue
136 lines
4.5 KiB
Vue
<template>
|
|
<draggable
|
|
ref="draggable"
|
|
v-model="iVal"
|
|
tag="ul"
|
|
handle=".handle"
|
|
ghost-class="ghost"
|
|
class="list-unstyled"
|
|
item-key="index"
|
|
:group="group"
|
|
>
|
|
<template #item="{ element: { value, index } }">
|
|
<li>
|
|
<div>
|
|
<div class="input-group input-group-sm mb-1">
|
|
<button
|
|
v-if="!disableSorting"
|
|
:class="['btn', 'btn-light border', readonly ? '' : 'handle']"
|
|
type="button"
|
|
:aria-label="$t('table.sort')"
|
|
:disabled="readonly"
|
|
>
|
|
<Icon v="bars" />
|
|
</button>
|
|
<slot :val="value" :update="curry(update)(index)" :i="index">
|
|
<input
|
|
:value="value"
|
|
type="text"
|
|
class="form-control"
|
|
required
|
|
:readonly="readonly"
|
|
:maxlength="maxlength"
|
|
@input="(event) => update(index, event.target.value)"
|
|
>
|
|
</slot>
|
|
<button
|
|
v-if="index >= minitems"
|
|
:class="['btn', readonly ? 'btn-light border' : 'btn-outline-danger']"
|
|
type="button"
|
|
:aria-label="$t('crud.remove')"
|
|
:disabled="readonly"
|
|
@click.prevent="remove(index)"
|
|
>
|
|
<Icon v="times" />
|
|
</button>
|
|
</div>
|
|
<slot name="validation" :val="value" :i="index"></slot>
|
|
</div>
|
|
</li>
|
|
</template>
|
|
<template #footer>
|
|
<li>
|
|
<button
|
|
v-if="!readonly && (maxitems === null || iVal.length < maxitems)"
|
|
class="btn btn-outline-success w-100 btn-sm"
|
|
type="button"
|
|
:aria-label="$t('crud.add')"
|
|
@click.prevent="add"
|
|
>
|
|
<Icon v="plus" />
|
|
</button>
|
|
</li>
|
|
</template>
|
|
<li v-if="maxitems && iVal.length > maxitems" class="alert alert-danger">
|
|
<Icon v="exclamation-triangle" />
|
|
<T :params="{ maxlength: maxitems }" class="ml-1">crud.validation.listMaxLength</T>
|
|
</li>
|
|
</draggable>
|
|
</template>
|
|
|
|
<script>
|
|
import draggable from 'vuedraggable';
|
|
|
|
import { curry } from '../src/helpers.ts';
|
|
|
|
export default {
|
|
components: {
|
|
draggable,
|
|
},
|
|
props: {
|
|
modelValue: {},
|
|
prototype: { default: '', type: [String, Object] },
|
|
group: {},
|
|
readonly: { type: Boolean },
|
|
maxlength: { default: 32, type: Number },
|
|
minitems: { default: 0, type: Number },
|
|
maxitems: { default: null, type: Number },
|
|
disableSorting: { type: Boolean },
|
|
},
|
|
emits: ['update:modelValue'],
|
|
data() {
|
|
return {
|
|
curry,
|
|
};
|
|
},
|
|
computed: {
|
|
iVal: {
|
|
get() {
|
|
return this.modelValue.map((value, index) => ({ value, index }));
|
|
},
|
|
set(iVal) {
|
|
this.$emit('update:modelValue', iVal.map(({ value }) => value));
|
|
},
|
|
},
|
|
},
|
|
methods: {
|
|
remove(i) {
|
|
const v = [...this.modelValue];
|
|
v.splice(i, 1);
|
|
this.$emit('update:modelValue', v);
|
|
},
|
|
add() {
|
|
// create a deep copy of the prototype
|
|
const addedItem = JSON.parse(JSON.stringify(this.prototype));
|
|
this.$emit('update:modelValue', [...this.modelValue, addedItem]);
|
|
this.$nextTick((_) => {
|
|
const items = this.$refs.draggable.$el.querySelectorAll('[data-draggable]');
|
|
items[items.length - 1].querySelector('input,textarea,select').focus();
|
|
});
|
|
},
|
|
update(i, val) {
|
|
const v = [...this.modelValue];
|
|
v[i] = val;
|
|
this.$emit('update:modelValue', v);
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.ghost {
|
|
opacity: 0.5;
|
|
background: #c8ebfb;
|
|
}
|
|
</style>
|