Sarcastic Text Generator

Beginner JavaScript

Enjoy these notes? Want to Slam Dunk JavaScript?

These are notes based on my Beginner JavaScript Video Course. It's a fun, exercise heavy approach to learning Modern JavaScript from scratch.

Use the code BEGINNERJS for an extra $10 off.

BeginnerJavaScript.com

This is a quick and fun exercise. You can try this yourself to see how you would structure it.

What we will be doing is building a sarcastic text generator where you can enter some text and then select one of 3 options:

  • sarcastic,
  • funky, or
  • unable to structure a sentence

Based on your selection, the text you entered will be modified to match the style option you selected.

The sarcastic option gives us "sponge bob case" as it's often called, which is depicted in the image below.

sarcastic text generator
unable to structure a sentence generator
funky text generator

Note: the funky option gives us those weird characters but you should never use these characters in a real world situation because they are inaccessible to someone with a screen reader.

We will be working out of the 56 - Sarcastic Text folder. Open up index.html.

The page already contains some base styles so we can center it on the page, then we have a div with a bunch of inputs.

<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="./text.js"></script>
</body>

The only thing we need to know about the inputs is they all have the same name attribute value. That allows us to only select one of the radio buttons at a time. We are going to be using their values (sarcastic, funky, or unable) to pull the specific filters.

First thing you want to to do is select a few things.

We need the text area that the person will input their text into, the paragraph where we display the result and then all the inputs.

Grab the text area. Use an attribute selector to specify the textarea in case in the future you add more than one to the page.

const textarea = document.querySelector('[name="text"]');

Then you need the result.

const result = document.querySelector('.result');

Then tiy need the inputs. Use querySelectorAll to select all the inputs with a name of "filter" on them.

const filterInputs = document.querySelectorAll('[name="filter"]');

Log them all the make sure they are working.

selecting textarea, paragraph and nodelist using querySelectorAll

Next we need a handler that will output the text.

Make a new function called transformText. It will take in some text and then output the result.

Add an event listener on the input event and call transformText every time the event fires.

To get the text out of the text area to pass to the function, use e.target.value.

function transformText(text) {
  result.textContent = 'Transformed Text';
}

textarea.addEventListener('input', e => transformText(e.target.value));

Check that it is working by simply logging the text within the transformText function. When you refresh the html page, open the console and type into the text area, you should see the text you are typing being logged.

Now we need to write some filters.

We have 3 filters, and we will start by writing the sarcastic filter.

One thing Wes likes to do when we have a few related things like these filters is to stick them in an object instead of having them all as their own function.

Name the object filters and each property will be the filter name like so 👇

const filters = {
  sarcastic: function() {

  },
  funky: function() {

  },
  unable: function() {

  },
}

You will notice when you save the file that it refactors the object as shown below, which is just a shortcut. It's not the same thing as an arrow function, it's just a shorter way to write functions in objects.

const filters = {
  sarcastic() {},
  funky() {},
  unable() {},
};

Now you need to take the text and loop over every letter, because for "sarcastic text", every other letter is a different case.

const transformText(text) {
  // take the text, and loop over each letter
  const mod = Array.from(text);
  console.log(mod);
  result.textContent = text;
}

Now every time you type in the textarea, you should get an array of every single letter that the person has in there.

array of every single letter

We want to loop over that array and uppercase and lowercase every other letter.

To do that, we will use map and pass it our filters.sarcastic function.

Since this is just a map function, our sarcastic method on the filters can take in all the arguments that a regular map can do.

Let's use the parameters letter and index and log those to make sure it's working correctly like so 👇

const filters = {
  sarcastic(letter, index) {
    console.log(letter, index);
    return letter;
  },
  funky() {},
  unable() {},
};

function transformText(text) {
  // take the text, and loop over each letter
  const mod = Array.from(text).map(filters.sarcastic);
  console.log(mod);
  result.textContent = text;
}

textarea.addEventListener("input", (e) => transformText(e.target.value));
log the letter on typing text

Now when you type, as you can see, we are logging the letter and the index. What we can do is say if it's an even number, lowercase it, and if it's an odd number, uppercase it.

How do you know if a number is even or odd?

The modulo operator

There is a neat way of doing that with the modulo operator. Let's do a little aside to go over how the modulo operator works.

