My flip gallery journey
SimoneVeitch
Posted on April 15, 2024
Hello readers,
I'm Simone, currently enrolled in the Academi Xi Front-End Web Development: Transform course. I'm in the midst of Phase 1, which focuses on JavaScript.
During this phase, I've gained a solid understanding of fundamental JavaScript concepts. I've learned how to manipulate the DOM, handle JavaScript events, and interact with the server using json-server.
At the end of this phase, all students, including myself, are tasked with creating a web application that demonstrates our grasp of the concepts covered so far. Additionally, we're required to write a blog post focusing on a technical aspect of front-end web development. This post is my attempt to fulfill that requirement.
In this post, I'll walk you through the development of the key feature of my web application. This feature integrates all the elements of my learning journey thus far, including HTML, CSS, DOM manipulation, JavaScript events, and server communication.
But before we delve into that, let's start from the beginning of my web application journey.
It’s a data thing
For my Phase 1 web application project, I had to build a frontend using HTML, CSS, and JavaScript that interacts with data
Before diving into the actual coding and visual design of my app, I had to work out what kind of data I was going to use.
Throughout Phase 1, we primarily used animal-related data, particularly relating to cats or dogs. Given this familiarity, I decided to start with this type of data for my project.
While exploring public APIs, I found that many of them provided only limited information, such as images or a single attribute like breed or name. This posed a challenge, as our project required us to use an API that returned a collection of at least five objects, each with at least three attributes. To address this, I opted to create my own db.json file to serve as the data source for my project.
Now that I knew where I would get my data from, I had to decide what kind of data I wanted to use and with that, what kind of web application I wanted to build.
Furry friends
Continuing with the pet theme, I chose to focus on dogs for my project since I lean more towards being a dog person than a cat person.
Additionally, working with pet data was a familiar choice, as mentioned earlier.
I knew I had to display data in an interesting way, use at least three distinct event listeners that enable interactivity and implement at least one instance of array iterations.
What would be a good way to do this…. A dog competition site of course! I called the web application, and the competition, Furry Fave 2024. In the competition fictional judges and users decide who should win Furry Fave 2024, i.e. who is the cutest, and most likeable dog.
With only eight weeks of JavaScript learning under my belt the app is fairly simple. On the site users can scroll through a gallery of the dog’s that are in the competition to be Furry Fave 2024, they can like their favourite dog, they can see who the leading puppy is (the dog with the most likes to date), and they can submit a dog to the competition.
There are a lot of elements to the app, but for today’s post I will focus on the key feature of the app; the contestants gallery.
Back to the future data
I wanted to display dog data in a visually interesting way that would encourage interactivity, while meeting the project requirements. I thought a gallery would be a good way to do this.
Going back to data, before delving into HTML, CSS or JavaScript I needed to decide what data I wanted to display on my app and create my db.json file.
To ensure the gallery was visually appealing, informative, and met the requirement of returning objects with at least three attributes, I decided that each dog should have the following attributes: id, image, name, breed and description. To then encourage interactivity I also added a like attribute as I knew I wanted a like button for each dog. In the data set the like attribute shows the total likes to date, a number I initially add, but which will increase dynamically.. I will get back to that.
In the end, the data structure for each dog in my db.json file looked like this
{
"dogs": [
{
"id": "1",
"image": "https://www.purina.co.uk/sites/default/files/styles/square_medium_440x440/public/2022-07/Spaniel%20%28American%20Cocker%291.jpg",
"name": "Vanilla",
"breed": "Cocker Spaniel",
"description": "Vanilla is a merry and busy little dog. Profuse and glamorous, she is an intelligent dog who loves her family",
"likes": 13
}
]
}
To ensure that the data I used would display on my published site, once I had added enough starting data in my db.json file I deployed my json-server on render.com. That deservers a separate blog post, but I won't go much further in to that today.
Javascripting away
With my data in place it was time to roll up my sleeves and dig into some JavaScript code.
I had the structure and hierarchy in place for my app, so the first thing I had to do was create a place for the gallery in my HTML file within that hierarchy.
In my HTML file I created a cards div container to hold the card elements that would be dynamically created using JavaScript. I gave the div a class to be used for CSS purposes later, and placed this div within the main contestants section on the site. As you can see there is no content within the div, that is because the content will be dynamically generated through JavaScript in interaction with the server.
<section id="contestants">
<h2><span class='bark'>CONTESTANTS</span></h2>
<div class="heart">
<img src="./Images/noun-footprint-3974617.png" alt="dog heart paw"/>
<img src="./Images/noun-footprint-3974617.png" alt="dog heart paw"/>
</div>
<div id="cards" class="card-gallery">
</div>
</section>
Next, I moved on to my index.js file, where I added a function to render each dog card to be displayed in the gallery. I called this function renderOneDog.
function renderOneDog(dog) {
const card = document.createElement('div');
card.className = 'card';
card.innerHTML = `
<div class="card-inner">
<div class="card-front">
<img src="${dog.image}" class="dog-image" />
</div>
<div class="card-back">
<h3>${dog.name} the ${dog.breed}</h3>
<p>${dog.description}</p>
<p class="like">${dog.likes} likes</p>
</div>
</div>
<button class="like-btn" data-id="${dog.id}">Like</button>
`;
card.querySelector('.like-btn').addEventListener('click', handleLike);
document.querySelector("#cards").appendChild(card);
}
The renderOneDog function takes a dog object as input and creates a new div element (card) for each dog. By passing a dog object to the function, it can access these properties and use them to dynamically create a card with correct/up to date information.
The card sets the class name of the card to card and populates its HTML content with the dog’s image, name, breed, description and number of likes.
The function also adds a like button (like-btn) to each card for liking the dog, with the dog’s ID as a data-id attribute. This is used to uniquely identify each dog in the gallery, which is important as the function that handles likes (which I will get to), needs to know which dog was liked so that it can update the likes count for that dog.
With all the card elements in place I added a function to fetch the data that will be used in the card and displayed on the site.
I wrote the renderOneDog function before the function that fetches the dog data because I use renderOneDog inside the function that fetches the data. I named the fetch function getAllDogs.
function getAllDogs(){
fetch('https://dogs-njbi.onrender.com/dogs')
.then (response => response.json())
.then (dogData =>
{dogData.forEach(dog => renderOneDog(dog));
// Note: I will not touch on the mostLikedDog element in this blog post as this data doesn’t display in the gallery.
mostLikedDog = dogData.reduce((prev, current) => (prev.likes > current.likes) ? prev : current);
renderMostLikedDog();
})
//
.catch (error => console.error("Error fetching dogs", error));
}
getAllDogs fetches data from the json-server. The data is fetched using a GET request to https://dogs-njbi.onrender.com/dogs. The function first converts the response to JSON format, which allows the data to be easily parsed and manipulated as a JavaScript object. This is necessary as it allows me to iterate over the data.
The function then iterates over each dog in the data, using a forEach iteration, calling the renderOneDog function to render each dog card. Through this, the function allows me to dynamically display the dog cards based on the data received from the server.
I added the .catch method for my own debugging purposes. I use it to handle errors that might occur when the data is being fetched. If an error is encountered during the fetch operation the .catch method is called and used to log the error message to the console along with the error object that was caught.
Next, lets handle that the like functionality
It doesn’t make sense to display the number of likes a dog has received if a user can’t also like a dog. I wanted to add a like button to encourage user interactivity, engagement and investment.
You will have noticed this line of code in my renderOneDog function
card.querySelector('.like-btn').addEventListener('click', handleLike);
Here I added an event listener to the like button. When clicked the handleLike function is called, which is the function where I handle the logic of what will happen when a user clicks on the button. In this context, the handleLike function is a callback function, as it is passed as an argument to another function (in this case, addEventListener) and is executed later, in response to a click on the like button.
For anything to happen I needed to write the handleLike function
function handleLike(event) {
const dogId = event.target.dataset.id;
const card = event.target.parentElement;
const likesElement = card.querySelector('.like');
let likesCount = parseInt(likesElement.textContent.match(/\d+/)[0]);
likesCount++;
fetch(`https://dogs-njbi.onrender.com/dogs/${dogId}`, {
method: 'PATCH',
headers: {
'Content-Type':'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
likes: likesCount
})
})
.then(response => response.json())
.then(updatedDog => {
likesElement.textContent = `${updatedDog.likes} likes`;
})
.catch(error => console.error("Error updating dog likes", error));
}
The handleLike function is responsible for updating the number of likes a dog has when the user clicks the like button on a card.
First, the function identifies the elements associated with the dog: dogId, card, and likesElement. It then extracts the current number of likes from the likesElement, converts it to an integer using parseInt, and stores it in the likesCount variable.
Next, it increments the likesCount variable to simulate the user liking the dog.
The function then makes a PATCH request to the API endpoint for the specific dog (https://dogs-njbi.onrender.com/dogs/${dogId}). After the request, the response is converted to JSON format. The .then(updatedDog => { ... }) block updates the text content of likesElement to display the updated number of likes.
Finally, the initialise function is called to start the processes of fetching and rendering dogs on the app when the site loads, as well as the like function which updates the likes in the DOM.
function initialise(){
getAllDogs();
}
initialise();
And there you have it, a quick overview of the Javascript code. Javascript out.
All this code makes up the foundation and key functionality of the contestants gallery, but without CSS the gallery wouldn’t have flesh, emotion, or expression. So let’s add some of that.
Design Design Design
My vision for the gallery was to present the dog data in a simple, professional, and visually appealing way, while also engaging the user.
I knew I wanted the gallery to be a scrolling gallery, that I wanted something to happen when the user hovered over the gallery card and that I wanted the like button to sit outside of the card so that it was always visible, encouraging user engagement.
After researching and experimenting with different designs, I settled on a flip gallery layout. This design allowed me to keep the initial gallery view visually appealing with just an image of the dog and a like button, while the back of the card could contain additional information about the dog.
This is what my CSS code for the gallery looks like
.card-gallery {
display: flex;
flex-wrap: nowrap;
overflow-x: auto;
min-height: 400px;
gap: 20px;
scroll-snap-type: x mandatory;
}
.card-gallery::-webkit-scrollbar {
display: none; /* Hiding scrollbar for Chrome, Safari */
}
.card:first-child {
padding-left: 20px;
}
.card {
text-align: center;
flex: 0 0 auto;
scroll-snap-align: start;
width: 300px;
height: 300px;
transform-style: preserve-3d;
-webkit-transform-style: preserve-3d;
transform: translateZ(0);
-webkit-transform: translateZ(0); /* Safari flip gallery fix*/
}
.card-inner {
width: 100%;
height: 100%;
transition: transform 0.5s;
transform-style: preserve-3d;
-webkit-transform-style: preserve-3d; /* Safari flip gallery fix*/
}
.card:hover .card-inner {
transform: rotateY(180deg);
-webkit-transform: rotateY(180deg); /* Safari flip gallery fix*/
}
.card-front,
.card-back {
width: 100%;
height: 100%;
position: absolute;
backface-visibility: hidden;
-webkit-backface-visibility: hidden; /* Safari flip gallery fix*/
}
.card-front {
display: flex;
justify-content: center;
align-items: center;
}
.card-back {
background-color: #4a6163;
color: #f9faf4;
text-align: center;
transform: rotateY(180deg);
-webkit-transform: rotateY(180deg); /* Safari flip gallery fix*/
border-radius: 10px;
}
.card-back p {
font-family: "Roboto", sans-serif;
font-weight: 300;
font-style: normal;
font-size: 15px;
padding: 10px;
letter-spacing: 0.5px;
}
.card-back .like {
font-style: italic;
}
.dog-image {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 10px;
}
.like {
text-align: center;
margin-top: 10px;
}
.like-btn {
display: block;
margin: 10px auto;
background-color: #4a6163;
color: #f9faf4;;
border: none;
width: 100px;
height: 40px;
font-family: "Roboto", sans-serif;
font-weight: 300;
font-style: normal;
font-size: 15px;
letter-spacing: 0.5px;
cursor: pointer;
border-radius: 10px;
}
.like-btn:hover {
background-color: #f17a7e;
color: #f9faf4;
}
Phew. That’s a lot of code!
The key things to point out in the CSS are the elements that make the gallery cards flip.
In the CSS for the gallery cards, .card-gallery styles the gallery container using flexbox, allowing for horizontal scrolling and preventing card wrapping. .card styles each card, centering content, setting dimensions, and enforcing no grow or shrink. .card-inner styles the inner content for flip animation, while .card:hover .card-inner handles the flip effect on hover. .card-front and .card-back style the front and back faces of each card, with .card-back initially rotated to hide it.
This does not cover all the styles in the CSS code above, but highlights those relevant to the flip effect.The flip effect adds an element of fun and interactivity to the user experience, especially when a user hovers over the dog image. This interactivity extends to the like button, which updates the number of likes on the back of the card after a click, enhancing user engagement.
The end result is not exactly, to the detail, what I had envisioned when I started my journey, but as a result of only 12 weeks of coding experience I am satisfied with how it ended up looking and working.
Thanks for reading.
Posted on April 15, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.