1

I posted a question regarding this a few days ago and the advice I got seemed to work, partially, with returning the results I wanted, but with the final rendering of data, I ran into my last issue. Here's the original post if you'd like to refer to it: Grab the value of a span element

I rewrote all of my code using Fetch instead of the older xmlHttp API since that was recommended in the previous post. I also wanted to break the functions up a bit into other functions for readability.

You'll see that the fetch API is making a call to another database (I had to hide the headers for privacy reasons) and calling the callFetch function. The callFetch function essentially takes the data object and runs the addresses from data through Google's distanceMatrix API in order to compare the user's input address (repeated the same number of times as the addresses array) to the addresses in the data object. Inside of getDistance, I'm getting all of the compared results and pushing the results to separated arrays.

Inside of getSortedValues, I'm taking my results from getDistance, sorting them by mileage (which is one of the more important parts to what I want to return in my rendered data) then passing them as separate objects (facility, distance, time, address) to an array called combined.

Lastly, renderTemplate takes combined and renders the html with values from each combined object's values and renders to the page dynamically.

I updated some of my code and am attempting to clarify a bit more. Buttons requested that I add mock values since there's no way for users to test the code. I tried that, but it changed the behavior of my code and took the dynamic part out of pulling data from another source where the problem of awaiting no longer existed.

I believe my main problem lies in sortValues. You'll see the first time everything fires (in the image) when I hit the submit button (I added console logs to each function to better understand the sequence of functions firing) sortValues returns an empty array. The second time the array populates with all four objects of 15 values each. So, I believe, regardless of me adding async to renderTemplateand awaiting sortValues, sortValues is still going to return an empty array the first time around. I think the root of all this is waiting for combined to populate inside of sortValues. Does anyone know what I can do to wait or get combined to populate the first time sortValues is called?

console log

let inputVal = [];
let facilityArray = [];
let distanceArray = [];
let timeArray = [];
let filteredArray = [];
let sortedArray = [];
let combined = [];
let facAddress = [];
let resultsObject, intDistanceResult;
const resetButton = document.querySelector("#refresh");


// Set data object
const data = {
    from: "xxxxxxxxx",
    // Facility id and address of each location
    select: [6, 8],
    options: {
        skip: 0,
        top: 0,
    },
};

fetchAPI = () => {
    fetch("https://api.quickbase.com/v1/records/query", {
            method: "POST",
            headers: {
                "QB-Realm-Hostname": "xxxxx.quickbase.com",
                "Authorization": "QB-USER-TOKEN xxxxxxxxxxxxxxxxxx",
                "Content-Type": "application/json",
            },
            body: JSON.stringify(data),
        })
        .then(resp => resp.json())
        .then(data => callFetch(data))
        .catch(error => console.log("Error:", error));
    console.log("fetchAPI fired")
};

callFetch = dataObject => {
    console.log("callFetch fired");
    // Map through the data object and grab the facId and address of each location
    const facId = dataObject.data.map(e => e["6"].value);
    const addresses = dataObject.data.map(e => e["8"].value);
    // Create Google Maps distance service instance
    distanceService = new google.maps.DistanceMatrixService();

    for (let i = 0; i < addresses.length; i++) {
        // Add matrix settings object
        distanceService.getDistanceMatrix({
                origins: [inputVal.toString("")],
                destinations: [addresses[i]],
                travelMode: "DRIVING",
                unitSystem: google.maps.UnitSystem.IMPERIAL,
                durationInTraffic: true,
                avoidHighways: false,
                avoidTolls: false,
            },
            // Set response and error capture
            (response, status) => {
                if (status !== google.maps.DistanceMatrixStatus.OK) {
                    console.log("Error:", status);
                    const message = document.querySelector("#message");
                    message.innerHTML = `Error: ${status}. Please resubmit.`;
                } else {
                    const distanceResult = response.rows[0].elements[0].distance
                        .text;
                    const timeResult = response.rows[0].elements[0].duration.text;
                    // Convert distanceResult values to integers and push to distanceArray to later sort
                    intDistanceResult = parseInt(distanceResult.replace(/,/g, ""));
                    // Push results to respective arrays to pass to combined object inside of sortValues function
                    facilityArray.push(facId[i]);
                    distanceArray.push(intDistanceResult);
                    timeArray.push(timeResult);
                    facAddress.push(addresses);
                }
            }
        );
    }
    renderTemplate();
};

sortValues = () => {
    console.log("sortValues fired");
    // Re-sort array of objects from getDistance function with values in order by mileage, ascending
    combined = facilityArray
        .map((facility, i) => ({
            facility,
            distance: distanceArray[i],
            time: timeArray[i],
            address: facAddress[i]
        }))
        .sort((first, second) => first.distance - second.distance);
    console.log(combined);
};

renderTemplate = async() => {
    await sortValues();
    console.log("renderTemplate fired");
    // Grab container div entry point in html
    let container = document.querySelector(".container-sm");
    for (let i = 0; i < combined.length; i++) {
        // Create html dynamically and populate based on combined' objects values
        let div = document.createElement("div");
        div.classList.add("d-inline-flex", "p-2", "mb-1");
        div.innerHTML =
            `<div class="container">
                <div class="card" style="width: 20rem; height: fixed;">
                    <div class="card-body">
                        <input class="form-control" hidden value="${inputVal.join('')}" type="text" placeholder="Destination Address" id="destaddress${i}">
                        <input class="form-control" hidden readonly type="text" placeholder="Start Address" id="startaddress${i}">
                        <h6 class="card-title" style="font-weight: bold">Service Center - ${combined[i].facility}</h6>
                        <h6 class="card-title">Distance - <span id="distance${i}">${combined[i].distance} miles</span></h6>
                        <h6 class="card-title">Drive Time - <span id="time${i}">${combined[i].time}</span></h6>
                    </div>
                </div>
            </div>`;
        // Append new values to the div
        container.appendChild(div);
    }
}

