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.
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)
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.
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:
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:
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):
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.