All articles

Jmix 2.3 Is Released

Jmix 2.3 has recently been released. In this article, we will explore the new features and improvements that have been introduced in this update.

For more information and guidance on migration, please visit the What's New page in the documentation.

Superset Add-on

Apache Superset is a leading open-source data exploration and visualization solution. It allows you to create highly customizable dashboards that contain various charts. The charts are populated from datasets which obtain data from your database using SQL queries.

The Jmix Superset add-on lets you easily connect your application to the Apache Superset server and embed dashboards to Jmix views. For example, the main view of the Bookstore application can contain a dashboard with the aggregated information about customer orders:

superset-1.png

You only need to specify the URL and user credentials to connect to the Superset server, and the add-on will take care of requesting, refreshing and using security tokens when displaying embedded dashboards.

After creating a dashboard in Superset, you should prepare it for embedding. The Superset generates an ID to refer to this dashboard from outside.

The dashboard UI component provided by the add-on allows you to include a dashboard in an application view. You only need to specify the dashboard ID in the embeddedId attribute:

<superset:dashboard id="ordersDashboard" 
		embeddedId="4bc14bf5-a3ec-4151-979e-a920420e1f66"  
        height="100%" width="100%" maxWidth="50em"/>

The Superset server connects directly to the application database. In order to filter data on dashboards according to user permissions or by any other criteria, you can pass a list of constraints from the view to the dashboard. In the following example, the dashboard will show data for the current tenant only:

@Install(to = "ordersDashboard", subject = "datasetConstraintsProvider")  
private List<DatasetConstraint> ordersDashboardDatasetConstraintsProvider() {  
    DatasetConstraint constraint = new DatasetConstraint(25,  
            "tenant = '" + tenantProvider.getCurrentUserTenantId() + "'");  
    return List.of(constraint);  
}

For detailed information on how to use the Superset add-on with your Jmix application, please refer to the Superset add-on documentation.

Support for OpenSearch

You can now use the Jmix Search add-on with the OpenSearch service. All features of the add-on (declarative index definitions, indexing queue, UI search field, etc.) work identically with both OpenSearch and Elasticsearch engines. Choosing between OpenSearch and Elasticsearch boils down to specifying the appropriate starter dependency in build.gradle. When the add-on is installed from Marketplace, OpenSearch is selected by default.

Fragments

A fragment is a new UI building block designed for code reuse and decomposition of complex UI structures. In terms of their features, fragments lie between views and composite components.

The same as a view, a fragment can have an XML descriptor with data components and actions. It supports component injection and event handlers in the fragment class. Studio offers a template for creating an empty fragment and the same UI designer as for views.

On the other hand, a fragment can be used as a component of views and other fragments.

The purpose of fragments is to provide you with more flexibility in structuring and reusing UI code that is bound to the data model.

The example below can give you an idea of how to create and use a fragment. This simple fragment represents data of an embeddable entity called Money. The UI designer with the fragment's XML and preview looks like this:

fragment-1.png

The fragment's class (AKA controller) can define setters to accept parameters from the host. In our case, it receives a Money instance and sets it to its own data container:

@FragmentDescriptor("money-fragment.xml")  
public class MoneyFragment extends Fragment<JmixFormLayout> {  
  
    @ViewComponent  
    private InstanceContainer<Money> moneyDc;  
  
    public void setMoney(Money money) {  
        moneyDc.setItem(money);  
    }  
}

To include the fragment in a view, we use the fragment element and specify the fragment class. Our host view contains two instances of the same fragment:

<vbox>  
    <h4 text="msg://price"/>  
    <fragment id="unitPriceFragment" 
		    class="io.jmix.bookstore.view.moneyfragment.MoneyFragment"/>  
</vbox>  
<vbox>  
    <h4 text="msg://discount"/>  
    <fragment id="discountFragment" 
		    class="io.jmix.bookstore.view.moneyfragment.MoneyFragment"/>  
