371 lines
10 KiB
Vue
371 lines
10 KiB
Vue
<template>
|
|
<div :class="`list-item-component list-item-${inputName}`">
|
|
<table
|
|
v-if="itemList.length > 0"
|
|
class="table text-center table-sm table-hover table-bordered m-0"
|
|
>
|
|
<thead>
|
|
<tr>
|
|
<th
|
|
v-if="displayHeader"
|
|
scope="col"
|
|
class="table-action"
|
|
>
|
|
<button
|
|
v-if="hasHeader"
|
|
@click.prevent="removeAllHeaders()"
|
|
class="btn btn-sm btn-danger"
|
|
:title="__('Remove all headers')"
|
|
data-bs-tooltip="tooltip"
|
|
>
|
|
<FontAwesomeIcon icon="fa-solid fa-minus" />
|
|
</button>
|
|
</th>
|
|
<th
|
|
v-if="displayHeader && displayHeaders.includes(true)"
|
|
class="col-6 align-middle"
|
|
>
|
|
{{ __("Header") }}
|
|
({{ countHeaders }})
|
|
</th>
|
|
<th
|
|
v-for="(column, columnIndex) of itemColumns"
|
|
:key="columnIndex"
|
|
class="align-middle"
|
|
:class="displayHeaders.includes(true) ? 'col-6' : ''"
|
|
scope="col"
|
|
>
|
|
{{ column[1] }}
|
|
({{ itemList.length }})
|
|
</th>
|
|
<th
|
|
scope="col"
|
|
v-if="displayBtnRemove"
|
|
class="table-action"
|
|
>
|
|
<button
|
|
v-if="itemList.length > 1"
|
|
@click.prevent="removeAllLines()"
|
|
class="btn btn-sm btn-danger"
|
|
:title="__('Remove all lines')"
|
|
data-bs-tooltip="tooltip"
|
|
>
|
|
<FontAwesomeIcon icon="fa-solid fa-trash" />
|
|
</button>
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="table-group-divider">
|
|
<tr
|
|
v-for="(item, itemIndex) of itemList"
|
|
:key="itemIndex"
|
|
:class="displayHeaders[itemIndex] ? 'header' : ''"
|
|
>
|
|
<td
|
|
v-if="displayHeader"
|
|
class="align-bottom"
|
|
scope="row"
|
|
>
|
|
<button
|
|
v-if="!displayHeaders[itemIndex]"
|
|
@click.prevent="updateStatusHeader(itemIndex)"
|
|
class="btn btn-sm btn-primary"
|
|
:title="__('Add a header')"
|
|
data-bs-tooltip="tooltip"
|
|
>
|
|
<FontAwesomeIcon icon="fa-solid fa-heading" />
|
|
</button>
|
|
<button
|
|
v-if="displayHeaders[itemIndex]"
|
|
@click.prevent="updateStatusHeader(itemIndex)"
|
|
class="btn btn-sm btn-danger"
|
|
:title="__('Remove this header')"
|
|
data-bs-tooltip="tooltip"
|
|
>
|
|
<FontAwesomeIcon icon="fa-solid fa-minus" />
|
|
</button>
|
|
</td>
|
|
<td
|
|
v-if="displayHeaders.includes(true)"
|
|
class="align-bottom"
|
|
scope="row"
|
|
>
|
|
<div
|
|
v-if="displayHeaders[itemIndex]"
|
|
class="d-flex align-items-center border-0"
|
|
>
|
|
<input
|
|
class="form-control form-control-sm"
|
|
:name="`${inputName}[${String(itemIndex)}][header]`"
|
|
type="text"
|
|
:placeholder="__('Recipe of chocolate cookies')"
|
|
v-model="item['header']"
|
|
>
|
|
<FontAwesomeIcon
|
|
icon="fa-solid fa-angles-right"
|
|
class="mx-2"
|
|
/>
|
|
</div>
|
|
</td>
|
|
<td
|
|
class="align-bottom"
|
|
v-for="(column, columnIndex) of itemColumns"
|
|
:key="columnIndex"
|
|
scope="row"
|
|
>
|
|
<p class="text-danger m-0">
|
|
{{
|
|
parseValidationInput(
|
|
`${inputName}.${String(itemIndex)}.${column[0]}`,
|
|
allErrors
|
|
)
|
|
}}
|
|
</p>
|
|
<input
|
|
class="form-control form-control-sm"
|
|
:name="`${inputName}[${String(itemIndex)}][${column[0]}]`"
|
|
:type="itemType[columnIndex]"
|
|
v-model="item[column[0]]"
|
|
:placeholder="inputPlaceholder[columnIndex]"
|
|
:min="itemType[columnIndex] == 'number' ? '0' : undefined"
|
|
>
|
|
</td>
|
|
<td
|
|
v-if="displayBtnRemove"
|
|
class="align-bottom"
|
|
scope="row"
|
|
>
|
|
<button
|
|
@click.prevent="removeLine(itemIndex)"
|
|
class="btn btn-sm btn-danger"
|
|
:title="__('Remove this line')"
|
|
data-bs-tooltip="tooltip"
|
|
>
|
|
<FontAwesomeIcon icon="fa-solid fa-trash" />
|
|
</button>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
<div v-else>
|
|
<p>{{ __("No item(s) filled in.") }}</p>
|
|
<input
|
|
class="form-control form-control-sm"
|
|
:name="`${inputName}`"
|
|
type="hidden"
|
|
value=""
|
|
>
|
|
</div>
|
|
<div
|
|
v-if="displayBtnAdd"
|
|
class="text-center mt-1"
|
|
>
|
|
<button
|
|
@click.prevent="addLine"
|
|
class="btn btn-sm btn-primary"
|
|
type="button"
|
|
:title="__('Add a line')"
|
|
data-bs-tooltip="tooltip"
|
|
>
|
|
<FontAwesomeIcon icon="fa-solid fa-plus" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { defineComponent } from "vue";
|
|
import { Tooltip } from "bootstrap";
|
|
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
|
|
import trans from "./../../modules/trans";
|
|
import parseLaravelErrorMessages from "./../../modules/parseLaravelErrorMessages";
|
|
|
|
export default defineComponent({
|
|
name: "ListItemComponent",
|
|
components: {
|
|
FontAwesomeIcon,
|
|
},
|
|
mixins: [trans, parseLaravelErrorMessages],
|
|
data(): {
|
|
itemList: Array<Record<string,string>>;
|
|
itemColumns: string[][];
|
|
itemLimit: number[];
|
|
inputName: string;
|
|
itemType: string[];
|
|
inputPlaceholder: string[];
|
|
min: number;
|
|
max: number | null;
|
|
allErrors: Record<string, string[]>;
|
|
displayBtnAdd: boolean;
|
|
displayBtnRemove: boolean;
|
|
displayHeader: boolean;
|
|
displayHeaders: Array<boolean>;
|
|
tooltipList: HTMLButtonElement[];
|
|
} {
|
|
return {
|
|
itemList: [],
|
|
itemColumns: [[""]],
|
|
itemLimit: [0, 0],
|
|
inputName: "",
|
|
itemType: [""],
|
|
inputPlaceholder: [""],
|
|
min: 0,
|
|
max: null,
|
|
allErrors: {},
|
|
displayBtnAdd: true,
|
|
displayBtnRemove: true,
|
|
displayHeader: false,
|
|
displayHeaders: [],
|
|
tooltipList: [],
|
|
};
|
|
},
|
|
computed : {
|
|
hasHeader() {
|
|
return this.displayHeaders.filter((header : boolean) => {
|
|
return header;
|
|
}).length > 1 && this.itemList.length > 1;
|
|
},
|
|
countHeaders() {
|
|
return this.displayHeaders.filter((header: boolean) => {
|
|
return header;
|
|
}).length;
|
|
}
|
|
},
|
|
mounted() {
|
|
const json = String(this.$attrs.json ?? "{}");
|
|
const data = JSON.parse(json);
|
|
this.itemList = data.itemList;
|
|
this.itemColumns = data.itemColumns;
|
|
this.itemType = data.itemType;
|
|
this.itemLimit = data.itemLimit;
|
|
this.inputName = data.inputName;
|
|
this.inputPlaceholder = data.inputPlaceholder;
|
|
this.displayHeader = data.header;
|
|
// * Min and max calc.
|
|
this.min = Number.parseInt((data?.itemLimit ?? [])[0] ?? 0);
|
|
this.min = this.min < 0 ? 0 : this.min;
|
|
this.max = Number.parseInt((data?.itemLimit ?? [])[1] ?? 0);
|
|
this.max = this.max <= this.min ? null : this.max;
|
|
this.allErrors = data.errors ?? {};
|
|
this.setDefaultLines();
|
|
this.updateComponent();
|
|
},
|
|
methods: {
|
|
/**
|
|
* Set default line(s)
|
|
* according to the minimum limit defined.
|
|
*/
|
|
setDefaultLines() {
|
|
if (this.itemList.length < 1) {
|
|
for (let index = 0; index < this.min; index++) {
|
|
this.addLine();
|
|
}
|
|
} else {
|
|
this.itemList.forEach((item: { header?: string }, key) => {
|
|
if (item.header) {
|
|
this.displayHeaders[key] = true;
|
|
}
|
|
});
|
|
}
|
|
},
|
|
/**
|
|
* Add a new line to the <table>,
|
|
* add a new element to the array,
|
|
* show remove line button.
|
|
*/
|
|
addLine() {
|
|
this.itemList.push({});
|
|
this.displayHeaders.push(false);
|
|
this.displayBtnRemove == false ? (this.displayBtnRemove = true) : "";
|
|
this.updateComponent();
|
|
},
|
|
removeAllHeaders() {
|
|
this.displayHeaders = [];
|
|
this.updateComponent();
|
|
},
|
|
updateStatusHeader(itemIndex: number) {
|
|
this.displayHeaders[itemIndex] = this.displayHeaders[itemIndex]
|
|
? false
|
|
: true;
|
|
this.updateComponent();
|
|
},
|
|
updateComponent() {
|
|
this.checkLengthList();
|
|
this.$nextTick(() => {
|
|
this.updateBootstrapTooltip();
|
|
});
|
|
},
|
|
removeAllLines() {
|
|
this.itemList.length = 0;
|
|
this.removeAllHeaders();
|
|
this.addLine();
|
|
this.updateComponent();
|
|
},
|
|
/**
|
|
* Remove a specific line from the <table>,
|
|
* remove a specific element to the array,
|
|
* show adding line button.
|
|
*/
|
|
removeLine(itemIndex: number) {
|
|
this.itemList.splice(itemIndex, 1);
|
|
this.displayHeaders.splice(itemIndex, 1);
|
|
!this.displayBtnAdd ? (this.displayBtnAdd = true) : "";
|
|
this.itemList.length <= 0 ? (this.itemList = []) : "";
|
|
this.updateComponent();
|
|
},
|
|
/**
|
|
* Check the length of the list,
|
|
* update variables accordingly.
|
|
*/
|
|
checkLengthList() {
|
|
/** Length minimum */
|
|
if (this.itemList.length <= this.min) {
|
|
this.displayBtnRemove = false;
|
|
}
|
|
/** Length maximum */
|
|
if (this.max !== null && this.itemList.length >= this.max) {
|
|
this.displayBtnAdd = false;
|
|
}
|
|
},
|
|
/**
|
|
* Update Bootstrap tooltips.
|
|
*/
|
|
updateBootstrapTooltip() {
|
|
let newTooltipList = [].slice.call(
|
|
document.querySelectorAll(
|
|
".list-item-" + this.inputName + " [data-bs-tooltip=\"tooltip\"]"
|
|
)
|
|
) as HTMLButtonElement[];
|
|
let tmp = newTooltipList.filter((x) => !this.tooltipList.includes(x));
|
|
tmp.map((tooltip) => {
|
|
return new Tooltip(tooltip);
|
|
});
|
|
this.closeBootstrapTooltip();
|
|
this.tooltipList = newTooltipList;
|
|
},
|
|
/**
|
|
* Close all Bootstrap tooltips.
|
|
*/
|
|
closeBootstrapTooltip() {
|
|
this.tooltipList.forEach((tooltip) => {
|
|
tooltip.blur();
|
|
Tooltip.getInstance(tooltip)?.hide();
|
|
});
|
|
},
|
|
},
|
|
});
|
|
</script>
|
|
|
|
<style lang="scss">
|
|
.list-item-component {
|
|
.table {
|
|
table-layout: fixed;
|
|
&-action {
|
|
width: 2.5rem;
|
|
}
|
|
}
|
|
.header {
|
|
border-top: 2px solid black;
|
|
}
|
|
}
|
|
</style>
|