The Code

						
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",
	},
];

const getEvents = () => {
	const eventsJson = localStorage.getItem("goScoutEvents");
	let storedEvents = events;
	if (!eventsJson) {
		saveEventsToLocalStorage(events);
	} else {
		storedEvents = JSON.parse(eventsJson);
	}
	return storedEvents;
}

const saveEventsToLocalStorage = events => {
	const eventsJson = JSON.stringify(events);
	localStorage.setItem("goScoutEvents", eventsJson);
}

const saveNewEvent = () => {
	const newEventForm = document.getElementById('EventForm');
	const formData = new FormData(newEventForm);
	const newEvent = Object.fromEntries(formData.entries());
	newEvent.attendance = parseInt(newEvent.attendance);
	newEvent.date = new Date(newEvent.date).toLocaleDateString();
	const allEvents = getEvents();
	allEvents.push(newEvent);
	saveEventsToLocalStorage(allEvents);
	clearForm(newEventForm);
	closeModal();
	createTableData();
	const selectedDropdownOption = document.getElementById('statsLocation');
	filterEventsByCity(selectedDropdownOption.innerText);
	showConfirmation(newEvent.event);
}

const showConfirmation = (eventName) => {
	Swal.fire({
		backdrop: false,
		title: 'Success!',
		text: `The ${eventName} event was saved!`,
		icon: 'success',
		confirmButtonColor: '#253439'
	})
}

const closeModal = () => {
	const modalElement = document.getElementById('formModal');
	const bsModal = bootstrap.Modal.getInstance(modalElement);
	bsModal.hide();
}

const clearForm = (form) => {
	form.reset();
}

const createTableData = () => {
	const currentEvents = getEvents();
	const cityNames = currentEvents.map(event => event.city);
	const uniqueCities = new Set(cityNames);
	const dropDownChoices = ['All', ...uniqueCities];
	createDropdown(dropDownChoices);
	displayEvents(currentEvents);
	const stats = calculateStats(currentEvents)
	displayStats(stats);
}

const createDropdown = (choices) => {
	const dropdownTemplate = document.querySelector('#dropdownItemTemplate');
	const cityDropdown = document.querySelector('#cityDropdown');
	cityDropdown.innerHTML = '';
	for (let i = 0; i < choices.length; i++) {
		const dropdownItem = dropdownTemplate.content.cloneNode(true);
		const cityName = choices[i];
		dropdownItem.querySelector('a').innerText = cityName;
		cityDropdown.appendChild(dropdownItem);
	}
}

const displayEvents = (events) => {
	const eventsTable = document.getElementById('eventsTable');
	eventsTable.innerHTML = '';

	for (let i = 0; i < events.length; i++) {
		const event = events[i];
		const eventRow = document.createElement('tr');
		const eventName = document.createElement('td');
		const eventCity = document.createElement('td');
		const eventState = document.createElement('td');
		const eventAttendance = document.createElement('td');
		const eventDate = document.createElement('td');
		eventName.innerText = event.event;
		eventCity.innerText = event.city;
		eventState.innerText = event.state;
		eventAttendance.innerText = event.attendance.toLocaleString();
		const dateObj = new Date(event.date);
		eventDate.innerText = dateObj.toLocaleDateString('en-US', {dateStyle: 'medium'});
		eventRow.appendChild(eventName);
		eventRow.appendChild(eventCity);
		eventRow.appendChild(eventState);
		eventRow.appendChild(eventAttendance);
		eventRow.appendChild(eventDate);
		eventsTable.appendChild(eventRow);
	}
}

const calculateStats = events => {
	let sum = 0;
	let min = Infinity;
	let max = -Infinity;
	for (let i = 0; i < events.length; i++) {
		const attendance = events[i].attendance;
		sum += attendance;
		if (min > attendance) {
			min = attendance
		}

		if (max < attendance) {
			max = attendance;
		}
	}
	let average = Math.floor(sum/events.length);
	
	const stats = {
		sum,
		average,
		min,
		max
	}
	return stats;
}