</vbox>

The host view controller invokes the fragment's API methods to pass the Money instances:

@ViewComponent  
private MoneyFragment unitPriceFragment;  
@ViewComponent  
private MoneyFragment discountFragment;

@Subscribe  
public void onReady(final ReadyEvent event) {  
    unitPriceFragment.setMoney(orderLineDc.getItem().getUnitPrice());  
    discountFragment.setMoney(orderLineDc.getItem().getDiscount());  
}

Data Repositories

This release completes the implementation of support for Spring Data repositories as first class citizens in the Jmix ecosystem. Now you can easily use them in views for loading and saving data, while keeping compatibility with all standard Jmix UI features like filtering, pagination and sorting.

When creating a view using the wizard, you can notice the Use Data Repository checkbox in the Advanced section. If you turn it on, you will be able to select an existing data repository. The wizard will generate a view with load and save delegates invoking methods of your repository.

The list load delegate will extract the Spring Data PageRequest from the Jmix LoadContext object and provide filtering conditions and other options to the data repository in the JmixDataRepositoryContext object. You can modify the delegate logic, for example to provide an initial sorting:

@Install(to = "customersDl", target = Target.DATA_LOADER)  
private List<Customer> loadDelegate(LoadContext<Customer> context) {  
    // convert Jmix paging and sorting parameters to Spring Data PageRequest  
    PageRequest pageable = JmixDataRepositoryUtils.buildPageRequest(context);  
    if (pageable.getSort().isEmpty()) {  
        // set initial sorting  
        pageable = pageable.withSort(Direction.ASC, "firstName", "lastName");  
    }  
    // provide Jmix conditions, fetch plan and hints  
    JmixDataRepositoryContext jmixContext = JmixDataRepositoryUtils.buildRepositoryContext(context);  
    // invoke repository method and return the page content  
    return repository.findAll(pageable, jmixContext).getContent();  
}

The above code does the same as the following JPQL in the loader:

<loader id="customersDl" readOnly="true">  
    <query>  
        <![CDATA[select e from bookstore_Customer e  
        order by e.firstName asc, e.lastName asc]]>  
    </query>  
</loader>

All methods of repositories that extend JmixDataRepository now support JmixDataRepositoryContext as an additional parameter. This makes the data repositories compatible with the genericFilter and propertyFilter components and declarative data loaders.

Lazy TabSheet Tabs

The TabSheet component is usually used when there are many UI controls on a view and you want to group them into different tabs.

In this release we added the ability to mark a tab as lazy. The content of such a tab will not be loaded automatically when the view is opened. If you use this feature for tabs that contain a lot of components and require loading of additional data, it can significantly improve performance.

The loading of data and other initialization for lazy tabs should be done in the tabsheet's SelectedChangeEvent listener, for example:

@ViewComponent  
private CollectionLoader<Position> positionsDl;  
  
private boolean positionsInitialized;

@Subscribe("tabSheet")  
public void onTabSheetSelectedChange(final JmixTabSheet.SelectedChangeEvent event) {  
    if ("positionTab".equals(event.getSelectedTab().getId().orElse(null))  
            && !positionsInitialized) {  
        positionsDl.load();  
        positionsInitialized = true;  
    }  
}

Keep in mind also that components located on a lazy tab cannot be injected into the view class, because they don't exist at that moment. So you have to use the UiComponentUtils.getComponent() method to get them after the tab has been initialized.

TwinColumn Component

The new twinColumn component is a great addition to the Jmix UI component library. It provides users with a familiar and convenient way to select items from a list:

Authorization Server Add-on

We've extracted the Authorization Server module into a separate add-on with its own documentation and installation process. Previously it was installed together with the Generic REST add-on. So now when installing the Generic REST add-on, you should decide how you will protect its endpoints: by using the Authorization Server or OIDC add-ons, or by some other means.

