In my last article, Using Ajax and REST APIs in .NET 5, I introduced you to using JavaScript and Ajax in HTML to communicate with a REST API developed in .NET 5. This article is going to continue from where you left off and finish creating a page that allows you to display a list of product data, and to add, edit, and delete products. If you use JavaScript, jQuery, Angular, React, or almost any front-end framework, you most likely use Ajax to get and modify data from a server. Most of those front-end frameworks have their own wrapper around the XMLHttpRequest object. What you're learning in this series of articles is how to use this object natively. I'm always a big fan of understanding what goes on under the hood as I believe it makes me a better programmer. If you're reading this article, and you read the last one, then you believe the same. So, let's create a CRUD page using JavaScript and the XMLHttpRequest object.

Download Starting Projects

To follow along with this article, download the projects created in the last article. You should also read the previous article, so you know how these projects were created. There were three projects created in the last article, a .NET 5 Web API project, a .NET 5 MVC project, and a Node server project. You need the .NET 5 Web API project loaded up and running, and then either the MVC or the Node project. The MVC and Node projects are used to run your HTML page, so choose the one with the technology you're most familiar developing with. Download the projects at www.pdsa.com/downloads and click on the link entitled “CODE Magazine - Using Ajax and REST APIs in .NET 5”.

In addition to the source code, you also need the Microsoft AdventureWorksLT sample database. I've placed a version of it on my GitHub account that you can download at https://github.com/PaulDSheriff/AdventureWorksLT. Install this database into your SQL Server.

Start Both Projects

After you've downloaded the sample projects to your hard drive, the first project to load is the Web API project. Open the WebAPI folder in VS Code, open the appsettings.json file, and modify the connection string as needed to be able to connect to the AdventureWorksLT database that you installed. Click on the Run > Start Debugging menus to load and run the .NET Web API project. Next, open another instance of VS Code and load the code from either the \AjaxSample-MVC or the \AjaxSample-Node folder.

If you're using a node application, open a terminal window and type npm install. Then type npm run dev to start the Web server running and to have it display the index page in your browser.

If you're using the .NET MVC application, click on the Run > Start Debugging menus to load and run the .NET MVC project. The index page should now be displayed in your browser window.

Try It Out

Go to your browser for the front-end Web server (localhost:3000) and you should see a page that looks like Figure 1. Open the Browser Tools in your browser, usually accomplished by clicking the F12 key. Click the Get Products button and you should see the product data retrieved from the Product table in the AdventureWorksLT database and displayed in your console window.

Figure 1: This is the starting project from which you're going to build your CRUD logic using Ajax and .NET Core 5.
Figure 1: This is the starting project from which you're going to build your CRUD logic using Ajax and .NET Core 5.

List All Products

Instead of displaying the list of product data in the console window, let's modify the page to display the product data in a table, as shown in Figure 2. Instead of writing code in JavaScript to build a table, let's use a templating engine to help build the table. I've been using mustache.js (https://www.npmjs.com/package/mustache) for years, so that's my preferred templating engine. If you haven't used mustache before, you can read a tutorial at https://bit.ly/3uPs90e. However, I'll show you exactly what to do, so you don't need to fully understand mustache to still follow along.

Figure 2: Display the product data in an HTML table.
Figure 2: Display the product data in an HTML table.

Open the index page and add a new <script> tag before the other <script> tags that are already in the index page. This new <script> tag references the mustache.js file from the https://cdnjs.com/ site.

<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/4.1.0/mustache.min.js<;/script>

Add Styles for the HTML Table

To get your table to look like the one in Figure 2, you need to add some styles. Open the sites.css file in the \styles folder and add the styles shown in Listing 1.

Listing 1: Add styles to format the HTML table.

table {border-collapse: collapse; }

table,th,td {border: 1px solid black;}
th,td {padding: 0.5em;}

tbody tr:nth-child(odd) {background-color: lightgray;}

tbody tr:nth-child(even) {background-color: white;}
.text-right {text-align: right;}

Modify the HTML

Open the index page and locate the <h1> element that has the words “Ajax Samples” in it. Replace the text with “Product Information,” as shown below.

<h1>Product Information</h1>

