UI development

Hello everyone and welcome
to our video devoted to UI Development. Our session will focus on user interface
development in platform-based applications. We will learn more about the types of screens, screen templates, and will create new
screens using Studio screen templates. Moreover, we will discuss how to work
with different types of Datasources, describe validation options available in the platform and the actions fine-tune. Also, we will cover the rules of screen
layout and common mistakes of UI development. Generic user interface subsystem allows you
to create UI screens using XML and Java. The screens created using this approach work identically in
both standard client blocks: Web Client and Desktop Client. Main components of Generic UI screens are marked as green: XML-descriptors are XML files containing
information about datasources and screen layout. Controllers are Java classes containing logic for screen
initialization and handling of events generated by UI controls. The code of application screens included in the gui module interacts with visual component interfaces implemented separately
in the web and desktop modules of the cuba base project. For Web Client the implementation is based on the Vaadin framework; for Desktop Client — on the Java Swing framework. Visual Components Library contains a
large set of ready-to-use components. Datasources mechanism provides a unified interface that
ensures functioning of data-aware visual components. The client’s infrastructure includes the main application
window, mechanisms for display and interaction of UI screens and means of interaction with the middleware. Let’s talk about screens in more details. The following basic types of screens are available in the platform: Simple Screen. Simple screens enable displaying and editing of arbitrary
information including individual instances and lists of entities. This screen type has only the core functionality to display it in the
main window of the application, close it and to work with datasources. Screen identifier in screens.xml may have an arbitrary format. The controller of a simple screen should be
inherited from the AbstractWindow class. Lookup Screen. When a lookup screen is invoked by openLookup() method, it displays a panel at the bottom with the buttons designed to pass
an instance of the currently selected entity to the calling code. That is the main difference between the lookup and simple screen. When being invoked by openWindow() method
or, for example, from the main menu, the panel with the buttons is not displayed. Lookup screens are recommended to be
used to display lists of entities. Visual components intended to display and edit links between entities, such as PickerField, LookupPickerField, invoke lookup screens to find related entities. For standard actions to work correctly, an identifier of a lookup screen in the screens.xml
file should have the format of {entity_name}.lookup, for example, sales$Customer.lookup. The controller of a lookup screen should be
inherited from the AbstractLookup class. The lookupComponent attribute of the screen’s XML
should refer to the component (for example Table), from which the selected entity instance
should be taken as a result of the lookup. By default, Studio generates an entity
browser as an inheritor of AbstractLookup. Edit Screen. Edit screen is designed to display and edit entity instances. It initializes the instance being edited and supports
actions for committing changes to the database. Edit screen should be opened by the openEditor() method, passing an entity instance as an argument. For standard actions to work correctly, an
identifier of an edit screen in screens.xml should have the format of {entity_name}.edit, for example, sales$Customer.edit The controller of an edit screen should be
inherited from the AbstractEditor class. The datasource attribute of the screen XML should refer
to the datasource containing the edited entity instance. Frames. Frames are parts of the screen intended for decomposition and reuse. The frame element of the screen XML
is used to adding a frame to the screen. It defines either path to the frame’s XML descriptor, or its identifier, if the frame is registered in the screens.xml file. A frame controller should be derived from the AbstractFrame class. Let’s see this in the example. Open Shop → Orders Select any order and click Edit You can see that the customer field is
represented with the PickerField component. Click the […] button to open the customers lookup screen. As it was mentioned before, a lookup screen has a panel
at the bottom with the buttons: Select and Cancel. Now let’s open the same screen by invoking the main menu item. Open Shop → Customers We see that the customers browser doesn’t have a panel at the bottom. Now, let’s see how screens can be created. Let’s open Studio. Open the Generic UI section. Click the New button The Generic UI templates dialog appears. Here we can see a list of screen templates. If you want to create a screen from the very
beginning, choose the Blank screen template. For an entity you can choose between the creation of two
separate screens to read the list and create/update an instance, or a single combined screen for all operations. To create a screen based on another screen
select the Extend an existing screen template. Specify the name of the screen that you
want to extend in the Extend Screen field. The Login window template is used to
customise the original login window. There are three templates that help to
create and customise the Main Window. The first template allows creating a standard
Main Window screen provided by the platform. The main difference between two other templates and this one is that they provide a side menu on the left side of the screen. It can be responsive or not. Studio provides the ability to modify existing templates. Click Copy template to create a copy of the selected
template in the studio-templates folder of your project. The copy will be opened in the template
editor and you will be able to change it. Use the Edit and Delete buttons to manage
templates located in the current project. To open the created screen in the designer
immediately, click the Create and open button, to stay in the templates list, click Create. Let’s create the Main window screen using the side menu. Select the Side menu main window template in the list of templates Click Create. Click Close. Now, let’s see how the modified main window looks in our application. We need to restart the application by invoking
the Run → Restart application server action After the successful login, we see the new Main window. To introduce the visual interfaces editor of the Studio, we will extend the application main screen
so it will display stats for customers. Users will see the stats immediately
after logging in to the the system. We have already extended the main window,
so let’s get it open and examine. Go to the Studio. Select ext-mainwindow.xml. Click Edit. The main window is a special system screen, identified as MainWindow. The main screen controller should be
inherited from the AbstractMainWindow class. We are extending the platform main screen,
so it has some components already: SideMenu, FtsField, NewWindowButton, AppWorkArea and so on. The special AppWorkArea component is a workspace
in which the application screens are opened. If the cuba.web.appWindowMode application
property value is set to TABBED, which is by default, the TabSheet component with the application
screens will be located on the workspace. Otherwise, the workspace will contain a single open screen. If no screens are opened, the workspace contains the
components defined in the embedded initialLayout element. We will place our stats table within initialLayout,
so that it shows up when you log in to the system, if no screens are opened. So, remove the welcomeLabel from it. The components library is located at the bottom right of the screen. There is a new set of components intended
to edit the main window in the palette tab. Let’s drag the Table component from the components
library into the initialLayout container. Our new component is highlighted on the canvas, and we can change its properties using the Properties tab. Let’s set the Table size so that it fills
the entire available width and height. Set the height to 100%. Set the width to 100%. To update the table data manually, add
the Refresh button to initialLayout. Drag and drop the Button component from the library to the canvas. The space of initialLayout has split
in halves between Button and Table. In order to allocate more space for Table,
we need to set the Table id to statsTable. And set the Expand attribute in the InitialLayout to statsTable We will talk about the Expand attribute
later, as well as about the layout rules. Let’s now pass on the data level. Datasources provide work of data-aware components. Visual components themselves do not access Middleware and get entity instances from related datasources. Furthermore, one datasource can work with multiple visual
components if they need the same instance or set of instances. The dependency between the visual component and the
datasource manifests itself in the following ways: When the user changes the value in the component, the new
value is set for the entity attribute in the datasource. When the entity attribute is modified in the code, the
new value is set and displayed in the visual component. User input can be monitored both by the datasource
listener and the value listener of the component. they are fired sequentially. To read or write the value of an attribute in the application code,
it is recommended to use the datasource, rather than the component. Datasources also track changes in entities contained therein and can send modified instances back to the
middleware for storing in the database. Now let’s go back to our application. Go to
the Datasources tab. Click the New button. There are several types of datasources. Let’s see the main ones. Datasource is a simple datasource designed
to work with one entity instance. CollectionDatasource is a datasource designed
to work with a collection of entity instances. Value Datasources enable execution of queries
that return scalar values and aggregates. Value datasources work with entities of
a special type named KeyValueEntity. This entity can contain an arbitrary number
of attributes which are defined at runtime. Typically, datasources are declared in the
dsContext section of a screen descriptor. For our stats table we will use a value datasource. Select the valueCollectionDatasource as the Type Set the id to customersStatsDs Let’s move on to the Query attribute. This attribute specifies a JPQL request by which
the records are retrieved from the database. This query selects customers with the total amount of orders. Click the edit button opposite the Properties attribute. In the Properties dialog click the Add button. Set the name to customer. Set the class to Customer. Click the Add button. Set the name to sum.
Set the datatype to decimal. Click OK. Go back to the Layout tab. Select statsTable. Select customersStatsDs as a datasource. Click OK to save the screen. Let’s re-login to the application. Our table has appeared in the main window. If we open any system screen, this screen will be hidden. Currently, the data is only updated when
you open the app for the first time. Let us go back to the Studio and open the ext-mainwindow screen. Select the Refresh button. Specify the method
name, refreshData, in the invoke attribute Click the>>button. Click OK to save the screen.
Click the IDE button. The screen descriptor will be opened in Intellij Idea. Switch to the controller by clicking the name
of the controller class while holding Ctrl. In order to get access to the datasource
from the controller, let us inject it. Go to the code section and select Generate.
The IDE will show you the context menu. Select Inject. Select the customersStatsDs
from the injection dialog and click OK. The field marked with the @Inject annotation
has been added to the controller. The value of this field will be injected
when the screen is initialized. Let’s move on to the refreshData method. Updating the data in the collectionDatasource can
be performed just by calling its refresh() method. So we need to add the following code. Let’s see how it works. Log in to the
system and create some new orders. Close the orders browser and click the Refresh button. The order list has been updated. Now, let’s see an another example of datasources usage. Open the Shop → Orders screen. Click Create. We see that we need to open
a lookup screen to select a customer. Let’s make this action more convenient. Go to the Studio. Select the order-edit.xml file. Click Edit. Go to the Datasources tab and click New. Another example of using a Collection Datasource is using as an Options Datasource for components
like LookupField, LookupPickerField and so on. So, you will have an ability to select related
entities from a drop-down list of options. Select the collectionDatasource as the Type. Select Customer as the Entity. Set the id to allCustomersDs. Open the Layout tab. Select the FieldGroup component.
Open the Properties panel Click the Edit button to edit fields. Select the Customer field. And specify allCustomersDs as the optionsDatasource value. Click OK to save field. Click OK to save the screen. Let’s reopen the OrderEdit screen to see our changes. Now we can select a customer using the drop-down menu. Now, let’s see how standard actions can be fine-tuned
and what opportunities we have to define the new ones. Open the Shop → Customers. Click Create. For now, Edit Screen is opened in the current tab. Let’s see how this behavior can be changed. Go to the Studio. Select the customer-browse.xml
file in the Generic UI section. Click the IDE button. We have two options to change the open type for actions. First, we can define the openType attribute
declaratively in screen descriptor. Let’s add the openType attribute to the
create action with the DIALOG value. The second way is changing OpenType programmatically. Go to the screen controller. Inject Edit Action Override the init method. And add the following code. Let’s see how it works. Go to the application.
Reopen Customer browse. Click Create. Now, we see that editor is opened in the DIALOG mode. Click Cancel. Select any customer. Click Edit. The Editor screen is also opened in the DIALOG mode. Let’s see what other opportunities we have while working with actions. Go to the screen controller. In addition to OpenType, we can define: captions,
descriptions, icons, shortcuts and many other options Other useful things that we can define for
standard actions are handlers, for example: BeforeActionPerformedHandler,
AfterCommitHandler, AfterWindowClosedHandler Let’s add a notification after customer editing.
Add the following code. Reopen the Customers browser and edit any customer. Now, we see a notification that customer has been edited. In addition to standard actions, we can define the new ones: Programmatically by extending one of the standard actions;
Or declaratively right in the screen descriptor. Let’s define a new action declaratively. Go to the screen descriptor. Add a new action element to the table actions. Set id to sendEmail. So, imagine that we want to send some advertisement to our customers. Set caption to Send Email. Set icon. Set invoke to sendEmail. We don’t have such method yet, so,
press ALT-ENTER and choose Add Method. Now we need to add a button corresponding to the newly defined action. Add a new button element to the buttons panel of the table. Set id to sendEmailBtn. Set action to customersTable.sendEmail. Now, let’s implement the sendEmail method. CTRL-Click on the method name. Inject customersTable
Add the following code. Let’s see how it works. Reopen the Customer browser. Select any customer. Click Send Email So, an email has been successfully sent. Notice, that the Send Email button is always available and if we click
on the button until no customer is selected, we get an exception. I suppose, that it would be very convenient if
this button tracked if any customer is selected. Like the Remove or Edit buttons. Go to the screen descriptor. Add the trackSelection=”true” attribute to the sendEmail action. Reopen the Customers browser to see the changes. So, the Send Email button is available
only if any customer is selected. Let’s take a look at the rules of layout. The basic component dimensions are width and height. There are three size types: Content-dependent or AUTO size. In this case, the component will take enough space to fit its content. For example, for Label, the size is defined by text length. For container, the size is defined by the sum
of all component sizes within a container Components with content-dependent size will adjust
their dimensions during screen layout initialization or when the content size is changed. Fixed size in pixels. Fixed size implies that the component
dimensions will not change at runtime. Relative size indicates the percentage of the available
space that will be occupied by the component. Components with the relative size will react to changes in the amount of the available
space and adjust their actual size on the screen. Container specifics. By default, containers without the expand attribute
provide equal space for all nested components. Exceptions: cssLayout, flowBox and htmlBox. Components and containers width and
height are content-dependent by default. Some containers have different default dimensions. The root layout element is a vertical container, VBox, which has 100% width and height. Tabs within the TabSheet are the VBox containers. GroupBox component contains a VBox or an HBox,
depending on the orientation property value. So, let’s see some examples of what we have talked about. Equal size. In this example we see that the container
provides the equal space for components. We have the VBox and two buttons within. And each button has the same space within the container. Content-based Size. In this example we have the VBox with
AUTO size for both height and width. As you can see, the VBox has 100% width and content-based
height enough to display two buttons within it. Relative Size. Here we can see that layout, as well as Vbox and Hbox,
provides equal space to all nested components. groupBox has 100% height. In addition to that, groupBox has 100% width
by default and takes all the available space. Expand. The container expand attribute allows specifying the
component that will be given the maximum available space. The component specified in expand will have 100%
size in the direction of the component expansion. Vertically — for VBox, horizontally — for HBox. When the container size is changed, the
component will change its size accordingly. In this example the GroupBox is expanded vertically. Form. In this example, the auxiliary Label element is used. Due to applied expand, it takes all the space left in the container. So, text fields are located at the top and buttons at the bottom. Margin. The margin attribute enables setting margins
between container borders and nested components. If margin is set to true, the margin is
applied to all sides of the container. Margins can be also set for each individual side: Top, Right, Bottom, Left. Here’s the example of a top margin. Spacing. The spacing attribute indicates whether the space should be added
between nested components in the direction of the container expansion. Aligning. The Align attribute is used for
aligning components within a container. Here we can see the label which is
located in the center of the container. The component with the specified alignment should
not have 100% size in the alignment direction. The container should provide more space,
than required by the component. The component will be aligned within the available space. Now, let’s move on to some common mistakes while developing UI. Relative Size. The one of the most common mistakes is setting the relative size
for a component within a container with the content-based size. In this example, the label has 100% height, while the default height for VBox is AUTO, i.e. content-based. Here, expand implicitly sets relative 100% height for the
label, which is not correct, just like in the example above. In such cases the screen may not look as expected. Some components may disappear or have zero size. If you encounter any layout problems, check that
relative sizes are specified correctly, first of all. Also, CUBA has a built-in mechanism to validate the correct
implementation of the layout at runtime – Layout Analyzer. Right-click the tab caption and select Analyze layout. The current view of the screen has been checked for
standard errors, and they are displayed in the list. Aligning. In this example, the HBox has the content-dependent
size, therefore the label alignment has no effect. It is important to remember that layout errors do not always
cause issues with displaying the screen in the browser, but they may lead to issues with making changes to the screen later. That is why they must be corrected, even if the screen looks OK. Our next topic is data validation. Data validation is a common task that
occurs in all layers of an application. Let us talk about validation options available in the platform. First of all, it is the Field.Validator interface. Then it is Bean Validation. And the third approach of validation is the postValidate() method. First of all, fields can be assigned a validator — a
class implementing the Field.Validator interface. The validator limits user input in addition
to what is already done by the datatype. Let’s assign a validator to the customer’s email field. Open the Generic UI section. Select customer-edit.xml. Click the IDE button. Go the the IDE. Inside customer-edit.xml, add a nested
component, validator, to the email field. And add the class EmailValidator. Let’s see how our changes work. Go to the application. Open the Shop → Customers screen. Click Create. Type some random symbols in the email field. Click OK. We see the default error message what we
set an invalid value for the email field. The second approach of validation is bean validation. Bean validation is an optional mechanism that provides
uniform validation of data on the middleware, in Generic UI and REST API. Let’s add bean validation for the quantity
attribute of the OrderLine entity. Go to Studio. Open the Data Model section. Select the OrderLine entity. Click Edit. Select the quantity attribute. Here we can see the list of available validations. Click the not set button opposite the DecimalMax validation Tick enabled. Set value to 20. Optionally, you can define an error message. Click OK. Click the not set button opposite the DecimalMin validation Tick enabled. Set value to 1. Click OK. Go to the Source tab. Here you can see two annotations added to the quantity attribute. Generic UI components connected to a datasource get an
instance of BeanValidator to check the field value. The validator is invoked from the
Component.Validatable.validate() method implemented by the visual component and can throw CompositeValidationException that contains the set of violations. Let’s see how this works. Restart the application. Open the Shop → Orders screen. Select any order and click Edit Click Create a new line. Set the quantity value to 0 and click OK. We see the default error message what we set
an invalid value for the quantity field. The third approach of validation is the postValidate() method. postValidate() is a template method that can be implemented
in a controller for additional screen validation. The method stores validation errors information in
the ValidationErrors object which is passed to it. Afterwards, this information is displayed
together with the errors of standard validation. Let’s see how this works. Open the Generic UI section. Select the product-edit.xml file. Click the IDE button. Go the the IDE and switch to the controller. And we need to add the following code. Go to the application. Open the Shop → Products screen. Select any product and click Edit. Set the price value to 0 and click OK. We see the defined error message what we
set invalid value for the price field. If you have any questions, feel free to post questions on
our forum page, and be sure you’ll soon get an answer. Thanks for watching!


Add a Comment

Your email address will not be published. Required fields are marked *