diff --git a/articles/building-apps/forms-data/add-grid/buffered-data.adoc b/articles/building-apps/forms-data/add-grid/buffered-data.adoc new file mode 100644 index 0000000000..9840fac584 --- /dev/null +++ b/articles/building-apps/forms-data/add-grid/buffered-data.adoc @@ -0,0 +1,69 @@ +--- +title: Buffered Data +page-title: How to add a buffered grid to a Vaadin application +description: Learn how to populate a grid with data from an application service. +meta-description: Learn how to populate a grid with data from an application service. +order: 20 +--- + += Add a Buffered Grid + +Buffered data refers to data that is *loaded from an application service where the entire filtered result set is fetched at once*. This approach works well when you can guarantee that filtered result sets remain small enough to fit in memory (for example, orders for a single customer, or tasks assigned to a user). + +As with <>, *buffered data is loaded into the grid at once and sorted in memory*. However, *the filtering happens in the application service*. + +This guide starts with a complete example that you can copy-paste into your project if you want. It then breaks down the example into sections that explain how to populate, filter, and sort the grid. The guides does not cover setting up the Grid component itself; for that, see the <> component documentation. + + +== Copy-Paste into Your Project + +If you want to quickly try out a buffered grid in your Vaadin application, copy-paste the following code into your project: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/BufferedGridView.java[tags=full,indent=0] +---- + +For more detailed walk-through of the example code, continue reading below. + + +== Getting the Data + +In this example, the data is coming from a simple service class that _simulates_ fetching data from a database: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/BufferedGridView.java[tags=data,indent=0] +---- + +In a real world application, the service would be in its own file. The data would be loaded from a database +and the result size limited to avoid flooding the UI with too many items. For the same reason an empty filter string returns no items in this example. + +.Can you call a repository or Data Access Object (DAO) directly from the UI? +[NOTE] +Yes, if you're not using <<../../security/protect-services#,method security>> or any other service layer logic. + + +== Populating and Filtering the Grid + +Since the filtering happens in the service, you need to call the service whenever the filter string changes, and then call `setItems()` on the grid with the returned items: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/BufferedGridView.java[tags=filtering,indent=0] +---- + +There is one caveat with this approach: *selection is not preserved* between calls to `setItems()`. If you need to preserve selection, consider using a <> instead. +// TODO Add link to guide about selection handling in grids + + +== Sorting + +Sorting happens in the grid automatically, for every column that is *marked as sortable*: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/BufferedGridView.java[tags=sorting,indent=0] +---- + +For more information about sorting, see the <> component documentation. diff --git a/articles/building-apps/forms-data/add-grid/index.adoc b/articles/building-apps/forms-data/add-grid/index.adoc new file mode 100644 index 0000000000..a11c2cacf3 --- /dev/null +++ b/articles/building-apps/forms-data/add-grid/index.adoc @@ -0,0 +1,25 @@ +--- +title: Add a Grid +page-title: How to add a data grid to a Vaadin view +description: Learn how to add a data grid to a Vaadin view. +meta-description: Learn how to add a data grid to a Vaadin view. +order: 10 +--- + += Add a Grid + +In business applications, grids are commonly used to display tabular data. Vaadin's <> component provides a powerful and flexible way to present large datasets. However, the way you populate, filter, and sort the grid depends on the nature of your data. + +Data in business applications typically falls into one of the following categories: + +* *Static data*: A fixed collection of objects held entirely in memory that never changes. This is typically reference data (for example, countries, categories, status types) that is either hardcoded or loaded once at application startup. Java `enum` constants also fall into this category. Filtering and sorting happen entirely in memory. + +* *Buffered data*: Data that is loaded from an application service where the entire filtered result set is fetched at once. Because all matching records are held in memory, sorting can happen locally without additional service calls. This approach works well when you can guarantee that filtered result sets remain small enough to fit in memory (for example, orders for a single customer, or tasks assigned to a user). + +* *Paginated data*: Data that is too large to fit in memory, even after filtering. It is loaded in chunks (pages) from an application service as needed. Both filtering and sorting are delegated to the service layer. + +You can use the Grid component with all these data types, but the implementation details differ. Each data type has its own guide: + +section_outline::[] + +For detailed information about binding data sets to UI components in Vaadin, see the <> reference guide. diff --git a/articles/building-apps/forms-data/add-grid/paginated-data.adoc b/articles/building-apps/forms-data/add-grid/paginated-data.adoc new file mode 100644 index 0000000000..43721d3cd7 --- /dev/null +++ b/articles/building-apps/forms-data/add-grid/paginated-data.adoc @@ -0,0 +1,98 @@ +--- +title: Paginated Data +page-title: How to add a paginated grid to a Vaadin application +description: Learn how to populate a grid with paginated data from an application service. +meta-description: Learn how to populate a grid with paginated data from an application service. +order: 30 +--- + += Add a Paginated Grid +:toclevels: 2 + +Paginated data refers to datasets that are too large to fit in memory, even after filtering. Instead, *the data is loaded in chunks (pages)* from an application service as needed. *Both filtering and sorting are delegated to the service layer* (typically a database). + +In Vaadin, the Grid component has built-in support for paginated data (often referred to as lazy loading in API documentation). When the user scrolls or changes the sorting/filtering criteria, the grid requests the appropriate data page from the backend service. To the user, this appears as a seamless scrolling experience. + +This guide starts with a complete example that you can copy-paste into your project if you want. It then breaks down the example into sections that explain how to populate, filter, and sort the grid. The guide does not cover setting up the Grid component itself; for that, see the <> component documentation. + + +== Copy-Paste into Your Project + +If you want to quickly try out a paginated grid in your Vaadin application, copy-paste the following code into your project: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/PaginatedGridView.java[tags=full,indent=0] +---- + +For more detailed walk-through of the example code, continue reading below. + + +== Getting the Data + +The Grid component can request pages either by using a page number and size, or by using an offset and limit. If the dataset is sorted, you have to sort it before applying pagination. + +This example uses offset-based pagination and uses Vaadin's `QuerySortOrder` API to pass sorting information to the application service: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/PaginatedGridView.java[tags=data,indent=0] +---- + +In a real world application, the service would be in its own file and the data would be queried from a database. + + +== Populating the Grid + +You populate the grid with paginated data by passing it a callback function that fetches data pages on demand. The callback function receives a `Query` object that contains information about the requested page, sorting, and filtering criteria. + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/PaginatedGridView.java[tags=dataprovider,indent=0] +---- + + +=== Spring Data Support + +If you are using Spring Data, use the `setItemsPageable()` method. It passes a `Pageable` object to the callback function that you can use directly with <<../repositories/jpa#,Spring Data repositories>>: + +[source,java] +---- +// Spring Data Repository: +public interface ItemReposiory extends PagingAndSortingRepository { + Slice findByNameContainingIgnoreCase(String name, Pageable pageable); +} + +// In the view class: +grid.setItemsPageable(pageable -> repository + .findByNameContainingIgnoreCase(filterField.getValue(), pageable) + .getContent() +); +---- + +.Can you call a repository directly from the UI? +[NOTE] +Yes, if you're not using <<../../security/protect-services#,method security>> or any other service layer logic. +Otherwise, it's better to have an <<../../business-logic/add-service#,application service>> between the UI and the repository. + + +== Filtering + +The filtering happens in the application service and the filter string is passed to the service via the callback function. Because of this, you have to refresh the grid whenever the filter string changes: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/PaginatedGridView.java[tags=filtering,indent=0] +---- + + +== Sorting + +Sorting happens in the application service and the sort orders are passed to the service via the callback function. Only columns with a specific *sort property* are sortable: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/PaginatedGridView.java[tags=sorting,indent=0] +---- + +Sort properties are strings that are passed to the application service. They typically correspond to database column names, but in this example they are mapped to the record fields in the `Item` class. diff --git a/articles/building-apps/forms-data/add-grid/static-data.adoc b/articles/building-apps/forms-data/add-grid/static-data.adoc new file mode 100644 index 0000000000..5d7b2abc40 --- /dev/null +++ b/articles/building-apps/forms-data/add-grid/static-data.adoc @@ -0,0 +1,79 @@ +--- +title: Static Data +page-title: How to add a static grid to a Vaadin application +description: Learn how to populate a grid with static data. +meta-description: Learn how to populate a grid with static data in a Vaadin application. +order: 10 +--- + += Add a Static Grid + +Static data refers to a fixed collection of objects held entirely in memory — such as *reference data (countries, categories, status types) that is either hardcoded or loaded once at application startup*. Java `enum` constants also fall into this category. + +Because the full dataset is available in memory, the static data is loaded into the grid at once. *Filtering and sorting can be performed without additional backend calls*. + +This guide starts with a complete example that you can copy-paste into your project if you want. It then breaks down the example into sections that explain how to populate, filter, and sort the grid. The guide does not cover setting up the Grid component itself; for that, see the <> component documentation. + + +== Copy-Paste into Your Project + +If you want to quickly try out a static grid in your Vaadin application, copy-paste the following code into your project: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/StaticGridView.java[tags=full,indent=0] +---- + +For more detailed walk-through of the example code, continue reading below. + + +== Getting the Data + +In this example, the static data is a list of record objects returned by a simple service class: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/StaticGridView.java[tags=data,indent=0] +---- + +In a real world application, the service would be in its own file. The data could be statically defined as in this example, or loaded from a file or a database during application startup. + + +== Populating the Grid + +You populate the grid with static data by passing the collection of items to the grid's `setItems()` method: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/StaticGridView.java[tags=dataprovider,indent=0] +---- + +The method returns a `ListDataView` object that you need if you want to filter the data. This is covered in the next section. + + +== Filtering + +To filter the grid, you need to add a filter to the `ListDataView`. A filter is a predicate that tests each item in the data set in memory. The filter is applied whenever the data view is refreshed, or when the filter itself is changed. + +In this example, the filter checks whether the item's name contains the filter string entered into the filter text field. If the filter string is blank, all items are shown: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/StaticGridView.java[tags=filtering,indent=0] +---- + +[NOTE] +The predicate executes once for each item in the data set. Because of this, you should keep the predicate as simple and efficient as possible. For instance, in the example above, the filter string is converted outside the predicate to avoid doing it repeatedly for each item. + +For more information about filtering, see the <> component documentation. + +== Sorting + +Sorting happens in the grid automatically, for every column that is *marked as sortable*: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/StaticGridView.java[tags=sorting,indent=0] +---- + +For more information about sorting, see the <> component documentation. diff --git a/articles/building-apps/views/add-master-detail.adoc b/articles/building-apps/views/add-master-detail.adoc new file mode 100644 index 0000000000..4e8087004d --- /dev/null +++ b/articles/building-apps/views/add-master-detail.adoc @@ -0,0 +1,145 @@ +--- +title: Add a Master-Detail View +page-title: How to add a master-detail view to a Vaadin application +description: Learn how to build a master-detail view. +meta-description: Learn how to build a master-detail view using Vaadin. +order: 25 +--- + + += Add a Master-Detail View +:toclevels: 2 + +This guide teaches you how to build a rudimentary master-detail view in Vaadin. It focuses on the UI pattern only: how to structure the layout, represent selection in the URL, and synchronize the two. It does not define data models, persistence, or form/grid configuration. + + +== Copy-Paste into Your Project + +If you want to quickly try out a master-detail view, you can copy-paste the following class into your Vaadin project: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/masterdetail/MasterDetailView.java[tags=full,indent=0]] +---- + +For more detailed instructions on how to build a master-detail view from scratch, continue reading below. The guide uses the same code as in the copy-paste example, but breaks it down into smaller parts with explanations. + + +== What is a Master-Detail View? + +In a master-detail view, the user selects an item from a list (the master), and the details of the selected item are shown in another area (the detail). When no item is selected, the detail area is either hidden or shows a placeholder message. + +In the following mock-up, the master is a list of employees, and the detail shows information about the selected employee to the right of the list: + +[.device] +image::images/master-detail.png[A mock-up of a master-detail view showing a list of employees on the left and details of the selected employee on the right] + +Master-detail views can look different depending on the application. In the following mock-up, the master is a list of offices, and the detail shows information about the selected office below the list: + +[.device] +image::images/master-detail2.png[A mock-up of a master-detail view showing a list of offices on the top and details of the selected office below the list] + +The example code in this guide does not look like the mock-ups above. It uses simple components and focuses only on the UI pattern. Substitute your own components where indicated. + + +== Scaffolding the View + +When creating a master-detail view, start with the general structure. You'll need: + +* a layout to arrange the master and detail components side by side, +* a master component (e.g., a <>), +* a detail component (e.g., a form), +* a placeholder component to show when no detail is selected, and +* a way to represent the selected item's ID in the URL. + +Because the selected item's ID appears in the URL, the view can always reconstruct the correct UI state after navigation, a refresh, or a shared link. + +The following listing shows the minimal structure of a master-detail view, using the same class as in the full example but with the implementation omitted for clarity. It uses a <> to arrange the master and detail components side by side, and a +<> to represent the selected item's ID: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/masterdetail/MasterDetailView.java[tags=scaffolding,indent=0] +---- + +With the class structure in place, the next step is to implement the master, detail, and placeholder components that the layout will display. + +[NOTE] +You can use any layout you like to arrange the master and detail components in your application. Split Layout is not the only option, but it works well for this simple example. + +== Creating the Master Component + +To keep this example simple, the master component is a list of buttons. The buttons represent user selections; each button simulates selecting a different item by navigating to a URL containing its ID: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/masterdetail/MasterDetailView.java[tags=master,indent=0] +---- + +In a real application, the master component would be a Grid with data loaded from a database. Adding such a Grid is covered in the <<../forms-data/add-grid#,Add a Grid>> guide. + + +=== Implementing Navigation Methods + +The master uses <> for showing the master and detail views. These methods either set or clear the URL parameter, resulting in the `setParameter()` method being called with the appropriate value: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/masterdetail/MasterDetailView.java[tags=navigation,indent=0] +---- + +The implementation of `setParameter()` is covered later in this guide. + + +== Creating the Detail Component + +The detail component in this example is even simpler: it shows the selected ID in a paragraph: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/masterdetail/MasterDetailView.java[tags=detail,indent=0] +---- + +In a real application, the detail component would be a form. How to build such a form is covered in the <<../forms-data/add-form#,Add a Form>> guide. + + +== Creating the Placeholder + +When no detail is selected, it's good practice to show a placeholder message. Here's a simple implementation that shows a message in a paragraph: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/masterdetail/MasterDetailView.java[tags=nodetail,indent=0] +---- + +This is helpful when a user opens the list view without selecting anything. + + +== Implementing View Logic + +With the components in place, the next step is to implement the view logic that ties everything together. Since the master is always visible, add it to the primary area of the Split Layout in the constructor: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/masterdetail/MasterDetailView.java[tags=constructor,indent=0] +---- + +The secondary area of the Split Layout should be updated based on the user's selection: + +* If the user has selected an item, show the detail component. +* If no item is selected, show the placeholder component. + +Because you're using a route parameter to represent the selected item's ID, implement this logic in the `setParameter()` method - remove the old detail area, check whether the ID is present, then show either the detail or the placeholder accordingly: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/masterdetail/MasterDetailView.java[tags=setparameter,indent=0] +---- + +In this simple example, the selection is not visible in the master component. In a real application, you'd want to reflect the selection state in the master component as well. + +For example, if you're using a Grid, you could select the corresponding row when an item is selected, and clear the selection when no item is selected. All this logic would also go into the `setParameter()` method. + + + +With these methods in place, the master-detail view is complete. The next step is to integrate it into your application and enhance it with real data and more complex UI components as needed. diff --git a/articles/building-apps/views/add-navi-menu.adoc b/articles/building-apps/views/add-navi-menu.adoc new file mode 100644 index 0000000000..fdce0d3fa5 --- /dev/null +++ b/articles/building-apps/views/add-navi-menu.adoc @@ -0,0 +1,125 @@ +--- +title: Add a Navigation Menu +page-title: How to add a navigation menu to a Vaadin application +description: Learn how to add a navigation menu to a router layout. +meta-description: Learn how to add a navigation menu to a router layout in Vaadin. +order: 21 +--- + + += Add a Navigation Menu +:toclevels: 2 + + +This guide teaches you how to add a navigation menu to a router layout in Vaadin. It is a continuation of the <> guide. + + +== Copy-Paste into Your Project + +If you want to quickly try out a navigation menu, you can copy-paste the following code into your router layout: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/routerlayout/MainLayout.java[tags=navimenu,indent=0] +---- + +Expand the code for a complete implementation of a router layout with a navigation menu. For more detailed instructions on how to use build a navigation menu, continue reading below. + + +== Creating a Navigation Menu + +You typically build a navigation menu in Vaadin using the Side Navigation components, which provide a vertical list of navigation links with support for collapsible, nested sections. This guide only covers the very basics of building a navigation menu. For more detailed information, see the <> documentation. + +Start by creating an instance of [classname]`SideNav` to act as the container for the navigation items: + +[source,java] +---- +private SideNav createSideNav() { + SideNav sideNav = new SideNav(); + // Add navigation items here + return sideNav; +} +---- + +For every item in the navigation menu, create an instance of [classname]`SideNavItem`, and add it to the [classname]`SideNav` instance. Each item represents a link to a view in your application. You can create a simple navigation item like this: + +[source,java] +---- +SideNavItem homeItem = new SideNavItem("Home", HomeView.class); +sideNav.addItem(homeItem); +---- + +Now, whenever the user clicks on the item, the router navigates to the specified view. Furthermore, the item will be highlighted when the user is on that view. + + +=== Highlighting Nested Views + +By default, a [classname]`SideNavItem` is highlighted only when the user is on the exact path of the view. That means that if the view has e.g. a URL parameter, or acts as a parent layout for another view, the item is not highlighted. + +For example, an item with the path `/customers` has the following behavior: + +* `/customers` -- item is highlighted +* `/customers/123` -- item is *not* highlighted +* `/customers/123/edit` -- item is *not* highlighted + + +To highlight the item for all paths that start with the view's path, use the [methodname]`SideNavItem.setMatchNested()` method: + +[source,java] +---- +SideNavItem customersItem = new SideNavItem("Customers", CustomersView.class); +customersItem.setMatchNested(true); +// Now matches /customers, /customers/123, /customers/123/edit, etc. +sideNav.addItem(customersItem); +---- + + +== Getting the Menu Items Dynamically + +If your views have <> defined using the [annotationname]`@Menu` annotation, you can build the menu dynamically. + +The `MenuConfiguration.getMenuEntries()` method returns a list of all menu entries defined in the application. You can iterate over these entries and create corresponding [classname]`SideNavItem` instances for each entry: + +[source,java] +---- +private SideNav createSideNav() { + var nav = new SideNav(); + MenuConfiguration.getMenuEntries() + .forEach(entry -> nav.addItem( + new SideNavItem( + entry.title(), + entry.path() + ) + )); + return nav; +} +---- + +For technical details, see the <> reference guide. + + +== Icons in the Menu + +You often want to have icons in your navigation menu to improve usability and visual appeal. The `@Menu` annotation has an `icon` attribute, but it's an ordinary string. Therefore, you need to decide how to interpret that string in your menu implementation. + +For example, if you use <>, you can create an icon for a menu item like this: + +[source,java] +---- +// Annotation on view: +@Menu(title = "Dashboard", icon = "vaadin:dashboard") + +// In the menu building code: +item.setPrefixComponent(new Icon(menuEntry.icon())); +---- + +If you're using SVG icons, you can do this instead: + +[source,java] +---- +// Annotation on view: +@Menu(title = "Dashboard", icon = "icons/dashboard.svg") + +// In the menu building code: +item.setPrefixComponent(new SvgIcon(menuEntry.icon())); +---- diff --git a/articles/building-apps/views/add-router-layout.adoc b/articles/building-apps/views/add-router-layout.adoc new file mode 100644 index 0000000000..d6bca929a3 --- /dev/null +++ b/articles/building-apps/views/add-router-layout.adoc @@ -0,0 +1,228 @@ +--- +title: Add a Router Layout +page-title: How to add a router layout to a Vaadin application +description: Learn how to add a router layout to a Vaadin application. +meta-description: Learn to create and apply router layouts in Vaadin, including automatic and explicit layouts, and route prefixes for structured navigation. +order: 20 +--- + + += Add a Router Layout +:toclevels: 2 + +This guide teaches you how to create a router layout in Vaadin, apply it to views automatically and explicitly, and use route prefixes for structured navigation. + + +== Copy-Paste into Your Project + +If you want to quickly try out a router layout, you can copy-paste the following two classes into your Vaadin project: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/routerlayout/MainLayout.java[tags=snippet,indent=0] +---- +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/routerlayout/HomeView.java[tags=snippet,indent=0] +---- + +For more detailed instructions on how to use router layouts, continue reading below. + + +== What is a Router Layout? + +Most business applications have interface elements that remain visible across different views, such as a navigation menu, header, or footer. Instead of duplicating these elements on every view, you can use a _router layout_. + +A router layout ensures that the router *renders views inside a predefined layout*, reducing redundancy. The screenshot below illustrates an empty view rendered within a router layout. The layout includes a sidebar with the application name, a navigation menu, and a user menu. Views are displayed in the white area on the right: + +image::images/main-layout.png[Example of a router layout] + + +== Creating a Router Layout + +Router layouts are UI components that implement the [interfacename]`RouterLayout` interface, which provides two key methods: + +* [methodname]`showRouterLayoutContent(HasElement)` -- shows the given view in the router layout. +* [methodname]`removeRouterLayoutContent(HasElement)` -- removes the given view in the router layout. + +When you navigate to a view, the router first determines which layout to use -- if any. If you're navigating from one view to another inside the same router layout, the *existing router layout instance is reused*. Otherwise, a new instance is created. The router then calls [methodname]`showRouterLayoutContent()`, passing in the new view instance. + +[TIP] +The router layout becomes the parent of the view in the component hierarchy. You can use `Component.getParent()` method to access the layout from the view after the view has been added to the layout. + +== Automatic Layouts + +To create an automatic layout, add the [annotationname]`@Layout` annotation to a router layout. This layout is automatically applied to all views unless explicitly disabled. + +The following example applies [classname]`MainLayout` to *all views*: + +[source,java] +---- +// tag::snippet[] +@Layout +// end::snippet[] +public class MainLayout extends AppLayout { // <1> + ... +} +---- +<1> `AppLayout` is a built-in router layout. See its <<{articles}/components/app-layout#,documentation page>> for more details. + + +=== Opting Out + +Sometimes, you may want to exclude specific views from the automatic layout. For example, displaying a login view within an application layout might not be appropriate. + +To *prevent a view from using the automatic layout*, set the `autoLayout` attribute of the [annotationname]`@Route` annotation to `false`: + +[source,java] +---- +@Route(value = "login", autoLayout = false) +public class LoginView extends Main { + ... +} +---- + +[NOTE] +[annotationname]`@RouteAlias` also has the `autoLayout` attribute. You can disable or enable the automatic layout for a single view depending on the route used to access it. + + +=== Scoping to a Path + +You can restrict an automatic layout to views with routes that start with a specific path. + +The following example applies [classname]`AdminLayout` *only to views with routes starting with `/admin`*: + +[source,java] +---- +// tag::snippet[] +@Layout("/admin") +// end::snippet[] +public class AdminLayout extends AppLayout { + ... +} +---- + +If a route matches multiple layouts, the layout with the longest matching path takes precedence. For example, given a main layout scoped to `/` (default) and an admin layout scoped to `/admin`, the layouts apply as follows: + +* `/` -> rendered inside the main layout +* `/customers` -> rendered inside the main layout +* `/admin/users` -> rendered inside the admin layout +* `/admin/groups` -> rendered inside the admin layout + +Defining multiple layouts with the exact same path results in an exception. + +[NOTE] +The path specified in `@Layout` need a leading slash (`/`), unlike the path in [annotationname]`@Route`. + + +== Explicit Layouts + +You can declare a view to use *a specific router layout* using the `layout` attribute of the [annotationname]`@Route` annotation: + +[source,java] +---- +// tag::snippet[] +@Route(layout = MyLayout.class) +// end::snippet[] +public class DefinedLayoutView extends Main { + ... +} +---- + +Declaring a layout explicitly also disables the automatic layout for the view. + +[NOTE] +[annotationname]`@RouteAlias` also has the `layout` attribute. You can render the same view in different layouts depending on the route used to access it. + + +== Nested Layouts + +Layouts can be nested within other layouts, for example when creating <>. The following screenshot demonstrates a view inside a router layout, which itself is inside another router layout: + +image::images/nested-layout.png[Example of a nested router layout] + +To *render a router layout inside another router layout*, use the [annotationname]`@ParentLayout` annotation: + +[source,java] +---- +// tag::snippet[] +@ParentLayout(MainLayout.class) +// end::snippet[] +public class NestedLayout extends Div implements RouterLayout { + ... +} +---- + +Parent layouts are always explicit. That is, automatic layouts never apply to other layouts. + + +== Route Prefixes + +By default, router layouts do not affect the routes of the views that they are applied to. You can change this with the [annotationname]`@RoutePrefix` annotation, which adds a prefix to all its routes. + +In the following example, `MyView` receives the `some` prefix from its router layout, resulting in `some/path` being its actual path: + +[source,java] +---- +@Route(value = "path", layout = MyLayout.class) +public class MyView extends Main { + ... +} + +// tag::snippet[] +@RoutePrefix("some") +// end::snippet[] +public class MyLayout extends Div implements RouterLayout { + ... +} +---- + + +=== Opting Out + +A view can opt out from a route prefix by setting the `absolute` attribute of [annotationname]`@Route` to `true`. + +In the following example, the path of `MyView` is `path`, ignoring the prefix coming from `MyLayout`: + +[source,java] +---- +// tag::snippet[] +@Route(value = "path", layout = MyLayout.class, absolute = true) +// end::snippet[] +public class MyView extends Main { + ... +} + +@RoutePrefix("some") +public class MyLayout extends Div implements RouterLayout { + ... +} +---- + +Nested router layouts can also opt out from route prefixes. + +In the following example, the path of `MyView` is in fact `nested/path`, as opposed to `some/nested/path`: + +[source,java] +---- +@Route(value = "path", layout = MyNestedLayout.class) +public class MyView extends Main { + ... +} + +// tag::snippet[] +@RoutePrefix(value = "nested", absolute = true) +// end::snippet[] +@ParentLayout(MyLayout.class) +public class MyNestedLayout extends Div implements RouterLayout { + ... +} + +@RoutePrefix("some") +public class MyLayout extends Div implements RouterLayout { + ... +} +---- + +[NOTE] +[annotationname]`@RouteAlias` also has the `absolute` attribute. diff --git a/articles/building-apps/views/add-router-layout/flow.adoc b/articles/building-apps/views/add-router-layout/flow.adoc deleted file mode 100644 index d68264c60b..0000000000 --- a/articles/building-apps/views/add-router-layout/flow.adoc +++ /dev/null @@ -1,505 +0,0 @@ ---- -title: Flow -page-title: How to add a Flow view to a Vaadin application -meta-description: Learn to create and apply router layouts in Vaadin Flow, including automatic and explicit layouts, nested layouts, and route prefixes for structured navigation. -order: 5 ---- - -// TODO We need a deep dive about this, as there are so many options. This guide covers only the most common use cases. - -= Router Layouts in Flow -:toclevels: 2 - -This guide teaches you how to create a router layout in Flow, apply it to views automatically and explicitly, and work with nested layouts. A hands-on mini-tutorial at the end will help you put these concepts into practice. - - -== Router Layouts in Flow - -In Flow, router layouts are UI components that implement the [interfacename]`RouterLayout` interface, which provides two key methods: - -* [methodname]`showRouterLayoutContent(HasElement)` -- shows the given view in the router layout. -* [methodname]`removeRouterLayoutContent(HasElement)` -- removes the given view in the router layout. - -When you navigate to a view, the router first determines which layout to use -- if any. If you're navigating from one view to another inside the same router layout, the *existing router layout instance is reused*. Otherwise, a new instance is created. The router then calls [methodname]`showRouterLayoutContent()`, passing in the new view instance. - - -== Automatic Layouts - -To create an automatic layout, add the [annotationname]`@Layout` annotation to a router layout. This layout is automatically applied to all views unless explicitly disabled. - -The following example applies [classname]`MainLayout` to *all views*: - -[source,java] ----- -// tag::snippet[] -@Layout -// end::snippet[] -public class MainLayout extends AppLayout { // <1> - ... -} ----- -<1> `AppLayout` is a built-in router layout. See its <<{articles}/components/app-layout#,documentation page>> for more details. - - -=== Opting Out - -Sometimes, you may want to exclude specific views from the automatic layout. For example, displaying a login view within an application layout might not be appropriate. - -To *prevent a view from using the automatic layout*, set the `autoLayout` attribute of the [annotationname]`@Route` annotation to `false`: - -[source,java] ----- -@Route(value = "login", autoLayout = false) -public class LoginView extends Main { - ... -} ----- - -[NOTE] -[annotationname]`@RouteAlias` also has the `autoLayout` attribute. You can disable or enable the automatic layout for a single view depending on the route used to access it. - - -=== Scoping to a Path - -You can restrict an automatic layout to views with routes that start with a specific path. - -The following example applies [classname]`AdminLayout` *only to views with routes starting with `/admin`*: - -[source,java] ----- -// tag::snippet[] -@Layout("/admin") -// end::snippet[] -public class AdminLayout extends AppLayout { - ... -} ----- - -If a route matches multiple layouts, the layout with the longest matching path takes precedence. For example, given a main layout scoped to `/` (default) and an admin layout scoped to `/admin`, the layouts apply as follows: - -* `/` -> rendered inside the main layout. -* `/customers` -> rendered inside the main layout. -* `/admin/users` -> rendered inside the admin layout -* `/admin/groups` -> rendered inside the admin layout. - -[IMPORTANT] -Defining multiple layouts with the exact same path will result in an exception. - - -== Explicit Layouts - -You can declare a view to use *a specific router layout* using the `layout` attribute of the [annotationname]`@Route` annotation: - -[source,java] ----- -// tag::snippet[] -@Route(layout = MyLayout.class) -// end::snippet[] -public class DefinedLayoutView extends Main { - ... -} ----- - -Declaring a layout explicitly also disables the automatic layout for the view. - -[NOTE] -[annotationname]`@RouteAlias` also has the `layout` attribute. You can render the same view in different layouts depending on the route used to access it. - - -== Nested Layouts - -Automatic layouts do not apply to other layouts. By default, a router layout is rendered directly in the browser tab. To *render a router layout inside another router layout*, use the [annotationname]`@ParentLayout` annotation: - -[source,java] ----- -// tag::snippet[] -@ParentLayout(MainLayout.class) -// end::snippet[] -public class NestedLayout extends Div implements RouterLayout { - ... -} ----- - - -== Path Prefixes - -By default, router layouts do not affect the routes of the views that they are applied to. You can change this with the [annotationname]`@RoutePrefix` annotation, which adds a prefix to all its routes. - -In the following example, `MyView` receives the `some` prefix from its router layout, resulting in `some/path` being its actual path: - -[source,java] ----- -@Route(value = "path", layout = MyLayout.class) -public class MyView extends Main { - ... -} - -// tag::snippet[] -@RoutePrefix("some") -// end::snippet[] -public class MyLayout extends Div implements RouterLayout { - ... -} ----- - - -=== Opting Out - -A view can opt out from a route prefix by setting the `absolute` attribute of [annotationname]`@Route` to `true`. - -In the following example, the path of `MyView` is `path`, ignoring the prefix coming from `MyLayout`: - -[source,java] ----- -// tag::snippet[] -@Route(value = "path", layout = MyLayout.class, absolute = true) -// end::snippet[] -public class MyView extends Main { - ... -} - -@RoutePrefix("some") -public class MyLayout extends Div implements RouterLayout { - ... -} ----- - -[NOTE] -[annotationname]`@RouteAlias` also has the `absolute` attribute. - -Nested router layouts can also opt out from route prefixes. - -In the following example, the path of `MyView` is in fact `nested/path`, as opposed to `some/nested/path`: - -[source,java] ----- -@Route(value = "path", layout = MyNestedLayout.class) -public class MyView extends Main { - ... -} - -// tag::snippet[] -@RoutePrefix(value = "nested", absolute = true) -// end::snippet[] -@ParentLayout(MyLayout.class) -public class MyNestedLayout extends Div implements RouterLayout { - ... -} - -@RoutePrefix("some") -public class MyLayout extends Div implements RouterLayout { - ... -} ----- - - -[.collapsible-list] -== Try It - -In this mini-tutorial, you'll explore router layouts using the Vaadin walking skeleton. You'll then create a nested layout and experiment with different ways to apply it to views. - - -.Set Up the Project -[%collapsible] -==== -First, generate a new application at http://start.vaadin.com[start.vaadin.com], <> it in your IDE, and <> it with hotswap enabled. -==== - - -.Explore the Main Layout -[%collapsible] -==== -The skeleton already contains a main layout. Instead of implementing one from scratch, you're going to have a look at it. Open [classname]`[application package].base.ui.view.MainLayout` in your IDE. - -The main layout is based on <<{articles}/components/app-layout#,App Layout>>: - -.MainLayout.java -[source,java] ----- -@Layout -public final class MainLayout extends AppLayout { - - public MainLayout() { - setPrimarySection(Section.DRAWER); - addToDrawer(createHeader(), new Scroller(createSideNav()), createUserMenu()); - } - ... -} ----- - -It has a drawer on the left side with the following elements: an application header, a navigation menu, and a user menu. All the elements are styled using <<{articles}/components/themes/lumo/utility-classes#,Lumo Utility Classes>>. -==== - - -.The Header -[%collapsible] -==== -The header is created by the [methodname]`createHeader()` method. It contains the application's name and logo: - -[source,java] ----- -private Div createHeader() { - // TODO Replace with real application logo and name - var appLogo = VaadinIcon.CUBES.create(); - appLogo.addClassNames(TextColor.PRIMARY, IconSize.LARGE); - - var appName = new Span("Walking Skeleton"); - appName.addClassNames(FontWeight.SEMIBOLD, FontSize.LARGE); - - var header = new Div(appLogo, appName); - header.addClassNames(Display.FLEX, Padding.MEDIUM, Gap.MEDIUM, AlignItems.CENTER); - return header; -} ----- - -Now, change the name and the logo. Use an icon from <<{articles}/components/icons/default-icons#,the default icons>>. -==== - - -.The Navigation Menu -[%collapsible] -==== -The navigation menu is created by the [methodname]`createSideNav()` method. It includes all views -- both Flow and React -- that have declared a menu item: - -[source,java] ----- -private SideNav createSideNav() { - var nav = new SideNav(); - nav.addClassNames(Margin.Horizontal.MEDIUM); - MenuConfiguration.getMenuEntries().forEach(entry -> // <1> - nav.addItem(createSideNavItem(entry))); - return nav; -} - -private SideNavItem createSideNavItem(MenuEntry menuEntry) { - if (menuEntry.icon() != null) { // <2> - return new SideNavItem(menuEntry.title(), menuEntry.path(), - new Icon(menuEntry.icon())); // <3> - } else { - return new SideNavItem(menuEntry.title(), menuEntry.path()); - } -} ----- -<1> [classname]`MenuConfiguration` gives access to all registered view menu items. -<2> This navigation menu assumes that all menu items have a title, but only some may have an icon. If you know all your menu items have icons, you can simplify this method. -<3> This navigation menu assumes that the `icon` attribute contains the name of an <<{articles}/components/icons#,Icon>>. -==== - - -.The User Menu -[%collapsible] -==== -The user menu is created by the [methodname]`createUserMenu()` method. It is the only part of the router layout that is a stub: - -[source,java] ----- -private Component createUserMenu() { - // TODO Replace with real user information and actions - var avatar = new Avatar("John Smith"); - avatar.addThemeVariants(AvatarVariant.LUMO_XSMALL); - avatar.addClassNames(Margin.Right.SMALL); - avatar.setColorIndex(5); - - var userMenu = new MenuBar(); - userMenu.addThemeVariants(MenuBarVariant.LUMO_TERTIARY_INLINE); - userMenu.addClassNames(Margin.MEDIUM); - - var userMenuItem = userMenu.addItem(avatar); - userMenuItem.add("John Smith"); - userMenuItem.getSubMenu().addItem("View Profile"); - userMenuItem.getSubMenu().addItem("Manage Settings"); - userMenuItem.getSubMenu().addItem("Logout"); - - return userMenu; -} ----- - -The <<{articles}/building-apps/security#,Security>> guides show you how to add real functionality to the user menu. -==== - - -.Create a Nested Layout -[%collapsible] -==== -Create a new Java package [packagename]`[application package].tutorial.ui.view`. Inside this package, create a new class called [classname]`NestedLayout`, like this: - -.NestedLayout.java -[source,java] ----- -import com.example.application.base.ui.view.MainLayout; // <1> -import com.vaadin.flow.component.HasElement; -import com.vaadin.flow.component.html.Div; -import com.vaadin.flow.router.*; -import com.vaadin.flow.theme.lumo.LumoUtility; - -@ParentLayout(MainLayout.class) // <2> -public class NestedLayout extends Div implements RouterLayout { - - private final Div content; - - public NestedLayout() { - addClassNames(LumoUtility.Display.FLEX, LumoUtility.FlexDirection.COLUMN, - LumoUtility.Gap.SMALL, LumoUtility.Padding.MEDIUM, - LumoUtility.BoxSizing.BORDER, LumoUtility.Height.FULL); - content = new Div(); - content.addClassNames(LumoUtility.Border.ALL, LumoUtility.Background.BASE); - content.setSizeFull(); - add(new Div("This is a layout: " + this), content); // <3> - } - - @Override - public void showRouterLayoutContent(HasElement content) { // <4> - this.content.getElement().appendChild(content.getElement()); - } -} ----- -<1> Replace with real package. -<2> This renders the nested layout inside the main layout. -<3> Prints `this` on the screen so that you can see when the layout instance changes. -<4> This renders views inside the `content` element. - -You can't see what your new layout looks like yet, because you don't have any views that use it. You'll fix that next. -==== - - -.Create Example Views -[%collapsible] -==== -You'll now create two views that contain links to each other and both use the new nested layout. Inside the [packagename]`[application package].tutorial.ui.view` package, create two new classes; [classname]`FirstView` and [classname]`SecondView`: - -.FirstView.java -[source,java] ----- -import com.vaadin.flow.component.html.H2; -import com.vaadin.flow.component.html.Main; -import com.vaadin.flow.router.Menu; -import com.vaadin.flow.router.Route; -import com.vaadin.flow.router.RouterLink; - -@Route(layout = NestedLayout.class) -@Menu -public class FirstView extends Main { - - public FirstView() { - add(new H2("First View"), - new RouterLink("Second View", SecondView.class)); - } -} ----- - -.SecondView.java -[source,java] ----- -import com.vaadin.flow.component.html.H2; -import com.vaadin.flow.component.html.Main; -import com.vaadin.flow.router.Menu; -import com.vaadin.flow.router.Route; -import com.vaadin.flow.router.RouterLink; - -@Route(layout = NestedLayout.class) -@Menu -public class SecondView extends Main { - - public SecondView() { - add(new H2("Second View"), - new RouterLink("First View", FirstView.class)); - } -} ----- -==== - - -.Test the Application -[%collapsible] -==== -Restart the application. Open your browser and navigate to: http://localhost:8080/first - -You should see the first view rendered inside the nested layout. - -Navigate back and forth between the first view and the second view. You should see that the nested layout instance remains unchanged. - -Now click on *Task List* in the navigation menu, then on *FirstView*. You should see that the nested layout instance has now changed. -==== - - -.Add a Route Prefix -[%collapsible] -==== -Add a `layout` route prefix to the nested layout: - -.NestedLayout.java -[source,java] ----- -@ParentLayout(MainLayout.class) -// tag::snippet[] -@RoutePrefix("layout") -// end::snippet[] -public class NestedLayout extends Div implements RouterLayout { - ... -} ----- - -Restart the application. Notice that the paths of first view and second view have now changed to `layout/first` and `layout/second`, respectively. -==== - - -.Enable Automatic Layout -[%collapsible] -==== -Up to this point, the nested layout has been explicitly applied to the first view and second view. You'll now change this so that it is applied automatically, while keeping the application's behavior unchanged. - -Start by removing the reference to [classname]`NestedLayout` from first view and second view, and add the `layout` prefix to each route: - -.FirstView.java -[source,java] ----- -// tag::snippet[] -@Route("layout/first") -// end::snippet[] -@Menu -public class FirstView extends Main { - ... -} ----- - -.SecondView.java -[source,java] ----- -// tag::snippet[] -@Route("layout/second") -// end::snippet[] -@Menu -public class SecondView extends Main { - ... -} ----- - -Next, change the [classname]`NestedLayout` to be automatically applied to all paths that start with `/layout`. Remove the [annotationname]`@RoutePrefix` annotation and add the [annotationname]`@Layout` annotation: - -.NestedLayout.java -[source,java] ----- -@ParentLayout(MainLayout.class) -// tag::snippet[] -@Layout("/layout") -// end::snippet[] -public class NestedLayout extends Div implements RouterLayout { - ... -} ----- - -Restart the application and click around. It should behave the same way as before. -==== - - -.Final Thoughts -[%collapsible] -==== -You've now learned how to: - -* Create a custom router layout. -* Apply layouts explicitly and automatically. -* Use path prefixes in router layouts. - -Now, try experimenting with route aliases and absolute routes! -==== diff --git a/articles/building-apps/views/add-router-layout/hilla.adoc b/articles/building-apps/views/add-router-layout/hilla.adoc deleted file mode 100644 index e1251af593..0000000000 --- a/articles/building-apps/views/add-router-layout/hilla.adoc +++ /dev/null @@ -1,171 +0,0 @@ ---- -title: Hilla -page-title: How to use router layouts in Hilla | Vaadin -meta-description: Learn how to create and customize router layouts in Hilla applications, including main layouts and nested layouts. -order: 10 ---- - - -= Router Layouts in Hilla -:toclevels: 2 - -In this guide, you'll learn how to create and customize router layouts in Hilla views. Router layouts are special view components that wrap around other views, providing common UI elements like navigation bars, menus, and footers. - - -== Router Layouts in Hilla - -Router layouts in Hilla are React components that wrap other views. Router layouts do not create separate navigable routes, but they wrap views that are mapped to the actual routes. Common use cases for layouts are for providing the application's shell (e.g. including common UI elements like navigation bar, side menu, footer, etc.), or as a nested layout that wraps specific set of views with additional UI elements. - - -== Creating a Router Layout - -To create a router layout, create a file named `@layout.tsx` in any directory under the `views`. The Hilla router, by convention, wraps all the views in the respective directory and its subdirectories with that layout. The layout must render the `` component where child views should appear. - -Here's an example of a basic router layout created directly under the `views` directory that wraps all views in the application, as it is located in the root of `views` directory: - -[source,tsx] -.frontend/views/@layout.tsx ----- -// tag::snippet[] -import { Outlet } from 'react-router'; -// end::snippet[] - -export default function MainLayout() { - return ( -
-
-

