I am creating this post as notes from Wes Bos Javascript course which you can sign up for and do with me here: https://beginnerjavascript.com/. Here is a link to Wes' GitHub readme.md.
In my last article, I demonstrated how to use Javascript to manipulate canvases with a camera. Today, we are going to play around with some text to make it's format change using loops, arrays, and various methods. It should be a ton of fun - let's get started!
Getting Started
Build a project folder and create three files:
1. app.js
2. index.html
3. base.css
The HTML File should look like this:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Text Generator</title> <link rel="stylesheet" href="base.css"> <style> </style> </head> <body> <div class="typer"> <label for="sarcastic"> <input type="radio" value="sarcastic" id="sarcastic" name="filter" checked> Sarcastic </label> <label for="funky"> <input type="radio" value="funky" id="funky" name="filter"> Funky </label> <label for="unable"> <input type="radio" value="unable" id="unable" name="filter"> Unable to Structure a Sentence </label> <textarea name="text">so I was thinking about going to the store.</textarea> <p class="result"></p> </div> <script src="app.js"></script> </body> </html>
We are using three radio boxes with the same name
, this will prevent a user from being able to select more than one option and then we will be using the value
to make changes in Javascript.
The CSS file should look like this:
/* normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ button, hr, input { overflow: visible; } progress, sub, sup { vertical-align: baseline; } [type="checkbox"], [type="radio"], legend { box-sizing: border-box; padding: 0; } html { line-height: 1.15; -webkit-text-size-adjust: 100%; } body { margin: 0; } details, main { display: block; } h1 { font-size: 2em; margin: 0.67em 0; } hr { box-sizing: content-box; height: 0; } code, kbd, pre, samp { font-family: monospace, monospace; font-size: 1em; } a { background-color: transparent; } abbr[title] { border-bottom: none; text-decoration: underline; text-decoration: underline dotted; } b, strong { font-weight: bolder; } small { font-size: 80%; } sub, sup { font-size: 75%; line-height: 0; position: relative; } sub { bottom: -0.25em; } sup { top: -0.5em; } img { border-style: none; } button, input, optgroup, select, textarea { font-family: inherit; font-size: 100%; line-height: 1.15; margin: 0; } button, select { text-transform: none; } [type="button"], [type="reset"], [type="submit"], button { -webkit-appearance: button; } [type="button"]::-moz-focus-inner, [type="reset"]::-moz-focus-inner, [type="submit"]::-moz-focus-inner, button::-moz-focus-inner { border-style: none; padding: 0; } [type="button"]:-moz-focusring, [type="reset"]:-moz-focusring, [type="submit"]:-moz-focusring, button:-moz-focusring { outline: ButtonText dotted 1px; } fieldset { padding: 0.35em 0.75em 0.625em; } legend { color: inherit; display: table; max-width: 100%; white-space: normal; } textarea { overflow: auto; } [type="number"]::-webkit-inner-spin-button, [type="number"]::-webkit-outer-spin-button { height: auto; } [type="search"] { -webkit-appearance: textfield; outline-offset: -2px; } [type="search"]::-webkit-search-decoration { -webkit-appearance: none; } ::-webkit-file-upload-button { -webkit-appearance: button; font: inherit; } summary { display: list-item; } [hidden], template { display: none; } /* Variables */ html { --grey: #e7e7e7; --gray: var(--grey); --blue: #0072b9; --pink: #d60087; --yellow: #ffc600; --black: #2e2e2e; --red: #c73737; --green: #61e846; --text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.2); --box-shadow: 0 0 5px 5px rgba(0, 0, 0, 0.2); font-size: 62.5%; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif; box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; } body { font-size: 2rem; line-height: 1.5; background-color: var(--blue); background-image: url("data:image/svg+xml,%3Csvg width='20' height='100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 21.184c.13.357.264.72.402 1.088l.661 1.768C4.653 33.64 6 39.647 6 50c0 10.271-1.222 15.362-4.928 24.629-.383.955-.74 1.869-1.072 2.75v6.225c.73-2.51 1.691-5.139 2.928-8.233C6.722 65.888 8 60.562 8 50c0-10.626-1.397-16.855-5.063-26.66l-.662-1.767C1.352 19.098.601 16.913 0 14.85v6.335zm20 0C17.108 13.258 16 8.077 16 0h2c0 5.744.574 9.951 2 14.85v6.334zm0 56.195c-2.966 7.86-4 13.123-4 22.621h2c0-6.842.542-11.386 2-16.396v-6.225zM6 0c0 8.44 1.21 13.718 4.402 22.272l.661 1.768C14.653 33.64 16 39.647 16 50c0 10.271-1.222 15.362-4.928 24.629C7.278 84.112 6 89.438 6 100h2c0-10.271 1.222-15.362 4.928-24.629C16.722 65.888 18 60.562 18 50c0-10.626-1.397-16.855-5.063-26.66l-.662-1.767C9.16 13.223 8 8.163 8 0H6z' fill='%23fff' fill-rule='nonzero' fill-opacity='.1' opacity='.349'/%3E%3C/svg%3E%0A"); background-size: 15px; } /* Table Styles */ table { border-radius: 5px; overflow: hidden; margin-bottom: 2rem; border-collapse: collapse; } td, th { border: 1px solid var(--grey); padding: 0.5rem; } /* Helper Divs */ .wrapper { max-width: 1000px; margin: 4rem auto; padding: 2rem; background: white; } .box, .wrapper { box-shadow: 0 0 3px 5px rgba(0, 0, 0, 0.08653); } a { color: var(--blue); text-decoration-color: var(--yellow); } a.button, button, input[type="button"] { color: white; background: var(--pink); padding: 1rem; border: 0; border: 2px solid transparent; text-decoration: none; font-weight: 600; font-size: 2rem; } :focus { outline-color: var(--pink); } fieldset { border: 1px solid black; } input:not([type="checkbox"]):not([type="radio"]), textarea, select { display: block; padding: 1rem; border: 1px solid var(--grey); } .success { border: 1px solid red; } h1, h2, h3, h4, h5, h6 { color: white; margin-top: 0; line-height: 1; text-shadow: var(--text-shadow); } .wrapper h1, .wrapper h2, .wrapper h3, .wrapper h4, .wrapper h5, .wrapper h6 { color: var(--black); text-shadow: none; } body { min-height: 100vh; display: grid; align-items: center; } .typer { margin: 0 auto; background: white; width: 500px; padding: 2rem; padding: 2rem; border-radius: 3px; display: grid; } textarea { width: 100%; }
The app.js
file should just have something written in it like console.log('connected');
so that you can verify all three files are working. Let's start writing some Javascript!
Write Some Javascript
Let's do some selecting:
const textArea = document.querySelector('[name="text"]'); const result = document.querySelector('.result'); const filterInputs = Array.from(document.querySelectorAll('[name="filter"]')); // TEST TO MAKE SURE THESE SELECTORS WORK console.log(textArea); console.log(result); console.log(filterInputs);
Use LiveServer to start the site and open the console.log. You should see the individual elements. Now you can delete those console logs.
Create Some Functions
const textArea = document.querySelector('[name="text"]'); const result = document.querySelector('.result'); const filterInputs = Array.from(document.querySelectorAll('[name="filter"]')); //TRANSFORM TEXT FUNCTION function transformText(text) { console.log(text); result.textContent = 'transformed Text'; }; //PASS THE EVENT TO THE TRANSFORMTEXT() FUNCTION textArea.addEventListener('input', event => transformText(event.target.value));
Sarcastic Filter
Make an object called filters
and then we will add our values
as object keys and punch in a function as the key's value pair. Let's take a look at that:
const textArea = document.querySelector('[name="text"]'); const result = document.querySelector('.result'); const filterInputs = Array.from(document.querySelectorAll('[name="filter"]')); //TRANSFORM TEXT FUNCTION function transformText(text) { // USE TEXT AND LOOP OVER EACH LETTER const mod = Array.from(text).map(filters.sarcastic); console.log(mod); result.textContent = 'transformed Text'; }; const filters = { sarcastic(letter, index) { console.log(letter, index); }, funky() { }, unable(){ }, }; //PASS THE EVENT TO THE TRANSFORMTEXT() FUNCTION textArea.addEventListener('input', event => transformText(event.target.value));
In the textarea
input field, you can type something in and now it will output into the console the letter and the index number. It'll look something like this:
What we can do is create a function that selects an even index or odd index number and capitalize the odd index / lowercase the even index.
How Would We Ask JavaScript If A Number Is Even Or Odd?
Let's talk about modulo. In math, you can use a modulo function built into Javascript which is %
.
We use the %
operator.
When JavaScript sees the % operator, it divides the left side by the right side, and then it returns a result. The result is not the quotient of the division, it is the value of the remainder, its modulus.
6 % 2; <--- returns 0
5 % 3; <-- returns 1
4 % 7; <--- returns 4 because 4 cannot be normally divided by 7.
Thats Cool But How Does It apply here?
4 % 2 === 0;
<-- which means, when I divide 4 by 2 do I get a remainder of zero? For which JavaScript will reply with Boolean true
4% 3 === 0;
<-- returns false
4%3 === 1;
<-- returns true
Are you seeing how we can use this yet? Let's build it:
sarcastic(letter, index) { // index % 2 on an odd number results in 1 (truthy) if (index % 2) { //if index is even == false return letter.toUpperCase() }else letter.toLowercase(); },
Ok so we have the function but they are still individual letters in the console, how do we fix that? We can use mod.join('');
on the result.textContent
. Let's see it all put together:
const textArea = document.querySelector('[name="text"]'); const result = document.querySelector('.result'); const filterInputs = Array.from(document.querySelectorAll('[name="filter"]')); const filters = { sarcastic(letter, index) { // index % 2 on an odd number results in 1 (truthy) if (index % 2) { //if index is even == false return letter.toUpperCase() }return letter.toLowerCase(); }, funky() { }, unable(){ }, }; //TRANSFORM TEXT FUNCTION function transformText(text) { // USE TEXT AND LOOP OVER EACH LETTER const mod = Array.from(text).map(filters.sarcastic); console.log(mod); result.textContent = mod.join(''); }; //PASS THE EVENT TO THE TRANSFORMTEXT() FUNCTION textArea.addEventListener('input', event => transformText(event.target.value));
In the mod
variable, we are currently only filtering on "sarcastic" and we don't want to repeat this bit of code three times (once for each option) with only a difference of filters. Let's address that.
Filter
Inside the transformText(text)
function, we could use a filter variable. If we did, it would look something like this:
const filter = document.querySelector('[name="filter"]:checked').value;
While you could do it that way, the better way to do this is with an Array so you don't have to re-rerun the querySelector but I'm not going to get into that here.
Now we can do a property lookup in the place of the mod
variable. This is fairly simple, just replace "sarcastic" with the "filter" variable we just created (be sure to put it inside of brackets since we are looking up a value). That should look like this:
const mod = Array.from(text).map(filters[filter]);
That should work like this:
Funky
We need some funky letters. Be sure to copy all of the text below and paste it at the top of the app.js
file with the rest of your variables:
const funkyLetters = { '-': '₋', '!': 'ᵎ', '?': 'ˀ', '(': '⁽', ')': '₎', '+': '⁺', '=': '₌', '0': '⁰', '1': '₁', '2': '²', '4': '₄', '5': '₅', '6': '₆', '7': '⁷', '8': '⁸', '9': '⁹', a: 'ᵃ', A: 'ᴬ', B: 'ᴮ', b: 'ᵦ', C: '?', d: 'ᵈ', D: 'ᴰ', e: 'ₑ', E: 'ᴱ', f: '?', F: 'ᶠ', g: 'ᵍ', G: 'ᴳ', h: 'ʰ', H: 'H', I: 'ᵢ', i: 'ᵢ', j: 'ʲ', J: 'ᴶ', K: 'k', k: 'K', l: 'ˡ', L: 'ᴸ', m: 'ᵐ', M: 'M', n: 'n', N: 'ᴺ', o: 'ᵒ', O: 'ᴼ', p: 'ᵖ', P: 'ᴾ', Q: 'ᵠ', q: 'ᑫ', r: 'ʳ', R: 'ᵣ', S: 'ˢ', s: 'ˢ', t: 'ᵗ', T: 't', u: 'ᵘ', U: 'ᵤ', v: 'ᵛ', V: 'ᵥ', w: 'W', W: 'ʷ', x: 'ˣ', X: 'ˣ', y: 'y', Y: 'Y', z: '?', Z: 'ᶻ' };
Now pass letter
as an argument to the funky function within the filters
variable and then run three conditions:
1. Check if there is a funky letter for this case.
2. If not check for a lowercase version.
3. If none of the above, return the letter.
funky(letter) { // check if there is a funky letter for this case let funkyLetter = funkyLetters[letter]; if (funkyLetter) return funkyLetter; // if not check for a lowercase version funkyLetter = funkyLetters[letter.toLowerCase()]; if (funkyLetter) return funkyLetter; // else return regular character return letter; },
That should produce something like this:
Unable To Structure A Sentence
This is the last one, it will take in a letter as an argument and we are going to make it create a "..." between 1 and 3 spaces. For this we will use some math functions Math.floor
and Math.random
.
Math.floor(Math.random() *3);
The outcome of the above will be a random whole number between 0 and 2 ( eg. 0, 1, or 2).
Let's put that into a variable and create an if statement that will return ... randomly when a space is entered.
unable(letter) { const random = Math.floor(Math.random() *3); if(letter === ' ' && random === 2){ return '...'; } return letter; },
You should be able to test it out and see something like this:
Trigger the Filter (Last Step I Promise)
Loop over the filter inputs, each input has an event, then runs the transformText and passes it the value of the text.
filterInputs.forEach(input => input.addEventListener('input', () => { transformText(textArea.value); }) );
It should now work like the gif at the top of this article. Here is the final app.js project. If you get hung up, use a tool called https://text-compare.com/ to compare your code to mine below.
const textArea = document.querySelector('[name="text"]'); const result = document.querySelector('.result'); const filterInputs = Array.from(document.querySelectorAll('[name="filter"]')); const funkyLetters = { '-': '₋', '!': 'ᵎ', '?': 'ˀ', '(': '⁽', ')': '₎', '+': '⁺', '=': '₌', '0': '⁰', '1': '₁', '2': '²', '4': '₄', '5': '₅', '6': '₆', '7': '⁷', '8': '⁸', '9': '⁹', a: 'ᵃ', A: 'ᴬ', B: 'ᴮ', b: 'ᵦ', C: '?', d: 'ᵈ', D: 'ᴰ', e: 'ₑ', E: 'ᴱ', f: '?', F: 'ᶠ', g: 'ᵍ', G: 'ᴳ', h: 'ʰ', H: 'H', I: 'ᵢ', i: 'ᵢ', j: 'ʲ', J: 'ᴶ', K: 'k', k: 'K', l: 'ˡ', L: 'ᴸ', m: 'ᵐ', M: 'M', n: 'n', N: 'ᴺ', o: 'ᵒ', O: 'ᴼ', p: 'ᵖ', P: 'ᴾ', Q: 'ᵠ', q: 'ᑫ', r: 'ʳ', R: 'ᵣ', S: 'ˢ', s: 'ˢ', t: 'ᵗ', T: 't', u: 'ᵘ', U: 'ᵤ', v: 'ᵛ', V: 'ᵥ', w: 'W', W: 'ʷ', x: 'ˣ', X: 'ˣ', y: 'y', Y: 'Y', z: '?', Z: 'ᶻ' }; const filters = { sarcastic(letter, index) { // index % 2 on an odd number results in 1 (truthy) if (index % 2) { //if index is even == false return letter.toUpperCase() }; return letter.toLowerCase(); }, funky(letter) { // check if there is a funky letter for this case let funkyLetter = funkyLetters[letter]; if (funkyLetter) return funkyLetter; // if not check for a lowercase version funkyLetter = funkyLetters[letter.toLowerCase()]; if (funkyLetter) return funkyLetter; // else return regular character return letter; }, unable(letter) { const random = Math.floor(Math.random() *3); if(letter === ' ' && random === 2){ return '...'; } return letter; }, }; //TRANSFORM TEXT FUNCTION function transformText(text) { const filter = document.querySelector('[name="filter"]:checked').value; // USE TEXT AND LOOP OVER EACH LETTER const mod = Array.from(text).map(filters[filter]); result.textContent = mod.join(''); }; //PASS THE EVENT TO THE TRANSFORMTEXT() FUNCTION textArea.addEventListener('input', event => transformText(event.target.value)); filterInputs.forEach(input => input.addEventListener('input', () => { transformText(textArea.value); }) );
My Ask and Final Thoughts
In this exercise, we learned how to manipulate text output using Array.from
(turning a string into an array of letters), editing letters based on the application, putting them back together in the desired output and lastly, and hopefully most importantly, we had fun!
If you found this article helpful, share/retweet it and follow me on twitter @codingwithdrewk! There is so much more in Wes' courses I think you will find valuable as I have. I'm learning so much and really enjoying the course, Wes has an amazingly simple way to explain the difficult bits of Javascript that other courses I've taken could only wish. You can view the course over at WesBos.com. (I am in no way getting any referrals or kickbacks for recommending this)
Drew is a seasoned DevOps Engineer with a rich background that spans multiple industries and technologies. With foundational training as a Nuclear Engineer in the US Navy, Drew brings a meticulous approach to operational efficiency and reliability. His expertise lies in cloud migration strategies, CI/CD automation, and Kubernetes orchestration. Known for a keen focus on facts and correctness, Drew is proficient in a range of programming languages including Bash and JavaScript. His diverse experiences, from serving in the military to working in the corporate world, have equipped him with a comprehensive worldview and a knack for creative problem-solving. Drew advocates for streamlined, fact-based approaches in both code and business, making him a reliable authority in the tech industry.