Introduction and Lesson Overview

Welcome back! In our previous lessons, we laid the groundwork for our travel planner application by creating a RESTful API with Flask and rendering a basic HTML page. Now, we are ready to enhance the user experience by adding client-side JavaScript. This lesson will focus on implementing JavaScript to handle form submissions, simulate API calls, and dynamically render content. By the end of this lesson, you will be able to create a more interactive and engaging application that responds to user input in real-time.

Reviewing the Project Structure

Before we dive into linking JavaScript to our HTML, let's focus on the specific file we are creating and its location within the project structure.

We are creating the travel-planner.js file inside the static/js/ directory. This file will contain the JavaScript code responsible for enhancing the interactivity of our travel planner application. By placing it in the static/js/ directory, we ensure that it is served correctly by Flask as a static asset, allowing us to link it to our index.html file for client-side functionality.

Linking JavaScript to HTML

Now that we have created the travel-planner.js file in the static/js/ directory, it's time to link this JavaScript file to our HTML page. This is done by including a <script> tag in the index.html file. In Flask, we use the url_for function to correctly reference the static JavaScript file. This ensures that our JavaScript code is loaded and ready to enhance the page's functionality.

Here's how your index.html file should look, with comments indicating where the rest of the code should be:

This line of code is placed just before the closing </body> tag in your HTML file, ensuring that the JavaScript is loaded after the HTML content. By linking the JavaScript file, we enable the dynamic features and interactivity that will enhance the user experience of our travel planner application.

Preventing Default Form Submission and Initializing UI Elements

With our JavaScript file linked to the HTML, we are ready to start writing the JavaScript code in the travel-planner.js file. Our first task is to set up the form to handle submissions in a way that allows us to process the data and update the UI dynamically without refreshing the page.

Step-by-Step Guide:

  1. Add an Event Listener to the Form: We begin by adding an event listener to the form. This listener will trigger an async function whenever the form is submitted. Using async allows us to use the await keyword inside the function, which is useful for handling asynchronous operations like simulating API calls.

  2. Prevent Default Form Submission: By default, submitting a form causes the page to reload. We want to prevent this behavior so that we can handle the form data with JavaScript. We do this by calling e.preventDefault().

  3. Show Loading Spinner and Results Card: To provide feedback to the user that their submission is being processed, we display a loading spinner. We also prepare the results card where the itinerary will be displayed by clearing any previous content.

Here's how you can implement these steps in your travel-planner.js file:

In this code, we set up an event listener on the form with the ID tripForm to handle submissions. By calling e.preventDefault(), we stop the default form submission, which would normally reload the page. Instead, we display a loading spinner and prepare the results card by clearing any previous content. This approach allows us to process the form data and update the user interface dynamically, providing a seamless and interactive experience for the user.

Handling Form Data with FormData Object

After intercepting the form submission, we can access the form data using the FormData object. This object provides a convenient way to extract user input values from the form fields. We can then process these values, such as converting them to the appropriate data types for further use in our application logic.

Using the FormData object simplifies the process of gathering and manipulating form data, allowing us to focus on the core functionality of our application.

Simulating API Calls with Error Handling and UI Feedback

In this context, simulating API calls allows us to develop and test the client-side logic and user interface before a real backend or external service is available. This approach helps ensure that our application's interactive features work as intended, even while the backend is still under development.

To create a realistic user experience, we simulate an API call using a try-catch-finally block. The try block contains a simulated delay using setTimeout, which mimics the time taken for an API response. We use the await keyword to pause the execution of the function until the simulated delay is complete, just as we would when waiting for a real API call to return data. If an error occurs during this process, the catch block displays an error message to the user. The finally block ensures that the loading spinner is hidden, providing a seamless user experience regardless of the outcome.

By implementing this structure, we provide users with feedback during the processing phase and handle any potential errors gracefully, ensuring a robust and user-friendly application.

Simulating Response Data

To effectively test our application's functionality, we simulate response data that mimics what a real API might return. This involves creating mock data for the travel itinerary based on user input, such as the city, number of days, and attractions per day. By generating this data, we can ensure that our application processes and displays information correctly without relying on a live API.

