Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Core changes

Javascript replaced by TypeScript

One cannot really deny that Javascript sucks like a black hole, really, for bigger projects (in my opinion - like any other dynamically typed language). Inside DomUI the Javascript code became huge and was very hard to maintain - and buggy as well because no one knew what was called with what and returned whatever.

So, the Javascript code inside domui.js has been completely converted to TypeScript. TypeScript, while having its own oddities, greatly helps with adding almost full static typing to Javascript, so things can be defined like this:

Code Block
export function truncateUtfBytes(str: string, nbytes: number): number

In the process a great many bugs have been found and hopefully solved. Not everything is properly typescripted yet, but at least the current code forms a good basis to fix more. It is so unimaginably nice to actually get a compiler error for a mistake instead of some obscure console log line during testing.

The domui.js module has been split into several files that all together form a single namespace called WebUI, and almost all functions there are exported so they are globally visible. This ensures that existing calls to code remain working at the cost of still having bad TypeScript code. A later iteration will replace the namespace with several modules - but that requires all call sites to be visited.

One pretty huge sadness in TypeScript is that using a namespace with multiple files, while supported, is horribly implemented: you cannot easily define order between the files inside the namespace. To circumvent this for now the files are added to the _files section of the tsconfig.json file in the correct order.

setClicked() argument changed (breaking)

The setClicked(IClickBase) method has been replaced by two separate methods:

  • setClicked(IClicked<?>)
  • setClicked2(IClicked2<?>)

This change allows for easier use of lambda's as the type for the method is now well-defined. So instead of having to write

Code Block
d.setClicked((IClicked<Div>) b -> setCurrentTab(ti));

you can now invoke the lambda without a cast:

Code Block
d.setClicked(b -> setCurrentTab(ti));

This change will some existing code as all calls to setClicked that passed a IClicked2 instance now need to use setClicked2.

INodeContentRenderer removed (breaking change)

This interface was the main interface to render generic content inside some NodeContainer. Its definition was this:

Code Block
public interface INodeContentRenderer<T> {
   void renderNodeContent(@Nonnull NodeBase component, @Nonnull NodeContainer node, @Nullable T object, @Nullable Object parameters) throws Exception;
}

This interface was abused all over the place and makes it very unclear what parameters are actually passed into renderers, as the parameters component and parameters were often used in non-obvious ways. It also opened the doors for wide abuse, because renderers could abuse the values in component to actually "fix" the components rendering. This of course makes it impossible to maintain those components.

In addition the "object" parameter needed to be @Nullable because a lot of usages of this interface sometimes do pass a null value. But this makes it hard for the zillion places where this value is never null: it forces a check to be needed inside the implementation always.

The interface was replaced by the following:

Code Block
public interface IRenderInto<T> {
   void render(@Nonnull NodeContainer node, @Nonnull T object) throws Exception;

   default void renderOpt(@Nonnull NodeContainer node, @Nullable T object) throws Exception {
      if(null != object)
         render(node, object);
   }
}

The implementer usually implements the render() method only. When there is a need to also handle nulls in a special way then the renderOpt() method needs to be overridden too.

This interface is used at most places where INodeContentRenderer was used. At places where indeed more than these two parameters are needed new interfaces will have been introduced which better describe the parameters and their expected use. Also see the notes about the content renderers for the LookupInput control.

For those not eager to replace all occurrences of INodeContentRenderer: you might be able to define it again in your own code, making it extend IRenderInto, and make the render() method call the old method inside INodeContentRenderer, something like:

Code Block
public interface INodeContentRenderer<T> extends IRenderInto<T> {
   void renderNodeContent(@Nonnull NodeBase component, @Nonnull NodeContainer node, @Nullable T object, @Nullable Object parameters) throws Exception;
   default void render(@Nonnull NodeContainer node, @Nonnull T object) throws Exception {
       renderNodeContent(node, node, object, null);
   }
}

IProgress replaced with Progress

...

Hibernate 3.6.10 upgraded to Hibernate 5.2.10.Final

Most of this change should be transparent as long as you use DomUI's generic access layer. The one exception is configuring Hibernate where some changes have been made. The most important one is the part that facilitates participating in the Hibernate bootstrap process. The method HibernateConfigurator.addConfigListener now needs an interface implementation with methods for the different phases of Hibernate's bootstrap process. This was needed because 5.2 removed most functionality from the Configuration class.

The new interface has a set of methods that can be implemented to change parts of the configuration at the time these are done. See the IHibernateConfigListener for details.

Basic Hibernate JPA support

DomUI works with Hibernate in JPA mode. It does assume that JPA initialization and bootstrap has been done, and provides the method

Code Block
HibernateJpaConfigurator.initialize(entityManagerFactory)

to initialize access to the data functions within DomUI. Support is basic as more than one factory is not directly supported, but as you have the source code it should be easy to see where that can be fixed.

Javascript replaced by TypeScript

