The Code
// Helper Functions
const getFirstFizzBuzz = (fizz, buzz) => {
let smallerNum = buzz;
if (buzz > fizz) {
smallerNum = fizz;
}
for (let i = smallerNum; i <= fizz * buzz; i++) {
if (i % fizz === 0 && i % buzz === 0) {
return i;
}
}
}
const getClass = input => {
if (input === "FizzBuzz") {
return "table-primary";
} else if (input === "Fizz") {
return "table-light";
} else if (input === "Buzz") {
return "table-dark";
} else {
return "";
}
}
const displayError = (error) => {
Swal.fire(
{
backdrop: false,
title: 'Oh no',
text: error,
icon: "error",
confirmButtonColor: "#ff993b"
}
);
}
const checkInputValidity = (stop, fizz, buzz) => {
const allNumbers = Number.isInteger(stop) && Number.isInteger(fizz) && Number.isInteger(buzz);
const firstFizzBuzz = getFirstFizzBuzz(fizz, buzz);
const minimumStop = stop >= firstFizzBuzz;
const positiveNums = fizz > 0 && buzz > 0;
const uniqueNums = fizz !== buzz;
if (!allNumbers) {
displayError("Please enter numerical values.");
}
if (!minimumStop) {
displayError(`The stop value should atleast be ${firstFizzBuzz}`);
}
if (!positiveNums) {
displayError("The Fizz and Buzz value have to be greater than 0.");
}
if (!uniqueNums) {
displayError("Please enter different fizz and buzz values.")
}
return allNumbers && minimumStop && positiveNums && uniqueNums;
}
//DOM functions
const removeLogo = () => {
const fizzBuzzLogo = document.querySelector('.fizzbuzz-logo');
fizzBuzzLogo?.remove();
}
const generateFizzBuzz = (stop, fizz, buzz) => {
const output = [];
for (let i = 1; i <= stop; i++) {
if (i % fizz === 0 && i % buzz === 0) {
output.push("FizzBuzz");
} else if (i % fizz === 0) {
output.push("Fizz");
} else if (i % buzz === 0) {
output.push("Buzz");
} else {
output.push(i);
}
}
return output;
}
// Recursion alternative
// const generateFizzBuzz = (stop, fizz, buzz, i) => {
// if (i > stop) {
// return [];
// }
// let currentEntry;
// if (i % fizz === 0 && i % buzz === 0) {
// currentEntry = "FizzBuzz";
// } else if (i % fizz === 0) {
// currentEntry = "Fizz";
// } else if (i % buzz === 0) {
// currentEntry = "Buzz";
// } else {
// currentEntry = i;
// }
// return [currentEntry, ...generateFizzBuzz(stop, fizz, buzz, i + 1)];
// }
const getValues = () => {
const stopValue = parseInt(document.getElementById('stopValue').value);
const fizzValue = parseInt(document.getElementById('fizzValue').value);
const buzzValue = parseInt(document.getElementById('buzzValue').value);
if (checkInputValidity(stopValue, fizzValue, buzzValue)) {
const generatedFizzBuzz = generateFizzBuzz(stopValue, fizzValue, buzzValue, 1);
let rowLength;
if (fizzValue > buzzValue) {
rowLength = fizzValue;
} else {
rowLength = buzzValue;
}
displayFizzBuzz(generatedFizzBuzz, rowLength);
}
}
const displayFizzBuzz = (listFizzBuzz, length) => {
let html = '';
let rowLength = length;
if (rowLength > 5) {
rowLength = 5;
}
for (let i = 0; i < listFizzBuzz.length; i++) {
if (i % rowLength === 0) {
html += '<tr>';
}
const tableData = listFizzBuzz[i];
const className = getClass(tableData);
html += `<td style="width: ${100/rowLength}%" class="${className}">${tableData}</td>`
if (i % rowLength === rowLength - 1) {
html += '</tr>';
}
};
removeLogo();
const displayTable = document.getElementById('results');
displayTable.innerHTML = html;
}
Abstract
My break down for this app's functionality was:
- Get the user input values.
- Display a helpful error handling message if input is invalid.
- If the input is indeed valid, generate all the numbers from 1 to
stopValue
- Go through this "list" of numbers, and evaluate each number against some conditions.
- Check for the most specific condition first, because of how Javascript treats conditions.
- Upon any condition being true, replace the value with the corresponding string.
- Use this data to display a table of values.
-
The table's number of columns should be equal to the largest between
fizz
value,buzz
value and5
. -
Add different CSS classes to table data to ensure visual distinction between
fizz
,buzz
andfizzbuzz
.
How I processed the input values
The entry function of the app is getValues
.
In other words, that's the function which gets triggered when user clicks the
Generate numbers
button.
This function gets the values of the relevant input fields from the DOM. It then calls the
checkInputValidity
function with the input values as the arguments. The rest of the code is
only executed if the checkInputValidity
returns true
.
The checkInputValidity
function checks for whether:
- All inputs are numbers.
- The stop value is at least equal to the first fizzbuzz value.
- The fizz value and buzz value are positive and unique.
If any of those conditions are not met, an error alert is generated using a dynamic
displayError
function.
If checkInputValidity
returns true
, It calls the generateFizzBuzz
and
the displayFizzBuzz
function.
How I replaced the selected values with relevant terms
The getValues
function calls the generateFizzBuzz
function with the arguments of
stopValue, fizzValue and buzzValue
. The generateFizzBuzz
function loops from
1
to stopValue
and relies on an if/else-if condition tree. The way javascript
treats if/else-if is that it checks for the first condition that holds true. If the condition is true,
javascrpt executes the enclosed codeblock and does not check the next conditions anyway. As a result of this
behavior, we have to check for the most specific condition first and only if it's false, should we move to
the broader conditions.
Applying this to our use case, fizzbuzz
is the most specific condition in that it requires a
number to be divisible by 2 different numbers fizzValue
and buzzValue
.
We have that specific condition on line 76. If that condition is true, we push FizzBuzz
to an
empty array called output
.
If that condition is false, we check the other conditions of fizz
and buzz
. If
either of the two are true, we push the corresponding string to the output
array. If these
conditions are false too, we execute the else
code block which is a catch-all code block,
executed if all prior conditions are false.
In my case, that else
code-block pushes the number to the output
array.
To summarize, we check for any matching conditions of the number being divisible by the calculated
fizzbuzz
value, the specified fizz
and buzz
values. If any is true,
we push the corresponding string to the output
array. If none of the conditions are true, we
push the number to the array.
At this point, we have just the data we need in the right order.
How I displayed the values.
In the generateFizzBuzz
function, we got the right data but we still need to plug that data
back to the DOM for the user to be able to consume the data. The function that takes care of the same is
displayFizzBuzz
. It uses 2 parameters. One is the array of FizzBuzz items
listFizzBuzz
and length
that we use to style our table.
I initialized a variable html
with the intention to store the desired HTML in this variable.
I initialized a rowLength
variable which is equal to the parameter length
only
upto a max value of 5. This will be the maximum number of columns in our table.
Now that I had something to begin with, I looped through the array listFizzBuzz
. Since I had to
add the data into a table row, I had to add logic for when to add the opening <tr>
and the
closing </tr>
tags.
Let's visualize the tag logic. If the rowLength
is 5, we would want the
<tr>
tag just before the first, sixth, eleventh item and so on. Or, the elements at index
position 0, 5, 10. The pattern is easy to see with the index position. It has to be a multiple of
rowLength
.
Similarly, the logic for the closing </tr>
tag is if the index position of the iterated
item in the loop, when divided by rowLength
, gives us the remainder rowLength-1.
I used helper functions getClass
and removeLogo
. The getClass
gives
us the pre conditioned class string that we assign to the <td>
item. The
removeLogo
function removes the logo from the table element when the user generates the
numbers.
On line 142, we convert our data to html tags and add it to the html
variable.
Finally, just to place the last piece of the puzzle, I used the innerHTML
method to add the
html
variable's code back into the DOM. This connects everything together and makes for a quick
cycle of user interaction triggering a javascript function which processes some user input data, and finally
manipulates the DOM again so that the user can visually see the same.
An alternative approach
(line 91) Instead of a for loop for the iterations, I could've also used recursion. The way I'd give a loop
an end constraint like i < input.length
, I could give a base state to the recursive function
like
.
if (i > stop)
{ return [] }
The way I'd tell a loop to increment i++
, I'd give a carry state to the recursive function
like
return [currentEntry, ...generateFizzBuzz(stop, fizz, buzz, i + 1)]
. The key is to return
another function invocation with an incremented argument i + 1
and also having an early return
in one condition. Without the early return, the callstack would get stuck in an endless loop. Meaning, every
function would call another function and there won't be an end to this. The program could freeze or give you
an error.