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 and 5.
  • Add different CSS classes to table data to ensure visual distinction between fizz, buzz and fizzbuzz.

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 displayFizzBuzzfunction.

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.