From 26c75fbb3a95f7441208aa5e883d53d3fd3a1732 Mon Sep 17 00:00:00 2001 From: Eric Handtke Date: Wed, 27 Aug 2025 16:59:08 +0200 Subject: [PATCH 1/3] Updated tutorial docs for spring integration. --- .../tutorial/creating-a-basic-app.md | 211 ++++++------------ docs/docs/introduction/tutorial/overview.md | 16 +- .../introduction/tutorial/project-setup.md | 10 +- .../scaling-with-routing-and-composites.md | 114 ++++++++-- .../tutorial/validating-and-binding-data.md | 89 +++----- .../tutorial/working-with-data.md | 181 ++++++--------- 6 files changed, 285 insertions(+), 336 deletions(-) diff --git a/docs/docs/introduction/tutorial/creating-a-basic-app.md b/docs/docs/introduction/tutorial/creating-a-basic-app.md index 8e2c19675..be926e1e6 100644 --- a/docs/docs/introduction/tutorial/creating-a-basic-app.md +++ b/docs/docs/introduction/tutorial/creating-a-basic-app.md @@ -3,185 +3,118 @@ title: Creating a Basic App sidebar_position: 2 --- -This first step lays the foundation for the customer management app by creating a simple, interactive interface. This demonstrates how to set up a basic webforJ app, with a single button that opens a dialog when clicked. It’s a straightforward implementation that introduces key components and gives you a feel for how webforJ works. +This first step lays the foundation for your customer management app by creating a simple, interactive interface using webforJ with Spring Boot. You’ll set up a minimal Spring Boot project, define your main app class, and build a UI with a button and dialog—demonstrating the basics of webforJ’s component model and event handling. -This step leverages the base app class provided by webforJ to define the structure and behavior of the app. Following through to later steps will transition to a more advanced setup using routing to manage multiple screens, introduced in [Scaling with Routing and Composites](./scaling-with-routing-and-composites). +**Note:** this step uses a single `Application` class that directly hosts the UI content. Routing and separate view classes will be introduced in later steps. -By the end of this step, you’ll have a functioning app that demonstrates basic interaction with components and event handling in webforJ. To run the app: +By the end of this step, you’ll have a running app that demonstrates basic interaction and is ready for further extension. -- Go to the `1-creating-a-basic-app` directory -- Run the `mvn jetty:run` command - -
- -
+--- -## Creating a webforJ app {#creating-a-webforj-app} +## Prerequisites -In webforJ, an `App` represents the central hub for defining and managing your project. Every webforJ app starts by creating one class that extends the foundational `App` class, which serves as the core framework to: +- Java 17 or higher +- Maven +- A Java IDE (e.g., IntelliJ IDEA, Eclipse, VSCode) +- Web browser -- Manage the app lifecycle, including initialization and termination. -- Handle routing and navigation if enabled. -- Define the app’s theme, locale, and other overall configurations. -- Provide essential utilities for interacting with the environment and components. +--- -### Extending the `App` class {#extending-the-app-class} +## 1. Project setup -For this step, a class called `DemoApplication.java` is created, and extends the `App` class. +You can create your project using [startforJ](https://docs.webforj.com/startforj) (choose the “webforJ + Spring Boot” flavor) or with the Maven archetype: -```java title="DemoApplication.java" -public class DemoApplication extends App { - @Override - public void run() { - // Core app logic will go here - } -} +```bash +mvn -B archetype:generate \ + -DarchetypeGroupId=com.webforj \ + -DarchetypeArtifactId=webforj-archetype-hello-world \ + -DarchetypeVersion=LATEST \ + -DgroupId=org.example \ + -DartifactId=my-app \ + -Dversion=1.0-SNAPSHOT \ + -Dflavor=webforj-spring ``` -:::tip Key Configuration Properties - -In this demo app, the `webforj.conf` file is configured with the following two essential properties: - -- **`webforj.entry`**: Specifies the fully qualified name of the class extending `App` that acts as the main entry point for your project. For this tutorial, set it to `com.webforj.demos.DemoApplication` to avoid ambiguity during initialization. - ```hocon - webforj.entry = com.webforj.demos.DemoApplication - ``` -- **`webforj.debug`**: Enables debug mode for detailed logs and error visibility during development. Make sure this is set to `true` while working on this tutorial: - ```hocon - webforj.debug = true - ``` - -For more details on additional configuration options, see the [Configuration Guide](../../configuration/overview). -::: +--- -### Overriding the `run()` method {#overriding-the-run-method} +## 2. Main app class -After ensuring correct configuration for the project, the `run()` method in your `App` class is overridden. +Create a class called `Application.java` that extends `App` and is annotated for Spring Boot and webforJ: -The `run()` method is the core of your app in webforJ. It defines what happens after the app is initialized and is the main entry point for your app's features. By overriding the `run()` method, you can implement the logic that creates and manages your app's user interface and behavior. +```java title="Application.java" +package com.webforj.demos; -:::tip Using routing -When implementing routing within an app, overriding the `run()` method is unnecessary, as the framework automatically handles the initialization of routes and the creation of the initial `Frame`. The `run()` method is invoked after the base route is resolved, ensuring that the app's navigation system is fully initialized before any logic is executed. This tutorial will go further into depth on implementing routing in [step 3](scaling-with-routing-and-composites). More information is also available in the [Routing Article](../../routing/overview). -::: +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import com.webforj.App; +import com.webforj.annotation.StyleSheet; +import com.webforj.annotation.AppTheme; +import com.webforj.annotation.AppProfile; -```java title="DemoApplication.java" -public class DemoApplication extends App { - @Override - public void run() throws WebforjException { - // App logic +@SpringBootApplication +@StyleSheet("ws://app.css") +@AppTheme("system") +@AppProfile(name = "DemoApplication", shortName = "DemoApplication") +public class Application extends App { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); } -} -``` - -## Adding components {#adding-components} - -In webforJ, components are the building blocks of your app’s user interface. These components represent discrete pieces of your app's UI, such as buttons, text fields, dialogs, or tables. -You can think of a UI as a tree of components, with a `Frame` serving as the root. Each component added to the `Frame` becomes a branch or leaf in this tree, contributing to the overall structure and behavior of your app. - -:::tip Component catalog -See [this page](../../components/overview) for a list of the various components available in webforJ. -::: - -### App `Frame` {#app-frame} - -The `Frame` class in webforJ represents a non-nestable, top-level window in your app. A `Frame` typically acts as the main containers for UI components, making it an essential building block for constructing the user interface. Every app starts with at least one `Frame`, and you can add components such as buttons, dialogs, or forms to these frames. - -A `Frame` within the `run()` method is created in this step - later on, components will be added here. - -```java title="DemoApplication.java" -public class DemoApplication extends App { @Override - public void run() throws WebforjException { - Frame mainFrame = new Frame(); + public void run() { + // UI setup goes here } } ``` -### Server and client side components {#server-and-client-side-components} - -Each server-side component in webforJ has a matching client-side web component. Server-side components handle logic and backend interactions, while client-side components like `dwc-button` and `dwc-dialog` manage frontend rendering and styling. - -:::tip Composite components +--- -Alongside the core components provided by webforJ, you can design custom composite components by grouping multiple elements into a single reusable unit. This concept will be covered in this step of the tutorial. More information is available in the [Composite Article](../../building-ui/composite-components) -::: +## 3. Adding components -Components need to be added to a container class that implements the HasComponents interface. The `Frame` is one such class - for this step, add a `Paragraph` and a `Button` to the `Frame`, which will render in the UI in the browser: +Inside the `run()` method, set up your main UI. For example, add a `Frame`, a `Paragraph`, and a `Button`: -```java title="DemoApplication.java" -public class DemoApplication extends App { +```java +@Override +public void run() { + Frame mainFrame = new Frame(); Paragraph demo = new Paragraph("Demo Application!"); Button btn = new Button("Info"); + mainFrame.addClassName("mainFrame"); - @Override - public void run() throws WebforjException { - Frame mainFrame = new Frame(); - btn.setTheme(ButtonTheme.PRIMARY) - .addClickListener(e -> showMessageDialog("This is a demo!", "Info")); - mainFrame.add(demo, btn); - } + btn.setTheme(ButtonTheme.PRIMARY) + .addClickListener(e -> OptionDialog.showMessageDialog("This is a demo!", "Info")); + mainFrame.add(demo, btn); } ``` -Running this should give you a simple styled button enabling a message popping up saying "This is a demo!" - -## Styling with CSS {#styling-with-css} - -Styling in webforJ gives you complete flexibility to design your app’s appearance. While the framework supports a cohesive design and style out of the box, it doesn't enforce a specific styling approach, allowing you to apply custom styles that align with your app’s requirements. - -With webforJ, you can dynamically apply class names to components for conditional or interactive styling, use CSS for a consistent and scalable design system, and inject entire inline or external stylesheets. +--- -### Adding CSS classes to components {#adding-css-classes-to-components} +## 4. Configuration -You can dynamically add or remove class names to components using the `addClassName()` and `removeClassName()` methods. These methods allow you to control the component’s styles based on your app's logic. Add the `mainFrame` class name to the `Frame` created in the previous steps by including the following code in the `run()` method: +- `src/main/resources/application.properties`: + ``` + spring.application.name=DemoApplication + server.port=8080 + webforj.entry = com.webforj.demos.Application + webforj.debug=true + ``` -```java -mainFrame.addClassName("mainFrame"); -``` +- Place your CSS in `src/main/resources/static/app.css` and reference it with `@StyleSheet("ws://app.css")`. -### Attaching CSS files {#attaching-css-files} +--- -To style your app, you can include CSS files in your project either by using asset annotations or by utilizing the webforJ asset API at runtime. [See this article](../../managing-resources/importing-assets) for more information. +## 5. Running the app -For instance, The @StyleSheet annotation is used to include styles from the resources/static directory. It automatically generates a URL for the specified file and injects it into the DOM, ensuring the styles are applied to your app. Note that files outside the static directory aren't accessible. +From your project directory, run: -```java title="DemoApplication.java" -@StyleSheet("ws://styles/library.css") -public class DemoApplication extends App { - @Override - public void run() { - // App logic here - } -} -``` -:::tip Web server URLs -To ensure static files are accessible, they should be placed in the resources/static folder. To include a static file, you can construct its URL using the web server protocol. -::: - -### Sample CSS code {#sample-css-code} - -A CSS file is used in your project at `resources > static > css > demoApplication.css`, and the following CSS is used to apply some basic styling to the app. - -```css -.mainFrame { - display: inline-grid; - gap: 20px; - margin: 20px; - padding: 20px; - border: 1px dashed; - border-radius: 10px; -} +```bash +mvn spring-boot:run ``` -Once this is done, the following annotation should be added to your `App` class: +Then open [http://localhost:8080](http://localhost:8080) in your browser. -```java title="DemoApplication.java" -@StyleSheet("ws://css/demoApplication.css") -@AppTitle("Demo Step 1") -public class DemoApplication extends App { -``` +--- + +## Next steps -The CSS styles are applied to the main `Frame` and provide structure by arranging components with a [grid layout](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_grid_layout), and adding margin, padding, and border styles to make the UI visually organized. +You now have a working Spring Boot + webforJ app with a simple UI. The next steps will introduce routing, data binding, and more advanced features. diff --git a/docs/docs/introduction/tutorial/overview.md b/docs/docs/introduction/tutorial/overview.md index f6d17a26a..e96523f93 100644 --- a/docs/docs/introduction/tutorial/overview.md +++ b/docs/docs/introduction/tutorial/overview.md @@ -3,22 +3,24 @@ title: Overview hide_giscus_comments: true --- -This tutorial is designed to guide you step by step through the process of creating the app. This app, designed to manage customer information, demonstrates how to use webforJ to build a functional and user-friendly interface with features for viewing, adding, and editing customer data. Each section will build upon the last, but feel free to skip ahead as needed. -Each step in the tutorial will result in a program that compiles into a WAR file, which can be deployed to any Java web app server. For this tutorial, the Maven Jetty plugin will be used to deploy the app locally. This lightweight setup ensures the app can quickly run, and that changes will be seen in real time during development. +This tutorial guides you step by step through building a customer management app using webforJ with Spring Boot. The app demonstrates how to use webforJ to create a modern, user-friendly interface for viewing, adding, and editing customer data. Each section builds on the previous, but you can skip ahead as needed. + +Each step in the tutorial results in a runnable Spring Boot app (JAR), which you can launch locally using Maven. With this setup, you get a fast development cycle and a production-ready deployment model, using Spring Boot’s embedded server. ## Tutorial app features {#tutorial-app-features} - Working with data in a table. - Using the [`ObjectTable`](https://javadoc.io/doc/com.webforj/webforj-foundation/latest/com/webforj/environment/ObjectTable.html) and asset management. - [Routing](../../routing/overview) and [navigation](../../routing/route-navigation) - - [Data Bindings](../../data-binding/overview) and [validation](../../data-binding/validation/overview) + - [Data binding](../../data-binding/overview) and [validation](../../data-binding/validation/overview) ## Prerequisites {#prerequisites} -To get the most out of this tutorial, it’s assumed that you have a basic understanding of Java programming and are familiar with tools like Maven. If you’re new to webforJ, don’t worry - the framework’s fundamentals will be covered along the way. -The following tools/resources should be present on your development machine +To get the most out of this tutorial, you should have a basic understanding of Java and Maven. No prior Spring Boot experience is required—key concepts will be introduced as needed. + +The following tools/resources should be present on your development machine: - Java 17 or higher @@ -28,7 +30,7 @@ The following tools/resources should be present on your development machine - Git (recommended but not required) -:::tip webforJ Prerequisites +:::tip webforJ prerequisites See [this article](../prerequisites) for a more detailed overview of the required tools. ::: @@ -37,7 +39,7 @@ See [this article](../prerequisites) for a more detailed overview of the require The tutorial is broken into the following sections. Proceed sequentially for a comprehensive walkthrough, or skip ahead for specific information. :::tip Project setup -For those looking to skip ahead to specific topics, it's recommended to first read the Project Setup section before moving ahead. +Before you continue, complete the Project setup section to prepare your Spring Boot + webforJ environment for the tutorial steps. ::: \ No newline at end of file diff --git a/docs/docs/introduction/tutorial/project-setup.md b/docs/docs/introduction/tutorial/project-setup.md index 9b15bfef5..16465b62b 100644 --- a/docs/docs/introduction/tutorial/project-setup.md +++ b/docs/docs/introduction/tutorial/project-setup.md @@ -1,5 +1,5 @@ --- -title: Project Setup +title: Project setup sidebar_position: 1 --- @@ -47,12 +47,14 @@ To see the app in action at any stage: 1) Navigate to the directory for the desired step. This should be the top level directory for that step, containing the `pom.xml` -2) Use the Maven Jetty plugin to deploy the app locally by running: + +2. Use Maven to run the Spring Boot app locally by running: ```bash -mvn jetty:run +mvn spring-boot:run ``` -3) Open your browser and navigate to http://localhost:8080 to view the app. + +3. Open your browser and go to http://localhost:8080 to view the app. Repeat this process for each step as you follow along with the tutorial, allowing you to explore the app’s features as they're added. \ No newline at end of file diff --git a/docs/docs/introduction/tutorial/scaling-with-routing-and-composites.md b/docs/docs/introduction/tutorial/scaling-with-routing-and-composites.md index 171966b79..1fce13af7 100644 --- a/docs/docs/introduction/tutorial/scaling-with-routing-and-composites.md +++ b/docs/docs/introduction/tutorial/scaling-with-routing-and-composites.md @@ -1,14 +1,15 @@ --- -title: Scaling with Routing and Composites +title: Scaling with routing and composites sidebar_position: 4 --- -This step focuses on implementing routing to enhance the scalability and organization of the app structure. To achieve this, the app will be updated to handle multiple views, allowing navigation between different functionalities such as editing and creating customer entries. It will outline creating views for these functionalities, using components like `Composite` to build modular and reusable layouts. -The app created in the [previous step](./working-with-data) will have a routing setup that supports multiple views, enabling users to manage customer data more effectively while maintaining a clean and scalable codebase. To run the app: +This step introduces routing so the app can display multiple views and support navigation between features such as editing and creating customer entries. You’ll create views for these features using `Composite` components for modular, reusable layouts. + +The app from the [previous step](./working-with-data) is now structured for routing and navigation, using Spring Boot as the runtime. To run the app: - Go to the `3-scaling-with-routing-and-composites` directory -- Run the `mvn jetty:run` command +- Run the `mvn spring-boot:run` command
-## Routing {#routing} -[Routing](../../routing/overview) is the mechanism that allows your app to manage navigation between different views or pages. Instead of keeping all logic and behavior in a single location, routing enables you to break your app into smaller, focused component. +## Routing + +[Routing](../../routing/overview) allows your app to manage navigation between different views or pages. Instead of keeping all logic in a single location, routing lets you break your app into smaller, focused components. + +With Spring Boot, routing in webforJ works the same as in standard webforJ apps, but you can inject Spring beans (like services) into your views. + +### Enabling routing in the app class + +To enable routing, annotate your main app class with `@Routify` and specify the package containing your views: + +```java title="Application.java" +@SpringBootApplication +@StyleSheet("ws://css/app.css") +@AppTheme("system") +@Routify(packages = "com.webforj.demos.views") +@AppProfile(name = "DemoApplication", shortName = "DemoApplication") +public class Application extends App { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} +``` + +### Creating views and defining routes + +Each view is a separate class, typically extending `Composite
`. Use the `@Route` annotation to assign a URL path, and `@FrameTitle` for the browser title. + +**DemoView** (table and navigation): -At its core, routing connects specific URLs to the views or components that handle those URLs. When a user interacts with your app—such as clicking a button or entering a URL directly in their browser—the router resolves the URL to the appropriate view, initializes it, and displays it on the screen. This approach makes it easy to manage navigation and maintain the app's state. +```java title="DemoView.java" +@Route("/") +@FrameTitle("Demo") +public class DemoView extends Composite
{ + private final CustomerService customerService; + // ... table, button, and layout setup ... + public DemoView(CustomerService customerService) { + this.customerService = customerService; + // ... UI setup ... + } + // ... navigation logic ... +} +``` -This step focuses on changing the `App` class, creating files for the views, and configuring routes to enable smooth navigation between different parts of your app. +**FormView** (add/edit customer): -Instead of placing all logic within the `run()` method of `App`, views like `DemoView` and `FormView` are implemented as separate classes. This approach more closely aligns with standard Java practices. +```java title="FormView.java" +@StyleSheet("ws://css/views/form.css") +@Route("customer/:id?") +@FrameTitle("Customer Form") +public class FormView extends Composite
implements DidEnterObserver { + private final CustomerService customerService; + // ... form fields and layout ... + public FormView(CustomerService customerService) { + this.customerService = customerService; + // ... UI setup ... + } + @Override + public void onDidEnter(DidEnterEvent event, ParametersBag parameters) { + // ... load data for edit/add ... + } +} +``` + +- The `@Route("/")` on `DemoView` makes it the default landing page. +- The `@Route("customer/:id?")` on `FormView` allows both add and edit modes, depending on the presence of an `id` parameter. + +### Navigation and event handling + +- Use `Router.getCurrent().navigate(FormView.class)` to navigate to the form. +- Use `Router.getCurrent().navigate(FormView.class, ParametersBag.of("id=" + e.getItemKey()))` to navigate with a specific customer ID for editing. + +### Dependency injection + +With Spring Boot, you can inject services (like `CustomerService`) directly into your views via constructor injection. This allows you to use Spring-managed beans for data access, business logic, etc. + +### Running the app + +From the step directory, run: + +```bash +mvn spring-boot:run +``` + +Then open [http://localhost:8080](http://localhost:8080) in your browser. + +--- -- **DemoView**: Handles displaying the table and navigating to `FormView`. -- **FormView**: Manages adding and editing customer data. +With routing and view separation in place, your app is now ready for further scaling, including advanced data binding and validation in the next step. ### Changing the `App` class {#changing-the-app-class} @@ -44,11 +122,11 @@ public class DemoApplication extends App { ### Creating files for the views and configuring routes {#creating-files-for-the-views-and-configuring-routes} -Once routing has been enabled, separate Java files for each view the app will contain are created, in this case, `DemoView.java` and `FormView.java`. Unique routes are assigned to these views using the `@Route` annotation. This ensures that each view is accessible through a specific URL. +Once routing is enabled, create separate Java files for each view the app will contain, in this case, `DemoView.java` and `FormView.java`. Assign unique routes to these views using the `@Route` annotation. Each view is then accessible through a specific URL. When the `@Route` annotation associated with a class with one of these suffixes has no value, webforJ automatically assigns the class's name without the suffix as the route. For example, `DemoView` will map the route `/demo` by default. Since in this case `DemoView` is supposed to be the default route tho you will assign it a route. -The `/` route serves as the default entry point for your app. Assigning this route to a view ensures that it's the first page users see when accessing the app. In most cases, a dashboard or summary view is assigned to `/`. +The `/` route is the default entry point for your app. Assign this route to a view to make it the first page users see when accessing the app. In most cases, a dashboard or summary view is assigned to `/`. ```java title="DemoView.java" {1} @Route("/") @@ -81,7 +159,7 @@ More information regarding the different ways to implement those route patterns ## Using `Composite` components to display pages {#using-composite-components-to-display-pages} -Composite components in webforJ, such as `Composite
`, allow you to encapsulate UI logic and structure within a reusable container. By extending `Composite`, you limit the methods and data exposed to the rest of the app, ensuring cleaner code and better encapsulation. +Composite components in webforJ, such as `Composite
`, encapsulate UI logic and structure within a reusable container. By extending `Composite`, you limit the methods and data exposed to the rest of the app, which helps keep your code organized and maintainable. For example, `DemoView` extends `Composite
` instead of directly extending `Div`: @@ -128,7 +206,7 @@ In addition to navigation via button click, many apps also allow for navigation Once the details have been edited on the appropriate screen, the changes are saved, and the `Table` is updated to display the changed data from the selected item. -To facilitate this navigation, item clicks in the table are handled by the `TableItemClickEvent` listener. The event contains the `id` of the clicked customer, which it passes to the `FormView` by utilizing the `navigate()` method with a `ParametersBag`: +To enable this navigation, item clicks in the table are handled by the `TableItemClickEvent` listener. The event contains the `id` of the clicked customer, which it passes to the `FormView` by using the `navigate()` method with a `ParametersBag`: ```java title="DemoView.java" private void editCustomer(TableItemClickEvent e) { @@ -170,7 +248,7 @@ The `onDidEnter` method in `FormView` checks for the presence of an `id` paramet When finished editing the data, it's necessary to submit it to the service handling the repository. Therefore the `Service` class that has been already set up in the previous step of this tutorial -now needs to be enhanced with additional methods, allowing users to add and edit customers. +now needs additional methods so users can add and edit customers. The snippet below shows how to accomplish this: @@ -187,7 +265,7 @@ public void editCustomer(Customer editedCustomer) { ### Using `commit()` {#using-commit} -The `commit()` method in the `Repository` class keeps the app’s data and UI in sync. It provides a mechanism to refresh the data stored in the `Repository`, ensuring the latest state is reflected in the app. +The `commit()` method in the `Repository` class keeps the app’s data and UI in sync. It provides a way to refresh the data stored in the `Repository` so the app always displays the latest state. This method can be used in two ways: @@ -195,7 +273,7 @@ This method can be used in two ways: Calling `commit()` without arguments reloads all entities from the repository's underlying data source, such as a database or a service class. 2) **Refreshing a single entity:** - Calling `commit(T entity)` reloads a specific entity, ensuring its state matches the latest data source changes. + Calling `commit(T entity)` reloads a specific entity so its state matches the latest data source changes. Call `commit()` when data in the `Repository` changes, such as after adding or modifying entities in the data source. diff --git a/docs/docs/introduction/tutorial/validating-and-binding-data.md b/docs/docs/introduction/tutorial/validating-and-binding-data.md index 8473b1019..c4fe58938 100644 --- a/docs/docs/introduction/tutorial/validating-and-binding-data.md +++ b/docs/docs/introduction/tutorial/validating-and-binding-data.md @@ -1,17 +1,16 @@ --- -title: Validating and Binding Data +title: Validating and binding data sidebar_position: 5 pagination_next: null --- -Data binding is a mechanism that connects the UI components of your app directly with the underlying data model, enabling automatic synchronization of values between the two. This eliminates the need for repetitive getter and setter calls, reducing development time and improving code reliability. -Validation, in this context, ensures that the data entered into the form adheres to predefined rules, such as being non-empty or following a specific format. By combining data binding with validation, you can streamline the user experience while maintaining data integrity without writing extensive manual checks. +Data binding connects UI components directly with your data model, enabling automatic synchronization of values. This reduces boilerplate and improves reliability. Validation checks that form data follows rules such as being non-empty or matching a pattern. With webforJ and Spring Boot, you can use Jakarta validation annotations and webforJ’s binding system for a user-friendly experience. -For more information on data binding reference [this article.](../../data-binding/overview) To run the app: +To run the app: - Go to the `4-validating-and-binding-data` directory -- Run the `mvn jetty:run` command +- Run `mvn spring-boot:run`
-### Binding the fields {#binding-the-fields} -The data binding setup begins with initializing a `BindingContext` for the `Customer` model. The `BindingContext` links the model properties to the form fields, enabling automatic data synchronization. This is set up in the `FormView` constructor. +## Binding the fields + +The data binding setup begins with initializing a `BindingContext` for the `Customer` model. The `BindingContext` links model properties to form fields, enabling automatic data sync. This is set up in the `FormView` constructor: ```java title="FormView.java" -BindingContext context; context = BindingContext.of(this, Customer.class, true); +context.onValidate(e -> submit.setEnabled(e.isValid())); ``` -`BindingContext.of(this, Customer.class, true)` initializes the binding context for the `Customer` class. The third parameter, `true`, enables [jakarta validation](https://beanvalidation.org/). +The third parameter (`true`) enables Jakarta validation. :::info -This implementation uses auto-binding as described in the [Data Binding Article](../../data-binding/automatic-binding). This works if the fields in the data model `Customer` are named the same as the corresponding fields in the `FormView`. - -Should the fields not be named the same you can add the `UseProperty` annotation in the form over the field you want to bind so they know which data fields to refer to. +This uses auto-binding as described in the [Data Binding Article](../../data-binding/automatic-binding). Field names in the data model and form must match, or you can use the `UseProperty` annotation to map them. ::: -### Data binding with `onDidEnter()` {#data-binding-with-ondidenter} -The `onDidEnter` method leverages the data binding setup to streamline the process of populating the form fields. Instead of manually setting values for each field, the data is now synchronized automatically with the `BindingContext`. +## Data binding with `onDidEnter()` + +The `onDidEnter` method uses the binding context to populate the form fields. Instead of setting each value manually, the context synchronizes the UI with the model: -```java {7} +```java title="FormView.java" @Override - public void onDidEnter(DidEnterEvent event, ParametersBag parameters) { - parameters.get("id").ifPresent(id -> { - customer = Service.getCurrent().getCustomerByKey(UUID.fromString(id)); - customerId = id; - }); - context.read(customer); - } +public void onDidEnter(DidEnterEvent event, ParametersBag parameters) { + parameters.get("id").ifPresent(id -> { + customer = customerService.getCustomerByKey(Long.parseLong(id)); + customerId = Long.parseLong(id); + }); + context.read(customer); +} ``` -The `context.read` method in webforJ's data binding system synchronizes the fields of a UI component with the values from a data model. It's used in this case to populate form fields with data from an existing model, ensuring the UI reflects the current state of the data. - -## Validating data {#validating-data} -Validation ensures that the data entered into the form adheres to specified rules, improving data quality and preventing invalid submissions. With data binding, validation no longer needs to be manually implemented but instead simply configured, allowing real-time feedback on user inputs. +## Validating data -### Defining validation rules {#defining-validation-rules} +Validation is handled by Jakarta annotations in the `Customer` entity: -Using [Jakarta](https://beanvalidation.org) and regular expressions, you can enforce a multitude of rules on a field. Often used examples would be ensuring the field -isn't empty or null, or follows a certain pattern. -Through annotations in the customer class you can give jakarta validation parameters to the field. - -:::info -More details regarding the setup of the validation is available [here](../../data-binding/validation/jakarta-validation.md#installation). -::: - -```java - @NotEmpty(message = "Name cannot be empty") - @Pattern(regexp = "[a-zA-Z]*", message = "Invalid characters") - private String firstName = ""; +```java title="Customer.java" +@NotEmpty(message = "Customer name is required") +@Pattern(regexp = "[a-zA-Z]*", message = "Invalid characters") +@Column(name = "first_name") +private String firstName = ""; ``` -The `onValidate` method is then added to control the `Submit` button's state based on the validity of the form fields. This ensures that only valid data can be submitted. +The binding context disables the submit button if the form is invalid: ```java title="FormView.java" context.onValidate(e -> submit.setEnabled(e.isValid())); ``` -`e.isValid()` returns true if all fields are valid, and false if not. This means that the `Submit` button is enabled as long as all fields are valid. Otherwise, it remains turned off, preventing submission until corrections are made. - -### Adding and editing entries with validation {#adding-and-editing-entries-with-validation} - -The `submitCustomer()` method now validates data using the `BindingContext` before performing add or edit operations. This approach eliminates the need for manual validation checks, leveraging the context's built-in mechanisms to ensure that only valid data is processed. - -- **Add Mode**: If no `id` is provided, the form is in add mode. The validated data is written to the `Customer` model and added to the repository via `Service.getCurrent().addCustomer(customer)`. -- **Edit Mode**: If an `id` is present, the method retrieves the corresponding customer data, updates it with validated inputs, and commits the changes to the repository. -Calling `context.write(customer)` will return an instance of a `ValidationResult`. This class indicates whether or not the validation was successful, and stores any messages associated with this result. +## Adding and editing entries with validation -This code ensures that all changes are validated and automatically applied to the model before being adding a new or editing an existing `Customer`. +The `submitCustomer()` method validates data using the binding context before add or edit operations. Only valid data is processed: ```java title="FormView.java" private void submitCustomer() { ValidationResult results = context.write(customer); if (results.isValid()) { - if (customerId.isEmpty()) { - Service.getCurrent().addCustomer(customer); + if (customerId.intValue() == 0) { + customerService.createCustomer(customer); + } else { + customerService.updateCustomer(customer); } Router.getCurrent().navigate(DemoView.class); } } ``` -By completing this step, the app now supports data binding and validation, ensuring that form inputs are synchronized with the model and adhere to predefined rules. +With these changes, the app now supports data binding and validation using Spring Boot and webforJ. Form inputs are synchronized with the model and checked against validation rules automatically. diff --git a/docs/docs/introduction/tutorial/working-with-data.md b/docs/docs/introduction/tutorial/working-with-data.md index 03b4a0402..336019543 100644 --- a/docs/docs/introduction/tutorial/working-with-data.md +++ b/docs/docs/introduction/tutorial/working-with-data.md @@ -1,16 +1,15 @@ --- -title: Working With Data +title: Working with data sidebar_position: 3 --- -This step focuses on adding data management and display capabilities to the demo app. To do this, dummy data about various `Customer` objects will be created, and the app will be updated to handle this data and display it in a [`Table`](../../components/table/overview) added to the previous app. -It will outline creating a `Customer` model class, and integrating it with a `Service` class to access and manage the necessary data using the implementation of a repository. Then, it will detail how to use the retrieved data to implement a `Table` component in the app, displaying customer information in an interactive and structured format. +This step adds data management and display capabilities to the demo app. You’ll define a `Customer` entity, use a Spring Data repository and service for data access, and display customer data in a [`Table`](../../components/table/overview) component. -By the end of this step, the app created in the [previous step](./creating-a-basic-app) will display a table with the created data that can then be expanded on in the following steps. To run the app: +By the end of this step, the app from the [previous step](./creating-a-basic-app) will display a table with customer data, ready for further extension. To run the app: - Go to the `2-working-with-data` directory -- Run `mvn jetty:run` +- Run `mvn spring-boot:run` @@ -22,171 +21,123 @@ By the end of this step, the app created in the [previous step](./creating-a-bas -## Creating a data model {#creating-a-data-model} -In order to create a `Table` that displays data in the main app, a Java bean class that can be used with the `Table` to display data needs to be created. +## Creating a data model -In this program, the `Customer` class in `src/main/java/com/webforj/demos/data/Customer.java` does this. This class serves as the core data model for the app, encapsulating customer-related attributes such as `firstName`, `lastName`, `company`, and `country`. This model will also contain a unique ID. +The `Customer` entity is a JPA model class, used with Spring Data and webforJ’s `Table`. It encapsulates customer attributes and provides a unique key for each row. ```java title="Customer.java" +@Entity +@Table(name = "customers") public class Customer implements HasEntityKey { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String firstName = ""; private String lastName = ""; private String company = ""; private Country country = Country.UNKNOWN; - private UUID uuid = UUID.randomUUID(); public enum Country { - - @SerializedName("Germany") - GERMANY, - - // Remaining countries + UNKNOWN, GERMANY, ENGLAND, ITALY, USA } - // Getters and Setters - - @Override - public Object getEntityKey() { - return uuid; - } + // Getters, setters, and getEntityKey() returning id } ``` -:::info Using `HasEntityKey` for Unique Identifiers - -Implementing the `HasEntityKey` interface is crucial for managing unique identifiers in models used with a `Table`. It ensures that each instance of the model has a unique key, allowing the `Table` to identify and manage rows effectively. -For this demo, the `getEntityKey()` method returns a UUID for each customer, ensuring unique identification. While UUIDs are used here for simplicity, in real-world applications, a database primary key is often a better choice for generating unique keys. - -If `HasEntityKey` isn't implemented, the `Table` will default to using the Java hash code as the key. Since hash codes aren't guaranteed to be unique, this can cause conflicts when managing rows in the `Table`. +:::info Using `HasEntityKey` for unique identifiers +Implementing `HasEntityKey` allows the `Table` to uniquely identify each row using the entity’s primary key (`id`). This is important for correct row management and updates. ::: With the `Customer` data model in place, the next step is to manage and organize these models within the app. -## Creating a `Service` class {#creating-a-service-class} - -Acting as a centralized data manager, the `Service` class not only loads `Customer` data but also provides an efficient interface for accessing and interacting with it. -The `Service.java` class is created in `src/main/java/com/webforj/demos/data`. Instead of manually passing data between components or classes, the `Service` acts as a shared resource, allowing interested parties to retrieve and interact with data easily. +## Creating a service and repository -In this demo, the `Service` class reads customer data from a JSON file located at `src/main/resources/data/customers.json`. The data is mapped onto `Customer` objects and stored in an `ArrayList`, which forms the foundation for the table's `Repository`. +Spring Boot integration lets you use Spring Data repositories and services for data access. The `CustomerService` class provides methods to create, update, delete, and query customers, and exposes a `SpringDataRepository` for use with webforJ’s `Table`. -In webforJ, the `Repository` class provides a structured way to manage and retrieve collections of entities. It acts as an interface between your app and its data, offering methods to query, count, and refresh data while maintaining a clean and consistent structure. It's used by the `Table` class to display the data stored within. +```java title="CustomerService.java" +@Service +@Transactional +public class CustomerService { -Although the `Repository` doesn’t include methods for updating or deleting entities, it serves as a structured wrapper around a collection of objects. This makes it ideal for providing organized, efficient data access. + @Autowired + private CustomerRepository repository; -```java -public class Service { - private List data = new ArrayList<>(); - private CollectionRepository repository; - - private Service() { - data = buildDemoList(); - repository = new CollectionRepository<>(data); + public Customer createCustomer(Customer customer) { + return repository.save(customer); } - //Remaining implementation -} -``` - -To populate the `Repository` with data, the `Service` class acts as the central manager, handling the loading and organization of assets in the app. Customer data is read from a JSON file and mapped to the `Customer` objects in the `Repository`. - -The `Assets` utility in webforJ makes it easy to load this data dynamically using context URLs To load assets and data in webforJ, the `Service` class uses context URLs with the `Assets` utility. For example, customer data can be loaded from the JSON file as follows: - -```java -String content = Assets.contentOf(Assets.resolveContextUrl("context://data/customers.json")); -``` - -:::tip Using the `ObjectTable` -The `Service` class uses the `ObjectTable` to manage instances dynamically, instead of relying on static fields. This approach addresses a key limitation when using servlets: static fields are tied to the server’s lifecycle and can lead to issues in environments with multiple requests or concurrent sessions. The `ObjectTable` is scoped to the user session, and using it ensures a singleton-like behavior without these limitations, enabling consistent and scalable data management. -::: - -```java title="Service.java" -public class Service { - - private List data = new ArrayList<>(); - private CollectionRepository repository; - - // Private constructor to enforce controlled instantiation - private Service() { - // implementation + public Customer updateCustomer(Customer customer) { + if (!repository.existsById(customer.getId())) { + throw new IllegalArgumentException("Customer not found with ID: " + customer.getEntityKey()); + } + return repository.save(customer); } - // Retrieves the current instance of Service or creates one if it doesn’t exist - public static Service getCurrent() { - // implementation + public void deleteCustomer(Long id) { + if (!repository.existsById(id)) { + throw new IllegalArgumentException("Customer not found with ID: " + id); + } + repository.deleteById(id); } - // Load customer data from the JSON file and map it to Customer objects - private List buildDemoList() { - // implementation + public long getTotalCustomersCount() { + return repository.count(); } - // Getter... + public SpringDataRepository getFilterableRepository() { + return new SpringDataRepository<>(repository); + } } ``` -## Creating and using a `Table` {#creating-and-using-a-table} - -Now that the data needed has been properly created via the `Customer` class, and can is returned as a `Repository` via the `Service` class, the final task in this step is to integrate the `Table` component into the app to display customer data. - -:::tip More about the `Table` -For a more detailed overview of the various features of behaviors of the `Table`, see [this article](../../components/table/overview). -::: - -The `Table` provides a dynamic and flexible way to display structured data in your app. It's designed to integrate with the `Repository` class, enabling features like data querying, pagination, and efficient updates. A `Table` is highly configurable, allowing you to define columns, control its appearance, and bind it to data repositories with minimal effort. - -### Implementing the `Table` in the app {#implementing-the-table-in-the-app} -Since the data for the `Table` is handled fully through the `Service` class, the main task in `DemoApplication.java` is configuring the `Table` and linking it to the `Repository` provided by the `Service`. +## Displaying data in a table -To configure the `Table`: +The main app class injects the `CustomerService` and configures a `Table` to display customer data. -- Set its width and height for layout purposes using the `setHeight()` and `setWidth()` methods. -- Define the columns, specifying their names and the methods to fetch the data for each. -- Assign the `Repository` to provide data dynamically. +```java title="Application.java" +@SpringBootApplication +@StyleSheet("ws://css/app.css") +@AppTheme("system") +@AppProfile(name = "DemoApplication", shortName = "DemoApplication") +public class Application extends App { + private final CustomerService customerService; -After doing this, the code will look similar to the following snippet: - -```java title="DemoApplication.java" -public class DemoApplication extends App { - // Other components from step one + public Application(CustomerService customerService) { + this.customerService = customerService; + } - // The Table component for displaying Customer data - Table table = new Table<>(); + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } @Override public void run() throws WebforjException { - // Previous implementation of step one - buildTable(); - mainFrame.add(demo, btn, table); - } + Frame mainFrame = new Frame(); + Paragraph demo = new Paragraph("Demo Application!"); + Button btn = new Button("Info"); + Table table = new Table<>(); - private void buildTable() { - // Set the table's height to 300 pixels - table.setHeight("300px"); - // Set the table's width to 1000 pixels + mainFrame.addClassName("mainFrame"); + table.setHeight("294px"); table.setWidth(1000); - // Add the various column titles and assign the appropriate getters table.addColumn("First Name", Customer::getFirstName); table.addColumn("Last Name", Customer::getLastName); table.addColumn("Company", Customer::getCompany); table.addColumn("Country", Customer::getCountry); - // Bind the Table to a Repository containing Customer data - // The Repository is retrieved through the Service class - table.setRepository(Service.getCurrent().getCustomers()); + table.setRepository(customerService.getFilterableRepository()); + btn.setTheme(ButtonTheme.PRIMARY).addClickListener(e -> OptionDialog.showMessageDialog("This is a demo!", "Info")); + mainFrame.add(demo, btn, table); } } ``` -With the completed changes to the app implemented, the following steps will happen when the app runs: - -1. The `Service` class retrieves `Customer` data from the JSON file and stores it in a `Repository`. -2. The `Table` integrates the `Repository` for data and populates its rows dynamically. - -With the `Table` now displaying `Customer` data, the next step will focus on creating a new screen to modify customer details and integrating routing into the app. -This will allow organization of the app’s logic more effectively by moving it out of the main `App` class, and into constituent screens access via routes. +With these changes, the app loads customer data from the database (or in-memory store), and displays it in a table. The next step will introduce routing and multiple views for editing and adding customers. From 4b0c06ce4efa386abb2b7ef7521e72d3a2f57d64 Mon Sep 17 00:00:00 2001 From: Eric Handtke Date: Wed, 3 Sep 2025 11:23:12 +0200 Subject: [PATCH 2/3] Addressed feedback. --- .../tutorial/creating-a-basic-app.md | 13 +- docs/docs/introduction/tutorial/overview.md | 6 +- .../introduction/tutorial/project-setup.md | 2 +- .../scaling-with-routing-and-composites.md | 205 ++---------------- .../tutorial/validating-and-binding-data.md | 24 +- .../tutorial/working-with-data.md | 14 ++ 6 files changed, 70 insertions(+), 194 deletions(-) diff --git a/docs/docs/introduction/tutorial/creating-a-basic-app.md b/docs/docs/introduction/tutorial/creating-a-basic-app.md index be926e1e6..792886f5f 100644 --- a/docs/docs/introduction/tutorial/creating-a-basic-app.md +++ b/docs/docs/introduction/tutorial/creating-a-basic-app.md @@ -3,7 +3,8 @@ title: Creating a Basic App sidebar_position: 2 --- -This first step lays the foundation for your customer management app by creating a simple, interactive interface using webforJ with Spring Boot. You’ll set up a minimal Spring Boot project, define your main app class, and build a UI with a button and dialog—demonstrating the basics of webforJ’s component model and event handling. + +This first step lays the foundation for your customer management app by creating a simple, interactive interface using webforJ with Spring Boot. You’ll set up a minimal Spring Boot project, define your main app class, and build a UI with a button and dialog. This straightforward implementation introduces key components and gives you a feel for how webforJ works. **Note:** this step uses a single `Application` class that directly hosts the UI content. Routing and separate view classes will be introduced in later steps. @@ -13,7 +14,7 @@ By the end of this step, you’ll have a running app that demonstrates basic int ## Prerequisites -- Java 17 or higher +- Java 17 or 21 - Maven - A Java IDE (e.g., IntelliJ IDEA, Eclipse, VSCode) - Web browser @@ -41,6 +42,10 @@ mvn -B archetype:generate \ Create a class called `Application.java` that extends `App` and is annotated for Spring Boot and webforJ: +The `@SpringBootApplication` annotation marks this class as the main entry point for a Spring Boot app. It enables auto-configuration, component scanning, and allows Spring Boot to start your app with an embedded server. This means you don't need extra configuration to get your app running. Spring Boot handles it for you. + +The `@StyleSheet` annotation loads the style sheet, in this case provided by the [webserver-protocol](../../managing-resources/assets-protocols#the-webserver-protocol). + ```java title="Application.java" package com.webforj.demos; @@ -67,6 +72,8 @@ public class Application extends App { } ``` + + --- ## 3. Adding components @@ -99,6 +106,8 @@ public void run() { webforj.debug=true ``` +- Make sure the spring [dependencies](../../integrations/spring/spring-boot#step-2-add-spring-dependencies) are correctly configured in your POM, if not, add them. + - Place your CSS in `src/main/resources/static/app.css` and reference it with `@StyleSheet("ws://app.css")`. --- diff --git a/docs/docs/introduction/tutorial/overview.md b/docs/docs/introduction/tutorial/overview.md index e96523f93..8ce9e084b 100644 --- a/docs/docs/introduction/tutorial/overview.md +++ b/docs/docs/introduction/tutorial/overview.md @@ -11,19 +11,19 @@ Each step in the tutorial results in a runnable Spring Boot app (JAR), which you ## Tutorial app features {#tutorial-app-features} - Working with data in a table. - - Using the [`ObjectTable`](https://javadoc.io/doc/com.webforj/webforj-foundation/latest/com/webforj/environment/ObjectTable.html) and asset management. + - [Respository](../../advanced/repository/overview) and asset management. - [Routing](../../routing/overview) and [navigation](../../routing/route-navigation) - [Data binding](../../data-binding/overview) and [validation](../../data-binding/validation/overview) ## Prerequisites {#prerequisites} -To get the most out of this tutorial, you should have a basic understanding of Java and Maven. No prior Spring Boot experience is required—key concepts will be introduced as needed. +To get the most out of this tutorial, you should have a basic understanding of Java and Maven. No prior Spring Boot experience is required—key concepts will be introduced as needed. Should you be interested in additional spring resources you can find them [here](https://spring.io/learn); The following tools/resources should be present on your development machine: -- Java 17 or higher +- Java 17 or 21 - Maven - A Java IDE - A web browser diff --git a/docs/docs/introduction/tutorial/project-setup.md b/docs/docs/introduction/tutorial/project-setup.md index 16465b62b..95deacc72 100644 --- a/docs/docs/introduction/tutorial/project-setup.md +++ b/docs/docs/introduction/tutorial/project-setup.md @@ -45,7 +45,7 @@ webforj-demo-application To see the app in action at any stage: -1) Navigate to the directory for the desired step. This should be the top level directory for that step, containing the `pom.xml` +1. Navigate to the directory for the desired step. This should be the top level directory for that step, containing the `pom.xml` 2. Use Maven to run the Spring Boot app locally by running: diff --git a/docs/docs/introduction/tutorial/scaling-with-routing-and-composites.md b/docs/docs/introduction/tutorial/scaling-with-routing-and-composites.md index 1fce13af7..276653521 100644 --- a/docs/docs/introduction/tutorial/scaling-with-routing-and-composites.md +++ b/docs/docs/introduction/tutorial/scaling-with-routing-and-composites.md @@ -18,15 +18,14 @@ The app from the [previous step](./working-with-data) is now structured for rout
-## Routing -[Routing](../../routing/overview) allows your app to manage navigation between different views or pages. Instead of keeping all logic in a single location, routing lets you break your app into smaller, focused components. +## Routing and view structure -With Spring Boot, routing in webforJ works the same as in standard webforJ apps, but you can inject Spring beans (like services) into your views. +[Routing](../../routing/overview) in webforJ lets your app manage navigation between different views or pages, breaking your UI into focused, modular components. ### Enabling routing in the app class -To enable routing, annotate your main app class with `@Routify` and specify the package containing your views: +Annotate your main app class with `@Routify`, specifying the package containing your views: ```java title="Application.java" @SpringBootApplication @@ -43,7 +42,7 @@ public class Application extends App { ### Creating views and defining routes -Each view is a separate class, typically extending `Composite
`. Use the `@Route` annotation to assign a URL path, and `@FrameTitle` for the browser title. +Each view is a separate class, typically extending `Composite
`. Use the `@Route` annotation to assign a URL path, and `@FrameTitle` for the browser title. With Spring Boot, you can inject services (like `CustomerService`) directly into your views via constructor injection. **DemoView** (table and navigation): @@ -76,7 +75,8 @@ public class FormView extends Composite
implements DidEnterObserver { } @Override public void onDidEnter(DidEnterEvent event, ParametersBag parameters) { - // ... load data for edit/add ... + // Check for 'id' parameter to determine add or edit mode + // If present, load customer data and pre-fill form; otherwise, show blank form } } ``` @@ -86,148 +86,18 @@ public class FormView extends Composite
implements DidEnterObserver { ### Navigation and event handling -- Use `Router.getCurrent().navigate(FormView.class)` to navigate to the form. -- Use `Router.getCurrent().navigate(FormView.class, ParametersBag.of("id=" + e.getItemKey()))` to navigate with a specific customer ID for editing. +Use `Router.getCurrent().navigate(FormView.class)` to navigate to the form, or `Router.getCurrent().navigate(FormView.class, ParametersBag.of("id=" + e.getItemKey()))` to navigate with a specific customer ID for editing. -### Dependency injection - -With Spring Boot, you can inject services (like `CustomerService`) directly into your views via constructor injection. This allows you to use Spring-managed beans for data access, business logic, etc. - -### Running the app - -From the step directory, run: - -```bash -mvn spring-boot:run -``` - -Then open [http://localhost:8080](http://localhost:8080) in your browser. - ---- - -With routing and view separation in place, your app is now ready for further scaling, including advanced data binding and validation in the next step. - -### Changing the `App` class {#changing-the-app-class} - -To enable routing in your app, update the `App` class with the `@Routify` annotation. This tells webforJ to activate routing and scan specified packages for route-enabled views. - -```java title="DemoApplication.java" {1} -@Routify(packages = "com.webforj.demos.views", debug = true) -public class DemoApplication extends App { -} -``` - -- **`packages`**: Specifies which packages are scanned for views that define routes. -- **`debug`**: Enables debugging mode for easier troubleshooting during development. - -### Creating files for the views and configuring routes {#creating-files-for-the-views-and-configuring-routes} - -Once routing is enabled, create separate Java files for each view the app will contain, in this case, `DemoView.java` and `FormView.java`. Assign unique routes to these views using the `@Route` annotation. Each view is then accessible through a specific URL. - -When the `@Route` annotation associated with a class with one of these suffixes has no value, webforJ automatically assigns the class's name without the suffix as the route. For example, `DemoView` will map the route `/demo` by default. Since in this case `DemoView` is supposed to be the default route tho you will assign it a route. - -The `/` route is the default entry point for your app. Assign this route to a view to make it the first page users see when accessing the app. In most cases, a dashboard or summary view is assigned to `/`. - -```java title="DemoView.java" {1} -@Route("/") -@FrameTitle("Demo") -public class DemoView extends Composite
{ - // DemoView logic -} -``` - -:::info -More information regarding the different route types is available [here](../../routing/defining-routes). -::: - -For the `FormView` the route `customer/:id?` uses an optional parameter `id` to determine the mode of the `FormView`. - -- **Add Mode**: When `id` isn't provided, `FormView` initializes with a blank form for adding new customer data. -- **Edit Mode**: When `id` is provided, `FormView` fetches the corresponding customer’s data using `Service` and pre-fills the form, allowing edits to be made to the existing entry. - -```java title="FormView.java" {1} -@Route("customer/:id?") -@FrameTitle("Customer Form") -public class FormView extends Composite
implements DidEnterObserver { - // FormView logic -} -``` - -:::info -More information regarding the different ways to implement those route patterns is available [here](../../routing/route-patterns). -::: - -## Using `Composite` components to display pages {#using-composite-components-to-display-pages} - -Composite components in webforJ, such as `Composite
`, encapsulate UI logic and structure within a reusable container. By extending `Composite`, you limit the methods and data exposed to the rest of the app, which helps keep your code organized and maintainable. - -For example, `DemoView` extends `Composite
` instead of directly extending `Div`: - -```java title="DemoView.java" -public class DemoView extends Composite
{ - private Table table = new Table<>(); - private Button add = new Button("Add Customer", ButtonTheme.PRIMARY); - - public DemoView() { - setupLayout(); - } - - private void setupLayout() { - FlexLayout layout = FlexLayout.create(table, add) - .vertical().contentAlign().center().build().setPadding("var(--dwc-space-l)"); - getBoundComponent().add(layout); - } -} -``` - -## Connecting the routes {#connecting-the-routes} - -After configuring routing and setting up views, connect the views and data using event listeners and service methods. The first step is to add one or more -UI elements to navigate from one view to the other. - -### Button navigation {#button-navigation} - -The `Button` component triggers a navigation event to transition from one view to another using the `Router` class. For example: - -```java title="DemoView.java" -private Button add = new Button("Add Customer", ButtonTheme.PRIMARY, - e -> Router.getCurrent().navigate(FormView.class)); -``` - -:::info -The Router class uses the given class to resolve the route and build an URL to navigate to. All browser navigation is then handled so that history management -and view initialization is of no concern. -For more details on navigation, see the [Route Navigation Article](../../routing/route-navigation). -::: - -### Table editing {#table-editing} - -In addition to navigation via button click, many apps also allow for navigation to other parts of an app when a `Table` is double clicked. The following changes are made to allow users to double-click an item in the table to navigate to a form pre-filled with the item's details. - -Once the details have been edited on the appropriate screen, the changes are saved, and the `Table` is updated to display the changed data from the selected item. - -To enable this navigation, item clicks in the table are handled by the `TableItemClickEvent` listener. The event contains the `id` of the clicked customer, which it passes to the `FormView` by using the `navigate()` method with a `ParametersBag`: - -```java title="DemoView.java" -private void editCustomer(TableItemClickEvent e) { - Router.getCurrent().navigate(FormView.class, - ParametersBag.of("id=" + e.getItemKey())); -} -``` - -### Handling initialization with `onDidEnter` {#handling-initialization-with-ondidenter} - -The `onDidEnter` method in webforJ is part of the routing lifecycle and is triggered when a view becomes active. +### Lifecycle: Handling Initialization with `onDidEnter` When the `Router` navigates to a view, `onDidEnter` is triggered as part of the lifecycle to: - **Load Data**: Initialize or fetch data required for the view based on route parameters. - **Set Up the View**: Update UI elements dynamically based on the context. - **React to State Changes**: Perform actions that depend on the view being active, such as resetting forms or highlighting components. -The `onDidEnter` method in `FormView` checks for the presence of an `id` parameter in the route and adjusts the form's behavior accordingly: - -- **Edit Mode**: If an `id` is provided, the method fetches the corresponding customer’s data using `Service` and pre-fills the form fields. The `Submit` button is configured to update the existing entry. -- **Add Mode**: If no `id` is present, the form remains blank, and the `Submit` button is configured to create a new customer. +In `FormView`, the `onDidEnter` method checks for the presence of an `id` parameter and adjusts the form's behavior: +- **Edit Mode**: If an `id` is provided, fetch the customer’s data and pre-fill the form. The `Submit` button updates the entry. +- **Add Mode**: If no `id` is present, show a blank form. The `Submit` button creates a new customer. ```java @Override @@ -243,55 +113,16 @@ The `onDidEnter` method in `FormView` checks for the presence of an `id` paramet } ``` +### Running the app -### Submitting data {#submitting-data} - -When finished editing the data, it's necessary to submit it to the service handling the repository. Therefore the -`Service` class that has been already set up in the previous step of this tutorial -now needs additional methods so users can add and edit customers. - -The snippet below shows how to accomplish this: - -```java title="Service.java" -public void addCustomer(Customer newCustomer) { - data.add(newCustomer); - repository.commit(newCustomer); -} - -public void editCustomer(Customer editedCustomer) { - repository.commit(editedCustomer); -} -``` - -### Using `commit()` {#using-commit} - -The `commit()` method in the `Repository` class keeps the app’s data and UI in sync. It provides a way to refresh the data stored in the `Repository` so the app always displays the latest state. - -This method can be used in two ways: - -1) **Refreshing all data:** - Calling `commit()` without arguments reloads all entities from the repository's underlying data source, such as a database or a service class. - -2) **Refreshing a single entity:** - Calling `commit(T entity)` reloads a specific entity so its state matches the latest data source changes. - -Call `commit()` when data in the `Repository` changes, such as after adding or modifying entities in the data source. - -```java -// Refresh all customer data in the repository -customerRepository.commit(); - -// Refresh a single customer entity -Customer updatedCustomer = ...; // Updated from an external source -customerRepository.commit(updatedCustomer); +From the step directory, run: +```bash +mvn spring-boot:run ``` -With these changes, the following goals have been achieved: +Then open [http://localhost:8080](http://localhost:8080) in your browser. - 1. Implemented routing and set it up so future views can be integrated with little effort. - 2. Removed UI implementations out of the `App` and into a separate view. - 3. Added an additional view to manipulate the data that's displayed in the customer table. +--- -With the modification of the customer details and routing accomplished, the next step will focus on -implementing data binding and using it to facilitate validation. \ No newline at end of file +With routing and view separation in place, your app is now ready for further scaling, including advanced data binding and validation in the next step. \ No newline at end of file diff --git a/docs/docs/introduction/tutorial/validating-and-binding-data.md b/docs/docs/introduction/tutorial/validating-and-binding-data.md index c4fe58938..bb00f8358 100644 --- a/docs/docs/introduction/tutorial/validating-and-binding-data.md +++ b/docs/docs/introduction/tutorial/validating-and-binding-data.md @@ -5,7 +5,7 @@ pagination_next: null --- -Data binding connects UI components directly with your data model, enabling automatic synchronization of values. This reduces boilerplate and improves reliability. Validation checks that form data follows rules such as being non-empty or matching a pattern. With webforJ and Spring Boot, you can use Jakarta validation annotations and webforJ’s binding system for a user-friendly experience. +[Data binding](../../data-binding/overview.md) connects UI components directly with your data model, enabling automatic synchronization of values. This reduces boilerplate and improves reliability. Validation checks that form data follows rules such as being non-empty or matching a pattern. With webforJ and Spring Boot, you can use Jakarta validation annotations and webforJ’s binding system for a user-friendly experience. To run the app: @@ -62,6 +62,28 @@ Validation is handled by Jakarta annotations in the `Customer` entity: private String firstName = ""; ``` +**Annotation overview:** + +- `@NotEmpty` and `@Pattern` are [Jakarta Validation](https://beanvalidation.org/) annotations. They declare validation rules directly on the model property: + - `@NotEmpty` requires the value to be non-empty. + - `@Pattern` restricts input to the specified regular expression (here, only letters). + +**Other common Jakarta Validation annotations:** + +- `@NotNull`: Value must not be null. +- `@NotBlank`: String must not be null and must contain at least one non-whitespace character. +- `@Size(min=, max=)`: String, collection, or array must have a length/size within the given bounds. +- `@Email`: Value must be a valid email address. +- `@Min` / `@Max`: Numeric value must be within the specified range. +- `@Positive` / `@Negative`: Value must be positive or negative. +- `@Past` / `@Future`: Date/time value must be in the past or future. +- `@Digits(integer=, fraction=)`: Number must have the specified number of integer and fraction digits. + +See the [Jakarta Bean Validation constraints reference](https://jakarta.ee/specifications/bean-validation/3.0/apidocs/jakarta/validation/constraints/package-summary.html) for a full list. + +webforJ integrates Jakarta validation via the `BindingContext` (with validation enabled), so these constraints are automatically checked when binding data. For more, see [Jakarta Validation in webforJ](../../data-binding/validation/jakarta-validation.md). + + The binding context disables the submit button if the form is invalid: ```java title="FormView.java" diff --git a/docs/docs/introduction/tutorial/working-with-data.md b/docs/docs/introduction/tutorial/working-with-data.md index 336019543..a43877e2e 100644 --- a/docs/docs/introduction/tutorial/working-with-data.md +++ b/docs/docs/introduction/tutorial/working-with-data.md @@ -6,6 +6,8 @@ sidebar_position: 3 This step adds data management and display capabilities to the demo app. You’ll define a `Customer` entity, use a Spring Data repository and service for data access, and display customer data in a [`Table`](../../components/table/overview) component. +This and all future steps utilize a H2 database. To use H2 with Spring Boot, add the H2 dependency to your pom.xml and set spring.datasource.url=jdbc:h2:mem:testdb in your application.properties. Spring Boot will auto-configure and start the H2 database for you. + By the end of this step, the app from the [previous step](./creating-a-basic-app) will display a table with customer data, ready for further extension. To run the app: - Go to the `2-working-with-data` directory @@ -59,6 +61,14 @@ With the `Customer` data model in place, the next step is to manage and organize Spring Boot integration lets you use Spring Data repositories and services for data access. The `CustomerService` class provides methods to create, update, delete, and query customers, and exposes a `SpringDataRepository` for use with webforJ’s `Table`. +Note that there are three spring annotations used here: + +- `@Service` marks a class as a service component in Spring, making it automatically detected and managed as a bean for business logic or reusable operations. + +- `@Transactional` tells Spring to run the method or class within a database transaction, so all operations inside are committed or rolled back together. More detail is available in their official [documentation](https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html#page-title). + +- `@Autowired` tells Spring to automatically inject the required dependency into a field, constructor, or method. This means you don’t have to create the object yourself—Spring finds and supplies it from its app context. More detail is available in their official [documentation](https://docs.spring.io/spring-framework/reference/core/beans/annotation-config/autowired.html). + ```java title="CustomerService.java" @Service @Transactional @@ -95,6 +105,10 @@ public class CustomerService { } ``` +:::note +`SpringDataRepository` is a webforJ wrapper that lets you connect UI components directly to Spring Data repositories. It simplifies data binding and CRUD operations by allowing your webforJ tables and forms to work freely with your Spring-managed data layer. +::: + ## Displaying data in a table From 49cacf68c7e0c84f58c29bba732f0900ad1d4cee Mon Sep 17 00:00:00 2001 From: Eric Handtke Date: Wed, 3 Sep 2025 11:26:04 +0200 Subject: [PATCH 3/3] fixed linting. --- docs/docs/introduction/tutorial/working-with-data.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/introduction/tutorial/working-with-data.md b/docs/docs/introduction/tutorial/working-with-data.md index a43877e2e..890dc2da0 100644 --- a/docs/docs/introduction/tutorial/working-with-data.md +++ b/docs/docs/introduction/tutorial/working-with-data.md @@ -6,7 +6,7 @@ sidebar_position: 3 This step adds data management and display capabilities to the demo app. You’ll define a `Customer` entity, use a Spring Data repository and service for data access, and display customer data in a [`Table`](../../components/table/overview) component. -This and all future steps utilize a H2 database. To use H2 with Spring Boot, add the H2 dependency to your pom.xml and set spring.datasource.url=jdbc:h2:mem:testdb in your application.properties. Spring Boot will auto-configure and start the H2 database for you. +This and all future steps use a H2 database. To use H2 with Spring Boot, add the H2 dependency to your pom.xml and set `spring.datasource.url=jdbc:h2:mem:testdb` in your `application.properties`. Spring Boot will auto-configure and start the H2 database for you. By the end of this step, the app from the [previous step](./creating-a-basic-app) will display a table with customer data, ready for further extension. To run the app: