Introduction to Adding User History Tab

Welcome to our fourth lesson in building an image generation web application with Flask!

In our previous lesson, we implemented the core JavaScript functionality for our application, including tab navigation and the image generation process. We also introduced some basic loading states and error handling for the image generation feature.

In this lesson, we'll build upon that foundation to create a more comprehensive user experience by focusing on the history tab functionality. We'll implement loading states and error handling specifically for retrieving and displaying previously generated images.

Importance of Feedback in Web Applications

When working with web applications that make API calls, it's crucial to provide users with clear feedback about what's happening behind the scenes.

Without proper loading indicators, users might wonder if their action was registered or if the application is frozen. Similarly, without proper error handling, users might be left confused when something goes wrong.

We've already seen how loading states and error handling work in our generateImage() function. Now, we'll apply similar principles to our history feature, ensuring a consistent and user-friendly experience throughout the application.

Overview of the History Tab Feature

Our application has two main tabs: one for generating images and another for viewing previously generated images. In this lesson, we'll focus on implementing the functionality for the history tab, which will allow users to retrieve and view their image generation history.

Let's get started by implementing the fetchHistory() function that will handle retrieving image history from our backend API.

Implementing the History Tab Functionality

Now that we understand the importance of providing feedback during API operations, let's implement the fetchHistory() function that will retrieve the user's image generation history from our backend API.

This function will be called when the user clicks the "Load Previous Images" button in the history tab. It will send a request to our backend API, retrieve the list of previously generated images, and display them in the history container.

Here's the implementation of the fetchHistory() function:

JavaScript
1function fetchHistory() { 2 const historyLoadingMessage = document.getElementById('history-loading-message'); 3 const historyButton = document.getElementById('history-button'); 4 const historyContainer = document.getElementById('history-container'); 5 6 historyLoadingMessage.style.display = 'block'; 7 historyButton.style.display = 'none'; 8 historyContainer.innerHTML = ''; 9 10 fetch('/api/get_images') 11 .then(response => response.json()) 12 .then(data => { 13 historyLoadingMessage.style.display = 'none'; 14 historyButton.style.display = 'block'; 15 16 if (data.images.length === 0) { 17 historyContainer.innerHTML = '<p>No images found.</p>'; 18 return; 19 } 20 21 data.images.forEach(image => { 22 const img = document.createElement('img'); 23 img.src = `data:image/png;base64,${image.image_base64}`; 24 historyContainer.appendChild(img); 25 }); 26 }) 27 .catch(error => { 28 historyLoadingMessage.style.display = 'none'; 29 historyButton.style.display = 'block'; 30 alert('Error fetching image history. Please try again.'); 31 console.error('Error:', error); 32 }); 33}

Let's break down this function step by step:

Step 1: Understanding Key DOM References

First, we get references to the DOM elements we'll need to update during the history retrieval process:

JavaScript
1const historyLoadingMessage = document.getElementById('history-loading-message'); 2const historyButton = document.getElementById('history-button'); 3const historyContainer = document.getElementById('history-container');

These elements include the loading message that will be displayed while the history is being fetched, the button that the user clicks to load the history, and the container where the images will be displayed.

Step 2: Updating the UI Before Fetch

We update the UI to show that the history retrieval is in progress:

JavaScript
1historyLoadingMessage.style.display = 'block'; 2historyButton.style.display = 'none'; 3historyContainer.innerHTML = '';

This shows the loading message, hides the "Load Previous Images" button, and clears any previously displayed images from the container.

Step 3: Fetching the Image History

Then, we send a GET request to our backend API to retrieve the image history:

JavaScript
1fetch('/api/get_images') 2.then(response => response.json())

Unlike the image generation function, which sends a POST request with user input, this function sends a simple GET request to the /api/get_images endpoint. We expect the response to be a JSON object containing an array of image data.

Step 4: Displaying the Retrieved Images

Once we receive and parse the response, we update the UI based on the data we received:

JavaScript
1.then(data => { 2 historyLoadingMessage.style.display = 'none'; 3 historyButton.style.display = 'block'; 4 5 if (data.images.length === 0) { 6 historyContainer.innerHTML = '<p>No images found.</p>'; 7 return; 8 } 9 10 data.images.forEach(image => { 11 const img = document.createElement('img'); 12 img.src = `data:image/png;base64,${image.image_base64}`; 13 historyContainer.appendChild(img); 14 }); 15})
Step 5: Handling Fetch Errors

To ensure a smooth user experience, we handle errors like this:

JavaScript
1.catch(error => { 2 historyLoadingMessage.style.display = 'none'; 3 historyButton.style.display = 'block'; 4 alert('Error fetching image history. Please try again.'); 5 console.error('Error:', error); 6});

This resets the UI and shows a helpful message.

Code summary

First, we reset the UI by hiding the loading message and showing the button again. Then, we check if the response contains any images. If not, we display a message indicating that no images were found.

If there are images, we iterate through each one, create an img element for it, set its source to a data URL containing the base64-encoded image data, and append it to the history container. This will display all the previously generated images in the history tab.

With this function in place, users can now click the "Load Previous Images" button in the history tab to view their image generation history. The function will retrieve the images from the backend API and display them in the history container.

