Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The Vaadin Way of building the Presentation Layer #3516

Draft
wants to merge 35 commits into
base: latest
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a9eeeef
First draft of "designing the architecture"
peholmst Apr 23, 2024
2e72f55
Merge branch 'next' into vaadin-way-architecture
peholmst Apr 23, 2024
9d77684
Rename "designing" to "building blocks"
peholmst Apr 23, 2024
1f7aa14
Use C4 to introduce the architectural concepts
peholmst Apr 24, 2024
a1e80df
Tweaks to cross-references
peholmst Apr 24, 2024
2945e5d
Remove link to deep dive until it has been written.
peholmst Apr 25, 2024
e376a7e
Merge branch 'next' into vaadin-way-architecture
peholmst Apr 25, 2024
54de9b2
First draft about monoliths
peholmst Apr 25, 2024
d3bfc62
First draft of microservices page.
peholmst Apr 26, 2024
0450b65
Merge branch 'next' into vaadin-way-architecture
peholmst Apr 26, 2024
f249827
Edited most of documents added.
russelljtdyer Apr 26, 2024
8865ab3
More initial editing.
russelljtdyer Apr 27, 2024
90e931f
Merge branch 'next' into vaadin-way-architecture
peholmst Apr 30, 2024
9b61ad4
Merge branch 'next' into vaadin-way-architecture
peholmst May 2, 2024
563a000
Merge branch 'next' into vaadin-way-architecture
peholmst May 2, 2024
80e2528
Merge branch 'next' into vaadin-way-architecture
peholmst Jun 5, 2024
a76442c
Merge branch 'latest' into vaadin-way-architecture
peholmst Jun 12, 2024
14e3c84
Merge branch 'latest' into vaadin-way-architecture
peholmst Jun 24, 2024
e73c03d
Merge branch 'latest' into vaadin-way-architecture
peholmst Jun 26, 2024
6f81752
Fix vale errors
peholmst Jun 26, 2024
53900c1
Add introduction
peholmst Jun 26, 2024
44e82b0
Merge branch 'latest' into vaadin-way-architecture
peholmst Jun 27, 2024
7de6d06
Move Building Apps down in the menu until it has more content
peholmst Jun 27, 2024
a21fdba
First version of view composition page
peholmst Jun 27, 2024
ed1fa83
Add missing index.adoc
peholmst Jun 27, 2024
0055b75
First draft or URL design.
peholmst Jun 28, 2024
4b5e87c
Fix typos
peholmst Jun 28, 2024
ef5e595
Merge branch 'latest' into vaadin-way-presentation-layer
peholmst Jul 1, 2024
b614c89
Merge branch 'latest' into vaadin-way-presentation-layer
peholmst Jul 3, 2024
9164352
Add review notes
peholmst Jul 10, 2024
5528c7b
Merge branch 'latest' into vaadin-way-presentation-layer
russelljtdyer Jul 12, 2024
e7adf50
Edited document touched: /articles/building-apps/architecture/design.…
russelljtdyer Jul 12, 2024
26ad8a6
Edited document touched: /articles/building-apps/presentation-layer/v…
russelljtdyer Jul 12, 2024
8073e85
Edited document touched: /articles/building-apps/presentation-layer/v…
russelljtdyer Jul 12, 2024
4dd52a2
Merge branch 'latest' into vaadin-way-presentation-layer
peholmst Sep 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/styles/Vaadin/Abbr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ exceptions:
- URL
- USB
- UTF
- UUID
- WAR
- WCAG
- XML
Expand Down
5 changes: 5 additions & 0 deletions articles/building-apps/presentation-layer/index.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: Presentation Layer
description: Learn how to build the presentation layer of your Vaadin app.
order: 30
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
---
title: View Composition
description: TODO write description
order: 10
---

# View Composition

You build Vaadin user interfaces by composing UI components together. Some UI components are provided out-of-the-box by Vaadin, others you have to write yourself. Some components are generic and reusable in different settings, others are designed for one specific use case and should not be reused at all. Knowing how to identify and implement these components is key to succeeding in building complex Vaadin user interfaces.

You are now going to learn the basics of view composition in Vaadin. The starting point is the following mock-up of a fictional business application:

image:images/application.png[alt=A mock-up of a fictional business application,width=800]

Step by step, you are going to learn how to split it up into smaller parts until you are ready to implement it.

## Main Layout

The first step is to identify which parts of the user interface belong to the _layout_ and which belong to the _view_. A layout contains elements that are always visible in the user interface, such as the title of the application, navigation links and information about the current user. A view is one page of the user interface and is typically rendered inside a layout. As the user navigates between views, the layout remains the same. It is also possible to nest layouts inside each other.

The main layout of the mock-up is the following:

