WDD 330: Web Frontend Development II

W02 Individual Activity: Dynamic Product List

Overview

This task continues the process of making the site more dynamic by addressing the home page. The goal is to list more products, requiring the product listing to be built 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.
    Your other team members may have added themselves already. That is OK.
  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 you 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.

You 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. You can get Vite to preview that build directory as well by adding a command to the package.json file.
    ✔ Add the following: "preview": "vite preview", to the "scripts" section if needed and then run the build again.
  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 you made the changes to the product details page last week, you 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 something like this:

    build: {
      outDir: "../dist",
      rollupOptions: {
        input: {
          main: resolve(__dirname, "src/index.html"),
          cart: resolve(__dirname, "src/cart/index.html"),
          checkout: resolve(__dirname, "src/checkout/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"
          ),
        },
      },
    },

    You aren't using the specific product pages anymore, and instead have an index.html file that you need to let the 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"),
          checkout: resolve(__dirname, "src/checkout/index.html"),
          product: resolve(__dirname, "src/product_pages/index.html"
          ),
    
        },
      },
    },

    Anytime you add or remove more .HTML files to the site you will need to update this config or the production version of the site will break.

  5. You 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 directory 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 public in the src directory. Then move the json directory into that new directory. Vite will automatically copy everything in the public directory 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 (the current data source).

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

ProductList 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 ProductList and export this class as default.
  3. Begin creating your ProductList module by writing the code for the constructor. There are more than one category of products that will need to be independently listed. In order to make the ProductList 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. You 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 ProductList {
      constructor(category, dataSource, listElement) {
        // You passed in this information to make the class as reusable as possible.
        // Being able to define these things when you use the class will make it very flexible
        this.category = category;
        this.dataSource = dataSource;
        this.listElement = listElement;
      }
    
      async init() {
        // the dataSource will return a Promise...so you can use await to resolve it.
        const list = await this.dataSource.getData();
        // next, render the list – ** future **
      }
    }
  5. Import the ProductList class into main.js as a type module.
    Make sure to reference main.js in the index.html file with a script tag.
  6. Then create an instance of your ProductList class in main.js and make sure that you can see the list of products.

Working with templates

As you think about building the product list, each product card will have very similar markup. Templates are a way for us to reuse markup in an efficient way. Before you were able to leave the markup in the HTML file, and just fill in the values with JavaScript, but that won't work as you 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 you 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 team 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 the templating needs are pretty light, you 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/?product=">
          <img src="" alt="Image of ">
          <h2 class="card__brand"></h2>
          <h3 class="card__name"></h3>
          <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 the 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 you 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 you don't have any duplicates.

Make renderList reusable

The code for your renderList method looks something like this:

renderList(list) {
  const htmlStrings = list.map(productCardTemplate);
  this.listElement.insertAdjacentHTML('afterbegin', htmlStrings.join(''));
}

You 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 you want to insert the new HTML into is being provided by the class: this.listElement.
  2. 2️⃣ The template function you will use is hard coded: productCardTemplate
  3. 3️⃣ You have also hard coded the position of the inserted HTML: afterbegin, this might not always be the desired case. You might also sometimes want to clear out the contents of the element first.

The list of products is also specific, but you are already passing that in.

  1. Dealing with items 1️⃣ and 3️⃣ are easy because you 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 you 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 the new utility function.

Example Solution

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. Even if you use the example code to help you, you are welcome to report that you finished the core requirements, if you code them up yourself.

Example Solution

The example solution can be found in the src directory: WDD 330 Sleep Outside Repository week2-Individual branch.

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 Canvas to report on your work.