Overview of This Lesson

Refresh this page because I am probably still making changes to it.

Path variables are an easy way to send simple data from a page to a controller using a path segment of the URL. It's not a query string, but the concept is similar. Of course, this means that you shouldn't use a path variable for any data that requires privacy or security, since it's visible as plain text in a URL.

Helpful Resources

Pre-Requisites

Make sure you've completed the exercises in the lesson on CRUD Operations: Create/Read before doing this lesson.

Path Variables

Sometimes you will want to keep track of a record's unique ID in a program. For example, if your user is viewing a list of Player records and they want to edit one of them, we would need a means of selecting that specific record, and then passing that player's ID number to a handler method so that the record can then be displayed on an input form for the user to edit, and then once again that record's ID passed to a handler method that runs an update query. Similarly, to select a record to delete, that record's ID has to be passed to the method that does the delete query.

A nice easy way to do this is by using path variables.

You're already familiar with passing parameters as key-value pairs in a URL, but you can also pass values without using key-value pairs in the URL: you can pass data using URL path segments. Recall that a URL path segment is the part of the URL separated by the slash character. For example

localhost:8080/myproject/foo/boo/moo

This URL has 4 path segments: myproject, foo, boo, and moo

If you wanted to allow a user to edit specific inventory items displayed in a list, you could just append the inventory ID to the end of the URL as an extra segment:

http://petstore/inventory/edit/123 (the extra segment has the value 123)
http://petstore/inventory/edit/24 (the extra segment has the value 24)
http://petstore/inventory/edit/ab123 (the extra segment has the value ab123)

The value in the last segment can be captured by adding a variable to a URL pattern in the controller's @GetMapping or @PostMapping:

@GetMapping("/inventory/edit/{invId}")

The {invId} means that whatever segment comes after /inventory/edit/, capture it as the path variable "invId".

Inside the handler method, you can create a special parameter to access the path variable by using the @PathVariable annotation:

public String handlerMethod(@PathVariable String invId) {
    ...
}

If you wanted to call the parameter something different, specify the path variable name using the name="" property or on its own (just like you did with @RequestParam)

@PathVariable(name="invId") String itemId
@PathVariable("invId") String itemId

To make a path variable optional, use required=false, just like you did with @RequestParam:

@PathVariable(required=false) String invId

You can also include multiple segments for multiple path variables:

@GetMapping("/inventory/{op}/{invId}")
public String handlerMethod(@PathVariable("op") String operation,
    @PathVariable String invId) {

    if (operation.equalsIgnoreCase("edit")) {
       da.update(invId);
    } else if (operation.equalsIgnoreCase("delete")) {
       da.delete(invId);
    }
}

Alternatively, you can use a Map<String, String> instead:

@GetMapping("/inventory/{op}/{invId}")
public String handlerMethod(@PathVariable() Map<String, String> pathVars) {

    String operation = pathVars.get("op");
    String invId = pathVars.get("invId");
    if (operation.equalsIgnoreCase("edit")) {
       da.update(invId);
    } else if (operation.equalsIgnoreCase("delete")) {
       da.delete(invId);
    }
}

The Map<K, V> should use the concrete type String for both the key and the value (you can convert the map entry values as needed). The parameter variable for the map can be called anything you like, as long as it followed standard, legal variable identifier rules.

Let's try a demonstration using our Teams and Players.

For this demo, you should edit your data.sql file and add some player records on different teams to your players table.

We want to be able to add a page that allows a user to click on a team and to view all the players on that team, if any.

a bulleted list of team abbreviations and a table showing players on a team
The user should be able to click on a team link
to view the players on that team.

First, we need a database access method to retrieve a list of players for a specific team. You did a method like this already when you did the exercises for the previous lesson. In mine, I used a variable-length argument list for my parameter instead of a string array, so that I could use this method for one team, or several teams!

public List<Player> searchByTeam(String... team) {
   ...		
}

You can use the same method for this part of the application, as well. Just make sure your method can handle a single team or a list of teams so you don't have to copy and paste or rewrite any of the code.

Create a new HTML/Thymeleaf page that displays all the Team ID values in an unordered list. Be sure to add a method in your controller that loads this page with a collection of teams (you can use the same database access method to get a list of teams that you used in the previous lesson).

<ul>
<li th:each="t : ${teams}">
</ul>

In each list item, we want to include a link to /viewTeam/ID where "ID" is the team's ID. The link text should also be the same 3-letter team ID. For example, the LI element for Calgary would be rendered in HTML as

<li><a href="/viewTeam/CGY">CGY</a></li>

To make the link we have to use a Thymeleaf URL expression @{ }, which also means we need to use the th:href attribute:

th:href="|@{/viewTeam/}${t.id}|"

We want to build a URL of /viewTeam/ID where ID is the 3-letter team ID so we have to use @{/viewTeam/} and then concatenate the ${t.id} onto it.

Now we set the link text to the 3-letter ID for each team:

th:text="${t.id}"

So now this gives us:

<li th:each="t : ${teams}"><a href="#" th:href="|@{/viewTeam/}${t.id}|" 
th:text="${t.id}">team</a></li>

Try loading your page to make sure it works. You should see the bulleted list of team ID values.

Hover over each link and look at the status bar in the bottom of the window, or use the browser's Inspect too if you're unable to read the status bar text. You can see that each link is /viewTeam/ followed by the 3-letter team ID!

Now, in the controller, I need a method that executes when the team link is clicked. This handler method should invoke the database access method you wrote in the previous exercises (my example above was searchByTeams()). This method needs at least one team ID string. Remember the team string is now part of the URL:

<li><a href="/viewTeam/CGY">CGY</a></li>
<li><a href="/viewTeam/EDM">EDM</a></li>
Etc.

So I create a URL mapping that lets me capture the team ID as the last segment of the URL:

@GetMapping("/viewTeam/{teamId}")
public String viewTeam(Model model, @PathVariable String teamId) {

}

Now we can pass that teamId to the data access method:

model.addAttribute("players", da.searchByTeam(teamId));

Add the teams list to the model one more time so we can build the same links on the page when we go back to it (or if you were using the Teams enum instead, don't worry about it):

model.addAttribute("teams", da.getTeams());
return "index.html";

Now go back to your index page (or whatever page you put your bulleted list on) and add the code to display each player in a table (or another list, whatever you like: maybe you could use CSS to create a pop-down list under the team link!!)

Now the links should work! Go to the browser and try clicking on a link. You should see the table of teams appear!

One thing you might notice is that if you click on a team with no players, you get a blank table. It would be no trouble to add some code to your index page to only display the players if the list of players wasn't empty! I'll leave that with you as an exercise.

That's how path variables work! We can use path variables in our JDBC applications when we want to display a set of records and allow the user to select a record to edit or delete. We'll do this in a later lesson.