Nano ASP.NET SaaS Boilerplate
Admin credentials (all tenants): admin@email.com / Password123!
Sample data resets every hour
Nano ASP.NET SaaS Boilerplate
General
Front-End Development
Back-End Development
Front-End Development

Build Web App Part 2: JavaScript

In part one as part of our front-end development tutorial, we set up the HTML structure and CSS styling for a product inventory web application.

Now to make our nice looking interface interactive and actually do something, we’ll need to add JavaScript code. In the last section, we learned about let and const which we will use here in this JavaScript tutorial for beginners.

In the head section, we’ve already linked to a blank JavaScript file with the <script> tag. Open the script.js file with VS code. To get started, let’s just have the button output something to the browser’s console. JavaScript comes with several built in objects that we can work with. The document object is one of the most important ones. It represents the whole HTML page.

The document object has a querySelector method which we can use to select elements on the page using the same kind of selection criteria as with CSS. We can select the add product button by its ID, using the name prepended with a hash tag, and store that element as a variable.


  const addProductButton = document.querySelector("#addProductButton");

  addProductButton.addEventListener("click", () => {
    console.log("Button clicked!");
  });

Certain types of element variables can have behaviors attached to them using the addEventListener method. This method takes two parameters, first the event type which will trigger the action, in this case a click, followed by a function that will run. Within the function we can use the console object with its log method to output something to the console window.

Reload the page and open a console window. If we test things out and click the button, we see that nothing is output and instead there is an error to the console in red that says ‘cannot read properties of null reading addEventListener’. Why is this?

If you click the link within the error, it will take you to the source of the problem. What it’s saying is that the addProductButton variable is null. The document.querySelector isn’t selecting the button. The reason for this is that the JavaScript code is executing before all the elements on the page are fully rendered.

We need a way to wait for the page to be finished loading before any JavaScript code executes. To do so we can use the document object again and use addEventListener on it. The event type to trigger upon is the DOMContentLoaded event. The function to run will wrap the rest of our code! This is common practice, and here is how that looks:

document.addEventListener("DOMContentLoaded", () => {

  const addProductButton = document.querySelector("#addProductButton");

  addProductButton.addEventListener("click", () => {
    console.log("Button clicked!");
  });
});

The DOM, which stands for Document Object Model, is the technical term which simply refers to the HTML page. When we were working in the online code editor this wasn’t an issue because that wasn’t a real environment. When working with actual pages though, this is always necessary.

Now if we reload the page and test the button click, you should see “Button clicked!” being output to the console window.

Now we’ll see how we can perform DOM manipulation in JavaScript. We want to select the input field with the document.querySelector, using a hash tag to select by the ID of the element #userInput

  const userInput = document.querySelector("#userInput");

Create another const variable for the array of products we will store. We can use const because even though we add items to it, the array itself will not change. This is how you create a new empty array:

  const products = [];

Within the click event, take the value of the input field userInput and add it as a string item to the products array using the push method. Any text entered by the user should be cleared once they’ve added the product, so set the value to an empty string after. To see what is going on, console.log() the products array to the console.

document.addEventListener("DOMContentLoaded", () => {
  const products = [];

  const addProductButton = document.querySelector("#addProductButton");
  const userInput = document.querySelector("#userInput");

  addProductButton.addEventListener("click", () => {
    products.push(userInput.value);
    userInput.value = ""; // Clear the input field
    console.log(products);
  });
});

Try the newly added code by adding a few products. You should see the array getting output to the console with updated values.

Now to output the values, we need to select the div for the output area. Create a const variable and use the document.querySelector again using the hash tag and ID of the #outputArea.

Instead of writing this new code for rendering the list of products within the button click event function, create a new function called renderList.

  const renderList = () => {
    outputArea.innerHTML = ""; // Clear the output area
    products.forEach((item) => {
      outputArea.innerHTML += `<div class="output-area__item">${item}</div>`;
    });
  };

This function has a forEach loop which is a JavaScript array method that iterates over each item in the product array. The innerHTML property can be used to render content within a div, in this case the output area. The plus equals (+=) is a shorthand way of adding to an existing value. Using back ticks is called a template literal and is a good way to create appended strings with the variables in dollar sign and brackets (${}). Any content within output area is cleared before the loop executes.

Remember to call the function within the button click event using the function name with parenthesis.

renderList();

Now whenever products are added to the array, we can see them being rendered to the output area on the page instead of only the console window. We could make the list look a bit more impressive by adding some new CSS rules for the output area and items within it.

