Hi All,
I’m working on an OutSystems Reactive Web App and trying to implement input masking for a field.Actual value format: 123456789Display Format : ##-#######When users gives input it should be populated in same format.Eg:1 -> # , 12 -> ##-, 123-> ##-#, 1234 -> ##-## .....User should never see the actual digits Mask should work for:
Typing
Paste
Focus / click
I have implemented this using custom JavaScript in a Reactive screen.The masking works initially, but when I change the Input Valid property to True or False (for validation purposes), the input sometimes becomes unmasked or resets.
Any guidance or examples would be greatly appreciated.
Thanks in advance!
Use this component https://www.outsystems.com/forge/component-overview/2258/input-masks-library-o11 drag your input field inside this component make sure your data type should be text and in the pattern you can give your format
you can also use this https://www.outsystems.com/forge/component-overview/17750/maskingssn-o11 component this is different but it will give you the idea how to achieve this
Hi @Rakesh K(function () { const containerSelector = '[data-qa="masked-nine-container"]'; const inputSelector = '[data-qa="masked-nine-input"]'; const hiddenSelector = '[data-qa="masked-nine-hidden"]'; const toDigits = (str) => (str || '').replace(/\D+/g, '').slice(0, 9); const toMasked = (digits) => { const n = digits.length; if (n === 0) return ''; if (n <= 2) return '#'.repeat(n) + (n === 2 ? '-' : ''); return '##-' + '#'.repeat(n - 2); }; function attachMaskedHandlers() { const inputEl = document.querySelector(inputSelector); const hiddenEl = document.querySelector(hiddenSelector); if (!inputEl || !hiddenEl) return; const apply = (raw) => { const digits = toDigits(raw); const masked = toMasked(digits); // Update visible masked value inputEl.value = masked; // Push actual digits into hidden input (bound to OutSystems variable) hiddenEl.value = digits; hiddenEl.dispatchEvent(new Event('change', { bubbles: true })); }; // Initialize from current value (handles re-renders) apply(inputEl.value); // Typing (also covers delete/backspace) inputEl.addEventListener('input', (e) => { apply(e.target.value); }); // Paste: sanitize clipboard and apply inputEl.addEventListener('paste', (e) => { e.preventDefault(); const text = (e.clipboardData || window.clipboardData).getData('text') || ''; apply(text); }); // Focus / click: keep caret at end for a clean UX const moveCaretToEnd = () => { const len = (inputEl.value || '').length; // Defer to let the browser set value first setTimeout(() => { try { inputEl.setSelectionRange(len, len); } catch (err) {} }, 0); }; inputEl.addEventListener('focus', moveCaretToEnd); inputEl.addEventListener('click', moveCaretToEnd); } // Re-attach after any DOM changes (e.g., when Valid toggles and OutSystems re-renders) function observeAndAttach() { const container = document.querySelector(containerSelector) || document.body; const run = () => attachMaskedHandlers(); run(); // initial attach const mo = new MutationObserver(() => run()); mo.observe(container, { childList: true, subtree: true }); } observeAndAttach();})(); use this js
Hi Rakesh,
What you’re seeing is expected behavior in Reactive apps.
In OutSystems Reactive, whenever you change properties like Input Valid, the framework may re-render or partially recreate the input element. When that happens, any masking applied directly via custom JavaScript on the DOM can be lost, causing the value to reset or become unmasked.
That’s why it works initially but breaks after validation state changes.
A few important points:
Consider a supported masking libraryLibraries like Inputmask or Cleave.js tend to behave better if initialized correctly after each render.
In short, the issue isn’t your masking logic itself, but the interaction between custom JavaScript and the Reactive rendering lifecycle. Any solution needs to account for re-renders triggered by validation changes.
Best, Miguel