pack of 10 smarties (a candy in canada)

Let's say Wes he has a pack of 10 smarties (which are a candy in Canada) and three kids.

If you try to divide 10 by 3 you get 10 / 3 = 3.33333333335. Well you can't really split a smartie into thirds. You might think we can use Math.floor(10/3) which does return 3.

divide and math.floor as wrong approach

So each kid gets 3 smarties.

If we have a pack of 10, each kid gets 3, if we had a pack of 20, each kid will get six.

Now the question is, how many are leftover that Wes gets?

You could try to figure it out using math but it's not the best way. 8:50

What you can do is you can use the modulo operator. Let's say you have 10 smarties, and 2 kids. The modulo operator will tell us after they are evenly divided, how many are left.

10 % 3 = 1
modulo operator in console

So if we have 10 smarties that we divide by 3, there will be one leftover. If we have 6 smarties, there will be 0 leftover.

The modulo operator is great for knowing how much is left after you have evenly divided.

We can use this operator to check if something is even or not.

10 % 2 will be 0, because 10 is even. If you do 11 % 2 you will get 1 returned.

So anytime that 1 is returned with the modulo operator you know it's odd and if 0 is returned, then it's even.

We can use this to upper/lowercase every other letter in our sarcastic filter.

const filters = {
  sarcastic(letter, index) {
    // if it is od, it will return 1 which is truthy.
    if (index % 2) {
      return letter.toUpperCase();
    }

    // if it is even, it will return 0 and we will lowercase it.
    return letter.toLowerCase();
  },

If you refresh the page and start typing, you will see that every other letter is uppercase.

upper/lowercase every other letter using modulo operator

Set the result to be mod.join(' ').

function transformText(text) {
  // take the text, and loop over each letter
  const mod = Array.from(text).map(filters.sarcastic);
  console.log(mod);
  result.textContent = mod.join("");
}

Now if you type in the textarea and select the sarcastic filter, you should see something like this 👇

applying sarcastic filter to the text typed

So that is our first filter, and we are just hard coding it in our transformText function right now. But it would be great if we can use it based on the radio selection that we have.

How could we do that?

We can modify the first line of transformText to grab the filter by finding the input with name of filter that is also checked, and then grabbing it's value.

const filter = document.querySelector('[name="filter"]:checked').value;
console.log(filter);
console log of characters typed

Now when we type in it, we get the selected filter type logged in the console.

querySelector filter type checked to find the radio button checked

Another way we could have done the filter is because we already have the inputs, we could use the find method to find the input that is checked like so 👇

filterInputs.find(input => input.checked);
error - filterInputs.find is not a function

If you try that, you should notice an error in the console that says

filterInputs.find is not a function.

That is because filterInputs is a NodeList, not an array. We could wrap it in Array.from and then call find like so 👇

Array.from(filterInputs).find(input => input.checked);
using Array.from method to covert the node list to array

This is actually a better approach because why rerun the query selector if we have already selected all of the inputs!

Change the code to use that instead.

function transformText(text) {
  const filter = Array.from(filterInputs).find((input) => input.checked).value;
}

With the way the code is written now, we will be transforming our filterInputs into an array every time the person types. That is not ideal.

Let's instead turn it into an array once on page load because it is unnecessary to have to keep doing that.

Modify the code like so 👇

const filterInputs = Array.from(document.querySelectorAll('[name="filter"]'));

Let's do some cleanup and get rid of the logs within the transformText function.

Take the filter variable and use it as a property lookup instead of hard coding the value.

const mod = Array.from(text).map(filters.sarcastic);

Replace the line of code above 👆 with the code below 👇

const mod = Array.from(text).map(filters[filter]);

Because filter is stored in a variable, we need to use square brackets to look up the property.

If you refresh the page, the sarcastic option should still work but nothing should happen when you select the other 2 options because we haven't hooked them up yet.

Let's do funky now. Add the parameter letter to the function, like so 👇

  funky(letter) {},
  unable() {},
};

We need some sort of dictionary or lookup of funky letters. If you open the text-DEMO or text-FINISHED file, you will see an object of funky letters.

Copy that variable along with the /* eslint-disable */ and /* eslint-enable */ comments into our text.js file.

Paste it towards the top of the file, after our filterInputs declaration.

funkyLetters object

This object is just a lookup of letters that match to funky letters.

