101

Our application uses selectionStart on input fields to determine whether to automatically move the user to the next/previous field when they press the arrow keys (ie, when the selection is at the end of the text and the user presses the right arrow we move to the next field, otherwise)

Chrome now prevents selectionStart from being used where type="number". It now throws the exception:

Failed to read the 'selectionStart' property from 'HTMLInputElement': The input element's type ('number') does not support selection.

See following:

https://codereview.chromium.org/100433008/#ps60001

http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#do-not-apply

Is there any way to determine the location of the caret in an input field of type="number"?

sideshowbarker
  • 81,827
  • 26
  • 193
  • 197
Steven
  • 3,878
  • 3
  • 21
  • 21
  • 4
    hmm no update to this. Curious as to why this feature is being removed. – Apples Feb 04 '14 at 18:43
  • 1
    Another utilization: if you use jquery-maskmoney plugin with input type number, the same problem occurs – Alexandre Feb 21 '14 at 12:20
  • Could you probably change input type temporarily to `text`, check the caret position, and then change the type back to `number`? – Stan Feb 22 '14 at 20:02
  • 1
    @Stan I don't think swapping types would be very workable although I haven't tried it yet. – Steven Feb 23 '14 at 23:20
  • 2
    @Steven It doesn't work, the selectionStart/End always return as zero: http://jsfiddle.net/Mottie/wMYKU/ – Mottie Feb 25 '14 at 04:08
  • @Stan I've done a lot of investigation on this and unfortunately you can not convert the field to text without losing the selection range in the field. We can't solve the text selection within a number field in Chrome now but we can determine if the browser will fail to enable text selection and pre-convert all number fields on the fly/page load to overcome this. I've posted that problem and solution here: http://stackoverflow.com/questions/22381837/how-to-overcome-whatwg-w3c-chrome-version-33-0-1750-146-regression-bug-with-i – scunliffe Mar 13 '14 at 18:05

13 Answers13

51

Selection is only permitted with text/search, URL, tel and password. The likely reason that selection has been disabled for inputs of type number is that on some devices, or under some circumstances (e.g., when the input has been is presented as a short list), there might not be a caret. The only solution I have found was to change the input type to text (with appropriate pattern to restrict input). I am still looking for a way to do with without changing the input type and will post an update when I find something.

