In this tutorial, we are going to build an Event Booking App with HTML, CSS, JavaScript, and Firebase.
Sorry for the interrupt!
If you're interested in learning JavaScript in a comprehensive way, I highly recommend this course: JavaScript - The Complete Guide 2020 (Beginner + Advanced)
It's an affiliate link, so by purchasing, you support the blog at the same time.Plan our app
We are not going to build a complete event booking app with all the functionalities. It's not relevant to cover everything in just one tutorial, I just want to keep things simple and easy to digest, maybe the authentication part will be covered in a separate article.
So, our Event Booking App will have the following functionalities:
- The user can create an event and store it to Firestore.
- The user can fetch all events in real-time.
- The user can book an event.
- The user can't book an event more than once.
Now, we know what our app will look like, let's start building it in the next section.
Markup
Our HTML file will be relatively simple. We will hold our navigation bar and the latest event in the header
tag.
- In
index.html
<body>
<header id="home">
<nav class="navbar">
<h1>Eventic</h1>
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#events">Events</a></li>
<li><a href="#add-event">New Event</a></li>
</ul>
</nav>
<div class="welcome-event"></div>
</header>
<main>
<section id="events">
<h1 class="section-title">Events</h1>
<div class="events-container"></div>
</section>
<section id="add-event">
<h1 class="section-title">New Event</h1>
<form class="form">
<input type="text" id="name" placeholder="Name" />
<input type="number" id="attendee" placeholder="Attendees" />
<textarea
id="description"
cols="30"
rows="10"
placeholder="Description..."
></textarea>
<select id="status">
<option value="0">Free</option>
<option value="1">Paid</option>
</select>
<button class="btn btn-primary">Save</button>
</form>
</section>
</main>
</body>
Next, the main
tag will wrap the list of events and the form which enables us to create a new event.
The events will be displayed later with the help of JavaScript.
- In
index.html
<script src="https://www.gstatic.com/firebasejs/7.9.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.9.1/firebase-firestore.js"></script>
<script>
// Your web app's Firebase configuration
var firebaseConfig = {
apiKey: "xxxxxxxxxxxxxxxxxxxxxxxxx",
authDomain: "xxxxxxxxxxxxxxxxxxxxxxxx",
databaseURL: "xxxxxxxxxxxxxxxxxxxxxxxxx",
projectId: "xxxxxxxxxxxxxxxxxxxxxxxxx",
storageBucket: "xxxxxxxxxxxxxxxxxxxxxxxxx",
messagingSenderId: "xxxxxxxxxxxxxxxxxxxxxxxxx",
appId: "xxxxxxxxxxxxxxxxxxxxxxxxx"
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore()
</script>
<script src="db.js"></script>
<script src="app.js"></script>
</body>
</html>
Next, we need to connect our app to Firebase to be able to store our data.
To have these credentials, you will need to create a new app in the Firebase Console. And once the project created, click on the code icon </>
that sits next to the IOS and Android icons to register your app.
Now, to generate the credentials, you have to register the name of your app. And finally, put the credentials in the HTML file as I do here.
Next, duplicate the first script
tag and change firebase-app.js
to firebase-firestore.js
because we will use Firestore in this project.
Then, initialize firebase
with the configuration and declare a db
variable that will be used later to interact with Firebase.
Now, we have our markup and successfully linked our project to Firebase, let's start styling it in the next section.
Styling
The CSS file is a bit long, therefore I won't cover everything in this post, I will just highlight the most important parts. However, no worries, you will find the source code at the end of the article.
As usual, we start by importing our font and do some resets to prevent the default behavior.
- In
style.css
@import url("https://fonts.googleapis.com/css?family=Nunito:400,700&display=swap");
*,
*::after,
*::before {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
--primary-color: #e74c3c;
--secondary-color: #222;
--tertiary-color: #333;
--light-color: #fff;
scroll-behavior: smooth;
}
body {
background-color: #1f1f1f;
font-family: "Nunito", sans-serif;
font-size: 1rem;
color: var(--light-color);
line-height: 1.6;
}
Next, we use CSS Variables to store our colors and set the scroll-behavior
to smooth
to just have a nice scrolling effect when the user clicks in the navbar links.
However, be careful with the scroll-behavior
, it's not supported by all browsers. You can still handle the browsers compatibility here.
- In
style.css
nav {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 2.5rem;
z-index: 100;
width: 100%;
transition: background 0.3s;
position: fixed;
top: 0;
left: 0;
}
nav ul {
display: flex;
list-style: none;
}
nav li:not(:last-child),
.welcome-event div span {
margin-right: 1.5rem;
}
For the navbar, by default, the background will be transparent, and for better usability, it will change when the user starts scrolling.
Our Event Booking App starts to taking shape, now, let's start implementing Firebase and connect our app to Firestore.
Interact With Firebase
Firebase is a platform that handles everything related to the back-end for us, the only thing, we have to do is connecting our app to it and start using the database or other services.
Firestore is a NoSQL database, it's non-relational and uses documents, collections, etc. to create the database.
Now, let's connect to Firestore and create our very first database.
Fetch events
Earlier in this tutorial, we had declared a variable db
in the HTML part. Now, let's use that variable to connect our app to Firestore.
I will put everything related to the database on the db.js
file to just have a more clean structure.
- In
db.js
db.collection("events").onSnapshot(snapshot => {
// Handle the latest event
const newestEvent = snapshot.docChanges()[0].doc.data()
const id = snapshot.docChanges()[0].doc.id
showLatestEvent(newestEvent, id)
// delete the latest event element
snapshot.docChanges().shift()
snapshot.docChanges().forEach(event => {
showEvents(event.doc.data(), event.doc.id)
})
})
With the help of db
, we can now access our collection events
. It's just the name of our database, and if it doesn't exist, Firestore will create it on the fly for us.
The collection object has a very handy method called onSnapshot()
. It helps to listen in real-time to the database, which that means, whenever a change occurs on it, it will react and returns us the change in real-time.
The onSnapshot()
method will also help us to access to the document (our data). And now, we can extract the latest event to show on the header. And, before looping through the events array, delete the latest event to just not display it again.
Now, to display the events on the UI, we have to call our needed functions showLatestEvent()
and showEvents()
, then, pass to them as parameter the event and the id.
We can now fetch the events from Firestore, however, we still not have events to show. Let's store our very first event in our database.
Create an event
To get the values entered by the user, we have to first select the form
tag and use it to pick the id of each input and pull the value entered.
- In
db.js
const addNewEvent = () => {
const event = {
name: form.name.value,
attendee: form.attendee.value,
booked: 0,
description: form.description.value,
status: parseInt(form.status.value, 10),
}
db.collection("events")
.add(event)
.then(() => {
// Reset the form values
;(form.name.value = ""),
(form.attendee.value = ""),
(form.description.value = ""),
(form.status.value = "")
alert("Your event has been successfully saved")
})
.catch(err => console.log(err))
}
The db
variable (remember it's the reference to firebase.firestore()
) has another method to save data to Firestore, the save()
function. It's a promise, and once it's complete, we can now reset the values of the form and show a success message to the user.
Now, let's move on and handle the case when the user wants to book an event.
Book an event
As I said earlier, we can't check if the user is authenticated or not, hence, he can potentially book an event more than once.
So to handle it, I will use localStorage
to prevent booking duplication.
- In
db.js
let bookedEvents = []
const bookEvent = (booked, id) => {
const getBookedEvents = localStorage.getItem("booked-events")
if (getBookedEvents) {
bookedEvents = JSON.parse(localStorage.getItem("booked-events"))
if (bookedEvents.includes(id)) {
alert("Seems like you have already booked this event")
} else {
saveBooking(booked, id)
}
} else {
saveBooking(booked, id)
}
}
const saveBooking = (booked, id) => {
bookedEvents.push(id)
localStorage.setItem("booked-events", JSON.stringify(bookedEvents))
const data = { booked: booked + 1 }
db.collection("events")
.doc(id)
.update(data)
.then(() => alert("Event successfully booked"))
.catch(err => console.log(err))
}
And as you can see here, we first check if the event id is stored or not in the localStorage. If it's the case, the user can't book the same event again, otherwise, he will be able to book the event.
And to update the booking counter, we use again db
to update the event on Firestore.
The db.js
file is now complete, So, let's move to the final part and connect our project to db.js
Show and Update Data with JavaScript
As usual, we start by selecting the needed elements.
const eventsContainer = document.querySelector(".events-container")
const nav = document.querySelector("nav")
const welcomeEvent = document.querySelector(".welcome-event")
const form = document.querySelector(".form")
const showEvents = (event, id) => {
const { name, attendee, status, description, booked } = event
const eventStatus = status === 0 ? "free" : "paid"
const output = `
<div class="card">
<div class="card--details">
<div>
<h1>${name}</h1>
<span>${attendee - booked} attendees</span>
</div>
<span class="card--details-ribbon ribbon-${eventStatus}">
${eventStatus}
</span>
<p>${description}</p>
<button onclick="bookEvent(${booked} ,'${id}')" class="btn btn-tertiary">Book</button>
</div>
</div>
`
eventsContainer.innerHTML += output
}
Earlier in this article, we had passed as a parameter to the showEvents()
function the event fetched from Firestore in the db.js
file.
We can now pull the values held on the event object, and display it. And, when the user clicks on the button to book an event, we will call the bookEvent()
function to handle it.
const showLatestEvent = (latestEvent, id) => {
const { name, attendee, status, description, booked } = latestEvent
// Get the first event
welcomeEvent.innerHTML = `
<h1>${name}</h1>
<p>${
description.length >= 100
? `${description.substring(0, 100)}...`
: description
}</p>
<div>
<span>Attendees: ${attendee - booked}</span>
<span>Status: ${status === 0 ? "free" : "paid"}</span>
</div>
<button onclick="bookEvent(${booked} ,'${id}')" class="btn btn-tertiary">Book</button>
`
}
form.addEventListener("submit", e => {
e.preventDefault()
addNewEvent()
})
window.onscroll = () => {
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
nav.style.background = "var(--tertiary-color)"
nav.style.boxShadow = "0 10px 42px rgba(25,17,34,.1)"
} else {
nav.style.background = "none"
nav.style.boxShadow = "none"
}
}
As you can see, the showLatestEvent()
method is quite similar to showEvents()
, unlike the element used to display the event.
And, when the description is a bit long, we use substring()
to truncate the value.
Next, we listen to the form
element to handle the submit event and store it to Firestore with addNewEvent()
.
And to make everything looking nice, when the user scrolls, we add a background color and a box-shadow to the navigation bar.
With that change, we have now our Event booking App using JavaScript and Firebase.
That being said, Thanks for reading this article.
You can check it live here or find the Source Code here.