(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; } })();