Patrice Chalin
  • 15,440
  • 7
  • 33
  • 44
  • 6
    I understand that some devices may not display a caret but it seems to complicate things. If we end up using type="text" for everything it defeats the purpose of even having a type. It also loses the ability for phones to show different keyboards for numeric input etc. I disagree with their choice to remove selection from inputs but I'm not sure what the solution is... – Steven Feb 23 '14 at 23:24
  • 64
    This really sucks. I've contacted the WHAT-WG on this as I think this is a major mistake. 99.9% of user agents render an `` field as a text field that sets the default on-screen keyboard (if available) to the numeric keyboard but still render the field as a "text input" field. Any existing/future attempt to add a calculator or similar value manipulation feature for the field are now rendered useless unless we force the field back to text and ruin the user experience. – scunliffe Feb 25 '14 at 21:34
  • 3
    I agree with both of you. As I point out in [this](http://pchalin.blogspot.com/2014/02/html5-number-input-value-idl-attribute.html) post, I think that the value IDL attribute should always contain a text rendering of whatever the input contains. Also, if the user agent allows selection, then selectionStart/End should be available. – Patrice Chalin Feb 26 '14 at 02:36
  • 6
    Whaaaaat o_0 Chrome just broke the internet.. and my tests. On a more serious note I don't get the logic. Why shouldn't a user be able to select their email address or a number? http://stackoverflow.com/questions/22171694/why-shouldnt-a-user-agent-be-able-to-select-an-email-address-or-a-number – Peter Mar 04 '14 at 12:14
27

I have found a simple workaround (tested on Chrome) for setSelectionRange(). You can simply change the type to text before you use setSelectionRange() and then change it back to number.

Here is a simple example with jquery that will position the caret at position 4 in the number input whenever you click on the input (add more than 5 number in the input to see the behavior)

plunker

Nate
  • 7,606
  • 23
  • 72
  • 124
  • This approach also works for `email` inputs (which is how I ended up on this page wondering why `setSelectionRange()` worked in IE11 (!) and not Chrome). – jdunning Apr 28 '18 at 20:59
  • This works without jQuery, as pure JavaScript obj.type = "text"; then return to obj.type = "number"; – jacouh Apr 20 '19 at 19:43
  • But the plunkr no longer allows the user to make a selection with the mouse. – Mr Lister May 18 '19 at 12:48
  • 11
    For me (in Chrome version 80) this no longer works because if I set the input type to "text" using ```input.type = 'text'```, Chrome removes focus from the input, and the cursor disappears so that ```input.selectionStart``` etc returns null. – Ananda Masri Feb 24 '20 at 22:48
  • 1
    @AnandaMasri Can't you just `input.focus()` first? – kungfooman Jan 20 '23 at 15:48
  • @kungfooman if you insert the `input.focus()` in the above plunker, you'll see that it doesn't work (I'm using Chrome v108 now). – Ananda Masri Jan 26 '23 at 00:46
18

​ Currently the only elements that allow text selection safely are:

<input type="text|search|password|tel|url"> as described in:
whatwg: selectionStart attribute.

You can also read the documentation for the HTMLInputElement interface to take a closer look of the input elements.

To overcome this "issue" safely, the best for now is deal with an <input type="text"> and apply a mask/constraint that accept only numbers. There are some plugins around that satisfy the requirement:

You can see a live demo of one of the previous plugins here:

If you want to use safely selectionStart, then you can check for those elements that support it (see input type attributes)

Implementation

// Fix: failed to read the 'selectionStart' property from 'HTMLInputElement'
// The @fn parameter provides a callback to execute additional code
var _fixSelection = (function() {
    var _SELECTABLE_TYPES = /text|password|search|tel|url/;
    return function fixSelection (dom, fn) {
        var validType = _SELECTABLE_TYPES.test(dom.type),
            selection = {
                start: validType ? dom.selectionStart : 0,
                end: validType ? dom.selectionEnd : 0
            };
        if (validType && fn instanceof Function) fn(dom);
        return selection;
    };
}());

// Gets the current position of the cursor in the @dom element
function getCaretPosition (dom) {
    var selection, sel;
    if ('selectionStart' in dom) {
        return _fixSelection(dom).start;
    } else { // IE below version 9
        selection = document.selection;
        if (selection) {
            sel = selection.createRange();
            sel.moveStart('character', -dom.value.length);
            return sel.text.length;
        }
    }
    return -1;
}

Usage

// If the DOM element does not support `selectionStart`,
// the returned object sets its properties to -1.
var box = document.getElementById("price"),
    pos = getCaretPosition(box);
console.log("position: ", pos);

The above example can be found here: jsu.fnGetCaretPosition()

jherax
  • 5,238
  • 5
  • 38
  • 50
  • Hi, thank you for the post. Could you just explain what do you mean by ' constraint to accept only numbers'? – PetarMI Jul 28 '15 at 15:06
  • 1
    Hi @PetarMI, by _constraint_ I mean that an element can accept only numeric characters, and it is done via JavaScript, that is because not all browsers works properly with the new HTML5 tags. – jherax Jul 30 '15 at 02:35
9

There is one way you can accomplish this on Chrome (and maybe some other browsers, but Chrome is the big offender here). Use window.getSelection() to retrieve the selection object from the current input and then test extend the selection backwards (or forwards) and see if the toString() value of the selection changes. If it doesn't, the cursor is at the end of the input and you can move to your next input. If it does, you have to then reverse the operation to undo the selection.

s = window.getSelection();
len = s.toString().length;
s.modify('extend', 'backward', 'character');
if (len < s.toString().length) {
    // It's not at the beginning of the input, restore previous selection
    s.modify('extend', 'forward', 'character');
} else {
    // It's at the end, you can move to the previous input
}

I got this idea from this SO answer: https://stackoverflow.com/a/24247942

Community
  • 1
  • 1
Pre101
  • 2,370
  • 16
  • 23
5

This was happening for us while using the jQuery Numeric Plugin, version 1.3.x, so wrapping the selectionStart and selectionEnd with a try...catch{} we were able to suppress the error.

Source: https://github.com/joaquingatica/jQuery-Plugins/commit/a53f82044759d29ff30bac698b09e3202b456545

jbwebtech
  • 424
  • 6
  • 11
5

As a work-around, the type="tel" input type provides very similar functionality to type="number" in Chrome and does not have this limitation (as pointed out by Patrice).

Kevin Borders
  • 2,933
  • 27
  • 32
  • 1
    fwiw this gives a telephone specific numpad on android whereas number gives a different numpad – patrick Aug 05 '20 at 15:22
3

can't we just reverse the element check to just include the elements that are supported, like this:

if (deviceIsIOS &&
    targetElement.setSelectionRange &&
    (
        targetElement.type === 'text' ||
        targetElement.type === 'search' ||
        targetElement.type === 'password' ||
        targetElement.type === 'url' ||
        targetElement.type === 'tel'
    )
) {

instead of this:

if (deviceIsIOS && 
    targetElement.setSelectionRange && 
    targetElement.type.indexOf('date') !== 0 && 
    targetElement.type !== 'time' && 
    targetElement.type !== 'month'
) {
Jochie Nabuurs
  • 136
  • 1
  • 5
1

While the issue with the deprecated events for this type of form field may be still relevant, as far as I can see, this kind of field can be listened to with the "change" event, as usual.

I got to this article here while looking for an answer to the events issue for this type of field, but while testing it, I found that I can simply rely on the "change" event as mentioned.

TheCuBeMan
  • 1,954
  • 4
  • 23
  • 35
1

I received this error in an angularjs website.

I had created a custom data attribute on an input and was also using ng-blur (with $event).

When my callback was called I was trying to access the 'data-id' value like:

var id = $event.target.attributes["data-id"]

And it should have been like this:

var id = $event.target.attributes["data-id"].value

There doesn't seem to be too much about this error anywhere so I am leaving this here.

Mike Cheel
  • 12,626
  • 10
  • 72
  • 101
0

I know this answer comes too late but here is a plug-in that implements selectionStart for all types of elements, for most of the browsers (old & new):

https://github.com/adelriosantiago/caret

I have updated it to deal with the type="number" issue by using @ncohen 's answer.

adelriosantiago
  • 7,762
  • 7
  • 38
  • 71
0

Solve Failed to read the 'selectionStart' property from 'HTMLInputElement': The input element's type ('number') does not support selection. as follows:

var checkFocus = false;
var originalValue = "";
$(document).ready(function() {
  $("input").focus(function() {
    var that = this;
    if ($('#' + $(this).context.id).attr("type") == "text") {
      setTimeout(function() {
        if (that.selectionStart != null || that.selectionEnd != null) {
          that.selectionStart = that.selectionEnd = 10000;
        }
      }, 0);
    } else if ($('#' + $(this).context.id).attr("type") == "number") {
      if (checkFocus) {
        moveCursorToEnd($('#' + $(this).context.id), $(this).context.id);
        checkValue($('#' + $(this).context.id))
        checkFocus = false;
      } else {
        $('#' + $(this).context.id).blur();
      }
    }
  });
});

function FocusTextBox(id) {
  checkFocus = true;
  document.getElementById(id).focus();
}

function checkValue(input) {
  if ($(input).val() == originalValue || $(input).val() == "") {
    $(input).val(originalValue)
  }
}

function moveCursorToEnd(input, id) {
  originalValue = input.val();
  input.val('');
  document.getElementById(id).focus();
  return originalValue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<button onclick="FocusTextBox('textid')">
   <input id="textid" type="number" value="1234" > 
</button>
gnat
  • 6,213
  • 108
  • 53
  • 73
Cha Mildz
  • 11
  • 2
0

Watch out for false positive on this in Angular if using Chrome's device testing mode.

So this is clearly Chrome and not an actual iPhone - yet the error still gets raised because _platform.IOS returns true and then setSelectionRange subsequently fails.

This is Angular - but similar issues may exist in other frameworks / environments.

enter image description here

Simon_Weaver
  • 140,023
  • 84
  • 646
  • 689
0

You can get the cursor position (selectionStart, selectionEnd) modifying the type attribute in event.target object:

const input = document.querySelector("input");
input.addEventListener("keyup", (e) => {
  const {target} = e;
  target.setAttribute("type", "text")
  console.log(target.selectionStart, target.selectionEnd)
});
<input type="number" />
lissettdm
  • 12,267
  • 1
  • 18
  • 39