first commit + add fe component vue accone
This commit is contained in:
218
common/MoneyInput.vue
Normal file
218
common/MoneyInput.vue
Normal file
@@ -0,0 +1,218 @@
|
||||
<template>
|
||||
<div class="money-input-container">
|
||||
<input
|
||||
ref="input"
|
||||
:value="displayValue"
|
||||
@input="handleInput"
|
||||
@keydown="handleKeydown"
|
||||
@blur="handleBlur"
|
||||
v-bind="$attrs"
|
||||
v-on="inputListeners"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MoneyInput',
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localValue: '',
|
||||
hasComma: false,
|
||||
rawDisplayValue: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// Format untuk tampilan
|
||||
displayValue() {
|
||||
// Jika pengguna baru mengetik, gunakan rawDisplayValue
|
||||
if (this.rawDisplayValue) {
|
||||
return this.rawDisplayValue;
|
||||
}
|
||||
|
||||
// Format dari nilai model
|
||||
return this.formatValue(this.localValue);
|
||||
},
|
||||
|
||||
// Event listeners yang diteruskan
|
||||
inputListeners() {
|
||||
const listeners = { ...this.$listeners };
|
||||
|
||||
// Hapus event handlers yang dikelola sendiri
|
||||
delete listeners.input;
|
||||
delete listeners.keydown;
|
||||
delete listeners.blur;
|
||||
|
||||
return listeners;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// Sinkronkan props dengan data lokal
|
||||
value: {
|
||||
immediate: true,
|
||||
handler(newValue) {
|
||||
// Hindari loop jika nilai sama
|
||||
if (newValue !== this.localValue) {
|
||||
this.localValue = newValue;
|
||||
this.rawDisplayValue = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// Format nilai untuk tampilan (1234.56 -> 1.234,56)
|
||||
formatValue(value) {
|
||||
if (!value && value !== 0) return '';
|
||||
|
||||
// Convert ke string
|
||||
const strValue = value.toString();
|
||||
|
||||
// Pisahkan bagian integer dan desimal
|
||||
const parts = strValue.split('.');
|
||||
const integerPart = parts[0];
|
||||
const decimalPart = parts.length > 1 ? parts[1] : '';
|
||||
|
||||
// Format bagian integer dengan titik ribuan
|
||||
const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
||||
|
||||
// Gabungkan dengan format display
|
||||
return decimalPart ? `${formattedInteger},${decimalPart}` : formattedInteger;
|
||||
},
|
||||
|
||||
// Parse dari format display ke nilai model (1.234,56 -> 1234.56)
|
||||
parseValue(displayValue) {
|
||||
if (!displayValue) return '';
|
||||
|
||||
// Pisahkan bagian integer dan desimal
|
||||
const parts = displayValue.split(',');
|
||||
const integerPart = parts[0].replace(/\./g, ''); // Hapus titik ribuan
|
||||
const decimalPart = parts.length > 1 ? parts[1] : '';
|
||||
|
||||
// Gabungkan dengan format model (titik untuk desimal)
|
||||
return decimalPart ? `${integerPart}.${decimalPart}` : integerPart;
|
||||
},
|
||||
|
||||
// Tangani event input
|
||||
handleInput(event) {
|
||||
const input = event.target;
|
||||
const cursorPos = input.selectionStart;
|
||||
const rawValue = input.value;
|
||||
|
||||
// Cek apakah ada koma
|
||||
this.hasComma = rawValue.includes(',');
|
||||
|
||||
// Pisahkan bagian integer dan desimal
|
||||
let integerPart, decimalPart;
|
||||
|
||||
if (this.hasComma) {
|
||||
const parts = rawValue.split(',');
|
||||
integerPart = parts[0];
|
||||
decimalPart = parts.length > 1 ? parts[1] : '';
|
||||
} else {
|
||||
integerPart = rawValue;
|
||||
decimalPart = '';
|
||||
}
|
||||
|
||||
// Format bagian integer (hapus titik lama, tambahkan titik baru)
|
||||
const cleanInteger = integerPart.replace(/\./g, '');
|
||||
const formattedInteger = cleanInteger.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
||||
|
||||
// Buat nilai tampilan baru
|
||||
this.rawDisplayValue = this.hasComma ? `${formattedInteger},${decimalPart}` : formattedInteger;
|
||||
|
||||
// Update model value (tanpa formatter)
|
||||
const modelValue = this.hasComma ? `${cleanInteger}.${decimalPart}` : cleanInteger;
|
||||
this.localValue = modelValue;
|
||||
|
||||
// Emit event
|
||||
this.$emit('input', modelValue);
|
||||
|
||||
// Set cursor position setelah DOM diupdate
|
||||
this.$nextTick(() => {
|
||||
// Posisi kursor relatif
|
||||
if (this.hasComma) {
|
||||
const commaPos = rawValue.indexOf(',');
|
||||
|
||||
if (cursorPos <= commaPos) {
|
||||
// Hitung pergeseran karena dot formatting
|
||||
const oldDotsBeforeCursor = (integerPart.substring(0, cursorPos).match(/\./g) || []).length;
|
||||
const newDotsBeforeCursor = (formattedInteger.substring(0, cursorPos).match(/\./g) || []).length;
|
||||
const offset = newDotsBeforeCursor - oldDotsBeforeCursor;
|
||||
input.setSelectionRange(cursorPos + offset, cursorPos + offset);
|
||||
} else {
|
||||
// Setelah koma, sesuaikan dengan posisi koma baru
|
||||
const newCommaPos = formattedInteger.length;
|
||||
const offsetAfterComma = cursorPos - commaPos;
|
||||
input.setSelectionRange(newCommaPos + offsetAfterComma, newCommaPos + offsetAfterComma);
|
||||
}
|
||||
} else {
|
||||
// Format tanpa koma
|
||||
const oldDots = (rawValue.substring(0, cursorPos).match(/\./g) || []).length;
|
||||
const newDots = (this.rawDisplayValue.substring(0, cursorPos).match(/\./g) || []).length;
|
||||
input.setSelectionRange(cursorPos + (newDots - oldDots), cursorPos + (newDots - oldDots));
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// Tangani tombol koma dan titik
|
||||
handleKeydown(event) {
|
||||
// Khusus untuk tombol koma dan titik
|
||||
if (event.key === ',' || event.key === '.') {
|
||||
// Jika sudah ada koma, cegah input
|
||||
if (this.rawDisplayValue.includes(',')) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
// Ambil nilai dan posisi kursor saat ini
|
||||
const input = event.target;
|
||||
const cursorPos = input.selectionStart;
|
||||
const currentValue = this.rawDisplayValue || this.displayValue;
|
||||
|
||||
// Sisipkan koma
|
||||
const beforeComma = currentValue.substring(0, cursorPos);
|
||||
const afterComma = currentValue.substring(cursorPos);
|
||||
const newDisplayValue = beforeComma + ',' + afterComma;
|
||||
|
||||
// Set nilai display
|
||||
this.rawDisplayValue = newDisplayValue;
|
||||
|
||||
// Update model value
|
||||
const parts = newDisplayValue.split(',');
|
||||
const integerPart = parts[0].replace(/\./g, '');
|
||||
const decimalPart = parts.length > 1 ? parts[1] : '';
|
||||
const modelValue = integerPart + (decimalPart ? '.' + decimalPart : '');
|
||||
|
||||
this.localValue = modelValue;
|
||||
this.hasComma = true;
|
||||
|
||||
// Emit event
|
||||
this.$emit('input', modelValue);
|
||||
|
||||
// Set kursor setelah koma
|
||||
this.$nextTick(() => {
|
||||
input.setSelectionRange(cursorPos + 1, cursorPos + 1);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Format ulang pada blur
|
||||
handleBlur() {
|
||||
// Reset raw display value
|
||||
this.rawDisplayValue = '';
|
||||
|
||||
// Emit event
|
||||
this.$emit('blur');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user