'use strict'; /* ============================================================================ * 숫자 입력 칸 천 단위 쉼표 (v1.4.5) * * 화면의 모든 숫자 입력 칸(input type=number)을 "12,000" 처럼 3자리마다 쉼표가 보이는 칸으로 바꾼다. * - 브라우저의 number 칸은 쉼표를 넣을 수 없어, 글자 칸(type=text, inputmode=numeric/decimal)으로 바꾸고 입력할 때마다 쉼표를 다시 넣는다. * - 기존 화면 코드(admin.js 등)는 input.value 로 값을 읽어 서버에 보낸다. 그 코드를 모두 고치지 않도록 이 칸들의 value 를 * "쉼표를 뺀 숫자 문자열" 로 돌려주고, value 에 넣는 값은 쉼표를 붙여 보이게 한다(요소별 value 속성 재정의). * → 서버로 가는 값·검증(최소·최대)·저장하지 않은 변경 표시(data-dirty-watch)는 이전과 같다. * - 나중에 만들어지는 칸(사이트 관리자 입력 창, 도메인 레코드 등)도 MutationObserver 로 따라가 바꾼다. * - 쉼표가 어울리지 않는 칸(포트 번호 등)은 data-plain 속성을 붙이면 그대로 둔다. * 전역: window.WMNum = { format(값), clean(문자열), enhance(입력칸) } * ========================================================================== */ (function () { var desc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value'); /** 화면에 보일 모양: 정수부 3자리마다 쉼표, 소수부·음수 부호는 그대로 */ function format(raw) { raw = clean(raw); if (raw === '' || raw === '-' || raw === '.') return raw; var neg = raw.charAt(0) === '-'; var s = neg ? raw.slice(1) : raw; var dot = s.indexOf('.'); var int = dot < 0 ? s : s.slice(0, dot); var frac = dot < 0 ? '' : s.slice(dot); int = int.replace(/^0+(?=\d)/, '').replace(/\B(?=(\d{3})+(?!\d))/g, ','); return (neg ? '-' : '') + int + frac; } /** 쉼표·공백·숫자 아닌 글자를 뺀 값 (서버로 보낼 값). 부호는 맨 앞, 소수점은 하나만 */ function clean(v) { var s = String(v == null ? '' : v).replace(/[^\d.\-]/g, ''); var neg = s.charAt(0) === '-'; s = s.replace(/-/g, ''); var i = s.indexOf('.'); if (i >= 0) s = s.slice(0, i + 1) + s.slice(i + 1).replace(/\./g, ''); return (neg ? '-' : '') + s; } /** 입력 중 쉼표를 다시 넣어도 커서가 같은 숫자 뒤에 머물도록 위치를 옮긴다 */ function reformat(inp) { var old = desc.get.call(inp); var pos = inp.selectionStart == null ? old.length : inp.selectionStart; var keep = old.slice(0, pos).replace(/[^\d.\-]/g, '').length; // 커서 앞의 의미 있는 글자 수 var next = format(old); if (next === old) return; desc.set.call(inp, next); var p = 0, n = 0; while (p < next.length && n < keep) { if (/[\d.\-]/.test(next.charAt(p))) n++; p++; } try { inp.setSelectionRange(p, p); } catch (_) {} } function enhance(inp) { if (!inp || inp.__wmNum || inp.tagName !== 'INPUT' || inp.type !== 'number' || inp.hasAttribute('data-plain')) return; inp.__wmNum = true; var cur = desc.get.call(inp); var step = inp.getAttribute('step'); inp.type = 'text'; inp.setAttribute('inputmode', step && step !== 'any' && Number(step) % 1 === 0 && !(Number(inp.getAttribute('min')) < 0) ? 'numeric' : 'decimal'); inp.setAttribute('autocomplete', 'off'); inp.classList.add('num-input'); Object.defineProperty(inp, 'value', { configurable: true, get: function () { return clean(desc.get.call(this)); }, set: function (v) { desc.set.call(this, format(v)); } }); inp.value = cur; inp.addEventListener('input', function () { reformat(inp); }); } function scan(root) { if (!root || !root.querySelectorAll) return; if (root.tagName === 'INPUT') enhance(root); var l = root.querySelectorAll('input[type="number"]'); for (var i = 0; i < l.length; i++) enhance(l[i]); } function start() { scan(document.body); new MutationObserver(function (ms) { for (var i = 0; i < ms.length; i++) { var m = ms[i]; if (m.type === 'attributes') enhance(m.target); else for (var j = 0; j < m.addedNodes.length; j++) scan(m.addedNodes[j]); } }) .observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['type'] }); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start); else start(); window.WMNum = { format: format, clean: clean, enhance: enhance }; })();