ldc-lionor/resources/ts/bo/components/ListItemComponent.vue
2023-06-23 10:12:50 +00:00

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>