Component Based Design - Part 1

Before starting this activity, read this article.

Large and complex projects require you to take a big problem and break it down into a series of smaller problems.

Assume that you are tasked with building a car, that's a very big problem! Here are some of the smaller problems that you might have to solve in order to build a car:

Each one of these problems is still very complex and could be broken down into smaller problems.

The process of breaking a big problem down into smaller problems is called decomposition (you decompose a large/complex problem into smaller/simpler problems).

After decomposing, you start building the parts to solve the smaller problems. For example, the car's breaking system would probably require you to design break pads that are used to slow the car down. A break pad is a component that is used to make a car.

Once you've built a break pad component, you need to test it to make sure it will do it's job properly. You might run tests to make sure it will stand up to the force required to slow down a car (in the world of software development, this is called unit testing).

When you are confident that your simplest components have been thoroughly tested and are working properly, then you start assembling them to make sure they work together (in software development this is known as integration testing). For example, you might run tests to make sure that the break pad and the disc/rotor work well with one another. When you integrate components, the result is sometimes referred to as a module (although the term 'module' is nebulus and can mean different things to different people in different contexts).

Eventually, you'll end up with a breaking system that will be integrated into the car. The breaking system may depend on other systems in the car in order work, such as the hydraulic system (in software development, when a component or system depends on another component or system, then it is known as a dependency).

Ideally, components are designed to be reusable so that a break pad, or even an entire breaking system, can be used in many types of cars.

In summary, complex projects are executed by decomposing problems, building components, and integrating them into systems. Wikipedia defines a system as 'a group of interacting or interrelated elements that act according to a set of rules to form a unified whole'. It goes on to say that 'a system is surrounded and influenced by its environment'.

As web developers, the applications that we develop are systems that run within and operating system (such as Windows or Linux). So the operating system is the environment that surrounds our systems.

Well, that was a very high level overview of what we do as developers. Let's get back to the simplest parts, the components!

Component Based Design in Web Development

Software developers have adopted component based design in order to build complicated applications. There are some different approaches to building components in software development. For example you could write a class that can be used to instantiate components. You could also write a function that creates a component. In this activity, I'll show you examples of components that use both functions and classes.

As web developers, we already have quite a few simple components that we can use to create more complicated ones. In HTML, a label element is a component, and so is a checkbox. You can think of any HTML element as a component that can used to 'compose' the parts of a page.

For this activity, we will use a label and a checkbox to build a more complicated component. We'll call it a LabelCheckBox component. Then we'll do a simple test to try out the component. And finally, we'll reuse the component to help us solve a complicated problem, which is to figure out which vegetables the user likes. We'll create a function to define the component.

Start by creating a folder named component-based-design, you can put all the files we create during this activity in this folder.

Then create a page called component-based-design-with-functions.html, and put this code in it:

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>Component Based Design with Functions</title>
	<script type="text/javascript">
		// We'll put our JS code here
	</script>
</head>
<body>
	<div id="test-container"></div>
	<br><br>
	<h3>Which veggies do you like?</h3>
	<div id="veggie-container"></div>
</body>
</html>

Note that we'll be using the two DIV elements on this page as the 'containers' for our components. The first DIV will be used for a quick test of our LabelCheckBox component, and the second will be used to solve our big problem. When we create our component, we need to tell it where it must attach itself to the page. In other words, the component depends on the element that will contain it. To deal with this, the function will have a parameter called el as you'll see in a moment. When you pass in dependencies as parameters to a function component (or in the case of a class, when you pass in dependencies when calling a constructor), it is known as dependency injection.

And now for the code that defines our component, place this function inside the SCRIPT element:

function createLabelCheckBox(el, labelText, chkValue){

	// Create the UI elements
	const label = document.createElement("label");	
	const checkBox = document.createElement("input");
	checkBox.setAttribute("type", "checkbox");	
	const div = document.createElement("div");
	div.append(checkBox, label);
	el.append(div);

	// Bind the data
	label.innerHTML = labelText;
	checkBox.value = chkValue;
}

Hopefully you can understand every line of code in this function (if not, ask me for help). Keep in mind that the el parameter is the DOM element/object on the page will display the component.

And now let's try it out by calling the function, and attaching the component to the test container. Put this code just after the createLabelCheckBox function:

window.addEventListener("load", () => {
	// TRY OUT THE COMPONENT
	const testContainer = document.getElementById("test-container");
	// The component depends on three things:
	// 1. A container to know where to attach itself on the page
	// 2. The text (data) to display in the label
	// 3. The value (data) to bind to the checkbox
	// We will 'inject' these dependencies into the component:
	createLabelCheckBox(testContainer, "Check if you like veggies", "yes");


});

Load the page in the browser and you should see the component!

Before we move on, I'm going to jump ahead a bit and talk about a very important concept in software development and CBD, which is how do components send messages within the system. If something changes within one component, the system (or another component) may need to be aware of the change. For example, when the breaks are pressed in a car, the break lights should light up.

In our case, we need to know when the checkbox is checked or unchecked by the user, so that it can update the underlying data. Luckily, JavaScript gives us a way to be notified when variouis changes occur, we can use the addEventListener() method.

Add this code (inside the window load event handler):

// the components should be able to notify the system when needed:
testContainer.addEventListener("change", evt => {
	alert(evt.target.value + " = " + evt.target.checked);
}); 

Run the page, and change the checkbox.

At this point, we might use another component to update a database so that it can store whether, or not, you like veggies.

Now let's begin to solve our bigger problem, which is to determine exactly which veggies the user likes. Add this to the code inside the window load event handler:

// REUSE THE COMPONENT
// The problem is to figure out which veggies a user likes.

