This website uses cookies for visitor traffic analysis. By using the website, you agree with storing the cookies on your computer.More information

Category: Development

Xamarin Client (simple PoC)

What is Xamarin?

Read all details here.

It's a company and a really cool platform for mobile application development. It enables you to use one codebase for iOS and Android.

Since 2016, it's more or less free for everyone.

We tried Xamarin some years before 2016 and it wasn't free. So it wasn't an option for us. Not because it's using C# as programming language. We already have mobile clients for JVx applications but they don't share the same codebase. There is one client for Android and another one for iOS. The source code is licensed under Apache 2.0.

Our native clients work great but we think they could be better.

In the last days we made some tests with different tools, to create a new universal client with one technology. A HTML5 client wasn't an option for us because of different criterias. A real native client was important during our evaluation.

Some links to different opinions:

We made good first progress with Xamarin and because of its pricing, it was the winner of our evaluation. Sure, Xamarin is C# but it's better to write C# than JavaScript. Another plus was that we already have a simple JVx connection port to .NET based on C#. So we could reuse this code.

Why not JavaFX?

The problem with JavaFX is, that it's unclear what will happen with JavaFX in the future. There are different articles about the future of JavaFX, but the one from Jonathan Giles makes it clear

Sure, there's Gluon and some other individual developers, but the overall performance isn't relly comparable to native apps. It's awesome what happened in the last years, but the progress is missing.

We still have our JavaFX based UI, but we're not sure if JavaFX will survive or it will be commercialized. So it's better to have an option.

Why not CodenameOne?

It's a really cool technology and works great with some smaller performance glitches (simply try the current KitchenSink from the App stores)..
WORA just works!

The cloud build isn't what we prefer and the pricing is not good for our use-case. Our client should be free, open and easy to use for every developer.

Here is a simple test application for REST access to our Heroes application, used in our Angular test application.

iOS client

iOS client

Android client

Android client

The source code for this test:

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using Newtonsoft.Json;
using Xamarin.Forms;

namespace TableViewSamples
{
  public class RESTTable : ContentPage
  {
    public RESTTable()
    {
      GetData();
    }

    async void GetData()
    {
      this.Title = "REST Data";
      var table = new TableView() { Intent = TableIntent.Data };
      var root = new TableRoot();
      var section1 = new TableSection() { Title = "First Section" };

      var authData = string.Format("{0}:{1}", "user", "pwd");
      var authHeaderValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(authData));

      HttpClient client = new HttpClient();
      client.DefaultRequestHeaders.Authorization =
        new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", authHeaderValue);
      HttpResponseMessage response = await client.GetAsync("https://...");

      if (response.IsSuccessStatusCode)
      {
          HttpContent content = response.Content;

          var result = await content.ReadAsStringAsync();

          var parsed = JsonConvert.DeserializeObject<List<HEROES>>(result);

          foreach (var record in parsed)
          {
              System.Diagnostics.Debug.WriteLine("Data: {0}", record);

              section1.Add(new TextCell { Text = record.NAME });
          }
      }

      table.Root = root;
      root.Add(section1);

      Content = table;
    }
  }

  public class HEROES
  {
    public int ID { get; set; }
    public string NAME { get; set; }
  }
}

VisionX 5 is available

visionx_five_small VisionX 5 is available

It's an awesome version with so many new features and new possibilities.
We had some hard months but now it's done and it's available for everyone.

We made the decision to wait a little bit longer with VisionX 5 because of changes in used frameworks.

The new Version is more web centric than any version before, but it's not a web tool. VisionX 5 still supports Desktop application creation.

So what's new?

  • Default Stylesheet

    All new applications will contain an empty application.css. This file could be used to add custom HTLM5 application styles.

  • New CSS Editor (Web settings)

    The new CSS editor makes it possible to change the style of the application directly within VisionX. The Editor supports Search/Replace/Goto line. It's also supported to reload the application in the browser. Simply test your style changes immediately.

    CSS Editor

    CSS Editor

    It supports syntax highlighting and auto completion. Read more...

  • New default Theme for HTML5

    Newly created applications will use the new default theme "valo" as standard theme. It's possible to change the theme in web settings wizard.

  • Additional Themes

    VisionX 5 is shipped with following themes: Standard, Valo, Valo (small), Mobile

  • Navigation

    The page navigation is now enabled by default. It support Back/Forward Browser navigation for the application.

  • New Tabset navigation

    It's also possible to enable tabset navigation. This feature enables Back/Forward navigation for tab sheets. This feature is disabled by default. It can be found in the web settings.

  • Favicon

    It wasn't easy to change the favicon for your application because it is stored in different locations. VisionX 5 solves this problem because it's possible to change the favicon directly in VisionX.

    Favicon support

    Favicon support

    It's also possible to use the favicon as icon for desktop applications. VisionX will extract the image directly from the favicon.

  • New GUI element: Menu Button

    The Menu Button is a simple button which shows a popup menu if you click the button. The component itself also supports a default action, but this is a new feature of JVx' UIPopupMenuButton. The component is available in the elements area.

    Menu Button

    Menu Button

  • New Web Elements

    The web elements is a new element area. The elements are preconfigured components like an Insert Button. This Button simply inserts a new record in a table. This web elements will work for desktop applications as well, but are often used in web applications.

    Web Elements

    Web Elements

  • New web element: Table Menu Button

    The Table Menu Button is a Menu Button with preconfigured menu items like Delete, Export. It's also possible to add custom menu items via menu customizer.

    Table Menu Button Items

    Table Menu Button Items

  • Menubar Image

    It's now possible to configure the web menus independent of the desktop menu. Every screen has a menu icon and a toolbar icon. The menu icon will be used for the menubar and the toolbar icon for the toolbar in Desktop applications.

    The HTML5 application has different layout modes. The standard, the corporation and the legacy mode. The standard mode shows the menu in a sidebar on the left or right of the browser frame. This sidebar was used as toolbar in versions before VisionX 5. If you did configure a toolbar image, the screen was shown as image in the toolbar of your Desktop application and in the sidebar of your HTML5 application. It wasn't possible to configure an image, only, for the HTML5 sidebar. This feature is now available in the menu settings wizard.

    Web Menubar

    Web Menubar

    The toolbar image will be used for the toolbar in the corporation mode, as usual.

  • New Edit Panel Customizer

    The Edit Panel now supports a Navigation mode: Single click, Double click and No navigation.

    Customizer

    Customizer

    It's also possible to morp an Edit Panel into a Tabset, a simple Panel or a Group Panel.

  • All/No columns

    It's now possible to show/hide all columns of a table via customizer. It wasn't nice to click through e.g. 50 columns. Now it's easy to show only 2 columns of e.g. 50 columns.

    Show/Hide all

    Show/Hide all

  • Default image mapping for HTML5

    It was possible to map desktop images for e.g. HTML5. It's better to use simple/flat images in web applications compared to desktop applications. The web style should be or is different to standard desktops.

    With VisionX 5, we did the default mapping for you and a standard web application will show different standard images.

    Image mapping

    Image mapping

    Same application, no changes necessary. Only some image mappings!

  • FontAwesome support

    JVx has FontAwesome support for a while, but this feature wasn't integrated in VisionX. The new image chooser supports FontAwesome.

    FontAwesome

    FontAwesome

  • Custom image libraries

    It's now supported to extend VisionX with custom image libraries. We have different commercial AddOns in our Solution store.

    Image library AddOns

    Image library AddOns

  • The libraries will extend the image chooser.

    Extended Image chooser

    Extended Image chooser

    It's also possible to integrate your own offline image libraries.

  • New tutorials

    We have new tutorials and new Tutorial Applications in our Store.

  • New Edit Panel features

    The integrated edit panel has new features like Tab Navigation by Buttons. The edit dialog now has OK and Cancel. It supports single our double click navigation.

  • Server actions and DB functions

    Our action editor now supports (Oracle) database function calls and server-side function calls.

    Server Actions

    Server Actions

  • Better Action Code Editor

    The source code editor now supports syntax highlighting and code folding.

    Action Code Editor

    Action Code Editor

  • New Button options

    It's now possible to change the button border behaviour.

    Button options

    Button options

  • New Search Options

    The search field now supports Contains and Starts with.

    Search options

    Search options

  • Better HTML5 default styling

    We improved the Split Panels and fixed some problems with focus.

    HTML5 Styling

    HTML5 Styling

    The split handle is now small.

  • Vaadin 8

    Our HTML5 GUI is now based on Vaadin 8.1 instead of Vaadin 7. With Vaadin 8, the UI is faster and supports all modern AddOns from the Vaadin Directory.

    We offer a Vaadin AddOn for VisionX to integrate AddOns from the directory.

  • New HTML5 layouting mechanism

    We rewrote the layout mechanism of our HTML5 UI because our Form Layout didn't support all available features. Now it's possible to overlap elements or to work with Anchors directly. The new layout mechanism can be disabled if necessary. In this case, the old mechanism will be used.

  • Documentation

    The online documentation is now available.

    Documentation

    Documentation

  • Library updates

    JVx changes, VaadinUI changes