My Application

-
- -
- {/* tag::snippet[] */} - - {/* end::snippet[] */} -
- -
-

© 2025 My Company

-
-
- ); -} ----- - -In this example, the `MainLayout` component wraps all views in the application with a common header and footer. The `` component acts as a placeholder of where the child views should render. Having `@layout.tsx` files is not limited to the root directory, you can create them in any subdirectory to create nested layouts. - -Here's an example of a layout that wraps the views defined in the `customers` directory and any possible subdirectories: - -[source,tsx] -.frontend/views/customers/@layout.tsx ----- -import { Outlet } from 'react-router'; - -export default function CustomersLayout() { - return ( -
-
-

Customers

-
- -
- -
-
- ); -} ----- - -Depending on where a view file is located, by default it is wrapped and rendered within the available `@layout.tsx` of that directory, and also all the parent layouts of that directory. - -For example: - -[source] ----- -views -├── customers -│ ├── {id} -│ │ ├── @index.tsx -│ │ └── edit.tsx -│ ├── @index.tsx -│ └── @layout.tsx <1> -├── @index.tsx -└── @layout.tsx <2> ----- -<1> The layout file that wraps all views in the `customers` directory and its subdirectories -<2> The layout file that wraps all views in the application - - -== Skipping Layouts - -There are certain views and routes that should not be rendered inside any layouts. A `login` view is common example of such a view that should escape being rendered within the application layout. You can skip the layouts that are applied to views using the `ViewConfig` configuration object. Export this object from your view file to instruct the router not to wrap this view inside any layout available in the directory structure: - -[source,tsx] -.frontend/views/login.tsx ----- -// tag::snippet[] -import { ViewConfig } from '@vaadin/hilla-file-router/types.js'; -// end::snippet[] - -export default function LoginView() { - return ( -
Login form here
- ); -} - -export const config: ViewConfig = { -// tag::snippet[] - skipLayouts: true, // <1> -// end::snippet[] -}; ----- -<1> Instruct the router to skip all the layouts for this view - - -== Creating Dynamic Menus - -The Hilla router provides utilities to create navigation menus based on your route structure. Use the `createMenuItems()` utility to automatically generate menu items: - -[source,tsx] -.frontend/views/@layout.tsx ----- -// tag::snippet[] -import { createMenuItems } from '@vaadin/hilla-file-router/runtime.js'; -// end::snippet[] -import { useLocation, useNavigate } from 'react-router'; -import { SideNav } from '@vaadin/react-components/SideNav.js'; -import { SideNavItem } from '@vaadin/react-components/SideNavItem.js'; -import { Icon } from '@vaadin/react-components/Icon.js'; - -export default function MainMenu() { - const navigate = useNavigate(); - const location = useLocation(); - - return ( - path && navigate(path)} - location={location} - > - {createMenuItems().map(({ to, icon, title }) => ( // <1> - - {icon && } - {title} - - ))} - - ); -} ----- -<1> Iterate over the list of available routes returned by `createMenuItems()` and create a menu item for each route - -[NOTE] -The `createMenuItems()` utility returns all routes available in the application, including the routes from Flow views. - - -== Best Practices - -When working with router layouts in Hilla, follow these best practices: - -1. Use `@layout.tsx` naming convention for layout files -2. Always render the `` component where child views should appear -3. Consider skipping layouts for authentication views - diff --git a/articles/building-apps/views/add-router-layout/index.adoc b/articles/building-apps/views/add-router-layout/index.adoc deleted file mode 100644 index 182769a472..0000000000 --- a/articles/building-apps/views/add-router-layout/index.adoc +++ /dev/null @@ -1,43 +0,0 @@ ---- -title: Add a Router Layout -page-title: How to add a router layout to a Vaadin application -description: Learn how to add a router layout to a Vaadin application. -meta-description: Learn how to use router layouts in Vaadin to create consistent UI structures, reduce redundancy, and nest layouts in Flow and Hilla applications. -layout: tabbed-page -tab-title: Overview -order: 20 ---- - - -= Add a Router Layout - -Most business applications have interface elements that remain visible across different views, such as a navigation menu, header, or footer. Instead of duplicating these elements on every view, you can use a _router layout_. - -A router layout ensures that the router *renders views inside a predefined layout*, reducing redundancy. The screenshot below illustrates an empty view rendered within a router layout: - -image::images/main-layout.png[Example of a router layout] - -The router layout includes a sidebar with the application name, a navigation menu, and a user menu. Views are displayed in the white area on the right. - - -== Nested Layouts - -Layouts can be nested within other layouts. The following screenshot demonstrates a view inside a router layout, which itself is inside another router layout: - -image::images/nested-layout.png[Example of a nested router layout] - -Nested layouts are useful for creating primary-detail interfaces, among other use cases. - - -== Flow and Hilla - -You can implement router layouts in both Vaadin Flow and Hilla. Additionally, Flow views can be rendered inside Hilla layouts, and vice versa. - -.Consistency Matters -[TIP] -If most of your application is built with Flow, implement the router layouts in Flow. If it's primarily in Hilla, use Hilla for router layouts. This ensures code consistency. - -The following guides provide step-by-step instructions for implementing router layouts: - -* <> -* <> diff --git a/articles/building-apps/views/add-view.adoc b/articles/building-apps/views/add-view.adoc new file mode 100644 index 0000000000..b5585f4a72 --- /dev/null +++ b/articles/building-apps/views/add-view.adoc @@ -0,0 +1,204 @@ +--- +title: Add a View +page-title: How to add a view to a Vaadin application +description: Learn how to add a view to a Vaadin application. +meta-description: Learn how to add a view to a Vaadin application. +order: 10 +--- + + += Add a View +:toclevels: 2 + +In this guide, you'll learn how to create and name views in Java, assign multiple routes to a single view, and organize views into Java packages. + + +== Copy-Paste into Your Project + +If you want to quickly try out a view in your Vaadin application, copy-paste the following code into a new Java class named [classname]`HelloWorldView` in your project's main package: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/addview/HelloWorldView.java[tags=snippet,indent=0] +---- + +For more detailed instructions on how to add a view, continue reading below. + + +== What Is a View? + +In a Vaadin application, a _view_ is a user interface component that forms a logical whole of the user interface. A page can contain other components than a view, but only one view at a time. Typically, the view takes up most of the page. + +=== Views or Routes? + +Each view is associated with its own URL path. When the user navigates from one view to another, the URL of the page changes. *The mapping between a URL path and a view is called a _route_*, where the *view is the _target_ of the route*. + +All routes must be unique to allow the routing logic to determine the view to render without any disambiguation. When conflicts are detected, the application fails to start and logs a message explaining the reason for the conflict. + +The main view of an application is mapped to the root path (`""`). It is sometimes also called the _root_ view. + +.Screenshot of a main or root view +image::images/root-view.png[Example of a main view] + +.Screenshot of a view mapped to the "customers" path +image::images/customers-view.png[Example of a customers view] + +== Creating Views + +Vaadin views are Java classes that are annotated with [annotationname]`@Route` and extend [classname]`com.vaadin.flow.component.Component` -- or any of its subclasses. The default parameter of the [annotationname]`@Route` annotation is the path of the view. + +For example, you can define the [classname]`HelloWorld` component as the main view like this: + +[source,java] +---- +// tag::snippet[] +@Route("") +// end::snippet[] +public class HelloWorld extends Div { + public HelloWorld() { + setText("Hello world"); + } +} +---- + +If the application is running from the root context, users would be able to access this view by navigating to `\https://example.com`. + +Likewise, you can define the [classname]`CustomerListView` component as the target for the `customer/list` route: + +[source,java] +---- +// tag::snippet[] +@Route("customer/list") +// end::snippet[] +public class CustomerListView extends Main { + public CustomerListView() { + //... + } +} +---- + +Users would be able to access this view by navigating to `\https://example.com/customer/list`. + +[NOTE] +Don't include a leading `/` when you specify the path of a view. + +Navigation between views is covered in more detail in the <> guide. + +.Which Package to Put Views In? +[TIP] +For information about package structures for Vaadin applications, see the <> architecture deep-dive. + +== View Naming + +If you do not specify a default parameter for [annotationname]`@Route`, the path is derived from the name of the view. The derived name is the class name in lower case, with the `View` suffix removed if there is one. Also, [classname]`MainView` and [classname]`Main` are mapped to root. + +For example: + +* [classname]`MyEditor` becomes `"myeditor"` +* [classname]`PersonView` becomes `"person"` +* [classname]`MainView` becomes `""` + +If you specify the path explicitly, you can name your views any way you like. + + +== Route Aliases + +You can create multiple routes that target the same view. You do this with the `@RouteAlias` annotation. + +For example, you can map `\https://example.com`, `\https://example.com/home`, and `\https://example.com/main` to [classname]`HomeView` like this: + +[source,java] +---- +@Route("") +// tag::snippet[] +@RouteAlias("home") +@RouteAlias("main") +// end::snippet[] +public class HomeView extends Div { + public HomeView() { + //... + } +} +---- + +Whenever you use route aliases, you have to create a primary route by using the [annotationname]`@Route` annotation. Only adding [annotationname]`@RouteAlias` to a view does not work. + + +== Page Title + +By default, Vaadin views do not set a page title. You can define the page title either declaratively or dynamically. + + +=== Declarative Page Title + +To set a page title declaratively, use the [annotationname]`@PageTitle` annotation: + +[source,java] +---- +@Route("") +// tag::snippet[] +@PageTitle("Home Page") +// end::snippet[] +public class HomeView extends Div { + public HomeView() { + //... + } +} +---- + + +=== Dynamic Page Title + +To set the page title dynamically, implement the [interfacename]`HasDynamicTitle` interface: + +[source,java] +---- +@Route("") +// tag::snippet[] +public class HomeView extends Div implements HasDynamicTitle { +// end::snippet[] + + public HomeView() { + //... + } + +// tag::snippet[] + @Override + public String getPageTitle() { + return "Home Page"; + } +// end::snippet[] +} +---- + +The title is determined when the router navigates to the view. Any changes made after navigation will not affect the title. + +[IMPORTANT] +A view cannot use both the [annotationname]`@PageTitle` annotation and implement the [interfacename]`HasDynamicTitle` interface simultaneously. + + +== Navigation Menu Item + +Most business applications include a navigation menu. This menu can be generated dynamically using Vaadin's [classname]`MenuConfiguration`. To make a view appear in the menu, add the `@Menu` annotation: + +[source,java] +---- +@Route() +// tag::snippet[] +@Menu(title = "Dashboard", order = 1, icon = "vaadin:dashboard") +// end::snippet[] +public class DashboardView extends Main { + public DashboardView() { + //... + } +} +---- + +The [annotationname]`@Menu` annotation has the following attributes: + +`title` :: The menu title. Defaults to the page title if not specified. +`order` :: Determines the menu item's position. Items with a defined order appear above unordered items. +`icon` :: Specifies the menu icon. This is a string, allowing flexibility in interpretation. It could be an <<{articles}/components/icons#,Icon>> name or an SVG source, depending on the menu implementation. + +For more information on building a navigation menu, see <>. +// TODO Do we need a separate guide on building a navigation menu? diff --git a/articles/building-apps/views/add-view/flow.adoc b/articles/building-apps/views/add-view/flow.adoc deleted file mode 100644 index e0012fe0f1..0000000000 --- a/articles/building-apps/views/add-view/flow.adoc +++ /dev/null @@ -1,315 +0,0 @@ ---- -title: Flow -page-title: How to add a Flow view to a Vaadin application -meta-description: Learn about defining routes, naming conventions, using route aliases, and structuring views into Java packages. Includes a hands-on tutorial to apply key concepts. -order: 5 ---- - - -= Flow Views -:toclevels: 2 - -In this guide, you'll learn how to create and name views in Java, assign multiple routes to a single view, and organize views into Java packages. To reinforce your learning, a hands-on mini-tutorial at the end helps you apply these concepts in a real Vaadin application. - - -== Creating Views - -Flow views are Java classes that are annotated with [annotationname]`@Route` and extend [classname]`com.vaadin.flow.component.Component` -- or any of its subclasses. The default parameter of the [annotationname]`@Route` annotation is the path of the view. - -For example, you can define the [classname]`HelloWorld` component as the main view like this: - -[source,java] ----- -// tag::snippet[] -@Route("") -// end::snippet[] -public class HelloWorld extends Div { - public HelloWorld() { - setText("Hello world"); - } -} ----- - -If the application is running from the root context, users would be able to access this view by navigating to `\https://example.com`. - -Likewise, you can define the [classname]`CustomerListView` component as the target for the `customer/list` route: - -[source,java] ----- -// tag::snippet[] -@Route("customer/list") -// end::snippet[] -public class CustomerListView extends Main { - public CustomerListView() { - //... - } -} ----- - -Users would be able to access this view by navigating to `\https://example.com/customer/list`. - -[NOTE] -Don't include a leading `/` when you specify the path of a view. - -Navigation between views is covered in more detail in the <<../navigate#,Navigate to a View>> guide. - - -== View Naming - -If you do not specify a default parameter for [annotationname]`@Route`, the path is derived from the name of the view. The derived name is the class name in lower case, with the `View` suffix removed if there is one. Also, [classname]`MainView` and [classname]`Main` are mapped to root. - -For example: - -* [classname]`MyEditor` becomes `"myeditor"` -* [classname]`PersonView` becomes `"person"` -* [classname]`MainView` becomes `""` - -If you specify the path explicitly, you can name your views any way you like. - - -== Route Aliases - -You can create multiple routes that target the same view. You do this with the `@RouteAlias` annotation. - -For example, you can map `\https://example.com`, `\https://example.com/home`, and `\https://example.com/main` to [classname]`HomeView` like this: - -[source,java] ----- -@Route("") -// tag::snippet[] -@RouteAlias("home") -@RouteAlias("main") -// end::snippet[] -public class HomeView extends Div { - public HomeView() { - //... - } -} ----- - -Whenever you use route aliases, you have to create a primary route by using the [annotationname]`@Route` annotation. Only adding [annotationname]`@RouteAlias` to a view does not work. - - -== Page Title - -By default, Flow views do not set a page title. You can define the page title either declaratively or dynamically. - - -=== Declarative Page Title - -To set a page title declaratively, use the [annotationname]`@PageTitle` annotation: - -[source,java] ----- -@Route("") -// tag::snippet[] -@PageTitle("Home Page") -// end::snippet[] -public class HomeView extends Div { - public HomeView() { - //... - } -} ----- - - -=== Dynamic Page Title - -To set the page title dynamically, implement the [interfacename]`HasDynamicTitle` interface: - -[source,java] ----- -@Route("") -// tag::snippet[] -public class HomeView extends Div implements HasDynamicTitle { -// end::snippet[] - - public HomeView() { - //... - } - -// tag::snippet[] - @Override - public String getPageTitle() { - return "Home Page"; - } -// end::snippet[] -} ----- - -The title is determined when the router navigates to the view. Any changes made after navigation will not affect the title. - -[IMPORTANT] -A view cannot use both the [annotationname]`@PageTitle` annotation and implement the [interfacename]`HasDynamicTitle` interface simultaneously. - - -== Navigation Menu Item - -Most business applications include a navigation menu. This menu can be generated dynamically using Vaadin's [classname]`MenuConfiguration`. To make a Flow view appear in the menu, add the `@Menu` annotation: - -[source,java] ----- -@Route() -// tag::snippet[] -@Menu(title = "Dashboard", order = 1, icon = "vaadin:dashboard") -// end::snippet[] -public class DashboardView extends Main { - public DashboardView() { - //... - } -} ----- - -The [annotationname]`@Menu` annotation has the following attributes: - -`title` :: The menu title. Defaults to the page title if not specified. -`order` :: Determines the menu item's position. Items with a defined order appear above unordered items. -`icon` :: Specifies the menu icon. This is a string, allowing flexibility in interpretation. It could be an <<{articles}/components/icons#,Icon>> name or an SVG source, depending on the menu implementation. - -For more information on building a navigation menu, see <<../add-router-layout/flow#the-navigation-menu,Add a Router Layout>>. -// TODO Do we need a separate guide on building a navigation menu? - -== Package Naming - -The recommended naming convention for Java packages containing views is [packagename]`[feature].ui.view`, where `[feature]` is the name of the full-stack feature that the view belongs to. - -If the view consists of a single class only, you can store it directly in the `ui.view` package, like this: - -[source] ----- -com.example.application -└── crm <1> - └── ui - └── view - ├── CustomerOnboardingView.java <2> - ├── CustomerListView.java - └── CustomerDetailsView.java ----- -<1> The example feature is "customer relationship management". -<2> All the views are in the same `ui.view` package. - -If the view consists of more than one class, consider creating a separate package for it, like this: - -[source] ----- -com.example.application -└── crm - └── ui - └── view - ├── onboarding - │ ├── CustomerOnboardingView.java <1> - │ └── ... - ├── CustomerListView.java <2> - └── CustomerDetailsView.java ----- -<1> The onboarding view consists of multiple classes and has its own package. -<2> The other views remain in the `ui.view` package. - -If you don't know whether your new view is going to be small or large, start by putting it in the `ui.view` package. You can always refactor it into its own package later. - - -[.collapsible-list] -== Try It - -In this mini-tutorial, you'll explore both derived and explicit routes. You'll also create a new, simple view and specify multiple routes for it. - - -.Set Up the Project -[%collapsible] -==== -First, generate a new application at http://start.vaadin.com[start.vaadin.com], <> it in your IDE, and <> it with hotswap enabled. -==== - - -.Create a Dashboard View -[%collapsible] -==== -Next, you'll create a new view. Create a new package [packagename]`[application package].tutorial.ui.view`, and inside it a new class called [classname]`DashboardView`, like this: - -.DashboardView.java -[source,java] ----- -import com.vaadin.flow.component.html.Main; -import com.vaadin.flow.router.Route; - -@Route -public class DashboardView extends Main { - public DashboardView() { - setText("Dashboard View"); - } -} ----- - -The path is derived from the class name, which means you can access the view at: http://localhost:8080/dashboard -==== - - -.Delete the Main View -[%collapsible] -==== -You'll now make the dashboard view the default landing page of the application. To do this, you first have to delete the old main view. Locate the class [classname]`MainView` inside the package [packagename]`[application package].base.ui.view` and delete it. -==== - -.Add a Route Alias -[%collapsible] -==== -Next, add a `@RouteAlias("")` annotation to [classname]`DashboardView`, like this: - -.DashboardView.java -[source,java] ----- -import com.vaadin.flow.component.html.Main; -import com.vaadin.flow.router.Route; -// tag::snippet[] -import com.vaadin.flow.router.RouteAlias; -// end::snippet[] - -@Route -// tag::snippet[] -@RouteAlias("") -// end::snippet[] -public class DashboardView extends Main { - - public MainView() { - setText("Dashboard View"); - } -} ----- - -You can now access the dashboard view also at: http://localhost:8080/ -==== - - -.Try a Route with Multiple Segments -[%collapsible] -==== -Now open [classname]`TaskListView` and change the path to `manage/tasks/with/vaadin`, like this: - -.TaskListView.java -[source,java] ----- -// tag::snippet[] -@Route("manage/tasks/with/vaadin") -// end::snippet[] -@PageTitle("Task List") -@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Task List") -public class TaskListView extends Main { - ... -} ----- - -You can now access the task list view at: http://localhost:8080/manage/tasks/with/vaadin -==== - - -.Final Thoughts -[%collapsible] -==== -Now you've explored how to define and organize Flow views in a Vaadin application. You've learned how to: - -* Use both derived and explicit routes to structure your application's navigation. -* Define multiple routes for a single view, making navigation more flexible. -* Work with multi-segment routes to create more readable and meaningful URLs. - -Next, see the <<../navigate#,Navigate to a View>> guide to learn how to navigate from one view to another. -==== diff --git a/articles/building-apps/views/add-view/hilla.adoc b/articles/building-apps/views/add-view/hilla.adoc deleted file mode 100644 index 41454dafd7..0000000000 --- a/articles/building-apps/views/add-view/hilla.adoc +++ /dev/null @@ -1,171 +0,0 @@ ---- -title: Hilla -page-title: How to add a Hilla view to a Vaadin application -meta-description: Learn to create, name, and organize views in Hilla - routing conventions, directory structures, route aliases, and best practices for defining explicit routes. -order: 10 ---- - - -= Hilla Views -:toclevels: 2 - -In this guide, you'll learn how to create and name views in Hilla, assign multiple routes to a single view, and organize views into view directories. - - -== Creating Views - -Hilla views are React components that are returned by the default exported function defined in a TSX file: - -[source,tsx] -.hello.tsx ----- -export default function HelloView() { - return

Hello, World!

; -} ----- - -The default way for creating routes for views in Hilla is based on the file system structure. The route for a view is determined based on: - -1. where the respective file for a view is located, relative to the `src/main/frontend/views` directory, and - -2. how the respective file for a view is named. - -For example, if the [filename]`hello.tsx` file is located directly under `src/main/frontend/views`, then the route for `HelloView` component (default export from this file) is `/hello`, which means the file name [filename]`hello.tsx` excluding the `.tsx`. If the application is running from the root context, users would be able to access this view by navigating to `\https://example.com/hello`. Moreover, moving this file under `src/main/frontend/views/greeting` directory changes the route to `/greeting/hello`, so the users can access this view by navigating to `\https://example.com/greeting/hello`. - -Likewise, naming the view file as [filename]`hello-world.tsx` or [filename]`HelloWorld.tsx` results in the route `/hello-world` or `/HelloWorld`. - -To avoid repeating the `src/main/frontend/views` throughout this guide, `views` is used as an alias to refer to this directory. - -If a view is supposed to target the root route, the file should be named as [filename]`@index.tsx` and located directly under the `views` directory: - -[source,tsx] -.@index.tsx ----- -export default function HomeView() { - return

Home

; -} ----- - -Then, the route for the above component is `/`, and if the application is running from the root context, users can access this view by navigating to `\https://example.com/`. - - -== View Naming - -React only recognizes an exported function as a component if the function name starts with an uppercase letter. For example, the following component is recognized as a valid React component: - -[source,tsx] -.customers.tsx ----- -export default function CustomersView() { - return

Customers

; -} ----- - -Defining the function name as `customersView` or `customerList` does not result in a compile or runtime error, but is not recognized as a React component either. - -The routing uses the React component's name for creating the default automatic title for the view. For example, the title for the `CustomersView` component is `Customers`, and the title for the `HelloWorldView` component is `Hello World`. This automatically-determined title is used when creating the navigation menu based on utilities from the routing API. - -Another important convention to consider while naming the views and directories is to use the `_` (underscore) character at the beginning of the file or directory name to instruct the routing system to ignore it. For example, a file named as `_AddressFormComponent.tsx` is ignored when creating routes for views. This is useful for creating utility files and reusable components that are not intended to be available as navigation targets. - -The details about the automatic title and the navigation menu are covered in more detail in the <<../navigate#,Navigate to a View>> guide. - - -== Route Aliases - -In Hilla, there is no shortcut for creating multiple routes that target the same view. However, you can create a new view file that exports a component returning the target component. You can place this alias component in any directory to create desired route alias. For example, the following view targets the root route (`/`): - -[source,tsx] -.@index.tsx ----- -export default function RootView() { - return