// This will be the data that we use to generate instances of our component
// and keep track of the user preferences
const veggies = [
	{id:1, name:"Brocoli", liked:null},
	{id:2, name:"Carrots", liked:null},
	{id:3, name:"Peas", liked:null}
];

const veggieContainer = document.getElementById("veggie-container");
// Create a LabelCheckBox for each object in the veggies array:
veggies.forEach( veggieObj => createLabelCheckBox(veggieContainer, veggieObj.name, veggieObj.id));

If you load the page, you should see an instance of our component for each object in the veggies array.

Now we need to be notified when a checkbox is changed, so that we can update the data.

Add this code (make sure to put it inside the window load handler):

// Listen for change events
veggieContainer.addEventListener("change", evt => {

	const veggieId = evt.target.value;
	const vegObj = veggies.find(v => v.id == veggieId);
	// bind the data based on the state of the checkbox
	vegObj.liked = evt.target.checked;

	if(vegObj.liked){
		alert("Glad you like " + vegObj.name);
	}else{
		alert("Sorry you don't like " + vegObj.name);
	}

	console.log("Here is the current state of your veggie data:", veggies);
});

Make sure you understand this code. If not, then maybe try stepping through it with the debugger. If you don't remember how to use the debugger, please ask me for a demo.

Our data is now updated whenever a checkbox is changed. We are not 'persisting' the data by storing it in a database, or a file yet, but we'll be getting into that later.

If you want to see what our code might look like if we used a class to define our component (rather than a function), I've put the code at the bottom of this page.

Component Based Design and JavaScript Frameworks

There are many JavaScript frameworks that are centered around component based design (React, Vue, Angular, etc.). As you know, web applications often need to work with data (we have been working with 'veggie' data) One of the main advantages to using a framework is that they take care of most of the data binding code, which can become messy and cumbersome. Frameworks such as Vue and React will automatically update the UI when the data changes, and will update the data when the UI changes (remember that data-binding is bi-directional). In our Veggie app, a good amount of of the code was centered around binding the data when the page loads, and updating the data when the user changes a checkbox.

Many CBD frameworks also allow you to define components and then incorporate them into a web page as if they were HTML elements. For example, if we were to define a component named LabelCheckBox in the Vue framework, then we might add the component to a page by using code that looks something like this:

<labelCheckbox labelText="Peas" chkValue="1" onchange="doSomething()" />

Notice how the data that the component depends on (the dependencies) are passed in as attributes of labelCheckbox element. We'll see quite a bit of code that looks a little bit like this when we start working with the VueJS framework.

A Class Based Component

Our code might look like this if we used a class rather than a function to create the LabelCheckBox component, create a file named component-based-design-with-classes.html and paste this code into it:

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title>Component Based Design with Classes</title>
	<script type="text/javascript">

	class LabelCheckBox{

		// PRIVATE INSTANCE VARIABLES
		#el; 		// the element that will contain this component (the 'container element')
		#label;		// the label element that is used in this component
		#checkBox;	// the checkbox that is used in this component
		#labelText	// the data that will be bound to the label element
		#chkValue	// the value that will be bound to the checkbox
		#div;		// the label and checkbox will be put inside this div
		
		// The constructor is used to set up the instance variables
		// (the dependencies should be passed into the constructor, this is 'dependency injection')
		constructor(el, labelText, chkValue){
			this.#el = el;
			this.#labelText = labelText;
			this.#chkValue = chkValue;
			this.#label = document.createElement("label");	
			this.#checkBox = document.createElement("input");
			this.#checkBox.setAttribute("type", "checkbox");
			this.#div = document.createElement("div");

			this.render();
		}

		// The render method displays the UI,
		// and binds the data
		render(){
					
			this.#div.append(
				this.#checkBox,
				this.#label
			)

			this.#el.append(this.#div);
			
			// bind the data
			this.#label.append(this.#labelText);
			this.#checkBox.value = this.#chkValue;
		}
	}


	window.addEventListener("load", () => {

		// TRY OUT THE COMPONENT
		const testContainer = document.getElementById("test-container");
		// The component depends on three things:
		// 1. A container to know where to attach itself on the page
		// 2. The text (data) to display in the label
		// 3. The value (data) to bind to the checkbox
		// We will 'inject' these dependencies into the component:
		const lblChk1 = new LabelCheckBox(testContainer, "Check if you like veggies", "yes");

		// the components should be able to notify the system when needed:
		testContainer.addEventListener("change", evt => {
			console.log(evt.target.value, evt.target.checked);
		})
		
		
		// REUSE THE COMPONENT
		// The problem is to figure out which veggies a user likes.
		const veggieContainer = document.getElementById("veggie-container");
		
		const veggies = [
			{id:1, name:"Brocoli", liked:null},
			{id:2, name:"Carrots", liked:null},
			{id:3, name:"Peas", liked:null}
		];

		// Instantiate the component for each object in the veggies array
		veggies.forEach( vObj => new LabelCheckBox(veggieContainer, vObj.name, vObj.id));
		

		veggieContainer.addEventListener("change", evt => {

			const veggieId = evt.target.value;
			const vegObj = veggies.find(v => v.id == veggieId);
			// bind the data based on the state of the checkbox
			vegObj.liked = evt.target.checked; 

			if(vegObj.liked){
				alert("Glad you like " + vegObj.name);
			}else{
				alert("Sorry you don't like " + vegObj.name);
			}

			console.log("Here is the current state of your veggie data:", veggies);
		})

	});
	
	</script>
</head>
<body>
	<div id="test-container"></div>
	<br><br>
	<h3>Which veggies do you like?</h3>
	<div id="veggie-container"></div>
</body>
</html>