const displayStats = values => {
	const totalAttendanceElement = document.getElementById('totalAttendance');
	totalAttendanceElement.innerText = values.sum.toLocaleString();
	const avgAttendanceElement = document.getElementById('averageAttendance');
	avgAttendanceElement.innerText = values.average.toLocaleString();
	const minAttendanceElement = document.getElementById('leastAttendance');
	minAttendanceElement.innerText = values.min.toLocaleString();
	const maxAttendanceElement = document.getElementById('mostAttendance');
	maxAttendanceElement.innerText = values.max.toLocaleString();
}

const filterEventsByCity = city => {
	const allEvents = getEvents();
	const statsTextCity = document.getElementById('statsLocation');
	const dropDownBtn = document.getElementById('dropdownButton');
	statsTextCity.innerText = city;
	let filteredEvents = [];
	if (city === "All") {
		filteredEvents = allEvents;
		dropDownBtn.innerText = 'Pick a Location';
	} else {
		dropDownBtn.innerText = city;
		filteredEvents = allEvents.filter(event => event.city === city);
	}
	displayStats(calculateStats(filteredEvents));
	displayEvents(filteredEvents);
}
						
					

Abstract

Here's a chronological high level breakdown of the steps I followed:

  • Upon window-load, check if the events we care about exist in the browser's local storage.
  • If the local storage events exist, use those. Otherwise, save and use a hardcoded events array(line 1).
  • Extract unique cities from these events and create a dropdown menu allowing users to select one of the cities.
  • Using the events array, generate a table displaying all the events in the user-selected city.
  • Calculate some stats from the events of interest and display those in a stats table as well.
  • If a user chooses a different city in the dropdown, the events table and the data table should reflect updated data.
  • If a user clicks on the Add Event button, display a form in a modal where they can enter a new event.
  • Upon form submission, clear the form, close the modal, update the events data, save that data to local storage, refresh page data and display a form submission confirmation.

Starter Kit

Before talking about the entry function, I'd like to talk through the helper functions because of the integral part they play:

  • getEvents(): (line 67) It checks for the goScoutEvents item in the local storage. If it doesn't find one, it saves and returns the hardcoded events array. This saving is helpful for the next session. If it does find one, it parses the JSON to an array and returns the same.
  • createDropDown(choices): (line 131) This function, clears the city-dropdown html first. It then loops through the choices array of city names and generates li template clone for each choice. It appends these li elements to the parent element.
  • displayEvents(events): (line 143) This function clears the html of the events table. It loops through the events. For each event, it generates a <tr> tag. It also generates <td> tags depicting each important data point. The function first populates these <td> tags with relevant properties of the event and then appends the <td> tags to the table row. But up until now, the row representing the event only exists in javascript. We finally append the row (which has the <td> tags) to the table element for the row to actually get displayed.
  • calculateStats(events): (line 170) This function initializes sum, min, max values for event attendances and loops through the events array to update those values after each iteration. Once it's calculated the sum, it also calculates the average. Finally, it returns the four values in an object.
  • displayStats(events): (line 196) This function gets the <td> elements representing stats and displays the sum, min, max and avg from the values object it uses as a parameter.

Page Load

The entry function of the app is createTableData() which is triggered by window load and orchestrates the invocations of the functions above. This function dynamically creates all the data the page needs on load - including the events table, the dropdown menu and the stats table.

Choosing a city from the dropdown

The dropdown of city options are created with a template tag. Each template tag consists of an event listener which calls the function filterEventsByCity with the argument of this.innerText. The this.innerText here would always represent the clicked option's text and the function can take it up from there.

The function filterEventsByCity uses a parameter of city representing the city name. It replaces the text in the dropdown button. If the option selected is All, the function refreshes the page data referencing allEvents. If the option selected is a city, the function filters the array and refreshes the page data referencing just the events from the selected city.

Saving a New Event

When a user submits the form for saving a new event, it triggers the function saveNewEvent() (line 83).

This function gets the form entry values and assigns them to the newEvent variable.

  • It stores the latest version of events in the allEvents variable and pushes the newElement in the allEvents array.
  • It saves this array to local storage.
  • It clears the form and closes the modal by calling some helper functions.
  • It then invokes the entry function createTableData again to create the page's initial data.
  • Because the page displays "all" events by default, the function calls the filterEventsByCity function to retain the view the user was on.
  • Finally, the function displays a success alert message confirming the event being saved.