VisionX 5 still contains Applet and Webstart support, but with VisionX 6 we will remove Applet support because the technology is EOL.

The new Version of VisionX will require a new license. Simply ask your sales contact to get a new one. The download area already contains links to VisionX 5 binaries.

Please report any problems as usual and have fun with VisionX 5.

Eclipse Oxygen.2 with ANT and JRE6

The support for ANT and JRE6 with Eclipse Oxygen.2 (December 2017).

The plugin was created for:

Version: Oxygen.2 Release (4.7.2)
Build id: 20171218-0600

Don't forget the -clean start (read the original article for more details)!

One more thing you should do:

Oxygen.1 and Oxygen.2 contain ANT 1.10.1. This version requires Java 8. In order to use Java 6, you have to download ANT 1.9.x. Simply download ANT and set the ANT Home to your version, via

Window >> Preferences >> ANT >> Runtime >> Ant Home...

If you try to use the bundled ANT with JRE 6, it will fail with an Exception.

Download the plugin from here. It works for us - no warranty!

More Details: Eclipse Oxygen.1 with ANT and JRE6

VisionX CSS Styling feature

The upcoming release of VisionX will support CSS styling. We'll show you a first impression with a short video:

Vaadin AddOn for VisionX

The editor supports autocompletion and syntax highlighting:

VisionX CSS Editor

VisionX CSS Editor

We did integrat the RSyntaxTextArea for this. If you want to know how this works, check the documentation.

JVx and PostgreSQL, supporting Savepoints

Transactions are an integral mechanism of databases, without them we could hardly ensure data consistency. Well, we could of course, but it would be a lot more work. JDBC has of course support for transactions, but it also supports so called "Savepoints", which we will have a look at today.

What are transactions?

Normally, I'd assume that everyone of you knows what transactions are and what they are used for, but today I will quickly rehash it to make sure. Transactions enable us to define groups of statements to be executed on a database with all either succeeding or all failing. Imagine the following statements which might be executed on a database as one action:

1: Insert record into A
2: Update record of B
3: Update another record of B
4: Insert record into C

This might happen in the background if you click a button or do something similar. This example is straightforward but if we start thinking about possible problems we will soon realize that there is a lot of potential for things going wrong. For example if statement #3 would fail we would miss records in the tables B and C. Overall, our data would be in an inconsistent state after that. To make sure that it does not happen, we can define these statements as a single transaction.

Transaction
  |-Insert record into A
  |-Update record of B
  |-Update another record of B
  |-Insert record into C
  \-Commit

If one of these statements fail, all changes done by previous statements can be undone and the database will return to the state before we started manipulating it. That is great, because we can now guarantee that even though an error has happened, the data will remain in a consistent state.

We could even extend that with additional application logic, for example we insert three records and then we check if the values of all records add up to a certain threshold, if yes, we simply undo the changes. Or if we notice that a constraint has been violated (though, pretty much all databases support constraint definitions in one way or the other). Additionally, transactions are completely isolated from all other connected clients. That means that if we start a transaction and insert a record into table A, all other clients will not see this record until we commit our changes. So the data is never in an inconsistent state, not even temporarily or transiently.

What are Savepoints?

Savepoints are sub-transactions within another transaction. It allows to undo parts of an ongoing transaction.

Another example:

Transaction
  |-Insert record into A
  |-Insert child record into B
  |-Insert child record into B
  |-Insert another record into A
  |-Insert child record into B
  |-Insert child record into B
  |-Insert another record into A
  |-Insert child record into B
  |-Insert child record into B
  \-Commit

This is a little bit more fabricated and complicated, assume we want to insert three master records with two detail records each. The following conditions apply:

  • If a master record fails to insert, nothing should be inserted.
  • If a detail record fails to insert, the corresponding master record should also not be inserted.

This is hard to do with a simple transaction, but is quite easy when it comes to using savepoints:

Transaction
  |-Savepoint 1
  |   |-Insert record into A
  |   |-Insert child record into B
  |   \-Insert child record into B
  |-Savepoint 2
  |   |-Insert another record into A
  |   |-Insert child record into B
  |   \-Insert child record into B
  |-Savepoint 3
  |   |-Insert another record into A
  |   |-Insert child record into B
  |   \-Insert child record into B
  \-Commit

We create a savepoint before the insert of the master record, if the insert of the master record fails we rollback the transaction, if the insert of a detail record fails we rollback to the savepoint we created earlier. This allows quite complicated and nested transactions, especially because there is no defined limit to how deep savepoints can be nested.

Error behavior

Of course it can always happen that a statement fails for one reason or another, so it is important to know how the database behaves once an error occurred. We built ourselves another simple example:

Transaction
  |-Insert record into A
  |-Insert record into B (this fails)
  |-Insert record into C
  \-Commit

We insert three records, and the second one fails to insert. How should the database behave in such a situation? Turns out that this differs between different database systems.

Silent/Automatic restore to a previous state

Many databases perform a simple "silent/automatic restore to a previous state", all changes done (if any) of the current statement are undone and the transaction can be treated like the failing statement never happened. With the example above, and assuming that we do not cancel the transaction on an error, the records would be inserted into A and C.

In our tests Oracle, MySQL/MariaDB, H2 and SQLite were all behaving this way.

Requiring manual recovery

PostgresSQL requires to perform a "manual recovery" from a failed statement. So that once an error occurred during a transaction, the user has to revert to a (manually) set savepoint or rollback the complete transaction. We will go into details on that later.

Reverting everything and happily continuing

MS SQL on the other hand has a quite different approach. When an error occurs during a transaction, all changes are (automatically) rolled back but the transaction can still be used. So in our example, only the record in C would be inserted.

PostgreSQL JDBC and Savepoints

