Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions webforj-nested-beans/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# http://editorconfig.org

root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
insert_final_newline = false
trim_trailing_whitespace = false
44 changes: 44 additions & 0 deletions webforj-nested-beans/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Compiled class file
*.class

# Log file
*.log

# BlueJ files
*.ctxt

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
replay_pid*

**/target
/.idea
/.claude
*.iml
/.project
/.classpath
**/sample/

/.settings/org.eclipse.core.resources.prefs
/.settings/org.eclipse.jdt.core.prefs
/.settings/org.eclipse.m2e.core.prefs

bbj/.bdtPath
bbj/.buildpath
bbj/.project

.DS_Store
*.versionsBackup

26 changes: 26 additions & 0 deletions webforj-nested-beans/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"java.configuration.updateBuildConfiguration": "interactive",
"java.completion.favoriteStaticMembers": [
"org.junit.Assert.*",
"org.junit.Assume.*",
"org.junit.jupiter.api.Assertions.*",
"org.junit.jupiter.api.Assumptions.*",
"org.junit.jupiter.api.DynamicContainer.*",
"org.junit.jupiter.api.DynamicTest.*",
"org.mockito.Mockito.*",
"org.mockito.ArgumentMatchers.*",
"org.mockito.Answers.*",
"com.webforj.component.optiondialog.OptionDialog.*",
"com.webforj.App.console",
],
"java.completion.filteredTypes": [
"java.awt.*",
"com.sun.*",
"sun.*",
"jdk.*",
"org.graalvm.*",
"io.micrometer.shaded.*",
"com.basis.*",
"javax.swing.*",
],
}
71 changes: 71 additions & 0 deletions webforj-nested-beans/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# webforj-nestedbeans

A small webforJ 26.01 demo of **data binding over nested Java beans**, packaged as a traditional WAR and run on the Jetty Maven plugin.

The form binds an `Employee` that contains a nested `Address` and `EmergencyContact`. A **single `BindingContext<Employee>`** wires every UI field, including nested fields via dotted property paths (e.g. `"address.street"`). Jakarta Validation cascades through the nested beans via `@Valid`, and `@Embeddable`/`@Embedded` mark the nested types so future JPA persistence is a drop-in change.

## Prerequisites

- Java 21 or newer
- Maven 3.9+

## Running

```bash
mvn jetty:run
```

Then open <http://localhost:8080>.

The Jetty plugin scans for class and resource changes every second (`<jetty.scan>1</jetty.scan>` in `pom.xml`), so edits redeploy automatically.

## Building a WAR

```bash
mvn clean package
```

Produces `target/webforj-nestedbeans.war`, ready to drop into any Servlet 6 container.

## Project layout

```
src/main/
├── java/com/webforj/nestedbeans/
│ ├── Application.java # webforJ entry (@Routify, @AppTheme, @StyleSheet)
│ ├── domain/
│ │ ├── Employee.java # @Embedded address + emergencyContact
│ │ ├── Address.java # @Embeddable
│ │ └── EmergencyContact.java # @Embeddable
│ ├── service/
│ │ └── EmployeeService.java # static singleton, in-memory ArrayList
│ └── views/
│ └── EmployeeFormView.java # single BindingContext + dot-notation
├── resources/
│ └── webforj.conf # webforJ entry / debug flags (HOCON)
└── webapp/
└── WEB-INF/web.xml # WebforjServlet mapped to /*
```

## How the binding works

```java
context = new BindingContext<>(Employee.class, true);
context.bind(firstName, "firstName").add();
context.bind(street, "address.street").add();
context.bind(phone, "emergencyContact.phone").add();
context.read(employee); // reads through nested gets
context.write(employee); // creates missing nested beans via no-arg ctor, then writes
```

One context, dotted paths for every nested field. Jakarta validation messages declared on `Address`/`EmergencyContact` light up automatically because the `Employee` fields are annotated `@Valid`.

## Viewing saved entries

The page has a **View saved** button that opens a `Dialog` containing an `Accordion`. Each accordion panel expands to show one saved employee's identity, address, and emergency contact.

