> Markdown version of [Show Details](https://vaadin.com/docs/next/getting-started/tutorial/show-details). Section index: [llms.txt](https://vaadin.com/docs/next/getting-started/llms.txt)

# Show Product Details

In the previous step, you implemented sorting and filtering in the product catalog. However, the database contains more information about each product than what is visible in the grid.

In this step, you’ll create a drawer and a form for showing the product details. You’ll then add a Form Data Object (FDO) and bind it to the form. Finally, you’ll fetch the FDO and populate the form whenever the user selects an item in the product catalog grid.

> **Tip: More Information Available**
>
> This step is based on the [Add a Form](https://vaadin.com/docs/next/building-apps/forms-data/add-form.md) guide. Read the guide if you want more detailed information about working with forms in Vaadin.

## <a id="add-a-product-form-drawer"></a>Add a Product Form Drawer

You’ll display the product details in a drawer next to the grid. Start by creating the drawer component - `ProductFormDrawer` - in the `com.example.product` package:

ProductFormDrawer.java

```java
package com.example.product;

import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;

class ProductFormDrawer extends Composite<VerticalLayout> { // (1)

    ProductFormDrawer() {
        var header = new H2("Product Details");

        var layout = getContent(); // (2)
        layout.add(header);
    }
}
```

1. Create a [composite component](https://vaadin.com/docs/next/flow/component-internals/basic.md#composite-components) that encapsulates a `VerticalLayout`.

2. The `getContent()` method returns the encapsulated layout.

Next, add the drawer to `ProductCatalogView`, turning it into a master-detail view. You’ll use the [Master-Detail Layout](https://vaadin.com/docs/next/components/master-detail-layout.md) component for this. It has a *master* area, which shows the product list, and a *detail* area, which shows the drawer. When there’s enough room, it shows the areas side by side, with a border between them. When there isn’t, it shows the detail area as an overlay on top of the master area. See [Add a Master-Detail View](https://vaadin.com/docs/next/building-apps/views/add-master-detail.md) for more about this pattern.

This requires two steps:

1. Make `ProductCatalogView` extend `MasterDetailLayout` instead of `VerticalLayout`.

2. Put the search field and the grid in the master area, and the drawer in the detail area.

Change the code of the `ProductCatalogView` as follows:

ProductCatalogView\.java

```java
package com.example.product;

import com.vaadin.flow.component.grid.ColumnTextAlign;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.icon.VaadinIcon;
// tag::snippet[]
import com.vaadin.flow.component.masterdetaillayout.MasterDetailLayout;
// end::snippet[]
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;

@Route("")
@PageTitle("Product Catalog")
// tag::snippet[]
class ProductCatalogView extends MasterDetailLayout {
// end::snippet[]

    ProductCatalogView(ProductCatalogItemRepository repository) {
        // Create components
        var searchField = new TextField();
        searchField.setPlaceholder("Search");
        searchField.setPrefixComponent(VaadinIcon.SEARCH.create());
        searchField.setValueChangeMode(ValueChangeMode.LAZY);

        var grid = new Grid<ProductCatalogItem>();
        grid.addColumn(ProductCatalogItem::name)
            .setHeader("Name")
            .setSortProperty(ProductCatalogItem.SORT_PROPERTY_NAME);
        grid.addColumn(ProductCatalogItem::price)
            .setHeader("Price")
            .setTextAlign(ColumnTextAlign.END)
            .setSortProperty(ProductCatalogItem.SORT_PROPERTY_PRICE);
        grid.addColumn(ProductCatalogItem::description)
            .setHeader("Description")
            .setSortProperty(ProductCatalogItem.SORT_PROPERTY_DESCRIPTION);
        grid.addColumn(ProductCatalogItem::category)
            .setHeader("Category")
            .setSortProperty(ProductCatalogItem.SORT_PROPERTY_CATEGORY);
        grid.addColumn(ProductCatalogItem::brand)
            .setHeader("Brand")
            .setSortProperty(ProductCatalogItem.SORT_PROPERTY_BRAND);
        grid.setItemsPageable(pageable -> repository
            .findByNameContainingIgnoreCase(searchField.getValue(),
                pageable)
            .getContent()
        );

        // tag::snippet[]
        var drawer = new ProductFormDrawer();
        // end::snippet[]

        searchField.addValueChangeListener(e ->
                grid.getDataProvider().refreshAll());


        // Layout view
        // tag::snippet[]
        setSizeFull();
        setDetailSize("300px"); // (1)
        setExpandMaster(true); // (2)

        var listLayout = new VerticalLayout(searchField, grid); // (3)
        listLayout.setSizeFull();
        grid.setSizeFull();

        setMaster(listLayout); // (4)
        setDetail(drawer); // (5)
        // end::snippet[]
    }
}
```

1. Make the detail area, and thereby the drawer, 300 pixels wide.

2. Let the master area grow to fill the space that the drawer doesn’t use.

3. Wrap the search field and grid in a `VerticalLayout` to create the list part of the master-detail view.

4. Show the list layout in the master area.

5. Show the drawer in the detail area. For now, the drawer is always visible. You’ll change this later in this step.

The view should now look like this:

[Image: The empty product drawer]

## <a id="create-a-product-form"></a>Create a Product Form

Now that you have the drawer in place, it’s time to add a form to display the product details. Create a new class called `ProductForm` in the `com.example.product` package:

ProductForm.java

```java
package com.example.product;

import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.textfield.BigDecimalField;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField;

class ProductForm extends Composite<FormLayout>  {

    ProductForm() {
        // Create components
        var nameField = new TextField("Name");
        var descriptionField = new TextArea("Description");
        var categoryField = new TextField("Category");
        var brandField = new TextField("Brand");
        var skuField = new TextField("SKU");
        var releaseDateField = new DatePicker("Release Date");
        var priceField = new BigDecimalField("Price");
        var discountField = new BigDecimalField("Discount");

        // Layout form
        var layout = getContent();
        layout.add(nameField);
        layout.add(descriptionField);
        layout.add(categoryField);
        layout.add(brandField);
        layout.add(skuField);
        layout.add(releaseDateField);
        layout.add(priceField);
        layout.add(discountField);
    }
}
```

Then add it to the drawer:

```java
package com.example.product;

import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.html.H2;
// tag::snippet[]
import com.vaadin.flow.component.orderedlayout.Scroller;
// end::snippet[]
import com.vaadin.flow.component.orderedlayout.VerticalLayout;

class ProductFormDrawer extends Composite<VerticalLayout> {

    ProductFormDrawer() {
        var header = new H2("Product Details");
        // tag::snippet[]
        var form = new ProductForm();
        // end::snippet[]

        var layout = getContent();
        layout.add(header);
        // tag::snippet[]
        layout.add(new Scroller(form)); // (1)
        layout.setHeightFull(); // (2)
        // end::snippet[]
    }
}
```

1. The `Scroller` component makes the form scrollable in case it doesn’t fit in the drawer.

2. Make the drawer as tall as the detail area. Otherwise, the drawer grows with the form, and the `Scroller` has nothing to scroll.

The view should now look like this:

[Image: The product form in the drawer]

## <a id="create-a-form-data-object"></a>Create a Form Data Object

If you compare the form fields with the grid, you’ll notice that the form has more fields than the grid displays. That means you can’t use `ProductCatalogItem` for the form data. Instead, you need to create a new [Form Data Object](https://vaadin.com/docs/next/building-apps/forms-data/add-form/fields-and-binding.md) (FDO).

Since you’ll also be using this FDO to edit products, you should make it a mutable [JavaBean](https://vaadin.com/docs/next/building-apps/forms-data/add-form/fields-and-binding.md#javabean-fdos) and not a record. Create a new `ProductDetails` class like this:

ProductDetails.java

```java
package com.example.product;

import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.relational.core.mapping.Table;

import java.math.BigDecimal;
import java.time.LocalDate;

@Table("PRODUCTS")
class ProductDetails {

    @Id
    private Long productId;
    @Version
    private Long version; // (1)
    private String name;
    private String description;
    private String category;
    private String brand;
    private String sku;
    private LocalDate releaseDate;
    private BigDecimal price;
    private BigDecimal discount;

    public Long getProductId() { // (2)
        return productId;
    }

    public Long getVersion() {
        return version;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getSku() {
        return sku;
    }

    public void setSku(String sku) {
        this.sku = sku;
    }

    public LocalDate getReleaseDate() {
        return releaseDate;
    }

    public void setReleaseDate(LocalDate releaseDate) {
        this.releaseDate = releaseDate;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public BigDecimal getDiscount() {
        return discount;
    }

    public void setDiscount(BigDecimal discount) {
        this.discount = discount;
    }
}
```

1. This field is used for [optimistic locking](https://vaadin.com/docs/next/building-apps/forms-data/consistency/optimistic-locking.md). You’ll use it later when implementing editing.

2. You shouldn’t need setters for `productId` and `version` as Spring Data sets these fields automatically. Unlike in `ProductCatalogItem`, the ID is a `Long` rather than a `long`, because a new product doesn’t have an ID until it’s saved.

> **Note: Why Is the FDO Also an Entity?**
>
> `ProductDetails` is both the FDO and a Spring Data entity, mapped to the `PRODUCTS` table. The form edits the entity directly. This is a pragmatic choice. The form and the table have the same structure, with one field for each column. They also change together, since a new column means a new field. A separate FDO would only copy the same values back and forth. Introduce one when the form and the stored data start to differ, for example when the form combines data from several tables, or when the entity contains business logic. The [Add a Service](https://vaadin.com/docs/next/building-apps/business-logic/add-service.md#entities) guide describes when to pass entities to the user interface, and when to use separate objects instead.

## <a id="bind-the-form-and-the-fdo"></a>Bind the Form and the FDO

Next, you have to [bind the form and the FDO](https://vaadin.com/docs/next/building-apps/forms-data/add-form/fields-and-binding.md#binding-to-a-bean) so that the data shows up in the correct fields. For this, you’ll use a `Binder`. Change the `ProductForm` as follows:

ProductForm.java

```java
package com.example.product;

import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.textfield.BigDecimalField;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField;
// tag::snippet[]
import com.vaadin.flow.data.binder.Binder;
import org.jspecify.annotations.Nullable; // (1)
// end::snippet[]

class ProductForm extends Composite<FormLayout>  {

// tag::snippet[]
    private final Binder<ProductDetails> binder;
// end::snippet[]

    ProductForm() {
        // Create components
        var nameField = new TextField("Name");
        var descriptionField = new TextArea("Description");
        var categoryField = new TextField("Category");
        var brandField = new TextField("Brand");
        var skuField = new TextField("SKU");
        var releaseDateField = new DatePicker("Release Date");
        var priceField = new BigDecimalField("Price");
        var discountField = new BigDecimalField("Discount");

        // Layout form
        var layout = getContent();
        layout.add(nameField);
        layout.add(descriptionField);
        layout.add(categoryField);
        layout.add(brandField);
        layout.add(skuField);
        layout.add(releaseDateField);
        layout.add(priceField);
        layout.add(discountField);

// tag::snippet[]
        // Bind fields
        binder = new Binder<>();
        binder.forField(nameField)
              .bind(ProductDetails::getName,
                    ProductDetails::setName); // (2)
        binder.forField(descriptionField)
              .bind(ProductDetails::getDescription,
                    ProductDetails::setDescription);
        binder.forField(categoryField)
              .bind(ProductDetails::getCategory,
                    ProductDetails::setCategory);
        binder.forField(brandField)
              .bind(ProductDetails::getBrand,
                    ProductDetails::setBrand);
        binder.forField(skuField)
              .bind(ProductDetails::getSku,
                    ProductDetails::setSku);
        binder.forField(releaseDateField)
              .bind(ProductDetails::getReleaseDate,
                    ProductDetails::setReleaseDate);
        binder.forField(priceField)
              .bind(ProductDetails::getPrice,
                    ProductDetails::setPrice);
        binder.forField(discountField)
              .bind(ProductDetails::getDiscount,
                    ProductDetails::setDiscount);
        binder.setReadOnly(true); // (3)
// end::snippet[]
    }

// tag::snippet[]
    public void setFormDataObject(@Nullable ProductDetails productDetails) { // (4)
        binder.setBean(productDetails); // (5)
    }
// end::snippet[]
}
```

1. It’s a good idea, although not required, to use JSpecify’s `@Nullable` annotation to mark parameters that can be `null`.

2. Every field in `ProductForm` is bound to the corresponding JavaBean property in `ProductDetails`.

3. Make all fields read-only as you’re only using the form to view data for now.

4. Passing `null` clears the form.

5. You’re using the `Binder` in *write-through* mode. See the [Fields & Bindings](https://vaadin.com/docs/next/building-apps/forms-data/add-form/fields-and-binding.md#buffered-vs-write-through) guide for details.

## <a id="create-a-repository-interface"></a>Create a Repository Interface

To fetch the product details from the database, you’ll again use a Spring Data repository. Create a new interface named `ProductDetailsRepository`:

ProductDetailsRepository.java

```java
package com.example.product;

import org.springframework.data.repository.CrudRepository;

interface ProductDetailsRepository
    extends CrudRepository<ProductDetails, Long> {
}
```

As for `ProductCatalogItemRepository`, Spring Data provides the implementation of the interface.

## <a id="populate-the-form"></a>Populate the Form

You now have all the building blocks needed to show product details in the drawer. Start by adding an API to `ProductFormDrawer` for setting a `ProductDetails`:

ProductFormDrawer.java

```java
package com.example.product;

import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.orderedlayout.Scroller;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;

class ProductFormDrawer extends Composite<VerticalLayout> {

// tag::snippet[]
    private final ProductForm form;
// end::snippet[]

    ProductFormDrawer() {
        var header = new H2("Product Details");
// tag::snippet[]
        form = new ProductForm(); // (1)
// end::snippet[]

        var layout = getContent();
        layout.add(header);
        layout.add(new Scroller(form));
        layout.setHeightFull();
    }

// tag::snippet[]
    public void setProductDetails(ProductDetails productDetails) {
        form.setFormDataObject(productDetails);
    }
// end::snippet[]
}
```

1. Because you’re calling a method on the form from `setProductDetails()` below, the form can no longer be a local variable.

Next, you’ll add a selection listener to the product grid. Whenever a user selects an item, you’ll fetch the details from the `ProductDetailsRepository` and show them in the drawer. If no item is selected, you’ll remove the drawer from the detail area. Master-Detail Layout shows the detail area only when it contains something, so this hides the drawer.

Update `ProductCatalogView` as follows. Also remove the `setDetail(drawer)` call from the layout code. From now on, the selection listener decides when to show the drawer.

ProductCatalogView\.java

```java
package com.example.product;

import com.vaadin.flow.component.grid.ColumnTextAlign;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.masterdetaillayout.MasterDetailLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;

@Route("")
@PageTitle("Product Catalog")
class ProductCatalogView extends MasterDetailLayout {

    ProductCatalogView(ProductCatalogItemRepository repository,
// tag::snippet[]
                       ProductDetailsRepository productDetailsRepository) { // (1)
// end::snippet[]
        // Create components
        var searchField = new TextField();
        searchField.setPlaceholder("Search");
        searchField.setPrefixComponent(VaadinIcon.SEARCH.create());
        searchField.setValueChangeMode(ValueChangeMode.LAZY);

        var grid = new Grid<ProductCatalogItem>();
        grid.addColumn(ProductCatalogItem::name)
            .setHeader("Name")
            .setSortProperty(ProductCatalogItem.SORT_PROPERTY_NAME);
        grid.addColumn(ProductCatalogItem::price)
            .setHeader("Price")
            .setTextAlign(ColumnTextAlign.END)
            .setSortProperty(ProductCatalogItem.SORT_PROPERTY_PRICE);
        grid.addColumn(ProductCatalogItem::description)
            .setHeader("Description")
            .setSortProperty(ProductCatalogItem.SORT_PROPERTY_DESCRIPTION);
        grid.addColumn(ProductCatalogItem::category)
            .setHeader("Category")
            .setSortProperty(ProductCatalogItem.SORT_PROPERTY_CATEGORY);
        grid.addColumn(ProductCatalogItem::brand)
            .setHeader("Brand")
            .setSortProperty(ProductCatalogItem.SORT_PROPERTY_BRAND);
        grid.setItemsPageable(pageable -> repository
            .findByNameContainingIgnoreCase(searchField.getValue(),
                pageable)
            .getContent()
        );

        var drawer = new ProductFormDrawer();

        searchField.addValueChangeListener(e ->
                grid.getDataProvider().refreshAll());

        // tag::snippet[]
        grid.addSelectionListener(e -> {
            var productDetails = e.getFirstSelectedItem() // (2)
                .flatMap(item -> productDetailsRepository
                    .findById(item.productId())); // (3)
            productDetails.ifPresent(drawer::setProductDetails); // (4)
            setDetail(productDetails.isPresent() ? drawer : null); // (5)
        });
        addBackdropClickListener(e -> grid.deselectAll()); // (6)
        addDetailEscapePressListener(e -> grid.deselectAll());
        // end::snippet[]

        // Layout view
        setSizeFull();
        setDetailSize("300px");
        setExpandMaster(true);

        var listLayout = new VerticalLayout(searchField, grid);
        listLayout.setSizeFull();
        grid.setSizeFull();

        setMaster(listLayout);
    }
}
```

1. Inject `ProductDetailsRepository` so that you can call it from the selection listener. The view still only reads data, so it can keep calling the repositories directly.

2. `getFirstSelectedItem()` returns an `Optional` that contains the selected item if there is one. The name comes from the fact that Grid supports selecting multiple items if you configure it to.

3. `findById()` also returns an `Optional`. The result is empty if no item is selected, or if the product details can’t be found in the database.

4. Populate the drawer, if there are details to show.

5. Show the drawer in the detail area if there are details to show. Otherwise, remove it, which hides the detail area.

6. When the drawer appears as an overlay, the user can close it by clicking outside it, or by pressing `Esc`. Both clear the grid selection, which hides the drawer through the selection listener.

The view should now look like this:

[Image: The product drawer with data]

If you select items in the grid, their details show up in the drawer. If you clear the selection, the drawer disappears and the grid takes up the whole view.

Now make the browser window narrower. When the list and the drawer no longer fit side by side, the drawer slides in on top of the list instead. Close it by clicking outside it or by pressing `Esc`. You didn’t have to write any code for this, since Master-Detail Layout handles it for you.

## <a id="complete-code-for-this-step"></a>Complete Code for This Step

If your application doesn’t behave as described, compare your files with these listings. They show every class in the application as it should look at the end of this step.

ProductCatalogView\.java

```java
package com.example.product;

import com.vaadin.flow.component.grid.ColumnTextAlign;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.masterdetaillayout.MasterDetailLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.value.ValueChangeMode;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;

@Route("")
@PageTitle("Product Catalog")
class ProductCatalogView extends MasterDetailLayout {

    ProductCatalogView(ProductCatalogItemRepository repository,
                       ProductDetailsRepository productDetailsRepository) {
        // Create components
        var searchField = new TextField();
        searchField.setPlaceholder("Search");
        searchField.setPrefixComponent(VaadinIcon.SEARCH.create());
        searchField.setValueChangeMode(ValueChangeMode.LAZY);

        var grid = new Grid<ProductCatalogItem>();
        grid.addColumn(ProductCatalogItem::name)
            .setHeader("Name")
            .setSortProperty(ProductCatalogItem.SORT_PROPERTY_NAME);
        grid.addColumn(ProductCatalogItem::price)
            .setHeader("Price")
            .setTextAlign(ColumnTextAlign.END)
            .setSortProperty(ProductCatalogItem.SORT_PROPERTY_PRICE);
        grid.addColumn(ProductCatalogItem::description)
            .setHeader("Description")
            .setSortProperty(ProductCatalogItem.SORT_PROPERTY_DESCRIPTION);
        grid.addColumn(ProductCatalogItem::category)
            .setHeader("Category")
            .setSortProperty(ProductCatalogItem.SORT_PROPERTY_CATEGORY);
        grid.addColumn(ProductCatalogItem::brand)
            .setHeader("Brand")
            .setSortProperty(ProductCatalogItem.SORT_PROPERTY_BRAND);
        grid.setItemsPageable(pageable -> repository
            .findByNameContainingIgnoreCase(searchField.getValue(),
                pageable)
            .getContent()
        );

        var drawer = new ProductFormDrawer();

        searchField.addValueChangeListener(e ->
                grid.getDataProvider().refreshAll());

        grid.addSelectionListener(e -> {
            var productDetails = e.getFirstSelectedItem()
                .flatMap(item -> productDetailsRepository
                    .findById(item.productId()));
            productDetails.ifPresent(drawer::setProductDetails);
            setDetail(productDetails.isPresent() ? drawer : null);
        });
        addBackdropClickListener(e -> grid.deselectAll());
        addDetailEscapePressListener(e -> grid.deselectAll());

        // Layout view
        setSizeFull();
        setDetailSize("300px");
        setExpandMaster(true);

        var listLayout = new VerticalLayout(searchField, grid);
        listLayout.setSizeFull();
        grid.setSizeFull();

        setMaster(listLayout);
    }
}
```

ProductCatalogItem.java

```java
package com.example.product;

import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Table;

import java.math.BigDecimal;

@Table("PRODUCTS")
record ProductCatalogItem(
        @Id long productId,
        String name,
        String description,
        String category,
        String brand,
        BigDecimal price
) {
    static final String SORT_PROPERTY_NAME = "name";
    static final String SORT_PROPERTY_PRICE = "price";
    static final String SORT_PROPERTY_DESCRIPTION = "description";
    static final String SORT_PROPERTY_CATEGORY = "category";
    static final String SORT_PROPERTY_BRAND = "brand";
}
```

ProductCatalogItemRepository.java

```java
package com.example.product;

import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;

import org.springframework.data.repository.PagingAndSortingRepository;

interface ProductCatalogItemRepository
    extends PagingAndSortingRepository<ProductCatalogItem, Long> {

    Slice<ProductCatalogItem> findByNameContainingIgnoreCase(String name,
        Pageable pageable);
}
```

ProductDetails.java

```java
package com.example.product;

import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.relational.core.mapping.Table;

import java.math.BigDecimal;
import java.time.LocalDate;

@Table("PRODUCTS")
class ProductDetails {

    @Id
    private Long productId;
    @Version
    private Long version;
    private String name;
    private String description;
    private String category;
    private String brand;
    private String sku;
    private LocalDate releaseDate;
    private BigDecimal price;
    private BigDecimal discount;

    public Long getProductId() {
        return productId;
    }

    public Long getVersion() {
        return version;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getSku() {
        return sku;
    }

    public void setSku(String sku) {
        this.sku = sku;
    }

    public LocalDate getReleaseDate() {
        return releaseDate;
    }

    public void setReleaseDate(LocalDate releaseDate) {
        this.releaseDate = releaseDate;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public BigDecimal getDiscount() {
        return discount;
    }

    public void setDiscount(BigDecimal discount) {
        this.discount = discount;
    }
}
```

ProductDetailsRepository.java

```java
package com.example.product;

import org.springframework.data.repository.CrudRepository;

interface ProductDetailsRepository
    extends CrudRepository<ProductDetails, Long> {
}
```

ProductForm.java

```java
package com.example.product;

import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.textfield.BigDecimalField;
import com.vaadin.flow.component.textfield.TextArea;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.Binder;
import org.jspecify.annotations.Nullable;

class ProductForm extends Composite<FormLayout>  {

    private final Binder<ProductDetails> binder;

    ProductForm() {
        // Create components
        var nameField = new TextField("Name");
        var descriptionField = new TextArea("Description");
        var categoryField = new TextField("Category");
        var brandField = new TextField("Brand");
        var skuField = new TextField("SKU");
        var releaseDateField = new DatePicker("Release Date");
        var priceField = new BigDecimalField("Price");
        var discountField = new BigDecimalField("Discount");

        // Layout form
        var layout = getContent();
        layout.add(nameField);
        layout.add(descriptionField);
        layout.add(categoryField);
        layout.add(brandField);
        layout.add(skuField);
        layout.add(releaseDateField);
        layout.add(priceField);
        layout.add(discountField);

        // Bind fields
        binder = new Binder<>();
        binder.forField(nameField)
              .bind(ProductDetails::getName,
                    ProductDetails::setName);
        binder.forField(descriptionField)
              .bind(ProductDetails::getDescription,
                    ProductDetails::setDescription);
        binder.forField(categoryField)
              .bind(ProductDetails::getCategory,
                    ProductDetails::setCategory);
        binder.forField(brandField)
              .bind(ProductDetails::getBrand,
                    ProductDetails::setBrand);
        binder.forField(skuField)
              .bind(ProductDetails::getSku,
                    ProductDetails::setSku);
        binder.forField(releaseDateField)
              .bind(ProductDetails::getReleaseDate,
                    ProductDetails::setReleaseDate);
        binder.forField(priceField)
              .bind(ProductDetails::getPrice,
                    ProductDetails::setPrice);
        binder.forField(discountField)
              .bind(ProductDetails::getDiscount,
                    ProductDetails::setDiscount);
        binder.setReadOnly(true);
    }

    public void setFormDataObject(@Nullable ProductDetails productDetails) {
        binder.setBean(productDetails);
    }
}
```

ProductFormDrawer.java

```java
package com.example.product;

import com.vaadin.flow.component.Composite;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.orderedlayout.Scroller;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;

class ProductFormDrawer extends Composite<VerticalLayout> {

    private final ProductForm form;

    ProductFormDrawer() {
        var header = new H2("Product Details");
        form = new ProductForm();

        var layout = getContent();
        layout.add(header);
        layout.add(new Scroller(form));
        layout.setHeightFull();
    }

    public void setProductDetails(ProductDetails productDetails) {
        form.setFormDataObject(productDetails);
    }
}
```

## <a id="next-steps"></a>Next Steps

You have now implemented a simple master-detail view in Vaadin. In the next step, you’ll add support for editing and saving product details. Proceed to the [Edit Product Details](https://vaadin.com/docs/next/getting-started/tutorial/edit-details.md) step to continue the tutorial.

[Next: Edit Product Details](../../getting-started/tutorial/edit-details)
