In this first phase, we focus on creating the foundational structure of our application and implementing the basic functionality of fetching and displaying cat images.
// Phase 1 Implementation
window.onload = () => {
// Create main container for our application
const container = document.createElement('div');
container.id = 'content';
document.body.appendChild(container);
// Create and add image element
const img = document.createElement('img');
img.id = 'cat-image';
container.appendChild(img);
// Function to fetch a random cat image
async function fetchCatImage() {
try {
const response = await fetch('https://api.thecatapi.com/v1/images/search');
const data = await response.json();
img.src = data[0].url;
} catch (error) {
console.error('Error fetching cat image:', error);
}
}
// Load initial image
fetchCatImage();
}
Key Concepts in Phase 1:
The window.onload event ensures our code runs only after the DOM is fully loaded. Think of this like waiting for all the ingredients to be on the counter before starting to cook.
We use createElement to build our page structure dynamically, similar to assembling building blocks one at a time.
The fetch API is used to request images from the cat API. This is like sending a request to a photo library and receiving a random photo in return.
In Phase 2, we enhance our application by adding user interaction features: voting and commenting.
// Phase 2 Implementation - Building upon Phase 1
window.onload = () => {
// Previous Phase 1 code remains...
// Add voting section
const votingDiv = document.createElement('div');
const upvoteBtn = document.createElement('button');
const downvoteBtn = document.createElement('button');
const voteCount = document.createElement('span');
upvoteBtn.textContent = '👍';
downvoteBtn.textContent = '👎';
voteCount.textContent = '0';
votingDiv.appendChild(upvoteBtn);
votingDiv.appendChild(voteCount);
votingDiv.appendChild(downvoteBtn);
container.appendChild(votingDiv);
// Add comment section
const commentForm = document.createElement('form');
const commentInput = document.createElement('input');
const commentButton = document.createElement('button');
const commentList = document.createElement('ul');
commentInput.type = 'text';
commentInput.placeholder = 'Add a comment...';
commentButton.textContent = 'Post';
commentForm.appendChild(commentInput);
commentForm.appendChild(commentButton);
container.appendChild(commentForm);
container.appendChild(commentList);
// Add New Picture button
const newPicButton = document.createElement('button');
newPicButton.textContent = 'New Cat Picture';
container.insertBefore(newPicButton, votingDiv);
// Track votes
let votes = 0;
// Event Listeners
upvoteBtn.addEventListener('click', () => {
votes++;
voteCount.textContent = votes;
});
downvoteBtn.addEventListener('click', () => {
votes--;
voteCount.textContent = votes;
});
commentForm.addEventListener('submit', (e) => {
e.preventDefault();
if (commentInput.value.trim()) {
const li = document.createElement('li');
li.textContent = commentInput.value;
commentList.appendChild(li);
commentInput.value = '';
}
});
newPicButton.addEventListener('click', () => {
fetchCatImage();
votes = 0;
voteCount.textContent = '0';
commentList.innerHTML = '';
});
}
Key Concepts in Phase 2:
Event Listeners act like special assistants watching for specific actions. When a user clicks a button or submits a comment, the appropriate code runs automatically.
Form handling requires preventing the default form submission behavior (e.preventDefault()) to keep the page from reloading.
DOM manipulation becomes more complex as we add and remove elements dynamically.
The final phase focuses on maintaining the application's state across page refreshes using localStorage.
// Phase 3 Implementation - Complete Solution
window.onload = () => {
// State management
let state = {
currentImage: null,
votes: 0,
comments: []
};
// Load saved state if it exists
const savedState = localStorage.getItem('catstagramState');
if (savedState) {
state = JSON.parse(savedState);
}
// Previous DOM creation code...
// Modified event listeners with state management
upvoteBtn.addEventListener('click', () => {
state.votes++;
voteCount.textContent = state.votes;
saveState();
});
downvoteBtn.addEventListener('click', () => {
state.votes--;
voteCount.textContent = state.votes;
saveState();
});
commentForm.addEventListener('submit', (e) => {
e.preventDefault();
if (commentInput.value.trim()) {
state.comments.push(commentInput.value);
const li = document.createElement('li');
li.textContent = commentInput.value;
commentList.appendChild(li);
commentInput.value = '';
saveState();
}
});
// Modified fetch function with state update
async function fetchCatImage() {
try {
const response = await fetch('https://api.thecatapi.com/v1/images/search');
const data = await response.json();
state.currentImage = data[0].url;
img.src = state.currentImage;
saveState();
} catch (error) {
console.error('Error fetching cat image:', error);
}
}
// Function to save state
function saveState() {
localStorage.setItem('catstagramState', JSON.stringify(state));
}
// Initialize page with saved state or new image
if (state.currentImage) {
img.src = state.currentImage;
voteCount.textContent = state.votes;
state.comments.forEach(comment => {
const li = document.createElement('li');
li.textContent = comment;
commentList.appendChild(li);
});
} else {
fetchCatImage();
}
}
Key Concepts in Phase 3:
State management is like keeping a detailed log of everything happening in our application. The state object tracks the current image, votes, and comments.
localStorage acts like a persistent notebook that remembers information even after the page is closed. Think of it as saving a document to your computer.
JSON.stringify and JSON.parse are used to convert our state object to a string for storage and back to an object when loading. This is similar to packaging items for storage and unpacking them later.
When implementing this project, you might encounter these common challenges:
API Rate Limiting: The Cat API might limit how many requests you can make. Consider implementing error handling and user feedback for when requests fail.
State Synchronization: Keeping the DOM and state in sync can be tricky. Always update both together to maintain consistency.
Local Storage Limitations: Remember that localStorage can only store strings, which is why we need to use JSON.stringify and JSON.parse.
To ensure your implementation works correctly:
Test page refresh behavior: The same image, votes, and comments should persist.
Verify error handling: The application should gracefully handle API failures.
Check edge cases: Test with empty comments, rapid voting, and multiple image refreshes.
Once you have the basic implementation working, consider these improvements:
Add comment deletion functionality
Implement image favoriting
Add user authentication
Implement share functionality