8
\$\begingroup\$

I have an input element on one of the pages. This input is supposed to take a string of digits from a user. In order to enhance UX I'd like to mask input's value at any given time. Like so: enter image description here

What came up with as a solution is the following piece of code:

(function () {
  var input = document.querySelector('input');

  var state = '';

  input.addEventListener('keyup', function (e) {
    if ( e.key.match(/\d/) )     state += e.key;
    if ( e.key === 'Backspace' ) state = state.slice(0, -1);

    var degree = state.slice(0,2),
        minute = state.slice(2,4),
        second = state.slice(4,6);

    if (degree) degree += '°';
    if (minute) minute += "'";
    if (second) second += '"';

    input.value = [degree, minute, second].join(' ');
  });
})();
<input type="text" />

I do not claim it to be perfect, so I very much appreciate any input.

\$\endgroup\$
1
\$\begingroup\$

Correctly implementing auto-masking in a text input, and handling all the edge cases like pasting, cursor placement and deletion, etc -- is extremely hard.

UX Walkthrough

Let's walk through your implementation, from a pure UX perspective. We'll start with the example in your gif:

  1. I want to enter 12 degrees, so I focus the input and press 1, I see:

Whoa! I wanted to to enter 12, not 1. Do I need to reposition the cursor with my mouse to add the 2? Hmmm... maybe I'll just try pressing 2 anyway:

Okay, that worked. Cool. A little weird, but okay...

  1. I'm getting the hang of it now: I type, you autoformat me. So I quickly type 34 now, but I mistype and enter 24, I see:

Crap. Okay, I can fix that. I put my cursor between the 2 and 4:

and press "delete":

Wtf!? I was trying to delete the 2, but it looks like the 4 got deleted, and the cursor jumped to the end again. Hmmm... okay, guess I should have deleted the entire 24 and retyped 34. Not a huge deal in this case, but what if I had typed out the entire thing and realized that it should have been 22 34 56 instead of 12 34 56. That would be annoying. I'd have to delete everything and retype it all.

Anyway, I think I see how this thing works now...

  1. Next day I come back, and luckily I have today's entry, 234567, in my clipboard now, so I can paste it and avoid some typing. I paste it in, I see my numbers flash for a second, and then the input is blank again. Ugh, I guess I'll have to retype it all...

  2. A separate point is that it's not indicated to the user the entry expects leading zeros (eg, with a placeholder example). So if I want to enter 1 degrees, 23 mintues..., I'll probably type a 1 and 2 before realizing my mistake, and I'll have to guess at how to fix it:

Summing up...

Here's a non-exhaustive list of things that "just work" in an ordinary text input, that you need to properly re-implement:

  1. Manual cursor placement with deletion and re-entry
  2. Manual highlighting and "retyping over"
  3. Pasting a full entry
  4. Partial pasting (meaning type in the first half, paste in the second)
  5. Partial pasting out of order (meaning type the whole thing, highlight the first half, or a middle part, then paste over -- the highlighted part should be correctly replaced with the pasted portion.

What does this all mean?

You probably shouldn't do this, and if you do, expect to spend a lot of time testing to get it right (we haven't even mentioned cross-browser differences yet).

Other Alternatives

  1. Auto-tabbing (easier, but a bit tricky in itself) is one alternative, as others have mentioned. With some nice CSS to hide the text input borders, etc, you can make this look pretty good and closely mimic the experience you're attempting with the masking.

  2. Use a single, default text input (raw), and show the user's formatted text next to it, above it, or below it. This is, admittedly, not as slick as a perfectly executed auto-masker would be, but it's much easier to get right, and much less likely to confuse or frustrate your user.

  3. Use a well-tested library. Not sure if this one is perfect, and it requires jquery, but http://digitalbush.com/projects/masked-input-plugin/ addresses at least most of the concerns I've raised.

\$\endgroup\$
5
\$\begingroup\$

Unless you know for sure that the degrees, minutes, and seconds are always going to be 2 digits each, this approach will cause issues. I would recommend using 3 separate inputs for each value and automatically tabbing over to the next one when the first one is filled.

If you do know they will always be 2 digits, why not just concat the value with + instead of putting them in an array and joining them?

\$\endgroup\$
  • \$\begingroup\$ Coincedentally in the specs of this component latitude value was defined as three separate input boxes, but I specifically asked PM to let me make a single field input mask. I thought it must be less hassle \$\endgroup\$ – askhat Jun 28 '17 at 13:57
  • \$\begingroup\$ Concatination is not in help, because I can't let unit character without actual digits leak in to resulting value. That is why last if block defined, join() actually joins undefined in many cases \$\endgroup\$ – askhat Jun 28 '17 at 14:01
  • 4
    \$\begingroup\$ Be careful if you auto-tab on a filled input. I've seen a lot of UXs break the interface doing that, because if you have to go back to edit an already-filled input box, it will just automatically tab you over to the next input still. So make sure and don't do that. \$\endgroup\$ – Conor Mancone Jun 28 '17 at 16:17
2
\$\begingroup\$

Your code looks pretty simple and clean.

You just need to make sure that all features that a normal field has are still working in your 'latitude-field'. Every time that I create a new field-like component I ask these questions:

  • What happens when the user copy-past the content in the field?
  • What happens when the user try to type letters/invalid characters in the field?
  • If need, there is a good way to get the raw user input from the field?

Other than that, I would say that your code is pretty good.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.