We kept the ESLint disable comments because otherwise when you save the JavaScript file, it formats the object so each property is on it's own line, which makes it hard to work within the file because it becomes so long.

Back to our funky function logic, we need to do the following:

  • Check if there is a funky letter for this case
  • If there is no funky letter for this case, check if there is a lowercase version.
  • If there is nothing, return the regular letter

You have the letter argument, which you can use to look up inside of funkyLetters. For example if someone types T, we know there is a t for us to use.

accessing the funky letter using its property assigned

Add the following code 👇

let funkyLetter = funkyLetters[letter];

What we are doing there is we are looking up the letter in our funkyLetter object and assigning the value to our funkyLetter variable.

If there is a funkyLetter, we will return it. If there is not, we will return the original letter.

funky(letter) {
  // first check if there is a funky letter for this case
  const funkyLetter = funkyLetters[letter];
  if (funkyLetter) return funkyLetter;
  // if there is not, check if there is a lowercase version
  // if there is nothing, return the regular letter
  return letter;
},

If you refresh the page and try typing something while selecting the funky radio button, you should see it start working.

funkyLetters filter in action

If no funkyLetter is returned, we want to check for the lowercase version of it because some letters have a funky version for both lower and uppercase, like f.

checking the lowercase version of funkyLetters

To do that, we will modify funkyLetter to be a let instead of a const, and then after the comment where we check if there is a lowercase version, we will try to grab the lowercase version by lowercasing the letter and looking it up in the funkyLetters object and then assigning the value to funkyLetter.

If a value is found, we will return it, otherwise we will just return the letter as is.

funky(letter) {
    // first check if there is a funky letter for this case
    let funkyLetter = funkyLetters[letter];
    if (funkyLetter) return funkyLetter;
    // if there is not, check if there is a lowercase version
    funkyLetter = funkyLetters[letter.toLowerCase()];
    if (funkyLetter) return funkyLetter;
    // if there is nothing, return the regular letter
    return letter;
  },

The last one is unable. It will take in a letter, and what we will do is one-in-three spaces, we will use the dot dot dot.

So we need a random number between 0 and 2, such as 1, 2, or 3 or 0, 1, 2.

The way that we can do that is using Math.random() * 3. That returns a number with decimals so we need to wrap it in a Math.floor().

generating a random number using math.random function

Now we will get 0, 1, or 2 returned. Grab that random number within the unable function.

const random = Math.floor(Math.random() * 3);

Next you are going to check whether the letter is equal to an empty space, and that the random is equal to 2 (so 1 in 3), then return "...". Otherwise, we just return the letter.

unable(letter) {
  const random = Math.floor(Math.random() * 3);

  if (letter === " " && random === 2) {
    return "....";
  }

  return letter;
},

Now if you refresh the page, select the unable option and try typing into the textarea, there is a one in three chance that the space will be turned into a ...

unable to structure sentence output in action

The last thing we want to do is when you click on the radio button options, it should trigger that filter and show you the result.

To do this, take filterInputs and loop over each one of them.

Add an event listener that listens for the input event, and then when there is an input event, we will call another function which calls transformText and passes the text from the textarea.

filterInputs.forEach((input) =>
  input.addEventListener("input", () => {
    transformText(textarea.value);
  })
);

Now each of our filter inputs have an event listener that listens to the input event, and when that fires, it triggers the transformText event and passes the value of the text.

If you refresh the page, you should be able to select the different radio buttons and the text formatting will change based on the filter you select.

The main thing Wes wanted to get across in this lesson is that you can store methods inside of an object, to keep them together, as well as look them up on that object with a variable that is populated from some external input.

Find an issue with this post? Think you could clarify, update or add something?

All my posts are available to edit on Github. Any fix, little or small, is appreciated!

Edit on Github

Syntax Podcast

Hold on — I'm grabbin' the last one.

Listen Now →
Syntax Podcast

@wesbos Instant Grams

Beginner JavaScript

Beginner JavaScript

A fun, exercise heavy approach to learning Modern JavaScript from scratch. This is a course for absolute beginners or anyone looking to brush up on their fundamentals. Start here if you are new to JS or programming in general!

BeginnerJavaScript.com
I post videos on and code on

Wes Bos © 1999 — 2024

Terms × Privacy Policy