W02 Individual Activity: Dynamic Product List
Overview
This task will continue the process of making our site more dynamic. We will do this by addressing the home page. Eventually we will want to list out more products, so we need to make the product listing build dynamically based on provided data.
Instructions
Complete the following assignment individually. Each student will write code for their own copy.
Core Requirements
Start the Task
- Visit the team's copy of the Trello board for the project.
- Add yourself to the
Individual Activity W02: Dynamic product list
task. - If no one else has done it yet, move it to
'Doing'
. - Read the details of the card.
- Make sure to pull any changes from GitHub before proceeding.
- Create a new branch called
initials--individual2
.For example, if your name were John Doe the branch should be calledjd--individual2
.
Check Your Build
You should have had some pull requests created and merged at this point as well. Have you checked the "production" build of your site since then? Remember that we are deploying the site to Netlify. This deployment is actually automatic. Every time you change the "main" branch of your site it will update Netlify.
We can also check the production build locally. If you run
npm run build
it will update the files in the
dist
directory. You should do this periodically to
check to make sure nothing is broken.
If you visit your site on Netlify you will probably see that some things are broken.
- Run
npm run build
. - We can get Vite to preview that build directory as well by adding a
new command to our
package.json
file. Add the following:"preview": "vite preview",
to the scripts section, then run it. - Check to see what still works and what is broken.
After making any changes, you always need to run
npm run build
before you runnpm run preview
. - When we made the changes to the product details page last week, we forgot to update Vite
so that it knew about the changes. Let's do that now.
Open up the
vite.config.js
file. Note the section that looks like this:build: { outDir: "../dist", rollupOptions: { input: { main: resolve(__dirname, "src/index.html"), cart: resolve(__dirname, "src/cart/index.html"), product1: resolve( __dirname, "src/product_pages/cedar-ridge-rimrock-2.html" ), product2: resolve(__dirname, "src/product_pages/marmot-ajax-3.html"), product3: resolve( __dirname, "src/product_pages/northface-alpine-3.html" ), product4: resolve( __dirname, "src/product_pages/northface-talus-4.html" ), }, }, },
We aren't using the specific product pages anymore, and instead have an
index.html
file that we need to let our bundler know about. Change that section so that it looks like this instead:build: { outDir: "../dist", rollupOptions: { input: { main: resolve(__dirname, "src/index.html"), cart: resolve(__dirname, "src/cart/index.html"), product: resolve(__dirname, "src/product_pages/index.html" ), }, }, },
Anytime we add or remove more .HTML files to the site we will need to update this config or the production version of our site will break. -
We need to make one more build tool related change. If you look inside of the
dist
directory you will notice that the.json
files from the json folder are not there. Vite doesn't know it needs to copy them over.The easiest way to fix this would be to create a new directory called
src/public
. Then move thejson
directory into that new folder. Vite will automatically copy everything in thepublic
folder over to the build. -
You should move the
images
directory into thepublic
directory as well.
Someone on the team should check the build periodically to make sure it is not broken. A good time to do this would be each time a pull request is merged!
Read the Data
Our first task is to read the product data out of the
tents.json
file (our current data source).
- Import the
ProductData
module intomain.js
and create an instance of it.
ProductListing class
- Create a new file
ProductList.mjs
. This purpose of this script will be to generate a list of product cards in HTML from an array. - Add a class called
ProductListing
and export this class as default. - Begin creating your ProductListing module by writing the code for the
constructor.
There are more than one category of products that will need to be listed out independently.
In order to make the ProductListing class as flexible and reusable as possible, the
constructor should receive the following parameters:
- the product
category
, - the
dataSource
, and - the HTML element (listElement) in which to render the product list (output target).
- the product
-
Finally, use the dataSource to get the list of products to work with. We could do that in
the constructor or in an
init()
method. One advantage of the init method is that it will allow us to useasync/await
when calling the promise ingetData()
.Click for example code (
ProductList.mjs
)export default class ProductListing { constructor(category, dataSource, listElement) { // We passed in this information to make our class as reusable as possible. // Being able to define these things when we use the class will make it very flexible this.category = category; this.dataSource = dataSource; this.listElement = listElement; } async init() { // our dataSource will return a Promise...so we can use await to resolve it. const list = await this.dataSource.getData(); // render the list - to be completed } }
- Import the ProductListing class into
main.js
.Make sure that you referencemain.js
into your index.html file with a script tag. Otherwise you may end up wondering why nothing you do has any effect. -
Then create an instance of your
ProductListing
class inmain.js
and make sure that you can see the list of products.
Working with templates
As we think about building our product list, each product card will have very similar markup. Templates are a way for us to reuse markup in an efficient way. Before we were able to leave the markup in the HTML file, and just fill in the values with JavaScript, but that won't work as we need multiple copies of the template. There are many ways that developers will handle template issues.
- Ideally the solution will maintain the separation of concerns. It would be good if we left the HTML where it belongs, in the .html file instead of buried in the JavaScript. This leads to more maintainable code.
- The HTML and CSS for a site is often managed by a different group than the logic in the JavaScript. If the HTML is in the JavaScript then every time a styling change needs to be made the JavaScript will have to be modified instead of just changing the HTML. If your templating solution involves embedding the HTML in the JavaScript code, is it done in such a way that does not require a high level of JS knowledge in order to change a template?
- Performance. Our templating solution should not lead to performance issues.
Some of the options that are used can be found below and are taught in the degree program stack of courses:
- Framework or Library: If the project is using a frontend framework or library like React, Angular, Vue, Svelte. The framework or library will have the templating solution built in.
- Server Side Rendering (SSR): We could use a server side language or framework like PHP, .NET, or NodeJS to insert the template when the page was requested, but before it was sent.
- Templating Tools: There are templating solutions that become part of the frontend build process. Tools like Handlebars, Nunjucks, pug, and other engines. Most of these tools would be integrated into your workflow through your bundler or task manager.
- HTML Template: Use the <template> tag in HTML with some JavaScript to build your own solution.
- Template Literal: Use template literal strings in JavaScript to build your own solution.
- Because our templating needs are pretty light, we are going to go with one of the simplest templating solutions on the list, template literal strings. Some of the other options provide more functionality, and in some cases better scaling but, at the cost of more complexity.
-
Create a template function that will simply return a template literal string for each of the
templates needed. At the top of your
ProductList
module add a function for aproductCardTemplate(product)
. You can use the current HTML in the/index.html
file as your starting point.productCardTemplate() example
// ProductList.mjs function productCardTemplate(product) { return `<li class="product-card"> <a href="product_pages/index.html?product="> <img src="" alt="Image of "> <h3 class="card__brand"></h3> <h2 class="card__name"></h2> <p class="product-card__price">$</p> </a> </li>` }
-
Add a method to your ProductList class to use this template
called
renderList
, call the template function once for each product in our list, and insert it to the DOM. The method should take the product list as an argument. Remember:map
is great way to transform data objects into HTML strings.
Adding in the data
- You should have some product cards showing, but they are all the same and are missing critical information. The next step is to insert the specific product information into the HTML template. This is why we used a template literal string, to take advantage of the variable interpolation it provides.
-
Insert the correct variables into your template. Then test again
to make sure everything is displaying correctly. Remember to
remove the hard coded products in the
/index.html
file so we don't have any duplicates.
Stretch Activity
Make it reusable
The code for your renderList
method looks something like this:
renderList(list) {
const htmlStrings = list.map(productCardTemplate);
this.listElement.insertAdjacentHTML('afterbegin', htmlStrings.join(''));
}
We will need to generate more content with this HTML template in other parts of the program. To make this this function reusable, first examine all the parts of the code that are specific to this particular use case which is making a list of product cards.
- 1️⃣ The element we want to insert the new HTML into is being provided by the class:
this.listElement
. - 2️⃣ The template function we will use is hard coded:
productCardTemplate
- 3️⃣ We have also hard coded the position of the inserted HTML:
afterbegin
, this might not always be the desired case. We might also sometimes want to clear out the contents of the element first.
The list of products is also specific, but we are already passing that in.
- Dealing with items 1️⃣ and 3️⃣ are easy because we can just pass those values into the function as parameters.
- Item 2️⃣, however, is a function. Functions can be passed into other functions in
JavaScript. Make a new function in the
utils.mjs
file calledrenderListWithTemplate
and export it.- It should receive five (5) arguments:
templateFn, parentElement, list, position, and clear
. - Then move the code from your
renderList
function over to this new, exported function inutils.mjs
and refactor the code to work with these new parameters.
- It should receive five (5) arguments:
-
Often
afterbegin
will be the position we want to insert into, and often we may not want to clear out the contents. When you declare your function set a default value for those two parameters.function renderListWithTemplate(templateFn, parentElement, list, position = "afterbegin", clear = false)
- Add the logic to your function to clear out the element provided if
clear
is true. -
Then import in your function to the
ProductList
module, and refactorrenderList
to use our new utility function.
Remove extra products
tents.json
has more tents than we currently need.
- Write a method in
productList.js
that will filter our list of products to just the four (4) we need. - Use that method to show only those four (4) tents.
Instructor's Solution
As a part of this individual 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 individually 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. Even if you use the instructor's code to help you, you are welcome to report that you finished the core requirements, if you code them up yourself.
Finally
Wrap up this individual project work by finishing up your individual work and then coordinating with your team.
- Commit and push your changes.
- Meet with your team and discuss which team member's changes (branch) you would like to
keep.
Your team will only keep one. - Submit a request for the branch that you decided to keep.
- Review the Pull Request, close it, and merge the branch back into the Main branch.
- Move the Dynamic Product List card/task to the
"Done"
column.
Submission
Return to I-Learn to report on your work.