In the code provided, we simulate response data to test how our application processes and displays it. This approach allows us to ensure that the user interface behaves as expected without relying on a live API. Here's a detailed explanation of what we did in the code:

  1. Create Simulated API Response Data: We construct an object named data that mimics the structure of a real API response. This object includes details such as the city, number of days, and daily plans.

  2. Generate Daily Plans: We loop through the number of days specified by the user and create a simple itinerary for each day. For each day, we generate a list of attractions based on the number of attractions per day specified by the user. Each attraction includes a name, category, estimated duration, and description.

  3. Add Overall Tips: We include a section for overall travel tips, providing general advice for the trip.

By simulating this data, we can proceed to test how the application processes and displays it. This ensures that the user interface behaves as expected, allowing us to verify that the dynamic rendering of the itinerary works correctly.

Setting Up the Itinerary Display

With the simulated response data in hand, the next step is to dynamically render the itinerary on the page. We start by initializing an HTML string that will hold the itinerary content and checking if the daily plans data exists and is in the expected format.

In this code, we begin by creating a variable html that will store the HTML content for our itinerary. We initialize it with a div element that has a class of "itinerary" to serve as a container. We then check if the data.daily_plans property exists and is an array. This validation ensures that we have valid data to work with before attempting to render the itinerary. If the data is not available or not in the expected format, we display a message to the user indicating that no itinerary data is available.

Iterating Through Each Day's Plan

Once we've confirmed that the daily plans data exists, we need to iterate through each day's plan to construct the HTML content. This involves creating a structure that will display the day number, attractions, and meal suggestions for each day.

Here, we set up the structure for iterating over each day's plan in the data.daily_plans array. We use the forEach method to loop through each day, which allows us to access the details for that specific day. Inside this loop, we will construct the HTML content for each day's itinerary, including attractions and meal suggestions. After processing all days, we'll add overall tips to provide general advice for the trip. If the daily plans data is not available, we display a message to inform the user.

Building Detailed Daily Activities

For each day in the itinerary, we construct detailed HTML content that includes the day number, a list of attractions with their details, and meal suggestions. This creates a comprehensive view of the daily activities for the user.

In this implementation, we use template literals to construct the HTML content for each day's plan. For each day, we create a div with a class of "day-plan" and include an h6 heading that displays the day number. We then create an unordered list to display the attractions for that day. Using the map method, we iterate over each attraction and create a list item that includes the attraction's name, category, estimated duration, and description. The join('') method combines all the list items into a single string.

We also check if meal suggestions are available for the day using a conditional (ternary) operator. If meal suggestions exist, we create another section that lists them. This approach ensures that we only display the meal suggestions section if there are actual suggestions to show. The resulting HTML provides a detailed and organized view of each day's activities.

Adding Travel Tips and Finalizing the Display

After displaying the daily plans, we add a section for overall travel tips if they are available. These tips provide general advice for the trip. Finally, we inject the constructed HTML content into the results section of the page, making the itinerary visible to the user.

In this final section, we check if overall tips are available by verifying that the data.overall_tips property exists. If it does, we append a new div to our HTML string that contains an h6 heading labeled "Overall Tips" and a div that displays the tips content. This provides users with general advice for their trip, such as what to pack or local customs to be aware of.

After constructing all the HTML content for the itinerary, we use the innerHTML property to inject this content into the element with the ID "resultsContent". This replaces any previous content in that element with our newly generated itinerary. By doing this, we dynamically update the page to display the travel plan without requiring a page refresh, providing a seamless and interactive user experience.

Recap and Summary

In this lesson, we explored how to enhance our travel planner application with client-side JavaScript. We linked a JavaScript file to our HTML, handled form submissions, simulated API calls, and dynamically rendered content. We also implemented error handling and UI feedback to improve the user experience. As you move on to the hands-on practice exercises, remember to apply these concepts and experiment with the JavaScript code. These steps are essential as we continue to build a full-featured application. Keep up the great work, and I look forward to seeing your progress!

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