| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 | <template>	<div class="control">		<label for="name">{{ name }}</label>		<button v-if="entries.length === 0 && !readonly" @click="addEntry()" type="button">+</button>		<div class="control-row" v-for="(entry, entryIndex) in entries">			<div class="control-col" v-for="(fieldType, fieldIndex) in fieldTypes" :class="{ 'fill-remaining': fieldType.fill }">				<input :ref="`input:${entryIndex}:${fieldType.fieldTypeId}`" :disabled="readonly" name="name" type="text" v-if="fieldType.type === 'text'" v-model="entry[fieldType.fieldTypeId]" @change="onInputChange()" v-on:blur="blurInput(entryIndex, fieldType.fieldTypeId)" v-on:focus="focusInput(entryIndex, fieldType.fieldTypeId)" v-on:keydown="keydownInput(entryIndex, fieldType.fieldTypeId)" @keyup.ctrl.exact.59="fillInput(entryIndex, fieldType.fieldTypeId, 'date')" @keyup.ctrl.shift.exact.59="fillInput(entryIndex, fieldType.fieldTypeId, 'time')" />				<select name="name" v-if="fieldType.type === 'select'" :disabled="readonly" class="fill-remaining" v-model="entry[fieldType.fieldTypeId]" @change="onSelectChange()">					<option v-for="option in fieldType.options" :value="option.value">{{option.text}}</option>				</select>				<div tabindex="0" v-on:keyup.enter="toggleCheckbox(entryIndex, fieldType.fieldTypeId)" v-on:keyup.space="toggleCheckbox(entryIndex, fieldType.fieldTypeId)" name="name" class="checkbox" v-if="fieldType.type === 'checkbox'" :class="{ checked: entry[fieldType.fieldTypeId], disabled: readonly }" @click="toggleCheckbox(entryIndex, fieldType.fieldTypeId)"></div>				<button v-if="fieldType.extraButtons && !readonly" v-for="buttonInfo in fieldType.extraButtons" type="button" :class="[buttonInfo.style]">{{buttonInfo.icon}}</button>				<button v-if="entryIndex + 1 === entries.length && entryIndex + 1 < maxEntries && fieldIndex + 1 === fieldTypes.length && !readonly" @click="addEntry()" type="button">+</button>				<button v-if="entries.length > minEntries && fieldIndex + 1 === fieldTypes.length && !readonly" @click="removeEntry(entryIndex)" type="button">-</button>				<div v-if="fieldType.autosuggestGroup && (focusedInput === `${entryIndex}.${fieldType.fieldTypeId}` || autosuggestHover === `${entryIndex}.${fieldType.fieldTypeId}`)" class="autosuggest-container" @mouseover="focusAutosuggestContainer(entryIndex, fieldType.fieldTypeId)" @mouseleave="blurAutosuggestContainer()">					<div v-for="autosuggestItem in filter(autosuggest[fieldType.autosuggestGroup], entry[fieldType.fieldTypeId])" class="autosuggest-item" @click="selectAutosuggest(entryIndex, fieldType.fieldTypeId, autosuggestItem)">						{{ autosuggestItem }}					</div>				</div>			</div>		</div>			</div></template><script>function Field() {}export default {	data: function() {		return {			entries: [...this.initialEntries],			focusedInput: "",			activeAutosuggestHover: "",			autosuggestHover: ""		};	},	props: {		name: String,		fieldTypes: Array,		minEntries: Number,		maxEntries: Number,		initialEntries: Array,		autosuggest: Object,		onChange: Function,		readonly: Boolean	},	mounted() {			},	methods: {		addEntry() {			let emptyEntry = {};			this.fieldTypes.forEach((fieldType) => {				if (fieldType.type === "text" || fieldType.type === "select") emptyEntry[fieldType.fieldTypeId] = "";				else if (fieldType.type === "checkbox") emptyEntry[fieldType.fieldTypeId] = false;			});			this.entries.push(emptyEntry);			this.onChange();		},		removeEntry(index) {			this.entries.splice(index, 1);			this.onChange();		},		toggleCheckbox(entryIndex, fieldTypeId) {			if (this.readonly) return;			this.entries[entryIndex][fieldTypeId] = !this.entries[entryIndex][fieldTypeId];			this.onChange();		},		selectAutosuggest(entryIndex, fieldTypeId, autosuggestItem) {			this.entries[entryIndex][fieldTypeId] = autosuggestItem;		},		blurInput(entryIndex, fieldTypeId) {			this.focusedInput = "";		},		focusInput(entryIndex, fieldTypeId) {			this.focusedInput = `${entryIndex}.${fieldTypeId}`;		},		keydownInput(entryIndex, fieldTypeId) {		},		focusAutosuggestContainer(entryIndex, fieldTypeId) {			this.autosuggestHover = `${entryIndex}.${fieldTypeId}`;		},		blurAutosuggestContainer() {			this.autosuggestHover = "";		},		onSelectChange() {			this.onChange();		},		onInputChange() {			this.onChange();		},		filter(autosuggest, value) {			return autosuggest.filter(autosuggestItem => autosuggestItem.toLowerCase().startsWith(value.toLowerCase())).sort();		},		fillInput(entryIndex, fieldTypeId, fillType) {			let input = this.$refs[`input:${entryIndex}:${fieldTypeId}`][0];			const cursorPoint = input.selectionStart;			const currentValue = this.entries[entryIndex][fieldTypeId];			const firstPart = currentValue.substring(0, cursorPoint);			const lastPart = currentValue.substring(cursorPoint, currentValue.length);			let fillValue = "";			const currentDate = new Date();			switch(fillType) {				case "date":					const year = currentDate.getFullYear();					const month = `0${(currentDate.getMonth() + 1)}`.slice(-2);					const day = `0${currentDate.getDate()}`.slice(-2);					fillValue = `${year}-${month}-${day}`;					break;				case "time":					const hour = `0${currentDate.getHours()}`.slice(-2);					const minute = `0${currentDate.getMinutes()}`.slice(-2);					fillValue = `${hour}:${minute}`;					break;			}			const newValue = `${firstPart}${fillValue}${lastPart}`;			this.entries[entryIndex][fieldTypeId] = newValue;			const newCaretPosition = firstPart.length + fillValue.length;			this.$nextTick(() => {				input.focus();				input.setSelectionRange(newCaretPosition, newCaretPosition);			});		}	}};</script><style lang="scss" scoped>.control {	width: 100%;	margin-bottom: 16px;	label {		display: block;		margin-bottom: 4px;	}	.control-row {		height: 32px;		display: flex;		grid-column-gap: 12px;	}	.control-row:not(:last-of-type) {		margin-bottom: 8px;	}	.control-col {		display: flex;		position: relative;		button:last-of-type {			border-radius: 0 5px 5px 0;		}		> input:not(:last-child) {			border-right: none;			border-top-right-radius: 0;			border-bottom-right-radius: 0;		}		.autosuggest-container {			position: absolute;			top: 31px;			width: 100%;			border-radius: 5px 5px 5px 5px;			border: solid .5px #464646;			z-index: 10;			.autosuggest-item {				padding: 8px;				cursor: pointer;				background-color: white;								&:hover, &:focus {					background-color: lightgray;				}			}		}	}	input, select {		border: solid .5px #464646;		border-radius: 5px;		padding: 4px;	}	input {		width: 100%;	}	button {		width: 32px;		height: 32px;		border: none;		font-size: 24px;		background-color: green;		color: white;		cursor: pointer;	}	button:hover, button:focus {		background-color: darkgreen;	}}.fill-remaining {	width: 100%;	width: -moz-available;          /* WebKit-based browsers will ignore this. */    width: -webkit-fill-available;  /* Mozilla-based browsers will ignore this. */    width: fill-available;}.checkbox {	height: 32px;	width: 32px;	background-color: white;	border: .5px #464646 solid;	border-radius: 3px;	cursor: pointer;	position: relative;	box-sizing: border-box;	&.disabled {		cursor:auto;	}}.checkbox.checked::after {	content: "";	width: 26px;	height: 26px;	left: 2px;	top: 2px;	display: inline-block;	position: absolute;	border-radius: 3px;	background-color: #69B862;}</style>
 |