Just below the <h1> element is a <p> tag with the text “Bring up console window” in it. Remove this <p> tag completely from the index page. Next, locate the form HTML element. You need to assign an “id” attribute to the form as you're going to want to make this form appear and disappear depending on whether you're displaying the list of products or are adding or editing a single product. Modify the form element to look like the following code snippet.

<form id="detail" style="display:none;">

Now it's time to add the HTML table where you're going to display the list of product information. Just above the form element, add the code shown in Listing 2. The button used to add a product isn't hooked up to the add() function yet; you'll do that later in this article. In the table, be sure to include the <thead> and <tbody> elements. I know that many developers don't use these, but they're needed for the demo here.

Listing 2: Add a div tag with an add button and an HTML table in it.

<div id="list" style="display:none;">
    <div class="row">
        <button type="button" onclick="add();">Add Product</button>  
    </div>

    <table id="products">    
        <thead>      
            <tr>        
                <th>Product ID</th>
                <th>Product Name</th>
                <th>Product Number</th>
                <th>Color</th>
                <th class="text-right">Cost</th>
                <th class="text-right">Price</th>
            </tr>
        </thead>
        <tbody></tbody>
    </table>
</div>

Create the HTML Table Template

If you look at the JSON passed back from the Web API project, you see that it's an array of product objects. It's the properties from each of these objects you need to display in the HTML table. Using mustache.js, you create a template in a <script> tag to tell mustache how to display that data. Add a <script> tag just above the <script> tag in which your JavaScript is located. You need to add an “id” attribute to this tag and the “type” attribute, as shown in Listing 3.

Listing 3: Place a mustache template within a <script> tag.

