Replacing Double-Dashes With Em Dashes While Typing In JavaScript
One of the little delighters that I see many applications baking into their user experience (UX) is the auto-injection of Em Dashes whenever the user enters two normal dashes (hyphens) in a row. The Em Dash is often used used in lieu of other punctuation marks, such as commas or parenthesis, in order to increase readability or to indicate strong pivots in the sentence. As a fun "code kata", I wanted to see if I could implement this auto-injection of Em Dashes using JavaScript.
Run this demo in my JavaScript Demos project on GitHub.
View this code in my JavaScript Demos project on GitHub.
Officially, when an Em Dash (or an En Dash) is used, it's supposed to butt-up against the words on either side of it. And, in fact, on some platforms, like Medium, the JavaScript controlling the page won't even let you add a space before or after an Em Dash. But, for this code kata, I'm not going to get that formal - I'll leave that as an exercise in measure for the user. In my implementation, I'm simply going to replace two dashes with an em dash.
Now, in my implementation, I've decided to perform the auto-injection at the moment the user requests the second dash (essentially canceling and replacing their keydown event). Other implementations may wait for a space to be entered before executing the replacement. There's no right or wrong approach here - this was just an arbitrary decision.
As a fun caveat to this experiment, I've decided to use event-delegation on the document, targeting any and all inputs that have the ".emdashify" class. And, to demonstrate said targeting, I've included both activated and non-activated inputs in my demo:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>
Replacing Double-Dashes With Em Dashes While Typing In JavaScript
</title>
<link rel="stylesheet" type="text/css" href="./demo.css">
</head>
<body>
<h1>
Replacing Double-Dashes With Em Dashes While Typing In JavaScript
</h1>
<p>
In some of the following textareas, <strong>double-dashes</strong>
will be converted to <strong>em dashes</strong> as you type.
</p>
<textarea placeholder="Enabled..." class="input emdashify"></textarea>
<textarea placeholder="Enabled..." class="input emdashify"></textarea>
<textarea placeholder="Not enabled..." class="input"></textarea>
<script type="text/javascript">
var dash = "-";
var emdash = "-"; // Shift+Option+Hyphen
// Add our keydown event-handler. In this case, we're going to listen at the
// document level using event-delegation. Then, we'll activate our logic for
// inputs that contain the "emdashify" class token.
document.addEventListener( "keydown", handleKeydown, false );
// --------------------------------------------------------------------------- //
// --------------------------------------------------------------------------- //
// I handle the keydown event. If the event is for a DASH that is about to
// follow an existing DASH, then we will replace the double-DASH with an EMDASH.
function handleKeydown( event ) {
var target = event.target;
// We only care about the dash - ignore any other key events at this time.
if ( event.key !== dash ) {
return;
}
// If the target input does not contain the "emdashify" class, then we don't
// want to alter they keydown behavior.
if ( ! target.classList.contains( "emdashify" ) ) {
return;
}
// Get the current offset of the carrot / cursor. This represents the
// location of the keydown event BEFORE the impending character has been
// added to the input value.
var offset = target.selectionStart;
// If the character right before the impending dash is another dash, then we
// want to cancel the current keyboard event (such that the impending dash
// is not rendered) and replace the existing dash with the emdash.
// --
// NOTE: If the offset is currently 0 (zero), then the -1 will return
// undefined, which will fail the === appropriately.
if ( target.value[ offset - 1 ] === dash ) {
event.preventDefault();
// Calculate the substrings that come before and after the dash that
// we're about to replace.
var beforeDash = target.value.slice( 0, ( offset - 1 ) );
var afterDash = target.value.slice( offset );
// Replace the existing dash with the emdash.
target.value = ( beforeDash + emdash + afterDash );
// In order to prevent the cursor from jumping to the end of the input
// value after we've set it programmatically, we have to explicitly
// define the selection as staying in the same place that it was before.
target.selectionStart = offset;
target.selectionEnd = offset;
}
}
</script>
</body>
</html>
As you can see, the algorithm is such that when I detect a "-" key is about to be entered, I look to see if the existing character at the given selection-start is also a dash. If it is, I cancel the current keydown behavior (thereby preventing the requested "-" character from getting added to the input); and, I replace the existing dash with an em dash.
If we run this code and enter some double-dashes, we get the following output:
As you can see, the first two inputs, which have the "emdashify" class, successfully auto-replace the double-dash with an em dash. And, the third input, which is not "emdashified", allows double-dashes to be entered just as the user typed them.
One thing to keep in mind is that an input doesn't emit an "input" event when the value is changed programmatically. As such, if you override the value being entered, other handlers that are watching for "input" events won't see the change you added. This isn't a problem, per-say; it's just something to be aware of in the context of a larger application interface.
Anyway, this was just a fun little JavaScript experiment. I like the look and feel of an Em Dash. And, I like that a lot of applications are facilitating their use with these kind of auto-injection delighters. It's a good tool to have in the back of my toolbox.
Want to use code from this post? Check out the license.
Reader Comments
@All,
After posting this, I got some great feedback from a few people at work:
Johannes Hoff suggested that I should include
target.selectionEnd
when slicing the text so that I remove any highlighted text, which would be more in line with the user's intent.Don Abrams suggested that there should be a way to undo the auto-replacement in cases where a user actually does want to type
--
.I'll let this marinate in the back of my mind and see what I can come up with.