8

Is there a good way to disable the "Smart Punctuation" the iOS 11 Apple Keyboard generates - in Safari on an HTML login form - username field in particular?

The problem is that we have users with apostrophes in their usernames. Typing their usernames on iOS 11 and not knowing the subtleties of unicode they are not able to sign in.

Ideally we could just instruct such users to disable smart quotes or type the proper character by holding down the apostrophe key - but I am working on educational software for small children and that is out of the question.

The problem is compounded by the fact that there are also a small selection of users with actual curly single quotes in their usernames, so a simple map of replacements won't work - and we can't canonicalize the usernames as they come from a number of external systems we don't control / can't have a lot of say over.

donatJ
  • 2,467
  • 2
  • 26
  • 38

2 Answers2

9

Unfortunately according to this MDN page there is no known Webkit <input> or <textarea> attributes to disable smart-quotes, but you can patch it with a script I've written derived from this QA: How to disable smart quotes for textarea fields in the browser?

window.addEventListener('DOMContentLoaded', attachFilterToInputs );

function attachFilterToInputs() {

    var textInputs = document.querySelectorAll( 'input[type=text], input[type=password], input[type=email], input[type=search], input[type=url], textarea, *[contenteditable=true]' );
    for( var i = 0; i < textInputs.length; i++ ) {
        textInputs[i].addEventListener( 'keypress', preventPretentiousPunctuation );
    } 
}

var conversionMap = createConversionMap();

function createConversionMap() {

    var map = {};
    // Open-quotes: http://www.fileformat.info/info/unicode/category/Pi/list.htm
    map[ 0x2018 ] = '\'';
    map[ 0x201B ] = '\'';
    map[ 0x201C ] = '"';
    map[ 0x201F ] = '"';

    // Close-quotes: http://www.fileformat.info/info/unicode/category/Pf/list.htm
    map[ 0x2019 ] = '\'';
    map[ 0x201D ] = '\"';

    // Primes: http://www.fileformat.info/info/unicode/category/Po/list.htm
    map[ 0x2032 ] = '\'';
    map[ 0x2033 ] = '"';
    map[ 0x2035 ] = '\'';
    map[ 0x2036 ] = '"';

    map[ 0x2014 ] = '-'; // iOS 11 also replaces dashes with em-dash
    map[ 0x2013 ] = '-'; // and "--" with en-dash

    return map;
}

function preventPretentiousPunctuation( event ) {

    if( event.key.length != 1 ) return;

    var code = event.key.codePointAt(0);
    var replacement = conversionMap[ code ];
    if( replacement ) {

        event.preventDefault();
        document.execCommand( 'insertText', 0, replacement );
    }
} 
mellamokb
  • 53,762
  • 11
  • 101
  • 131
Dai
  • 110,988
  • 21
  • 188
  • 277
  • 3
    I've had this open all day so that it'd be ready to go when I got around to "fixing" our code to deal with Apple's changes. I only just clocked the name of preventPretentiousPunctuation :) ā€“ Matt Lacey Mar 13 '18 at 06:44
  • Unfortunately this doesn't work for autocorrect events like turning "Im" into "Iā€™m" ā€“ EionRobb Apr 13 '18 at 04:19
7

Thanks to Dai for the outline of the answer. Unfortunately when we implemented their solution and tested on an iOS 11 device, we found that the keypress event listener wasn't firing at all. We reworked their solution using the input event and regex string replace and found this did work for us.

Here is our variation, using jQuery for the selector and a shorter replace list.

<script type="text/javascript">

    // Here are the reference to Unicode Characters in the Punctuation
    // Open-quotes: http://www.fileformat.info/info/unicode/category/Pi/list.htm    
    // Close-quotes: http://www.fileformat.info/info/unicode/category/Pf/list.htm    

    window.addEventListener('DOMContentLoaded', attachFilterToInputs);

    // Patch the iOS Smart Punctuation functionality on restricted field(s), 
    // so that the user types acceptable characters.
    function attachFilterToInputs() {
        try {
            $("[name$='MyProtectedFieldName']").on('input', preventPretentiousPunctuation);
        }
        catch (err) {
            // do nothing
        }
    }

    function preventPretentiousPunctuation(event) {
        try {
            var str = $(this).val();
            var replacedStr = "";
            replacedStr = str.replace(/[\u2018\u2019\u201C\u201D]/g, 
                (c) => '\'\'""'.substr('\u2018\u2019\u201C\u201D'.indexOf(c), 1));

            if (str !== replacedStr) {
                $(this).val(replacedStr);
            }            
        }
        catch (err) {
            // do nothing
        }        
    }
</script>

Adding as an answer since I don't have the rep to comment.

spaced - out
  • 113
  • 1
  • 7
  • Note that this code is not compatible with IE 11. To fix, change it to two separate replaces, which is probably also easier to understand. `var replacedStr = str; replacedStr = replacedStr.replace(/[\u2018\u2019]/g, "'"); replacedStr = replacedStr.replace(/[\u201C\u201D]/g, '"');` ā€“ mellamokb Jan 15 '20 at 16:05