One cannot really deny that Javascript sucks like a black hole, really, for bigger projects (in my opinion - like any other dynamically typed language). Inside DomUI the Javascript code became huge and was very hard to maintain - and buggy as well because no one knew what was called with what and returned whatever.

So, the Javascript code inside domui.js has been completely converted to TypeScript. TypeScript, while having its own oddities, greatly helps with adding almost full static typing to Javascript, so things can be defined like this:

Code Block
export function truncateUtfBytes(str: string, nbytes: number): number

In the process a great many bugs have been found and hopefully solved. Not everything is properly typescripted yet, but at least the current code forms a good basis to fix more. It is so unimaginably nice to actually get a compiler error for a mistake instead of some obscure console log line during testing.

The domui.js module has been split into several files that all together form a single namespace called WebUI, and almost all functions there are exported so they are globally visible. This ensures that existing calls to code remain working at the cost of still having bad TypeScript code. A later iteration will replace the namespace with several modules - but that requires all call sites to be visited.

One pretty huge sadness in TypeScript is that using a namespace with multiple files, while supported, is horribly implemented: you cannot easily define order between the files inside the namespace. To circumvent this for now the files are added to the _files section of the tsconfig.json file in the correct order.

setClicked() argument changed (breaking)

The setClicked(IClickBase) method has been replaced by two separate methods:

  • setClicked(IClicked<?>)
  • setClicked2(IClicked2<?>)

This change allows for easier use of lambda's as the type for the method is now well-defined. So instead of having to write

Code Block
d.setClicked((IClicked<Div>) b -> setCurrentTab(ti));

you can now invoke the lambda without a cast:

Code Block
d.setClicked(b -> setCurrentTab(ti));

This change will some existing code as all calls to setClicked that passed a IClicked2 instance now need to use setClicked2.

INodeContentRenderer removed (breaking change)

This interface was the main interface to render generic content inside some NodeContainer. Its definition was this:

Code Block
public interface INodeContentRenderer<T> {
   void renderNodeContent(@Nonnull NodeBase component, @Nonnull NodeContainer node, @Nullable T object, @Nullable Object parameters) throws Exception;
}

This interface was abused all over the place and makes it very unclear what parameters are actually passed into renderers, as the parameters component and parameters were often used in non-obvious ways. It also opened the doors for wide abuse, because renderers could abuse the values in component to actually "fix" the components rendering. This of course makes it impossible to maintain those components.

In addition the "object" parameter needed to be @Nullable because a lot of usages of this interface sometimes do pass a null value. But this makes it hard for the zillion places where this value is never null: it forces a check to be needed inside the implementation always.

The interface was replaced by the following:

Code Block
public interface IRenderInto<T> {
   void render(@Nonnull NodeContainer node, @Nonnull T object) throws Exception;

   default void renderOpt(@Nonnull NodeContainer node, @Nullable T object) throws Exception {
      if(null != object)
         render(node, object);
   }
}

The implementer usually implements the render() method only. When there is a need to also handle nulls in a special way then the renderOpt() method needs to be overridden too.

This interface is used at most places where INodeContentRenderer was used. At places where indeed more than these two parameters are needed new interfaces will have been introduced which better describe the parameters and their expected use. Also see the notes about the content renderers for the LookupInput control.

For those not eager to replace all occurrences of INodeContentRenderer: you might be able to define it again in your own code, making it extend IRenderInto, and make the render() method call the old method inside INodeContentRenderer, something like:

Code Block
public interface INodeContentRenderer<T> extends IRenderInto<T> {
   void renderNodeContent(@Nonnull NodeBase component, @Nonnull NodeContainer node, @Nullable T object, @Nullable Object parameters) throws Exception;
   default void render(@Nonnull NodeContainer node, @Nonnull T object) throws Exception {
       renderNodeContent(node, node, object, null);
   }
}

IProgress replaced with Progress

The IProgress interface, used in all asynchronous tasks, has been replaced by the Progress implementation from to.etc.alg. The interface was completely useless as all async code created a specific implementation anyway, and the implementation was very limited in what it could do. The Progress implementation is fully threadsafe and allows nested accounting of progress.

Using enums as message bundle constants

Using BundleRef's with String constants for the message names is very deprecated and will be removed. The replacement is to use enum classes that implement the new interface IBundleCode interface. The enum values act as key names for the messages, and because an enum is a class the BundleRef is associated directly with the enum too. It makes it way easier to work with message bundles. See the localisation page for more details.

Rendering through a HTML template

For cases where you want to embed DomUI pages inside a complex existing HTML page you can now render DomUI pages through a HTML template. You set the template to use per page, for instance:

Code Block
getPage().setRenderTemplate(DomApplication.get().getResource("/mytemplate.html", ResourceDependencyList.NULL));

Inside the html template you need to place special constructs to indicate where DomUI can render its <head> and <body> information, by using:

Code Block
<% r.renderHeadContent(); %>

and

Code Block
<% r.renderBody(); %>


Metadata changes

Initialization of metadata

...