7
\$\begingroup\$

I'd be grateful for a review of my AJAX request. If I have taken the correct approach here and especially any security concerns!

I am creating a Purchase Order system for my manager (as part of an apprenticeship I am doing).

There is a select html element where a supplier can be selected. I also have a button to trigger a bootstrap modal with a form to enter a new supplier. I have an AJAX request to add the new supplier to the database then a chain AJAX request to query the database so the select element can be updated.


<script>
    window.addEventListener('DOMContentLoaded', function () {
        // User can add a new supplier using a modal form.
        // 1) Send an AJAX POST request to update the database
        // 2) Send AJAX GET request to query the database
        // 3) Repopulate the select element with the updated records.
        document.getElementById("newSupplierModalForm").addEventListener('submit', function (e) {
            // Send AJAX request to add new supplier.
            // TODO: Validate name field has text and handle error.
            e.preventDefault();
            const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
            const xhr = new XMLHttpRequest();
            xhr.open('POST', '/supplier', true);
            // Set headers
            xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
            xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
            xhr.setRequestHeader('X-CSRF-Token', token);
            //Set paramaters
            const params = new URLSearchParams();
            params.set("name", document.getElementById('supName').value);
            params.set("address_1", document.getElementById('supAddress_1').value);
            params.set("address_2", document.getElementById('supAddress_2').value);
            params.set("town", document.getElementById('supTown').value);
            params.set("post_code", document.getElementById('supPost_code').value);
            params.set("contact_name", document.getElementById('supContact_name').value);
            params.set("contact_tel", document.getElementById('supContact_tel').value);
            params.set("contact_email", document.getElementById('supContact_email').value);
            // Logic
            xhr.onload = function (e) {
                if (xhr.status === 200) {
                    // Close modal
                    document.getElementById('addSuppModalClose').click();

                    // AJAX request for updated supplier records to repopulate select element.
                    const xhr_getSuppliers = new XMLHttpRequest();
                    xhr_getSuppliers.open('GET', '/getSuppliers', true);
                    // Set headers
                    xhr_getSuppliers.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
                    xhr_getSuppliers.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
                    xhr_getSuppliers.setRequestHeader('X-CSRF-Token', token);
                    // Logic
                    xhr_getSuppliers.onload = function (e) {
                        if (xhr.status === 200) {
                            const data = JSON.parse(xhr_getSuppliers.response);
                            const selectElem = document.getElementById('supplier');
                            selectElem.options.length = 0; //reset options
                            // populate options
                            for (index in  data) {
                                console.log(data[index], index);
                                selectElem.options[selectElem.options.length] = new Option(data[index], index);
                            }
                        }
                    };
                    // Send GET Request
                    xhr_getSuppliers.send();
                } else {
                    // Show alert if AJAX POST Request does not receive a 200 response.
                    // I need to write code to feed this back to the user later.
                    alert(xhr.response);
                };
            };
            // Send POST Request
            xhr.send(params.toString());
        });
    });
</script>

```
\$\endgroup\$
2
\$\begingroup\$

Feedback

The code uses const for variables that don't need to be re-assigned. This is good because it prevents accidental re-assignment. It also uses === when comparing the status codes, avoiding type coercion. That is a good habit.

There are at least four indentation levels because the onload callbacks are anonymous functions. Some readers of the code would refer to this as "callback hell", because really there is just one large function here with functions defined inside of it, making it challenging to read. See the first section of the Suggestions below for tips on avoiding this.

The const keyword was added to the Ecmascript Latest Draft in Draft status and standard in the Ecmascript 2015 specification1 so other ecmascript-2015 (AKA ES-6) features could be used to simplify the code.

Suggestions

Callbacks and Nesting/Indentation levels

While most of the examples on callbackhell.com focus on promise callbacks and asynchronous code, the concepts apply here as well. Name the functions and then move the definitions out to different lines. If you need to access variables from outside then you may need to pass them as arguments.

Data Object and repeated DOM lookups

I will be honest - I haven't encountered the URLSearchParams before. I typically see code that uses FormData. Are all of the input elements inside of a <form> tag? if they are then a reference to that element could be passed to the FormData constructor. Otherwise, do the name attributes match the keys added to params - i.e. the id attributes without the sup prefix? You could iterate over the input elements and add the parameters to params.

Avoid global variables

In the review by dfhwze, The following line is referenced:

for (index in  data) {

Without any keyword before index, like var, let or const that variable becomes a global (on window).

\$\endgroup\$
  • 2
    \$\begingroup\$ Pointing out good aspects of the OP's code is something I should do more often in my reviews. Well done. \$\endgroup\$ – dfhwze Aug 9 at 19:02
2
\$\begingroup\$

For let variable in

EcmaScript6 lets you create a closure in a for loop using let. It is preferred over var, which is preferred over nothing (unless you specifically want to reuse an existing variable declared before the loop).

for (index in  data) {
  console.log(data[index], index);
  selectElem.options[selectElem.options.length] = new Option(data[index], index);
}
for (let index in data) {
  console.log(data[index], index);
  selectElem.options[selectElem.options.length] = new Option(data[index], index);
}

Semi-colon

If-Else branch does not require a semi-colon to terminate it.

 } else {
   // Show alert if AJAX POST Request does not receive a 200 response.
   // I need to write code to feed this back to the user later.
   alert(xhr.response);
 };
 } else {
   // Show alert if AJAX POST Request does not receive a 200 response.
   // I need to write code to feed this back to the user later.
   alert(xhr.response);
 }
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.