image:images/main-layout.png[width=800]

It consists of a header and a content area. The content are contains whatever the current view is. The header consists of three different components: the application's logo, navigation links to the _Employees_, _Teams_ and _Locations_ views, and the current user's avatar.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The content are contains whatever the current view is.

are -> area


*The main layout is a UI component you have to implement.* The header is so simple that it can be nested inside the layout.

## The View

Having discovered the main layout, you can remove those parts from the mock-up. This leaves you with the actual Teams view, which looks like this:

image:images/view.png[width=800]

It is a good idea to look for common user interface design patterns. In this view, the most obvious one is the _Master-Detail_ pattern. On the left, there is a list of teams that the user can select from. When the user selects a team, its details show up on the right.

From the mock-up it is unclear what should happen if there are no teams in the list or no team is selected. You should find this out before you start implementing.

### Team Selection Panel

The master-part of the master-detail is the team selection panel:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think master-part would read better without the dash. Same goes for detail-part later.


image:images/view-master.png[width=200]

At the top, there is a header containing the name of the Teams view, a button for creating new teams and a text field for filtering the list of teams.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are many views on this matter, but personally I prefer Oxford comma -- i.e. a comma before each 'and' in lists of three or more items. I find lists easier to read with the Oxford comma, especially when each item contains a longer bit of text, like this one does. But of course it's also important to be consistent, so if the comma is added here, it should be added to all the other lists as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


The list of teams itself consists of panels, one for each team. Each team panel contains the name and description of the team. *The team panel is a UI component you _may_ have to implement*, depending on how you choose to implement the list itself.

You select a team by clicking on the panel. From the mock-up it is unclear whether keyboard navigation should be supported or not. It is also unclear whether the team selection panel is resizable or not, although the lack of a splitter indicates it has a fixed width. These are things you should find out before you start implementing, as it affects which components you can use.

*The team selection panel is a UI component you have to implement*. The header is so simple that it can be nested inside the panel.

### Team Details Panel

The detail-part of the master-detail is the team details panel:

image:images/view-detail.png[width=800]

At the top, there is again a header. It contains the name and description of the team, and buttons for editing, sharing and deleting the team.

To the left, there is a sidebar with two sections: one with general information about the team and another with a list of managers of the team. From the mock-up it is unclear whether the managers are clickable or not. It is also unclear whether the sidebar is resizable or not, although the lack of a splitter indicates it has a fixed width. Again, these are things you should find out before you start implementing, as it affects which components you can use.

To the right, there are tabs that control the contents of the rest of the panel. This indicates that the entire team details panel is in fact a nested layout with three sub-views: _Employees_, _Salaries_, and _Documents_. In fact, the team details panel looks like this:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two 'in fact' in close succession. I might switch the second one to 'In other words', or even tweak the final sentence all the way to In other words, the team details panel should look like this:


image:images/view-detail-panel.png[width=800]

*The team details panel is a UI component you have to implement.* The sidebar and header are so simple that they can be nested inside the panel.

#### Employees Sub-View

The first, and only sub-view visible in the mock-up, is the Employees sub-view:

image:images/employees-sub-view.png[width=600]

As with ordinary views, it is a good idea to look for common design patterns. In this case, you have another Master-Detail. At the top, there is a grid of team members. When you select a team member, its details show up in a bottom panel. The splitter between the grid and the bottom panel indicates the bottom panel is resizable.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it intentional, that the team member selection doesn't show in the grid? Took me a moment to find Cody Fisher in the list.


From the mock-up it is unclear what should happen if there are no employees in the grid or no employee is selected. You should find this out before you start implementing.

The employee details panel is read-only, which makes it simple to implement. It contains general information about the employee.

*The employees sub-view is a UI component you have to implement. The employee details panel is a UI component you _may_ have to implement*, depending on how you choose to implement the sub-view itself.

## Generic Components

You have now split the mock-up into a layout, a view, a nested layout and a sub-view. In addition, you have identified some panels that you need to implement. The final step before you can start to actually write code is to identify any generic components that are re-used in multiple places. In this mock-up, there are at least two.

The first generic component is the person panel:

image:images/person-panel.png[width=300]

It is used both in the list of managers and in the employees sub-view. It contains the person's picture or avatar, name, and title. From the mock-up, you can see that the panel is smaller in the list and larger in the sub-view.

The second generic component is the item with icon:

image:images/items.png[width=300]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it would be better to have these items as separate files rather than all in one, or to otherwise separate them visually somehow (a grey background behind white items, perhaps)? It took me a moment to figure out that this wasn't the entire general information item set from underneath the Summary header, but three separate items that were just shown together for comparison.


It is used both in the team summary and in the employees sub-view. From the mock-up it is unclear whether the phone number and email should be clickable. You should find this out before you start implementing.

