mirror of
https://gitlab.com/PronounsPage/PronounsPage.git
synced 2025-09-24 05:05:20 -04:00
119 lines
3.6 KiB
Vue
119 lines
3.6 KiB
Vue
<template>
|
|
<draggable
|
|
v-model="iVal"
|
|
tag="ul"
|
|
handle=".handle"
|
|
ghost-class="ghost"
|
|
class="list-unstyled"
|
|
:group="group"
|
|
@end="$emit('input', iVal)"
|
|
@add="$emit('input', iVal)"
|
|
>
|
|
<li v-for="(v, i) in iVal" ref="items">
|
|
<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="iVal[i]" :update="curry(update)(i)" :i="i">
|
|
<input
|
|
v-model="iVal[i]"
|
|
type="text"
|
|
class="form-control"
|
|
required
|
|
:readonly="readonly"
|
|
:maxlength="maxlength"
|
|
>
|
|
</slot>
|
|
<button
|
|
v-if="i >= minitems"
|
|
:class="['btn', readonly ? 'btn-light border' : 'btn-outline-danger']"
|
|
type="button"
|
|
:aria-label="$t('crud.remove')"
|
|
:disabled="readonly"
|
|
@click.prevent="remove(i)"
|
|
>
|
|
<Icon v="times" />
|
|
</button>
|
|
</div>
|
|
<slot name="validation" :val="iVal[i]" :i="i"></slot>
|
|
</div>
|
|
</li>
|
|
|
|
<li slot="footer">
|
|
<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>
|
|
<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 { curry } from '../src/helpers.ts';
|
|
import draggable from 'vuedraggable';
|
|
|
|
export default {
|
|
components: {
|
|
draggable,
|
|
},
|
|
props: {
|
|
value: {},
|
|
prototype: { default: '' },
|
|
group: {},
|
|
readonly: { type: Boolean },
|
|
maxlength: { default: 32, type: Number },
|
|
minitems: { default: 0, type: Number },
|
|
maxitems: { default: null, type: Number },
|
|
disableSorting: { type: Boolean },
|
|
},
|
|
data() {
|
|
return {
|
|
iVal: this.value,
|
|
curry,
|
|
};
|
|
},
|
|
watch: {
|
|
value() {
|
|
this.iVal = this.value;
|
|
},
|
|
},
|
|
methods: {
|
|
remove(i) {
|
|
const v = [...this.value];
|
|
v.splice(i, 1);
|
|
this.$emit('input', v);
|
|
},
|
|
add() {
|
|
// create a deep copy of the prototype
|
|
const addedItem = JSON.parse(JSON.stringify(this.prototype));
|
|
this.$emit('input', [...this.value, addedItem]);
|
|
this.$nextTick((_) => {
|
|
this.$refs.items[this.value.length - 1].querySelector('input,textarea,select').focus();
|
|
});
|
|
},
|
|
update(i, val) {
|
|
const v = [...this.value];
|
|
v[i] = val;
|
|
this.$emit('input', v);
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.ghost {
|
|
opacity: 0.5;
|
|
background: #c8ebfb;
|
|
}
|
|
</style>
|