Home

; -} ----- - -Now to have the same view accessible via `/home` and `/main`, you can create two additional view files: - -[source,tsx] -.home.tsx ----- -export default function HomeView() { - return RootView(); -} ----- - -and - -[source,tsx] -.main.tsx ----- -export default function MainView() { - return RootView(); -} ----- - - -== Directory Structure - -As mentioned earlier, the file system structure determines the route for a view. Therefore, organizing views into directories helps maintain a clear structure for views and routes. - -Simple views that consist of only one file can be placed directly under the `views` directory. For example, "About", "Contact Us", and the "Home" view do not need a complex composition of components, nor should have a long route, so it is a good practice to place them directly under the `views` directory: - -[source] ----- -src -└── main - └── frontend - └── views - ├── @index.tsx - ├── about.tsx - └── contact-us.tsx ----- - -This way, the routes for these views are `/`, `/about`, and `/contact-us`, respectively. - -However, for more complex views that consist of other components, or if they should accept route parameters, it is recommended to place all the files related to that view under a directory, named by the functionality they provide. For example, views related to customers can be grouped under a `customers` directory, and views related to products under a `products` directory. The following is an example of a directory structure for view files that handle the customer related functionalities: - -[source] ----- -src -└── main - └── frontend - └── views - └── customers - ├── {id} <1> - │ ├── edit.tsx <2> - │ └── index.tsx <3> - ├── @index.tsx <4> - └── new.tsx <5> ----- -1. The `{id}` directory is a placeholder for the route parameter. You will learn more about route parameters in the <<../navigate#,Navigate to a View>> guide. -2. The [filename]`edit.tsx` file is responsible for editing a specified customer details. The route for this view is `/customers/:id/edit`. -3. The [filename]`@index.tsx` file is responsible for displaying the details of a specified customer. The route for this view is `/customers/:id`. -4. The [filename]`index.tsx` file is responsible for displaying the list of customers. The route for this view is `/customers`. -5. The [filename]`new.tsx` file is responsible for adding a new customer. The route for this view is `/customers/new`. - -As this guide focuses on basics of creating views in Hilla, further details about routing conventions are covered in the <<{articles}/hilla/guides/routing#, Routing>> guide. - - -== Defining Explicit Routes - -So far, you have learned how to create views and how routes are automatically resolved based on the file system structure and file name. However, if you want to have a custom route for a view, you can export a `ViewConfig` object named `config` from the view file. The path specified for the `route` overrides the automatically-resolved path according to the routing conventions. For example, the following view has a custom route `/custom-route`: - -[source,tsx] -.hello.tsx ----- -import { ViewConfig } from "@vaadin/hilla-file-router/types.js"; - -export const config: ViewConfig = { - route: "/custom-route", -}; - -export default function HelloView() { - return