Back to PostgresSQL and how it requires manual recovery. When a statement fails within a transaction in PostgreSQL, the transaction enters the aborted state and one can then see an error like the one below if further statements are issued:

Current transaction is aborted, commands ignored until end of transaction block.

What that means is simple that the connection/server is still waiting on input on what to do with the transaction. There are three possible ways to recover from there:

  1. Rollback the complete transaction
  2. Commit, which will be transformed into a rollback at this point
  3. Rollback to a savepoint

But these actions must be initiated by the user before the transaction can be further used (or not, if it is being rolled back completely). If one wants to emulate the behavior of other databases in PostgreSQL, every statement that is executed within a transaction has to be "wrapped" with a savepoint, in pseudo-code:

begin transaction
    savepoint
    try
        execute statement
        release savepoint
    catch
        rollback to savepoint
   
    ...
commit

Even though that seems tedious, that is not the case. If you have such a requirement you already have central point through which all statements pass before being executed, so this can be implemented easily.

JVx and Savepoints

In our case it is DBAccess, our main datasource. Because every database interaction has to pass through DBAccess (in one way or the other), we could easily implement such emulation at a low-level and it is automatically available to all users. To be exact, DBAccess has received internal support to wrap all statements in savepoints when configured to do so. This configuration possibility is protected and is currently only used by the PostgresSQL DBAccess extension. It does exactly what it says on the tin and is only active when enabled and automatic commits have been turned off. So this change does have no effect on any other database but PostgreSQL.

We already have plans to extend this basic savepoint support with a public API which allows users of JVx to utilize this new functionality. One of the ideas that we are currently discussing is to provide the ability to create named savepoints. A simple mockup of that idea:

  1. // Switch off auto commit to use transactions.
  2. dbAccess.setAutoCommit(false);
  3.  
  4. // Insert some data.
  5. dbStorage.insert(aRecord);
  6. dbStorage.insert(anotherRecord);
  7.  
  8. // This part is optional.
  9. try
  10. {
  11.     dbAccess.setSavepoint("NAME");
  12.    
  13.     dbStorage.insert(yetAnotherRecord);
  14.     dbStorage.update(someOtherRecord);
  15. }
  16. catch (DataSourceException dse)
  17. {
  18.     log.error(dse);
  19.     dbAccess.rollbackTo("NAME");
  20. }
  21.  
  22. dbAccess.commit();

As said, we are currently in the process of discussing such possibilities but definitely want to provide such an API at one point.

Conclusion

As it turns out, the "special" behavior of PostgreSQL isn't as special as it seems to be. It is a design decision that was taken and that is understandable. Changing this behavior now, 20+ years in, is out of the question as it would require a substantial effort to make sure that this behavior is backwards compatible. The gains from such a change on the other hand would be very little, as it is a quite specialized case in which this behavior matters and the "fix" is rather easy.

DokuWiki XMLRPC with Java

DokuWiki

is a simple to use and highly versatile Open Source wiki software that doesn't require a database.

It's a really nice software for your documentation. The default theme isn't super modern, but it works. There are many custom themes available and styling is simple.

The really cool thing is the remote API of DokuWiki. It has a XML based RPC interface. Here are some details.