The new feature of the Authorization Server add-on is the implementation of Resource Owner Password Credentials grant. This grant type is not recommended by OAuth specification, but we received a lot of requests from the application developers and decided to implement it. It can be used in trusted, legacy, or highly controlled environments for simple authentication of REST clients as registered Jmix application users.

Liquibase Changelog Aggregation

The most prominent new feature in Studio is the ability to aggregate existing Liquibase changelogs. It allows developers to combine several latest changelogs into one and get rid of duplicated actions in changesets.

Consider the following situation: due to iterative development of the data model you have three changelogs. The first one adds the ALPHA column, the second adds the BETA column, and the third adds GAMMA and removes ALPHA. So the changes introduced in the first changelog are overridden by the third one.

The Aggregate Liquibase Changelogs action is available in the data store context menu. It allows you to select any number of latest changelogs for aggregation. Let's select the three changelogs described above:

aggregate-changelogs-1.png

The resulting changelog will include only changes that really bring the database schema in sync with the data model: adding BETA and GAMMA columns:

aggregate-changelogs-2.png

Studio will remove the selected changelog files and add the new aggregated one. To keep the set of changelogs compatible with databases where old changelogs have already been executed, Studio can add a precondition to the new changesets. This precondition instructs Liquibase not to execute the new changesets if the first changlog from the replaced list have been executed:

<preConditions onFail="MARK_RAN">  
    <not>  
        <changeSetExecuted id="1" author="bookstore"  
			changeLogFile="io/jmix/bookstore/liquibase/changelog/2024/06/27-1-add-alpha.xml"/>  
    </not>  
</preConditions>

The changelog aggregation should be performed with caution, because it actually doesn't analyze and reproduce the content of the existing changelogs. Instead, it generates a new changelog for the difference between the schema before the first selected changelog and the current model. So if the aggregated changelogs contain some data updates or schema changes that are not reflected in the model (for example, stored procedures), they will be lost.

Obviously, the most natural and safe way to use this functionality is to aggregate changelogs before committing latest changes to a shared source code repository, or merging a feature into a common branch.

Other Studio Improvements

This release introduces several Studio features that improve the developer experience.

  • The Jmix tool window has an action to generate a UI exception handler.
  • The Convert to action in the Jmix UI structure context menu allows you to convert one component into another with a single click. The Wrap into action can wrap multiple selected components into a TabSheet tab.
  • Improved JPQL Designer layout.
  • Improved Dockerfile generation.

See the list of all Studio improvements in our issue tracker.

What's next?

Our plans for the next feature release in October 2024 include several very important features in Jmix UI:

  • Integration of a few third-party JavaScript components: Calendar, PivotTable and Kanban board.
  • Possibility to use fragments to define inner layout of VirtualList items.
  • Tabbed main window mode, when views are opened in the tabs inside the main window instead of the browser tabs.

The latter feature is the most requested by developers who want to migrate their solutions from the Classic UI. So we are going to provide a solution for working with multiple views in a single browser tab, while sacrificing some browser capabilities such as history and deep links.

Another planned feature that can help with migration from older versions of the platform is the declarative UI permissions. This feature was present in CUBA Platform and allowed application developers and administrators to restrict access to arbitrary parts of the UI (fields, buttons, actions) without having to write any code.

On the Studio side, we are going to improve the Jmix UI inspector usability and provide more editors for UI component properties (for example for formLayout.responsiveSteps). Also, we will present the first results of our work on integrating with external data sources.

Like always, we will allocate considerable time for bug fixing, small feature additions, and performance enhancements.

Our detailed roadmap for future releases is available as a GitHub project. Patches for the current version 2.3 will be released approximately once a month to ensure regular updates.

We appreciate your feedback on our forum, and we are thankful to all those who have submitted pull requests and shared their ideas, suggestions, and bug reports!

Jmix is an open-source platform for building enterprise applications in Java