Managing Loading States for History Retrieval

In the previous section, we implemented the fetchHistory() function to retrieve and display the user's image generation history. Now, let's take a closer look at how we're managing loading states during this process.

Loading states are visual indicators that inform users that an operation is in progress. They're especially important for operations that might take some time to complete, such as API calls. Without loading states, users might think that the application is frozen or that their action wasn't registered.

In our fetchHistory() function, we're managing loading states by toggling the visibility of certain UI elements:

JavaScript
1historyLoadingMessage.style.display = 'block'; 2historyButton.style.display = 'none'; 3historyContainer.innerHTML = '';
JavaScript
1historyLoadingMessage.style.display = 'none'; 2historyButton.style.display = 'block';

Before sending the API request, we show the loading message, hide the "Load Previous Images" button, and clear any previously displayed images from the container. This provides immediate feedback to the user that their action was registered and that the operation is in progress.

After receiving the API response, we hide the loading message and show the button again, indicating that the operation has completed. We then update the history container based on the data we received.

Setting Up the Loading Message Element

In our HTML, we've already set up the loading message element with an initial display: none style, which means it's hidden by default:

HTML, XML
1<p id="history-loading-message" style="display: none;">Loading history... Please wait.</p>

This element is only shown when we explicitly set its display style to 'block' in our JavaScript code.

By managing loading states in this way, we provide a better user experience by giving clear feedback about what's happening behind the scenes. Users can see that their action was registered and that the application is working on retrieving their image history.

Handling Empty Results

In addition to managing loading states, it's important to handle different types of responses and potential errors that might occur during the history retrieval process. Let's take a closer look at how we're handling these scenarios in our fetchHistory() function.

First, let's consider the case where the user has no previously generated images. In this scenario, the API response will contain an empty array of images. We handle this case by checking the length of the data.images array and displaying a message if it's empty:

JavaScript
1if (data.images.length === 0) { 2 historyContainer.innerHTML = '<p>No images found.</p>'; 3 return; 4}

This provides clear feedback to the user that there are no images to display, rather than leaving the container empty, which might be confusing.

Handling Errors in History View

Let's consider the case where an error occurs during the fetch operation. This could happen for various reasons, such as network issues, server errors, or invalid responses. We handle these errors in the .catch() block:

JavaScript
1.catch(error => { 2 historyLoadingMessage.style.display = 'none'; 3 historyButton.style.display = 'block'; 4 alert('Error fetching image history. Please try again.'); 5 console.error('Error:', error); 6});

When an error occurs, we reset the UI by hiding the loading message and showing the button again. We then display an alert with a user-friendly error message, encouraging the user to try again. We also log the error to the console for debugging purposes.

It's worth noting that we're using a simple alert() for error messages in this example. In a production application, you might want to use a more sophisticated approach, such as displaying the error message in a designated area of the page or using a modal dialog.

By handling errors in this way, we ensure that users always receive appropriate feedback, even when things don't go as expected. This contributes to a more robust and user-friendly application.

Displaying Retrieved Images

Let's consider how we're handling the successful case, where the API response contains one or more images:

JavaScript
1data.images.forEach(image => { 2 const img = document.createElement('img'); 3 img.src = `data:image/png;base64,${image.image_base64}`; 4 historyContainer.appendChild(img); 5});

For each image in the response, we create an img element, set its source to a data URL containing the base64-encoded image data, and append it to the history container. This displays all the previously generated images in the history tab.

By handling all these different scenarios, we create a comprehensive user experience that provides appropriate feedback in all situations, from successful operations to empty results and errors.

Testing Our Implementation

To test our implementation, you can follow these steps:

  1. Make sure your Flask application is running and that the /api/get_images endpoint is properly implemented to return a list of previously generated images.
  2. Open your application in a web browser and navigate to the history tab by clicking on the "View History" tab button.
  3. Click the "Load Previous Images" button to trigger the fetchHistory() function.
  4. Observe the loading message that appears while the history is being retrieved.
  5. Once the history is retrieved, observe the images that are displayed in the history container.
  6. If there are no images, observe the "No images found." message that is displayed.
  7. To test error handling, you can temporarily modify your code to simulate an error, such as by changing the API endpoint URL to one that doesn't exist.
Summary

Now that we've implemented the fetchHistory() function with proper loading states and error handling, let's review what we've accomplished.

In this lesson, we've built upon the foundation laid in the previous lesson to create a more comprehensive user experience for our image generation web application. We've implemented the fetchHistory() function, which retrieves and displays the user's image generation history. We've also added proper loading states and error handling to ensure that users receive appropriate feedback during the history retrieval process.

Remember that proper loading states and error handling are crucial for creating a good user experience in web applications. They provide feedback to users about what's happening behind the scenes and help them understand and recover from errors.

In the practice exercises that follow this lesson, you'll have the opportunity to experiment with the code we've written and further enhance the user experience of our application. You might try adding more sophisticated loading indicators, improving error messages, or implementing additional features such as pagination for the history view.

By mastering the concepts covered in this lesson, you'll be well-equipped to create web applications that provide a smooth and user-friendly experience, even when dealing with asynchronous operations and potential errors.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal