{"id":1639,"date":"2019-03-05T05:05:24","date_gmt":"2019-03-05T05:05:24","guid":{"rendered":"https:\/\/blog.hassler.ec\/wp\/?p=1639"},"modified":"2019-02-28T03:22:49","modified_gmt":"2019-02-28T03:22:49","slug":"how-to-detect-a-sequence-of-keystrokes-in-javascript","status":"publish","type":"post","link":"https:\/\/blog.hassler.ec\/wp\/2019\/03\/05\/how-to-detect-a-sequence-of-keystrokes-in-javascript\/","title":{"rendered":"How to detect a sequence of keystrokes in JavaScript"},"content":{"rendered":"<div class=\"section-inner sectionLayout--insetColumn\">\n<h1 id=\"c38d\" class=\"graf graf--h3 graf--leading graf--title\"><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" style=\"font-size: 14px;\" src=\"https:\/\/cdn-images-1.medium.com\/max\/1000\/0*3w4fVWLvNPtxYDq0\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/1000\/0*3w4fVWLvNPtxYDq0\"><\/h1>\n<\/div>\n<div class=\"section-inner sectionLayout--outsetColumn\">\n<figure id=\"8f73\" class=\"graf graf--figure graf--layoutOutsetCenter graf-after--h3\" data-scroll=\"native\"><figcaption class=\"imageCaption\">Photo by&nbsp;<a class=\"markup--anchor markup--figure-anchor\" href=\"https:\/\/unsplash.com\/@lemonzandtea?utm_source=medium&amp;utm_medium=referral\" target=\"_blank\" rel=\"photo-creator nofollow noopener\" data-href=\"https:\/\/unsplash.com\/@lemonzandtea?utm_source=medium&amp;utm_medium=referral\">Aleksandar Cvetanovic<\/a>&nbsp;on&nbsp;<a class=\"markup--anchor markup--figure-anchor\" href=\"https:\/\/unsplash.com\/?utm_source=medium&amp;utm_medium=referral\" target=\"_blank\" rel=\"photo-source nofollow noopener\" data-href=\"https:\/\/unsplash.com?utm_source=medium&amp;utm_medium=referral\">Unsplash<\/a><\/figcaption><\/figure>\n<\/div>\n<div class=\"section-inner sectionLayout--insetColumn\">\n<p id=\"6b59\" class=\"graf graf--p graf-after--figure\">One of the most used features of JavaScript is its ability to react on various events that may occur while the user interacts with the web page. In fact, that was the very idea of JavaScript when it appeared, to make web pages dynamic by adding some interactivity to them. With JavaScript we can react when a user clicks on a specific part of the page, when a key is pressed, when mouse moves, when the page is loaded, when an element gets focused, etc.<\/p>\n<blockquote id=\"7638\" class=\"graf graf--blockquote graf-after--p\"><p>For the list of all JavaScript events available&nbsp;<a class=\"markup--anchor markup--blockquote-anchor\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Events\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Events\">check this MDN page<\/a>.<\/p><\/blockquote>\n<p id=\"84c4\" class=\"graf graf--p graf-after--blockquote\">Even though today JavaScript can be used to do so much more, this basic feature of adding interactivity to the web page is still widely used to provide the users with rich and interesting experiences.<\/p>\n<p id=\"42e5\" class=\"graf graf--p graf-after--p\">In this article, we will be looking into how we can use JavaScript to react to the keyboard events, specifically, how to react to the specific key sequence that the user types. So, when the user presses a key combination, the web page might show some content, like opening a menu or a modal, it can change the styling of the page or perform any other action you can imagine which is within the JavaScript capabilities.<\/p>\n<figure id=\"80ed\" class=\"graf graf--figure graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*mY6ZUX9rxe6bPEirIjpOZw.gif\" data-width=\"384\" data-height=\"270\" data-scroll=\"native\"><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*mY6ZUX9rxe6bPEirIjpOZw.gif\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*mY6ZUX9rxe6bPEirIjpOZw.gif\"><\/div>\n<\/div>\n<\/figure>\n<p id=\"5d8e\" class=\"graf graf--p graf-after--figure\"><em class=\"markup--em markup--p-em\">Before we start I just want to point out that I\u2019m using some ES6 features like&nbsp;<\/em><code class=\"markup--code markup--p-code\"><a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Statements\/const\" target=\"_blank\" rel=\"nofollow noopener nofollow noopener\" data-href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Statements\/const\"><em class=\"markup--em markup--p-em\">const<\/em><\/a><\/code><em class=\"markup--em markup--p-em\">and<\/em><code class=\"markup--code markup--p-code\"><a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Statements\/let\" target=\"_blank\" rel=\"nofollow noopener nofollow noopener\" data-href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Statements\/let\"><em class=\"markup--em markup--p-em\">let<\/em><\/a><\/code><em class=\"markup--em markup--p-em\">, the&nbsp;<\/em><a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Operators\/Spread_syntax\" target=\"_blank\" rel=\"nofollow noopener nofollow noopener\" data-href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Operators\/Spread_syntax\"><em class=\"markup--em markup--p-em\">spread operator<\/em><\/a><em class=\"markup--em markup--p-em\">&nbsp;(<\/em><code class=\"markup--code markup--p-code\"><em class=\"markup--em markup--p-em\">...<\/em><\/code><em class=\"markup--em markup--p-em\">),&nbsp;<\/em><a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Functions\/Arrow_functions\" target=\"_blank\" rel=\"nofollow noopener nofollow noopener\" data-href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Functions\/Arrow_functions\"><em class=\"markup--em markup--p-em\">arrow functions<\/em><\/a><em class=\"markup--em markup--p-em\">,&nbsp;<\/em><a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/New_in_JavaScript\/ECMAScript_2015_support_in_Mozilla\" target=\"_blank\" rel=\"nofollow noopener nofollow noopener\" data-href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/New_in_JavaScript\/ECMAScript_2015_support_in_Mozilla\"><em class=\"markup--em markup--p-em\">etc.<\/em><\/a><em class=\"markup--em markup--p-em\">&nbsp;If you are not familiar with those, check the provided links to learn more.<\/em><\/p>\n<h3 id=\"7c79\" class=\"graf graf--h3 graf-after--p\">What are we building?<\/h3>\n<p id=\"ced5\" class=\"graf graf--p graf-after--h3\">For this article I\u2019ve decided to make an element on the page change its background image based on the sequence of keys the user types. So we are basically dealing with the styling, nothing too fancy. We will also add some text updates as well just to make the demonstration richer and to provide more info to the user.<\/p>\n<p id=\"84ab\" class=\"graf graf--p graf-after--p\">My idea was to use the key sequences that are used as cheat codes in an old FPS game, Doom and Doom 2. Basically, when the user types a key combination, the game enables some benefits to the user, like more ammo, more health, invulnerability, etc.<\/p>\n<p id=\"018c\" class=\"graf graf--p graf-after--p\">In the example that we will build, we will make the background image for an element on the page to change based on the key sequence typed in by the user. You can see and try the demo&nbsp;<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/gruximillian.github.io\/sequential-keybinding\/\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/gruximillian.github.io\/sequential-keybinding\/\">here<\/a>.<\/p>\n<h3 id=\"cf35\" class=\"graf graf--h3 graf-after--p\">Basic project<\/h3>\n<p id=\"992f\" class=\"graf graf--p graf-after--h3\">In order to focus only on JavaScript, I\u2019ve created complete HTML and CSS for the project, and linked the JavaScript file which only contains a console log to make sure it works. You can download the initial project files&nbsp;<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/github.com\/Gruximillian\/sequential-keybinding\/tree\/01-project-structure\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/github.com\/Gruximillian\/sequential-keybinding\/tree\/01-project-structure\">here<\/a>&nbsp;or you can create your own if you like to have it made your own way.<\/p>\n<p id=\"fa0e\" class=\"graf graf--p graf-after--p\">Here is quick breakdown of the JavaScript content we initially have.<\/p>\n<figure id=\"ba79\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>document.addEventListener('DOMContentLoaded', () =&gt; {\n    'use strict';\n\n    console.log('content loaded');\n});<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"8d8b\" class=\"graf graf--p graf-after--figure\">It\u2019s pretty straightforward, we add an event listener to the document that waits until the DOM is fully loaded and then calls the callback function. The callback function currently only has the&nbsp;<code class=\"markup--code markup--p-code\">'use strict'<\/code>&nbsp;statement and a console log to verify that the file is correctly linked to the HTML page.<\/p>\n<blockquote id=\"e937\" class=\"graf graf--blockquote graf-after--p\"><p>We can omit this event listener and the callback that is passed to it, but then the script file should be placed at the end of the&nbsp;<code class=\"markup--code markup--blockquote-code\">body<\/code>&nbsp;HTML element to make sure that the DOM is fully loaded when the javascript starts executing. If you choose so, feel free to do it. This is just my preferred way of doing it, with the benefit that it also encapsulates all of the JavaScript code into a function and avoids global scope pollution.<\/p><\/blockquote>\n<h4 id=\"b6ef\" class=\"graf graf--h4 graf-after--blockquote\">Listening to the key&nbsp;Events<\/h4>\n<p id=\"2900\" class=\"graf graf--p graf-after--h4\">The next step would be to detect when the user presses a key. We do that the same way we added the listener for the&nbsp;<code class=\"markup--code markup--p-code\">DOMContentLoaded<\/code>&nbsp;event, but now we will specify a different event name. There are&nbsp;<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Events#Keyboard_Events\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Events#Keyboard_Events\">three keyboard events<\/a>&nbsp;that we can specify:&nbsp;<code class=\"markup--code markup--p-code\">keydown<\/code>,&nbsp;<code class=\"markup--code markup--p-code\">keyup<\/code>&nbsp;and&nbsp;<code class=\"markup--code markup--p-code\">keypress<\/code>.<\/p>\n<ul class=\"postList\">\n<li id=\"6cb4\" class=\"graf graf--li graf-after--p\"><code class=\"markup--code markup--li-code\">keydown<\/code>&nbsp;&#8211; fires immediately when the key is pressed<\/li>\n<li id=\"1ef5\" class=\"graf graf--li graf-after--li\"><code class=\"markup--code markup--li-code\">keyup<\/code>&nbsp;&#8211; fires when the key is released<\/li>\n<li id=\"c532\" class=\"graf graf--li graf-after--li\"><code class=\"markup--code markup--li-code\">keypress<\/code>&nbsp;&#8211; fires continuously while key is held pressed<\/li>\n<\/ul>\n<p id=\"c1a4\" class=\"graf graf--p graf-after--li\">From this description we can immediately see that the&nbsp;<code class=\"markup--code markup--p-code\">keypress<\/code>&nbsp;is definitely not suitable for our purpose. That leaves us with&nbsp;<code class=\"markup--code markup--p-code\">keydown<\/code>and<code class=\"markup--code markup--p-code\">keyup<\/code>. In our case both of these will do well, but in a different situation one of them might be more suitable than the other, so you will need to assess the situation and choose accordingly.<\/p>\n<p id=\"12af\" class=\"graf graf--p graf-after--p\">The only difference we can notice here is that if we use&nbsp;<code class=\"markup--code markup--p-code\">keyup<\/code>&nbsp;it might feel a bit laggy, while&nbsp;<code class=\"markup--code markup--p-code\">keydown<\/code>&nbsp;really feels like the reaction is immediate. I will choose&nbsp;<code class=\"markup--code markup--p-code\">keydown<\/code>, but since the same can be achieved with&nbsp;<code class=\"markup--code markup--p-code\">keyup<\/code>, feel free to use the event you like more.<\/p>\n<p id=\"5713\" class=\"graf graf--p graf-after--p\">Now, let\u2019s add the event listener and remove the console log:<\/p>\n<figure id=\"f355\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>document.addEventListener('DOMContentLoaded', () =&gt; {\n    'use strict';\n\n    document.addEventListener('keydown', event =&gt; {\n\n    });\n});<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"38e5\" class=\"graf graf--p graf-after--figure\">There are two important things to notice here. First, we have provided the&nbsp;<code class=\"markup--code markup--p-code\">keydown<\/code>&nbsp;event name to the<code class=\"markup--code markup--p-code\">addEventListener<\/code>, and we are providing the&nbsp;<code class=\"markup--code markup--p-code\">event<\/code>&nbsp;parameter to the event handler callback function. We will need that&nbsp;<code class=\"markup--code markup--p-code\">event<\/code>&nbsp;parameter to check which key is being pressed.<\/p>\n<p id=\"cbe0\" class=\"graf graf--p graf-after--p\">So, to get the key that has been pressed we can access the&nbsp;<code class=\"markup--code markup--p-code\">key<\/code>&nbsp;property of the&nbsp;<code class=\"markup--code markup--p-code\">event<\/code>&nbsp;object:<\/p>\n<figure id=\"01cf\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>document.addEventListener('DOMContentLoaded', () =&gt; {\n    'use strict';\n\n    document.addEventListener('keydown', event =&gt; {\n        const key = event.key.toLowerCase();\n        console.log(key);\n    });\n});<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<blockquote id=\"63b2\" class=\"graf graf--blockquote graf-after--figure\"><p><strong class=\"markup--strong markup--blockquote-strong\">Hint:<\/strong>&nbsp;To see which properties of the&nbsp;<code class=\"markup--code markup--blockquote-code\">event<\/code>&nbsp;object are available, simply do&nbsp;<code class=\"markup--code markup--blockquote-code\">console.log(event)<\/code>&nbsp;and look at the logged object. I know this might seem obvious, but if you are a beginner you might try and look online for available options, which is fine, but the answer is usually just a console log away.<\/p><\/blockquote>\n<p id=\"c4be\" class=\"graf graf--p graf-after--blockquote\">After getting the&nbsp;<code class=\"markup--code markup--p-code\">event.key<\/code>, we are transforming it to lower case because it would be good to make these keyboard shortcuts case insensitive. It won&#8217;t be the case every time, but this time, it&#8217;s perfectly fine.<\/p>\n<p id=\"0582\" class=\"graf graf--p graf-after--p\">If you open the browser console, click back at the page and try to type something, you can see that the key that you pressed is logged to the console. But if you are attentive you might notice that it logs ANY key that is pressed, meaning keys like&nbsp;<code class=\"markup--code markup--p-code\">Enter<\/code>,<code class=\"markup--code markup--p-code\">Delete<\/code>,&nbsp;<code class=\"markup--code markup--p-code\">Backspace<\/code>, etc. are also logged. That is great and can be useful, but in this case I want to use only letters and digits.<\/p>\n<p id=\"84a9\" class=\"graf graf--p graf-after--p\">Therefore, let&#8217;s add a check if the key that&#8217;s being pressed is a letter or a digit.<\/p>\n<figure id=\"142f\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>document.addEventListener('DOMContentLoaded', () =&gt; {\n    'use strict';\n\n    document.addEventListener('keydown', event =&gt; {\n        const charList = 'abcdefghijklmnopqrstuvwxyz0123456789';\n        const key = event.key.toLowerCase();\n\n        \/\/ we are only interested in alphanumeric keys\n        if (charList.indexOf(key) === -1) return;\n\n        console.log(key);\n    });\n});<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"2ce7\" class=\"graf graf--p graf-after--figure\">By creating the&nbsp;<code class=\"markup--code markup--p-code\">charList<\/code>&nbsp;variable which contains all the characters we are interested in, we are able to check if the&nbsp;<code class=\"markup--code markup--p-code\">key<\/code>&nbsp;is present in the&nbsp;<code class=\"markup--code markup--p-code\">charList<\/code>string, and if not, we simply return from the function without logging the character.<\/p>\n<p id=\"da76\" class=\"graf graf--p graf-after--p\">Now that we are listening to the key events and are able to filter only the keys we are interested in, it is time to see how we can save a sequence of entered keys.<\/p>\n<h4 id=\"db40\" class=\"graf graf--h4 graf-after--p\">Saving the entered key&nbsp;sequence<\/h4>\n<p id=\"8549\" class=\"graf graf--p graf-after--h4\">To save the sequence of entered keys we need to add a variable that will store that sequence and which we can update on every key event that we are interested in.<\/p>\n<p id=\"547a\" class=\"graf graf--p graf-after--p\">We could use a string that we can concatenate keys on, but we can also use an array in which we can store individual keys as the array items. In this simple use case, it doesn\u2019t matter much what we use, but I\u2019ll go with the array approach.<\/p>\n<p id=\"5fac\" class=\"graf graf--p graf-after--p\">The array approach is a suggested approach over the string concatenation in cases where a lot of strings have to be concatenated, and it also allows us to use all of the array methods if we need them.<\/p>\n<p id=\"098c\" class=\"graf graf--p graf-after--p\">Let\u2019s create a variable called&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>, an empty array initially, and push the new keys into it.<\/p>\n<figure id=\"1ed4\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>document.addEventListener('DOMContentLoaded', () =&gt; {\n    'use strict';\n\n    let buffer = [];\n\n    document.addEventListener('keydown', event =&gt; {\n        const charList = 'abcdefghijklmnopqrstuvwxyz0123456789';\n        const key = event.key.toLowerCase();\n\n        \/\/ we are only interested in alphanumeric keys\n        if (charList.indexOf(key) === -1) return;\n\n        buffer.push(key);\n        console.log(buffer);\n    });\n});<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"bdf0\" class=\"graf graf--p graf-after--figure\">If we define the&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>&nbsp;array inside the&nbsp;<code class=\"markup--code markup--p-code\">keydown<\/code>&nbsp;event listener, it will be reset every time a key is pressed, and the entered sequence of keys will be lost. Because of that we need to define it outside of the&nbsp;<code class=\"markup--code markup--p-code\">keydown<\/code>&nbsp;event listener. Now we are able to save all of the entered keys.<\/p>\n<p id=\"6a55\" class=\"graf graf--p graf-after--p\">But if we look at the log, we can notice that now we are saving the keys all the time, and the array just grows with new keys being pressed. Check the gif bellow to see the result.<\/p>\n<figure id=\"1374\" class=\"graf graf--figure graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\"><\/div>\n<div class=\"progressiveMedia js-progressiveMedia graf-image is-canvasLoaded is-imageLoaded\" data-image-id=\"1*Xi38dQwHETPTMx9Bs4WkDQ.gif\" data-width=\"918\" data-height=\"510\" data-action=\"zoom\" data-action-value=\"1*Xi38dQwHETPTMx9Bs4WkDQ.gif\" data-scroll=\"native\"><img decoding=\"async\" class=\"progressiveMedia-image js-progressiveMedia-image\" src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*Xi38dQwHETPTMx9Bs4WkDQ.gif\" data-src=\"https:\/\/cdn-images-1.medium.com\/max\/800\/1*Xi38dQwHETPTMx9Bs4WkDQ.gif\"><\/div>\n<\/div><figcaption class=\"imageCaption\">We are constantly adding keystrokes into the buffer&nbsp;array<\/figcaption><\/figure>\n<p id=\"c76b\" class=\"graf graf--p graf-after--figure\">See how after writing \u2018hello\u2019 and some delay, when I continue typing, it still has all of the previous values in the array. That is not very useful for what we want. It would be good to reset the list of entered keys after a certain time has passed since the user entered the last key.<\/p>\n<p id=\"6abd\" class=\"graf graf--p graf-after--p\">For the condition to do that we can use the time interval between the last two key presses. If that interval is longer than the specified amount of time, then we will reset the&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>&nbsp;array. Let&#8217;s do that next.<\/p>\n<h4 id=\"2af1\" class=\"graf graf--h4 graf-after--p\">Limit the time interval between key&nbsp;presses<\/h4>\n<p id=\"3d18\" class=\"graf graf--p graf-after--h4\">For this we need to compare when the last and the current keystroke happened and then check if the time between those was greater than some desired time delay, like 1 second, 0.5 seconds, whatever you find most convenient.<\/p>\n<p id=\"e353\" class=\"graf graf--p graf-after--p\">So, we need to have at least one new variable to save the last keystroke time. In order to be able to check that value, we need to define the variable outside the event listener and then update it after keystroke happened.<\/p>\n<figure id=\"2e04\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>document.addEventListener('DOMContentLoaded', () =&gt; {\n    'use strict';\n\n    let buffer = [];\n    let lastKeyTime = Date.now();\n\n    document.addEventListener('keydown', event =&gt; {\n        const charList = 'abcdefghijklmnopqrstuvwxyz0123456789';\n        const key = event.key.toLowerCase();\n\n        \/\/ we are only interested in alphanumeric keys\n        if (charList.indexOf(key) === -1) return;\n\n        const currentTime = Date.now();\n\n        if (currentTime - lastKeyTime &gt; 1000) {\n            buffer = [];\n        }\n\n        buffer.push(key);\n        lastKeyTime = currentTime;\n\n        console.log(buffer);\n    });\n});<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"9e11\" class=\"graf graf--p graf-after--figure\">We introduced two new variables,&nbsp;<code class=\"markup--code markup--p-code\">lastKeyTime<\/code>&nbsp;and&nbsp;<code class=\"markup--code markup--p-code\">currentTime<\/code>. We can do it with just&nbsp;<code class=\"markup--code markup--p-code\">lastKeyTime<\/code>, but it is much easier to reason about with&nbsp;<code class=\"markup--code markup--p-code\">currentTime<\/code>&nbsp;variable present. Let&#8217;s see what we did here.<\/p>\n<p id=\"5893\" class=\"graf graf--p graf-after--p\">First, we defined&nbsp;<code class=\"markup--code markup--p-code\">lastKeyTime<\/code>&nbsp;variable and initialized it with a current time value. We had to initialize it because we need some value to perform a calculation bellow. We can use zero as a value here, but to make it consistent, let&#8217;s use the current time value.<\/p>\n<p id=\"b746\" class=\"graf graf--p graf-after--p\">Next, we defined&nbsp;<code class=\"markup--code markup--p-code\">currentTime<\/code>&nbsp;variable and initialized it with the current time as well. At this point someone might ask why two variables that have the same value?! Bare in mind that those are just initialization values, at least for&nbsp;<code class=\"markup--code markup--p-code\">lastKeyTime<\/code>. We will need to update that value after every keystroke. And notice that&nbsp;<code class=\"markup--code markup--p-code\">lastKeyTime<\/code>&nbsp;is initialized outside the event listener, that is, only once, while&nbsp;<code class=\"markup--code markup--p-code\">currentTime<\/code>&nbsp;will be reinitialized on every keystroke.<\/p>\n<p id=\"42af\" class=\"graf graf--p graf-after--p\">Now we have an important part, the check whether enough time has passed between the keystrokes. If it has, we want to reset the&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>&nbsp;variable, the array that holds characters that user has pressed.<\/p>\n<p id=\"4b53\" class=\"graf graf--p graf-after--p\">To do that check we simply subtract&nbsp;<code class=\"markup--code markup--p-code\">lastKeyTime<\/code>from<code class=\"markup--code markup--p-code\">currentTime<\/code>&nbsp;variable and check if the result is greater than some number. In the example, that number is 1000, that is 1000 milliseconds or 1 second. That is a good starting value, and it can be changed later to better tune the experience.<\/p>\n<p id=\"f223\" class=\"graf graf--p graf-after--p\">So, if the condition&nbsp;<code class=\"markup--code markup--p-code\">currentTime - lastKeyTime &gt; 1000<\/code>&nbsp;is true, we are resetting the&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>&nbsp;to an empty array:&nbsp;<code class=\"markup--code markup--p-code\">buffer = []<\/code>; After that, the&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>will receive a new key, but it will be the only one in the array.<\/p>\n<p id=\"3464\" class=\"graf graf--p graf-after--p\">The last thing we need to do here is to update the&nbsp;<code class=\"markup--code markup--p-code\">lastKeyTime<\/code>&nbsp;value. We simply set its value to be the same as&nbsp;<code class=\"markup--code markup--p-code\">currentTime<\/code>&nbsp;value. On the next keystroke,&nbsp;<code class=\"markup--code markup--p-code\">lastKeyTime<\/code>&nbsp;will hold the time when the last keystroke happened while&nbsp;<code class=\"markup--code markup--p-code\">currentTime<\/code>&nbsp;will get a new value.<\/p>\n<h4 id=\"795b\" class=\"graf graf--h4 graf-after--p\">Updating the background image<\/h4>\n<p id=\"f802\" class=\"graf graf--p graf-after--h4\">Finally, we are ready to do what we wanted, change the background based on the sequence of keys that user has entered.<\/p>\n<p id=\"04a1\" class=\"graf graf--p graf-after--p\">If the user enters the correct key sequence, we need to grab an element on the page and update its background according to the user input. That seems like we need to have a check for the correct key sequence.<\/p>\n<p id=\"8f83\" class=\"graf graf--p graf-after--p\">While that is almost certainly true in most cases, in this one it is not necessary. The nature of this problem allows us to skip the check if we are ok with what happens if the key sequence is not correct.<\/p>\n<p id=\"2583\" class=\"graf graf--p graf-after--p\">What do I mean by this? Let me explain in detail.<\/p>\n<p id=\"ea6b\" class=\"graf graf--p graf-after--p\">If we want to change the background image using CSS, we will need to update the url for the image. If the images are named the same as the key sequences, then all we need to do is to read the input, make it a string, and set that as the url for the background image.<\/p>\n<p id=\"3d93\" class=\"graf graf--p graf-after--p\">The interesting thing is that if the image does not exist, the background will not be shown. Which means that in cases where we do have an image displayed, and then type something that does not match an existing image url, the background image will be gone.<\/p>\n<p id=\"893d\" class=\"graf graf--p graf-after--p\">In this case I am ok with that, therefore the check if the url is correct is not needed. But if we would like to keep the currently displayed image, then that check will be necessary. Let\u2019s do it the simple way, that is, without that check.<\/p>\n<figure id=\"0469\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>document.addEventListener('DOMContentLoaded', () =&gt; {\n    'use strict';\n\n    let buffer = [];\n    let lastKeyTime = Date.now();\n\n    document.addEventListener('keydown', event =&gt; {\n        const charList = 'abcdefghijklmnopqrstuvwxyz0123456789';\n        const key = event.key.toLowerCase();\n\n        \/\/ we are only interested in alphanumeric keys\n        if (charList.indexOf(key) === -1) return;\n\n        const currentTime = Date.now();\n\n        if (currentTime - lastKeyTime &gt; 1000) {\n            buffer = [];\n        }\n\n        buffer.push(key);\n        lastKeyTime = currentTime;\n\n        const container = document.querySelector('#background');\n        container.style.backgroundImage = `url(images\/${buffer.join('')}.jpg)`;\n    });\n});<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"ac2f\" class=\"graf graf--p graf-after--figure\">We have removed&nbsp;<code class=\"markup--code markup--p-code\">console.log<\/code>, and instead added a query to get the element on which the background will be displayed, and in the next line we apply the background url.<\/p>\n<p id=\"b711\" class=\"graf graf--p graf-after--p\">This url structure will depend on your project structure, in this case, the images are in the &#8216;images&#8217; folder and are in jpg format. Important to notice here is that we need to create a string out of the&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>&nbsp;array so that it can be used in image url. We do that by simply joining the array elements with an empty character as the join character,&nbsp;<code class=\"markup--code markup--p-code\">buffer.join('')<\/code>.<\/p>\n<p id=\"c615\" class=\"graf graf--p graf-after--p\">With this we have achieved the main goal of this article. Now the user can try to enter the correct key sequence and if it is successful, the background will change. What we have done is simple and functional and can be easily incorporated into any project.<\/p>\n<p id=\"906f\" class=\"graf graf--p graf-after--p\">But it is still possible to make some improvements. So, if you like to have more flexibility in your code, keep on reading.<\/p>\n<h3 id=\"f7aa\" class=\"graf graf--h3 graf-after--p\">Improving the&nbsp;project<\/h3>\n<p id=\"c407\" class=\"graf graf--p graf-after--h3\">Looking at our code in whole shows us that we are using some \u2018global\u2019 variables. Global in the sense that they are in the top context of our script, enclosed in the&nbsp;<code class=\"markup--code markup--p-code\">DOMContentLoaded<\/code>&nbsp;event listener function where all the rest of the code will be, meaning that they will mix with other variables in the top level context of our script.<\/p>\n<p id=\"5a1c\" class=\"graf graf--p graf-after--p\">But the declaration of the variables&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>and<code class=\"markup--code markup--p-code\">lastKeyTime<\/code>&nbsp;can&#8217;t go inside the&nbsp;<code class=\"markup--code markup--p-code\">keydown<\/code>&nbsp;event listener because that will reset them with every keystroke and break the functionality.<\/p>\n<h4 id=\"9fcb\" class=\"graf graf--h4 graf-after--p\">Wrapping the code into a&nbsp;function<\/h4>\n<p id=\"acdb\" class=\"graf graf--p graf-after--h4\">To isolate the code that controls our key events, we can put variables and the&nbsp;<code class=\"markup--code markup--p-code\">keydown<\/code>&nbsp;event listener into a separate function.<\/p>\n<figure id=\"01fd\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>document.addEventListener('DOMContentLoaded', () =&gt; {\n    'use strict';\n\n    keyMapper();\n});\n\nfunction keyMapper() {\n    let buffer = [];\n    let lastKeyTime = Date.now();\n\n    document.addEventListener('keydown', event =&gt; {\n        const charList = 'abcdefghijklmnopqrstuvwxyz0123456789';\n        const key = event.key.toLowerCase();\n\n        \/\/ we are only interested in alphanumeric keys\n        if (charList.indexOf(key) === -1) return;\n\n        const currentTime = Date.now();\n\n        if (currentTime - lastKeyTime &gt; 1000) {\n            buffer = [];\n        }\n\n        buffer.push(key);\n        lastKeyTime = currentTime;\n\n        const container = document.querySelector('#background');\n        container.style.backgroundImage = `url(images\/${buffer.join('')}.jpg)`;\n    });\n}<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"0427\" class=\"graf graf--p graf-after--figure\">With this, our&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function is completely independent of other code in our script, and it can be even imported from another file to make things even clearer. But now that we have this function we can see other possibilities for improvement.<\/p>\n<h4 id=\"6e84\" class=\"graf graf--h4 graf-after--p\">Removing hardcoded values<\/h4>\n<p id=\"dc67\" class=\"graf graf--p graf-after--h4\">One of the essential things that we can do in order to make the function more flexible is to remove any hardcoded values that we use and instead pass them to the function as parameters.<\/p>\n<p id=\"18ca\" class=\"graf graf--p graf-after--p\">The most important values that we need which are hardcoded are the selector for the background container element (<code class=\"markup--code markup--p-code\">#background<\/code>), the parts for the image url, and the time delay value in this check&nbsp;<code class=\"markup--code markup--p-code\">if (currentTime - lastKeyTime &gt; 1000)<\/code>. There is also the&nbsp;<code class=\"markup--code markup--p-code\">charList<\/code>&nbsp;variable, but even though we can also pass it as the parameter and control exactly which characters are allowed, we can leave it for now to make things simpler.<\/p>\n<p id=\"5be6\" class=\"graf graf--p graf-after--p\">Instead, what we can do with the<code class=\"markup--code markup--p-code\">charList<\/code>&nbsp;is to move it out of the event listener, into the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function to avoid declaring it on every keystroke. So, let&#8217;s make all these changes.<\/p>\n<figure id=\"abdd\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>document.addEventListener('DOMContentLoaded', () =&gt; {\n    'use strict';\n\n    keyMapper('#background', 'images\/', '.jpg', 1000);\n});\n\nfunction keyMapper(selector, imgPrefix, imgSuffix, keystrokeDelay) {\n    const charList = 'abcdefghijklmnopqrstuvwxyz0123456789';\n    let buffer = [];\n    let lastKeyTime = Date.now();\n\n    document.addEventListener('keydown', event =&gt; {\n        const key = event.key.toLowerCase();\n\n        \/\/ we are only interested in alphanumeric keys\n        if (charList.indexOf(key) === -1) return;\n\n        const currentTime = Date.now();\n\n        if (currentTime - lastKeyTime &gt; keystrokeDelay) {\n            buffer = [];\n        }\n\n        buffer.push(key);\n        lastKeyTime = currentTime;\n\n        const container = document.querySelector(selector);\n        container.style.backgroundImage = `url(${imgPrefix}${buffer.join('')}${imgSuffix})`;\n    });\n}<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"fdec\" class=\"graf graf--p graf-after--figure\">Now the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function takes four parameters which enable us to customize it more easily. Now, when a function has more than three parameters it starts to feel a bit clumsy, it is easy to forget or mix up some. We will fix that a bit later, but for now let&#8217;s go with this.<\/p>\n<h4 id=\"4c13\" class=\"graf graf--h4 graf-after--p\">Create a callback&nbsp;function<\/h4>\n<p id=\"c92b\" class=\"graf graf--p graf-after--h4\">The desired action of our script is to update the background on an element on the page. That happens on the last line of the script. But maybe you want something completely different to happen after a specific key sequence is typed by the user, maybe play a sound, open a dialog or whatever you can think of.<\/p>\n<p id=\"2858\" class=\"graf graf--p graf-after--p\">In our current version of the script, we can only update the background. But it would be really nice if we can easily tell our script if we want to do something else instead, that is, to use the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function to be able to execute a specific function that we have defined. In order to do that, we can define a function that we can pass to the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function and then execute it from there.<\/p>\n<figure id=\"43f8\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>document.addEventListener('DOMContentLoaded', () =&gt; {\n    'use strict';\n\n    keyMapper('#background', 'images\/', '.jpg', 1000, updateBackground);\n});\n\nfunction keyMapper(selector, imgPrefix, imgSuffix, keystrokeDelay, callback) {\n    const charList = 'abcdefghijklmnopqrstuvwxyz0123456789';\n    let buffer = [];\n    let lastKeyTime = Date.now();\n\n    document.addEventListener('keydown', event =&gt; {\n        const key = event.key.toLowerCase();\n\n        \/\/ we are only interested in alphanumeric keys\n        if (charList.indexOf(key) === -1) return;\n\n        const currentTime = Date.now();\n\n        if (currentTime - lastKeyTime &gt; keystrokeDelay) {\n            buffer = [];\n        }\n\n        buffer.push(key);\n        lastKeyTime = currentTime;\n\n        callback(selector, imgPrefix, buffer, imgSuffix);\n    });\n}\n\nfunction updateBackground(selector, imgPrefix, keySequence, imgSuffix) {\n    const container = document.querySelector(selector);\n    container.style.backgroundImage = `url(${imgPrefix}${keySequence.join('')}${imgSuffix})`;\n}<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"3b02\" class=\"graf graf--p graf-after--figure\">As you can see, new<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function accepts one more parameter (dooh!),&nbsp;<code class=\"markup--code markup--p-code\">callback<\/code>, which is a function that we want to execute after a key is pressed. On the last line inside&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>function, we are calling that&nbsp;<code class=\"markup--code markup--p-code\">callback<\/code>function and passing to it some variables that it needs. When&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;is called, we passed to it the function named&nbsp;<code class=\"markup--code markup--p-code\">updateBackground<\/code>&nbsp;which is declared after the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>function.<\/p>\n<p id=\"de2c\" class=\"graf graf--p graf-after--p\">Function<code class=\"markup--code markup--p-code\">updateBackground<\/code>&nbsp;basically contains those two last lines from&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;that we have removed. With this we have completely separated the key sequence saving and the action that is performed after that sequence is obtained.<\/p>\n<p id=\"26d4\" class=\"graf graf--p graf-after--p\">If we were to write a new function and pass it to the<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;as the callback instead of the&nbsp;<code class=\"markup--code markup--p-code\">updateBackground<\/code>&nbsp;function, we can get a completely different behavior.<\/p>\n<h4 id=\"9fe0\" class=\"graf graf--h4 graf-after--p\">A step&nbsp;back<\/h4>\n<p id=\"d0fc\" class=\"graf graf--p graf-after--h4\">One thing that started to bug me a lot is the fact that we now have five parameters for the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function and four for the&nbsp;<code class=\"markup--code markup--p-code\">updateBackground<\/code>function, and all of the parameters for the&nbsp;<code class=\"markup--code markup--p-code\">updateBackground<\/code>&nbsp;are also passed to the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function before they are passed to the&nbsp;<code class=\"markup--code markup--p-code\">updateBackground<\/code>function.<\/p>\n<p id=\"21d2\" class=\"graf graf--p graf-after--p\">Now that we have separated the callback function from the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>function, it makes sense to simply hardcode back required values in the&nbsp;<code class=\"markup--code markup--p-code\">updateBackground<\/code>&nbsp;function since that way we will contain all of the information required inside the callback function and we can call it with only one parameter, the key sequence that the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;provides.<\/p>\n<p id=\"1711\" class=\"graf graf--p graf-after--p\">That also means that the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function can be called with only two parameters, the&nbsp;<code class=\"markup--code markup--p-code\">keystrokeDelay<\/code>&nbsp;and the&nbsp;<code class=\"markup--code markup--p-code\">callback<\/code>&nbsp;function. Here&#8217;s the updated script:<\/p>\n<figure id=\"3098\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>document.addEventListener('DOMContentLoaded', () =&gt; {\n    'use strict';\n\n    keyMapper(1000, updateBackground);\n});\n\nfunction keyMapper(keystrokeDelay, callback) {\n    const charList = 'abcdefghijklmnopqrstuvwxyz0123456789';\n    let buffer = [];\n    let lastKeyTime = Date.now();\n\n    document.addEventListener('keydown', event =&gt; {\n        const key = event.key.toLowerCase();\n\n        \/\/ we are only interested in alphanumeric keys\n        if (charList.indexOf(key) === -1) return;\n\n        const currentTime = Date.now();\n\n        if (currentTime - lastKeyTime &gt; keystrokeDelay) {\n            buffer = [];\n        }\n\n        buffer.push(key);\n        lastKeyTime = currentTime;\n\n        callback(buffer);\n    });\n}\n\nfunction updateBackground(keySequence) {\n    const container = document.querySelector('#background');\n    container.style.backgroundImage = `url(images\/${keySequence.join('')}.jpg)`;\n}<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"1ed7\" class=\"graf graf--p graf-after--figure\">Now, that looks a bit more cleaner and the functionality and parameters are nicely separated.<\/p>\n<p id=\"431b\" class=\"graf graf--p graf-after--p\">Still, we can do a bit better than this.<\/p>\n<p id=\"fadc\" class=\"graf graf--p graf-after--p\">You might have noticed that there\u2019s another string hardcoded in the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function, the&nbsp;<code class=\"markup--code markup--p-code\">keydown<\/code>&nbsp;event name. If we decided to have the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;work on&nbsp;<code class=\"markup--code markup--p-code\">keyup<\/code>event instead, we would need to change it directly inside the function. That is not necessarily a big problem but it would be nice if we can control that trough parameters as well.<\/p>\n<p id=\"7656\" class=\"graf graf--p graf-after--p\">So, we add a third parameter to the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function?<\/p>\n<p id=\"d4dd\" class=\"graf graf--p graf-after--p\">We could do that, but let\u2019s go a step further and make it just one parameter. Let\u2019s introduce&nbsp;\u2026<\/p>\n<h4 id=\"721a\" class=\"graf graf--h4 graf-after--p\">The options&nbsp;object<\/h4>\n<p id=\"5485\" class=\"graf graf--p graf-after--h4\">A really nice way of providing parameters for functions is by providing the object with the properties which hold the parameter values. It is most useful in cases where there are many parameters to be passed to the function and especially in cases where those parameters are optional and represent some sort of configuration for the function.<\/p>\n<p id=\"76b7\" class=\"graf graf--p graf-after--p\">The parameters that we need for the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function,&nbsp;<code class=\"markup--code markup--p-code\">keystrokeDelay<\/code>and the new&nbsp;<code class=\"markup--code markup--p-code\">eventType<\/code>&nbsp;to be added, really do look like the configuration values. One extra benefit of using the object to pass the parameters to the function is that the order of the parameters doesn&#8217;t matter.<\/p>\n<p id=\"c8ab\" class=\"graf graf--p graf-after--p\">Here\u2019s how we can do that now:<\/p>\n<figure id=\"b189\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>document.addEventListener('DOMContentLoaded', () =&gt; {\n    'use strict';\n\n    const options = {\n        eventType: 'keydown',\n        keystrokeDelay: 1000\n    };\n\n    keyMapper(updateBackground, options);\n});\n\nfunction keyMapper(callback, options) {\n    const charList = 'abcdefghijklmnopqrstuvwxyz0123456789';\n    const eventType = options &amp;&amp; options.eventType || 'keydown';\n    const keystrokeDelay = options &amp;&amp; options.keystrokeDelay || 1000;\n    let buffer = [];\n    let lastKeyTime = Date.now();\n\n    document.addEventListener(eventType, event =&gt; {\n        const key = event.key.toLowerCase();\n\n        \/\/ we are only interested in alphanumeric keys\n        if (charList.indexOf(key) === -1) return;\n\n        const currentTime = Date.now();\n\n        if (currentTime - lastKeyTime &gt; keystrokeDelay) {\n            buffer = [];\n        }\n\n        buffer.push(key);\n        lastKeyTime = currentTime;\n\n        callback(buffer);\n    });\n}\n\nfunction updateBackground(keySequence) {\n    const container = document.querySelector('#background');\n    container.style.backgroundImage = `url(images\/${keySequence.join('')}.jpg)`;\n}<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"ec8c\" class=\"graf graf--p graf-after--figure\">The&nbsp;<code class=\"markup--code markup--p-code\">options<\/code>&nbsp;object now holds the name of the event to listen to and the time delay between the keystrokes. Inside the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function we also have these two lines:<\/p>\n<pre id=\"0868\" class=\"graf graf--pre graf-after--p\">const eventType = options &amp;&amp; options.eventType || 'keydown';\nconst keystrokeDelay = options &amp;&amp; options.keystrokeDelay || 1000;<\/pre>\n<p id=\"a793\" class=\"graf graf--p graf-after--pre\">This is where we made our parameters optional. If the values are provided through the&nbsp;<code class=\"markup--code markup--p-code\">options<\/code>&nbsp;object, they will be used, if not, we will use the default values. You can test that by changing the values in the&nbsp;<code class=\"markup--code markup--p-code\">options<\/code>&nbsp;object. You may completely omit the&nbsp;<code class=\"markup--code markup--p-code\">options<\/code>&nbsp;object in the call to&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function and it will still work with the default values.<\/p>\n<p id=\"8d4f\" class=\"graf graf--p graf-after--p\">We can also pass the callback function through the options object, but since without that function we would not have any sensible functionality, it seems better to pass it as a separate and a required parameter.<\/p>\n<p id=\"8b5c\" class=\"graf graf--p graf-after--p\">Another thing to notice in this last change is that the parameter order for the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function is reversed. It wasn&#8217;t necessary to do so, but if a parameter is optional, it is usually provided after the required ones, otherwise if you want to omit the optional&nbsp;<code class=\"markup--code markup--p-code\">options<\/code>&nbsp;parameter, you would need to call&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function like this:<\/p>\n<pre id=\"8a27\" class=\"graf graf--pre graf-after--p\">keyMapper(null, updateBackground); \/\/ null is the value for the options object<\/pre>\n<h4 id=\"9012\" class=\"graf graf--h4 graf-after--pre\">Add the state management<\/h4>\n<p id=\"67ea\" class=\"graf graf--p graf-after--h4\">No, this won\u2019t be redux or any other state management library, it would be silly to add the whole library for such small script. We will simply organize our variables which hold the state of the script, the&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>and<code class=\"markup--code markup--p-code\">lastKeyTime<\/code>, into one object and update it on every change.<\/p>\n<figure id=\"d6a4\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>document.addEventListener('DOMContentLoaded', () =&gt; {\n    'use strict';\n\n    const options = {\n        eventType: 'keydown',\n        keystrokeDelay: 1000\n    };\n\n    keyMapper(updateBackground, options);\n});\n\nfunction keyMapper(callback, options) {\n    const eventType = options &amp;&amp; options.eventType || 'keydown';\n    const keystrokeDelay = options &amp;&amp; options.keystrokeDelay || 1000;\n\n    let state = {\n        buffer: [],\n        lastKeyTime: Date.now()\n    };\n\n    document.addEventListener(eventType, event =&gt; {\n        const key = event.key.toLowerCase();\n        const currentTime = Date.now();\n        let buffer = [];\n\n        if (currentTime - state.lastKeyTime &gt; keystrokeDelay) {\n            buffer = [key];\n        } else {\n            buffer = [...state.buffer, key];\n        }\n\n        state = {buffer: buffer, lastKeyTime: currentTime};\n\n        callback(buffer);\n    });\n}\n\nfunction updateBackground(keySequence) {\n    const container = document.querySelector('#background');\n    container.style.backgroundImage = `url(images\/${keySequence.join('')}.jpg)`;\n}<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"4ed1\" class=\"graf graf--p graf-after--figure\">Now the key sequence and the time of the last keystroke are saved as the&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>&nbsp;property and the&nbsp;<code class=\"markup--code markup--p-code\">lastKeyTime<\/code>&nbsp;property of the&nbsp;<code class=\"markup--code markup--p-code\">state<\/code>&nbsp;object. On every keystroke we copy the&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>&nbsp;property into the local&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>&nbsp;variable and update that variable accordingly.<\/p>\n<p id=\"3715\" class=\"graf graf--p graf-after--p\">After that, the state gets updated with the local&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>&nbsp;variable and the&nbsp;<code class=\"markup--code markup--p-code\">currentTime<\/code>&nbsp;variable. At the end, the<code class=\"markup--code markup--p-code\">callback<\/code>&nbsp;is called with the current&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>&nbsp;value.<\/p>\n<p id=\"dde2\" class=\"graf graf--p graf-after--p\">One thing to note here is that we never directly update the existing state object nor its&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>&nbsp;property, but rather we always create new object for the&nbsp;<code class=\"markup--code markup--p-code\">state<\/code>&nbsp;and new array for the<code class=\"markup--code markup--p-code\">buffer<\/code>. This is known as the immutable update of the state and it is the preferred way of updating the state. We never change previous value\/object, we always assign the new value\/object to the state.<\/p>\n<p id=\"cbd5\" class=\"graf graf--p graf-after--p\">The line:&nbsp;<code class=\"markup--code markup--p-code\">buffer = [key];<\/code>&nbsp;is resetting the&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>&nbsp;in the case where more time has passed between keystrokes than the&nbsp;<code class=\"markup--code markup--p-code\">keystrokeDelay<\/code>&nbsp;defines.<\/p>\n<p id=\"172c\" class=\"graf graf--p graf-after--p\">The line&nbsp;<code class=\"markup--code markup--p-code\">buffer = [\u2026state.buffer, key];<\/code>&nbsp;is using the spread operator&nbsp;<code class=\"markup--code markup--p-code\">...<\/code>&nbsp;to fill the new array with the values from the state buffer, and then we add the current key to the array. With that, the buffer is updated.<\/p>\n<p id=\"3808\" class=\"graf graf--p graf-after--p\">Finally, the line&nbsp;<code class=\"markup--code markup--p-code\">state = {buffer: buffer, lastKeyTime: currentTime};<\/code>&nbsp;is updating the&nbsp;<code class=\"markup--code markup--p-code\">state<\/code>&nbsp;by assigning the new object with new&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>and<code class=\"markup--code markup--p-code\">lastKeyTime<\/code>&nbsp;values.<\/p>\n<p id=\"c6b6\" class=\"graf graf--p graf-after--p\">In this last code sample, I have removed the character test since it does not really improve the functionality of the script, but of course, you can always add your own check to control how the script behaves.<\/p>\n<h3 id=\"aeb3\" class=\"graf graf--h3 graf-after--p\">Final touch<\/h3>\n<p id=\"2c40\" class=\"graf graf--p graf-after--h3\">Our script is complete now. We can set options, create any callback function that we want, the state is updated immutably. Overall, our&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>function is now pretty flexible.<\/p>\n<p id=\"0752\" class=\"graf graf--p graf-after--p\">If you take a closer look at the page in the browser, you will notice that there is that column on the left with some information. On the lower part of the column you can see the placeholders for the key sequence (\u201csome keys\u201d) that the user has entered and for the feedback message (\u201cnothing yet\u201d) for the user. Those are currently useless, but we can change that easily. There are multiple way of doing that.<\/p>\n<p id=\"e2cc\" class=\"graf graf--p graf-after--p\">The simplest way is to add more functionality to the callback function&nbsp;<code class=\"markup--code markup--p-code\">updateBackground<\/code>. But that will then change what the function does and its name won&#8217;t be describing properly what it does.<\/p>\n<p id=\"bb7c\" class=\"graf graf--p graf-after--p\">So, we can write our new code inside another function and call that function from the&nbsp;<code class=\"markup--code markup--p-code\">updateBackground<\/code>&nbsp;function. That is not ideal solution, but it is better than to just slap the new code which has nothing to do with updating the background into the&nbsp;<code class=\"markup--code markup--p-code\">updateBackground<\/code>&nbsp;function. Here&#8217;s how we&#8217;d do that:<\/p>\n<figure id=\"2905\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>function updateBackground(keySequence) {\n    const container = document.querySelector('#background');\n    container.style.backgroundImage = `url(images\/${keySequence.join('')}.jpg)`;\n\n    updateUI(keySequence);\n}\n\nfunction updateUI(keySequence) {\n    const userInput = keySequence.join('');\n    const keySequences = {\n        'idfa': 'All Weapons + Ammo',\n        'idkfa': 'All Weapons + Ammo + Keys',\n        'idbeholds': 'Beserk Pack',\n        'idclev31': 'Bonus Level'\n    };\n    const userInputDisplay = document.querySelector('#user_input');\n    userInputDisplay.textContent = userInput;\n\n    const cheatMessage = document.querySelector('#cheat_message');\n    cheatMessage.textContent = keySequences[userInput] || 'Nothing';\n}<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"9e0b\" class=\"graf graf--p graf-after--figure\">Another way of doing this is to simply call&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;second time with the&nbsp;<code class=\"markup--code markup--p-code\">updateUI<\/code>&nbsp;function as the callback function:<\/p>\n<pre>document.addEventListener('DOMContentLoaded', () =&gt; {\n    'use strict';\n\n    const options = {\n        eventType: 'keydown',\n        keystrokeDelay: 1000\n    };\n\n    keyMapper(updateBackground, options);\n    keyMapper(updateUI, options);\n});<\/pre>\n<p id=\"ddce\" class=\"graf graf--p graf-after--figure\">This is slightly better when it comes to the separation of functionality, but we now have two event listeners that basically do the same thing.<\/p>\n<p id=\"e520\" class=\"graf graf--p graf-after--p\">It would be good if we can have only one event listener for the key events and then call all the functions that we want when the event happens.<\/p>\n<p id=\"a5b7\" class=\"graf graf--p graf-after--p\">And we can do that, we only need to provide all those functions to the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function and the best way to do that is to pass an array that will hold the references to all the callback functions that we want to execute when key event occurs. Updating the code to achieve this is really simple:<\/p>\n<figure id=\"e655\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>document.addEventListener('DOMContentLoaded', () =&gt; {\n    'use strict';\n\n    const options = {\n        eventType: 'keydown',\n        keystrokeDelay: 1000\n    };\n\n    keyMapper([updateBackground, updateUI], options);\n});\n\nfunction keyMapper(callbackList, options) {\n    const eventType = options &amp;&amp; options.eventType || 'keydown';\n    const keystrokeDelay = options &amp;&amp; options.keystrokeDelay || 1000;\n\n    let state = {\n        buffer: [],\n        lastKeyTime: Date.now()\n    };\n\n    document.addEventListener(eventType, event =&gt; {\n        const key = event.key;\n        const currentTime = Date.now();\n        let buffer = [];\n\n        if (currentTime - state.lastKeyTime &gt; keystrokeDelay) {\n            buffer = [key];\n        } else {\n            buffer = [...state.buffer, key];\n        }\n\n        state = {buffer: buffer, lastKeyTime: currentTime};\n\n        callbackList.forEach(callback =&gt; callback(buffer));\n    });\n}\n\nfunction updateBackground(keySequence) {\n    const validKeys = keySequence.every(key =&gt; !isNaN(parseInt(key)) || key.toLowerCase() !== key.toUpperCase());\n    if (!validKeys) return;\n    const container = document.querySelector('#background');\n    container.style.backgroundImage = `url(images\/${keys.join('')}.jpg)`;\n}\n\nfunction updateUI(keySequence) {\n    const userInput = keySequence.join('').toLowerCase();\n    const keySequences = {\n        'idfa': 'All Weapons + Ammo',\n        'idkfa': 'All Weapons + Ammo + Keys',\n        'idbeholds': 'Beserk Pack',\n        'idclev31': 'Bonus Level'\n    };\n    const userInputDisplay = document.querySelector('#user_input');\n    userInputDisplay.textContent = userInput;\n\n    const cheatMessage = document.querySelector('#cheat_message');\n    cheatMessage.textContent = keySequences[userInput] || 'Nothing';\n}<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"6079\" class=\"graf graf--p graf-after--figure\">So,&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function is now receiving an array of function references:<\/p>\n<pre id=\"fea0\" class=\"graf graf--pre graf-after--p\">keyMapper([updateBackground, updateUI], options);<\/pre>\n<p id=\"9e3e\" class=\"graf graf--p graf-after--pre\">and then on every&nbsp;<code class=\"markup--code markup--p-code\">buffer<\/code>&nbsp;update all of those functions get called:<\/p>\n<pre id=\"e610\" class=\"graf graf--pre graf-after--p\">callbackList.forEach(callback =&gt; callback(buffer));<\/pre>\n<p id=\"ffb5\" class=\"graf graf--p graf-after--pre\">Adding new functionality for key events is now as easy as writing a function that preforms what we want and passing its reference to the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>function when we call it.<\/p>\n<h4 id=\"9b04\" class=\"graf graf--h4 graf-after--p\">UPDATE (Feb 21,&nbsp;2019)<\/h4>\n<p id=\"7459\" class=\"graf graf--p graf-after--h4\">It was brought up in the comments that the user can enter a path like&nbsp;<code class=\"markup--code markup--p-code\">..\/myimage<\/code>&nbsp;and that would access some file that is up one level in the directory structure\u2026 if it exists. To avoid the possibility to use any characters other than letters and numbers, this lines are added to the&nbsp;<code class=\"markup--code markup--p-code\">updateBackground<\/code>function:<\/p>\n<pre id=\"2d6e\" class=\"graf graf--pre graf-after--p\">const validKeys = keySequence.every(key =&gt; !isNaN(parseInt(key)) || key.toLowerCase() !== key.toUpperCase());<\/pre>\n<pre id=\"19c6\" class=\"graf graf--pre graf-after--pre\">if (!validKeys) return;<\/pre>\n<p id=\"8618\" class=\"graf graf--p graf-after--pre\">Also, the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function now returns keys not converted to lower case. That conversion now happens in the callback functions that need to do that, like&nbsp;<code class=\"markup--code markup--p-code\">updateUI<\/code>function.<\/p>\n<h4 id=\"d828\" class=\"graf graf--h4 graf-after--p\"><strong class=\"markup--strong markup--h4-strong\">UPDATE (Feb 15, 2019) &#8211; JavaScript says this is true (0 == false), and this (\u00bb ==&nbsp;false)<\/strong><\/h4>\n<p id=\"7cf1\" class=\"graf graf--p graf-after--h4\">After being reminded in the&nbsp;<a class=\"markup--anchor markup--p-anchor\" href=\"https:\/\/medium.com\/@Shrekgrinch\/might-want-to-consider-dumping-the-truthy-checking-for-the-options-parameters-4fe666d5909d\" target=\"_blank\" rel=\"nofollow noopener\" data-href=\"https:\/\/medium.com\/@Shrekgrinch\/might-want-to-consider-dumping-the-truthy-checking-for-the-options-parameters-4fe666d5909d\">comments<\/a>&nbsp;about the dangers of using the evaluation of truthy values in JavaScript I\u2019ve decided to address that and fix the problematic part.<\/p>\n<p id=\"8f13\" class=\"graf graf--p graf-after--p\">The issue is within these lines:<\/p>\n<pre id=\"f3de\" class=\"graf graf--pre graf-after--p\">const eventType = options &amp;&amp; options.eventType || 'keydown';\nconst keystrokeDelay = options &amp;&amp; options.keystrokeDelay || 1000;<\/pre>\n<p id=\"2f9f\" class=\"graf graf--p graf-after--pre\">As I already mentioned, this will use the value from the options object if it exists, and if it does not exist, then it will use the default value. Except that it won\u2019t do that in all cases. If for some reason&nbsp;<code class=\"markup--code markup--p-code\">options.eventType<\/code>and<code class=\"markup--code markup--p-code\">options.keystrokeDelay<\/code>&nbsp;have values that are evaluated to falsy, then those values won&#8217;t be used and it will skip to the defaults.<\/p>\n<p id=\"49c7\" class=\"graf graf--p graf-after--p\">We expected to have the value&nbsp;<code class=\"markup--code markup--p-code\">undefined<\/code>&nbsp;for those properties if they don&#8217;t exist, which will evaluate to false and we will then use the defaults. The value&nbsp;<code class=\"markup--code markup--p-code\">null<\/code>&nbsp;can evaluate to false as well, which is also ok.<\/p>\n<p id=\"47a8\" class=\"graf graf--p graf-after--p\">What we haven\u2019t considered here is the type of the variables. That is because, depending on the type of the variable, we can have other falsy values to occur.<\/p>\n<p id=\"5982\" class=\"graf graf--p graf-after--p\">We expect the first property,&nbsp;<code class=\"markup--code markup--p-code\">options.eventType<\/code>, to be a string. But what if it is an empty string? JavaScript says that an empty string should evaluate to&nbsp;<code class=\"markup--code markup--p-code\">false<\/code>&nbsp;in such comparisons. Which means that if we pass an empty string to the&nbsp;<code class=\"markup--code markup--p-code\">options.eventType<\/code>&nbsp;property, we will still get the default&nbsp;<code class=\"markup--code markup--p-code\">keydown<\/code>&nbsp;event since the empty string will evaluate to false. But, if we don&#8217;t pass anything, it would not work! So, it this case it actually works in our favor to fail that check for the emtpy string and use the default&nbsp;<code class=\"markup--code markup--p-code\">keydown<\/code>&nbsp;event type. That&#8217;s good.<\/p>\n<p id=\"d5ac\" class=\"graf graf--p graf-after--p\">The second property,&nbsp;<code class=\"markup--code markup--p-code\">options.keystrokeDelay<\/code>, is expected to be a number. And in the case of numbers, JavaScript says that the zero should evaluate to&nbsp;<code class=\"markup--code markup--p-code\">false<\/code>. If we wanted to pass the zero for the delay value, it won&#8217;t be accepted and it will fall back to the default of 1000ms. But on the other hand, I hear you say \u00abWho could type so fast that the zero value should be considered at all?\u00bb. And you&#8217;re correct, in that case all of this is pointless, we could only listen for one key at a time.<\/p>\n<p id=\"2b35\" class=\"graf graf--p graf-after--p\">So, we\u2019re good here as well, no need to do anything? Actually no. What if the number provided is negative? It certainly won\u2019t go back in time and react to key presses before they occur, but it will do that as soon as they do occur. Basically, it is the same as setting the value to zero but it passes the truthy test. We see now that we need another condition here, we need numbers greater than zero. Now, we solved the issue, but what if someone passes number like 10? Is 10ms enough to connect two keystrokes for you? Not for me, that\u2019s for sure. I barely can do it with 250ms delay. From my point of view as the developer of this app I see no reason to enable delays less than the minimum I need to connect two keystrokes. So I\u2019ve decided to limit the delay to the minimum of 300 miliseconds.<\/p>\n<p id=\"fa60\" class=\"graf graf--p graf-after--p\">Here\u2019s updated code for the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function:<\/p>\n<figure id=\"7b90\" class=\"graf graf--figure graf--iframe graf-after--p\">\n<div class=\"aspectRatioPlaceholder is-locked\">\n<div class=\"aspectRatioPlaceholder-fill\">\n<pre>function keyMapper(callbackList, options) {\n    const delay = hasProperty('keystrokeDelay', options) &amp;&amp; options.keystrokeDelay &gt;= 300 &amp;&amp; options.keystrokeDelay;\n    const keystrokeDelay = delay || 1000;\n    const eventType = hasProperty('eventType', options) &amp;&amp; options.eventType || 'keydown';\n\n    let state = {\n        buffer: [],\n        lastKeyTime: Date.now()\n    };\n\n    document.addEventListener(eventType, event =&gt; {\n        const key = event.key;\n        const currentTime = Date.now();\n        let buffer = [];\n\n        if (currentTime - state.lastKeyTime &gt; keystrokeDelay) {\n            buffer = [key];\n        } else {\n            buffer = [...state.buffer, key];\n        }\n\n        state = {buffer: buffer, lastKeyTime: currentTime};\n\n        callbackList.forEach(callback =&gt; callback(buffer));\n    });\n\n    function hasProperty(property, object) {\n        return object &amp;&amp; object.hasOwnProperty(property);\n    }\n}<\/pre>\n<\/div>\n<\/div>\n<\/figure>\n<p id=\"25cf\" class=\"graf graf--p graf-after--figure\">I\u2019ve added a function&nbsp;<code class=\"markup--code markup--p-code\">hasProperty<\/code>&nbsp;which will check if the property exists on the object, and only after that check passes we can proceed on to the next checks. On the line for the<code class=\"markup--code markup--p-code\">delay<\/code>&nbsp;you can also see the&nbsp;<code class=\"markup--code markup--p-code\">options.keystrokeDelay &gt;= 300<\/code>&nbsp;check which will limit the delay to 300ms.<\/p>\n<p id=\"d2ea\" class=\"graf graf--p graf-after--p\">One last thing I\u2019d like to mention is that these problems that I\u2019m talking about in this updated section can all be avoided in the first place if we write proper tests for our code. For example, we can easily think of the issues with the empty string when we consider what possible inputs could the function get for the&nbsp;<code class=\"markup--code markup--p-code\">eventType<\/code>&nbsp;property. Same goes for the&nbsp;<code class=\"markup--code markup--p-code\">keystrokeDelay<\/code>&nbsp;property, while considering possible inputs, we can quickly realize that using zero, negatives or even too small numbers is not working good.<\/p>\n<h3 id=\"689a\" class=\"graf graf--h3 graf-after--p\">Conclusion<\/h3>\n<p id=\"9e02\" class=\"graf graf--p graf-after--h3\">Even though the basic functionality of reacting on a specific key sequence was a short and simple task, I wanted to go a few steps further and show how we can really make a function that is flexible to enable us a bit more control. We achieved that with passing the list of callback functions and an options object to the&nbsp;<code class=\"markup--code markup--p-code\">keyMapper<\/code>&nbsp;function.<\/p>\n<p id=\"91c8\" class=\"graf graf--p graf-after--p\">But you can make the function even more customizable by providing more options and adding the code into the function related to those new options.<\/p>\n<p id=\"250f\" class=\"graf graf--p graf-after--p\">Depending on what you need, you can extend it as much as you like. Of course, be reasonable in doing that because you want your code to be readable and easy to reason about.<\/p>\n<p id=\"bd2c\" class=\"graf graf--p graf-after--p\">I hope this article has given you some ideas how you can use this in your projects and please feel free to share them in the comments if you like.<\/p>\n<p id=\"9aee\" class=\"graf graf--p graf-after--p graf--trailing\">Thank you for reading and Happy New Year!<\/p>\n<\/div>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n<p>Source:<a href=\"https:\/\/medium.com\/javascript-in-plain-english\/how-to-detect-a-sequence-of-keystrokes-in-javascript-83ec6ffd8e93\"><strong>&nbsp;https:\/\/medium.com\/javascript-in-plain-english\/how-to-detect-a-sequence-of-keystrokes-in-javascript-83ec6ffd8e93<\/strong><\/a><\/p>\n<p>Written by<\/p>\n<div class=\"u-tableCell\"><a class=\"link u-baseColor--link avatar\" dir=\"auto\" title=\"Go to the profile of Vojislav Gruji\u0107\" href=\"https:\/\/medium.com\/@vojislav.grujic?source=footer_card\" aria-label=\"Go to the profile of Vojislav Gruji\u0107\" data-action-source=\"footer_card\" data-user-id=\"ea39b57952ba\"><img decoding=\"async\" class=\"avatar-image avatar-image--small alignleft\" src=\"https:\/\/cdn-images-1.medium.com\/fit\/c\/60\/60\/1*rIA6hVofnIL9TP-bhqMQ0w.jpeg\" alt=\"Go to the profile of Vojislav Gruji\u0107\"><\/a><\/div>\n<div class=\"u-tableCell u-verticalAlignMiddle u-breakWord u-paddingLeft15\">\n<h3 class=\"ui-h3 u-fontSize18 u-lineHeightTighter u-marginBottom4\"><a class=\"link link--primary u-accentColor--hoverTextNormal\" dir=\"auto\" title=\"Go to the profile of Vojislav Gruji\u0107\" href=\"https:\/\/medium.com\/@vojislav.grujic\" rel=\"author cc:attributionUrl\" aria-label=\"Go to the profile of Vojislav Gruji\u0107\" data-user-id=\"ea39b57952ba\">Vojislav Gruji\u0107<\/a><\/h3>\n<\/div>\n<p>&nbsp;<\/p>\n<div class=\"u-tableCell \"><a class=\"link u-baseColor--link avatar avatar--roundedRectangle\" title=\"Go to JavaScript In Plain English\" href=\"https:\/\/medium.com\/javascript-in-plain-english?source=footer_card\" aria-label=\"Go to JavaScript In Plain English\" data-action-source=\"footer_card\"><img decoding=\"async\" class=\"avatar-image u-size60x60 alignleft\" src=\"https:\/\/cdn-images-1.medium.com\/fit\/c\/60\/60\/1*5996wmlLTse13YC42IVAWw.png\" alt=\"JavaScript In Plain English\"><\/a><\/div>\n<div class=\"u-tableCell u-verticalAlignMiddle u-breakWord u-paddingLeft15\">\n<h3 class=\"ui-h3 u-fontSize18 u-lineHeightTighter u-marginBottom4\"><a class=\"link link--primary u-accentColor--hoverTextNormal\" href=\"https:\/\/medium.com\/javascript-in-plain-english?source=footer_card\" rel=\"collection\" data-action-source=\"footer_card\">JavaScript In Plain English<\/a><\/h3>\n<p class=\"ui-body u-fontSize14 u-lineHeightBaseSans u-textColorDark u-marginBottom4\">Learn the web&#8217;s most important programming language.<\/p>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>Photo by&nbsp;Aleksandar Cvetanovic&nbsp;on&nbsp;Unsplash One of the most used features of JavaScript is its ability to react on various events that [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":1641,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[49,12,70,91,118,44,47,29],"tags":[],"class_list":["post-1639","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-back-end","category-bloghassler-ec","category-chrome","category-data","category-internet","category-javascript","category-medium","category-programacion"],"_links":{"self":[{"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/posts\/1639","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/comments?post=1639"}],"version-history":[{"count":2,"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/posts\/1639\/revisions"}],"predecessor-version":[{"id":1643,"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/posts\/1639\/revisions\/1643"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/media\/1641"}],"wp:attachment":[{"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/media?parent=1639"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/categories?post=1639"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.hassler.ec\/wp\/wp-json\/wp\/v2\/tags?post=1639"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}