1

I initially referred to Get value of Span Text because I was trying to grab the text value of a span element:

image-desc

I tried what was mentioned in the article, but the values I have are coming up blank, unless I trigger the action a second time. Here's my code:

let startInput, destInput, distance, facId, time, resultsObject;
let facilityArray = [];
let distanceArray = [];
let timeArray = [];
let sortArray = [];
let tempArray = [];
let slicedArray = [];
let inputVal = [];

// Set headers
const headers = {
  'QB-Realm-Hostname': 'xxxxxx.quickbase.com',
  'Authorization': 'QB-USER-TOKEN xxxxxxxxxxxxxxxxxxxxxxxx',
  'Content-Type': 'application/json'
}

// Call QB table and pull data from column facID 6, address 8
const body = {
  "from": "xxxxxxxx",
  "select": [6, 8],
  "options": {
    "skip": 0,
    "top": 0
  }
}

const generateHTML = () => {
  // Create xml request class
  const xmlHttp = new XMLHttpRequest();
  xmlHttp.open('POST', 'https://api.quickbase.com/v1/records/query', true);
  // Extract keys from headers
  for (const key in headers) {
    // Grab keys and values from the headers object
    xmlHttp.setRequestHeader(key, headers[key]);
  }
  xmlHttp.onreadystatechange = () => {
    if (xmlHttp.readyState === XMLHttpRequest.DONE) {
      const jsonObject = JSON.parse(xmlHttp.responseText);
      facId = jsonObject.data.map(e => e["6"].value);
      const addresses = jsonObject.data.map(e => e["8"].value);

      // Grab container div entry point in html
      let container = document.querySelector(".container-sm")

      // Create html elements dynamically based on length of addresses array
      for (let i = 0; i < addresses.length; i++) {
        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 readonly type="text" placeholder="Start Address" id="startaddress${i}">
                                <input class="form-control" hidden value="${inputVal.join('')}" type="text" placeholder="Destination Address" id="destaddress${i}">
                                <h6 class="card-title">Service Center - ${facId[i]}</h6>
                                <h6 class="card-title">Distance - <span id="distance${i}"></span></h6>
                                <h6 class="card-title">Drive Time - <span id="time${i}"></span></h6>
                            </div>
                        </div>
                    </div>`;

        container.appendChild(div);
        // Dynamically set input fields
        startInput = document.querySelector(`#startaddress${[i]}`);
        startInput.value = addresses[i];
        destInput = document.querySelector(`#destaddress${[i]}`).innerText;
        distance = document.querySelector(`#distance${i}`).innerText;
        time = document.querySelector(`#time${i}`).innerText;

        // Push return values to each array
        facilityArray.push(startInput.value);
        distanceArray.push(distance);
        timeArray.push(time);

        const getDistance = () => {

          // Create Google Maps distance service class
          const distanceService = new google.maps.DistanceMatrixService();
          // Add matrix settings
          distanceService.getDistanceMatrix({
              origins: [document.getElementById(`startaddress${[i]}`).value],
              destinations: [document.getElementById(`destaddress${[i]}`).value],
              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 = document.getElementById(`distance${[i]}`).innerHTML = `${response.rows[0].elements[0].distance.text}`;
                timeResult = document.getElementById(`time${[i]}`).innerHTML = `${response.rows[0].elements[0].duration.text}`;
                // Convert distanceResult to an integer and push to distanceArray
                intDistanceResult = parseInt(distanceResult.replace(/,/g, ''));
                // Push values to the sort array
                sortArray.push(intDistanceResult);
                sortArray.sort((a, b) => a - b);
              }
            });
        }
        getDistance();
      }
      // This is giving me the top three values, I need to find a way to return this in the html above
      slicedArray = (sortArray.slice(0, 3));
      console.log(slicedArray);
    }
  };
  // Send body request object to Quick Base via RESTful API
  quickBaseQuery = () => {
    xmlHttp.send(JSON.stringify(body));
  }
  // Combine search results from arrays into a new object
  resultsObject = Object.assign({}, [facilityArray, distanceArray, timeArray]);
  console.log(resultsObject);
}


form.addEventListener("submit", (e) => {
  e.preventDefault();
  const patientAddressInput = document.querySelector("#patientaddress");
  inputVal.push(patientAddressInput.value);
  // Call The generateHTML function only after the user enters a value
  generateHTML();
  quickBaseQuery();
  // let disableButton = document.querySelector("#submit");
  // submit.disabled = true;
  let resetButton = document.querySelector("#refresh");
  resetButton.addEventListener("click", () => location.reload());
});
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" integrity="sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="style.css">

<body>
  <div class="container-sm mt-3">
    <form class="form mb-3" id="form">
      <div class="card">
        <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=[API_KEY]"></script>
  <script type="text/javascript" src="main.js"></script>
</body>

I have the facility, distance and time arrays being populated inside of the facility object, but only the facility data is coming through the first time the form is submitted. I created a resultsObject that I'm passing the values of facilityArray, distanceArray and timeArray. On the first submit, I have all 15 values of addresses because my

quickBaseQuery = () => {
    xmlHttp.send(JSON.stringify(body));
}

function is sending the http request headers so all of that data is coming in as soon as the form is submitted. It seems that this is all being triggered at the same time (facility, time and distance being populated) but it apparently is not. I want to return the header results along with distance and time at the same time, in order to return distance results that are only in a certain range.

Here's my console log:

console.log of object

You'll see that the values of the facility in array[0] inside the object (in the console) are being returned on the first submit, but the first 15 results of distance and time are empty, until the second submit is made. So now I have an object with an array[0] of 30 addresses, 15 which are duplicates, array[1] with 15 empty distances and 15 populated distances, then array[2] with 15 empty drive times and 15 populated drive times. How do I get all 15 results for each array to return at the same time? I ultimately want to sort the results in order to return the 3 closest locations in my html, not all 15 results.

vrintle
  • 5,501
  • 2
  • 16
  • 46
JackJack
  • 181
  • 1
  • 1
  • 20
  • Where is your HTML ? – Always Helping Aug 19 '20 at 12:26
  • A snippet would help – Alexandre Elshobokshy Aug 19 '20 at 12:27
  • My bad. Just added the HTML. – JackJack Aug 19 '20 at 12:33
  • 1
    Either create a snippet that reproduces your exact problem, or share only relevant code. To create a snippet, click on edit, and then click on the `<>` button, and add your code there. This snippet should reproduce your problem. – Alexandre Elshobokshy Aug 21 '20 at 13:33
  • 2
    it sounds like an async issue. looking at your code - the xmlHttp request API is a bit outdated - the fetch api is much easier to work with overall, and it's easier syntax to do await/async. if it's grabbing blanks, is that it is grabbing the value of the field BEFORE it has a value... this is likely happening around the eventListener calling and then moving on before waiting for the response... code pen gives me errors though with your code - saying "Google Maps JavaScript API error: InvalidKeyMapError" so I can't directly test. – altruios Aug 21 '20 at 18:40
  • altruios, you were absolutely right. I needed to implement this with fetch and use async/await. – JackJack Aug 23 '20 at 23:52

1 Answers1

2

Your

resultsObject = Object.assign({}, [facilityArray, distanceArray, timeArray]);
console.log(resultsObject);

actually fires before

xmlHttp.onreadystatechange

Even though its located lower.

That's why the second time you have those values present. Your Object.assign basically assigned empty arrays, and then your onreadystatechange fires.

You need to move your Object.assign into onreadystatechange, use Promise, async / await or fetch API because you support basically the same browsers.

ColdHands
  • 947
  • 8
  • 28