opened 09:18AM - 18 Jul 22 UTC
size: L
in: flowui
## Fragment
New base class for fragments: `Fragment`.
A fragment encapsula…tes a `Component` tree to allow creation of new components by composing existing components. The main purpose of fragments is to be used as a part of views and other fragments. By encapsulating the component, its API can be hidden or presented in a different way for the user of the fragment.
The encapsulated component tree is available through `getContent()`. Fragment will by default look at the generic type declaration of its subclass to find the content type and create an instance using `UiComponents`. You can also override `initContent()` to manually create the component tree or define components tree using XML markup and bind it to a fragment using `FragmentDescriptor`.
Fragment is a way to hide API on the server side. It does not contribute any element to the `Element` tree.
`Fragment` extends Vaadin `com.vaadin.flow.component.Composite` to have specific fixes, e.g. see `com.vaadin.flow.component.Composite#setContent` comment.
## Fragment descriptor
### @FragmentDescriptor annotation
Specifies a string value that is a file path to an xml descriptor that can be used for a `Fragment` initialization. If the **value** contains a file name only (i. e. don't start with `/`), it is assumed that the file is located in the **package** of the fragment class.
**Note:** if it's assumed that fragment can be extended, the file path should be defined as full path.
### Supported elements
There are three root elements:
1. `content` - required element that contains fragment layout (similar to view `layout` element). Since the root component of Fragment can be any component, `content` has no attributes and **doesn't represent any component**.
2. `actions` - optional fragment actions element (similar to view `actions` element). Actions defined for fragment have shortcut bound to root fragment, i.e. shortcut can be triggered only if focus within fragment layout.
3. `data` - optional fragment data element (similar to view `data` element). A fragment can have its own data containers and loaders, defined in the data XML element. At the same time, the framework creates a single instance of `DataContext` for the view and all its fragments. Therefore all loaded entities are merged to the same context and their changes are saved when the host view is committed. Data components defined with `provided="true"` attribute which means that the container with the same id must exist in a host screen or enclosing fragment, i.e it must be provided from outside.
```
<fragment xmlns="http://jmix.io/schema/flowui/fragment">
<data>
<collection id="productsDc" provided="true"
class="test_support.entity.sales.Product"/>
</data>
<actions>
<action id="testAction" text="msg://testAction.text" icon="PLUS"/>
</actions>
<content>
<vbox id="contentBox" classNames="overflow-auto">
<hbox id="buttonsPanel" classNames="buttons-panel">
<button id="createBtn" action="dataGrid.create"/>
<button id="editBtn" action="dataGrid.edit"/>
<button id="removeBtn" action="dataGrid.remove"/>
<button id="testBtn" action="testAction"/>
<gridColumnVisibility dataGrid="dataGrid" icon="COG_O"/>
</hbox>
<dataGrid id="dataGrid"
dataContainer="productsDc"
width="100%"
minHeight="20em">
<actions>
<action id="create" type="list_create"/>
<action id="edit" type="list_edit"/>
<action id="remove" type="list_remove"/>
</actions>
<columns resizable="true">
<column property="name"/>
<column property="price"/>
</columns>
</dataGrid>
</vbox>
</content>
</fragment>
```
**Note:** Nested components also support their actions, e.g. `DataGrid`.
### Unsupported elements
* `facets` - closely connected with views.
### Specifics of defining a layout in XML
To avoid **id** duplicates in components tree when several fragments of the same type is used simultaneously or fragment inner components have the same **id** as other components in the components tree, the value of `id` attribute of each component defined in fragment descriptor (so called **fragment id** or Shadow XML id) is stored in the component `Attributes` field instead of setting it as **actual id** using `Component. setId(String)`.
## Fragment API
* `getFragmentData()` - returns an object defining methods for interacting with data API elements of a fragment. Similar to `ViewData`.
* `getFragmentActions()` - returns an object defining methods for interacting with actions API of a fragment. Similar to `ViewActions`.
* `getParentController()` - returns a parent `FragmentOwner` object. Currently, it may be `View` or `Fragment`.
* `findInnerComponent` / `getInnerComponent` - return the inner component with given **fragment id**. These methods search among components added via an XML descriptor only.
* `ReadyEvent` - The event that is fired after the fragment and all its declaratively defined inner components are created and fully initialized. In this event listener, you can make final configuration of the fragment and its inner components, e.g. obtain inner components using `getInnerComponent(String)` and add their specific event listeners.
## Fragment element
Added `fragment` xml element that is used to create any fragments by FQN. In order to define fragment properties the nested `property` elements can be used (similar to actions).
For example:
```
<fragment id="dataGridFragmentProvided"
class="component.fragment.component.TestDataGridFragmentProvided"/>
<fragment id="dataGridFragment"
class="component.fragment.component.TestDataGridFragment">
<properties>
<property name="dataContainer" value="productsDc" type="CONTAINER_REF"/>
</properties>
</fragment>
```
## Fragments factory
In order to create a fragment programmatically, the `Fragments` bean **must** be used.
```
@Autowired
public Fragments fragments;
@Subscribe
public void onInit(InitEvent event) {
AddressFragment addressFragment = fragments.create(this, AddressFragment.class);
getContent().add(addressFragment);
}
```
## Injection
Similar to views, fragments support for injection of components defined in XML and their events, e.g.:
```
@ViewComponent
public JmixButton button;
@ViewComponent
public CollectionContainer<Customer> collectionDc;
@Subscribe
protected void onReady(ReadyEvent event) {
// ...
}
@Subscribe(value = "button", subject = "clickListener")
protected void onButtonClick(ClickEvent<JmixButton> event) {
// ...
}
@Install(to = "collectionDl", target = Target.DATA_LOADER)
protected List<Customer> collectionDlLoadDelegate(LoadContext<Customer> loadContext) {
return Collections.emptyList();
}
@Supply(to = "dataGrid.name", subject = "renderer")
protected Renderer<Customer> dataGridNameRenderer() {
return new ComponentRenderer<>(this::createGradeComponent, this::gradeComponentUpdater);
}
```
In addition to that, it's possible to subscribe on **Host View** events by defining `target = Target.HOST_CONTROLLER`, e.g.:
```
@Subscribe(target = Target.HOST_CONTROLLER)
protected void onHostInit(View.InitEvent event) {
// ...
}
@Subscribe(target = Target.HOST_CONTROLLER)
protected void onHostBeforeShow(View.BeforeShowEvent event) {
// ...
}
@Subscribe(target = Target.HOST_CONTROLLER)
protected void onHostReady(View.ReadyEvent event) {
// ...
}
```
## Generic Component XML element
Added `component` xml element that is used to create any UI component by FQN. In order to define component properties the nested `property` elements can be used (similar to actions).
For example:
```
<component id="button" class="com.vaadin.flow.component.button.Button">
<properties>
<property name="text" value="Button"/>
<property name="icon" value="PLUS" type="ICON"/>
</properties>
</component>
<component id="propertyFilter" class="io.jmix.flowui.component.propertyfilter.PropertyFilter">
<properties>
<property name="property" value="name"/>
<property name="operation" value="CONTAINS"/>
<property name="dataLoader" value="productsDl" type="LOADER_REF"/>
<property name="parameterName" value="productName"/>
<property name="label" value="Property Filter"/>
<property name="tabIndex" value="2"/>
</properties>
</component>
<component id="testComponent" class="component.generic_component.component.TestComponent">
<properties>
<property name="stringsList" value="a b ,c"/>
<property name="stringsSet" value="a b ,c"/>
<property name="stringsArray" value="a b ,c"/>
<property name="strings" value="a b ,c"/>
<property name="dataContainer" value="productsDc" type="CONTAINER_REF"/>
</properties>
</component>
```
### Properties limitations and specifics
* The `name` attribute value is converted to setter method name, e.g. `text` -> `setText`.
* The first method with this name and a single input parameter is used to set the value.
* (limitation) if there are several methods with the same name, no guarantee that the one you need is used to set the value
* The found method input parameter type is used to convert string value of the `value` attribute into actual value.
* The `type` attribute is used to indicate that string value representation must be converted by one of pluggable `PropertyParser` beans.
## Property parsers
As been mentioned above, if string value cannot be parsed using input parameter type, e.g. **icon** which is a `Component` or **data container** which is defined by its `id`, pluggable `PropertyParser` beans are used.
Currently there are three implementations:
* `IconPropertyParser` - converts string into a new `Icon` instance with the icon determined by the passed string. If a passed string contains `:` delimiter then a new `Icon` is created using **icon collection** and **icon name** values, otherwise the passed string is considered as `VaadinIcon` constant name.
* `DataContainerPropertyParser` - returns **data container** with the given `id` from corresponding view's `ViewData` object.
* `DataLoaderPropertyParser` - returns **data loader** with the given `id` from corresponding view's `ViewData` object.
## Fragment replacement
If a fragment needs to be extended and replaced by its inheritor, a `ComponentRegistration` should be used like for regular component, e.g.:
```
@Bean
public ComponentRegistration addressFragmentExt() {
return ComponentRegistrationBuilder.create(AddressFragmentExt.class)
.replaceComponent(AddressFragment.class)
.build();
}
```