This exercise allows you to practice and apply the concepts and techniques taught in class.
Upon completion of this exercise, you will be able to:
-
Select HTML elements using DOM methods and properties
querySelector()
,querySelectorAll()
,getElementsByClassName()
,getElementById()
,element.currentTarget
,element.parentNode
-
Access and modify content of HTML elements using HTML element properties
element.textContent
,element.innerHTML
-
Add or remove HTML elements to the DOM using Javascript DOM methods
document.createElement()
,element.append()
,element.appendChild()
,element.removeChild()
-
Add or remove event listeners to respond to user actions, such as button clicks
element.addEventListener()
,element.removeEventListener()
-
Pass functions as arguments to other functions (callbacks)
-
Iterate over lists of HTML elements and perform actions on each, such as accessing values, adding or removing events, etc.
-
Access and modify properties, values and attributes of HTML elements
element.setAttribute()
,element.getAttribute()
,element.classList.add()
,element.classList.remove()
,classList
,style
,value
,type
E-commerce has proven to be a big game-changer in the 21st-century economy. As one of the largest sales channels, second only to physical retail, e-commerce is expected to be responsible for 6.3 trillion USD in worldwide sales by the year 2024.
E-commerce is a highly competitive business, and building a positive user experience is crucial to retaining customers and improving conversions. As a result, it is common for companies to make a significant investment in optimizing the purchase flow on their e-commerce platforms.
One of the most significant components of this experience is the shopping cart.
In this lab, we will be building the IronCart, a shopping cart for the unofficial Ironhack merchandising store. Visitors should be able to add and remove products from the shopping cart and modify the number (quantity) of items they want to purchase. Additionally, users should be able to see the subtotal and total prices for the items they have added.
- Fork this repo.
- Clone it to your machine.
- Upon completion, run the following commands:
git add .
git commit -m "Solved lab"
git push origin master
- Create a Pull Request so that your TAs can check your work.
You will do most of your work in the src/index.js
file. We added the initial markup in index.html
and some basic styling. While developing, make sure you use the same class names as those already used (and available in the CSS file) to make our shopping cart nice and clean.
Let's go!
Open the index.html
in your browser. As you can see, there is only one row in the table that represents one product. In this first iteration, you will be focused just on that product, and later on, we will help you think of ways to upgrade to having multiple products.
Let's take a look at the provided HTML code. We have the table tag with the id #cart
, as shown below:
<table id="cart">
<thead>
<tr>
<th>Product Name</th>
<th>Unit Price</th>
<th>Quantity</th>
<th>Subtotal</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr class="product">
<!-- ... -->
</tr>
</tbody>
<!-- ... -->
</table>
The one product we currently have in our #cart
is placed in the tr
with the class product
(that goes inside of tbody
):
<tr class="product">
<td class="name">
<span>Ironhack Rubber Duck</span>
</td>
<td class="price">$<span>25.00</span></td>
<td class="quantity">
<input type="number" value="0" min="0" placeholder="Quantity" />
</td>
<td class="subtotal">$<span>0</span></td>
<td class="action">
<button class="btn btn-remove">Remove</button>
</td>
</tr>
The product has a price and quantity (where quantity represents how many items of a specific product a user has added to the cart). In the provided code, we see that there is also a subtotal price. The subtotal price will be the result of the multiplication of those values.
Your goal is to calculate the subtotal price, but let's approach it gradually. Let's break it down into a couple of steps:
- Step 0: In this step, our goal is to help you understand the provided code in the
src/index.js
. Thanks to the provided code, theCalculate Prices
button already has some functionality. Using the DOM manipulation, we got the element with theid="calculate"
and added an event listenerclick
to it. This button will fire thecalculateAll()
function when clicked. The following code snippet does exactly what we explained:
// src/index.js
window.addEventListener('load', () => {
const calculatePricesBtn = document.getElementById('calculate');
calculatePricesBtn.addEventListener('click', calculateAll);
});
Don't get confused with .addEventListener() method, it does exactly the same as onclick(), with some differences about which you can find more here. In this lab, you can use whichever method you prefer.
Ok, let's move to the calculateAll()
function. We used querySelector
in this function to get the first (and currently the only) DOM element with the class product
. This element (which we saved in the variable named singleProduct
) is passed as an argument to the updateSubtotal()
function. As you can find in the comments, the provided code snippet is used only for testing purposes within iteration 1.
function calculateAll() {
// code in the following two lines is added just for testing purposes.
// it runs when only iteration 1 is completed. at a later point, it can be removed.
const singleProduct = document.querySelector('.product');
updateSubtotal(singleProduct);
// end of the test
// ITERATION 2
//...
// ITERATION 3
//...
}
And finally, we started the updateSubtotal(product)
function. For now, this function is only alerting Calculate Prices clicked!
when the Calculate Prices button is clicked.
Let's start:
- Step 1: Inside
updateSubtotal()
, create two variables (we suggest naming themprice
andquantity
) and use your just gained DOM manipulation skills to GET DOM elements that hold price and quantity. To give you a head start, you could use the following code to get the DOM element holding theprice
:
// src/index.js
function updateSubtotal(product) {
const price = product.querySelector('.price span');
// ... your code goes here
}
-
Step 2: Now, when you got the above-mentioned DOM elements, your next step should be extracting the specific values from them. Hint: maybe
innerHTML
rings a bell? In case you are curious to find other ways to achieve the same result, you can start by checkingtextContent
andinnerText
on Google. Additionally, you can extract the value from an input by accessing an the input element'svalue
property. However, don't get distracted here, let this be your additional reading when you complete the lab. -
Step 3: Use the values you extracted from the above-mentioned DOM elements to calculate the subtotal price. You can create a new variable, and its value will be equal to the product of these values. Ex. If a user picked 3 Ironhack Rubber Ducks, they should see that the subtotal is 75 dollars ($25 * 3 = $75).
-
Step 4: Now, when you are becoming a DOM manipulation ninja, use your skills once again to get the DOM element that should hold the subtotal. Hint: it is the element with the class
subtotal
. -
Step 5: In step 3, you calculated the subtotal price, and in step 4, you got the DOM element that should have displayed this price. In this step, your goal is to set the subtotal price to the corresponding DOM element. Also, make sure this function returns the value of the subtotal so you can use it later when needed.
In this iteration, you have completed creating the function updateSubtotal
that calculates the subtotal for this specific product, updates the subtotal value for that same product in the DOM and returns the subtotal value.
As a single argument, the function should take a DOM node that corresponds to a single tr
element with a product
class. In the boilerplate code included, we called it product
.
function updateSubtotal(product) {
// ...
}
💡 Hint: Make sure your calculateAll()
function has the testing code we previously mentioned. If the code is in its place after you successfully finish the updateSubtotal()
function, you should see the corresponding updates in the Subtotal
field in the table.
Check here the expected output.
Our current code works perfectly for one product, but we expect to have more than one product in our cart. As such, we will use calculateAll
to trigger the update of subtotals for every product.
Complete the function named calculateAll()
. Its purpose is to call the function updateSubtotal
with every tr.product
DOM node in the table#cart
.
To start, remove or comment out the existing code inside the calculateAll()
(the code we used for testing purposes). Also, let's add a new product to our index.html
file. Duplicate the tr
with the class product
, rename the product inside, and change the product price.
💡 Hint: Start with getting the DOM nodes for each product row. Currently, we have two products; thus, we have two rows with the class product
. Maybe using getElementsByClassName could serve well here.
function calculateAll() {
// ...
}
The final output should look like the following:
Our calculation functionality is still incomplete. The subtotal for each product is being updated, but the total value remains untouched.
At the end of the calculateAll()
function, reuse the total value you just calculated in the previous iteration and update the corresponding DOM element. Calculate the total price for the products in your cart by summing all of the subtotals returned by updateSubtotal()
when it was called with each product.
Lastly, display that value on your DOM.
Users should be able to remove products from their carts. For that purpose, every product row in our table has a "Remove" button at the end.
But let's try to solve our problem one bit at a time. Inside of the existing function you're passing to window.addEventListener()
, start with querying the document for all "Remove" buttons, loop through them, and add a click
event listener to each, passing a named function removeProduct
as the callback argument. If you need a hint on how to do this, look at how we did it by adding an event listener on the calculatePricesBtn
.
We already declared removeProduct(event)
and added some starter code. After you are done with querying the remove buttons and adding the click
event listener on them, open the console and click on any Remove
button.
As we can see, removeProduct(event)
expects the event as a single argument, and that is going to trigger the removal of the corresponding product from the cart.
💡 Tip: To access the element an event was fired on, you can reference event.currentTarget
. To remove a node from the DOM, you need to access its parent node and call removeChild
on it. You can access the parent of a DOM node from its property parentNode
.
Ensure the price is updated when you remove products from the shopping cart.
Click here to see the expected result.
To finish it off, we will allow users to add a custom product to their cart.
Uncomment the tfoot
element and its children from the index.html
file:
<table>
<tbody>
<!-- ... -->
</tbody>
<!-- <tfoot>
<tr class="create-product">
<td>
<input type="text" placeholder="Product Name" />
</td>
<td>
<input type="number" min="0" value="0" placeholder="Product Price" />
</td>
<td></td>
<td></td>
<td>
<button id="create" class="btn">Create Product</button>
</td>
</tr>
</tfoot> -->
</table>
The two inputs inside of tfoot
represent the new product's name and the unit price, respectively. When triggered, the "Create Product" button should add a new product to the cart.
Add a click
event handler to the "Create Product" that will take a function named createProduct
as a callback.
In createProduct
, you should target the name and unit price input DOM nodes, extract their values, add a new row to the table with the product name and unitary price, as well as the quantity input and "Remove" button, and ensure that all of the functionality works as expected.
Remember, the new product should look undistinguished and behave like any of the products previously included in the cart. As such, one should be able to calculate its subtotal when the "Calculate All" button is clicked and remove the product.
When the product creation is finalized, please, clear the input fields in the creation form.
Click here to see the expected result.
Happy coding! ❤️
I am stuck in the exercise and don't know how to solve the problem or where to start.
If you are stuck in your code and don't know how to solve the problem or where to start, you should take a step back and try to form a clear question about the specific issue you are facing. This will help you narrow down the problem and develop potential solutions.
For example, is it a concept you don't understand, or are you receiving an error message you don't know how to fix? It is usually helpful to try to state the problem as clearly as possible, including any error messages you are receiving. This can help you communicate the issue to others and potentially get help from classmates or online resources.
Once you have a clear understanding of the problem, you will be able to start working toward the solution.
How do I loop over an array using the forEach()
method?
The forEach()
method executes a provided function once for each array element. It does not return a new array but rather executes the function on each element in the array.
The syntax of the forEach()
method is as follows:
array.forEach( function(element) {
// code to be executed for each element
});
Here is an example that uses the forEach()
method to log each element and its index in an array to the console:
const fruits = ['apple', 'banana', 'cherry'];
fruits.forEach( function(element, index) {
console.log(`${index}: ${element}`);
});
You can also use an arrow function as the callback function for forEach()
:
fruits.forEach((element, index) => {
console.log(`${index}: ${element}`);
});
How can I append a new DOM element to an existing one?
To append a new DOM element to an existing one in JavaScript, you can use the appendChild()
method.
Example:
// Get the parent element
var parentElement = document.getElementById("parent");
// Create the new element
var newElement = document.createElement("p");
// Set the text content of the new element
newElement.textContent = "This is a new paragraph.";
// Append the new element to the parent element
parentElement.appendChild(newElement);
This will create a new p
element with the text "This is a new paragraph."
and append it to the element with the ID parent
.
Why do some DOM element values have to be converted to numbers when they already seem to be numbers?
This is because all values in HTML are strings and all attribute values are strings. Therefore, DOM element values are returned as strings even if they contain numeric values.
If you want to use a value from a DOM element as a number, you will need to convert it to a number type.
Here is an example of how to access and convert the value of the price
element to a number using JavaScript:
// Get the input element
const input = item.querySelector('input');
// Convert the string value of the input element to a number
const value = Number(input.value);
I keep getting the NaN
result in my program. How can I fix it?
In JavaScript, NaN
stands for "Not a Number". It is a special value that represents a problem with a numerical operation or a failed type of coercion. There are several reasons why you might get NaN
as a result in your JavaScript code:
- Dividing a number by
0
: Any operation that involves dividing a number by0
(zero) will result inNaN
. Example:
const result = 10 / 0;
console.log(result); // NaN
- Parsing an invalid number: If you try to parse
undefined
or a string that can't be represented as a number using theparseInt()
andparseFloat()
functions, you will getNaN
as a result.
Example of parsing an invalid value with parseInt()
:
const result1 = parseInt("ironhack");
const result2 = parseInt(undefined);
console.log(result1); // NaN
console.log(result2); // NaN
Example of parsing an invalid value with parseFloat()
:
const result1 = parseFloat("ironhack");
const result2 = parseFloat(undefined);
console.log(result1); // NaN
console.log(result2); // NaN
Example of parsing an invalid value with Number()
:
const result1 = Number("ironhack");
const result2 = Number(undefined);
console.log(result1); // NaN
console.log(result2); // NaN
To fix the NaN
issue in your code, you can try a couple of things:
- Check if you are attempting to divide a number by
0
. - Make sure that the strings you are trying to parse as numbers are actually valid representations of numbers. You can use the
console.log()
to check the values of your variables and see if this is the issue.
How do I add an event listener?
Use the addEventListener()
method to add an event listener. This method takes two arguments: the event type and the event handler function that will be called when the event occurs.
Here's an example of how to add a click event listener to a button element:
const button = document.querySelector('button');
function handleClick() {
console.log('Button was clicked');
}
button.addEventListener('click', handleClick);
This will add a click event listener to the button
element, which will call the handleClick()
function whenever the button is clicked.
For more information, check: MDN - addEventListener()
How do I remove an event listener?
Use the removeEventListener()
method to remove an event listener. This method takes two arguments: the event type and the event handler function that was originally assigned when adding the event listener.
For example, let's say you have added the following event listener:
button.addEventListener('click', handleClick);
To remove this event listener, you can use the following code:
button.removeEventListener('click', handleClick);
For more information, check: MDN - removeEventListener()
Why am I getting null
when I try to access an HTML element?
There are a couple of possible reasons why you might be getting a null
value when trying to access a DOM element in JavaScript:
-
You are using the wrong selector or mistyping the name: Make sure you are using the correct selector and spelling to access the element. If you are using an incorrect selector or mistyping the name, you will get a
null
value when you try to access the element. -
The element is not yet loaded: If you are trying to access an element that is not yet loaded in the DOM (e.g., an element that is defined in a script that is loaded at the bottom of the page), you will get a
null
value when you try to access it. You can fix this by wrapping your code in awindow.onload
event handler, which will ensure that the element is available before your code runs:
window.addEventListener("load", function (event) {
const element = document.querySelector('#my-element');
// now you can safely access the element
};
I am unable to push changes to the repository. What should I do?
There are a couple of possible reasons why you may be unable to push changes to a Git repository:
- You have not committed your changes: Before you can push your changes to the repository, you need to commit them using the
git commit
command. Make sure you have committed your changes and try pushing again. To do this, run the following terminal commands from the project folder:
git add .
git commit -m "Your commit message"
git push
- You do not have permission to push to the repository: If you have cloned the repository directly from the main Ironhack repository without making a Fork first, you do not have write access to the repository. To check which remote repository you have cloned, run the following terminal command from the project folder:
git remote -v
If the link shown is the same as the main Ironhack repository, you will need to fork the repository to your GitHub account first and then clone your fork to your local machine to be able to push the changes.
Note: You should make a copy of your local code to avoid losing it in the process.