form.addEventListener("submit", (e) => {
    e.preventDefault();
    const patientAddressInput = document.querySelector("#patientaddress");
    // Get user entry and assign to inputVal in dynamically created HTML
    inputVal.push(patientAddressInput.value);
    fetchAPI();
    console.log("Listener fired");
});

resetButton.addEventListener("click", () => location.reload());
<!DOCTYPE html>
<html>

<head>
  <title>Ethos Service Center - Google Maps Distance Search</title>
  <link rel="stylesheet" type="text/css" href="style.css">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
</head>

<body>
  <div class="container-sm mt-3">
    <form class="form mb-3" id="form">
      <div class="search">
        <div class="card-body">
          <div class="input-group">
            <div class="input-group-prepend">
              <span class="input-group-text">Patient Destination</span>
            </div>
            <input class="form-control" type="text" placeholder="Enter Zip Code" class="patientaddress" id="patientaddress" required>
            <div class="input-group-prepend">
              <span class="input-group-text" id="message"></span>
            </div>
          </div>
          <hr>
          <button class="btn btn-primary mt-2" type="submit" id="submit">Submit</button>
          <button class="btn btn-outline-success mt-2 ml-3" type="reset" value="Reset" id="refresh">Clear Destination</button>
        </div>
      </div>
    </form>
  </div>
  <script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false&libraries=places&key=[Google Maps API Key]"></script>
  <script type="text/javascript" src="main.js"></script>
</body>

</html>
JackJack
  • 181
  • 1
  • 1
  • 20
  • Seems like `fetchAPI()` function is supposed to be `await`ed. And the `renderTemplate` function isn't doing anything async. A working code example with mock data would be great. It's unclear to me. You can try adjusting your code to `await fetchAPI();` and then call the `getSortedValues()` function because now all the variables are in place. – butttons Aug 25 '20 at 14:48
  • Thanks. I'll work on adding mock data since I have to exclude some of these private keys. – JackJack Aug 25 '20 at 14:50
  • @butttons Using the fetch api promises with `then` instead of `await` is totally fine. – Bergi Aug 26 '20 at 11:56
  • Your `fetch` call and the promise chain are good. The problem is not with `renderTemplate` or `sortValues` either - those work (and are synchronous, you should not use `await` with them). The problem is actually the asynchronous `distanceService.getDistanceMatrix` calls - you are trying to render the (still empty) template before those were finished. – Bergi Aug 26 '20 at 11:57
  • Thanks, Bergi. So how do I go about fixing that, if possible? – JackJack Aug 26 '20 at 12:03
  • 1
    @Ang Extract the `getDistanceMatrix` thing into a helper function and [make that return a promise](https://stackoverflow.com/q/22519784/1048572). Then either `await` them in your loop, or create all promises at once, put them in an array, and use `Promise.all` which you can `await` (or chain a `.then()` to), at which point you can render the results. – Bergi Aug 26 '20 at 15:10

1 Answers1

0

I found a way to do this, albeit a hackish, cheesy and probably not the most efficient way. I added a ternary operator at the end of the for loop that checks if the value of i is equal to the index value of the last element in the array. If it is, wait 1 second (by running a setTimeout function) for the DistanceMatrix calls to run, so that I have some values, then call the sortValues function. It works, at the very least. All of the code is the same, but here's the callFetch function with ternary operator and setTimeout function added.

callFetch = async dataObject => {
    console.log("callFetch fired");
    // Map through the data object and grab the facId and address of each location
    const facId = dataObject.data.map(e => e["6"].value);
    const addresses = dataObject.data.map(e => e["8"].value);
    // Create Google Maps distance service instance
    distanceService = new google.maps.DistanceMatrixService();

    for (let i = 0; i < addresses.length; i++) {
        // Add matrix settings object
        distanceService.getDistanceMatrix({
                origins: [inputVal.toString("")],
                destinations: [addresses[i]],
                travelMode: "DRIVING",
                unitSystem: google.maps.UnitSystem.IMPERIAL,
                durationInTraffic: true,
                avoidHighways: false,
                avoidTolls: false,
            },
            // Set response and error capture
            (response, status) => {
                if (status !== google.maps.DistanceMatrixStatus.OK) {
                    console.log("Error:", status);
                    const message = document.querySelector("#message");
                    message.innerHTML = `Error: ${status}. Please resubmit.`;
                } else {
                    distanceResult = response.rows[0].elements[0].distance.text;
                    timeResult = response.rows[0].elements[0].duration.text;
                    // Convert distanceResult values to integers and push to distanceArray to later sort
                    intDistanceResult = parseInt(distanceResult.replace(/,/g, ""));
                    // Push results to respective arrays to pass to combined object inside of sortValues function
                    facilityArray.push(facId[i]);
                    distanceArray.push(intDistanceResult);
                    timeArray.push(timeResult);
                    facAddress.push(addresses);
                }
            });
        (i == addresses.length - 1 ? setTimeout(() => { sortValues() }, 1000) : null)
    }
};
JackJack
  • 181
  • 1
  • 1
  • 20