WDD 330: Web Frontend Development II

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.

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 you have given that section a try. Then after you look at the example, resist the temptation to copy/paste. Use the examples to get correction, or help you get unstuck. Do not just use them to get it done.

Core Requirements

Start the Task

  1. Visit the team's copy of the Trello board for the project.
  2. Add yourself to the Individual Activity W02: Dynamic product list task.
  3. If no one else has done it yet, move it to 'Doing'.
  4. Read the details of the card.
  5. Make sure to pull any changes from GitHub before proceeding.
  6. Create a new branch called initials--individual2.
    For example, if your name were John Doe the branch should be called jd--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.

  1. Run npm run build.
  2. 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.
  3. Check to see what still works and what is broken.
    After making any changes, you always need to run npm run build before you run npm run preview.
  4. 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.
  5. 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 the json directory into that new folder. Vite will automatically copy everything in the public folder over to the build.

  6. You should move the images directory into the public 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).

  1. Import the ProductData module into main.js and create an instance of it.

ProductListing class

  1. 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.
  2. Add a class called ProductListing and export this class as default.
  3. 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:
    1. the product category,
    2. the dataSource, and
    3. the HTML element (listElement) in which to render the product list (output target).
  4. 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 use async/await when calling the promise in getData().
    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
              }
            }
  5. Import the ProductListing class into main.js.
    Make sure that you reference main.js into your index.html file with a script tag. Otherwise you may end up wondering why nothing you do has any effect.
  6. Then create an instance of your ProductListing class in main.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.

When choosing a template solution, keep a few things in mind:
  1. 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.
  2. 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?
  3. 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:

  1. 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.
  2. 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 a productCardTemplate(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>`
          }
  3. 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

  1. 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.
  2. 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. 1️⃣ The element we want to insert the new HTML into is being provided by the class: this.listElement.
  2. 2️⃣ The template function we will use is hard coded: productCardTemplate
  3. 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.

  1. Dealing with items 1️⃣ and 3️⃣ are easy because we can just pass those values into the function as parameters.
  2. Item 2️⃣, however, is a function. Functions can be passed into other functions in JavaScript. Make a new function in the utils.mjs file called renderListWithTemplate and export it.
    1. It should receive five (5) arguments: templateFn, parentElement, list, position, and clear.
    2. Then move the code from your renderList function over to this new, exported function in utils.mjs and refactor the code to work with these new parameters.
  3. 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)
  4. Add the logic to your function to clear out the element provided if clear is true.
  5. Then import in your function to the ProductList module, and refactor renderList to use our new utility function.

Remove extra products

tents.json has more tents than we currently need.

  1. Write a method in productList.js that will filter our list of products to just the four (4) we need.
  2. 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.

After working individually for the one hour activity, click here for the instructor's solution.

Finally

Wrap up this individual project work by finishing up your individual work and then coordinating with your team.

  1. Commit and push your changes.
  2. Meet with your team and discuss which team member's changes (branch) you would like to keep.
    Your team will only keep one.
  3. Submit a request for the branch that you decided to keep.
  4. Review the Pull Request, close it, and merge the branch back into the Main branch.
  5. Move the Dynamic Product List card/task to the "Done" column.

Submission

Return to I-Learn to report on your work.