Hello, World!

; -} ----- - -Now, users can access this view by navigating to `\https://example.com/custom-route`. - -[NOTE] -Avoid using explicit routes unless absolutely necessary. The routing system is designed to automatically resolve the routes based on the file system structure and the file name, which helps to keep the routes consistent and predictable. - diff --git a/articles/building-apps/views/add-view/index.adoc b/articles/building-apps/views/add-view/index.adoc deleted file mode 100644 index 8fc0ed009f..0000000000 --- a/articles/building-apps/views/add-view/index.adoc +++ /dev/null @@ -1,35 +0,0 @@ ---- -title: Add a View -page-title: How to add a view to a Vaadin application -description: Learn how to add a view to a Vaadin application. -meta-description: Learn how to add a view to a Vaadin application using either Vaadin Flow or Hilla. -layout: tabbed-page -tab-title: Overview -order: 10 ---- - - -= Add a View - -In a Vaadin application, a _view_ is a user interface component that forms a logical whole of the user interface. A page can contain other components than a view, but only one view at a time. Typically, the view takes up most of the page. - -Each view is associated with its own URL path. When the user navigates from one view to another, the URL of the page changes. The mapping between a URL path and a view is called a _route_, where the view is the _target_ of the route. - -[NOTE] -All routes must be unique to allow the routing logic to determine the view to render without any disambiguation. When conflicts are detected, the application fails to start and logs a message explaining the reason for the conflict. - -In this screenshot, the visible view is the _main_ view. It is mapped to the `""` path: - -image::images/root-view.png[Example of a main view] - -[NOTE] -The main view is sometimes also called the _root_ view. The terms are interchangeable. - -In this screenshot, the visible view is mapped to the `"customers"` path: - -image::images/customers-view.png[] - -You can write your views using either Flow and Java, or Hilla, React and TypeScript -- in the same application. The following guides show you how: - -* <> -* <> diff --git a/articles/building-apps/views/add-view/images/customers-view.png b/articles/building-apps/views/images/customers-view.png similarity index 100% rename from articles/building-apps/views/add-view/images/customers-view.png rename to articles/building-apps/views/images/customers-view.png diff --git a/articles/building-apps/views/add-router-layout/images/main-layout.png b/articles/building-apps/views/images/main-layout.png similarity index 100% rename from articles/building-apps/views/add-router-layout/images/main-layout.png rename to articles/building-apps/views/images/main-layout.png diff --git a/articles/building-apps/views/images/master-detail.png b/articles/building-apps/views/images/master-detail.png new file mode 100644 index 0000000000..44ffc88604 Binary files /dev/null and b/articles/building-apps/views/images/master-detail.png differ diff --git a/articles/building-apps/views/images/master-detail2.png b/articles/building-apps/views/images/master-detail2.png new file mode 100644 index 0000000000..3d53b73c14 Binary files /dev/null and b/articles/building-apps/views/images/master-detail2.png differ diff --git a/articles/building-apps/views/add-router-layout/images/nested-layout.png b/articles/building-apps/views/images/nested-layout.png similarity index 100% rename from articles/building-apps/views/add-router-layout/images/nested-layout.png rename to articles/building-apps/views/images/nested-layout.png diff --git a/articles/building-apps/views/add-view/images/root-view.png b/articles/building-apps/views/images/root-view.png similarity index 100% rename from articles/building-apps/views/add-view/images/root-view.png rename to articles/building-apps/views/images/root-view.png diff --git a/articles/building-apps/views/navigate/flow.adoc b/articles/building-apps/views/navigate.adoc similarity index 55% rename from articles/building-apps/views/navigate/flow.adoc rename to articles/building-apps/views/navigate.adoc index 14ffb1ed50..fc024c70c4 100644 --- a/articles/building-apps/views/navigate/flow.adoc +++ b/articles/building-apps/views/navigate.adoc @@ -1,15 +1,38 @@ --- -title: Flow -page-title: How to navigate to a view in Flow | Vaadin -meta-description: Learn how to navigate between views in Vaadin Flow using RouterLink and UI.navigate(), and improve code readability by encapsulating navigation logic. -order: 5 +title: Navigate to a View +page-title: How to navigate to a view in a Vaadin application +description: Learn how to navigate between the views of a Vaadin application. +meta-description: Learn how to navigate between views in Vaadin using links, APIs, or direct URL changes, with a router that handles view rendering and parameter passing. +order: 15 --- -= Navigation in Flow += Navigate to a View :toclevels: 2 -In this guide, you'll learn how to use [classname]`RouterLink` and [methodname]`UI.navigate()` to navigate between views. You'll also learn how to improve the readability of your code by encapsulating some of the navigation logic into your own API. At the end, a mini-tutorial helps you to apply these concepts in a real Vaadin application. +In this guide, you'll learn how to use [classname]`RouterLink` and [methodname]`UI.navigate()` to navigate between views. You'll also learn how to improve the readability of your code by encapsulating some of the navigation logic into your own API. + +You can navigate to a view either programmatically through an API, by clicking a link, or by changing the URL of the browser. + +In a Vaadin application, navigation is handled by a _router_. The router takes care of rendering the correct view (i.e. the route target) and keeping the URL in sync. + +If the view accepts parameters, the router also extracts them from the URL and passes them on to the view. This is covered in the <> guide. + + +== Copy-Paste into Your Project + +If you want to quickly try out navigating between views, you can copy-paste the following two classes into your Vaadin project: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/navigate/HomeView.java[tags=snippet,indent=0] +---- +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/navigate/AboutView.java[tags=snippet,indent=0] +---- + +For more detailed instructions on how to navigate between views, continue reading below. == Router Links @@ -27,6 +50,8 @@ var link = new RouterLink("Home", MainView.class); myLayout.add(link); ---- +=== Route Parameters in Links + If the view is accepting a single route parameter, you can pass the parameter value to the [classname]`RouterLink` constructor. In the following example, [classname]`CustomerDetailsView` implements the [interfacename]`HasUrlParameter` interface and takes a single string parameter - the customer's ID. The link navigates to the details of the customer with ID `"cu1234"`: @@ -37,7 +62,7 @@ var link = new RouterLink("Customer Details", CustomerDetailsView.class, "cu1234 myLayout.add(link); ---- -If the view is accepting multiple route parameters, you need to construct an instance of [classname]`RouteParameters` and pass it to the [classname]`RouterLink` constructor. You can construct it in different ways; see its https://vaadin.com/api/platform/current/com/vaadin/flow/router/RouteParameters.html[API documentation] for details. +If the view is accepting multiple route parameters, you need to construct an instance of [classname]`RouteParameters` and pass it to the [classname]`RouterLink` constructor. You can construct it in different ways; see its https://vaadin.com/api/platform/{moduleMavenVersion:com.vaadin:vaadin}/com/vaadin/flow/router/RouteParameters.html[API documentation] for details. // TODO Should the API link be versioned? The following example creates a link to the customer details view with two route parameters; `customerId` with the value of `"cu1234"`, and `mode` with the value of `"edit"`: @@ -49,7 +74,7 @@ var link = new RouterLink("Edit Customer", CustomerDetailsView.class, myLayout.add(link); ---- -For more information about route parameters, see the <<../pass-data/route-parameters#,Route Parameters>> guide. +For more information about route parameters, see the <> guide. == Programmatic Navigation @@ -67,6 +92,8 @@ button.addClickListener(event -> ); ---- +=== Route Parameters in Programmatic Navigation + If the view is accepting a single route parameter, you can pass the parameter value to [methodname]`UI.navigate()`, like this: [source,java] @@ -118,6 +145,9 @@ button.addClickListener(event -> ); ---- + +=== Multiple Route Parameters + If you use multiple route parameters, or custom parameter types, this approach becomes even more useful. In the following example, the [classname]`CustomerDetailsView` accepts two route parameters; a value object [classname]`CustomerId` and an enum [classname]`Mode`: @@ -177,11 +207,11 @@ button.addClickListener(event -> == React Views -So far, all the examples have covered navigating from one Flow view to another. However, you can also navigate from a Flow view to a React view. Unlike Flow views, which use Java class references for navigation, React views require string-based routes because they don't have a corresponding Java class. +So far, all the examples have covered navigating from one Java view to another. However, you can also navigate from a Java view to a React view. Unlike Java views, which use class references for navigation, React views require string-based routes because they don't have a corresponding Java class. You can use anchor elements for navigation, or trigger programmatic navigation using [methodname]`UI.navigate()`. -In Flow, you create anchors like this: +You create anchors like this: [source,java] ---- @@ -200,160 +230,4 @@ var button = new Button("Go to React view"); button.addClickListener(event -> UI.getCurrent().navigate("path/to/react/view")); ---- - -[.collapsible-list] -== Try It - -In this mini-tutorial, you'll learn how to navigate between Flow views using both *links* and *programmatic navigation*. Route parameters are not covered here, as they have their own dedicated guides. - - -.Set Up the Project -[%collapsible] -==== -First, generate a new application at http://start.vaadin.com[start.vaadin.com], <> it in your IDE, and <> it with hotswap enabled. - -[NOTE] -If you completed the mini-tutorial on <<../add-view/flow#try-it,adding views>>, you can continue using the same project. - -==== - - -.Create the Links View -[%collapsible] -==== -Now, you'll create a new view that provides multiple ways to navigate to the task list view. Create a new package [packagename]`[application package].tutorial.ui.view` package, and in it a class called `LinksView`: - -.LinksView.java -[source,java] ----- -import com.vaadin.flow.component.html.Main; -import com.vaadin.flow.router.Route; - -@Route("links") -public class LinksView extends Main { - public LinksView() { - } -} ----- -==== - - -.Add a Router Link -[%collapsible] -==== -A [classname]`RouterLink` creates a clickable link to another view. Modify [classname]`LinksView` to include a link to the [classname]`TaskListView`: - -.LinksView.java -[source,java] ----- -import com.vaadin.flow.component.html.Main; -import com.vaadin.flow.router.Route; -// tag::snippet[] -import com.vaadin.flow.router.RouterLink; -// end::snippet[] - -@Route("links") -public class LinksView extends Main { - - public LinksView() { -// tag::snippet[] - add(new RouterLink("Task List", TaskListView.class)); -// end::snippet[] - } -} ----- - -Now, open your browser and go to: http://localhost:8080/links - -Hover on the "Task List" link to see that it points to `\http://localhost:8080`. Click the link to navigate to the task list view, then use the *browser's back button* to return. -==== - - -.Navigate Programmatically -[%collapsible] -==== -Next, you'll add a button that navigates to the task list view when clicked. Modify [classname]`LinksView` to include a [classname]`Button`: - -.LinksView.java -[source,java] ----- -// tag::snippet[] -import com.vaadin.flow.component.UI; -import com.vaadin.flow.component.button.Button; -// end::snippet[] -import com.vaadin.flow.component.html.Main; -import com.vaadin.flow.router.Route; -import com.vaadin.flow.router.RouterLink; - -@Route("links") -public class LinksView extends Main { - - public LinksView() { - add(new RouterLink("Task List", TaskListView.class)); - // tag::snippet[] - add(new Button("Task List", - event -> UI.getCurrent().navigate(TaskListView.class))); - // end::snippet[] - } -} ----- - -Switch back to the browser. Thanks to *hotswap*, the new [guibutton]*Task List* button should appear automatically. Click it to navigate to the task list view. -==== - - -.Create an API -[%collapsible] -==== -To make navigation more reusable and readable, you'll now create a dedicated method for navigating to the task list view. - -Open [classname]`TaskListView` and add this method: - -.TaskListView.java -[source,java] ----- -@Route("") -@PageTitle("Task List") -@Menu(order = 0, icon = "vaadin:clipboard-check", title = "Task List") -public class TaskListView extends Main { - ... - // tag::snippet[] - public static void showTasks() { - UI.getCurrent().navigate(TaskListView.class); - } - // end::snippet[] -} ----- - -Now, update [classname]`LinksView` to use this method instead of calling [methodname]`UI.getCurrent().navigate()` directly: - -.LinksView.java -[source,java] ----- -@Route("links") -public class LinksView extends Main { - - public LinksView() { - add(new RouterLink("Task List", TaskListView.class)); - // tag::snippet[] - add(new Button("Task List", event -> TaskListView.showTasks())); - // end::snippet[] - } -} ----- - -Go back to the browser and click the button. It works the same as before, but your code is *cleaner and easier to maintain*. -==== - - -.Final Thoughts -[%collapsible] -==== -You've now explored different ways to navigate between Flow views. Here's what you've learned: - -* Creating a navigation link using [classname]`RouterLink`. -* Programmatically navigating using [methodname]`UI.navigate()`. -* Building a reusable navigation API, improving code readability. - -Now that you know how to navigate between views, check out the <<../pass-data#,Pass Data to a View>> guide to learn how to pass data to a view while navigating to it. -==== \ No newline at end of file +For more information about using React views in Vaadin, see the <> guides. \ No newline at end of file diff --git a/articles/building-apps/views/navigate/hilla.adoc b/articles/building-apps/views/navigate/hilla.adoc deleted file mode 100644 index 6b52f58d91..0000000000 --- a/articles/building-apps/views/navigate/hilla.adoc +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: Hilla -page-title: How to navigate to a view in Hilla | Vaadin -description: Learn how to navigate between Hilla views in a Vaadin application. -meta-description: This guide covers how to navigate to a Hilla or Flow view in Vaadin applications, including creating links and programmatic navigation. -order: 10 ---- - - -= Navigation in Hilla - -In this guide, you'll learn how to use `` component and `useNavigate` hook of `react-router` to navigate between views. - - -== Navigating Between Views Using Links - -The `` component from `react-router` renders a clickable link for navigation. In HTML, it corresponds to an anchor (``) element. - -[TIP] -Links are preferable to programmatic navigation because they *improve accessibility*. They also allow users to open links in new browser tabs. - - -For example, to create a link to a view located at `/some-view`, you can use the following code: - -[source,tsx] ----- -import { NavLink } from 'react-router'; - -Some View ----- - -This code creates a clickable link labeled "Some View" that navigates to the `/some-view` route when clicked. - - -== Programmatic Navigation - -In some scenarios, you may need to navigate between views programmatically, such as after a form submission or in response to user interactions. For this you can use the `useNavigate` hook of `react-router` to achieve this. - -Here's an example of how to use `useNavigate` for programmatic navigation: - -[source,tsx] ----- -import { useNavigate } from 'react-router'; - -function MyComponent() { -const navigate = useNavigate(); - - const handleClick = () => { - navigate('/target-view'); - }; - - return ( - - ); -} ----- - -In the above example, clicking the button navigates the user to `/target-view`. - - -== Flow Views - -To navigate from a Hilla view to a Flow view that is implemented in Java, the same principles apply. You can use the `NavLink` component or the `useNavigate` hook to navigate to the Flow view. - diff --git a/articles/building-apps/views/navigate/index.adoc b/articles/building-apps/views/navigate/index.adoc deleted file mode 100644 index b0a0519948..0000000000 --- a/articles/building-apps/views/navigate/index.adoc +++ /dev/null @@ -1,25 +0,0 @@ ---- -title: Navigate to a View -page-title: How to navigate to a view in a Vaadin application -description: Learn how to navigate between the views of a Vaadin application. -meta-description: Learn how to navigate between views in Vaadin using links, APIs, or direct URL changes, with a router that handles view rendering and parameter passing. -layout: tabbed-page -tab-title: Overview -order: 15 ---- - - -= Navigate to a View - -You can navigate to a view either programmatically through an API, by clicking a link, or by changing the URL of the browser. - -In a Vaadin application, navigation is handled by a _router_. The router takes care of rendering the correct view (i.e. the route target) and keeping the URL in sync. If the view accepts parameters, the router also extracts them from the URL and passes them on to the view. - -Flow and Hilla views are handled by the same router. You can navigate to a Flow view from a Hilla view, and vice versa. - -The following guides teach you how to navigate between views in Vaadin: - -* <> -* <> - -// TODO Write a deep-dive about routing diff --git a/articles/building-apps/views/pass-data/query-parameters.adoc b/articles/building-apps/views/pass-data/query-parameters.adoc new file mode 100644 index 0000000000..6301a4ced6 --- /dev/null +++ b/articles/building-apps/views/pass-data/query-parameters.adoc @@ -0,0 +1,148 @@ +--- +title: Query Parameters +page-title: How to use query parameters in a Vaadin application +description: Learn how to manage state with query parameters in Vaadin. +meta-description: Learn how to use query parameters in Vaadin to store view state, manage sorting and filtering, and enhance navigation. +order: 30 +--- + + += Query Parameters +:toclevels: 2 + +In this guide, you'll learn how to access and set query parameters in a Vaadin view. + + +== Copy-Paste into Your Project + +If you want to quickly try out query parameters in your Vaadin application, copy-paste the following code into a new Java class named [classname]`QueryParameterView` in your project's main package: + +[source,java] +---- +include::{root}/src/main/java/com/vaadin/demo/buildingapps/passdata/QueryParameterView.java[tags=snippet,indent=0] +---- + +For more detailed instructions on how to use route parameters, continue reading below. + + +== What Are Query Parameters? + +Query parameters are key-value pairs appended to the end of a URL after a `?` character. Multiple parameters are separated by `&`. + +For example, this URL: `/orders?sort=date&filter=shipped` + +Contains the following query parameters: + +* `sort` -> `"date"` +* `filter` -> `"shipped"` + +Query parameters are commonly used to store view-related state, such as a grid's sort order or a search field's value. Since they are optional, always provide default values when a parameter is missing. + +[TIP] +If you need required parameters, consider using <> instead. + + +== Accessing Query Parameter Values + +You access query parameters similarly to <>. Your view must implement the [interfacename]`BeforeEnterObserver` interface, which defines the [methodname]`beforeEnter()` method. Inside this method, you can retrieve query parameters: + +[source,java] +---- +@Route +public class OrdersView extends Main implements BeforeEnterObserver { + + private static final String QUERY_PARAM_SORT = "sort"; // <1> + private static final String QUERY_PARAM_FILTER = "filter"; + + @Override + public void beforeEnter(BeforeEnterEvent event) { + event.getLocation().getQueryParameters().getSingleParameter(QUERY_PARAM_SORT) + .ifPresent(sort -> { + // Process the sort parameter + }); + event.getLocation().getQueryParameters().getSingleParameter(QUERY_PARAM_FILTER) + .ifPresent(filter -> { + // Process the filter parameter + }); + } + ... +} +---- +<1> *Tip:* To improve readability and maintainability, declare query parameter names as constants. + +Now, if you navigate to `/orders?sort=date&filter=shipped`, the query parameter values will be: + +* `sort` -> `"date"` +* `filter` -> `"shipped"` + +The [classname]`QueryParameters` class provides methods for accessing query parameter values. Familiarize yourself with its API if you frequently use query parameters in your application. + +[NOTE] +You can also retrieve query parameters using [methodname]`UI.getCurrent().getActiveViewLocation().getQueryParameters()`. + +Since query parameters are always strings and optional, they may be empty or contain invalid data. To handle this, you can provide default values, redirect users to a different view, or display an error message. + + +== Setting Query Parameters + +The [classname]`QueryParameters` class is immutable, meaning you can't modify existing query parameters. Instead, you must create a new [classname]`QueryParameters` object and pass it to [methodname]`UI.navigate()`. Query parameters are set when navigating to a view. + +In the following example, [classname]`OrdersView` provides a <<../navigate#your-own-api,custom API>> for navigating to itself while setting the `filter` query parameter: + +[source,java] +---- +@Route +public class OrdersView extends Main implements BeforeEnterObserver { + + private static final String QUERY_PARAM_SORT = "sort"; + private static final String QUERY_PARAM_FILTER = "filter"; + +// tag::snippet[] + public static void showOrders(@Nullable String filter) { + var queryParameters = filter == null ? QueryParameters.empty() + : QueryParameters.of(QUERY_PARAM_FILTER, filter); + UI.getCurrent().navigate(OrdersView.class, queryParameters); + } +// end::snippet[] + ... +} +---- + +[IMPORTANT] +You _cannot_ set query parameters to `null`. To clear a query parameter, exclude it from the [classname]`QueryParameters` object. + + +=== Updating Query Parameters Dynamically + +Query parameters are often set dynamically, such as when users apply filters or change sort options. In such cases, the view must navigate to itself while updating query parameters. + +When navigating to the _same_ view, the existing view instance is reused. The browser URL updates, and [methodname]`beforeEnter()` is invoked again. + +The following example updates the `filter` query parameter when a user types in a text field: + +[source,java] +---- +@Route +public class OrdersView extends Main implements BeforeEnterObserver { + + private static final String QUERY_PARAM_SORT = "sort"; + private static final String QUERY_PARAM_FILTER = "filter"; + +// tag::snippet[] + public OrdersView() { + var filterField = new TextField(); + add(filterField); + + filterField.addValueChangeListener(event -> { + var queryParameters = UI.getCurrent().getActiveViewLocation() + .getQueryParameters() + .merging(QUERY_PARAM_FILTER, event.getValue()); + UI.getCurrent().navigate(OrdersView.class, queryParameters); + }); + } +// end::snippet[] + ... +} +---- + +If the original URL was `/orders?sort=date&filter=shipped`, and the user enters "foobar" in the text field, the URL updates to `/orders?sort=date&filter=foobar`. diff --git a/articles/building-apps/views/pass-data/query-parameters/flow.adoc b/articles/building-apps/views/pass-data/query-parameters/flow.adoc deleted file mode 100644 index 5b8d0f4ca9..0000000000 --- a/articles/building-apps/views/pass-data/query-parameters/flow.adoc +++ /dev/null @@ -1,273 +0,0 @@ ---- -title: Flow -page-title: How to use query parameters in Flow | Vaadin -meta-description: Learn how to access, set, and dynamically update query parameters in Vaadin Flow to manage view state, filters, and sorting in your applications. -order: 5 ---- - - -= Query Parameters in Flow -:toclevels: 2 - -In this guide, you'll learn how to access and set query parameters in a Flow view. A mini-tutorial at the end will help you apply these concepts in a real Vaadin application. - - -== Accessing Query Parameter Values - -You access query parameters similarly to <<../route-templates#accessing-route-parameter-values,multiple route parameters>>. Your view must implement the [interfacename]`BeforeEnterObserver` interface, which defines the [methodname]`beforeEnter()` method. Inside this method, you can retrieve query parameters: - -[source,java] ----- -@Route -public class OrdersView extends Main implements BeforeEnterObserver { - - private static final String QUERY_PARAM_SORT = "sort"; // <1> - private static final String QUERY_PARAM_FILTER = "filter"; - - @Override - public void beforeEnter(BeforeEnterEvent event) { - event.getLocation().getQueryParameters().getSingleParameter(QUERY_PARAM_SORT) - .ifPresent(sort -> { - // Process the sort parameter - }); - event.getLocation().getQueryParameters().getSingleParameter(QUERY_PARAM_FILTER) - .ifPresent(filter -> { - // Process the filter parameter - }); - } - ... -} ----- -<1> *Tip:* To improve readability and maintainability, declare query parameter names as constants. - -Now, if you navigate to `/orders?sort=date&filter=shipped`, the query parameter values will be: - -* `sort` -> `"date"` -* `filter` -> `"shipped"` - -The [classname]`QueryParameters` class provides methods for accessing query parameter values. Familiarize yourself with its API if you frequently use query parameters in your application. - -[NOTE] -You can also retrieve query parameters using [methodname]`UI.getCurrent().getActiveViewLocation().getQueryParameters()`. - -Since query parameters are always strings and optional, they may be empty or contain invalid data. To handle this, you can provide default values, redirect users to a different view, or display an error message. - - -== Setting Query Parameters - -The [classname]`QueryParameters` class is immutable, meaning you can't modify existing query parameters. Instead, you must create a new [classname]`QueryParameters` object and pass it to [methodname]`UI.navigate()`. Query parameters are set when navigating to a view. - -In the following example, [classname]`OrdersView` provides a <<../../navigate/flow#your-own-api,custom API>> for navigating to itself while setting the `filter` query parameter: - -[source,java] ----- -@Route -public class OrdersView extends Main implements BeforeEnterObserver { - - private static final String QUERY_PARAM_SORT = "sort"; - private static final String QUERY_PARAM_FILTER = "filter"; - -// tag::snippet[] - public static void showOrders(@Nullable String filter) { - var queryParameters = filter == null ? QueryParameters.empty() - : QueryParameters.of(QUERY_PARAM_FILTER, filter); - UI.getCurrent().navigate(OrdersView.class, queryParameters); - } -// end::snippet[] - ... -} ----- - -[IMPORTANT] -You _cannot_ set query parameters to `null`. To clear a query parameter, exclude it from the [classname]`QueryParameters` object. - - -=== Updating Query Parameters Dynamically - -Query parameters are often set dynamically, such as when users apply filters or change sort options. In such cases, the view must navigate to itself while updating query parameters. - -When navigating to the _same_ view, the existing view instance is reused. The browser URL updates, and [methodname]`beforeEnter()` is invoked again. - -The following example updates the `filter` query parameter when a user types in a text field: - -[source,java] ----- -@Route -public class OrdersView extends Main implements BeforeEnterObserver { - - private static final String QUERY_PARAM_SORT = "sort"; - private static final String QUERY_PARAM_FILTER = "filter"; - -// tag::snippet[] - public OrdersView() { - var filterField = new TextField(); - add(filterField); - - filterField.addValueChangeListener(event -> { - var queryParameters = UI.getCurrent().getActiveViewLocation() - .getQueryParameters() - .merging(QUERY_PARAM_FILTER, event.getValue()); - UI.getCurrent().navigate(OrdersView.class, queryParameters); - }); - } -// end::snippet[] - ... -} ----- - -If the original URL was `/orders?sort=date&filter=shipped`, and the user enters "foobar" in the text field, the URL updates to `/orders?sort=date&filter=foobar`. - - -[.collapsible-list] -== Try It - -In this mini-tutorial, you'll create a view that accesses and dynamically updates two query parameters. - - -.Set Up the Project -[%collapsible] -==== -First, generate a new application at http://start.vaadin.com[start.vaadin.com], <> it in your IDE, and <> it with hotswap enabled. -==== - - -.Create the View -[%collapsible] -==== -Create a new package [packagename]`[application package].tutorial.ui.view`. Then, create a class named [classname]`QueryParameterView`: - -.QueryParameterView.java -[source,java] ----- -import com.vaadin.flow.component.checkbox.Checkbox; -import com.vaadin.flow.component.html.Main; -import com.vaadin.flow.component.textfield.TextField; -import com.vaadin.flow.router.BeforeEnterEvent; -import com.vaadin.flow.router.BeforeEnterObserver; -import com.vaadin.flow.router.Route; - -@Route -public class QueryParameterView extends Main implements BeforeEnterObserver { - - private static final String QUERY_PARAMETER_TEXT = "text"; - private static final String QUERY_PARAMETER_CHECK = "check"; - - private final TextField textField; - private final Checkbox checkbox; - - public QueryParameterView() { - textField = new TextField(); - checkbox = new Checkbox(); - add(textField, checkbox); - } - - @Override - public void beforeEnter(BeforeEnterEvent event) { - } -} ----- - -Open your browser and navigate to: http://localhost:8080/queryparameter - -You should see an empty text field and an unchecked checkbox. -==== - - -.Access Query Parameters -[%collapsible] -==== -Modify the [methodname]`beforeEnter()` method to populate the components: - -[source,java] ----- -@Override -public void beforeEnter(BeforeEnterEvent event) { - var queryParameters = event.getLocation().getQueryParameters(); - queryParameters.getSingleParameter(QUERY_PARAMETER_TEXT) - .ifPresent(textField::setValue); - queryParameters.getSingleParameter(QUERY_PARAMETER_CHECK) - .map(Boolean::parseBoolean).ifPresent(checkbox::setValue); -} ----- - -Navigate to: http://localhost:8080/queryparameter?text=hello+world&check=true - -The text field should now contain `"hello world"` and the checkbox should be checked. -==== - - -.Update the `text` Query Parameter -[%collapsible] -==== -Modify the constructor to update the query parameter dynamically: - -[source,java] ----- -public QueryParameterView() { - textField = new TextField(); -// tag::snippet[] - textField.setValueChangeMode(ValueChangeMode.LAZY); // <1> - textField.addValueChangeListener(event -> { - var queryParameters = UI.getCurrent().getActiveViewLocation() - .getQueryParameters().merging(QUERY_PARAMETER_TEXT, event.getValue()); - UI.getCurrent().navigate(QueryParameterView.class, queryParameters); - }); -// end::snippet[] - checkbox = new Checkbox(); - add(textField, checkbox); -} ----- -<1> The field updates 400 ms after the user stops typing. - -Try changing the text field value in the browser. The URL updates automatically. -==== - - -.Update the `check` Query Parameter -[%collapsible] -==== -Add a listener for the checkbox: - -[source,java] ----- -public QueryParameterView() { - textField = new TextField(); - textField.setValueChangeMode(ValueChangeMode.LAZY); // <1> - textField.addValueChangeListener(event -> { - var queryParameters = UI.getCurrent().getActiveViewLocation() - .getQueryParameters() - .merging(QUERY_PARAMETER_TEXT, event.getValue()); - UI.getCurrent().navigate(QueryParameterView.class, queryParameters); - }); - checkbox = new Checkbox(); -// tag::snippet[] - checkbox.addValueChangeListener(event -> { - var queryParameters = UI.getCurrent().getActiveViewLocation() - .getQueryParameters() - .merging(QUERY_PARAMETER_CHECK, event.getValue().toString()); - UI.getCurrent().navigate(QueryParameterView.class, queryParameters); - }); -// end::snippet[] - add(textField, checkbox); -} ----- - -Try toggling the checkbox. The URL updates automatically while preserving the `text` parameter. -==== - - -.Final Thoughts -[%collapsible] -==== -You've now successfully implemented query parameters in Flow. You learned how to: - -* Access query parameter values. -* Update query parameter values dynamically. - -Next, try modifying the code to: - -* Use `1` and `0` instead of `true` and `false` for the `check` parameter. -* Validate the `text` query parameter to allow only *letters, digits and whitespace*. - -You're now ready to use query parameters in real Vaadin applications! -==== diff --git a/articles/building-apps/views/pass-data/query-parameters/hilla.adoc b/articles/building-apps/views/pass-data/query-parameters/hilla.adoc deleted file mode 100644 index 05eb5249d0..0000000000 --- a/articles/building-apps/views/pass-data/query-parameters/hilla.adoc +++ /dev/null @@ -1,148 +0,0 @@ ---- -title: Hilla -page-title: How to use query parameters in Hilla | Vaadin -meta-description: Learn how to use query parameters in Hilla to manage view state, handle search and filtering, and enhance navigation in Hilla applications. -order: 10 ---- - - -= Query Parameters in Hilla -:toclevels: 2 - -In this guide, you'll learn how to access and set query parameters in a Hilla view using React. - - -== Using Query Parameters - -To work with query parameters in Hilla views, you can use the `useSearchParams` hook from `react-router`. This hook provides methods to read, update, and delete query parameters without causing a full page reload. - -The `useSearchParams` hook returns an array with two elements: - -- `searchParams` -- a `URLSearchParams` object for reading parameters. -- `setSearchParams` -- a function to update or clear query parameters. - -Here's an example of a view that manages a search term using query parameters: - -[source,tsx] ----- -// tag::snippet[] -import { useSearchParams } from 'react-router'; -// end::snippet[] -import { TextField } from '@vaadin/react-components/TextField.js'; - -export default function ProductView() { -// tag::snippet[] - const [searchParams, setSearchParams] = useSearchParams(); - const searchTerm = searchParams.get('category') || ''; -// end::snippet[] - return ( -
- { - const newValue = e.detail.value; - if (newValue) { -// tag::snippet[] - setSearchParams({ category: newValue }); -// end::snippet[] - } else { -// tag::snippet[] - setSearchParams({}); -// end::snippet[] - } - }} - /> -
Current search term: {searchTerm}
-
- ); -} ----- - -In this example, the `category` parameter is used to store the search term. When a user types a new value, `setSearchParams` updates the query parameter accordingly. If the value is empty, passing an empty object to `setSearchParams` clears all query parameters. - - -== Multiple Query Parameters - -You can work with multiple query parameters simultaneously. In the following example, a view manages both a search filter (`category`) and a sorting order (`sort`) while ensuring any other query parameters are preserved when updating values: - -[source,tsx] ----- -import { useSearchParams } from 'react-router'; -import { TextField } from '@vaadin/react-components/TextField.js'; -import { Select } from "@vaadin/react-components"; - -export default function ProductView() { - const [searchParams, setSearchParams] = useSearchParams(); - const category = searchParams.get('category') || ''; - const sortOrder = searchParams.get('sort') || 'asc'; - - // Function to update query parameters while preserving existing ones - const updateParams = (params: Record) => { - setSearchParams({ - ...Object.fromEntries(searchParams), - ...params - }); - }; - - // Ensure URL parameters reflect the current state - if (category !== searchParams.get('category') - || sortOrder !== searchParams.get('sort')) { - updateParams({ category: category, sort: sortOrder }); - } - - return ( -
- updateParams({ category: e.detail.value })} - /> -