From 67290fd6a7c0cf733d1ac7d9b4ea78430e05702a Mon Sep 17 00:00:00 2001 From: Paulo Reis Date: Thu, 18 Aug 2016 17:56:53 +0100 Subject: [PATCH] Fix formatting on change and paste events. --- .../coffee/directives/creditCards.coffee | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/services/web/public/coffee/directives/creditCards.coffee b/services/web/public/coffee/directives/creditCards.coffee index bfac125f6a..3ee89a610a 100644 --- a/services/web/public/coffee/directives/creditCards.coffee +++ b/services/web/public/coffee/directives/creditCards.coffee @@ -99,6 +99,30 @@ define [ groups = $.grep(groups, (n) -> n) # Filter empty groups groups.join(' ') + formatExpiry = (expiry) -> + parts = expiry.match(/^\D*(\d{1,2})(\D+)?(\d{1,4})?/) + return '' unless parts + + mon = parts[1] || '' + sep = parts[2] || '' + year = parts[3] || '' + + if year.length > 0 + sep = ' / ' + + else if sep is ' /' + mon = mon.substring(0, 1) + sep = '' + + else if mon.length == 2 or sep.length > 0 + sep = ' / ' + + else if mon.length == 1 and mon not in ['0', '1'] + mon = "0#{mon}" + sep = ' / ' + + return mon + sep + year + parseExpiry = (value = "") -> [month, year] = value.split(/[\s\/]+/, 2) @@ -119,6 +143,7 @@ define [ fromNumber: cardFromNumber fromType: cardFromType cardType: cardType + formatExpiry: formatExpiry formatCardNumber: formatCardNumber defaultFormat: defaultFormat defaultInputFormat: defaultInputFormat @@ -137,6 +162,45 @@ define [ false + safeVal = (value, $target) -> + try + cursor = $target.prop('selectionStart') + catch error + cursor = null + + last = $target.val() + $target.val(value) + + if cursor != null && $target.is(":focus") + cursor = value.length if cursor is last.length + + # This hack looks for scenarios where we are changing an input's value such + # that "X| " is replaced with " |X" (where "|" is the cursor). In those + # scenarios, we want " X|". + # + # For example: + # 1. Input field has value "4444| " + # 2. User types "1" + # 3. Input field has value "44441| " + # 4. Reformatter changes it to "4444 |1" + # 5. By incrementing the cursor, we make it "4444 1|" + # + # This is awful, and ideally doesn't go here, but given the current design + # of the system there does not appear to be a better solution. + # + # Note that we can't just detect when the cursor-1 is " ", because that + # would incorrectly increment the cursor when backspacing, e.g. pressing + # backspace in this scenario: "4444 1|234 5". + if last != value + prevPair = last[cursor-1..cursor] + currPair = value[cursor-1..cursor] + digit = value[cursor] + cursor = cursor + 1 if /\d/.test(digit) and + prevPair == "#{digit} " and currPair == " #{digit}" + + $target.prop('selectionStart', cursor) + $target.prop('selectionEnd', cursor) + # Replace Full-Width Chars replaceFullWidthChars = (str = '') -> fullWidth = '\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19' @@ -160,6 +224,7 @@ define [ value = $target.val() value = replaceFullWidthChars(value) value = value.replace(/\D/g, '') + safeVal(value, $target) # Format Card Number reFormatCardNumber = (e) -> @@ -168,6 +233,7 @@ define [ value = $target.val() value = replaceFullWidthChars(value) value = ccUtils.formatCardNumber(value) + safeVal(value, $target) formatCardNumber = (e) -> # Only format if input is a number @@ -249,7 +315,9 @@ define [ setTimeout -> value = $target.val() value = replaceFullWidthChars(value) - value = $.payment.formatExpiry(value) + value = ccUtils.formatExpiry(value) + safeVal(value, $target) + formatExpiry = (e) -> # Only format if input is a number @@ -328,6 +396,7 @@ define [ value = $target.val() value = replaceFullWidthChars(value) value = value.replace(/\D/g, '')[0...4] + safeVal(value, $target) # Restrictions restrictNumeric = (e) -> @@ -429,11 +498,15 @@ define [ restrict: "A" require: "ngModel" link: (scope, el, attrs, ngModel) -> + el.on "keypress", ccFormat.restrictNumeric el.on "keypress", ccFormat.restrictExpiry el.on "keypress", ccFormat.formatExpiry el.on "keypress", ccFormat.formatForwardSlash el.on "keypress", ccFormat.formatForwardExpiry el.on "keydown", ccFormat.formatBackExpiry + el.on "change", ccFormat.reFormatExpiry + el.on "input", ccFormat.reFormatExpiry + el.on "paste", ccFormat.reFormatExpiry ngModel.$parsers.push ccFormat.parseExpiry ngModel.$formatters.push ccFormat.parseExpiry @@ -442,6 +515,7 @@ define [ restrict: "A" require: "ngModel" link: (scope, el, attrs, ngModel) -> + el.on "keypress", ccFormat.restrictNumeric el.on "keypress", ccFormat.restrictCardNumber el.on "keypress", ccFormat.formatCardNumber el.on "keydown", ccFormat.formatBackCardNumber