As you start to implement the view, you may discover more generic components. This is especially the case with Flow, as laying out components in an imperative way is rather verbose.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: View Implementation with Flow
description: TODO write description
order: 30
---

// TODO Write me!
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
title: View Structure
description: TODO write description
order: 10
---
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: View Implementation with React
description: TODO write description
order: 40
---

// TODO Write me!
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
---
title: URL Design
description: TODO write description
order: 20
---

# URL Design

On the <<composition#,previous>> page, you learned how to split this mock-up into UI components:

image::images/application.png[]

You are now going to learn how to design the URL of the mock-up based on how a user is intended to navigate the view. The main focus is on deciding what _view state_ to store in the URL as path or query parameters. Now why is this important?

Say you are working with the application, and want one of your colleagues to have a look at something. Now could ask them to open the application and tell them how to look up the information you want to show. However, if the relevant state was stored in the URL, you could send it to your colleague. The colleague would then only need to click the link to end up with the same view as you (after authenticating, of course).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now could ask them to open the application

'You could', or 'Now, you could' (possibly works without a comma too)

you could send it to your colleague

I might go with 'send that' instead


## Path Parameters

Path parameters are, as their name suggests, a part of the URL path: `/view/[parameter1]/[parameter2]`. Use path parameters for navigating to a specific view, or sub-view within a view.

In the fictional application, the root path of the _Teams_ view is `/teams`. If you navigate to this path, you end up with this:

image::images/no-selection.png[]

The first path parameter is the ID of the team: `/teams/[teamId]`. For example, the team _Research and Development (R&D)_ could have the path `/teams/8zguABh6u`.

It is quite common to use incrementing long integers as database primary keys. If you use such a key in the URL, for example `/teams/1`, it is easy for an attacker to deduce that there may also be a `/teams/2`, a `/teams/3` and so on. Even if you have secured the application so that only authorized users get access regardless of whether they know the URL or not, the URL is exposing internal information. This information can be used to attack the application in another way. Instead, use a random ID such as `/teams/8zguABh6u`. Under the hood, you can still use long incrementing integers for primary and foreign keys, as they are faster than strings.

The Teams view has three sub-views, visible as tabs: _Employees_, _Salaries_, and _Documents_. The selected tab is the second path parameter: `/teams/[teamId]/[tab]`. For example, the sub-views could have the following paths:

* Employees: `/teams/8zguABh6u/employees`
* Salaries: `/teams/8zguABh6u/salaries`
* Documents: `/teams/8zguABh6u/documents`

Because the Employees tab should be selected by default, the path `/teams/8zguABh6u` should be considered an alias of `/teams/8zguABh6u/employees`. If you navigate to either of these paths, you end up with this:

image::images/no-employee-selection.png[]

The Employees sub-view allows you to select an employee to view their details. This selection is the third path parameter and it is only valid when the Employees tab is selected: `/teams/[teamId]/employees/[employeeId]`. For example, _Cody Fisher_ could have the path `/teams/8zguABh6u/employees/zxPIVBqJ2`. If you navigate to this path, you end up with the state of the original mock-up:

image::images/application.png[]

## Query Parameters

Query parameters are added to the end of a URL after a `?` symbol. You can specify multiple query parameters by separating them with `&` symbols: `/view?param1=foo&param2=bar`. Use query parameters if you want to be able to share or bookmark a view while it is in a specific state. Such a state could, for example, be a particular sort order or filter.

You don't have to use query parameters unless there is a clear benefit to do so. Looking at the mock-up, the view is already quite usable without query parameters. That said, it still has some candidates which you are going to look at next, for the sake of demonstration.

The Teams view has a text field for filtering the list of teams. This is the first candidate for a query parameter: `/teams/8zguABh6u?search=foobar`

The Employees sub-view has a grid that can be sorted by clicking at the column headers. This is the second candidate for a query parameter: `/teams/8zguABh6u/employees/?orderEmployeesBy=firstName,lastName`

You can have query parameters and path parameters for both the view and the sub-view at the same time. For example, look at this URL:

[source]
----
/teams/8zguABh6u/employees/zxPIVBqJ2?search=research&orderEmployeesBy=firstName,lastName
----

You can extract the following information:

* The current view is the Teams view.
* The team whose public ID is `8zguABh6u` is selected.
* The Employees sub-view is visible.
* The employee whose public ID is `zxPIVBqJ2` is selected.
* The team list is filtered using the search term `research`.
* The employee grid is sorted first by first name, then by last name.

## String ID:s in URL:s

The examples above used random strings as public ID:s. When you generate a random ID, you should pay attention to its usability. Since the ID is public, people may want to copy and paste it, either on its own or as a part of the URL. That means the ID must be URL-friendly, short enough and easy to copy.

