474 lines
19 KiB
JavaScript
474 lines
19 KiB
JavaScript
(function() {
|
|
// Definisi directive
|
|
var moneyFormat = {
|
|
bind: function(el, binding, vnode) {
|
|
var inputElement = el.tagName === 'INPUT' ? el : el.querySelector('input');
|
|
if (!inputElement) return;
|
|
|
|
var vm = vnode.context;
|
|
var modelName = binding.expression;
|
|
|
|
// Tambahkan state untuk input
|
|
el._moneyFormat = {
|
|
hasDecimal: false, // Flag apakah ada koma desimal
|
|
decimalPart: '', // Bagian desimal (setelah koma)
|
|
oldValue: '', // Nilai input sebelumnya
|
|
oldCursorPosition: 0 // Posisi kursor sebelumnya
|
|
};
|
|
|
|
// Format nilai awal
|
|
if (vm[modelName]) {
|
|
var formattedValue = formatNumber(vm[modelName]);
|
|
inputElement.value = formattedValue;
|
|
|
|
// Set flags
|
|
el._moneyFormat.hasDecimal = formattedValue.includes(',');
|
|
if (el._moneyFormat.hasDecimal) {
|
|
var parts = formattedValue.split(',');
|
|
el._moneyFormat.decimalPart = parts.length > 1 ? parts[1] : '';
|
|
}
|
|
el._moneyFormat.oldValue = formattedValue;
|
|
}
|
|
|
|
// Format angka untuk display
|
|
function formatNumber(value) {
|
|
if (!value && value !== 0) return '';
|
|
|
|
// Konversi ke string
|
|
var strValue = String(value);
|
|
|
|
// Ganti titik dengan koma untuk desimal
|
|
strValue = strValue.replace('.', ',');
|
|
|
|
// Split berdasarkan koma
|
|
var parts = strValue.split(',');
|
|
var integerPart = parts[0];
|
|
var decimalPart = parts.length > 1 ? parts[1] : '';
|
|
|
|
// Format bagian integer dengan titik ribuan
|
|
var formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
|
|
// Gabungkan kembali
|
|
return decimalPart ? formattedInteger + ',' + decimalPart : formattedInteger;
|
|
}
|
|
|
|
// Convert dari display format ke nilai untuk model
|
|
function parseNumber(displayValue) {
|
|
if (!displayValue) return '';
|
|
|
|
// Split berdasarkan koma
|
|
var parts = displayValue.split(',');
|
|
var integerPart = parts[0].replace(/\./g, ''); // Hapus titik ribuan
|
|
var decimalPart = parts.length > 1 ? parts[1] : '';
|
|
|
|
// Gabungkan dengan format JavaScript
|
|
return decimalPart ? integerPart + '.' + decimalPart : integerPart;
|
|
}
|
|
|
|
// Handler saat pengguna menekan tombol (keydown)
|
|
function onKeydown(e) {
|
|
// Khusus untuk koma dan titik (input desimal)
|
|
if (e.key === ',' || e.key === '.') {
|
|
e.preventDefault(); // Cegah default behavior
|
|
|
|
// Jika sudah ada koma, jangan tambahkan lagi
|
|
if (el._moneyFormat.hasDecimal) return;
|
|
|
|
// Posisi kursor saat ini
|
|
var cursorPos = inputElement.selectionStart;
|
|
|
|
// Tambahkan koma pada posisi kursor
|
|
var currentValue = inputElement.value;
|
|
var newValue = currentValue.substring(0, cursorPos) + ',' + currentValue.substring(cursorPos);
|
|
|
|
// Update nilai input
|
|
inputElement.value = newValue;
|
|
|
|
// Simpan flag
|
|
el._moneyFormat.hasDecimal = true;
|
|
el._moneyFormat.decimalPart = '';
|
|
|
|
// Update model
|
|
var modelValue = parseNumber(newValue);
|
|
vm.$set(vm, modelName, modelValue);
|
|
|
|
// Simpan state baru
|
|
el._moneyFormat.oldValue = newValue;
|
|
el._moneyFormat.oldCursorPosition = cursorPos + 1;
|
|
|
|
// Posisikan kursor setelah koma
|
|
setTimeout(function() {
|
|
inputElement.setSelectionRange(cursorPos + 1, cursorPos + 1);
|
|
}, 0);
|
|
}
|
|
|
|
// Simpan posisi kursor saat ini untuk referensi input handler
|
|
el._moneyFormat.oldCursorPosition = inputElement.selectionStart;
|
|
el._moneyFormat.oldValue = inputElement.value;
|
|
}
|
|
|
|
// Handler saat nilai input berubah
|
|
function onInput(e) {
|
|
// Nilai saat ini
|
|
var currentValue = inputElement.value;
|
|
var cursorPos = inputElement.selectionStart;
|
|
|
|
// Abaikan jika nilai tidak berubah
|
|
if (currentValue === el._moneyFormat.oldValue) return;
|
|
|
|
// Logika utama: Jika sudah ada koma sebelumnya
|
|
if (el._moneyFormat.hasDecimal) {
|
|
// Periksa apakah koma masih ada
|
|
if (currentValue.includes(',')) {
|
|
// Koma masih ada, format bagian integer dan pertahankan desimal
|
|
var parts = currentValue.split(',');
|
|
var integerPart = parts[0];
|
|
var newDecimalPart = parts.length > 1 ? parts[1] : '';
|
|
|
|
// Format bagian integer
|
|
var cleanInteger = integerPart.replace(/\./g, '');
|
|
var formattedInteger = cleanInteger.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
|
|
// Gabungkan dengan desimal
|
|
var formattedValue = formattedInteger + ',' + newDecimalPart;
|
|
|
|
// Update model
|
|
var modelValue = cleanInteger + (newDecimalPart ? '.' + newDecimalPart : '');
|
|
vm.$set(vm, modelName, modelValue);
|
|
|
|
// Update nilai input
|
|
inputElement.value = formattedValue;
|
|
|
|
// Update state
|
|
el._moneyFormat.hasDecimal = true;
|
|
el._moneyFormat.decimalPart = newDecimalPart;
|
|
el._moneyFormat.oldValue = formattedValue;
|
|
|
|
// Perbarui posisi kursor
|
|
var newCursorPos;
|
|
if (cursorPos <= parts[0].length) {
|
|
// Kursor di bagian integer
|
|
var oldDots = (integerPart.substring(0, cursorPos).match(/\./g) || []).length;
|
|
var newDots = (formattedInteger.substring(0, cursorPos).match(/\./g) || []).length;
|
|
newCursorPos = cursorPos + (newDots - oldDots);
|
|
} else {
|
|
// Kursor di bagian desimal
|
|
var offsetFromComma = cursorPos - parts[0].length - 1;
|
|
newCursorPos = formattedInteger.length + 1 + offsetFromComma;
|
|
}
|
|
|
|
setTimeout(function() {
|
|
inputElement.setSelectionRange(newCursorPos, newCursorPos);
|
|
el._moneyFormat.oldCursorPosition = newCursorPos;
|
|
}, 0);
|
|
} else {
|
|
// Koma hilang, tambahkan kembali
|
|
var oldValue = el._moneyFormat.oldValue;
|
|
var oldCommaPos = oldValue.indexOf(',');
|
|
|
|
// Bagian sebelum dan setelah koma di nilai lama
|
|
var oldBeforeComma = oldValue.substring(0, oldCommaPos);
|
|
var oldAfterComma = oldValue.substring(oldCommaPos + 1);
|
|
|
|
// Kasus khusus: jika user menghapus semua
|
|
if (currentValue === '') {
|
|
el._moneyFormat.hasDecimal = false;
|
|
el._moneyFormat.decimalPart = '';
|
|
el._moneyFormat.oldValue = '';
|
|
vm.$set(vm, modelName, '');
|
|
return;
|
|
}
|
|
|
|
// Logika: koma dihapus, coba pulihkan
|
|
|
|
// Kasus 1: Menghapus karakter tepat sebelum koma
|
|
if (cursorPos === oldCommaPos) {
|
|
// Format bagian integer
|
|
var cleanCurrentValue = currentValue.replace(/\./g, '');
|
|
var formattedInteger = cleanCurrentValue.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
|
|
// Gabungkan dengan bagian desimal
|
|
var restoredValue = formattedInteger + ',' + el._moneyFormat.decimalPart;
|
|
|
|
// Update input
|
|
inputElement.value = restoredValue;
|
|
|
|
// Update model
|
|
var modelValue = cleanCurrentValue + '.' + el._moneyFormat.decimalPart;
|
|
vm.$set(vm, modelName, modelValue);
|
|
|
|
// Perbarui state
|
|
el._moneyFormat.oldValue = restoredValue;
|
|
|
|
// Set kursor
|
|
setTimeout(function() {
|
|
var newCommaPos = formattedInteger.length;
|
|
inputElement.setSelectionRange(newCommaPos, newCommaPos);
|
|
el._moneyFormat.oldCursorPosition = newCommaPos;
|
|
}, 0);
|
|
|
|
return;
|
|
}
|
|
|
|
// Kasus 2: Menghapus koma itu sendiri
|
|
if (cursorPos === oldCommaPos || cursorPos === oldCommaPos + 1) {
|
|
// Format bagian integer
|
|
var cleanCurrentValue = currentValue.replace(/\./g, '');
|
|
var formattedInteger = cleanCurrentValue.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
|
|
// Gabungkan dengan bagian desimal yang disimpan
|
|
var restoredValue = formattedInteger + ',' + el._moneyFormat.decimalPart;
|
|
|
|
// Update input
|
|
inputElement.value = restoredValue;
|
|
|
|
// Update model
|
|
var modelValue = cleanCurrentValue + '.' + el._moneyFormat.decimalPart;
|
|
vm.$set(vm, modelName, modelValue);
|
|
|
|
// Perbarui state
|
|
el._moneyFormat.oldValue = restoredValue;
|
|
|
|
// Set kursor
|
|
setTimeout(function() {
|
|
var newCommaPos = formattedInteger.length;
|
|
var newCursorPos = newCommaPos + 1; // Setelah koma
|
|
inputElement.setSelectionRange(newCursorPos, newCursorPos);
|
|
el._moneyFormat.oldCursorPosition = newCursorPos;
|
|
}, 0);
|
|
|
|
return;
|
|
}
|
|
|
|
// Format bagian integer dan tambahkan koma
|
|
var cleanValue = currentValue.replace(/\./g, '');
|
|
var formattedInteger = cleanValue.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
var restoredValue = formattedInteger + ',' + el._moneyFormat.decimalPart;
|
|
|
|
// Update input
|
|
inputElement.value = restoredValue;
|
|
|
|
// Update model
|
|
var modelValue = cleanValue + '.' + el._moneyFormat.decimalPart;
|
|
vm.$set(vm, modelName, modelValue);
|
|
|
|
// Perbarui state
|
|
el._moneyFormat.oldValue = restoredValue;
|
|
|
|
// Perhitungan kursor
|
|
setTimeout(function() {
|
|
var newCursorPos;
|
|
|
|
if (cursorPos < oldCommaPos) {
|
|
// Kursor di bagian integer
|
|
var oldDots = (oldValue.substring(0, cursorPos).match(/\./g) || []).length;
|
|
var newDots = (formattedInteger.substring(0, cursorPos).match(/\./g) || []).length;
|
|
newCursorPos = cursorPos + (newDots - oldDots);
|
|
} else {
|
|
// Kursor di bagian desimal, kembalikan ke setelah koma
|
|
newCursorPos = formattedInteger.length + 1;
|
|
}
|
|
|
|
inputElement.setSelectionRange(newCursorPos, newCursorPos);
|
|
el._moneyFormat.oldCursorPosition = newCursorPos;
|
|
}, 0);
|
|
}
|
|
} else {
|
|
// Belum ada koma, format bagian integer saja
|
|
|
|
// Kasus khusus: jika user menambahkan koma
|
|
if (currentValue.includes(',')) {
|
|
// Ada koma baru, update flags
|
|
var parts = currentValue.split(',');
|
|
var integerPart = parts[0];
|
|
var decimalPart = parts.length > 1 ? parts[1] : '';
|
|
|
|
// Format bagian integer
|
|
var cleanInteger = integerPart.replace(/\./g, '');
|
|
var formattedInteger = cleanInteger.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
|
|
// Gabungkan
|
|
var formattedValue = formattedInteger + ',' + decimalPart;
|
|
|
|
// Update input
|
|
inputElement.value = formattedValue;
|
|
|
|
// Update model
|
|
var modelValue = cleanInteger + (decimalPart ? '.' + decimalPart : '.');
|
|
vm.$set(vm, modelName, modelValue);
|
|
|
|
// Perbarui state
|
|
el._moneyFormat.hasDecimal = true;
|
|
el._moneyFormat.decimalPart = decimalPart;
|
|
el._moneyFormat.oldValue = formattedValue;
|
|
|
|
// Set kursor
|
|
var commaPos = formattedInteger.length;
|
|
var newCursorPos;
|
|
|
|
if (cursorPos <= parts[0].length) {
|
|
// Kursor di bagian integer
|
|
var oldDots = (integerPart.substring(0, cursorPos).match(/\./g) || []).length;
|
|
var newDots = (formattedInteger.substring(0, cursorPos).match(/\./g) || []).length;
|
|
newCursorPos = cursorPos + (newDots - oldDots);
|
|
} else {
|
|
// Kursor di bagian desimal
|
|
var offsetFromComma = cursorPos - parts[0].length - 1;
|
|
newCursorPos = commaPos + 1 + offsetFromComma;
|
|
}
|
|
|
|
setTimeout(function() {
|
|
inputElement.setSelectionRange(newCursorPos, newCursorPos);
|
|
el._moneyFormat.oldCursorPosition = newCursorPos;
|
|
}, 0);
|
|
|
|
return;
|
|
}
|
|
|
|
// Format bagian integer (tanpa desimal)
|
|
var cleanValue = currentValue.replace(/\./g, '');
|
|
var formattedValue = cleanValue.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
|
|
// Update hanya jika berbeda
|
|
if (formattedValue !== currentValue) {
|
|
// Hitung offset kursor
|
|
var oldDots = (currentValue.substring(0, cursorPos).match(/\./g) || []).length;
|
|
var newDots = (formattedValue.substring(0, cursorPos).match(/\./g) || []).length;
|
|
var offset = newDots - oldDots;
|
|
|
|
// Update input
|
|
inputElement.value = formattedValue;
|
|
|
|
// Update model
|
|
vm.$set(vm, modelName, cleanValue);
|
|
|
|
// Perbarui state
|
|
el._moneyFormat.oldValue = formattedValue;
|
|
|
|
// Update kursor
|
|
var newCursorPos = cursorPos + offset;
|
|
setTimeout(function() {
|
|
inputElement.setSelectionRange(newCursorPos, newCursorPos);
|
|
el._moneyFormat.oldCursorPosition = newCursorPos;
|
|
}, 0);
|
|
} else {
|
|
// Tidak perlu format ulang
|
|
vm.$set(vm, modelName, cleanValue);
|
|
el._moneyFormat.oldValue = formattedValue;
|
|
el._moneyFormat.oldCursorPosition = cursorPos;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Format ulang saat keluar dari input
|
|
function onBlur() {
|
|
var modelValue = vm[modelName];
|
|
if (modelValue !== undefined && modelValue !== null) {
|
|
var formattedValue = formatNumber(modelValue);
|
|
inputElement.value = formattedValue;
|
|
|
|
// Update flags
|
|
el._moneyFormat.hasDecimal = formattedValue.includes(',');
|
|
if (el._moneyFormat.hasDecimal) {
|
|
var parts = formattedValue.split(',');
|
|
el._moneyFormat.decimalPart = parts.length > 1 ? parts[1] : '';
|
|
} else {
|
|
el._moneyFormat.decimalPart = '';
|
|
}
|
|
|
|
el._moneyFormat.oldValue = formattedValue;
|
|
}
|
|
}
|
|
|
|
// Register event handlers
|
|
inputElement.addEventListener('keydown', onKeydown);
|
|
inputElement.addEventListener('input', onInput);
|
|
inputElement.addEventListener('blur', onBlur);
|
|
|
|
// Store untuk cleanup
|
|
el._moneyFormat.handlers = {
|
|
keydown: onKeydown,
|
|
input: onInput,
|
|
blur: onBlur
|
|
};
|
|
},
|
|
|
|
update: function(el, binding, vnode) {
|
|
var inputElement = el.tagName === 'INPUT' ? el : el.querySelector('input');
|
|
if (!inputElement || !el._moneyFormat) return;
|
|
|
|
var vm = vnode.context;
|
|
var modelName = binding.expression;
|
|
|
|
// Format untuk display
|
|
function formatNumber(value) {
|
|
if (!value && value !== 0) return '';
|
|
|
|
// Konversi ke string
|
|
var strValue = String(value);
|
|
|
|
// Ganti titik dengan koma untuk desimal
|
|
strValue = strValue.replace('.', ',');
|
|
|
|
// Split berdasarkan koma
|
|
var parts = strValue.split(',');
|
|
var integerPart = parts[0];
|
|
var decimalPart = parts.length > 1 ? parts[1] : '';
|
|
|
|
// Format bagian integer dengan titik ribuan
|
|
var formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, '.');
|
|
|
|
// Gabungkan kembali
|
|
return decimalPart ? formattedInteger + ',' + decimalPart : formattedInteger;
|
|
}
|
|
|
|
// Update display jika tidak sedang fokus
|
|
if (document.activeElement !== inputElement) {
|
|
var modelValue = vm[modelName];
|
|
if (modelValue !== undefined && modelValue !== null) {
|
|
var formattedValue = formatNumber(modelValue);
|
|
inputElement.value = formattedValue;
|
|
|
|
// Update flags
|
|
el._moneyFormat.hasDecimal = formattedValue.includes(',');
|
|
if (el._moneyFormat.hasDecimal) {
|
|
var parts = formattedValue.split(',');
|
|
el._moneyFormat.decimalPart = parts.length > 1 ? parts[1] : '';
|
|
} else {
|
|
el._moneyFormat.decimalPart = '';
|
|
}
|
|
|
|
el._moneyFormat.oldValue = formattedValue;
|
|
}
|
|
}
|
|
},
|
|
|
|
unbind: function(el) {
|
|
var inputElement = el.tagName === 'INPUT' ? el : el.querySelector('input');
|
|
if (!inputElement || !el._moneyFormat || !el._moneyFormat.handlers) return;
|
|
|
|
// Remove event listeners
|
|
inputElement.removeEventListener('keydown', el._moneyFormat.handlers.keydown);
|
|
inputElement.removeEventListener('input', el._moneyFormat.handlers.input);
|
|
inputElement.removeEventListener('blur', el._moneyFormat.handlers.blur);
|
|
|
|
// Cleanup
|
|
delete el._moneyFormat;
|
|
}
|
|
};
|
|
|
|
// Register directive globally jika Vue tersedia
|
|
if (typeof Vue !== 'undefined') {
|
|
Vue.directive('money-format', moneyFormat);
|
|
}
|
|
|
|
// Export untuk CommonJS/AMD
|
|
if (typeof exports === 'object' && typeof module !== 'undefined') {
|
|
module.exports = moneyFormat;
|
|
} else if (typeof define === 'function' && define.amd) {
|
|
define(function() { return moneyFormat; });
|
|
} else {
|
|
// Browser global
|
|
window.moneyFormat = moneyFormat;
|
|
}
|
|
})(); |