0

From a fetched array of flight-data items I was able to created a table with each row displaying its specific flight-data alongside a button for booking the related flight.

I want to add each button into an array so that I can use the array in order to 1) access the currently booked flight flight and 2) add/display it to/at a cart.

The problem I have is that the array bookedFlights never gets appended so I can't access the button that's been clicked to use later.

How could one access the specific flight-data which relates to the button that has been clicked?

const fl = fetch("https://mocki.io/v1/fa695c4b-475e-4dfc-8608-952c0c5a28b5");

fl.then((response) => {
  return response.json();
}).then((result) => {
  const fakeFlights = result.flights;
  const table = document.createElement("table");
  const thead1 = document.createElement("thead");
  const thead2 = document.createElement("thead");
  const tbody = document.createElement("tbody");
  const bookingButtons = [];
  let row1 = document.createElement("tr");
  let row2 = document.createElement("tr");
  let headingTH = document.createElement("th");
  headingTH.innerText = "Available Flights";
  headingTH.colSpan = 7;
  row1.appendChild(headingTH);
  thead1.appendChild(row1);
  for (key in fakeFlights[0]) {
    let heading = document.createElement("th");
    if (key === "flightNumber") {
      key = "Flight Number"
    }
    if (key === "departureTime") {
      key = "Departure Time"
    }
    if (key === "arrivalTime") {
      key = "Arrival Time"
    }
    heading.innerText = key;
    row2.appendChild(heading);
  }
  let heading = document.createElement("th");
  heading.innerText = "Book the flight";
  row2.appendChild(heading);
  thead2.appendChild(row2);
  for (let i = 0; i < fakeFlights.length; i++) {
    let row = document.createElement("tr");
    for (key in fakeFlights[i]) {
      let rowData = document.createElement("td");
      rowData.innerHTML = fakeFlights[i][key];
      row.appendChild(rowData);
    }
    let btn = document.createElement("button");
    btn.innerText = "Book";
    btn.id = i;
    bookingButtons.push(btn);
    row.appendChild(btn);
    tbody.appendChild(row);
  }
  table.append(thead1, thead2, tbody);
  document.body.appendChild(table);
  let bookedFlights = [];
  for (let selectedButton of bookingButtons) {
    selectedButton.addEventListener("click", function() {
      bookedFlights.push(selectedButton.id);
    });
  }
  const data = {
    flights: fakeFlights,
    bookedFlights: bookedFlights
  }
  return data;
}).then((data) => {
  console.log(data.flights[data.bookedFlights[0]]);
}).catch(() => {
  document.write("error")
});
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37
  • 2
    I'd recommend using [Event Delegation](https://stackoverflow.com/questions/1687296/what-is-dom-event-delegation) instead of adding 41 seperate event listeners. – Reyno Jan 03 '23 at 13:33
  • 1
    Take a look at the way you're pushing data into `bookedFlights`. It only occurs if you click on a button. Should it work like that? – FiddlingAway Jan 03 '23 at 13:33
  • 1
    Welcome to SO. The OP might consider reading [_"How do I ask a good question?"_](https://stackoverflow.com/help/how-to-ask) especially considering the advices of [_"How to create a Minimal, Reproducible Example"_](https://stackoverflow.com/help/minimal-reproducible-example). Thus the OP should come up with an array of maybe just 3 flight-data items which the OP together with the rest of the code could provide as executable [Stack snippet](https://meta.stackoverflow.com/questions/358992/ive-been-told-to-create-a-runnable-example-with-stack-snippets-how-do-i-do) code. – Peter Seliger Jan 03 '23 at 13:44
  • **Other suggestions besides** the already mentioned [**event delegation**](https://davidwalsh.name/event-delegate) ... The OP also might consider separating the code into specialized task, hence just **a single properly named [function statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** for each, the fetching, the rendering and the event handling task. Thus one avoids nested and therefore more difficult to read / debug / maintain anonymous function expressions provided within promise and event handling methods. – Peter Seliger Jan 03 '23 at 13:58
  • @LearningCode ... It was nice of the OP if the OP engages with any kind of feedback. – Peter Seliger Jan 10 '23 at 21:19
  • @LearningCode ... Regarding the so far provided sole answer are there any questions left? – Peter Seliger Jan 23 '23 at 16:23

1 Answers1

0

The two main improvement patterns / techniques are ...

  • Event Delegation which for the OP's provided environment means, one has to register a single event listener exactly once, either at the table related element node, or even better, at the table body's related element node since the latter is the common single parent node of all flight-related table-data.

  • Separation of concerns is another one. In its most basic implementation, targeting the OP's originally provided code, this means just a single properly named function statement for each ...

    • the fetching (fetchAndDisplayFlightData ),
    • the rendering (createFlightOverview ),
    • the event handling (handleBookingOfSelectedFlight...),

    ... task.

The final implementation features following techniques / methods ...

  • fetchAndDisplayFlightData expects and URL where it does fetch the flight-data from. Within this function one creates the flight-data overview, here an html table element, by passing the response-data's flights-array to createFlightOverview.

  • The latter function does process this list of flights (listOfFlights) mostly via the code originally provided by the OP. There are some improvements though, like ...

    • ... using an object based headerContentLookup in order to access the correct table header content via each of a data-item's keys.

    • ... making use, for each rendered data-item's "Booking"-button, of the global data-* attribute data-flight-number and its related dataset property flightNumber since the latter is exactly what identifies a flight (or flight-data item).

  • As already mentioned, there is exactly one 'click'-handler going to be registered at the table-element's table-body element. The very handler function is implemented this-context aware, thus it expects a Map-instance to be bound. And the latter data gets created by a helper function (createFlightNumberBasedFlightDataMap) which does map the response-data's flights-array according by each flight-data item's flightNumber-value that is used as a flight-item's identifier-key.

    • The registered 'click'-handler which got ad-hoc created via bind implements the functionality of handleBookingOfSelectedFlightFromBoundFlightDataMap and thus expects a click-event and such an event's target. In case its closest element matches a button-element one can proceed with extracting the flightNumber from the very element's dataset property. The flight-number value then will be used for accessing the related flight-item from the lookup/map via get.

    • Within a final step one could pass the selected flight-data to yet another function like e.g. ... bookSelectedFlight(selectedFlightData).

// - the OP's own originally provided (most of it) table creation code.
function createFlightOverview(listOfFlights) {
  const headerContentLookup = {
    departureTime: 'Departure Time',
    arrivalTime: 'Arrival Time',
    flightNumber: 'Flight Number',
    destination: 'Destination',
    origin: 'Origin',
    price: 'Price',
  };
  const table = document.createElement("table");
  const thead1 = document.createElement("thead");
  const thead2 = document.createElement("thead");
  const tbody = document.createElement("tbody");

  // - not needed.
  // const bookingButtons = [];

  const row1 = document.createElement("tr");
  const row2 = document.createElement("tr");
  const headingTH = document.createElement("th");

  headingTH.textContent = "Available Flights";
  headingTH.colSpan = 7;

  row1.appendChild(headingTH);
  thead1.appendChild(row1);

  Object
    .keys(listOfFlights[0])
    .forEach(key => {
      const heading = document.createElement("th");
      heading.textContent = headerContentLookup[key] ?? key;
      row2.appendChild(heading);
    });
  const heading = document.createElement("th");
  heading.textContent = "Book the flight";

  row2.appendChild(heading);
  thead2.appendChild(row2);

  listOfFlights
    .forEach(flightItem => {
      const row = document.createElement("tr");
      Object
        .values(flightItem)
        .forEach(value => {
          const cellData = document.createElement("td");
          cellData.textContent = value;
          row.appendChild(cellData);
        });
      const btn = document.createElement("button");
      btn.textContent = "Book";

      // - not recommended.
      // btn.id = i;

      // - for the flight identification rather use
      //   a data-item's `flightNumber` provided as
      //   global `data-*` attribute ... like  e.g.
      btn.dataset.flightNumber = flightItem.flightNumber;

      // - not needed.
      // bookingButtons.push(btn);

      row.appendChild(btn);
      tbody.appendChild(row);
    });
  table.append(thead1, thead2, tbody);

  return table;
}

// - helper task which creates a `Map` instance from an array of flight
//   related data-items, based on each item's `flightNumber` value. 
function createFlightNumberBasedFlightDataMap(listOfFlights) {
  return new Map(
    listOfFlights
      .map(flightItem => [flightItem.flightNumber, flightItem])
  )
}

// - the final task which actually is going to trigger the
//   booking-process for the before selected flight-item.
function bookSelectedFlight(flightData) {
  console.log({ selectedFlightData: flightData });
}
// - the handler function which is aware of a bound `this` context
//   where `this` refers to the one time created (and bound)
//   flight number based lookup.
function handleBookingOfSelectedFlightFromBoundFlightDataMap({ target }) {
  const flightDataMap = this;
  const elmButton = target.closest('button');

  if (elmButton) {
    const { flightNumber } = elmButton.dataset ?? null;
    const selectedFlightData = flightDataMap.get(flightNumber);

    console.log({ elmButton, flightNumber/*, selectedFlightData*/ });

    bookSelectedFlight(selectedFlightData);
  }
}

// - the OP's provided example code changed according to the suggestions.
function fetchAndDisplayFlightData(url) {
  return fetch(url)
    .then(response => response.json())
    .then(flightData => {

      const listOfFlights = flightData.flights;
      const flightOverview = createFlightOverview(listOfFlights);

      // - register the button specific click-handling at the table's body.
      flightOverview
        .querySelector('tbody')
        .addEventListener(
          'click',
          // - create a handler function which has a flight number based
          //   `Map` instance as lookup table bound to its `this` context.
          handleBookingOfSelectedFlightFromBoundFlightDataMap
            .bind(
              createFlightNumberBasedFlightDataMap(listOfFlights)
            )
        );
      document
        .body
        .appendChild(flightOverview);
    })
    .catch(reason => 
      document.body.appendChild(document.createTextNode(String(reason)))
    );
}

fetchAndDisplayFlightData(
  'https://mocki.io/v1/fa695c4b-475e-4dfc-8608-952c0c5a28b5'
);
body { margin: 0 0 100px 0; zoom: .9; }
.as-console-wrapper { max-height: 100px!important; }
Peter Seliger
  • 11,747
  • 3
  • 28
  • 37