## Learn more

- [Full Documentation](https://docs.webforj.com)
- [Data binding docs](https://docs.webforj.com/docs/data-binding/bindings) (nested-property pattern lands in 26.01)
- [Maven Jetty plugin](https://docs.webforj.com/docs/configuration/deploy-reload/maven-jetty-plugin)
110 changes: 110 additions & 0 deletions webforj-nested-beans/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>

<groupId>com.webforj.nestedbeans</groupId>
<artifactId>webforj-nestedbeans</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>webforj-nestedbeans</name>

<properties>
<webforj.version>26.01-SNAPSHOT</webforj.version>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<jetty.version>12.0.14</jetty.version>
<jetty.scan>1</jetty.scan>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.webforj</groupId>
<artifactId>webforj-bom</artifactId>
<version>${webforj.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>com.webforj</groupId>
<artifactId>webforj</artifactId>
<version>${webforj.version}</version>
</dependency>

<!-- Jakarta Validation 3.0 API + reference implementation for Jakarta EE 10. -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>8.0.3.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish.expressly</groupId>
<artifactId>expressly</artifactId>
<version>5.0.0</version>
</dependency>

<!-- @Embeddable / @Embedded annotations on the nested beans (no JPA runtime). -->
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<version>3.2.0</version>
</dependency>
</dependencies>

<repositories>
<repository>
<id>central-portal-snapshots</id>
<name>Central Portal Snapshots</name>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>central-portal-snapshots</id>
<name>Central Portal Snapshots</name>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>

<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.eclipse.jetty.ee10</groupId>
<artifactId>jetty-ee10-maven-plugin</artifactId>
<version>${jetty.version}</version>
<configuration>
<scan>${jetty.scan}</scan>
<webApp>
<contextPath>/</contextPath>
</webApp>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.webforj.nestedbeans;

import com.webforj.App;
import com.webforj.annotation.AppProfile;
import com.webforj.annotation.AppTheme;
import com.webforj.annotation.Routify;
import com.webforj.annotation.StyleSheet;

@Routify(packages = "com.webforj.nestedbeans.views")
@StyleSheet("ws://app.css")
@AppTheme("system")
@AppProfile(name = "webforj-nestedbeans", shortName = "webforj-nestedbeans")
public class Application extends App {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.webforj.nestedbeans.domain;

import jakarta.persistence.Embeddable;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

@Embeddable
public class Address {

@NotBlank(message = "Street is required")
@Size(max = 80, message = "Street is too long")
private String street = "";

@NotBlank(message = "City is required")
@Size(max = 60, message = "City is too long")
private String city = "";

@NotBlank(message = "Postal code is required")
@Size(max = 12, message = "Postal code is too long")
private String postalCode = "";

@NotBlank(message = "Country is required")
private String country = "";

public String getStreet() {
return street;
}

public void setStreet(String street) {
this.street = street;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public String getPostalCode() {
return postalCode;
}

public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}

public String getCountry() {
return country;
}

public void setCountry(String country) {
this.country = country;
}

public String oneLine() {
return street + ", " + city + " " + postalCode + ", " + country;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.webforj.nestedbeans.domain;

import jakarta.persistence.Embeddable;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

@Embeddable
public class EmergencyContact {

@NotBlank(message = "Contact name is required")
@Size(max = 80, message = "Name is too long")
private String contactName = "";

@NotBlank(message = "Relationship is required")
private String relationship = "";

@NotBlank(message = "Phone is required")
@Pattern(regexp = "^[+()\\d\\s-]{7,20}$", message = "Enter a valid phone number")
private String phone = "";

public String getContactName() {
return contactName;
}

public void setContactName(String contactName) {
this.contactName = contactName;
}

public String getRelationship() {
return relationship;
}

public void setRelationship(String relationship) {
this.relationship = relationship;
}

public String getPhone() {
return phone;
}

public void setPhone(String phone) {
this.phone = phone;
}
}
Loading