The documentation looks promise. There's a Java example :)
But the last update of the Java client was 2016 :(

After downloading and testing the client, it was clear that it doesn't work. It had a problem with cookie management. I tried to fix the problem in the base lib - aXMLRPC but it was tricky. The problem was the standard Http URL connection. After replacing it with Apache HttpClient, the dokujclient was working without problems.
I didn't try to find the root cause of the problem because it wasn't worth the time. It was faster to replace the communication part.

Here's a link to our patch and all required libraries (pre-built).

Here's a short snippet

DokuJClient client = new DokuJClient(url);
client.login(username, password);

client.putPage(pNamespace + ":" + pPageName, sText);

FileSearch fs = new FileSearch();
fs.search(dirImages, true);

for (String file : fs.getFoundFiles())
{
    client.putAttachment(pNamespace + ":" + pPageName + ":" + FileUtil.getName(file),
                         file, true);
}

client.logoff();

We use the remote interface for automatic article updates. It works great!

EPlug 1.2.7

We're happy to announce the release of EPlug 1.2.7. This minor update comes with a big performance improvement!

Major performance improvement

The most notable and important feature of this release is the major speed improvement when checking files. We've revisited how the build pipeline works and could determine quite a few bottlenecks which did not only encumber the build process, but also the live checks of databooks. After fixing these bottlenecks the databook live checks are blazing fast and one does not notice any more that these are active.

While reviewing the code, we also saw the possibility for a few more tweaks which we will incorporate into the next major release, to squeeze even more performance out of this.

Improved DataBook View

The DataBook view has also received some much needed attention. There is now a toolbar button which allows to sort the columns by their name (instead of their order as returned by the datasource). Additionally the view does no longer flicker when refreshing and will also clear itself when the last editor closes.

Other fixes

  1. There is no longer a sleeping EPlug job in the Progress View.
  2. The EPlug preferences are now only available on Java projects.
  3. If there is no screen lifecycle object, the session object is now used.
  4. Removed raw HTML in tooltips.

How to get it?

Simply update EPlug via Eclipse!

JVx 2.7 is available

We're happy to announce that JVx 2.7 is available. It's a bigger bugfix release with a small number of new features.

What's new?

  • Tibero Database Support
  • boolean support

    Our DBAccess implementation now supports boolean as parameter.

  • SimpleJavaSource

    It's now possible to create java objects from java source code, see our REST interface

  • Struct support

    Struct support for Oracle DB in procedures and functions.

  • Session timeouts

    We fixed the problem of session timeouts with long up/downloads.

  • WorkScreen

    It's now possible to open a work-screen with additional parameters. A new (optional) Parameter class is available.

  • Mac OS

    We fixed the problems with internal frame borders.

  • Many improvements

The full changelog is available here.

Start with JVx

VisionX, JVx and native Vaadin

If you create an application with VisionX, it's always a JVx based application. You get all advantages of JVx and its GUI independency, but sometimes the GUI indepency is not important and you want to use native GUI controls in your JVx application because JVx doesn't contain the GUI control or you need a commercial control. This is a very simple use-case and it's not a problem to mix JVx components with native components. We have different examples, with different GUI technologies, for this use-case.

Here are some links:

But we don't have a link for our Vaadin implementation. But no problem, here it is!

Simply create a new application with VisionX and a dummy screen like this one:

Simple screen

Simple screen

The screen contains a simple table and two editors. Nothing special. Now we want to add a custom Vaadin component in the empty space. It doesn't matter which component you use. Every vaadin component or AddOn component can be used. JVx doesn't do specific things, it's only an UI abstraction layer.

So, lets add an Accordion to the screen:

Integrated Accordion

Integrated Accordion

The Accordion component is a standard Vaadin component, simply added to the screen. But more... Do you see the "Show Vaadin Notification" Button? This is a standard JVx component. So we mix native vaadin components with standard JVx components and get the full power of both. One advantage of the JVx components is that the automatic translation works without additional hacks, or what about JVx' layouts, event handling, ...

Interested in the source code?

No worries, it's super simple to understand :)

Lets have a look at the custom code:

UIButton butNotification = new UIButton("Show Vaadin Notification");
butNotification.eventAction().addListener(new IActionListener()
{
        public void action(UIActionEvent arg0) throws Throwable
        {
                Notification noti = new Notification("Message",
                                                     "Description", Type.WARNING_MESSAGE);
                noti.setDelayMsec(2000);
                noti.show(Page.getCurrent());
        }
});

UIFormLayout flJVxPanel = new UIFormLayout();

UIPanel panJVxPanel = new UIPanel();
panJVxPanel.setLayout(flJVxPanel);

panJVxPanel.add(butNotification);

if (getApplication().getLauncher().isWebEnvironment())
{
        Accordion acc = new Accordion();
        acc.setHeight(100.0f, Unit.PERCENTAGE);

        for (int i = 1; i < 8; i++)
        {

                if (i >= 2)
                {
                        final Label label = new Label("Welcome sheet!", ContentMode.HTML);
                        label.setWidth(100.0f, Unit.PERCENTAGE);

                        final VerticalLayout layout = new VerticalLayout(label);
                        layout.setMargin(true);

                        acc.addTab(layout, "Tab " + i);
                }
                else
                {
                        groupPanelOverview.add(panJVxPanel);

                        acc.addTab(((Component)panJVxPanel.getResource()), "Tab " + i);
                }
        }

        groupPanelOverview.add(new UICustomComponent(acc), formLayout1.getConstraints(0, 2, -1, -1));
}

The Accordion source code was copied from Vaadin Sampler:

sample = new Accordion();
sample.setHeight(100.0f, Unit.PERCENTAGE);
 
for (int i = 1; i < 8; i++) {
    final Label label = new Label(TabSheetSample.getLoremContent(), ContentMode.HTML);
    label.setWidth(100.0f, Unit.PERCENTAGE);
 
    final VerticalLayout layout = new VerticalLayout(label);
    layout.setMargin(true);
 
    sample.addTab(layout, "Tab " + i);
}

So, what are the most interesting parts in our code?

First, we add the JVx panel to another JVx panel. This is important to get support for translation. If it's not important for your, simply ignore the line:

groupPanelOverview.add(panJVxPanel);

The groupPanelOverview is a simple UIGroupPanel with UIFormLayout:

UIFormLayout formLayout1 = new UIFormLayout();

UIGroupPanel groupPanelOverview = new UIGroupPanel();

groupPanelOverview.setText("Overview");
groupPanelOverview.setLayout(formLayout1);

groupPanelOverview.add(labelName, formLayout1.getConstraints(0, 0));
groupPanelOverview.add(editOverviewName, formLayout1.getConstraints(1, 0));
groupPanelOverview.add(labelDescription, formLayout1.getConstraints(0, 1));
groupPanelOverview.add(editOverviewDescription, formLayout1.getConstraints(1, 1, -1, 1));

Our JVx button will be added to the native vaadin Accordion with following code:

acc.addTab(((Component)panJVxPanel.getResource()), "Tab " + i);

We don't add the JVx component itself, we use the wrapped resource. This is a simple vaadin component: com.vaadin.ui.Button

And finally, we add the Accordion as custom component to our JVx group panel:

groupPanelOverview.add(new UICustomComponent(acc), formLayout1.getConstraints(0, 2, -1, -1));

This code:

if (getApplication().getLauncher().isWebEnvironment())

is important for VisionX because the vaadin components aren't available in Swing, so we use this check for the supported environment.

So far, we mixed native vaadin components with JVx components. It's super easy to use, isn't it?

But it's also possible to use CSS for JVx components:

Css style for Button

Css style for Button

The button got the friendly style, which is defined in Vaadin CSS. Check some examples.

To add the friendly style class to the JVx button, simple add:

Style.addStyleNames(butNotification, "friendly");

This example is using predefined CSS from vaadin. It's also possible to set custom styles in your own css file. Simple follow this instructions.

This article covered the integration of native vaadin components into an existing JVx application with all advantages of vaadin.

Let's experiment, reducing code in screens and lifecycle objects.

Let's do a small experiment, and see if we can reduce the code required in screens and lifecycle objects to a minimum.

But be advised, this blog post is meant as a food for thoughts and less as a practical manual on doing things. So we will explore different possibilities which might not be practical.

Starting point

We will start with a small screen which has been automatically generated by VisionX, but has (manually) been stripped of all comments, documentation, imports and has some formatting modifications to make it easier readable in this post.

  1. public class PeopleWorkScreen extends DataSourceWorkScreen
  2. {
  3.     private UIEditor editPeopleFirstName = new UIEditor();
  4.     private UIEditor editPeopleLastName = new UIEditor();
  5.     private UIEditor editPeopleDateOfBirth = new UIEditor();
  6.     private UIEditor editPeopleOccupation = new UIEditor();
  7.     private UILabel labelFirstName = new UILabel();
  8.     private UILabel labelLastName = new UILabel();
  9.     private UILabel labelDateofBirth = new UILabel();
  10.     private UILabel labelOccupation = new UILabel();
  11.     private UIFormLayout formLayout1 = new UIFormLayout();
  12.     private UIGroupPanel groupPanelPeople = new UIGroupPanel();
  13.     private NavigationTable tablePeople = new NavigationTable();
  14.     private UIPanel splitPanelMainFirst = new UIPanel();
  15.     private UIPanel splitPanelMainSecond = new UIPanel();
  16.     private UISplitPanel splitPanelMain = new UISplitPanel();
  17.     private UIBorderLayout borderLayout1 = new UIBorderLayout();
  18.     private UIBorderLayout borderLayout2 = new UIBorderLayout();
  19.     private UIBorderLayout borderLayout3 = new UIBorderLayout();
  20.     private RemoteDataBook rdbPeople = new RemoteDataBook();
  21.    
  22.     public PeopleWorkScreen(
  23.             IWorkScreenApplication pApplication,
  24.             AbstractConnection pConnection,
  25.             Map pParameter) throws Throwable
  26.     {
  27.         super(pApplication, pConnection, pParameter);
  28.        
  29.         initializeModel();
  30.         initializeUI();
  31.     }
  32.    
  33.     private void initializeModel() throws Throwable
  34.     {
  35.         rdbPeople.setName("people");
  36.         rdbPeople.setDataSource(getDataSource());
  37.         rdbPeople.open();
  38.     }
  39.    
  40.     private void initializeUI() throws Throwable
  41.     {
  42.         tablePeople.setMaximumSize(new UIDimension(450, 350));
  43.         tablePeople.setDataBook(rdbPeople);
  44.         tablePeople.setAutoResize(false);
  45.        
  46.         labelFirstName.setText("First Name");
  47.        
  48.         labelLastName.setText("Last Name");
  49.        
  50.         labelDateofBirth.setText("Date of Birth");
  51.        
  52.         labelOccupation.setText("Occupation");
  53.        
  54.         editPeopleFirstName.setDataRow(rdbPeople);
  55.         editPeopleFirstName.setColumnName("FIRST_NAME");
  56.        
  57.         editPeopleLastName.setDataRow(rdbPeople);
  58.         editPeopleLastName.setColumnName("LAST_NAME");
  59.        
  60.         editPeopleDateOfBirth.setDataRow(rdbPeople);
  61.         editPeopleDateOfBirth.setColumnName("DATE_OF_BIRTH");
  62.        
  63.         editPeopleOccupation.setDataRow(rdbPeople);
  64.         editPeopleOccupation.setColumnName("OCCUPATION");
  65.        
  66.         splitPanelMainFirst.setLayout(borderLayout2);
  67.         splitPanelMainFirst.add(tablePeople, UIBorderLayout.CENTER);
  68.        
  69.         groupPanelPeople.setText("People");
  70.         groupPanelPeople.setLayout(formLayout1);
  71.         groupPanelPeople.add(labelFirstName, formLayout1.getConstraints(0, 0));
  72.         groupPanelPeople.add(editPeopleFirstName, formLayout1.getConstraints(1, 0));
  73.         groupPanelPeople.add(labelLastName, formLayout1.getConstraints(2, 0));
  74.         groupPanelPeople.add(editPeopleLastName, formLayout1.getConstraints(3, 0));
  75.         groupPanelPeople.add(labelDateofBirth, formLayout1.getConstraints(0, 1));
  76.         groupPanelPeople.add(editPeopleDateOfBirth, formLayout1.getConstraints(1, 1));
  77.         groupPanelPeople.add(labelOccupation, formLayout1.getConstraints(2, 1));
  78.         groupPanelPeople.add(editPeopleOccupation, formLayout1.getConstraints(3, 1));
  79.        
  80.         splitPanelMainSecond.setLayout(borderLayout3);
  81.         splitPanelMainSecond.add(groupPanelPeople, UIBorderLayout.CENTER);
  82.        
  83.         splitPanelMain.add(splitPanelMainFirst, UISplitPanel.FIRST_COMPONENT);
  84.         splitPanelMain.add(splitPanelMainSecond, UISplitPanel.SECOND_COMPONENT);
  85.        
  86.         setLayout(borderLayout1);
  87.         add(splitPanelMain, UIBorderLayout.CENTER);
  88.     }
  89. }

This is the PeopleWorkScreen class. It really doesn't do much except containing a Split panel, having on the left a table and on the right a few editors. That does look quite manageable, also it does look as one would expect in the GUI.

The screen we are going to use as demonstration.

But it is a very good starting point for our experiments. As an additional note, we will lose VisionX support with all the changes we will make, unfortunately, that can't be circumvented I'm afraid.

Annotations for editors

The first thought one has when it comes to reducing code is to use Annotations to deliver important information. We can do this here, too, to remove some setup from the editors and instead add it to a more central place. So we will create a new Annotation which is holding the required information for the editors, which are the name of the databook and the name of the column.

  1. @Retention(RUNTIME)
  2. @Target(FIELD)
  3. public @interface DataBound
  4. {
  5.     public String dataBookName();
  6.     public String columnName();
  7. }
  1. public class PeopleWorkScreen extends DataSourceWorkScreen
  2. {
  3.     @DataBound(dataBookName = "people", columnName = "FIRST_NAME")
  4.     private UIEditor editPeopleFirstName = new UIEditor();
  5.     @DataBound(dataBookName = "people", columnName = "LAST_NAME")
  6.     private UIEditor editPeopleLastName = new UIEditor();
  7.     @DataBound(dataBookName = "people", columnName = "DATE_OF_BIRTH")
  8.     private UIEditor editPeopleDateOfBirth = new UIEditor();
  9.     @DataBound(dataBookName = "people", columnName = "OCCUPATION")
  10.     private UIEditor editPeopleOccupation = new UIEditor();
  11.     private UILabel labelFirstName = new UILabel();
  12.     private UILabel labelLastName = new UILabel();
  13.     private UILabel labelDateofBirth = new UILabel();
  14.     private UILabel labelOccupation = new UILabel();
  15.     private UIFormLayout formLayout1 = new UIFormLayout();
  16.     private UIGroupPanel groupPanelPeople = new UIGroupPanel();
  17.     private NavigationTable tablePeople = new NavigationTable();
  18.     private UIPanel splitPanelMainFirst = new UIPanel();
  19.     private UIPanel splitPanelMainSecond = new UIPanel();
  20.     private UISplitPanel splitPanelMain = new UISplitPanel();
  21.     private UIBorderLayout borderLayout1 = new UIBorderLayout();
  22.     private UIBorderLayout borderLayout2 = new UIBorderLayout();
  23.     private UIBorderLayout borderLayout3 = new UIBorderLayout();
  24.     private RemoteDataBook rdbPeople = new RemoteDataBook();
  25.    
  26.     public PeopleWorkScreen(
  27.             IWorkScreenApplication pApplication,
  28.             AbstractConnection pConnection,
  29.             Map pParameter) throws Throwable
  30.     {
  31.         super(pApplication, pConnection, pParameter);
  32.        
  33.         initializeModel();
  34.         initializeUI();
  35.     }
  36.    
  37.     private void initializeModel() throws Throwable
  38.     {
  39.         rdbPeople.setName("people");
  40.         rdbPeople.setDataSource(getDataSource());
  41.         rdbPeople.open();
  42.     }
  43.    
  44.     private void initializeUI() throws Throwable
  45.     {
  46.         tablePeople.setMaximumSize(new UIDimension(450, 350));
  47.         tablePeople.setDataBook(rdbPeople);
  48.         tablePeople.setAutoResize(false);
  49.        
  50.         labelFirstName.setText("First Name");
  51.        
  52.         labelLastName.setText("Last Name");
  53.        
  54.         labelDateofBirth.setText("Date of Birth");
  55.        
  56.         labelOccupation.setText("Occupation");
  57.        
  58.         splitPanelMainFirst.setLayout(borderLayout2);
  59.         splitPanelMainFirst.add(tablePeople, UIBorderLayout.CENTER);
  60.        
  61.         groupPanelPeople.setText("People");
  62.         groupPanelPeople.setLayout(formLayout1);
  63.         groupPanelPeople.add(labelFirstName, formLayout1.getConstraints(0, 0));
  64.         groupPanelPeople.add(editPeopleFirstName, formLayout1.getConstraints(1, 0));
  65.         groupPanelPeople.add(labelLastName, formLayout1.getConstraints(2, 0));
  66.         groupPanelPeople.add(editPeopleLastName, formLayout1.getConstraints(3, 0));
  67.         groupPanelPeople.add(labelDateofBirth, formLayout1.getConstraints(0, 1));
  68.         groupPanelPeople.add(editPeopleDateOfBirth, formLayout1.getConstraints(1, 1));
  69.         groupPanelPeople.add(labelOccupation, formLayout1.getConstraints(2, 1));
  70.         groupPanelPeople.add(editPeopleOccupation, formLayout1.getConstraints(3, 1));
  71.        
  72.         splitPanelMainSecond.setLayout(borderLayout3);
  73.         splitPanelMainSecond.add(groupPanelPeople, UIBorderLayout.CENTER);
  74.        
  75.         splitPanelMain.add(splitPanelMainFirst, UISplitPanel.FIRST_COMPONENT);
  76.         splitPanelMain.add(splitPanelMainSecond, UISplitPanel.SECOND_COMPONENT);
  77.        
  78.         setLayout(borderLayout1);
  79.         add(splitPanelMain, UIBorderLayout.CENTER);
  80.     }
  81. }

As one can see, we've annotated the fields with our new Annotation and removed the setup lines from the initializeUI() function. Of course, that alone does nothing, we must add the processing of the annotations somewhere. The best place would be in the application when the workscreen is opened.

  1. public class AnnotationAwareApplication extends ProjX
  2. {
  3.     public AnnotationAwareApplication(UILauncher pLauncher) throws Throwable
  4.     {
  5.         super(pLauncher);
  6.     }
  7.    
  8.     @Override
  9.     public synchronized IWorkScreen openWorkScreen(
  10.             String pClassName,
  11.             Modality pModality,
  12.             AbstractConnection pConnection,
  13.             Map pParameters,
  14.             boolean pSingleInstance) throws Throwable
  15.     {
  16.         DataSourceWorkScreen workScreen = (DataSourceWorkScreen)super.openWorkScreen(
  17.                 pClassName,
  18.                 pModality,
  19.                 pConnection,
  20.                 pParameters,
  21.                 pSingleInstance);
  22.        
  23.         for (Field field : workScreen.getClass().getDeclaredFields())
  24.         {
  25.             DataBound dataBound = field.getAnnotation(DataBound.class);
  26.            
  27.             if (dataBound != null && IEditor.class.isAssignableFrom(field.getType()))
  28.             {
  29.                 IDataBook dataBook = workScreen.getDataSource().
  30.                                         getDataBook(dataBound.dataBookName());
  31.                 String columnName = dataBound.columnName();
  32.                
  33.                 field.setAccessible(true);
  34.                
  35.                 IEditor editor = (IEditor)field.get(workScreen);
  36.                
  37.                 editor.setDataRow(dataBook);
  38.                 editor.setColumnName(columnName);
  39.             }
  40.         }
  41.        
  42.         return workScreen;
  43.     }
  44.    
  45. }

Easy enough, it removes some lines from the screen and the logic added inside the application is straightforward.

The upside is that we now have the information of the data binding right there in the field declaration, the downside is that it doesn't save us that much. In theory the gain is only 1 line per editor. We could do better than that.

Automatic editors

Annotations are interesting, but don't fit well here. We could do better when we extend the UIEditor itself and fit it with the necessary logic to be able to find its data binding on its own. That means that it would need to go upward at some point and find its parent workscreen to retrieve the datasource (which holds all the databooks). Walking upwards in the hierarchy is straightforward, the question is when we should do that? The best point in time would be when addNotify() is being called, because at that point the GUI is being created, so we are very, very likely inside the initializeUI() function of the workscreen or at a later point.

  1. public class AutomaticEditor extends UIEditor
  2. {
  3.     private String dataBookName = null;
  4.     private String columnName = null;
  5.    
  6.     public AutomaticEditor(String pDataBookName, String pColumnName)
  7.     {
  8.         super();
  9.        
  10.         dataBookName = pDataBookName;
  11.         columnName = pColumnName;
  12.     }
  13.  
  14.     @Override
  15.     public void addNotify()
  16.     {
  17.         DataSourceWorkScreen workScreen = getParentWorkScreen();
  18.        
  19.         try
  20.         {
  21.             setDataRow(workScreen.getDataSource().getDataBook(dataBookName));
  22.             setColumnName(columnName);
  23.         }
  24.         catch (ModelException e)
  25.         {
  26.             ExceptionHandler.raise(e);
  27.         }
  28.        
  29.         super.addNotify();
  30.     }
  31.    
  32.     private DataSourceWorkScreen getParentWorkScreen()
  33.     {
  34.         IContainer parent = getParent();
  35.        
  36.         while (parent != null && !(parent instanceof DataSourceWorkScreen))
  37.         {
  38.             parent = parent.getParent();
  39.         }
  40.        
  41.         return (DataSourceWorkScreen)parent;
  42.     }
  43. }
  1. public class PeopleWorkScreen extends DataSourceWorkScreen
  2. {
  3.     private UIEditor editPeopleFirstName=new AutomaticEditor("people","FIRST_NAME");
  4.     private UIEditor editPeopleLastName = new AutomaticEditor("people", "LAST_NAME");
  5.     private UIEditor editPeopleDoB = new AutomaticEditor("people", "DATE_OF_BIRTH");
  6.     private UIEditor editPeopleOccupation=new AutomaticEditor("people","OCCUPATION");
  7.     private UILabel labelFirstName = new UILabel();
  8.     private UILabel labelLastName = new UILabel();
  9.     private UILabel labelDateofBirth = new UILabel();
  10.     private UILabel labelOccupation = new UILabel();
  11.     private UIFormLayout formLayout1 = new UIFormLayout();
  12.     private UIGroupPanel groupPanelPeople = new UIGroupPanel();
  13.     private NavigationTable tablePeople = new NavigationTable();
  14.     private UIPanel splitPanelMainFirst = new UIPanel();
  15.     private UIPanel splitPanelMainSecond = new UIPanel();
  16.     private UISplitPanel splitPanelMain = new UISplitPanel();
  17.     private UIBorderLayout borderLayout1 = new UIBorderLayout();
  18.     private UIBorderLayout borderLayout2 = new UIBorderLayout();
  19.     private UIBorderLayout borderLayout3 = new UIBorderLayout();
  20.     private RemoteDataBook rdbPeople = new RemoteDataBook();
  21.    
  22.     public PeopleWorkScreen(
  23.             IWorkScreenApplication pApplication,
  24.             AbstractConnection pConnection,
  25.             Map pParameter) throws Throwable
  26.     {
  27.         super(pApplication, pConnection, pParameter);
  28.        
  29.         initializeModel();
  30.         initializeUI();
  31.     }
  32.    
  33.     private void initializeModel() throws Throwable
  34.     {
  35.         rdbPeople.setName("people");
  36.         rdbPeople.setDataSource(getDataSource());
  37.         rdbPeople.open();
  38.     }
  39.    
  40.     private void initializeUI() throws Throwable
  41.     {
  42.         tablePeople.setMaximumSize(new UIDimension(450, 350));
  43.         tablePeople.setDataBook(rdbPeople);
  44.         tablePeople.setAutoResize(false);
  45.        
  46.         labelFirstName.setText("First Name");
  47.        
  48.         labelLastName.setText("Last Name");
  49.        
  50.         labelDateofBirth.setText("Date of Birth");
  51.        
  52.         labelOccupation.setText("Occupation");
  53.        
  54.         splitPanelMainFirst.setLayout(borderLayout2);
  55.         splitPanelMainFirst.add(tablePeople, UIBorderLayout.CENTER);
  56.        
  57.         groupPanelPeople.setText("People");
  58.         groupPanelPeople.setLayout(formLayout1);
  59.         groupPanelPeople.add(labelFirstName, formLayout1.getConstraints(0, 0));
  60.         groupPanelPeople.add(editPeopleFirstName, formLayout1.getConstraints(1, 0));
  61.         groupPanelPeople.add(labelLastName, formLayout1.getConstraints(2, 0));
  62.         groupPanelPeople.add(editPeopleLastName, formLayout1.getConstraints(3, 0));
  63.         groupPanelPeople.add(labelDateofBirth, formLayout1.getConstraints(0, 1));
  64.         groupPanelPeople.add(editPeopleDoB, formLayout1.getConstraints(1, 1));
  65.         groupPanelPeople.add(labelOccupation, formLayout1.getConstraints(2, 1));
  66.         groupPanelPeople.add(editPeopleOccupation, formLayout1.getConstraints(3, 1));
  67.        
  68.         splitPanelMainSecond.setLayout(borderLayout3);
  69.         splitPanelMainSecond.add(groupPanelPeople, UIBorderLayout.CENTER);
  70.        
  71.         splitPanelMain.add(splitPanelMainFirst, UISplitPanel.FIRST_COMPONENT);
  72.         splitPanelMain.add(splitPanelMainSecond, UISplitPanel.SECOND_COMPONENT);
  73.        
  74.         setLayout(borderLayout1);
  75.         add(splitPanelMain, UIBorderLayout.CENTER);
  76.     }
  77. }

That removes the complete setup of the editor from the code, with the exception of the constructor, and instead the editor itself manages its own setup, neat. So I think we can't lose any more of the editor associated code at this point, the only further possibility would be to associate a "default" databook with the workscreen so that we can scrap the databook name from the constructor. Or, we could scan the fields of the screen and build the editors based on their name, but that is a rather fragile approach.

Inline fields

While we are at it and we will lose VisionX support anyway, we can inline the fields directly into the initializeUI() function to shed more lines.

  1. public class PeopleWorkScreen extends DataSourceWorkScreen
  2. {
  3.     private RemoteDataBook rdbPeople = new RemoteDataBook();
  4.    
  5.     public PeopleWorkScreen(
  6.             IWorkScreenApplication pApplication,
  7.             AbstractConnection pConnection,
  8.             Map pParameter) throws Throwable
  9.     {
  10.         super(pApplication, pConnection, pParameter);
  11.        
  12.         initializeModel();
  13.         initializeUI();
  14.     }
  15.    
  16.     private void initializeModel() throws Throwable
  17.     {
  18.         rdbPeople.setName("people");
  19.         rdbPeople.setDataSource(getDataSource());
  20.         rdbPeople.open();
  21.     }
  22.    
  23.     private void initializeUI() throws Throwable
  24.     {
  25.         NavigationTable tablePeople = new NavigationTable();
  26.         tablePeople.setMaximumSize(new UIDimension(450, 350));
  27.         tablePeople.setDataBook(rdbPeople);
  28.         tablePeople.setAutoResize(false);
  29.        
  30.         UIPanel splitPanelMainFirst = new UIPanel();
  31.         splitPanelMainFirst.setLayout(new UIBorderLayout());
  32.         splitPanelMainFirst.add(tablePeople, UIBorderLayout.CENTER);
  33.        
  34.         UIFormLayout formLayout1 = new UIFormLayout();
  35.        
  36.         UIGroupPanel groupPanelPeople = new UIGroupPanel();
  37.         groupPanelPeople.setText("People");
  38.         groupPanelPeople.setLayout(formLayout1);
  39.         groupPanelPeople.add(new UILabel("First Name"),
  40.                              formLayout1.getConstraints(0, 0));
  41.         groupPanelPeople.add(new AutomaticEditor("people", "FIRST_NAME"),
  42.                              formLayout1.getConstraints(1, 0));
  43.         groupPanelPeople.add(new UILabel("Last Name"),
  44.                              formLayout1.getConstraints(2, 0));
  45.         groupPanelPeople.add(new AutomaticEditor("people", "LAST_NAME"),
  46.                              formLayout1.getConstraints(3, 0));
  47.         groupPanelPeople.add(new UILabel("Date of Birth"),
  48.                              formLayout1.getConstraints(0, 1));
  49.         groupPanelPeople.add(new AutomaticEditor("people", "DATE_OF_BIRTH"),
  50.                              formLayout1.getConstraints(1, 1));
  51.         groupPanelPeople.add(new UILabel("Occupation"),
  52.                              formLayout1.getConstraints(2, 1));
  53.         groupPanelPeople.add(new AutomaticEditor("people", "OCCUPATION"),
  54.                              formLayout1.getConstraints(3, 1));
  55.        
  56.         UIPanel splitPanelMainSecond = new UIPanel();
  57.         splitPanelMainSecond.setLayout(new UIBorderLayout());
  58.         splitPanelMainSecond.add(groupPanelPeople, UIBorderLayout.CENTER);
  59.        
  60.         UISplitPanel splitPanelMain = new UISplitPanel();
  61.         splitPanelMain.add(splitPanelMainFirst, UISplitPanel.FIRST_COMPONENT);
  62.         splitPanelMain.add(splitPanelMainSecond, UISplitPanel.SECOND_COMPONENT);
  63.        
  64.         setLayout(new UIBorderLayout());
  65.         add(splitPanelMain, UIBorderLayout.CENTER);
  66.     }
  67. }

That makes the code harder to read but in the end it saves a lot of lines.

Automatic databooks

Now the only part of the screen which we can now reduce is the model part. This is a little more complicated, though. First, if we create a databook on the fly it's a little bit more complicated to get it customized, so we have to assume that we can use the databooks "as they are", with all the necessary setup being done on the server side (which is ideal anyway). Second, ideally we could let the connection create the databooks as they are needed, that is a little bit more complicated and for this example we will add that logic to the screen instead. So the PeopleWorkScreen gains the method getDataBook(String) which gets or creates a databook for the given name and returns it.

  1. public class PeopleWorkScreen extends DataSourceWorkScreen
  2. {
  3.     public PeopleWorkScreen(
  4.             IWorkScreenApplication pApplication,
  5.             AbstractConnection pConnection,
  6.             Map pParameter) throws Throwable
  7.     {
  8.         super(pApplication, pConnection, pParameter);
  9.        
  10.         initializeUI();
  11.     }
  12.    
  13.     private void initializeUI() throws Throwable
  14.     {
  15.         NavigationTable tablePeople = new NavigationTable();
  16.         tablePeople.setMaximumSize(new UIDimension(450, 350));
  17.         tablePeople.setDataBook(getDataBook("people"));
  18.         tablePeople.setAutoResize(false);
  19.        
  20.         UIPanel splitPanelMainFirst = new UIPanel();
  21.         splitPanelMainFirst.setLayout(new UIBorderLayout());
  22.         splitPanelMainFirst.add(tablePeople, UIBorderLayout.CENTER);
  23.        
  24.         UIFormLayout formLayout1 = new UIFormLayout();
  25.        
  26.         UIGroupPanel groupPanelPeople = new UIGroupPanel();
  27.         groupPanelPeople.setText("People");
  28.         groupPanelPeople.setLayout(formLayout1);
  29.         groupPanelPeople.add(new UILabel("First Name"),
  30.                              formLayout1.getConstraints(0, 0));
  31.         groupPanelPeople.add(new AutomaticEditor("people", "FIRST_NAME"),
  32.                              formLayout1.getConstraints(1, 0));
  33.         groupPanelPeople.add(new UILabel("Last Name"),
  34.                              formLayout1.getConstraints(2, 0));
  35.         groupPanelPeople.add(new AutomaticEditor("people", "LAST_NAME"),
  36.                              formLayout1.getConstraints(3, 0));
  37.         groupPanelPeople.add(new UILabel("Date of Birth"),
  38.                              formLayout1.getConstraints(0, 1));
  39.         groupPanelPeople.add(new AutomaticEditor("people", "DATE_OF_BIRTH"),
  40.                              formLayout1.getConstraints(1, 1));
  41.         groupPanelPeople.add(new UILabel("Occupation"),
  42.                              formLayout1.getConstraints(2, 1));
  43.         groupPanelPeople.add(new AutomaticEditor("people", "OCCUPATION"),
  44.                              formLayout1.getConstraints(3, 1));
  45.        
  46.         UIPanel splitPanelMainSecond = new UIPanel();
  47.         splitPanelMainSecond.setLayout(new UIBorderLayout());
  48.         splitPanelMainSecond.add(groupPanelPeople, UIBorderLayout.CENTER);
  49.        
  50.         UISplitPanel splitPanelMain = new UISplitPanel();
  51.         splitPanelMain.add(splitPanelMainFirst, UISplitPanel.FIRST_COMPONENT);
  52.         splitPanelMain.add(splitPanelMainSecond, UISplitPanel.SECOND_COMPONENT);
  53.        
  54.         setLayout(new UIBorderLayout());
  55.         add(splitPanelMain, UIBorderLayout.CENTER);
  56.     }
  57.    
  58.     public IDataBook getDataBook(String pDataBookName) throws ModelException
  59.     {
  60.         IDataBook dataBook = getDataSource().getDataBook(pDataBookName);
  61.        
  62.         if (dataBook == null)
  63.         {
  64.             dataBook = new RemoteDataBook();
  65.             dataBook.setName(pDataBookName);
  66.             dataBook.setDataSource(getDataSource());
  67.             dataBook.open();
  68.         }
  69.        
  70.         return dataBook;
  71.     }
  72. }

and the changed editor

  1. public class AutomaticEditor extends UIEditor
  2. {
  3.     private String dataBookName = null;
  4.     private String columnName = null;
  5.    
  6.     public AutomaticEditor(String pDataBookName, String pColumnName)
  7.     {
  8.         super();
  9.        
  10.         dataBookName = pDataBookName;
  11.         columnName = pColumnName;
  12.     }
  13.  
  14.     @Override
  15.     public void addNotify()
  16.     {
  17.         PeopleWorkScreen workScreen = getParentWorkScreen();
  18.        
  19.         try
  20.         {
  21.             setDataRow(workScreen.getDataBook(dataBookName));
  22.             setColumnName(columnName);
  23.         }
  24.         catch (ModelException e)
  25.         {
  26.             ExceptionHandler.raise(e);
  27.         }
  28.        
  29.         super.addNotify();
  30.     }
  31.    
  32.     private PeopleWorkScreen getParentWorkScreen()
  33.     {
  34.         IContainer parent = getParent();
  35.        
  36.         while (parent != null && !(parent instanceof PeopleWorkScreen))
  37.         {
  38.             parent = parent.getParent();
  39.         }
  40.        
  41.         return (PeopleWorkScreen)parent;
  42.     }
  43. }

This introduces a quite unhealthy coupling between the PeopleWorkScreen and the AutomaticEditor, we can live with that for this example, but in a real application we'd have to correctly structure these objects. For example by introducing a workscreen base class, or by actually extending the datasource to provide this functionality.

If we ingore the method we just introduced in the PeopleWorkScreen, we actually managed to reduce the screen class a great deal and removed code which can be automated. That means that, at least theoretically, the likelihood of errors as we write the code has been lowered and it has become easier to write error free code. We can also now see that the screen class has become quite minimal, there really isn't anything left that we could restructure or remove.

Lifecycle objects

Now let us jump to the server and have a look at the associated lifecycle object.

  1. public class People extends Session
  2. {
  3.     public DBStorage getPeople() throws Exception
  4.     {
  5.         DBStorage dbsPeople = (DBStorage)get("people");
  6.         if (dbsPeople == null)
  7.         {
  8.             dbsPeople = new DBStorage();
  9.             dbsPeople.setWritebackTable("PEOPLE");
  10.             dbsPeople.setDBAccess(getDBAccess());
  11.             dbsPeople.open();
  12.  
  13.             put("people", dbsPeople);
  14.         }
  15.         return dbsPeople;
  16.     }  
  17. }

There isn't much here that we can do, but a few small things might make it easier to write in the future. Again, we will be losing VisionX support if we edit this "too much", but we are far beyond that point anyway.

Reducing error potential

What can happen easily with managing storages is that one does copy and paste code and misses to edit the key, under which the storage is stored, correctly. That can happen quickly especially if there is a complex object hierarchy in place. So what we can do is separating the storing logic from the creation logic.

  1. public class People extends Session
  2. {
  3.     public DBStorage getPeople() throws Exception
  4.     {
  5.         return getOrCreateStorage("people", (storage) ->
  6.         {
  7.             storage.setWritebackTable("PEOPLE");
  8.         });
  9.     }
  10.    
  11.     protected DBStorage getOrCreateStorage(String pName, Consumer pStorageConfigurer)
  12.                                           throws Exception
  13.     {
  14.         DBStorage storage = (DBStorage)get(pName);
  15.         if (storage == null)
  16.         {
  17.             storage = new DBStorage();
  18.             storage.setDBAccess(getDBAccess());
  19.            
  20.             pStorageConfigurer.accept(storage);
  21.            
  22.             if (!storage.isOpen())
  23.             {
  24.                 storage.open();
  25.             }
  26.  
  27.             put(pName, storage);
  28.         }
  29.        
  30.         return storage;
  31.     }
  32. }

This has the upside that it would reduce the error potential greatly, especially with a lot of storages, and it would allow us to add additional safety checks, for example if a storage with that name is already existing and would be overridden. But it has the downside that with each access a new lambda function is created, that might or might not be important for your use case.

If we try to mitigate this side effect we will quickly reach certain limits, for example if we change the process to a registry based approach we will find that we've again introduced the very thing we wanted to remove. The storage configurer must be registered at the registry with a name and the storage must be received with a name, so we'd be back at square one, actually.

A more dynamic approach

To get rid of this duplication of the name we could create a registry with the configurers and use a generic approach to retrieving it. The problem here is that that is not possible at compile time, so what we need would actually be a system that catches non-matched calls for storages in the client/server connection and redirects it to our generic method. That is unfortunately not trivial and I will only outline this approach here now.

  1. public class People extends Session
  2. {
  3.     private Map<String, Consumer> registeredStorages = new HashMap();
  4.    
  5.     public People()
  6.     {
  7.         super();
  8.        
  9.         registerStorage("people", (storage) ->
  10.         {
  11.             storage.setWritebackTable("PEOPLE");
  12.         });
  13.     }
  14.    
  15.     protected DBStorage getOrCreateStorage(String pName) throws Exception
  16.     {
  17.         DBStorage storage = (DBStorage)get(pName);
  18.         if (storage == null)
  19.         {
  20.             storage = new DBStorage();
  21.             storage.setDBAccess(getDBAccess());
  22.            
  23.             registeredStorages.get(pName).accept(storage);
  24.            
  25.             if (!storage.isOpen())
  26.             {
  27.                 storage.open();
  28.             }
  29.            
  30.             put(pName, storage);
  31.         }
  32.        
  33.         return storage;
  34.     }
  35.    
  36.     protected void registerStorage(String pName, Consumer pStorageConfigurer)
  37.     {
  38.         registeredStorages.put(pName, pStorageConfigurer);
  39.     }
  40. }

Now all that is missing would be that the connection is calling our getOrCreateStorage(String) method with the name of the requested object. Of course there is also a lot of error checking missing here, but that is not relevant for our example.

And again, if we remove the additional code we added, because it should be contained inside a base class, we have managed to reduce the code on the server side significantly. From here going any further becomes complicated, for example, again, one might to work with Annotations but that won't save us anything here anymore.

Conclusion

From time to time it is necessary to let the thoughts drift and think about different things, for example how to reduce the code inside of classes which are automatically generated and managed. Once I had a nice discussion with someone online on how to do things differently in their library. We had a little back and forth on what could be done and in the end we agreed that it should stay the way it was because none of the approaches we came up with had a significant advantage over the current state. In the end they said that, even though nothing came of it, it was an important discussion to have because from time to time one had to engage in such stimulated, technical and theoretical discussions, and I agree completely with that. It is important to be open to new ideas and consider different approaches even when they don't end up taken.