Hi,
we’ve created an application component you might be interested in. It makes it possible do declarativly use generic features in your UI controllers and hold the logic for implementing those in a single place.
It is open source (Apache 2 license) and you can find it on github:
If you have any thoughts on that, like bugs, feature requests etc. we would love to hear from you.
I’ll just copy over the README.md so you get an impression on what it does:
CUBA Platform Component - Declarative Controllers
CUBA component that allows to write generic features for a Controller and use them in a declarative way.
Installation
Currently you have to download the app-component manually and import it into Studio. After opening it in studio, you have to execute “Run > Install app component”. After that you can go into your project and add the dependency to you project through “Project Properties > Edit > custom components (+) > cuba-component-runtime-diagnose”.
Note: This manual installation step will probably simplify with Version 6.6 of CUBA and studio.
Motivation & Example usage: Comments feature
So what does that mean? Here’s an example balvi/cuba-example-declarative-comments:
Let’s say you want to add a generic “comments-feature” on entities (in our case you can comment on “Customer” as well as “Product”) and you already got everything setup on the entity layer.
Now, as you want to present a default screen for comments on every browser that shows Customers or Products.
How do you not duplicate the UI logic, where you add a section on the side of the browser to show the comments on a selected item?
The default answer to this would be to create a Superclass called CommentsBrowser
which extends AbstractLookup
.
In this controller in the ready method you would probably hook into the ready
callback and do your stuff.
Now you set CustomerBrowse extends CommentsBrowser
and ProductBrowse extends CommentsBrowser
and you are ready to go - no code shared, so what is the problem?
Inheritance works exactly once
The problem occurs when you want to implement another feature. Let’s imagine, we want to have another generic feature like that it is possible to
assign one or more Documents to entities. When we try to do it as above, we have the problem that ProductBrowse
already extends CommentsBrowse
.
How do we resolve this? Creating another subclass CommentsBrowserWithDocuments
? Would work, but what if i only want the Documents feature?
This would require two classes: DocumentsBrowser
for the case where we want only the documents feature and the CommentsBrowserWithDocuments
class for
the case of both features.
Delegation to the rescue
As this does not scale at all (having five distinct features would end up in 2^5 = 32 classes), we need to do Delegation - and this is where this app-component comes into play.
Declarative definition of features through Annotations
This application component brings not only a way of dealing with the inheritance problem.
It also makes it possible to, instead of programmatically define that certain features should be used, activate these generic features
in a declarative way through the Usage of Annotations.
Here is how the ProductBrowse looks with the declarative definition of the Comments feature:
@Commentable(listComponent = "productsTable")
@HasDocuments(listComponent = "productsTable", createDocument = true)
public class ProductBrowse extends AnnotatableAbstractLookup {
}
The CustomerBrowse looks like this:
[url=]@Commentable(listComponent = “productsTable”)
public class CustomerBrowse extends AnnotatableAbstractLookup {
}[/url]
You just pick and choose your features and the implementation gets injected into your controllers. The only requirement is that your Controller
extends from AnnotatableAbstractLookup
instead of AbstractLookup
directly (or AnnotatableAbstractEditor
for editor controllers). But this
is the only superclass you need to extend from. Not for every feature another superclass.
Defining Annotations
Generally, there is another example repository, which shows the usage of this application component: balvi/cuba-example-declarative-comments
with the exact example of the @Commentable
.
To create custom feature like @Commentable
you have to do the following:
- Create a Annotation in your web module like this:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Commentable {
String listComponent() default "";
String buttonId() default "commentBtn";
String buttonsPanel() default "buttonsPanel";
}
When you want to parameterise your Annotation, you can define these settings as well as default values within the Annotation.
- Create a spring bean (in the web module) that implements either
BrowseAnnotationExecutor
orEditorAnnotationExecutor
for Browse screens or Editors
which contains the logic that sholuld get injected into the controllers:
@Component
class CommentableBrowseAnnotationExecutor implements BrowseAnnotationExecutor<Commentable> {
@Override
boolean supports(Annotation annotation) {
annotation instanceof Commentable
}
@Override
void init(Commentable annotation, Window.Lookup browse, Map<String, Object> params) {
// do your logic to add a button to the browse screen...
}
@Override
void ready(Commentable annotation, Window.Lookup browse, Map<String, Object> params) {
}
}
The supports
method declares for which Annotation this class is responsible.
Besides that it contains Hook methods for the corresponding Controllers (just like in the regular controllers).
For Browse screens these are:
init
ready
For editors the hook methods are:
init
postInit
Those hook methods have the same semantics as the standard CUBA controller hook methods. For more information
you can take a look at the corresponding CUBA docs for
AbstractLookup and AbstractEditor.
That’s it.
With this you have a single place where you can put your UI logic that is accessible for different screens.
You can easily take this and create CUBA studio templates that will add your Annotations to your screens,
so that you have a UI where you have a couple of checkboxes enable all of your generic features.