Advent of JavaScript, Day 10
Challenge #10 is creating a code verification screen, like you see with 2-factor authentication:
I decided to mix it up and use Svelte instead of Vue for this one!
With the project files downloaded and codesandbox
’d into a live CodeSandbox, I’m ready to get going!
(CodeSandbox lets you drag-and-drop files to upload, too!)
User Requirements
Again, let’s start with the User Requirements and speculate how I can solve these:
- type in a digit and automatically be taken to the next input
I like these UIs because they make the length requirements clear via distinct inputs. However, what’s weird is when you fix one of the digits out of order. Normally, the text would be inserted, but with a fixed amount, then what?
I’m going to limit maxLength
to 1
and onKeyUp
find the next <input>
and .focus()
it.
- paste in a 4-digit code
There’s a paste
event that I can listen to.
When this happens only on the 1st input, hopefully I can capture the full code and replace all <input>
values.
Wiring it Up
-
Adapting to Svelte
Unlike my previous Vue implementations, Svelte’s
index.html
is bare, just like most SPAs.<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>10 - 4 Digit Code || Advent of JavaScript</title> <link rel="stylesheet" href="public/bundle.css" /> </head> <body> <script src="bundle.js"></script> </body> </html>
To be fair, Vue would also be bare if I were using templates & components instead of progressive-enhancement.
But since this is my 1st bundled challenge, it was a bit of a shock to not just “wire things up” and instead have to port code.
I copy/pasted what was inside the original
index.html
’s<body>
intoApp.svelte
:<script> import './public/styles.css' </script> <div class="wrapper"> <h1>Authorization Code</h1> <p>Please enter the code that we sent to (***) *** - 2819.</p> <form action=""> <div class="fields"> <input type="text" maxlength="1" value="1" name="verification_1" /> <input type="text" maxlength="1" value="" name="verification_2" /> <input type="text" maxlength="1" value="" name="verification_3" /> <input type="text" maxlength="1" value="" name="verification_4" /> </div> <button>Verify</button> </form> </div>
Just like in modern bundlers like
esbuild
,rollup
, &webpack
, I canimport "styles.css"
and the bundler figures it out for me.(I prefer this over shoving everything in
/public
– colocation is a good thing.) -
bind:value
Rather than 4 distinct
<input>
fields, I used Svelte’s{#each}
&bind:value={digits[i]}
:<script> let digits = new Array(4) </script> {#each digits as digit, i} <input bind:value="{digits[i]}" min="1" max="9" maxlength="1" size="1" type="number" /> {/each}
Note – There’s apparently no way for vanilla HTML to restrict the
value
to a single 1-9 digit. -
Autofocus Inputs
Using
bind:this
, I assigned each<input>
to theinputs
array and advancedon:input
:<script> import './styles.css' let digits = new Array(4) let inputs = new Array(4) $: code = digits.join('') function nextInput(event) { const input = event.target const i = inputs.indexOf(input) const nextInput = inputs[i + 1] if (nextInput) { nextInput.focus() } else { input.blur() } } </script> {#each digits as digit, i} <input bind:this="{inputs[i]}" bind:value="{digits[i]}" on:input="{nextInput}" min="1" max="9" maxlength="1" size="1" type="number" /> {/each}
-
Handling
paste
eventsThanks to MDN, this was a simple
on:paste|preventDefault
bound tohandlePaste
:function handlePaste(event) { let paste = (event.clipboardData || window.clipboardData).getData('text') digits = paste.split('').slice(0, 4) event.target.blur() }
I replaced
digits
with the value ofpaste
(e.g."1234"
). Svelte is really nice in that it lets me mutate values and I don’t need to manage state directly.Then, I
.blur()
so that extra input isn’t accidentally entered.I decided to allow paste within any of the inputs since the expectation is that the entire code will be in the clipboard anyway.
<input bind:this="{inputs[i]}" on:input="{handleInput}" on:paste|preventDefault="{handlePaste}" ... />
-
1-Digit Only
Eventually I decided to have
on:input
force the single-digit:input.value = input.value[0]
Yes, this assumes the 1st digit is the correct one, but I was getting annoyed by this quirk.
Conclusion
I have to say, despite Svelte not having a CDN, run-time version, it was a breeze to work with in CodeSandbox and I really enjoyed the strict linting.
This was especially true when I did {#each item in items}
and the linter said, “Did you mean as
?”
I’m going to give Svelte another try soon and see when (and if) I run into limitations…