js/mask.js

/**
 * @module mask
 */

import { getPasteData } from './utils';
import events from './event';
const KEYBOARD_CUT_PASTE = 'xvc';

export default {

    init() {
    /*
     * These are hardcoded number input masks. The approach will be different if we
     * ever add complex user-defined input masks.
     */
        this._setNumberMask( '[data-type-xml="int"]', /^(-?[0-9]+$)/, '-0123456789' );
        this._setNumberMask( '[data-type-xml="decimal"]', /^(-?[0-9]+[.,]?[0-9]*$)/, '-0123456789.,' );
    },

    /**
     * @param {string} selector - selector of elements to apply mask to
     * @param {string} validRegex - regular expression for valid values
     * @param {string} allowedChars - string of allowed characters
     */
    _setNumberMask( selector, validRegex, allowedChars ) {
        const form = this.form.view.html;

        form.addEventListener( 'keydown', event => {
            if ( event.target.matches( selector ) ){
            // The "key" property is the correct standards-compliant property to use
            // but needs some corrections for non-standard-compliant IE behavior.
                if ( this._isNotPrintableKey( event ) || this._isKeyboardCutPaste( event ) || allowedChars.indexOf( event.key ) !== -1 ) {
                    return true;
                }
                event.preventDefault();
                event.stopPropagation();
            }

        } );

        form.addEventListener( 'paste', event => {
            if ( event.target.matches( selector ) ){
                const val = getPasteData( event );
                // HTML number input fields will trim the pasted value automatically.
                if ( val && validRegex.test( val.trim() ) ) {
                // Note that event.target.value will be empty if the pasted value is not a valid number (except in IE11).
                // In that case the paste action has the same result as pasting an empty value, ie
                // clearing any existing value.
                    return true;
                }

                event.target.value =  '';
                event.target.dispatchEvent( events.Change() );

                event.preventDefault();
                event.stopPropagation();
            }

        } );
        /*
     * Workaround for most browsers keeping invalid numbers visible in the input without a means to access the invalid value.
     * E.g. see https://bugs.chromium.org/p/chromium/issues/detail?id=178437&can=2&q=178437&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Component%20Status%20Owner%20Summary%20OS%20Modified
     *
     * A much more intelligent way to solve the problem would be to add a feedback loop from the Model to the input that would
     * correct (a converted number) or empty (an invalid number). https://github.com/enketo/enketo-core/issues/407
     */
        form.addEventListener( 'blur', event => {
            if ( event.target.matches( selector ) ){
            // proper browsers:
                if ( typeof event.target.validity !== 'undefined' && typeof event.target.validity.badInput !== 'undefined' && event.target.validity.badInput ) {
                    event.target.value = '';
                }
                // IE11 (no validity.badInput support, but does give access to invalid number with event.target.value)
                else if ( typeof event.target.validity.badInput === 'undefined' && event.target.value && !validRegex.test( event.target.value.trim() ) ) {
                    event.target.value = '';
                }
            }
        } );

    },

    // Using the (assumed) fact that a non-printable character key always has length > 1
    // IE11: non-confirming 'Spacebar'
    /**
     * @param {Event} e - event
     * @return {boolean} whether key is printable
     */
    _isNotPrintableKey( e ) {
        return e.key.length > 1 && e.key !== 'Spacebar';
    },

    /**
     * @param {Event} e - event
     * @return {boolean} whether event is a paste event
     */
    _isKeyboardCutPaste( e ) {
        return KEYBOARD_CUT_PASTE.indexOf( e.key ) !== -1 && ( e.metaKey || e.ctrlKey );
    }

};