Objectos PetClinic v002: No-Reload form submission with Objectos Way v0.1.9

Objectos Way is our library for writing web applications entirely in Java. It renders HTML on the server and sends it over the wire to the client. Web applications built with Objectos Way are far from static though.
Here's a video of the recently released Objectos PetClinic v002 application:
The video shows the following steps:
-
Navigate to
/owners
to retrieve a page listing all existing pet owners -
Click the "Add owner" button and open a pet owner form in a modal
-
Fill in the fields for first name, last name, address, city, and telephone, then click the "Create" button
-
The form is hidden, and the listing updates to show the newly created pet owner
This functionality works without any page reloads or redirects, providing a smooth user experience.
In this blog post I will show you how the PetClinic application uses Objectos Way v0.1.9 in order to implement this functionality. We'll focus on the back-end implementation, while the front-end details will be the subject of our next post.
Let's begin.
A note on versions
This blog post refers exclusively to the following versions:
-
Objectos PetClinic: v002
-
Objectos Way: v0.1.9
Both projects are actively under development; so the information provided here is certain to become outdated.
Declaring the /owners routes
In an Objectos Way application, HTTP requests are handled by instances of the Http.Handler
interface.
The interface declares a single handle
method:
void handle(Http.Exchange http);
The method accepts an instance of the Http.Exchange
interface,
which represents the HTTP request received by the server and the subsequent response sent to the client.
It provides methods for accessing details about the HTTP request, such as:
-
The HTTP request target path
-
The HTTP request method
A single Http.Handler
instance can theoretically handle all server requests.
But dispatching requests programmatically using if-else
statements is tedious and error-prone.
Objectos Way simplifies this process with the Http.Module
class, enabling declarative routing.
Example: PetClinic's SiteModule
In the PetClinic application there's a single Http.Module
subclass, the SiteModule
class.
It declares all of the application's routes:
package objectos.petclinic.site;import objectos.way.Http;public class SiteModule extends Http.Module { ... @Override protected final void configure() { ... } ...}
Each application path is defined within the configure
method.
For the /owners
path, the configuration is as follows:
route("/owners", interceptor(injector::transactional), handlerFactory(Owners::new, injector));
This configuration tells the server:
-
Intercept the request using the
injector::transactional
interceptor. -
Handle the request with a
Http.Handler
instance created by thehandlerFactory
.
The transactional interceptor is discussed here.
Understanding handlerFactory
The handlerFactory(Owners::new, injector)
expression declares a handler factory that is equivalent to:
// Equivalent instantiationHttp.Handler handler = new Owners(injector);
So, for each request to the /owners
path, a new Http.Handler
instance is created using this factory.
The Owners class
The Owners
class implements the Http.Handler
interface.
Here’s how its handle
method processes requests:
@Overridepublic final void handle(Http.Exchange http) { switch (http.method()) { case GET, HEAD -> get(http); case POST -> post(http); default -> http.methodNotAllowed(); }}private void get(Http.Exchange http) { // Handles GET and HEAD requests}private void post(Http.Exchange http) { // Handles POST request}
It switches over the HTTP request method:
-
GET or HEAD: dispatches to the private
get
method. -
POST: dispatches to the private
post
method. -
Other Methods: The server responds with a
405 Method Not Allowed
message.
Declarative Java without annotations
And that's how we declare the /owners
routes in the PetClinic application.
It exemplifies one of Objectos Way’s design goals: allows for declarative Java without the use of annotations whenever possible.
Handling the /owners form submission
Now that we've declared the /owners
route, let's examine how the server processes form submissions to add a new pet owner.
The /owners
page contains an HTML form that allows users to input details for a new pet owner.
The form includes fields for the following attributes: first name, last name, address, city, and telephone.
Additionally, the form declares these attributes:
form( action("/owners"), method("post"), // fields ... // buttons)
When submitted, the form sends an HTTP request message to the server with the following characteristics:
-
Method:
POST
-
Path:
/owners
So the request is processed by the private post
method we outlined in the previous section:
private void post(Http.Exchange http) { // Handles POST request}
Let's examine how this method is implemented.
Parsing Form Fields
The first step is to parse the request body containing the submitted form fields.
Since the form does not declare an explicit enctype
attribute, the request body uses the x-www-form-urlencoded
media type.
Objectos Way provides the Web.FormData
interface to handle this:
Web.FormData formData;formData = Web.FormData.parse(http);
The formData
object now contains the parsed form data, allowing access to each submitted field.
Retrieving the current database transaction
Next, we retrieve the active SQL transaction associated with the HTTP request:
Sql.Transaction trx;trx = http.get(Sql.Transaction.class);
Because the /owners
route is intercepted by the injector::transactional
interceptor,
the transaction is already initialized and ready to use.
Creating the new pet owner record
We use the parsed form data and the active transaction to insert a new record into the database:
trx.sql("""insert into owners (first_name, last_name, address, city, telephone)values (?, ?, ?, ?, ?)""");trx.add(formData.getOrDefault("firstName", ""));trx.add(formData.getOrDefault("lastName", ""));trx.add(formData.getOrDefault("address", ""));trx.add(formData.getOrDefault("city", ""));trx.add(formData.getOrDefault("telephone", ""));trx.update();
The getOrDefault
method retrieves the value of a specified form field,
or returns a default value (an empty string in this case) if the field is missing.
Currently, this implementation does not include data validation, which is planned for a future release to ensure only valid data is stored in the database.
Re-rendering the pet owners listing
If no errors occur, the database transaction now includes the newly created pet owner.
To update the client, we render an updated version of the /owners
page:
OwnersView view;view = renderView(http);
The OwnersView
class is responsible for generating the HTML for the pet owners page.
Finally, we send the updated content back to the client. Before doing so, we need to check if the request was initiated by the Objectos Way JS library.
The Objectos Way JS Library
Objectos Way applications include a JavaScript library to help build dynamic web user interfaces. This library:
-
Intercepts HTML form submissions
-
Sends the form data using AJAX
-
Adds a custom
Way-Request: true
header to the request -
Processes the server's response to update the page without a full reload
Conditional response handling
Depending on whether the request originated from the Objectos Way JS library, the response format varies:
String wayRequest;wayRequest = http.header(Http.HeaderName.WAY_REQUEST);if ("true".equals(wayRequest)) { Script.Action action; action = view.createAction(); http.ok(action);} else { http.ok(view);}
If the Way-Request
header is present and set to true
,
the server sends a Script.Action
object with the client-side updates:
private final Html.Id overlay = Html.Id.of("overlay");private final Html.Id tearsheet = Html.Id.of("tearsheet");final Script.Action createAction() { return Script.actions( Script.toggleClass(overlay, "opacity-0", "opacity-100"), Script.toggleClass(tearsheet, "translate-y-3/4", "translate-y-0"), Script.delay( 350, Script.toggleClass(overlay, "invisible", "visible"), Script.html(this) ) );}
This action instructs the client-side Objectos Way JS library to:
-
Change the overlay opacity by toggling CSS style classes
-
Animate the form overlay downard by changing its Y-axis CSS translation
-
Wait for 350 milliseconds
-
Make the overlay invisible by toggling CSS style classes
-
Update the pet owners listing with the newly rendered HTML
If the request did not originate from the Objectos Way JS library, the server sends the updated HTML directly.
No-reload form submission
And that's how the PetClinic application handles the /owners
form submission.
It shows how Objectos Way permits for dynamic user interfaces without page reloads or redirections.
Testing the /owners form submission
Another design goal of Objectos Way is to support testable code.
The PetClinic application includes a test to verify the /owners
form submission handling logic. Here's how it works:
Simulating a form submission with Http.TestingExchange
The test begins by creating an instance of Http.TestingExchange
to simulate a form submission:
Http.TestingExchange http;http = Http.TestingExchange.create(config -> { config.method(Http.Method.POST); config.path("/owners"); config.queryParam("page", "2"); config.formParam("firstName", "NEW"); config.formParam("lastName", "YYY"); config.formParam("address", "New Address"); config.formParam("city", "New City"); config.formParam("telephone", "1122334455"); config.set(Sql.Transaction.class, trx);});
This instance represents an HTTP request with the following characteristics:
-
Method:
POST
-
Path:
/owners
-
Query Parameters:
page=2
-
Form Data:
-
firstName
:NEW
-
lastName
:YYY
-
address
:New Address
-
city
:New City
-
telephone
:1122334455
-
By using Http.TestingExchange
we can simulate HTTP interactions without the need of a running server.
Handling the request
Next, the test creates an instance of the Owners
class and invokes its handle
method with the Http.TestingExchange
instance:
Owners owners;owners = new Owners(siteInjector);owners.handle(http);assertEquals(http.responseStatus(), Http.Status.OK);
This simulates processing a form submission through the /owners
route and verifies that the response status code is 200 OK
.
Verifying database changes
After the handle
method runs, the test verifies that a new pet owner record was added to the database.
It confirms the record by asserting that the updated owners listing includes the new data:
assertEquals( writeResponseBody(http, "testCase03"), """ owner.name: NEW YYY owner.address: New Address owner.city: New City owner.telephone: 1122334455 owner.pets: owner.name: OW15 ZZZ owner.address: Add 15 owner.city: City 15 owner.telephone: 15 owner.pets: """);
This assertion uses the Html.Template
testable facility which was discussed here.
Conclusion
In this post, we walked through how the Objectos PetClinic application handles form submissions using Objectos Way.
We discussed the declaration of the /owners route, the parsing and processing of form data on the server, and the mechanisms for instructing the client to update the user interface. We also demonstrated how the PetClinic project tests this functionality, showcasing Objectos Way's emphasis on testable code.
In the next post, we will focus on the front-end aspects of the PetClinic application. You will see how Objectos Way enables the creation of dynamic user interfaces written entirely in Java.
Stay tuned!