An easy way of generating random string ID:s in Java is to use Universally Unique IDentifiers (UUID). Java has a [classname]`UUID` class in the [packagename]`java.util` package. You can use it to generate random UUID:s by calling [methodname]`UUID.randomUUID()`. However, there is a problem with using UUID:s as public ID:s in URL:s.

Try to select this UUID by double-clicking on it:

[source]
----
78f98876-b150-4e08-8d7e-41bb5e0f7e72
----

Chances are you ended up selecting only a part of the ID instead of the whole string. This is because of the hyphens. You can fix this by either removing the hyphens or replacing them with underscores. Try to select these ID:s by double-clicking on them:

[source]
----
78f98876_b150_4e08_8d7e_41bb5e0f7e72
78f98876b1504e088d7e41bb5e0f7e72
----

You should now be able to select the whole string. However, the string is still quite long and when used as URL parameters, the URL becomes quite long too:

[source]
----
https://myapp.example.com/teams/78f98876b1504e088d7e41bb5e0f7e72/employees/5191cfa1823e40858b0f0e10ce50c28e
----

This is not a user friendly URL. You can copy-paste it, but it is difficult to read for the human eye. Telling it to somebody over the phone is next to impossible.

A better alternative for public random string ID:s is _NanoId_. NanoId:s allow you to pick both the length and the alphabet. By default, the length is 21 characters and the alphabet numbers 0--9, letters A-Z and a-z, and the symbols - (hyphen) and _ (underscore).

If you use 9 character NanoId:s generated from an alphabet consisting of only numbers, uppercase and lowercase letters, you end up with a URL like this:

[source]
----
https://myapp.example.com/teams/aftD6ZXp6/employees/m4wqAas1Q
----

This is both shorter and more user friendly.

NanoId has been implemented in different programming languages, among others https://github.com/aventrix/jnanoid[Java] and https://github.com/ai/nanoid[JavaScript].

## Back Button Behavior

Once you are ready with the URL design, there is one more thing to consider: the browser's back button.

When you navigate to a URL in a web browser, a new entry is pushed to the browser's history stack. When you click the back button, the browser goes back one step in the history stack. You can continue to click the button until the stack is empty.

When a user uses a web application, it is the application itself that handles the navigation in respond to the user's actions. For example, in the mock-up application, the user would not modify the URL itself to select a team or a tab. Rather, the user would click on the team and the tab and the application would be responsible for updating the URL.

When a web application updates the URL, it can do it in wo ways: either by pushing new entries to the history stack, or by replacing the current entry in the history stack. This affects the behavior of the browser's back button.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it can do it in wo ways

wo -> two


Consider the following use case: the user opens the application, looks for a team, selects a team, sorts the employee grid and selects an employee. If every URL change was pushed to the history stack, the history stack would look like the following, sorted from oldest to newest:

* `/teams`
* `/teams?search=research`
* `/teams/aftD6ZXp6?search=research`
* `/teams/aftD6ZXp6?search=research&orderEmployeesBy=lastName`
* `/teams/aftD6ZXp6/employees/zxPIVBqJ2?search=research&orderEmployeesBy=lastName`

If the user clicked the back button now, they would end up with a sorted employee grid without a selection. Another click on the button would take the user to the same employee grid, but with its default sort order. A third click on the button would deselect the team and show the search results. A final click on the button would show the list of all teams, without any filters applied.

Since the search fields has no extra button, the query is executed as soon as the user stops typing. If the user is typing slowly, you may end up with entries like the following in the history stack:

* `/teams?search=re`
* `/teams?search=resea`
* `/teams?search=research`

If the user now clicked the back button, they would be confused.

A good rule of thumb is to push new entries to the history stack whenever the path of the URL changes, and replace the current entry whenever query parameters change. With this behavior, the use case described earlier would result in the following history stack, sorted from oldest to newest:

* `/teams?search=research`
* `/teams/aftD6ZXp6?search=research&orderEmployeesBy=lastName`
* `/teams/aftD6ZXp6/employees/zxPIVBqJ2?search=research&orderEmployeesBy=lastName`

If the user clicked the back button now, they would end up with a sorted employee grid without a selection. Another click on the button would deselect the team and show the search results.

Finally, as with all rules of thumb, there may be exceptions. The important thing is to take back button behavior into consideration when designing the view URL and make sure it makes sense.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The important thing is to take back button behavior into consideration

I might tweak that to 'take the back button behavior', because when I was reading it for the first time, my brain first interpreted it as it being important to 'take back' something called 'button behavior' and then the sentence completely fell apart. It took a moment to rearrange that mentally into only 'taking' that 'back button behavior' into consideration.

Loading