first commit + add fe component vue accone

This commit is contained in:
2026-05-07 17:10:10 +07:00
parent fd8e4d694b
commit 38a182641a
327 changed files with 263946 additions and 0 deletions

474
libs/money-format.js Normal file
View File

@@ -0,0 +1,474 @@
(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;
}
})();