<script id="dataTmpl" type="text/html">
    {{#list}}
    <tr>
        <td>{{productID}}</td>
        <td>{{name}}</td>
        <td>{{productNumber}}</td>
        <td>{{color}}</td>
        <td class="text-right">{{standardCost}}</td>
        <td class="text-right">{{listPrice}}</td>
    </tr>
    {{/list}}
</script>

The values within the double curly braces are the names of the properties in each product object in the array passed back from the Web API. The {{#list}} is the name of a variable that contains the JSON array of product objects. Don't worry, you're going to create that very soon.

Add Functions to the product.js File

The product.js file within the \scripts folder contains functions used to interact with the product data on the index page. You need to add two additional functions to display the list of product data as an HTML table. Both the form and the div element have set their style to “display:none”. This means that neither one is displayed when the page is loaded. After you build the product table, you want it to appear on the page, so create a function named displayList() in the product.js file to display the table.

function displayList() {
    document.getElementById("list").style.display = "block";  
    document.getElementById("detail").style.display = "none";
}

Next, add a function named buildList() to use mustache to render the template you built in the <script> tag by feeding it the appropriate data. Open the product.js file and add the code shown in Listing 4.

Listing 4: The buildList() function uses mustache to render the HTML table.

function buildList(vm) {  
    // Get template from script  
    let template = document.getElementById("dataTmpl").innerHTML;

    // Call Mustache passing in the template and the object with the collection of data  
    let html = Mustache.render(template, vm);

    // Insert the rendered HTML into the DOM
    document.getElementById("products").getElementsByTagName("tbody")[0].innerHTML = html;

    // Display List  
    displayList();
}

The code in the buildList() function is passed in a JSON object that has a list property containing the array of product objects. Remember the {{#list}} token in the data template? This is how you match the name of a property to that data template. Next, the code retrieves the HTML from the <script> tag named “dataTmpl”. Pass both the HTML template and the vm object to the mustache render() method. This method loops through all the data in the array and creates the appropriate HTML for each product object. You then set this newly rendered HTML into the innerHTML property of the <tbody> of the table on your index page.

Connect It All Together

You now have almost all the various pieces built to support the building of the HTML table. First, you need the JSON object with the list property to hold the product array. Open the index page and, just after the ‘use strict’ statement in your JavaScript, add a variable named vm. This variable is your “view model” class with the list property that you pass to the buildList() function.

let vm = {
   "list": []
};

Within the if() statement of the get() function, replace the code within the if() with the lines of code shown below.

function get() {  
    let req = new XMLHttpRequest();  
    req.onreadystatechange = function () {    
        if (this.readyState === XMLHttpRequest.DONE        
                && this.status === 200) {      
            vm.list = JSON.parse(this.response);      
            buildList(vm);    
                }  
    };
    req.open("GET", URL);  
    req.send();
}

This new code takes the JSON string passed back from the Web API server and parses it into a JSON array. That array is placed into the vm.list property. The view model object is passed to the buildList() function where mustache is used to create the HTML from the template and this array of product data.

The final piece of refactoring is to call the get() function after the index page has completely loaded. Add the following code to call the get() function from the window.onload.

window.onload = function () {get();}

Try It Out

Save all the changes and go to your browser. If you've done everything correctly, you should see a page that looks like Figure 2. If you don't see this page, check the console window for any errors you may have accidently typed in.

Get a Single Product

Now that you have the list of product data displaying in an HTML table, it's time to retrieve a single product from the Web API and display that in the HTML form built in the last article. Open the index page and replace the empty getProduct() function with the code in the following snippet.

function getProduct(productId) {  
    let req = new XMLHttpRequest();  
    req.onreadystatechange = function () {    
        if (this.readyState === XMLHttpRequest.DONE
              && this.status === 200) {
            setInput(JSON.parse(this.response));
            displayDetail();    
             }  
    };  
    req.open("GET", URL + "/" + productId);  
    req.send();
}

The code above is very similar to the get() function, except you pass a productId value after the URL. An example of this might be http://localhost:5000/api/product/706. The value 706 is what's passed into this function as the productId. The response from the Web API call is a single product object expressed as JSON. Parse that JSON string into a real JavaScript object and pass that object to the setInput() function written in the last article. This function is in the product.js file and is used to put each property from the product object into the appropriate input elements within the form.

The last thing the getProduct() function does is to call a function named displayDetail(). Open the product.js file and add a new function called displayDetail() to hide the HTML table and make the form with the product detail visible.

function displayDetail() {
    document.getElementById("list").style.display = "none";  
    document.getElementById("detail").style.display = "block";
}

All you need to do now is to somehow pass a product ID from the table to the getProduct() function. Open the index page and locate the <table> element you added. Add a new table header before all the other headers, as shown in the code below.

<th>Action</th>

Next, locate the <script id="dataTmpl"> element and before all the other <td> elements, create a new <td> element before the other <td> elements. Add the code shown below to create a new button and for each button's click event, call the getProduct() function passing in the product ID for that product object.

<td>  
    <button type="button" onclick="getProduct({{productID}});">Edit</button>
</td>

Try It Out

Save all the changes in your pages and go back to the browser and you should now see an Edit button in the left-most column of the table. Click on any of those buttons and you should be shown the detail information for that product in the input elements of the form. To get back to the list of product data, click the Get Products button.

Prepare the Index Page for CRUD

In the last article, I set the stage for CRUD, but I didn't want to build the complete HTML table along with presenting the basics of building the Web API and using the XMLHttpRequest object. Now, it's time to refactor the index page to add a button for adding a new product, and two buttons within the table for editing and deleting individual products. First, locate the <div> with the infoMessage and errorMessage labels and move it from its current location to below the <h1> element.

<div class="row">
      <label id="message" class="infoMessage"></label>
      <label id="error" class="errorMessage"></label>
 </div>

Next, remove all the buttons within the <div class="row"> that's the last row in the form element. Add the following buttons back into this row where you just deleted the other buttons. The Save button is used to save updates to an existing product or to a new product. The Cancel button is used to go back to the list of products in the HTML table.

<button type="button" id="saveButton" onclick="save();">Save</button>
<button type="button" id="cancelButton" onclick="cancel();">Cancel</button>

Because you've deleted the buttons to delete a product, you must replace that functionality somewhere. Just like you created an Edit button on each row of the product table, you need to do the same for deleting a product. Add a Delete button within the <td> of the data template just after the Edit button, as shown in the code below.

<td>
    <button type="button" onclick="getProduct({{productID}});">Edit</button>
    &nbsp;
    <button type="button" onclick="deleteProduct({{productID}});">Delete</button>
</td>

Earlier in this article, you created an Add button that calls an add() function used to display a blank form for inputting a new product. You also have a function that calls the getProduct() to display an existing product in the form. When the user clicks on the Save button, how do you know whether the user wants to insert a new product or update an existing product? An easy way to accomplish this is to add a mode property to the view model variable. You can toggle this mode property between three values “list”, “edit”, and “add”. Modify the view model variable and add the “mode” property and initialize it to “list”.

let vm = {  
    "list": [],
    "mode": "list"
};

Add the add() function somewhere in your JavaScript code on the index page. This function sets the mode property of the view model variable to “add”. It then calls the clearInput() function to display blank product data in the input fields of the form. Finally, it calls displayDetail() to make the input form visible and to hide the table of product data.

function add() {
    vm.mode = "add";
    clearInput();
    displayDetail();
}

Add a save() function to the JavaScript on the index page. This function checks the mode property of the view model variable to determine whether to call the insertProduct() or the updateProduct() function.

function save() {
    if(vm.mode === "add") {
        insertProduct();  
    } else if(vm.mode === "edit") {
      updateProduct();  
    }
}

Add a cancel() function to the JavaScript on the index page. This function sets the mode property back to “list” and calls the get() function to get all the data from the server again and displays that data in the HTML table.

function cancel() {
    vm.mode = "list";  
    get();
}

When you click on the Edit button, you need to set the mode to “edit” in the view model variable. This way, when you click on the Save button, the code calls the updateProduct() function. Locate the getProduct() function and add the following code at the top of the function.

function getProduct(productId) {
    vm.mode = "edit";

    // REST OF THE CODE HERE}

The last thing you need to do is to modify the deleteProduct() function to accept a productId variable. You learn how to delete a product just a little later in this article.

function deleteProduct(productId) {
}

Try It Out

Save all your changes and go back to the browser and you should now see both Edit and Delete buttons in the left-most column of the table. Click on the Edit button to be sure it still displays existing product information. You can now click on the Cancel button to return to the list of product data. Click on the Add button to see a set of blank input fields on the product form.

Insert a Product

You add a new product to the SQL Server database by calling the Post() method in the Web API server. You call this method from the index page by adding the code shown in Listing 5 to the empty insertProduct() function. The first line in this function calls the getFromInput() function to build the product object to be submitted to the Post() Web API method. The rest of the Ajax code is similar to what you wrote before with just a few differences.

Listing 5: Change the verb to “POST” in the XMLHttpRequest object's open() method to insert data.

function insertProduct() {  
    // Get product data from input fields  
    let product = getFromInput();

    // Create XMLHttpRequest object  
    let req = new XMLHttpRequest();

    // Hook up the onreadystatechange event  
    req.onreadystatechange = function () {
        if (this.readyState === XMLHttpRequest.DONE &&
            this.status === 201) {
                // Get product object
                product = JSON.parse(this.response);

                // Display new product data
                setInput(product);
                
                // Display a success message
                displayMessage("Product inserted successfully.");

                // Hide buttons      
                document.getElementById("saveButton").style.display = "none";    
                document.getElementById("cancelButton").style.display = "none";

                // After a few seconds, redisplay product list
                setTimeout(() => {        
                    // Display List        
                    get();

                    // Clear message
                    displayMessage("");      
                }, TIMEOUT);
            }  
    };
    // Open the request object  
    req.open("POST", URL);
    // Add Content-Type header  
    req.setRequestHeader("Content-Type", "application/json");

    // Send the request to the server  
    req.send(JSON.stringify(product));
}

The first difference is the call to the open() method on the XMLHttpRequest object. You pass in the verb “POST” as the first parameter. This maps to the Post() method in the Web API because the attribute [HttpPost()] is decorating the Post() method.

The second difference is that you add a header to the request object to set the content-type header to “application/json”. This informs the Web API that a JSON object is being passed in. The third difference is that when you call the send() method you stringify the product JSON object.

The last difference in this function is in the onreadystatechange event. You need to check for a status code of 201, as this is what you're sending back from the Post() method in the Web API. When you get the response property, use the JSON.parse() method to convert the response into a JSON object and put that object into the product variable.

After the data is successfully posted, call the setInput() function, passing in the product object to display any new data, such as the new Product ID on the input fields. The Save and Cancel buttons are then hidden to avoid the user attempting to click on either of them again. Finally after a delay of a certain number of seconds, the get() function is called to redisplay the list of products with the new product now included in that list. The number of seconds to delay is specified by a constant that you should add near the other constant in the index page.

const TIMEOUT = 3000;

Bring Back Buttons

Because you've now hidden the buttons after inserting, if you click on the Add button again, or on an Edit button, you need to bring back the buttons. Add the following two lines of code at the top of the getProduct() function.

function getProduct(productId) {  
    // Display buttons
    document.getElementById("saveButton").style.display = "inline";
    document.getElementById("cancelButton").style.display = "inline";

    let req = new XMLHttpRequest();
    
    // REST OF THE CODE HERE
}

Try It Out

Save all changes to all files in the HTML project and go to your browser. You should see a page that looks like Figure 3. Fill in the product information that you wish to add, making sure to leave the Product ID field equal to a zero (0) value. Click the Save button and, if everything works correctly, you should see a value appear in the Product ID field. This lets you know that the product object was successfully inserted into the table. After a couple of seconds, the product table reappears with the new product you entered.

Figure 3: Fill in data to insert on the index page.
Figure 3: Fill in data to insert on the index page.

Error Handling

Up to this point, the code you've written doesn't check for any errors. You should always add error handling to your code. In the ajax-common.js file is a function called handleAjaxError(). This function should be called if an error occurs when making a Web API call. Modify the insertProduct() function to add some error handling. Locate the insertProduct() function and add the code shown in Listing 6 to this function.

Listing 6: Add some error handling to your insertProduct() function.

function insertProduct() {  
    // Get product data from input fields  
    let product = getFromInput();

    // Create XMLHttpRequest object  
    let req = new XMLHttpRequest();

    // When the entire request fails it is probably a network error  
    req.onerror = function () {
        handleAjaxError(new Error(
        'There was a network error.'));
    };
    // Hook up the onreadystatechange event  
    req.onreadystatechange = function () {
        if (this.readyState === XMLHttpRequest.DONE
        && this.status === 201) {

        // REST OF THE CODE HERE
        
        } else if (this.readyState === XMLHttpRequest.DONE && this.status >= 400) {
        
        // Check for error
        handleAjaxError({
            "status": this.status,
            "statusText": this.statusText,
            "response": this.response, 
            "responseText": this.responseText 
        });

        displayError("Product NOT inserted. Please try again.");    
        }  
    };

    // REST OF THE CODE HERE
}

Try It Out

Save all the changes in your HTML project and go to the browser. Click on the Add Product button and enter any data you want but use the exact same Product Number that you inserted previously. The Product table won't allow duplicates in the ProductNumber field, so this generates an error. Click on the Save button and you should get an error message displayed on the screen in response to the HTTP status code 500 reported back from the Web API Post() method.

Update Product Data

If you wish to update a product that already exists in the AdventureWorksLT Product table, you need to call the Put() method in your Web API project. Add the code to the updateProduct() function shown in Listing 7 into the index page to make this call.

Listing 7: Use the PUT verb to update a product.

function updateProduct() {
    // Get product data from input fields  
    let product = getFromInput();
    
    // Create XMLHttpRequest object  
    let req = new XMLHttpRequest();

    // When the entire request fails it is probably a network error
    req.onerror = function () {
        handleAjaxError(new Error('There was a network error.'));
    };

    // Hook up the onreadystatechange event  
    req.onreadystatechange = function () {    
        if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
            // Get product object
            product = JSON.parse(this.response);

        // Display new product data
        setInput(product);

        // Display a success message
        displayMessage("Product updated successfully.");

        // Hide buttons      
        document.getElementById("saveButton").style.display = "none";
        document.getElementById("cancelButton").style.display = "none";

        // After a few seconds, redisplay products
        setTimeout(() => {
            // Display List
            get();
        
            // Clear message    
            displayMessage("");
        }, TIMEOUT);    
        } else if (this.readyState === XMLHttpRequest.DONE && this.status >= 400) {
        // Check for error
        handleAjaxError({        
            "status": this.status,        
            "statusText": this.statusText,        
            "response": this.response,        
            "responseText": this.responseText      
        });

        displayError("Product NOT updated. Please try again.");    
        }
    };

    // Open the request object  
    req.open("PUT", URL + "/" + product.productID);

    // Add Content-Type header
    req.setRequestHeader("Content-Type", "application/json");

    // Send the request to the server  
    req.send(JSON.stringify(product));
}

There are a few differences between the insertProduct() and the updateProduct() function. The first is the Put() method, which returns a HTTP status code of 200 instead of 201. The next difference is that you pass the verb “PUT” to the open() method. The second parameter to the open() method adds the product ID to the URL. Other than these differences, the code is almost the same between inserting and update a product.

Try It Out

Save all the changes in your HTML project. Go to the browser and click the Edit button next to the new product you entered previously. Modify the Color, the Cost, or the Price values and click on the Save button to see if all the changes you entered work correctly.

Delete Product Data

Now that you've inserted and updated product data, let's learn to delete a product from the table. Add the code shown in Listing 8 to the deleteProduct() function on the index page. In this function, confirm from the user that they really wish to delete the product ID they selected. If they answer affirmatively, the code is similar to the code for inserting and updating. The big difference is the call to the open() method passes the verb “DELETE” as the first parameter, and the product ID is passed on the URL line.

Listing 8: Use the DELETE verb to delete a product.

function deleteProduct(productId) {  
    if (confirm("Delete product " + productId + "?")) {
        // Create XMLHttpRequest object    
        let req = new XMLHttpRequest();

        // When the entire request fails it is probably a network error
        req.onerror = function () {
            handleAjaxError(new Error('There was a network error.'));    
        };

        // Hook up the onreadystatechange event    
        req.onreadystatechange = function () {
            if (this.readyState === XMLHttpRequest.DONE && this.status === 200) {
                // Display success message 
                displayMessage("Product was deleted");

                 // Display list
                 get();
                 
                 // Clear Message
                 setTimeout(() => {
                     displayMessage("");        
                 }, TIMEOUT);
            } else if (this.readyState === XMLHttpRequest.DONE && this.status >= 400) {
                // Check for error
                handleAjaxError({
                    "status": this.status,
                    "statusText": this.statusText,
                    "response": this.response,
                    "responseText": this.responseText
                });
            }
        };
        // Open the request object
        req.open("DELETE", URL + "/" + productId);

        // Send the request to the server    
        req.send();  
    }
}

Try It Out

Save all the changes in your HTML project and go to the browser. Click on the Delete button next to the new product you previously entered. Answer the prompt to delete the product you wish to delete. The page refreshes with the product list that no longer has your new product in it. A message displays, specifying that the product was deleted. After a couple of seconds, that message will disappear.

Simplify the XMLHttpRequest Code

Looking at the Ajax code in your index page, notice that the functions for getting and modifying the data are almost identical to one another. If you see similar code in your projects, you should always try to make it generic. This Ajax code can be made generic by creating a function named ajax() in the ajax-common.js file. Open the ajax-common.js file and add the function shown in Listing 9. This function accepts five arguments that are described in Table 1.

Listing 9: Create a generic ajax() function that uses callbacks to indicate success or failure.

function ajax(verb, url, data, resolve, reject) {  
    // Create XMLHttpRequest object  
    let req = new XMLHttpRequest();

    // When the entire request fails it is probably a network error  
    req.onerror = function () {    
        if(reject) {
            reject(new Error("There was a network error."));
        }  
    };

    // Setup state change event  
    req.onreadystatechange = function () {    
        if (this.readyState === XMLHttpRequest.DONE) {
            // Check status property to see what is going on
            if (this.status >= 200  && this.status < 400) {
                if (resolve) {
                    resolve(JSON.parse(this.response));
                }
            } else if (this.status >= 400) {
                if(reject) {
                    reject({ 
                        "status": this.status,
                        "statusText": this.statusText,
                        "response": this.response,
                        "responseText": this.responseText
                    });
                }      
            }
        }
    };
    // Open Request  
    req.open(verb, url);

    // Set headers for JSON  
    req.setRequestHeader("Content-Type", "application/json");
    
    // Check to see if we need to pass data  
    if (data) {
        // Submit the request with data
        req.send(JSON.stringify(data));  
    }  else {
        // Submit the request  
        req.send();  
    }
}

Get All Products

Now that you've created this generic ajax() function, all of your Ajax calls can be greatly simplified. Open the index page and locate the get() function and modify it to look like the following snippet. The fourth and fifth parameters are callback functions to handle success and failure respectively.

function get() {  
    ajax("GET", URL, null,    
        function (data) { 
            vm.list = data;
            buildList(vm); 
        },
        function (error) {
            handleAjaxError(error); 
        });
}

Get a Single Product

Locate the getProduct() function and simplify the code to use the new ajax() function, as shown in Listing 10. What this function does hasn't changed; it still retrieves a single product object. However, how it accomplishes this task is simplified and is much easier to read.

Listing 10: Simplify the getProduct() function using the generic ajax() function.

function getProduct(productId) {
    vm.mode = "edit";

    // Display buttons  
    document.getElementById("saveButton").style.display = "inline"; 
    document.getElementById("cancelButton").style.display = "inline";

    ajax("GET", URL + "/" + productId, null, function (data) {
        setInput(data);
        displayDetail();    
    },
    function (error) { 
    handleAjaxError(error);
    });
}

Insert a Product

Locate the insertProduct() function and change it to use the new ajax() function, as shown in Listing 11. Once again, you can see how much easier this code is to read because you don't have all the XMLHttpRequest object code to deal with.

Listing 11: Simplify the insertProduct() function using the generic ajax() function.

function insertProduct() { 
    let product = getFromInput();

    ajax("POST", URL, product, function (data) {
        // Get product object 
        product = data;

        // Display new product data      
        setInput(product);

        // Display a success message
        displayMessage("Product inserted successfully.");

        // Hide buttons
        document.getElementById("saveButton").style.display = "none"; 
        document.getElementById("cancelButton").style.display = "none";

        // After a few seconds, redisplay products
        setTimeout(() => {
            // Display List
            get();
            // Clear message
            displayMessage("");      
        }, TIMEOUT);    
    },
    function (error) {
        handleAjaxError(error);

        displayError("Product NOT inserted. Please try again.");    
    });
}

Update a Product

Locate the updateProduct() function and simplify the code by using the new ajax() function, as shown in Listing 12.

Listing 12: Simplify the updateProduct() function using the generic ajax() function.

function updateProduct() {  
    let product = getFromInput();

    ajax("PUT", URL + "/" + product.productID, product,  
        function (data) {
            // Get product object 
            product = data;
        
            // Display new product data
            setInput(product);
        
            // Display a success message
            displayMessage("Product updated successfully.");
        
            // Hide buttons      
            document.getElementById("saveButton").style.display = "none";
            document.getElementById("cancelButton").style.display = "none";

            // After a few seconds, redisplay products
            setTimeout(() => { 
                // Display List
                get();

                // Clear message
                displayMessage("");      
            }, TIMEOUT);    
        },
        function (error) { 
            handleAjaxError(error);
            displayError("Product NOT updated. Please try again.");
        });
}

Delete a Product

Locate the deleteProduct() function and modify it to use the new ajax() function, as shown in Listing 13.

Listing 13: Simplify the deleteProduct() function using the generic ajax() function.

function deleteProduct(productId) {
    if (confirm("Delete product " + productId + "?")) {
        ajax("DELETE", URL + "/" + productId, null, 
            function (data) {  
                // Display success message
                displayMessage("Product was deleted");

                // Display list 
                get();

                setTimeout(() => {
                    // Clear message
                    displayMessage("");
                }, TIMEOUT);      
            },
            function (error) {
                handleAjaxError(error);

                displayError("Product NOT deleted. Please try again.");
            }
        );
    }
}

Try It Out

Save all changes you made in the HTML project and go to the browser and click on each of the buttons to ensure that you can still add, edit, and delete products. NOTE: If you run the page and you don't get any results, press Ctrl-5 to force a full refresh of the page. Sometimes the .js files get cached and aren't downloaded after you make changes.

Summary

In this article, you learned to use the XMLHttpRequest object and the mustache templating engine to build a CRUD page. In addition, you learned to add error handling to your code. When you find yourself creating a lot of duplicate code, try to find ways to make that code more generic by creating a function to call as you did by creating the ajax() function. Creating this generic function greatly simplifies the use of the XMLHttpRequest object throughout your project. In the next article, you'll learn to use Promises to wrap up the calls to the XMLHttpRequest object and see a built-in API call Fetch that also uses Promises.

Table 1: The arguments you can pass to the ajax() function.

ARGUMENTDESCRIPTION
verbAn HTTP verb such as GET, POST, PUT, DELETE, etc.
urlThe URL where the resource you wish to call is located.
dataThe data to send to the API call. This argument is optional.
resolveA callback that is called to indicate that the Ajax call was successful. This argument is optional.
rejectA callback that is called to indicate that the Ajax call was not successful. This argument is optional.