The Code

Click the headers below to hide/show the corresponding code excerpts.

const events = [
  {
    event: "ComicCon",
    city: "New York",
    state: "New York",
    attendance: 240000,
    date: "06/01/2017",
  },
  {
    event: "ComicCon",
    city: "New York",
    state: "New York",
    attendance: 250000,
    date: "06/01/2018",
  },
  {
    event: "ComicCon",
    city: "New York",
    state: "New York",
    attendance: 257000,
    date: "06/01/2019",
  },
  {
    event: "ComicCon",
    city: "San Diego",
    state: "California",
    attendance: 130000,
    date: "06/01/2017",
  },
  {
    event: "ComicCon",
    city: "San Diego",
    state: "California",
    attendance: 140000,
    date: "06/01/2018",
  },
  {
    event: "ComicCon",
    city: "San Diego",
    state: "California",
    attendance: 150000,
    date: "06/01/2019",
  },
  {
    event: "HeroesCon",
    city: "Charlotte",
    state: "North Carolina",
    attendance: 40000,
    date: "06/01/2017",
  },
  {
    event: "HeroesCon",
    city: "Charlotte",
    state: "North Carolina",
    attendance: 45000,
    date: "06/01/2018",
  },
  {
    event: "HeroesCon",
    city: "Charlotte",
    state: "North Carolina",
    attendance: 50000,
    date: "06/01/2019",
  },
];
JavaScript

This first section is where global constants and variables are declared and/or initialized. Only one constant is needed in this app, and it's an array of objects representing the default entries in the table of events. These events will be the ones present in the table the first time a device accesses the app, and they will be the only ones present when the device's local storage is reset.

function buildDropdown() {

  // get all the events that we know about
  let currentEvents = getEvents();

  // get a list of unique city names from those events
  let eventCities = currentEvents.map(event => event.city);
  let uniqueCities = new Set(eventCities);
  let dropdownChoices = ['All', ...uniqueCities];

  const dropdownTemplate = document.getElementById('dropdown-item-template');
  const dropdownMenu = document.getElementById('city-dropdown');
  dropdownMenu.innerHTML = '';

  // for each of those city names:
  for (let i = 0; i < dropdownChoices.length; i++) {
    let cityName = dropdownChoices[i];

    // - make a dropdown item HTML element
    let dropdownItem = dropdownTemplate.content.cloneNode(true);

    dropdownItem.querySelector('a').innerText = cityName;

    // - add that element to the dropdown menu
    dropdownMenu.appendChild(dropdownItem);
  }

  // to be continued...?
  displayEvents(currentEvents);
  displayStats(currentEvents);
  document.getElementById('stats-location').innerHTML = 'All';
}
JavaScript

This function is called when the app is loaded, and every time the user updates the table of events (by adding rows).

It populates the dropdown used to filter by city, with all the unique city names present in the recorded events. It does so by constructing a Set from an array of each event's city name, mapped from the array of the recorded event objects. Then the dropdown is cleared of its contents, and refilled with options made from the names in the Set. Lastly, the event table and summary section are refreshed to reflect all recorded events.

function getEvents() {
  //TODO: get events from local storage
  let eventsJson = localStorage.getItem('jele-events');

  let storedEvents = events;

  if (eventsJson == null) {
    saveEvents(events);
  } else {
    storedEvents = JSON.parse(eventsJson);
  }

  return storedEvents;
}
JavaScript

This function returns all of the events recorded in local storage. It is used when building the dropdown, filtering the table, and/or saving a new event.

If the app has no data recorded in local storage, the array of default event objects is recorded and then returned. Otherwise, the array of recorded events is pulled from local storage and returned.

The default array is converted into a string in JavaScript Object Notation (JSON) format before storage. When pulled, the recorded JSON data is parsed and converted back into an array of event objects.

function saveEvents(events) {
  let eventsJson = JSON.stringify(events);
  localStorage.setItem('jele-events', eventsJson);
}
JavaScript

This function is used to save an array of event objects in local storage. It is called in two situations: first, when the user creates a new event to add to the collection of recorded ones; and second, to save the default events to local storage after an attempt to pull from local storage produces no data.

The array is converted into a string in JavaScript Object Notation (JSON) format before being saved into local storage. When saved, this string will replace the preexisting one (if present).

