W02 Team Activity: Dynamic product detail pages
Overview
This activity will review the tools introduced last week, and begin the process of making our application scalable by adding dynamically generated product detail pages, and more organization using ESModules.
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.
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
We currently have one HTML file for each product we offer. This works okay when there are only 4 products, but the plan is to expand the inventory. We cannot continue with this model. The product pages all follow a similar template. There should be one HTML file that loads the details about the requested product through JavaScript.
Additionally, as our code becomes more complex, we will be well served by organizing the script using ESModules.
IMPORTANT!
Make sure that you are working in the src
directory when completing the
following items and NOT the dist
directory! The build directory will get erased and completely rebuilt each time you run
npm run build
. 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.
- Open one of your current product pages in the editor, and create a new file using
File->Save As->
/product-pages/index.html
- Add a new file, a module named
ProductDetails.mjs
in the"js"
directory. This script file will contain the code to dynamically produce the product detail pages. - It's very likely that other parts of our 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.Our 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 our module portable and easy to use, it uses
a class to expose all the public facing code from our module.
It is called
ProductData
, and it is the default export. -
We will eventually want to use this class for more than just
tents. We can do that by using the constructor to the class.
If we pass in a category name, e.g., 'tents', the class will
store it and also use it to build a path to the correct file
this.path = `../json/${this.category}.json`;
-
Notice the
getData()
method compared to thefindProductById()
method. One uses just promises and.then()
, the other uses async/await. Many find the async/await syntax to be easier to read (and write) than the typical.then()
based promise handling. - If the syntax
(data) => data
is confusing you may want to spend more time reading about arrow functions in JavaScript. You could re-write that short callback function like this using 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 theProductData.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 ESModules in JavaScript we have to tell the browser we want to use modules usingtype="module"
in the HTML script tag.
URL Parameters
We need to have some way of passing in the product that we want to show the details for. A
common way to do this is through a URL
Parameter. A link like this <a href="product_pages/index.html?product=880RR" >
has the product
id embedded into the link. When the page loads it will have access to that information. If
you read through that article it suggests the following lines of code to retrieve it:
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 calledgetParams(param)
that we can use to get a parameter from the URL when we need to. (Don't forget to return the parameter!) - Import your new function into
product.js
. - Open
/index.html
and change the link to the first product page fromhref="product_pages/marmot-ajax-3.html"
tohref="product_pages/?product=880RR"
.Where did the880RR
come from? If you look in thejson/tents.json
file you will see that each record in there has an id.880RR
just happens to be the id for the first tent. - Then test your getParams function in
product.js
to see if you can get the product id successfully when someone navigates to the product-details page. This would be a good time to test out ourfindProductById
method as well.Click for example code (
product.js
)import ProductData from './ProductData.mjs'; import { setLocalStorage, getParam } from './utils.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 thetents.json
file.
ProductDetails.mjs
-
Create a new file in the
js
folder calledProductDetails.mjs
- Model the
ProductDetails.mjs
file similarly to theProductData.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 our class can be used. Some will happen in the constructor and will happen automatically. Others it is nice to have more control over and so we will place them into an init method. -
addToCart()
: This is the function that is currently inproduct.js
. Move it here. -
renderProductDetails()
: Method to generate the HTML to display our product.
-
-
It will be nice for our 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 details we need to show once we retrieve them.constructor(productId, dataSource){ this.productId = productId; this.product = {}; this.dataSource = dataSource; }
-
To use this class, pull everything from the last few steps together in the
product.js
file. Once we get everything refactored that file should look something like this:import ProductData from './ProductData.mjs'; import ProductDetails from './ProductDetails.mjs'; import { setLocalStorage, getParam } from './utils.mjs'; const productId = getParam('product'); const dataSource = new ProductData('tents'); const product = new ProductDetails(productId, dataSource); product.init();
-
Notice we import in the code we need from our modules. Then we
get the id of our product using our helper function
getParams
. We create an instance of ourProductData
data class with the URL it should use to look for products. Then we use both of those to create an instance of ourProductDetails
class so that it has everything it needs to work. Finally we call ourinit()
method using our class instance to finish setting everything up.
Stretch Activity
Finish It Up!
Complete this activity by writing the rest of the code to display our product.
-
renderProductDetails()
: Use the HTML in the/product_pages/index.html
as a template to create this function. Once you have the function working remember to remove the HTML from the index.html file so you don't have multiple products showing up! -
addToCart()
: move the code fromproduct.js
for this function and make any changes necessary to make it work. -
init()
: this function needs to do a few things, copy/paste the following into your code to use as a guide for finishing it:async init() { // use our datasource to get the details for the current product. findProductById will return a promise! use await or .then() to process it // once we have the product details we can render out the HTML // once the HTML is rendered we can add a listener to Add to Cart button // Notice the .bind(this). Our callback will not work if we don't include that line. Review the readings from this week on 'this' to understand why. document.getElementById('addToCart') .addEventListener('click', this.addToCart.bind(this)); }
Instructor's Solution
As a part of this team activity, you are expected to look over a solution from the instructor, to compare your approach to that one.
Do NOT open the solution 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 instructor's solution and use it to help you complete your own code.
Make a Pull Request.
- Complete as much as you can on this activity.
- Review the instructor's 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
. - Move the Trello task card to the
"Done"
list.
Submission
When you have finished this activity, fill out the assessment in I-Learn. You are welcome to complete any additional parts of this activity by yourself or with others after your meeting before submitting the assessment.