.output-area {
  margin-top: 30px;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.output-area__item {
  background-color: rgb(0, 185, 108);
  padding: 10px;
  border-radius: 5px;
  color: white;
}

Before adding a new product we should check first to see if it already exists, and show the user an error dialogue if it does. For that, we can create a new function, call it checkDuplicates.

  const checkDuplicates = (input) => {
    let hasDuplicate = false;
    products.forEach((item) => {
      if (item === input) {
        hasDuplicate = true;
      }
    });
    return hasDuplicate;
  };

This function takes one parameter called input. When called, it uses the forEach JavaScript array method to loop through each product in the products array and checks to see if any match the value of the new product to be added. A local variable, hasDuplicate, a Boolean which by default is false, will be set to true if any product in the array matches. A true of false value is then returned from the function.

const hasDuplicate = checkDuplicates(userInput.value);

In the button click event, we can call the checkDuplicates function, passing it the the value of the text field as a parameter, and obtain a true or false value.  Then we can wrap the rest of the code in a conditional if statement that executes only when hasDuplicate is false. We should also check that the input value is not blank. The and operator (&&) combines conditions requiring all to be true (an or operator can be written like this || ).

The else if and else conditions will show an alert dialogue to the user. An alert dialogue is a built in feature of all browsers.

  addProductButton.addEventListener("click", () => {
    const hasDuplicate = checkDuplicates(userInput.value);

    if (!hasDuplicate && userInput.value !== "") {
      products.push(userInput.value);
      renderList();
      userInput.value = "";
    } else if (userInput.value == "") {
      alert("Please enter a product name!");
    } else {
      alert("Duplicate product found!");
    }
  });

If that all seemed easy, this might be more of a challenge. Lets modify the renderList function to render a delete button for each item. Before the forEach loop, create a new variable starting at zero to track the index of each item. In the innerHTML, add a button with class delete-button. Each button will have a value attribute with the index. At the end of each iteration, increment the index by one.

    let index = 0;
    products.forEach((item) => {
      outputArea.innerHTML += `<div class="output-area__item">${item}<button class="delete-button" value="${index}">Delete</button></div>`;
      index++;
    });

If you reload the page and add new items, you will see that each row contains a delete button but that clicking them does nothing. We need to attach a click event listener to each one.

We can use the document method querySelectorAll to select multiple elements on a page instead of just one at a time. This will return an array of elements. One tricky thing is that the delete buttons are added dynamically after the page loads, so the querySelectorAll method won’t find anything if its placed outside of the renderList function.

    const deleteButtons = document.querySelectorAll(".delete-button");

After the products are rendered to the page, add the query selector for the buttons and use their class name as the selection criteria. Then we can iterate over each button element returned with a forEach loop and use addEventListener to attach a click event.

Within the function for the delete button click event, we can access the attributes of the button instance by exposing an event parameter and accessing the target property. When you use addEventListener, it will return details about the click event if a variable is present in the call. The event object returned contains a lot of information, and one part of that is the target property. The target property contains the instance of the element that was clicked on.

deleteButtons.forEach((button) => {
      button.addEventListener("click", (event) => {
        const index = event.target.value;
        products.splice(index, 1); // Remove the product from the array
        renderList(); // Re-render the list
      });
    });

The attribute we’re after within this event.target is the value. The value contains the index number we assigned when rendering the innerHTML of the output area. This index number will correspond to the index of the item in the products array. With that we can use the splice method to eliminate the item from the array and then re-render the list.

Here is the complete code:

document.addEventListener("DOMContentLoaded", () => {
  const products = [];

  const addProductButton = document.querySelector("#addProductButton");
  const userInput = document.querySelector("#userInput");
  const outputArea = document.querySelector("#outputArea");

  addProductButton.addEventListener("click", () => {
    const hasDuplicate = checkDuplicates(userInput.value);

    if (!hasDuplicate && userInput.value !== "") {
      products.push(userInput.value);
      renderList();
      userInput.value = "";
    } else if (userInput.value == "") {
      alert("Please enter a product name!");
    } else {
      alert("Duplicate product found!");
    }

  });
  
  const checkDuplicates = (input) => {
    let hasDuplicate = false;
    products.forEach((item) => {
      if (item === input) {
        hasDuplicate = true;
      }
    });
    return hasDuplicate;
  };

  const renderList = () => {
    outputArea.innerHTML = ""; // Clear the output area
    let index = 0;
    products.forEach((item) => {
      outputArea.innerHTML += `<div class="output-area__item">${item}<button class="delete-button" value="${index}">Delete</button></div>`;
      index++;
    });

    const deleteButtons = document.querySelectorAll(".delete-button");
    deleteButtons.forEach((button) => {
      button.addEventListener("click", (event) => {
        const index = event.target.value;
        products.splice(index, 1); // Remove the product from the array
        renderList(); // Re-render the list
      });
    });
  };
});

In this JavaScript tutorial for beginners, we made a front-end web application not only looks nice, but does something too. We added JavaScript code that executes after the page content is loaded with four key functions: the button click, checking for duplicates, rendering the list of products, and delete behavior. In doing so, we used several of the fundamental covered in the introduction like variables, functions, loops, objects, javascript array methods, and conditional statements.

In completing this course, you will be well on your way to building real-world applications. When you feel like you have mastered the basics, check out the Nano ASP.NET Boilerplate. This re-deployable project template has all the foundational code laid out for you to deploy SaaS and MVP projects in record time.

This concludes the module on front-end development. You now know how to build a front-end client with HTML, CSS, and JavaScript.

The next module will cover back-end development. Back-end development is concerned with storing data to a database. You have probably noticed that whenever we reload the page, all of our products disappear; making our app pretty useless.

With a back-end in place, adding or removing products will actually send requests that manipulate rows of data in a database. We’ll see how this is done using ASP.NET, a web application framework which uses C# as its primary language. Its free, open-source, and you’ll love it!