function displayEvents(events) {

  // get the table to put the events in
  const eventTable = document.getElementById('events-table');

  // clear the table
  eventTable.innerHTML = '';

  // loop through events
  for (let i = 0; i < events.length; i++) {
    let event = events[i];

    // loop through events
    // - fill the table with rows
    //      - make a <tr></tr>
    let eventRow = document.createElement('tr');

    //      - make a <td> for each property
    //      - put the data into each <td>
    let eventName = document.createElement('td');
    eventName.innerText = event.event;
    eventRow.appendChild(eventName);

    let eventCity = document.createElement('td');
    eventCity.innerText = event.city;
    eventRow.appendChild(eventCity);

    let eventState = document.createElement('td');
    eventState.innerText = event.state;
    eventRow.appendChild(eventState);

    let eventAttendance = document.createElement('td');
    eventAttendance.innerText = event.attendance.toLocaleString();
    eventRow.appendChild(eventAttendance);

    let eventDate = document.createElement('td');
    let date = new Date(event.date);
    eventDate.innerText = date.toLocaleDateString();
    eventRow.appendChild(eventDate);

    //      - append the row to the <tbody>
    eventTable.appendChild(eventRow);
  }
}
JavaScript

This function is used to populate the events table with a passed-in array of event objects. It is called when building the city-filtering dropdown, and also when filtering the table by city.

The function starts with emptying the table of all its contents. Then it iterates through the elements of the passed-in array, creates a new row element for each one, and inserts them into the events table.

function calculateStats(events) {
  let stats = {
    sum: 0,
    avg: 0,
    max: 0,
    min: 0
  };

  if (events.length > 0) {
    stats.min = events[0].attendance;

    for (let i = 0; i < events.length; i++) {
      stats.sum += events[i].attendance;
      if (events[i].attendance > stats.max) stats.max = events[i].attendance;
      else if (events[i].attendance < stats.min) stats.min = events[i].attendance;
    }

    stats.avg = stats.sum / events.length;
  }

  return stats;
}
JavaScript

This function is used to calculate four stats - total, average, maximum, and minimum attendance counts - for a passed-in array of event objects. It is called when updating the stats summary section of the app based on this array.

All four stats are contained in a single object that gets returned in the end. To calculate these stats, the array is iterated through once, and each stat is updated at every point in the loop. The one exception is the average, which is calculated *after* iterating through the array; this minimizes the number of divisions needed.

function displayStats(events) {
  let stats = calculateStats(events);
  document.getElementById('total-attendance').innerHTML = stats.sum.toLocaleString();
  document.getElementById('avg-attendance').innerHTML = Math.round(stats.avg).toLocaleString();
  document.getElementById('max-attended').innerHTML = stats.max.toLocaleString();
  document.getElementById('min-attended').innerHTML = stats.min.toLocaleString();
}
JavaScript

This function is used update the stats summary section according to a passed-in array of event objects. It is called when building the city-filtering dropdown, and when filtering the event table by city.

When called, this function first calls calculateStats to calculate the total, average, minimum, and maximum attendance counts for the passed-in array. Then these values are inserted into their respective parts of the summary section.

function filterByCity(element) {
  // figure out which city we want
  let cityName = element.textContent;

  document.getElementById('stats-location').innerHTML = cityName;

  // get all the events
  let allEvents = getEvents();

  // filter those events to just one city

  let filteredEvents = cityName == 'All' ?
    allEvents : allEvents.filter(e => e.city == cityName);

  // call displayStats with the events for that city
  displayStats(filteredEvents);

  // call displayEvents with the events for that city
  displayEvents(filteredEvents);
}
JavaScript

This function is used to filter the events table by city. It is called any time the user selects a new option in the city-filtering dropdown.

When called, this function retrieves the array of recorded event objects. From that array, a new one is created via the filter array method. This new array contains only the event objects whose city name matches the passed-in value. The events table and stats summary section are then updated according to this new array.

function saveNewEvent() {
  // get the HTML Form element
  let newEventForm = document.getElementById('newEventForm');

  // create an object from the <input>s
  let formData = new FormData(newEventForm);
  let newEvent = Object.fromEntries(formData.entries());

  // fix the formats of the data
  newEvent.attendance = parseInt(newEvent.attendance);
  newEvent.date = new Date(newEvent.date).toLocaleDateString();

  // get all current events
  let allEvents = getEvents();
  // add our new event
  allEvents.push(newEvent);
  // save all events with the new event
  saveEvents(allEvents);

  // reset the form inputs
  newEventForm.reset();

  // hide the bootstrap modal
  let modalElement = document.getElementById('newEventModal');
  let bsModal = bootstrap.Modal.getInstance(modalElement);
  bsModal.hide();

  // display all events again
  buildDropdown();
}
JavaScript

This function is used to save a new user-inputted event to local storage with all the other recorded events. It is called any time the user appropriately fills the form on the "Add A New Event" modal and clicks the Save button.

The form entries are used to create a new event object. Then the array of existing recorded event objects is retrieved, and this new object is added onto the end of it. This new array is then saved to local storage. Finally, the modal is cleared and hidden, and - via buildDropdown - updates are made to the city-filtering dropdown, events table, and stats summary section to include the newly-added event.