W02 Team Activity: Dynamic Product Detail Page
Overview
This activity will review the tools introduced last week, and begin the process of making the application scalable by adding dynamically generated product detail pages, and more organization using ES Modules.
Activity Instructions
Complete the following assignment as a team. Designate one team member as the "main driver" and collaborate on their copy of the code. Everyone on the team should be actively engaged in writing the code and contributing to the solution. Once the solution is working, make sure that everyone on the team gets a copy of the code. Each week let someone else be the "main driver" of the coding.
There are many spots where code examples have been given. To get the most out of this activity, do not look at the examples until your team has given that section a try. Then after you look at the example, resist the temptation to copy/paste. Use the examples to make corrections or to help the team move forward. Do not just use them to get it done.
Core Requirements
Begin the Task – Trello Work
- In the synchronous meeting, the driver should visit the team's copy of the project Trello board.
- Add each of the attending team members to the
Team Activity W02: Dynamic product detail pages
card. - Move it to the
"Doing"
list in Trello. - Read the details of the card together.
GitHub Branch – Driver
- Pull any changes from the team GitHub project before proceeding.
- Create a new branch named
driverinitials--team2
where driverinitials is the driver's initials.
File Structure
There is currently an HTML file for each of the four tent products in the product_pages directory. This organization is fine when there are only a few products. The plan is to expand the inventory and therefore, this webpage per product model does not work.
The product pages all follow a similar layout and appearance so there should be only one HTML file that dynamically loads the details about the requested product.
In addition, as a web application grows, using ES Modules becomes increasingly important. ES Modules offer better organization and code reusability, which are crucial aspects for managing complex software projects.
Remember that the words directory and folder have essentially the same meaning. Directory is the more accurate term for file systems while folder 📂 refers to the graphical metaphor that is generally accepted because it is highly related to the term file.
IMPORTANT!
Make sure to work in the src
directory when completing this activity and NOT the
dist directory! The build directory will get erased and completely rebuilt each time the
npm run build
is run. Think of dist
as the production version of your code.
src is your working directory. You should not need to run npm run build
very
often, like once a week after you have merged some branches.
When you run npm run start
it will use the files in src
to build the project.
- In the product_pages directory, copy one of the tent product pages by opening it in the
editor and
then use
File> Save As...
and name the file index.html saving it in the product_pages directory. - Add a new file, a module named ProductDetails.mjs in the js
directory.
This script file will be programmed to contain the code to dynamically produce the product detail pages. - It's very likely that other parts of the application will need to make requests for product data. That functionality has already been exposed in the ProductData.mjs module.
- Other general functionality will need to be shared between parts of the application.
Notice that there is already an utils.mjs module that will hold that
utility functionality.
The goal will be to eventually have a one-to-one relationship with HTML views/pages and JS modules. Then any functionality that needs to be shared between modules can be placed in a utility module.
Review ProductData.mjs
Open up the ProductData.mjs module file. Note a few things:
Click for example code (ProductData.mjs)
function convertToJson(res) {
if (res.ok) {
return res.json();
} else {
throw new Error('Bad Response');
}
}
export default class ProductData {
constructor(category) {
this.category = category;
this.path = `../json/${this.category}.json`;
}
getData() {
return fetch(this.path)
.then(convertToJson).then((data) => data);
}
async findProductById(id) {
const products = await this.getData()
return products.find((item) => item.Id === id);
}
}
-
In order to make the module portable and easy to use, it uses a class to expose all the public facing code in
the module. The class is called
ProductData
, and it is the default export. -
The
ProductsDetails
class will be used for more than just tents. When a category name, for example, 'tents
', is passed to the class, the class will store it and use it to build a path to the correct JSON file.this.path = `../json/${this.category}.json`;
-
Notice the
getData()
method compared to thefindProductById()
method. ThegetData()
method uses just promises and.then()
, thefindProductById()
method uses async/await.In general, the async/await syntax is considered to be easier to read (and write) than the typical
.then()
based promise handling. - If the syntax
(data) => data
is confusing, spend some time reading about arrow functions. This short callback function can be rewritten to be like a more traditional anonymous function declaration:function(data) { return data; }
-
The callback
(item) => item.Id === id
. Another way to write that could be:function(item) { return item.Id === id; }
It is very common to see short functions that just return some value or expression written using the arrow function syntax.
- Open up the product.js file and note that the ProductData.mjs
module is imported.
And then an instance of the ProductData class is then created.import ProductData from './ProductData.js';
const dataSource = new ProductData('tents');
Remember that to use ES Modules in JavaScript a
type="module"
attribute must be included in the HTMLscript
tag.
URL Parameters
There needs to be some way of passing in the product to show the details. A common way to do this
is through a URL
Parameter or query string. The following example HTML anchor element (hyperlink) has the product ID embedded
into the link and named "product
". When the page loads it will have access to that information.
<a href="product_pages/index.html?product=880RR" >
The following statements:
- get the query string part of the current URL (the part after the
?
) - create a
URLSearchParams
object making it convenient to work with the parameters - retrieves that value of the named parameter, in this case
product
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString);
const product = urlParams.get('product')
-
Use those lines to create a new function in the utils.mjs file called
getParam(param)
that you can use to return a parameter from the URL when requested. - Import this new
getParam
function into product.js. - Open /index.html and change the link to the first product page from
href="product_pages/marmot-ajax-3.html"
tohref="product_pages/?product=880RR"
.Where did the
880RR
come from? Look in the json/tents.json file and note that each record has an id value.880RR
just happens to be the id for the first tent. - Test the
getParam
function in product.js to see test if theproductId
displays in the URL when a product is clicked. This would be a good time to test out thefindProductById
method as well.import { setLocalStorage, getParam } from './utils.mjs'; import ProductData from './ProductData.mjs'; const dataSource = new ProductData('tents'); const productId = getParam('product'); console.log(dataSource.findProductById(productId));
-
You should also update the rest of the links in
/index.html to match the first one. You can look up
the IDs in the
tents.json
file.
ProductDetails.mjs
- Open your new file ProductDetails.mjs which located in the js directory.
- Model the ProductDetails.mjs file similarly to the
ProductData.mjs file by placing the public methods in a class.
The following methods are recommended:-
constructor()
: This is recommended for classes. -
init()
: There are a few things that need to happen before the class can be used. Some will happen in the constructor, automatically. Other Others need to be controlled and will be placed in thisinit
method. -
addProductToCart()
: This is the function that is currently in product.js.
Move it here and note that project.js does not need to importsetLocalStorage
from utils.mjs anymore. This -
renderProductDetails()
: Method to generate or populate the HTML to display the product details.
-
-
It will be nice for the product to keep track of important information about itself. For example,
With that information the product will know which id it has, it will have a source to get the information it needs when the time comes, and will have a place to store the retrieved details.constructor(productId, dataSource){ this.productId = productId; this.product = {}; this.dataSource = dataSource; }
- Refactor the product.js code to pull everything from the last few steps together.
- Import the functions that are needed in this module from utils.mjs, ProductData.mjs, and ProductDetails.mjs? You may need to come back to add these as you review the other requirements.
- Get the
product
parameter, the product id, from the URL using the helper functiongetParam
- Create an instance of the
ProductData
data class with the URL it should use to look for products. - Use both of these variables to create an instance of the
ProductDetails
class - Call the
init()
method using the class instance to finish setting everything up.
import { getParam } from './utils.mjs'; import ProductData from './ProductData.mjs'; import ProductDetails from './ProductDetails.mjs'; const productId = getParam('product'); const dataSource = new ProductData('tents'); const product = new ProductDetails(productId, dataSource); product.init();
Finish It Up!
- Develop the rest of the methods in the
ProductDetails
class to make product selection work.-
init()
: This class initializer method needs to fetch product details using the provided data source and render the details. In addition, this method needs to handle the user clicking the 'Add to Cart' button.
Use the following comments as a guide in the development of this method.async init() { // use the datasource to get the details for the current product. findProductById will return a promise! use await or .then() to process it // the product details are needed before rendering the HTML // once the HTML is rendered, add a listener to the Add to Cart button // Notice the .bind(this). This callback will not work if the bind(this) is missing. Review the readings from this week on 'this' to understand why. document.getElementById('addToCart') .addEventListener('click', this.addToCart.bind(this)); }
-
addProductToCart()
: Move this function from product.js. Make any changes necessary to make it work. This method will be called from within theinit()
method. -
renderProductDetails()
: Use the HTML in the /product_pages/index.html as a guide to the HTML that needs to be generated. This method will also be called from within theinit()
method.
-
Example Solution
In this team activity, you are expected to look over a solution from the instructor, to compare your approach to one provided.
Do NOT open the solution example until you have worked through this activity as a team for at least one hour. At the end of the hour, if you are still struggling with some of the core requirements, you are welcome to view the example solution and use it to help you complete your own code.
Example Solution
The example solution can be found in the src directory: WDD 330 Sleep Outside Repository week2-Team branch.
Make a Pull Request
- Complete as much as you can on this activity.
- Review the example solution.
- Get your code working.
- Lint and format your code before committing.
- Driver: Commit and push changes to the team project repository.
- Driver: Submit a pull request for this driver branch.
- Review the pull request as a team.
- Close the request and merge the branch back into the
Main
. - Someone move the Trello task card to the
"Done"
list.
Submission
When you have finished this activity, fill out the assessment in Canvas. You are welcome to complete any additional parts of this activity by yourself or with others after your meeting before submitting the assessment.