Open Anthem

This part of the technical documentation will cover all of the core technologies used within the Framework.

1. The Domain Entity

Having a quality understanding of how to configure domain entities is essential to build other types of configuration offered by the Framework. Furthermore, it is a great place to be considered as an entry point in using the Framework. This chapter will describe the primary concepts and usage behind the domain entity.

1.1. Introduction to the Domain Entity

At it’s core, the domain entity is nothing more than a simple encapsulated Java object. It is the blueprint for the objects that the Framework will create and use, similarly to the way a class declaration is used to create an instance of an object in Java.

Aside from being a straightforward Java bean, the domain entity may have a specific Framework metadeta decorating it’s fields (the Framework refers to these as Config Annotations) which will be used to instruct the Framework on how to handle the domain entities created from this domain entity during certain events.

See the following sample domain entity:

@Domain("person")
@Getter @Setter @ToString
public class Person {
    private String firstName;
    private String lastName;
}

In this example Person has been defined as a simple domain entity with getters and setters defined for each of it’s fields.

It may be intuitive that this entity is a data-centric model created solely for the purpose of collecting data; it is. While this example is a simple one, it will be used in the examples to follow to demonstrate the domain modeling feature.

Domain Entity Config Annotations

In the previous example, @Domain was used to decorate a class declaration so that the Framework used to identify Person as a domain entity. Domain defines the root or topmost level of a domain entity. The Framework supports other types of domain entity markers as well.

The config annotations that can be used to register domain entities are:

Defining Behavior

There are many different variations of how the Framework can utilize domain entities. One such manner is to define a ListenerType on the domain entity that provides the Framework with extra instruction on how to handle an instance of it during specific scenarios. Consider the following example:

@Domain(value = "person", includeListeners = { ListenerType.persistence })
@Repo(value = Database.rep_mongodb, cache = Cache.rep_device)
@Getter @Setter @ToString
public class Person {
    @Id
    private String id;
    private String firstName;
    private String lastName;
}

This example is very similar to the first, except the decorative annotation @Domain contains an additional attribute with a value of ListenerType.persistence and a @Repo config annotation. This configuration instructs the framework to have the underlying persistence layer perform certain actions on instances of this domain entity during the Framework lifecycle. Consider a use case where there is a need to update the values of a particular entity within the application’s database according to what is stored in an instance of this Person object. Specifying ListenerType.persistence as a listener for this domain entity will handle such a scenario.

Note
There are other ListenerType types and other mechanisms to configure the domain entity behavior available on Domain and each of the other domain entity config annotations. See the specific config annotation for more details (e.g. Domain, Model).

1.2. Core versus View

A key concept to the Framework in terms on the domain entity is understanding the difference between a core domain entity and a view domain entity.

The Framework is reponsible for handling two primary sets of data in terms of configuration data:

  • The "view" data which tells the framework how to render the graphical user interface.

  • The "core" data that the application creates, reads, updates, and deletes.

Therefore it is essential that it knows how to differentiate between what is core and what is view. At the same time to take full advtange of abstraction capabilities within the Framework, the domain entities are essentially created in the same fasion. How the Framework will interpret between the domain entities is entirely up to how they are configured given the configuration metadata, which will be explained in later sections (see Mapping params and Configuration).

Definition:

A core domain entity is a domain entity who’s primary responsibility is maintaining the integrity of the data contained within the application.

Definition:

A view domain entity is a domain entity who’s primary responsibility is maintaining instructions on how the GUI should be rendered.

1.3. Defintion Structure

1.3.1. Simple Domain Entities

The Person domain entity previously defined is a representation of what is understood to be a simple domain entity.

Definition:

A simple domain entity is a domain entity who’s field data does not contain other domain entity within it.

In other words, it’s defined fields consist only of simple types.

While not always the case, simple domain entities are typically associated with core domain entities.

1.3.2. Complex Domain Entities

Aligning with the theme of object oriented programming offered by Java, the Framework supports the notion of "nesting" domain entity definition within each other.

Definition:

A complex entity, or complex object, is a domain entity* that contains another _domain entity declared within it.

Consider the following example of a view domain entity representing a form for the aforementioned Person:

Person.java
@Model
@Getter @Setter @ToString
public class VFPersonForm {

    @TextBox
    private String firstName;

    @TextBox
    private String lastName;

    private Address address;
}
Address.java
@Model
@Getter @Setter @ToString
public class Address {

    @TextBox
    private String line1;
    ...
}

In this example, both VFPersonForm and Address are decorated with @Model. Therefore, VFPersonForm is a complex domain entity because it contains an instance of Address.

This scenario contains only one level of nesting between VFPersonForm and Address. It is not uncommon to have multiple layers of "nesting" within a domain entity, hence giving rise to the term complex. Consider a common scenario in HTML where a page contains a section, which contains a tab, which contains a component, which contains a…​ the context of what could be rendered in the view could be boundless. The Framework is able to support this via "nesting" and using these complex domain entities.

While not always the case, complex domain entities are typically associated with view domain entities.

1.3.3. Defining a View

The UI Framework expects a specific pattern in terms of where components are placed. Each of the View Config Annotations has explicitely defined which components are to be placed where.

The same is true for setting up the standard page view. The following configuration is a typical view configuration to get started:

Sample View Entity 1
@Domain(value="sample_view1", includeListeners = { ListenerType.websocket })
@Repo(Database.rep_none)
public class VRSampleView1 {

    @Page
    private VPMain vpMain;

    @Model @Getter @Setter
    public static class VPMain {

        @Tile
        private VTMain vtMain;
    }

    @Model @Getter @Setter
    public static class VTMain {

        @Section
        private VSMain vsMain;
    }

    @Model @Getter @Setter
    public static class VSMain {

        // Components here...
    }
}

As mentioned, this is the standard view config which can be expanded upon for an application’s specific needs. The primary point to note is the structure of Domain Entity > Page > Tile > Section. Only once this structure has been defined will. For more specific information on what components can go where, please review the View Config Annotations section.

1.4. Loading the Domain Entities

The com.antheminc.oss.nimbus.domain.config.builder package is reponsible for consuming the domain entity information during the initial loading process. On application startup, the Framework consumes the defined domain entity configurations and stores that information included within them in memory.

The registration process for domain entities starts with a registered Spring bean of type DomainConfigBuilder. This instance of DomainConfigBuilder uses the Spring Framework’s scanning capabilities to retrieve Domain configuration defined within specific packages.

Configuring Base Packages

The packages that are scanned in this manner can and should be defined via the application properties using the standard Spring PropertyPlaceholderConfigurer mechanism (using application.properties, application.yml, etc.).

The property used to identify packages for registration is: domain.model.basePackages.

domain:
  model:
    basePackages:
      - com.antheminc.oss.nimbus.entity
      - com.acme.app.model.feature_a.core
      - com.acme.app.model.feature_a.view
      - com.acme.app.model.feature_b.core
      - com.acme.app.model.feature_b.view
Note
The order of which the entities are loaded may play an impact on certain framework features. It is recommended to always load core domain config prior to view domain config.

1.5. The Param Interface

Consider that the Framework is acting much like an object oriented programming language in that a domain entity definition is playing the part of a class declaration. The next step in the Framework lifecycle is to use that definition to create an instance, much like the way an object is instantiated in an Objected Oriented Programming language.

This section focuses on the creation and use of the resulting object created by using the configuration consumed from the domain entity. The framework refers to this as the param, created using the Param interface.

This chapter will focus on explaining the Param interface in depth.

1.5.1. Introduction

After the framework registers domain entity definitions during application startup, it is creating what the Framework understands as a Param. The Param interface defines a number of properties on it that both the framework and UI framework will use to perform a given operation. Some examples might be, setting the value of the object, setting the object’s visibility, or setting whether or not the object should have validation applied to it (if it is a form element). Whatever the case, the important thing to note is that the Param interface is collectively the "model" data that is used everywhere throughout the Framework.

While understanding the core concepts of the Param is not essential for building out the UI of the application, it is important to understand it when dealing with certain functional scenarios. For example, an application can be created using the Framework by simply using the View Config Annotations and Conditional Config Annotations; however, when needing to manipulate the model directly (perhaps in a rule or SpEL expression), having an understanding of Param will be extremely useful.

1.5.2. Param Path

As previously explained, every Java variable created under a domain entity definition will eventually result in the creation of a param. Each param that is created in this way has a unique path by which it can be identified through using the framework’s Command Query DSL.

Introduction

The path is represented as a URI address that uniquely identifies a param’s location within the Framework. It is built by traversing through a Java class’s members, with a / denoting a child member (child members are also often referred to as "nested" members).

An example of some param paths can be found in the sample code below:

@Domain("petshop")
@Repo
@Domain(value="petshop", includeListeners = { ListenerType.persistence })
@Repo(alias = "petshop", Database.rep_mongodb)
@Getter @Setter
public class PetShop {

    // Param Path: /petshop:1/name
    private String name;

    // Param Path: /petshop:1/address
    private Address address;

    // Param Path: /petshop:1/boardedPets
    private List<Pet> boardedPets;

    // Collection elements are also retrievable by supplying an index
    // immediately following member name.
    // Collection Element Param Path with index 0:
    //   /petshop:1/boardedPets/0
    // Collection Element Param Path with index 1:
    //   /petshop:1/boardedPets/1
    // Collection Element Param Path with index n (where n is an
    // integer >= 0):
    //   /petshop:1/boardedPets/n

    @Model
    @Getter @Setter
    public static class Address {

        // /petshop:1/address/street
        private String street;

        ...
    }

    @Model
    @Getter @Setter
    public static class Pet {

        // /petshop:1/boardedPets/0/name
        // /petshop:1/boardedPets/1/name
        // /petshop:1/boardedPets/.../name
        private String name;

        ...
    }
}

Please note that in the example above all of the provided param paths are of the form: /petshop:1/…​. The index 1 is known as a RefId, which will be explained in the following section.

RefId

A refId is a unique identifier used to identify a particular instance of domain entity that has been created.

When a domain entity is configured with @Repo, the refId would be equivalent to the persistence ID used to store the object in the configured database.

Using the refId

The refId is typically created and maintained by the Nimbus framework. It is often used in pathing or Nimbus DSL statements to retrieve data specific to a persisted entity.

refId is configurable in the param path string with the syntax: "{DOMAIN_ALIAS}:{REF_ID}". (e.g. /petshop:1 or /petshop:2)

Relative param paths

While params may be resolved from the root param, it is not uncommon for params to be resolved relative to another param. The relative param is commonly referred to as a context param. This simply means that the path to the resolving param is simply relative from the path of the context param. This is achieved in the framework by using the familiar unix-style pathing syntax. For example, consider the following configuration:

@Model
@Getter @Setter
public static class SectionA {

    private String p1; (1)

    private Subsection1 subsection1;

    @Model
    @Getter @Setter
    public static class Subsection1 {

        private String p2; (2)
    }
}
  1. Assume the full path for this param is: "/some_domain/page/view/tile/sectionA/p1"

  2. Assume the full path for this param is: "/some_domain/page/view/tile/sectionA/subsection1/p2"

Using relative pathing, the following examples demonstrate relativity from one param to another:

  • p2 is relative to p1 by the path: "/../subsection1/p2"

  • p1 is relative to p2 by the path: "/../../p1"

Cross Domain Pathing

Sometimes it may be necessary to change the domain entirely to retrieve the data needed when working with param paths. This is achievable through the use of the param path marker /p. Simply start a param path with /p and the root domain context will be changed.

See Reserved Keywords for more details on /p.

Path Variable Resolver

The Command Query DSL offers certain reserved variables or keywords that can be used in a path. The Framework uses a default implementation of CommandPathVariableResolver to resolve a param path from a provided from the context of another param.

Property Placeholders

The default implementation of CommandPathVariableResolver will attempt to first resolve ${…​} placeholders in the given path, replacing them with corresponding property values. Unresolvable placeholders without default value having been set is not supported.

Reserved Keywords

After resolving property placeholders reserved framework keywords will be resolved. The framework provides support for the following reserved keywords:

Keyword Description

/p

The param path marker that denotes the start of a param path. This is only needed to be given when using a full URL or switching param contexts and is only recognized when used as the start of the param path.

/.m

Resolves to the path of the param that is mapped to the param identified by the preceeding path. (e.g. PARAM_PATH/.m returns the mapped param of the param identified by PARAM_PATH.

/.d

Resolves to the path of the param that is the root domain of the param identified by the preceeding path. (e.g. PARAM_PATH/.d returns the domain param that owns the param identified by PARAM_PATH.

<!#elemId!>

Resolves to the elemId of the command param, provided it is a collection element param. Works for mapped params as well.

<!#env!>

Uses the Spring Environment to resolve the preceeding path property. (e.g. <!#env.spring.application.name!> resolves using Environment.getProperty("spring.application.name"))

<!#refId!>

Resolves to the RefId of the ExecutionContext from which the command is being executed.

<!#self!>

When preceeded with loginId or id, resolves to the respective field from the current logged in user’s ClientUser object. Otherwise, resolves to the current Client Alias.

<!#this!>

Resolves to the path of the param in the current context.

<!page=y!>

Resolves to a string constructed from page, pageSize, and sortBy query parameters from the parent Command DSL statement.

<!filterCriteria!>

Resolves to a query parameter statement of the form: where=QUERYDSL_STATEMENT, where QUERYDSL_STATEMENT is constructed from filter information sent in the parent Command DSL payload.

1.5.3. Param Subclasses

There are many different types of Param implementations that are available. Each describes the model as created in the domain entity in such a way that the Framework is able to understand. For specific details about the implementations, see the com.antheminc.oss.nimbus.domain.model.state package.

SampleEntity.java
@Domain("sample_entity")
@MapsTo.Type(SampleCoreEntity.class)
public class SampleEntity {

    // Creates a leaf param
    private String leaf;

    // Creates a nested param
    private NestedEntity nestedEntity;

    @Model @Getter @Setter
    public static class NestedEntity {

        // Creates a leaf param inside of a nested param
        private String nestedLeafEntity;
    }

    // Creates a collection param, and potentially collection element params
    private List<NestedEntity> collectionEntity;

    // Creates a leaf param and a mapped param
    @MapsTo.Path
    private String mappedEntity;
}

Refer to SampleEntity.java above when reviewing the different classifications of Param below.

Leaf Param

A leaf param is a Param instance who has no children or nested params. A leaf param is represented by the interface LeafParam.

The variable leaf in the SampleEntity.java domain entity would be constructed by the Framework as a LeafParam.

Nested Param

A nested param is a Param instance who has children params. A nested param is represented by the interface Model.

The variable nestedEntity in the SampleEntity.java domain entity would be constructed by the Framework as a Model.

Collection Param

A collection param is a Param instance who is defined as an array or collection type. A collection param is represented by the interface ListParam.

The variable collectionEntity in the SampleEntity.java domain entity would be constructed by the Framework as a ListParam.

Collection Element Param

A collection element param is a Param instance contained as a child of a ListParam. It represents a child of a collection in the same sense that Java considers a collection element to belong to a java.util.List. A collection param element is represented by the interface ListElemParam.

The variable collectionEntity in the SampleEntity.java domain entity would contain 0 or more ListElemParam instances.

Mapped Param

A mapped param is a Param instance who’s entity definition has been annotated with @MapsTo.Type. A mapped param is represented by the interface MappedParam.

The variable mappedEntity in the SampleEntity.java domain entity would be constructed by the Framework as a MappedParam.

For more information about mapped params and their behaviors, see Mapping params.

Transient Param

TODO

1.5.4. State

The methods getState() and setState(…​) of Param are available for retrieving and setting the stored value of a param located at a particular path.

For example, consider working with a param at the domain level using the following Domain Entity definition:

Sample Domain Entity POJO
@Domain("sample_object")
public class SampleObject {

    private String a;
    private NestedObject1 b;

    @Model
    public static class NestedObject1 {

        private String c;
        private NestedObject2 d;
    }

    @Model
    public static class NestedObject2 {

        private String e;
    }
}

Consider also that one domain entity of SampleObject has been created with RefId = 1, and has been set with the following data:

Set State Sample JSON
{
    "a": "1",
    "b": {
        "c": "2",
        "d": {
            "e": "3"
        }
    }
}

This means that the framework will have created params for the sample_object domain entity with RefId = 1, and the user will be able to retrieve that data using the framework’s DSL. The framework DSL to retrieve this item might look like the following:

/p/sample_object:1/_get

The framework retrieves state by recursively iterating over a param’s child params (also commonly referred to as nested params). If thinking of /p/sample_object:1 as the root entity, then using the domain entity definition for SampleObject, the children entities would look like the following:

  • /p/sample_object:1

  • /p/sample_object:1/a

  • /p/sample_object:1/b

  • /p/sample_object:1/b/c

  • /p/sample_object:1/b/d

  • /p/sample_object:1/b/d/e

Efficiency

For the sake of effiency, it is important to be aware at a high level of how the framework is handling operations regarding state. When working with simple domain entities, such as an individual String or Integer the operation is straightforward. When working at a layer which contains nested paths, caution should be exercised to understand if the get/set state is truly needed at that layer to avoid invoking unnecessary operations.

For the previous example, when setState(…​) is invoked on root domain entity using RefId=1, the framework is actually invoking more than a single setState(…​). See the diagram below:

Param State Invocation Diagram

This diagram attempts to render a visual representation of how each of the setState(…​) invocations would occur when setting the state of a particular object in JSON. It illustrates that if a user is attempting to execute a setState(…​) call on the object located at /p/sample_object:1, 6 setState(…​) invocations will occur (See 1-6 in the diagram above).

Consider a scenario where only a particular value within a domain entity is needed to be set. In that case, we can target the unique param for that value by using it’s path. (e.g. update the param at /p/sample_object:1/b/d/e instead of the entire param at /p/sample_object:1. See #6 in the diagram above.) This would mean that the framework has avoided the processing of 5 extra setState(…​) calls, when it only needed to execute 1.

1.5.5. Param messages

Messages are available to be set into a param object that can be used in a variety of ways to display additional data to the user. While the rendering of param messages decided by specific attributes provided with a Message object, the use of the different types of available messages are entirely up to application to decide the manner in which they are used.

Displaying messages using @MessageConditional

The easiest way to display a message is to conditionally apply a message using @MessageConditional.

Displaying messages manually

When a more manual approach is needed, a Message object can be set directly into a param object.

// Provided param from some source
Param<?> p = ...;

// Create a new message
Message message = new Message("messageId", 'Some message here', Message.Type.INFO, Message.Context.INLINE, "css-class-1 css-class-2");

// Collect any existing messages available on the param object
Set<Message> messageSet = new HashSet<>();
if (!CollectionUtils.isEmpty(param.getMessages())) {
    messageSet.addAll(param.getMessages());
}

// Add the newly created message
messageSet.add(message);

// Set the messages into the param
param.setMessages(messageSet);
Tip
Clearly, there are some extra management steps when adding a message manually. Prefer to use @MessageConditional.
Displaying a global notification message

Global notification messages in other words are TOAST messages. There are two kinds of messages that can be set on a Param. One of them is Context.TOAST and the other being Context.INLINE.

@TextBox
@NotNull
@MessageConditional(when="state =='set'", isTransient=true, context=Context.TOAST, message="'Message is set for p1'")
@Label(value = "Test p1")
private String p1;

@TextBox
@NotNull
@Label(value = "Transient p2")
@MessageConditional(when="state !=null", context=Context.TOAST, targetPath="'/.d/'", isTransient=true,  message="'Global Message Set")
private String p2;

The above code has two parameters and the @MessageConditional on 'p1' can be read as follows - When p1 has a value of 'set', a toast message would be shown on the UI.

The toast messages are always shown at the top right corner of the screen and can either be closed on click or would go away after a few seconds.

If there is a use case to show message on navigating between pages - it can be achieved using TOAST messages where the target path for the message to be shown would be the path to the domain. In the example above, parameter 'p2' would have a message set at the domain if the state is not null.

Displaying inline messages

TODO

Setting the message type

TODO

Preserving messages on successive loads or page navigations

The default behavior for messages is to clear the messages on successive retrievals of the param object. This is intentionally done to support displaying a message only once. If there is a need to preserve the message on successive retrievals (such as when navigating away from a page and back to it), use the isTransient property as false.

Set a non-transient message with @MessageConditional
@Label("Do you like apples?")
@Radio(postEventOnChange = true)
@Values(YesNo.class)
@MessageConditional(when = "state =='Yes'", targetPath = "/../q2", message = "'Apple lovers do not like oranges!'", isTransient  = false)
private String q1;
Set a non-transient message manually
Message message = new Message("messageId", 'Some message here', Message.Type.INFO, Message.Context.INLINE, "css-class-1 css-class-2");
message.setTransient(false)

2. Configuration

The core concept of the Nimbus Framework is that one is able to write the different types of configuration quickly to build an application. Configuration is a high-level term used to describe the code that ultimately drives the logic of the Framework. The topmost and most common type of configuration will typically be found within an application’s domain entities.

Several different means of configuration are available to the application through Java based annotations. As seen in several of the previous chapters, much of the emphasis of configuration is on decorating domain entity variables. The most common types of annotation configs used in an application fall under the following categories:

View Config Annotations

Instruct the UI to render a specific view component (e.g. textbox, calendar, accordion)

Core Config Annotations

Execute functional instructions or commands within the Framework

Conditional Config Annotations

Execute common functional instructions based on boolean SpEL expressions

This chapter will explore and explain the numerous configuration features of the Framework.

2.1. Writing view configuration

This section is intended to explain detailed concepts of writing view configuration. At it’s core, view configuration is nothing but a domain entity…​ just another object that is provided for the framework to create and manage. The only difference is that it has additional configurative decorators that define behaviors (such as how the UI should render the final view component).

Note
When reading this chapter, please note that explicit details for how an individual view configuration annotation is used can be found in the API or in the Appendix. This chapter is more dedicated to common scenarios and larger concepts that need to be explained when writing view configuration.

2.1.1. Creating a simple view domain

Creating a view domain often follows a common design structure containing the following components:

A simple hello world template can be seen below using this structure:

@Domain(value = "homepage", includeListeners = { ListenerType.websocket })
@Repo(value = Repo.Database.rep_none, cache = Repo.Cache.rep_device)
@ViewRoot(layout = "")
@Getter @Setter
public class VRHomepage {

    @Page(defaultPage = true)
    private VPMain vpMain;

    @Model
    @Getter @Setter
    public static class VPMain {

        @Tile
        private VTMain vtMain;
    }

    @Model
    @Getter @Setter
    public static class VTMain {

        @Section
        private VSMain vsMain;
    }

    @Model
    @Getter @Setter
    public static class VSMain {

        @Label("Hello World!")
        @Paragraph
        private String hello;
    }
}

2.1.2. Navigating between pages

External page

External page navigation can be achieved using a combination of @Link and @Config through the use of _nav and query parameter redirectUrl.

@Label("Nav Externally")
@Link
@Config(url = "/_nav?redirectUrl=https://mywebsite.com")
private String navExternally;

2.1.3. Defining layouts

A layout is just another view domain which acts as a reusable view for other view domains through the use of @ViewRoot.

A simple layout definition
@Domain(value="home", includeListeners={ListenerType.websocket})
@Repo(value = Database.rep_none, cache = Cache.rep_device)
@Getter @Setter
public class VLHome {

    @Page
    private VPHome vpHome;

    @Model @Getter @Setter
    public static class VPHome {

        @Section(Type.HEADER)
        private VSHomeHeader vsHomeHeader;

        @MenuPanel
        private VSGlobalNav vsGlobalNav;
    }

    @Model @Getter @Setter
    public static class VSGlobalNav { ... }
}

The configuration is similar to any view domain with a page that has a section and a menu panel. It is important to note that '@MenuPanel' and '@Section(Type.HEADER)' are supported in a '@Page'. Once the global layout(header, menupanel and footer) is defined, every other subflow could have its own embedded layout. For example, once the user lands on the home page, he/she can be navigated to a new screen that has new header, new side navigation panel in additional to the global header, footer.

So for the above example, a new layout domain needs to be configured and that layout domain alias needs to be configured in the view domain.

Configuring a view domain to use a layout

@ViewRoot is used to tell the UI framework which layout definition to use by it’s domain alias.

Sample view domain configuration with the "home" layout.
@Domain(value="newviewroot", includeListeners={ListenerType.websocket})
@Repo(value = Database.rep_none, cache = Cache.rep_device)
@ViewRoot(layout = "home")
@Getter @Setter
public class VRNewViewRoot {
    ...
}

The above code illustrates how to bind the "home" layout to the "newviewroot" view domain so that when the view domain is rendered the corresponding layout renders along with it.

Supported components

A layout is nothing more than a simple view domain definition with a specific set of components that the UI framework provides rendering support for:

Note
*@Section has a number of Type values that can be provided. See the Section.Type javadocs for more information.
Defining the "home" layout

The framework expects that there is a default layout defined with a domain alias "home". This would always be rendered for any application that has the global header, footer, etc. This is commonly referred to as the "global layout".

Defining embedded layouts

Once the global layout is defined every other page might need to have it’s own embedded layout. For example, once the user lands on the home page he/she can be navigated to a new screen that has a new header and a new side navigation panel in addition to the components rendered via the global layout.

An embedded layout
@Domain(value="innerlayout", includeListeners={ListenerType.websocket})
@Repo(value = Database.rep_none, cache = Cache.rep_device)
@Getter @Setter
public class VLInnerLayout {
    ...
}
Defining a view with no layout

If no layout is needed, simply give @ViewRoot("") with an empty value.

A view domain with no layout
@Domain(value="newviewroot", includeListeners={ListenerType.websocket})
@Repo(value = Database.rep_none, cache = Cache.rep_device)
@ViewRoot(layout = "")
@Getter @Setter
public class VRNewViewRoot {
    ...
}

2.1.4. Working with labels

@Label is used to display descriptive information for the user in coordination with any valid view annotations.

@Label("Enter a value here: ")
@TextBox
private String p1;

Each view annotation component has it’s own definition for how label related information should be rendered, however most UI components will typically keep the display consistent.

SpEL Support

Spring expression language (SpEL) property replacement is allowed here:

@Label("${env.labels.common.p1}")
@TextBox
private String p1;
Command Path Resolver Support

Path Variable Resolver support is available here:

@Label("Hello <!/../p1!>!")
@TextBox
private String p1;
Adding help text

Additional information about how to use the rendred component can be supplied using the helpText feature.

@Label(value = "Enter a value here: ", helpText = "Please use MM/YYYY format.")
@TextBox
private String expiration;

2.1.5. Styling components with configuration

TODO

2.1.6. Printing content

The @Button component can be configured with style = Button.Style.PRINT. This will instruct the UI on click of the rendered button to open a printable view of content that can easily be printed using the browser’s native print dialog window. The printed content is determined by supplying printPath = "[PARAM_PATH_TO_CONTENT]".

@Label("Print Demo")
@Button(style = Button.Style.PRINT, printPath = "/domain/page/tile/section")
private String printButton;

The previous example opens a print dialog with the rendered HTML content resolved from the @Button attribute: printPath, or the content rendered from "/domain/page/tile/section", in a new window/tab.

Customizing printable content

The rendered printable content can be configured by decorating print button definition with @PrintConfig. This allows the configurer the ability to give detailed instructions on how the content should be rendered or how the printing experience should behave.

@Label("Print Demo")
@Button(style = Button.Style.PRINT, printPath = "/domain/page/tile/section")
@PrintConfig(useAppStyles = true, closeAfterPrint = false)
private String printButton;

Only certain components are allowed to be "printable", such as container-based elements. See the @PrintConfig javadocs for more information on which components are able to be printed.

Tip
@PrintConfig values may need to be different under different scenarios. In that case, define multiple view config fields (e.g. another @Button) and conditionally hide or show the print button that performs the desired functionality.
Controlling the look and feel of printable content

The default behavior of a clicking on a "print button" will open the rendered content in a new browser tab or page with no additional styles added. Styling can be configured by providing additional details for @PrintConfig. See the @PrintConfig javadocs for more details.

2.1.7. UI Error Messages

All uncaught exceptions within the framework are handled by a global exception handler and delivered to the UI to be rendered and displayed to the user. This includes exceptions type that occur from within the framework itself or client managed beans. The WebActionControllerAdvice class is a good starting point for a deep-dive on how the exception handling response is delivered.

Providing custom messages for an exception

The UI offers some generic customization options for unhandled exceptions. When an unhandled exception makes it’s way to the UI, a default error message as well as a unique identifying id will be displayed to the user. By default, the error message is engineer-friendly but is not likely descriptive to the user.

Custom messages can be generically be defined for an exception type by defining the following in the application.yml configuration:

application:
  exceptions:
    com.acme.foo.exceptions.MySpecialException: Some custom message here
    java.lang.NullPointerException: Something was null
Reserved exception keywords

There are certain reserved keywords that when used in defining custom exception messages in application.yml that will be replaced.

Value

Description

ERR.UNIQUEID

The unique generated error id.

Defining a custom exception handler

The @ExceptionHandler defined within the framework is a catch all for all types of Throwable exceptions. Custom logic can be applied by defining a @ExceptionHandler within a nimbus client application to return a desirable response.

@Autowired
CommandTransactionInterceptor interceptor;

@ExceptionHandler(NullPointerException.class)
@ResponseBody
public ResponseEntity<MultiExecuteOutput> exception(NullPointerException exception) {
    HttpHeaders headers = new HttpHeaders();
    ExecuteOutput<?> executeOutput = new ExecuteOutput<>();
    executeOutput.setExecuteException(new ExecuteError(exception.getClass(), "Some custom message"));
    MultiExecuteOutput output = interceptor.handleResponse(executeOutput);
    return new ResponseEntity<>(output, headers, HttpStatus.INTERNAL_SERVER_ERROR);
}
Note
CommandTransactionInterceptor provides some utility methods for building the expected response the UI expects for a server side error.
Suppressing UI error messages

The UI will not render popup error messages if the error message that is returned from the server is null or empty. Simply set the error message to be empty to suppress a UI error message from being displayed.

application:
  exceptions:
    # Displays "Some cool exception message"
    com.foo.MyException: Some cool exception message

    # Does not render!
    java.lang.NullPointerException:

2.2. Configuring data source connections

TODO

2.2.1. MongoDB

TODO

2.2.2. External web services

Domain entity values can be persisted and retrieved using an external web service as the backing data source. To achieve this, a REST call must be configured to be sent to a web service that will provide or store the data.

@Domain(value = "ext_client")
@Repo(value = Database.rep_ws, cache = Cache.rep_device)
@Getter @Setter @ToString(callSuper = true)
public class ExtClient { ... }

Notice @Repo(value = Database.rep_ws) in the code above. This indicates that the framework will treat the domain entity for ExtClient as having an external web service as it’s data store. One additional configuration for ext.repository.targetUrl.[ALIAS] in application.yml is needed to define the entry point to the external web service.

ext:
  repository:
    targetUrl:
      ext_client: https://acoolwebservice.com/

With this configuration given, requests that look like: http://localhost:8080/a/b/p/ext_client/_get now become: https://acoolwebservice.com/a/b/p/ext_client/_get.

2.2.3. Custom repository implementations

The framework offers the ability to configure custom repository implementations that allows users to configure anything that is achievable from within the ModelRepository interface.

To configure a domain entity definition to use a custom model repository implementation, use @Repo with Database.rep_custom and modelRepositoryBean:

@Domain(value = "sample_core", includeListeners = { ListenerType.persistence })
@Repo(value = Database.rep_custom, modelRepositoryBean = "customRepositoryBean", cache = Cache.rep_device)
@Getter @Setter
public class SampleCoreEntity {
    ...
}

and create a ModelRepository Spring bean using the qualifier as the value provided for modelRepositoryBean.

public class ReallyCoolModelRepository implements ModelRepository {
    ...
}
@Bean("default.customRepositoryBean")
public ModelRepository reallyCoolModelRepository() {
    return new ReallyCoolModelRepository();
}
Note
Consider using rep_custom when needing to configure different data sources or multiple databases for one or more domain entity definitions.

2.3. Mapping params

The section Core versus View explains the difference between a core domain entity and a view domain entity. While a core domain entity is the primary holder of the data, the view domain entity is primarily concerned with the display of the data.

Such a distinction is necessary as view domain entities tend to be hierarchical in nature, whereas core domain entites tend to be more flat by comparison. It also allows for us to separate the view configuration when interacting with the UI rendered components and the core configuration our domain entities use when interacting with the underlying database.

To establish a connection between the core and the view (similar to how an MVC application does), the Framework introduces the concept of mapping the state between Param instances. This provides for a two-way binding between the state of mapped core and view params in that when the state of the core is updated, the state of the view will also be updated. The same is true when updating the state of a mapped view param, in that the state of the core param would be updated.

2.3.1. Configuring a Mapping

The process for defining a mapping between params is relatively straightforward. See the following example:

Sample Core Entity
@Domain("sample_core", includeListeners = { ListenerType.persistence })
@Repo(value = Database.rep_mongodb, cache = Cache.rep_device)
@Getter @Setter @ToString
public class SampleCoreEntity {

    private String name;
}

This class represents the definition of our core domain entity. Params created from this defintion will be mapped to from view Params.

@Domain(value="sample_view1", includeListeners = { ListenerType.websocket })
@Repo(Database.rep_none)
@MapsTo.Type(SampleCoreEntity.class) (1)
public class VRSampleView1 {

    // Page/Tile/Section/Form declarations omitted for brevity.

    @MapsTo.Type(SampleCoreEntity.class) (2)
    @Getter @Setter
    public static class SampleForm {

        @TextBox
        @MapsTo.Path (3)
        @Label("Enter name: ")
        private String name;
    }
}

This class represents the definition of a view domain entity.

Mapping a Type

Providing @MapsTo.Type gives instruction to the framework that the object structure it decorates should map children parameters to the given class. Notice that both VRSampleView1 and SampleForm map to SampleCoreEntity.class.

Note
When using @MapsTo.Path, the framework requires that @MapsTo.Type be provided as a decorator to the parent model, or else an InvalidConfigException will be thrown.
Mapping a Param

Now that name is decorated with @MapsTo.Path, the Framework knows that the textbox component defined at SampleForm.name is mapped to SampleCoreEntity.name. Hence the value displayed in the textbox would be equal to the state found in SampleCoreEntity.name.

2.3.2. Mapping an view entity to a core entity with a different name

A common scenario when writing configuration is that your view domain’s variable names may not match the variable names within the core domain. This is allowable. Using a concept similar to overriding, the mapped field be changed by specifying the path (relative to the core domain root). For example:

@MapsTo.Type(SampleCoreEntity.class)
@Getter @Setter
public static class SampleForm {

    @FieldValue
    @MapsTo.Path("name") (1)
    @Label("Hello: ")
    private String personName;
}
  1. Since "name" is provided to @MapsTo.Path, personName would map to the name in the core domain instead of personName

2.3.3. Mapping with hierarchies

Since @MapsTo.Path accepts a string containing a relative path from the core root domain object, it is possible to map between hierarchies.

@Domain("sample_core", includeListeners = { ListenerType.persistence })
@Repo(value = Database.rep_mongodb, cache = Cache.rep_device)
@Getter @Setter @ToString
public class SampleCoreEntity {

    private Person emergencyContact1;
}

public class Person {

    private String name;
    private Address address;
}
@MapsTo.Type(SampleCoreEntity.class)
@Getter @Setter
public static class SampleForm {

    @FieldValue
    @MapsTo.Path("/emergencyContact1/name") (1)
    @Label("Hello: ")
    private String personName;
}
  1. Maps to the value at name of emergencyContact1 declared in SampleCoreEntity.

2.3.4. Mapping at multiple layers

Since @MapsTo.Type is given at the class definition level, it is possible to define nested layers of mapping by unlinking the child parameter from the parent’s @MapsTo.Type using @MapsTo.Path(linked = false). For example:

@MapsTo.Type(SampleCoreEntity.class) (1)
@Getter @Setter
public static class SampleSection {

    @TextBox
    @MapsTo.Path
    private String name;

    @CardDetail
    @MapsTo.Path(linked = false) (2)
    private VCD1 vcd1;

    // Internal classes

    @MapsTo.Type(Person.class) (3)
    @Getter @Setter
    public static class VCD1 { ... }
}
  1. The parent SampleSection is mapped to SampleCoreEntity.

  2. The child vcd1 is no longer mapped to SampleCoreEntity.

  3. The child vcd1 is now mapped to Person.

2.4. Defining executable logic

2.4.1. Executing @Config statements

A majority of configuration is executed using the @Config decoration to invoke a Nimbus Command DSL statement.

The WebCommandDispatcher is responsible for handing off all HTTP requests to the DefaultCommandExecutorGateway, which is ultimately responsible for executing any @Config statements. Hence, any HTTP request with a valid param path in the command DSL would have the statements given in any decorating @Config annotations executed.

@Domain(value = "dashboard", includeListeners = { ListenerType.websocket })
@Repo(value = Repo.Database.rep_none, cache = Repo.Cache.rep_device)
@Getter @Setter
@ViewRoot(layout = "")
public class VRDashboard {

    @Config(url = "/p2/_get") (1)
    private String p1;

    private String p2;

    @Config(url = "/p2/_get") (2)
    @Config(url = "/p2/_delete") (3)
    private String p3;

    @Config(url = "/p3/_get") (4)
    private String p4;
}
Single @Config Execution

If the application is up and running and the following HTTP GET call is made:

Then statement <1> would be executed.

Multiple @Config Execution

The execution of multiple (more than one @Config decorating a field) @Config is supported. This is as suggested by making an HTTP GET call to:

This would execute both statements <2> and <3>.

Transitive @Config Execution

The execution of transitive (a @Config which invokes another @Config) @Config is supported. This is as suggested by making an HTTP GET call to:

This would execute statement <4>, which would then execute both statements <2> and <3>.

Use @Initialize to execute a set of @Configs after page load

@Initialize simply informs the UI framework to invoke a _get action call to the parameter that @Initialize is decorating. Specifically, it works for components also annotated with @Section and only after the page has rendered. So using @Initialize is a way to trigger any configs, events, or state changes to execute after the page has rendered.

Consider the following example:

// page, tile omitted for brevity (assume path to this point is: "/sampleview/vpMain/vtMain")

@Section
@Initialize (1)
@Config(url = "some-command-query-dsl-here") (2)
@Config(url = "some-other-command-query-dsl-here") (3)
private VSSampleSection vsSampleSection;

@Model
@Getter @Setter
public static class VSSampleSection { ... }
  1. Ensures that the configs 2 and 3 would be executed once the page renders.

2.5. Rule configuration

TODO

2.6. Events

The framework offers a set of hooks (called events) that can be used to define executable logic to be exected after the event takes place. One such example is any of the conditional event handlers used to drive the conditional annotations.

2.6.1. Predefined events

The following list shows the predefined events available out-of-the-box:

OnParamCreate

TODO

OnRootCommandExecute

TODO

OnRuntimeStart

TODO

OnRuntimeStop

TODO

OnSelfCommandExecute

TODO

OnStateChange

TODO

OnStateLoad

TODO

OnStateLoadGet

TODO

OnStateLoadNew

TODO

Script

TODO

2.6.2. Creating a custom event handler

TODO

2.7. Business process model and notation (BPMN)

TODO

2.8. Change log

Any command execution now gets captured in a collection called changelog in mongodb.

ChangeLog Attributes
Attribute Type Description

on

Date

command config execution date & time

by

String

command config execution by userId

root

String

root entity on which command config is executed

refId

Long

root entity id on which command config is executed

action

Action

action performed during command config execution

url

String

command config complete uri

path

String

path of the param path that is being updated(_replace/_update) as part of command config execution

value

Object

value of the param that is being updated (_replace/_update) as part of command config execution

2.8.1. Usage Examples

Lets take the example domain named Test with an alias "testdsl". it has a form with a button marked with @Configs.

Test.java
@Getter @Setter
@Domain(value = "testdsl")
@Repo(Database.rep_mongodb)
@ToString
public class Test extends IdString{

    @Ignore
    private static final long serialVersionUID = 1L;

    private String status;

    @Form
    private Form form;

    @Model
    @Getter @Setter
    public static class Form {

        @Button
        @Configs ( {
            @Config(url="/p/testdsl2/_new"),
            @Config(url="/status/_update?rawPayload=\"Test_Status\"")
        })
        private String button;

    }
}

In above example, when the user (logged user 'testUser') triggers an action by clicking submit button (testdsl has a RefId of 1), below command urls would get invoked in sequence:

  1. ../p/testdsl:1/form/button/_get?b=$execute

  2. ../p/testdsl2/_new

  3. ../p/testdsl:1/status/_update?rawPayalod=\"Test_Status\"

changelog collection would now contain entries for all 3 command executions:

ChangeLog collection entries
on by root refId action url path value

current datetime

testUser

testdsl

1

_get

/p/testdsl/form/button/_get?b=$execute

current datetime

testUser

testdsl2

2

_new

/p/testdsl2/_new

current datetime

testUser

testdsl

1

_update

/p/testdsl/status/_update?rawPayload=\"Test_Status\"

2.9. Validation

Note
Only client side validation is supported today.

2.9.1. Client side form validation

The Nimbus Framework supports client side validation by offering support for JSR Validation annotations, specifically from the javax.validation.constraints package. This includes annotations such as @NotNull, @Min, @Max, etc. Simply decorate a field underneath a @Form with a JSR supported validation and include a @Button with type Button.Type.submit to enable client side validation. See the following example:

@Form
private VFSample vfSample;

@MapsTo.Type(Person.class)
@Getter @Setter
public static class VFSample {

    @Label("First Name")
    @TextBox
    @NotNull
    @Path
    private String firstName;

    @Label("Submit")
    @Button(style = Button.Style.PRIMARY, type = Button.Type.submit)
    @Config(url = "<!#this!>/../_update")
    private String submit;
}

This sample would result in firstName being a required field.

Warning
Currently, any view parameter that is expected to use form validation must not have the same variable name as any other field under the form hierarchy. Java restricts duplicate variable names for class, but not within nested classes. This is a current limitation of the UI validation framework.

2.9.2. Validation Messages

Validation messages that will be displayed to the end-user using javax.validation.constraints annotations are configurable with respect to the following order:

  1. Messages explicitly set in the corresponding javax.validation.constraints annotation’s message attribute.

  2. Default messages defined within client jar (application.properties and YAML variants).

  3. Default messages defined within the Nimbus Framework

Default Message

A set of default values are provided by the framework for supported annotations.

@Label("Enter a value:")
@TextBox
@NotNull
private String textbox;

The previous code example would display the message Field is required., as is the default message for @NotNull.

Overriding a Default Message

The default validation message for an annotation can be overriden by supplying a key/value definition within the client jar’s application.properties or YAML variants.

Each javax.validation.constraints annotation has a unique message key set into it’s message attribute by default, which is used by the framework to determine the appropriate default message value. Simply provide the unique key for the javax.validation.constraints annotation to replace the default value across the application.

application.yml
# Default Validaton Message Overrides
javax:
  validation:
    constraints:
      NotNull:
        message: Very Important Field!
SampleViewEntity.java
@Label("Enter a value:")
@TextBox
@NotNull
private String textbox;

This would result in Very Important Field! being displayed on a validation error.

Set an Explicit Validation Message

To explicitely set a validation message for a field, provide a value for the message attribute of the javax.validation.constraints annotation.

@Label("Enter a value:")
@TextBox
@NotNull(message = "Not allowed to be empty!")
private String textbox;
SpEL Property Resolver Support

As SpEL Property Resolver support is included in all annotation attributes, it can also be utilized within the message attribute.

@Label("Enter a value:")
@TextBox
@NotNull(message = "${sample.form.textbox.notnull}")
private String textbox;
Sample messages_en-US.properties
sample.form.textbox.notnull=REQUIRED

3. The Command Query DSL

The Command is the instruction that the Framework understands to execute and come back with an output. It is similar to writing the traditional method calls for an action (such as a button click) to perform business logic, but attempts to introduce a standardized process via the use of a domain specific language (DSL) that the Framework can interpret. This is called the Command Query DSL.

The Command Query DSL is represented in URL format (commonly referred to as the Command URL) and is based on Query DSL.

Apart from the host (typical "host", or provider, of the application utilizing the Framework), the DSL’s Command URL can be broken down into four main subsections of:

  • Client-alias - identification path used to segregate data/logic for one or multiple "clients". Supports clientCode, leaf-org-id, and appAlias

  • Domain-alias - identification path within the framework to locate a defined object, or domain entity

  • Action - instruction used to identify the operation to perform on the object identified by domain-root

  • Query Parameters - A standard set of query parameters

The following diagram shows each of the subsections of the Command Query DSL mentioned above in various examples in an "exploded" view:

Command DSL Pattern
Figure 1. Command DSL Pattern Diagram

In the diagram above, several examples of Command URL’s can be seen. By splitting the URL into it’s individual parts, the diagram represents each of the Command URL’s as a vertical section. Requests sent to the Framework using each of these URLs perform different logic as defined by the Framework. The sections that follow will explain the logic defined for each of the individual subections of the Command URL as well as expain the Command Query DSL in-depth.

3.1. Client Alias

TODO

3.2. Domain Alias

The domain alias (also referred to as path) is the essential and active portion of working with the Command Query DSL. It is what is used to identify which object, or domain entity, the framework should perform a defined action on.

Each domain alias starts with a domain root, which is the highest level in the domain path used for identification purposes. This will correspond with values used in @Domain. Sometimes a RefId will be provided, to denote a persisted domain root object to retrieve.

From there, the Command Query DSL traverses the Java objects that are domain entities and finds a particular field identified by the remaining path. See The Param Interface for more information.

Using this path in the Command Query DSL can then be used to target a specific Action to perform, which will be discussed in the next section.

3.3. Actions

The Action is the third primary subsection of the Command URL. Action identifies which type of logic or instructions which should be applied to a given domain entity. The domain entity on which the action will be applied is identified by the domain-alias path given in the Command URL preceding the action parameter (see Domain Alias).

A Command URL without an action is not understood and hence not supported by the Framework. Hence, action is required.

See the following example:

@Config(url = "/domainobject/page/tile/section/form/_get?b=$execute")

In the example above, the action is _get. This states that we should perform the instructions defined for _get on the param located at domainobject/page/tilesection/form.

Each of the sections that follow will describe the logic defined within the Framework for that specific action.

3.3.1. _new

_new: Creates a new instance for the model

Available Function Handlers

3.3.2. _get

_get: Fetches the instance of the model referenced by the Id

Available Function Handlers

3.3.3. _save

_save: Saves the model into the database

3.3.4. _replace

The action replace sets the state of the param identified by the _domain alias in the Param Path to a given state. The state to set can be provided in the following manners:

  • provided as a JSON object in the HTTP POST Body

  • provided as a JSON query parameter: rawPayload

In each of these scenarios, the provided JSON should conform to the object identified by the Param Path.

When using the _replace action, not providing explicit values for fields will result in any existing state within the identified param to be set to null. In a situation where only some values of the state need to be set and the others preserved, consider using _update.

Note
See _replace Examples for examples using _replace.

3.3.5. _update

The action update sets individual fields of the state of the param identified by _domain alias in the Param Path to the fields of a given state.

  • provided as a JSON object in the HTTP POST Body

  • provided as a JSON query parameter: rawPayload

In each of these scenarios, the provided JSON should conform to the object identified by the Param Path.

When using the update action, not providing explicit values for fields will result in any existing state within the identified param to be preserved. In a situation where only the state needs to be explicitly set, consider using _replace.

Note
See _update Examples for examples using _update.

3.3.6. _delete

_delete: Removes the model from the database

_search: Searches the model based on a search criteria

Available Function Handlers

3.3.8. _process

_process: Executes asigned workflow process or custom handlers

Available Function Handlers

3.3.9. _nav

This action can be used to instruct the UI application to navigate to a specified pageId in the config. When @Domain has more than 1 @Page annotations configured, this action helps in configuring the required page that the user has to land on performing an action.

Examples
@Domain(value = "viewdomain")
@Repo(value = Repo.Database.rep_none, cache=Repo.Cache.rep_device)
@Getter @Setter
@MapsTo.Type(CoreDomain.class)
@ViewRoot(layout = "")
public class VRDomain {

    @Page(defaultPage=true)
    private Page1 page1;

    @Page
    private Page2 page2;

    @Model
    @Getter @Setter
    public static class Page1  {

        @Config(url = "/page1/_nav?pageId=page2")
        @Button
        private String navToNewPage;

        @Label("Nav Externally")
        @Link
        @Config(url = "/_nav?redirectUrl=https://mywebsite.com")
        private String navExternally;
    }
}

In the example above there are two pages configured in a domain with 'page1' being the default page. By configuring defaultPage=true the UI is getting instructions that if there is no navigation pageId specified by the server response, render 'page1' as a default.

The config on button can be read as follows - When the user clicks on the button(navToNewPage), redirect the user to page2.

The navigation can also be external to an application and an example can be seen above with the redirection to https://mywebsite.com

3.4. Query Params

TODO

3.5. Function Handlers

Function Handlers are an abstraction within the framework to execute a common set of instructions for a given Action.

Given an action, a particular function handler can be executed by specifying a value for the query parameter fn (e.g. /_process?fn=_set). Together, the action and fn value define a Function Handler ID, or a unique key by which to identify the function handler. Function handlers use the param resolved by the path preceeding the function handler id as the param context from which to execute logic against. For example:

@Config(url = "/p/patient:<!/.m/id!>/_process?fn=_set")

In the example above, /_process?fn=_set invokes the Set State Function Handler of the param located at /p/patient:<!/.m/id!>.

3.5.1. Providing Arguments to a Function Handler

Additional query params can be provided as arguments to a function handler to produce different behavior. For example,

@Config(url = "/p/patient:<!/.m/id!>/name/_process?fn=_set&value=Bob")

Please refer to the specific function handler implementation for more details on which and how parameters are supported.

3.5.2. Predefined Function Handlers

There are several default function handlers defined within the core framework to handle common instructions, such as setting the state of a parameter or adding a parameter value into a collection.

Param

_get?fn=param

InitEntity

_new?fn=_initEntity

Add

_process?fn=_add

BPM

_process?fn=_bpm

Filter

_process?fn=_filter

Set

_process?fn=_set

Set by Rule

_process?fn=_setByRule

Update

_process?fn=_update

Search by Example

_search?fn=example

Search by Lookup

_search?fn=lookup

Search by Query

_search?fn=query

Param

The param function handler allows for retrieval of a given parameter, as well as the ability to execute logic using the given parameter as a context.

Usage

The param function handler is invoked by ending the command query with "/_get?fn=param", using the param resolved by the preceeding path as the context param. Specific implementation details can be found within DefaultParamFunctionHandler.

Examples
@Label("Field 1")
@TextBox
private String field1;

@Label("Click to say hello")
@Button
@Config(url = "<!#this!>/../field1/_get?fn=param&expr=setState('Hello World!')")
private String actionA;

@Label("Counter")
@TextBox
private int counter;

@Config(url = "<!#this!>/../counter/_get?fn=param&expr=setState((null != state ? state : 0) + 1)")
private String actionB;
Supported Query Parameters

Name

Required

Description

expr

false

A string SpEL expression to be executed, using the context param as the context

Use Cases

The param function handler is extremely versatile in that the executor can execute nearly anything that one could craft in a SpEL expression. Beyond simply retrieving a param value, a common scenario for using the get param function handler is to manually invoke one of the methods directly on the given param instance. This may be useful when working with a certain type of param (e.g. a Collection Param or a Transient Param).

Caution
A word to the wise: Whatever the case, the ability to execute SpEL expressions at lower layers of the Nimbus Framework is inherently powerful and may introduce problematic or difficult-to-debug behavior.
InitEntity

The initEntity function handler instantiates the context param with provided values.

Usage

The initEntity function handler is invoked by ending the command query with "/_new?fn=_initEntity", using the param resolved by the preceeding path as the context param. Specific implementation details can be found within DefaultActionNewInitEntityFunctionHandler.

Examples
@Label("Note Type")
@ComboBox
@Values(NoteTypes.class)
private String noteType;

@Label("Note Description")
@RichText(readOnly = true)
private String noteDescription;

@Config(url = "/p/notes/_new?fn=_initEntity&target=/noteDescription&json=\"<!/../noteDescription!>\"&target=/noteType&json=\"<!/../noteType!>\"")
private String submitAndAddNew;
Supported Query Parameters

Name

Required

Description

target

true

A string representing a relative path of a param to instantiate (from the context param). Used exclusively with the json attribute that follows.

json

true

A JSON representation of the object to set into the param resolved from the path given in the preceeding target attribute.

Add

The add function handler attempts to add a Collection Element Param to a Collection Param.

Usage

The add function handler is invoked by ending the command query with "/_process?fn=_add", using the param resolved by the preceeding path as the context param. The context parameter should resolve to a Collection Param. Specific implementation details can be found within AddFunctionHandler.

Note
The add function handler is subclassed from URL Based Function Handler, which means that it shares common behavior amongst some of it’s attributes. See the URL Based Function Handler documentation for more details.
Examples
@Model
@Getter @Setter
public static class VSSection {

    private int counter;
    private SampleLineItem tmp;

    @Label("Sample Grid")
    @Grid
    private List<SampleLineItem> collection;

    @Label("Add an item")
    @Button
    @Config(url = "<!#this!>/../counter/_get?fn=param&expr=setState((null != state ? state : 0) + 1)")
    @Config(url = "<!#this!>/../tmp/_replace?rawPayload={\"name\": \"Item <!/../counter!>\"}")
    @Config(url = "<!#this!>/../collection/_process?fn=_add&url=/petcareassessmentview/<!#this!>/../tmp")
    private String add;
}

@Model
@Getter @Setter
public static class SampleLineItem {

    @Label("Item Name")
    @GridColumn
    private String name;
}
Supported Query Parameters

Name

Required

Description

value

false

A string containing the value to add into the context param.

url

false

A param path of a param containing the desired state to set into the context param. The param path may be either internal or external.

BPM

The BPM function handler executes a BPM flow.

Usage

The BPM function handler is invoked by ending the command query with "/_process?fn=_bpm", using the view root param as the context param for the BPM process. Specific implementation details can be found within StatelessBPMFunctionHanlder.

Examples
@Button
@Config(url = "/_process?fn=_bpm?processId=statelessbpmtest") (1)
private String submit;
  1. This assumes that the file statelessbpmtest.bpmn20.xml exists in the process-defs directory(src/main/resources/process-defs/).

Supported Query Parameters

Name

Required

Description

processId

true

The name of the BPM process (or file name) to execute

Filter

The filter function handler attempts to set the state of a given Collection Param to the result set obtained by removing collection elements do not meet a truthful evaluation of a given SpEL predicate.

Note
  • The action parameter must be a collection or array typed Param, and is by default the Param given in the preceding Command path.

  • The action paremeter to set may be overriden by supplying a path to the query parameter: targetParam.

Usage
Examples
@Domain(value = "sample_view", includeListeners={ ListenerType.websocket })
@MapsTo.Type(SampleCoreEntity.class)
@Repo(Database.rep_none)
@Getter @Setter
public class VRSampleViewRootEntity {

    private String[] arr;
    private List<String> simple_list;
    private List<ComplexObject> complex_list;

    @Config(url = "/arr/_process?fn=_filter&expr=contains('pickme')")
    @Config(url = "/simple_list/_process?fn=_filter&expr=contains('pickme')")
    @Config(url = "/complex_list/_process?fn=_filter&expr=state.p1.contains('pickme')")
    private String action_demoFilter;

    @Model
    @Data
    public static class ComplexObject {
        private String p1;
    }
}
Filtering complex vs non-complex typed collections

The expected SpEL predicate syntax is slightly different when the action parameter’s collection element type is complex versus when it is not. Non-complex elements will simply expose each collection element’s state to the SpEL expression context, while complex elements will expose the entire Param for each collection element.

Non-complex typed collection
@Config(url = "/simple_list/_process?fn=_filter&expr=contains('pickme')")

Since simple_list has type String, it is non-complex. Notice in this example that expr is making use of the String.contains method. This is because the state of the collection element (type String) is exposed as the context for the SpEL expression.

Complex typed collection
@Config(url = "/complex_list/_process?fn=_filter&expr=state.p1.contains('pickme')")

Since complex_list has type ComplexObject, it is complex. For complex typed collections, the Param interface for the collection element parameter is exposed as the context for the SpEL expression, which offers the ability to reference state. In this way, greater filtering support is available for complex elements.

Supported Query Parameters

Name

Required

Description

expr

true

A SpEL expression to evaluate to determine whether or not a collection element should be included in the final result set.

targetParam

false

The path to the param to set the final result set into. The type of the targetParam should match that of the collection element being filtered. If not provided, the state will be set into the action parameter.

Set

The set function handler sets the state of the context param to the provided state.

Usage

The set function handler is invoked by ending the command query with "/_process?fn=_set", using the param resolved by the preceeding path as the context param. Specific implementation details can be found within SetFunctionHandler.

Note
The set function handler is subclassed from URL Based Function Handler, which means that it shares common behavior amongst some of it’s attributes. See the URL Based Function Handler documentation for more details.
Examples
@Label("Field 1")
@TextBox
private String field1;

@Label("Click to say hello")
@Button
@Config(url = "<!#this!>/../field1/_process?fn=_set&value=Hello World!")
private String actionA;
Supported Query Parameters

Name

Required

Description

value

false

A string containing the state to set into the context param.

url

false

A param path of a param containing the desired state to set into the context param. The param path may be either internal or external.

Set by Rule

The set by rule function handler executes a given rule file from the perspective of a context param.

Usage

The set by rule function handler is invoked by ending the command query with "/_process?fn=_setByRule", using the param resolved by the preceeding path as the context param. Specific implementation details can be found within SetByRuleFunctionalHandler.

Examples
@Model
@Getter @Setter
public static class VFForm {

    ...

    @Button
    @Config(url = "/../_process?fn=_setByRule?rule=sample_rule") (1)
    private String submit;
}
  1. This assumes that the file sample_rule.drl exists in the src/main/resources directory.

Supported Query Parameters

Name

Required

Description

rule

true

The path to the rule file, relative to src/main/resources. The file extension should be omitted.

Update

The update function handler adds a Collection Element Param to a Collection Param.

Usage

The update function handler is invoked by ending the command query with "/_process?fn=_update", using the param resolved by the preceeding path as the context param. Specific implementation details can be found within UpdateFunctionHandler.

Note
The update function handler is subclassed from URL Based Function Handler, which means that it shares common behavior amongst some of it’s attributes. See the URL Based Function Handler documentation for more details.
Examples
@Model
@Getter @Setter
public static class VSSection {

    private int counter;
    private SampleLineItem tmp;

    @Label("Sample Grid")
    @Grid
    private List<SampleLineItem> collection;

    @Label("Add an item")
    @Button
    @Config(url = "<!#this!>/../counter/_get?fn=param&expr=setState((null != state ? state : 0) + 1)")
    @Config(url = "<!#this!>/../tmp/_replace?rawPayload={\"name\": \"Item <!/../counter!>\"}")
    @Config(url = "<!#this!>/../collection/_process?fn=_update&url=/petcareassessmentview/<!#this!>/../tmp")
    private String add;
}

@Model
@Getter @Setter
public static class SampleLineItem {

    @Label("Item Name")
    @GridColumn
    private String name;
}
Supported Query Parameters

Name

Required

Description

value

false

A string containing the value to add into the context param.

url

false

A param path of a param containing the desired state to set into the context param. The param path may be either internal or external.

Search by Example

The search by example function handler performs a search over the targeted domain entity by using the provided param state as search criteria.

Usage

The search by example function handler is invoked by ending the command query with "/_search?fn=example", using the preceeding path as the domain entity over which to search. Specific implementation details can be found within DefaultSearchFunctionHandlerExample.

Examples
@Model
@Getter @Setter
public static class VFForm {

    @Label("Search Term")
    @TextBox(postEventOnChange = true)
    private String searchTerm;

    @Label("Test Search Results")
    @Button
    @Config(url = "/p/person/_search?fn=example&rawPayload={\"firstName\": \"<!/../searchTerm!>\"}") (1)
    private String submit;
}
  1. The config will execute a search within the database that returns a result set of all documents having a firstName matching the state of searchTerm.

Note
The observant will note that we aren’t actually doing anything with the data in this particular example. This is intentional, as it is a simplified example.
Supported Query Parameters

Name

Required

Description

aggregate

false

An aggregate query (if applicable)

page

false

An integer representing the starting page number

pageSize

false

An integer for the number of elements to display per page

projection.alias

false

A string representing the domain entity id used to determine the resulting object type

rawPayload

false

A JSON String object that provides search criteria.

sortBy

false

A "key:value" string with the key representing the field over which to sort and the value representing the sort direction. Supported values are determined by the org.springframework.data.domain.Direction (asc, desc, etc.)

Setting example search criteria

The search criteria used to perform an example search can be provided by passing an example object. The example object is given as an argument (rawPayload) that matches the type of the object being searched. In other words, if searching over a core domain entity with an alias of person and that domain entity is built from a Person Java object, then rawPayload should also be of the type Person.

All of the data contained within the example object is then used by the repository implementation to obtain a list of search results, matching results similarly to how the where clause of a typical database query would.

Note
Wherever possible, the searching features will be delegated to the native repository implementation. For example, the default repository implementation (MongoDB) simply passes the example criteria into MongoDB and lets MongoDB do what it does best.
Providing rawPayload

One way to provide rawPayload is to simply pass a JSON string as a query parameter in the command query. This is seen in the example above. Using this method, it is possible to provide dynamic values using the Path Variable Resolver features.

Tip
The rawPayload that is specified here as a query parameter is actually much more complex in how it is set. Consequently, there are other ways to provide it to a function handler. For more understanding on how to provide rawPayload to a command query, see Sending a payload with a Command
Search by Lookup

The search by lookup function handler performs a search over the targeted domain entity by using the search criteria provided as query parameters.

Usage

The search by lookup function handler is invoked by ending the command query with "/_search?fn=lookup", using the preceeding path as the domain entity over which to search. Specific implementation details can be found within DefaultSearchFunctionHandlerLookup.

Examples
@Label("Choose a Person")
@Values(url = "/p/person/_search?fn=lookup&projection.mapsTo=code:id,label:name")
@ComboBox(postEventOnChange = true)
private Long chosenPersonId;

@Label("Choose a Woman")
@Values(url = "/p/person/_search?fn=lookup&where=person.sex.eq('female')&projection.mapsTo=code:id,label:name")
@ComboBox(postEventOnChange = true)
private Long chosenWomanId;

@Label("Choose a Gender")
@Values(url = "/p/staticCodeValue/_search?fn=lookup&where=staticCodeValue.paramCode.eq('/gender')")
@ComboBox
private String chosenGender;
Supported Query Parameters
Name Required Description

where

false

A QueryDSL filter statement used to determine whether or not a record of data should be contained in the final result set.

orderby

false

A string containing the name of the field by which to group and direction to sort the result set by. (e.g. "field.asc()) Possible sort directions are: .asc(), .desc().

fetch

false

An integer value representing the total number of records that should be returned.

aggregate

false

An aggregate query (if applicable)

projection.mapsTo

false

A key/value comma-separated string with the keys being the fields of ParamValue, and the values being the field to map. (e.g. "code:id,label:name"). Not required for staticCodeValue.

Sorting the result set is achievable at different layers, using in memory sorting or the native repository sorting provided by a database/repository implementation.

In Memory Sorting

Sorting (ascending or descending) happens in memory using the Collections' comparator interface on the property configured with orderby clause (for staticCodeValue as well as dynamic domain entities), only when the orderBy clause does not start with domain entity alias.

"/p/person/_search?fn=lookup&where=person.sex.eq('female')&orderBy=name.asc()"

would perform an in memory sort of all the females by their name field in ascending order, because the orderBy.asc() value is name.asc().

Threshold

A sortThreshold is defined for sorting to prevent potential memory leaks. If sorting is attempted on a collection with size greater than the configured integer sortThreshold, the sorting will simply be skipped.

Overriding the sort threshold number

The sortThreshold can be configured by configuring the property search.lookup.inMemory.sortThreshold (default: 500) with an integer value in application.yml.

Handling sorts greater than the defined sort threshold

In some cases, it may be desirable to throw an exception when the sorting threshold has been violated. Nimbus supports this natively by configuring the property search.lookup.inMemory.exceptionIfOverSortThreshold (default: false) in application.yml. Setting this value to true will result in a FrameworkRuntimeException when attempting to sort such a collection.

Repository Sorting

When the orderby clause starts with the domain alias, the sorting will occur inside the configured model repository’s query execution. In other words, since the default configured model repository uses MongoDB, the sorting would occur within the MongoDB query execution layer.

"/p/person/_search?fn=lookup&where=person.sex.eq('female')&orderBy=person.name.asc()"

would perform a sort within MongoDB of all the females by their name field in ascending order, because the orderBy.asc() value is person.name.asc().

Special Use Case: staticCodeValue

The domain staticCodeValue is a special use case within the Nimbus Framework.

Search by Query

The search by query function handler performs a search over the targeted domain entity by using the provided query data.

Usage

The search by query function handler is invoked by ending the command query with "/_search?fn=query", using the preceeding path as the domain entity over which to search. Specific implementation details can be found within DefaultSearchFunctionHandlerQuery.

Examples
@Label("Pets")
@Grid(onLoad = true)
@Path(linked = false)
@Config(url = "<!#this!>/.m/_process?fn=_set&url=/p/pet/_search?fn=query&where=pet.ownerId.eq(<!/../.m/id!>)") (1)
private List<PetLineItemOwnerLanding> pets;
  1. The right hand side of this query performs the search by query, with a where clause that returns a result set of all objects in the pet table that have an ownerId matching the resolved value of <!/../.m/id!>.

Supported Query Parameters

Name

Required

Description

aggregate

false

An aggregate query (if applicable)

fetch

false

An integer value representing the total number of records that should be returned.

orderby

false

A string containing the name of the field by which to group and direction to sort the result set by. (e.g. "field.asc()) Possible sort directions are: .asc(), .desc().

page

false

An integer representing the starting page number

pageSize

false

An integer for the number of elements to display per page

projection.alias

false

TODO

projection.mapsTo

false

TODO

sortBy

false

A "key:value" string with the key representing the field over which to sort and the value representing the sort direction. Supported values are determined by the org.springframework.data.domain.Direction (asc, desc, etc.)

where

false

A query dsl statement that contains the desired matching conditions. This clause should always begin with the domain alias

3.5.3. Custom Function Handlers

The Nimbus framework provides the ability for developers to define custom function handlers.

Defining custom function handler logic

The logic for a function handler resides in the abstract execute method of a FunctionHandler implementation.

Custom function handler implementation
public class CustomFunctionHandler<T, S> implements FunctionHandler<T, S> {

    @Override
    public S execute(ExecutionContext eCtx, Param<T> actionParameter) {
        // Add custom function handler logic here
        return null;
    }
}
Defining a custom function handler bean

The implementation must be constructed as a Spring bean and injected into the application context to be recognized by the framework.

Bean configuration
@Configuration
public class PetClinicExtensionConfig {

    @Bean(name="petclinic._process$execute?fn=_custom")
    public CustomFunctionHandler<?, ?> customFunctionHandler(BeanResolverStrategy beanResolver) {
        return new CustomFunctionHandler<>(beanResolver);
    }
}
Bean naming conventions for function handlers

The naming convention for all function handlers must follow the pattern:

<app-id>.<action><behavior>?fn=<function-handler-name>

Examples
  • default._process$execute?fn=_setByRule

  • default._get$execute?fn=_custom

The <app-id> of default is used for all default implementations within the framework. Custom implementations can and should use their own unique <app-id> (e.g. petclinic). All function handler implementations (including default) are able to be overridden by simply overriding the desired Spring bean.

Invoking custom function handlers

Invoking a custom function handler is no different than invoking any other function handler. Simply provide the Function Handler ID as the final part of the command query.

@Model
@Getter @Setter
public static class VFForm {

    @Button
    @Config(url = "/page/tile/section/form/_process?fn=_custom") (1)
    private String submit;
}
  1. The CustomFunctionHandler logic would be invoked with actionParameter resolving to the param located at "/page/tile/section/form".

3.5.4. URL Based Function Handler

URL Based Function handlers are function handlers that perform an operation over a single provided value.

Usage

The URL Based Function Handler is an abstract support class, extended by several other function handlers. It is not invoked directly. As it is expected to be fairly common across function handlers, it’s details are documented here.

Supported Query Parameters

Name

Required

Description

value

false

A string containing the state to perform an operation over

url

false

A param path of a param containing the desired state to perform an overation over. The param path may be either internal or external.

Providing the state to add

The hierarchy of how the state is determined and passed to subclasses is decided by URLBasedAssignmentFunctionHandler and is prioritized as follows:

  1. value attribute

  2. url attribute

A note regarding the url attribute

When using the url attribute, the state retrieval is considered to be external state retrieval if the value for url begins with "/p/". External state retrieval is also commonly referred to as being cross domain.

Otherwise, the path is treated as internal state retrieval, which means that the path is simply relative to the current context param.

3.6. Sending a payload with a Command

The rawPayload attribute of Command is used in several different client facing implementations to input data into Nimbus for processing. Primarily used as an argument, it is a unversal o

3.6.1. Send rawPayload as a query parameter

Simply provide rawPayload as a query parameter with a JSON string value representing the desired value to provide within the command query statement.

3.6.2. Send rawPayload as HTTP Payload content

The WebActionController offers native payload support for some HTTP operations such as POST or PUT. One such example can be seen with the following button implementation

@Form
private VFForm vfForm;

@MapsTo.Type(Person.class)
@Getter @Setter
public static class VFForm {

    @Label("Search Term")
    @TextBox
    @Path("/firstName")
    private String searchTerm;

    @Button(type = Button.Type.submit) (1)
    @Config(url = "<!#this!>/../_replace") (2)
    private String submit;
}
  1. On click of this button on the UI, the browser will send an HTTP POST with a JSON payload representing the form data. The WebActionController knows to convert this data into the rawPayload, which can then be forwarded on to subsequent commands.

  2. The command generated by this query will have it’s rawPayload set and consequently will use the _replace behavior to set the state of vfForm to it.

Note
The submit button behavior occurs since the default UI behavior for Button.Type.submit will post it’s parent’s encapsulated form data.

See the WebActionController implementation for more details.

3.6.3. Priority of how rawPayload is set

The priority of how rawPayload is used in the search by example handler is:

  1. Query param

  2. HTTP payload

4. Extensions

This section explains instructions regarding popular extensions and hooks that the framework exposes that a developer might be interested in using. The examples in this will typically be quite complex and expect the reader to have a thorough understanding of the Nimbus Framework before attempting to implement these features.

4.1. Importing data files to an entity

The framework provides support for the ability to upload data files and directly map the contained data into an associated a Nimbus configured Java object. The framework exposes a FileImportGateway object that can be used to process the data file uploaded alongside the traditional HttpServletRequest.

In this way, clients can configure their application to allow a user to upload a data file and process its contents into a framework domain entity, allowing any rules or process flows to execute over it.

4.1.1. Exposing a controller to capture the uploaded data file

The framework does not expose a native controller to handle file uploads, instead allowing the client to provide it’s own implementation. A sample can be seen below:

@Autowired
private FileImportGateway fileImportGateway;

@RequestMapping(value = "/**/p/event/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE,  produces = "application/json", method = RequestMethod.POST)
public Object handleUpload(HttpServletRequest req, @RequestParam("pfu") MultipartFile file, @RequestParam("domain") String domain) {
    Object obj = fileImportGateway.doImport(req, domain, file);
    Holder<Object> output = new Holder<>(obj);
    return output;
}

4.1.2. Supported file types

The default implementation DefaultFileImportGateway provides support for the following data types:

CSV

TabularDataFileImporter handles all .csv typed files.

Excel

ExcelFileImporter handles importing .xls and .xlsx file types.

  • Configurable Parser/Conversion Properties exposed via query parameters.

Importing other types of files

Other types of files can be imported by the configured FileImportGateway by providing custom Importer implementations.

public class SpecialFileImporter implements Importer {

    @Override
    public <T> void doImport(Command command, InputStream stream) {
        ...
    }

    @Override
    public boolean supports(String extension) {
        return ".special".equals(extension);
    }
}

@Bean
public SpecialFileImporter specialFileImporter() {
    return new SpecialFileImporter();
}

4.1.3. Configurable Parser/Conversion Properties

The framework uses Univocity to parse data files. Univocity offers a number of configurable properties, such as the ability to define a title row. These configurable properties are exposed via the command query dsl, as below:

/**/event/upload?domain=mypojo&PROPERTY_KEY=VALUE

4.1.4. Error Handling

Configure which type of error handling strategy should be used through the command URL:

**/event/upload?domain=mypojo&errors=STRICT

  • Types

    • SILENT (default): Silently continues processing when an error parsing row data occurs

    • STRICT: Rethrows the thrown data parsing exception when an error parsing row data occurs

More specific error handling may be determined by the specific Importer implementation.

4.1.5. POJO Writing Strategies

Configure which type of writing strategy should be used when converting a record of data into a POJO through the command URL:

**/event/upload?domain=mypojo&writeStrategy=MODEL_REPOSITORY

  • Types

    • COMMAND_DSL (default): Use Command DSL’s _new implementation to write each record of row data processed

    • MODEL_REPOSITORY: Use the provided domain’s ModelRepository implementation to write each record of row data processed

5. Appendix

5.1. Config Annotations

This section contains instructional reference material for each of the config annotations available within the Framework. Use this section when needing to identify specific features of a config annotation.

For more information about how config annotations are applied within the framework, see Configuration.

5.1.1. Conditional Config Annotations

This section covers the conditional behavior that can be applied to params. In this context, conditional behavior refers to performing a set of actions based on a given condition. The condition is evaluated by using the powerful capabilities of SpEL (Spring Expression Language).

Typically this conditional behavior is evaluated from the context of the conditionally decorated param (the param that is annotated with a conditional annotation), meaning that the condition will inspect the decorated param object to infer if the condition should be true or false.

The following example shows what a conditional configuration might look like:

@SampleConditional(when = "state == 'YES'")
@Radio(postEventOnChange = true)
@Values(YesNo.class)
private String question;

The when condition is effectively stating, when question.getState().equals("YES"). In this case, since question is the decorated param, we are evaluating from the context of question. This is why state is understood in the SpEL condition state == 'YES'.

Note
In this scenario @SampleConditional is not a real annotation understood by the framework, but it illustrates the idea that when this particular condition is true, we should perform some sort of behavior. We defer that logic to State Event Handlers.
AccessConditional

@AccessConditional allows to control the access of the specific area/functionality within the application based on User’s assigned role(s) and permission(s).

The logic for @AccessConditional can be found in AccessConditionalStateEventHandler. The handler logic will be executed during the following framework events:

  • OnStateLoad

Example:

@AccessConditional(whenAuthorities="?[#this == 'entity_assign'].empty", p=Permission.HIDDEN)
private Section_EntityAssignment vsEntityAssignment;

Above configuration would hide the entity assignment section for user(s) who do not have access to "entity_assign" action.

ActivateConditional

@ActivateConditional is an extension capability provided by the framework. The annotation is used to conditionally activate/deactivate the param based on a SpEL condition.

To dectivate a param means to mark the param the enabled and visible properties as well as nullify the state of the target param.

Tip
It is common to misuse @ActivateConditional when @EnableConditional or @EnableConditional would be a better choice. While this annotation is similar to using @EnableConditional and/or @VisibleConditional, it should be noted that @ActivateConditional nullify the state of the target param.

The logic for @ActivateConditional can be found in ActivateConditionalStateEventHandler. The handler logic will be executed during the following framework events:

  • OnStateLoad

  • OnStateChange

ActivateConditional.java
@CheckBox(postEventOnChange=true)
@ActivateConditional(when="state != 'true'", targetPath="/../sectionG_skip")
private String skipSectionG;

private sectionG_Skip sectionG_skip;

@Radio(postEventOnChange=true)
@Model.Param.Values(value=YNType.class)
@ActivateConditional(when="state == 'No'", targetPath="/../q3Level2")
private String answerYesOrNo;

private Q3 q3Level2;

@CheckBoxGroup(postEventOnChange=true)
@Model.Param.Values(value=Days.class)
@ActivateConditional(when="state == new String[]{'Sunday'}",targetPath="/../../../sundayDeliverySection")
private String[] deliveryDays;

/*Check if selection contains one or more specific values*/
@CheckBoxGroup(postEventOnChange=true)
@Model.Param.Values(value=Days.class)
@ActivateConditional(when="state != null && state.?[#this=='Sunday'] != new String[]{}",targetPath="/../../../sundayDeliverySection")
private String[] deliveryDays;

@CheckBoxGroup(postEventOnChange=true)
@Model.Param.Values(value=VisitCount.class)
@ActivateConditional(when="state != null && state.length > 2",targetPath="/../../section")
private String[] visits;
ConfigConditional

@ConfigConditional is an extension capability provided by the framework. The annotation is used to conditionally execute @Config calls based on a SpEL based condition.

The logic for @ConfigConditional can be found in ConfigConditionalStateChangeHandler. The handler logic will be executed during the following framework events:

  • OnStateChange

ConfigConditional.java
@ConfigConditional(
    when = "state == 'Completed'", config = {
    @Config(url="<!#this!>/../state/_update?rawpayload=\"Closed\""),
    @Config(url="/p/dashboard/_get")
})
private String status;
Note
In the above example , whenever there is statechange of status and the status is changed to Completed, the Configs will be executed.
EnableConditional

@EnableConditional is an extension capability provided by the framework. The annotation is used to conditionally activate/deactivate the param based on a SpEL condition.

The logic for @ConfigConditional can be found in ConfigConditionalStateChangeHandler. The handler logic will be executed during the following framework events:

  • OnStateChange

EnableConditional.java
@EnableConditional(when="state == 'hooli'", targetPath="../enable_p2")
private String enable_p1;

private String enable_p2;
ExpressionConditional

@ExpressionConditional is a very versatile conditional annotation that can be used to apply any logic that is able to be crafted using SpEL expressions.

The logic for @ExpressionConditional can be found in ExpressionConditionalStateEventHandler. The handler logic will be executed during the following framework events:

  • OnStateLoad

  • OnStateChange

@ExpressionConditional(when="null == state && onLoad()", then="setState(T(java.time.LocalDate).now())")
private LocalDate initDateOnLoad;
Caution
The observant will notice that @ExpressionConditional is versatile in that it could be used to perform the same logic achieved by several other framework defined conditional annotations. Ideally it should not be used when another conditional annotation is available to achieve the same behavor.
MessageConditional

@MessageConditional is a conditional annotation that allows the ability to conditionally assign of one or more Message objects to a param.

The logic for @MessageConditional can be found in MessageConditionalHandler. The handler logic will be executed during the following framework events:

  • OnStateLoad

  • OnStateChange

@Label("Do you like apples?")
@Radio(postEventOnChange = true)
@Values(YesNo.class)
@MessageConditional(when = "state =='Yes'", targetPath = "/../q2", message = "'Apple lovers do not like oranges!'")
private String q1;

@Label("Do you like oranges?")
@TextBox
private String q2;
ValidateConditional

@ValidateConditional is used to conditionally set validations that should appear for a param based on a SpEL condition. This annotation can be triggered for multiple conditions, if necessary.

The logic for @ValidateConditional can be found in ValidateConditionalStateEventHandler. The handler logic will be executed during the following framework events:

  • OnStateLoad

  • OnStateChange

Configuring Conditional by Group

@ValidateConditional works by first evaluating the when attribute by means of a SpEL condition. When the when condition is true, the framework will attempt to identify a subset of params with Validation constraints and apply validation logic to those params. See the following example:

ValidateConditional Sample 1
@ValidateConditional(when = "state == 'dog'", targetGroup = GROUP_1.class)
@TextBox(postEventOnChange = true)
private String petType;

@NotNull
@TextBox
private String petName;

@NotNull(groups = { GROUP_1.class })
@TextBox
private String dogFood;

@NotNull(groups = { GROUP_2.class })
@TextBox
private String catFood;
ValidateConditional Sample 1 - Results

In the above example, assuming the state of petType is "dog", the validations that will be applied are: petName and dogFood.

  • petName - There are no groups associated with petName, hence it is seen as a static validation and always applied.

  • dogFood - GROUP_1.class is present within the @NotNull’s `groups attribute, and this is matching petType’s `@ValidateConditional’s `targetGroup attribute(GROUP_1.class) and is applied.

The validations that will not be applied are: catFood.

  • catFood - While catFood has an entry in @NotNull’s `groups attribute(GROUP_2.class), it is not matching @ValidateConditional’s `targetGroup attribute(GROUP_1.class).

Specifying a Validation Group

As seen in the previous example, there may be many params decorated with javax.validation.constraints. The framework needs to uniquely identify which validations should be applied when the when condition is true. To handle this, the groups attribute will be used as it is supplied by of all javax.validation.constraints annotation classes. The final subset of params where validations will be applied will be composed of only those whos groups attribute contains the @ValidateConditional targetGroup attribute.

@ValidateConditional’s `targetGroup parameter is simply a marker interface of type ValidationGroup to be used for identification purposes by the framework to identify the subset of params.

Note
  • Different groups should be applied based on multiple when conditions

  • Multiple groups should be applied for the same when condition

Validation Group Identity Classes

In the previous example, we used targetGroup = GROUP_1.class. GROUP_1.class is an identity class that implements the ValidationGroup interface and is used by the framework to identify javax.validation.constraints annotatons that should be applied.

Any implementation that implements the ValidationGroup interface may be used as a group identity class. For convenience, a set of identify class implementations have been defined within @ValidateConditional as GROUP_X.class, where 0 <= X <= 29.

Note
If additional marker classes are needed, simply create a new implementation of ValidationGroup and use that class in the targetGroup attribute as well as the corresponding param’s javax.validation.constraints annotation.
Controlling Scope

The framework has provides the ability to define a ValidationScope as part of @ValidateConditional’s `scope to provide control over which params should be considered for conditional validation. The following scopes are available:

@ValidateConditional Scopes
Name Description

SIBLING

Applies validations to sibling params relative to the target param.

CHILDREN

Applies validations to children params relative to the target param.

Note
Scopes are recursive in that all nested params underneath params located in the scopes identified above would also have conditional validations applied.
Specifying Target Path

There may be a need to apply conditional validations to one or many different paths based on some when condition decorating a single param. The attribute targetPath can be used to target one or more paths to apply conditional validation to when a corresponding when condition is true. Consider the scenario below:

@Model @Getter @Setter
public static class Section1 {

    @NotNull(groups = { GROUP_1.class })
    @Calendar
    private String dateOfBirth;

    ...
}
@Model @Getter @Setter
public static class Section2 {

    @NotNull(groups = { GROUP_1.class })
    @Calendar
    private String neuterDate;

    ...
}
@ValidateConditional(when = "state == 'dog'", targetGroup = GROUP_1.class, targetPath = { "../section1", "../section2" }, scope = ValidationScope.CHILDREN)
@TextBox(postEventOnChange = true)
private String petType;

private Section1 section1;

private Section2 section2;

In this scenario, the configuration for petType mandates that both section1 and section2 would be targetted and all children params of these sections (recursive) would have conditional validation applied if GROUP_1.class is present on the Constraint annotation.

In this case, both dateOfBirth and neuterDate would have conditional validation applied.

Note
Similar behavior could have been achieved by omitting the targetPath attribute and using ValidationScope.SIBLING. This was simply an example to portrait the flexibility of targetPath and scope.
ValuesConditional

@ValuesConditional is an extension capability provided by the framework. The annotation is used to conditionally set the @Values configuration for a dependent field based on a SpEL condition.

The logic for @ValuesConditional can be found in ValuesConditionalStateEventHandler. The handler logic will be executed during the following framework events:

  • OnStateLoad

  • OnStateChange

Consider the following sample defined Values:

SampleValues.java
// Sample Values Implementations
public static class SRC_FOODS_ALL implements Source {
    @Override
    public List<ParamValue> getValues(String paramCode) {
        final List<ParamValue> values = new ArrayList<>();
        values.add(new ParamValue("Generic Food 1", "Generic Food 1"));
        values.add(new ParamValue("Generic Food 2", "Generic Food 2"));
        return values;
    }
}
public static class SRC_FOODS_DOG implements Source {
    @Override
    public List<ParamValue> getValues(String paramCode) {
        final List<ParamValue> values = new ArrayList<>();
        values.add(new ParamValue("Dog Food 1", "Dog Food 1"));
        return values;
    }
}
public static class SRC_FOODS_CAT implements Source {
    @Override
    public List<ParamValue> getValues(String paramCode) {
        final List<ParamValue> values = new ArrayList<>();
        values.add(new ParamValue("Cat Food 1", "Cat Food 1"));
        return values;
    }
}

Given a defined set of Values that we can assign using the @Values annotation, we can explicitly define conditions to set a dependent field’s values.

ValuesConditional.java
@Model
@Getter @Setter
public static class StatusForm {

    @ValuesConditional(target = "../petFoodSelection", condition = {
            @Condition(when = "state=='dog'", then = @Values(SRC_FOODS_DOG.class)),
            @Condition(when = "state=='cat'", then = @Values(SRC_FOODS_CAT.class)),
        }
    )
    @TextBox(postEventOnChange = true)
    private String petType;

    @Radio
    @Values(SRC_FOODS_ALL.class)
    private String petFoodSelection;
}

In this example, petType is the field and petFoodSelection is the dependent field. We set petFoodSelection to contain the defaults ["Generic Food 1", "Generic Food 2"] initially and conditionally define those values when petType's state is "dog" or "cat".

When the state of petType is "dog", then the Values for petFoodSelection will be ["Dog Food 1"]. Similarly when the state of petType is "cat", then the Values for petFoodSelection will be ["Cat Food 1"].

Conceptually speaking, we are pushing the updates of Values to the dependent field whenever the state of the annotated field loads or is changed.

VisibleConditional

@VisibleConditional is an extension capability provided by the framework. The annotation is used to conditionally set the UI visibility of a param based on a SpEL condition.

The logic for @VisibleConditional can be found in VisibleConditionalStateEventHandler. The handler logic will be executed during the following framework events:

  • OnStateLoad

  • OnStateChange

VisibleConditional.java
@VisibleConditional(when="state == 'hooli'", targetPath="../p2")
private String p1;

private String p2;

5.1.2. Core Config Annotations

ConceptId
ConceptId.java
@TextBox(postEventOnChange = true)
@ConceptId(value = "IOT1.1.1")
@Label(value = "If Other, provide reason")
private String otherReason;
Config

@Config is core annotation that handles the execution of a Nimbus Command DSL statement.

Config.java
@Config(url = "/pageAddEditGoal/tileEditGoal/sectionEditGoal/goalDetailsForm/_nav?pageId=pageCarePlanSummary")
@Button(type = Button.Type.PLAIN)
private String cancel;
ConfigNature
Ignore

Framework persists the data objects of a class in database using @Repo by serializing the class and associating a version number that is called seriaVersionUID. However, if we do not want the framwework to serialize for time being, we can use @Ignore component of ConfigNature class. The following example shows that: -

StartsWith.java
@Domain(value="patient", includeListeners={ListenerType.persistence})
@Repo(value=Database.rep_mongodb, cache=Cache.rep_device)
@ToString
public class Patient extends IdString {

    @Ignore
    private static final long serialVersionUID = 1L;

    }
Domain

Core Config configuration @Domain annotation persists data.

Core config @Domain will always be followed by @Repo that will specify the way data is persisted.

includeListeners={ListenerType.persistence, ListenerType.update} of @Domain specifies that the data will be persisted.
value=Database.rep_mongodb of @Repo specifies that a class with @Domain annotation will use MongoDb for persistence.

Domain.java
@Domain(value="cmcase", includeListeners={ListenerType.persistence, ListenerType.update})
@Repo(value=Database.rep_mongodb, cache=Cache.rep_device)
public class CMCase extends IdString {
}
Note
Please read @Repo for mroe information regarding @Repo annotation.
DomainMeta

Similar to ViewStyle component, DomainMeta component is used to define an ANNOTATION_TYPE level annotation that is used with few view config annotations, as follows: -

  • @ConceptId

@Retention(RetentionPolicy.RUNTIME)
    @Target(value={ElementType.ANNOTATION_TYPE})
    @Inherited
    public @interface DomainMeta {

    }

Here is an example: -

@Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.FIELD})
    @DomainMeta
    public @interface ConceptId {
        String value() default "";
    }
Execution

Execution is inherited annotation for @Config and @KeyValue. It is not currently being directly used, but is there for hieracrchial purposes.

More documentation will be added here if Execution expands or is directly used.

Initialize
Initialize.java
@Section
@Initialize
@Config(url="/vpAdvancedMemberSearch/vtAdvancedMemberSearch/vsMemberSearchCriteria/vfAdvMemberSearch/_process?fn=_setByRule&rule=updateadvmbrsearchcriteria")
private VSMemberSearchCriteria vsMemberSearchCriteria;
MapsTo
Path
Path.java
@Path()
private Long version;
Type
Type.java
@MapsTo.Type(CMCase.class)
public static class SectionEditGoal  {
    }
Note
If it is not mapped (@Mapped), an exception will be thrown.
If no exception is thrown, defaults to silent.
Model

@Model is a marker annotation for the framework to acknoledge an class declaration as a model entity. Useful when created "nested entities".

Model is similar to Domain, except that domain is seen as the "root" entity, or the topmost parent in a nested entity. Typically, model is expected to be used on nested class declarations. See the following example:

Parent.java
@Domain("parent")
@Getter @Setter @ToString
public class Parent {

    private Child1 child;
}
Child1.java
@Model
@Getter @Setter @ToString
public class Child1 {

    private Child2 child;
}
Child2.java
@Model
@Getter @Setter @ToString
public class Child2 {

}

In this example, the object hierarchy is Parent > Child1 > Child2, where Child1 and Child2 are decorated with @Modal.

ParamContext

@ParamContext is used to set the contextual properties of a field during the OnStateLoad event (e.g. visible, enabled).

The intent of @ParamContext is to be able to decorate fields to define default contextual behavior. For example:

SampleView.java
public static class SampleView {

   @TextBox
   @ParamContext(enabled=false, visible=false)
   private String myTextBox;
}

In this scenario we have configured the contextual values for enabled and visible to be false for myTextBox. These values will be set during the OnStateLoad event and myTextBox consequently will not be enabled or visible when the corresponding page is rendered.

Tip
@ParamContext can also be defined on annotations. In these scenarios when a field is decorated with that annotation, then the handler for @ParamContext will execute. This may be useful when building a framework extension.
PrintConfig

Defines print configuration that should be applied when invoking the print action on a view component.

Decoratable Fields

@PrintConfig currently is currently supported for the following components:

Sample Configuration
@Button(style = Button.Style.PRINT, printPath = "/domain/page/tile/modal")
@PrintConfig(autoPrint = false)
private String print;
SearchNature
StartsWith

This component is ued to validate a field. The wilcard attribute determines the validation criteria for a field. This is a server side component.

StartsWith.java
@NotNull
@StartsWith
@Label(value = "First Name")
private String firstName;
Note
The example will always search the first name that starts with anything, represented with the default value. A specific search criteria can be specified using wildcard attribute of @StartsWith.
Repo

@Repo is used to determine where the data will be persisted. It is always used along with @Domain.

rep_mongodb

The following example shows how data is persisted/ retrieved using MongoDB as a source.

Repo.java
@Domain(value="cmassessment", includeListeners={ListenerType.persistence})
@Repo(alias="cmassessment",value=Database.rep_mongodb, cache=Cache.rep_device)
@Getter @Setter
public class CMAssessment extends IdString {
}
Rule

@Rule allows its decorated field a mechanism for triggering one or more rule definitions during its OnStateLoad and OnStateChange events.

SampleRuleEntity.java
@Domain(value="sample_rule_entity", includeListeners={ListenerType.persistence})
@Repo(Database.rep_mongodb)
@Getter @Setter
public class SampleRuleEntity {

    // Execute the rule at "rules/sample_increment" during the OnStateLoad and
    // OnStateChange events of ruleParam.
    @Rule("rules/sample_increment")
    private String rule_param;
}

By default, the framework provides support for firing all rules for a given domain entity. That is, for the SampleRuleEntity.java above we might have a rule file defined as sample_rule_entity.drl which will be automatically fired by naming convention.

For cases where additional configuration for other rules is needed, @Rule can be used.

Values

Values provides a mechanism for populating a fields values property. This can be used by a number of components to perform such functions as: define a set of selections for radio buttons and checkboxes, or populating a dropdown list.

Source

The Source is a simple abstraction for providing a contract between implementations to provide data to the framework.

Source is exclusively used for @Values.

Source.java
public static interface Source {
    public List<ParamValue> getValues(String paramCode);
}

We can use this to define several different types of values providers. A simple static Source implementation is shown below:

SampleStaticSource.java
public class SampleStaticSource implements Source {
    public List<ParamValue> getValues(String paramCode) {
        List<ParamValue> values = new ArrayList();
        values.add(new ParamValue("sample.value.1", "Sample Value 1"));
        return values;
    }
}
EnableAPIMetricCollection

@EnableAPIMetricCollection allows its decorated class, method level logging mechanism with log entries for method - entry, exit, arguments and response. It can be configured with log level of info or debug for arguments and response.

EnableAPIMetricCollection.java
@EnableAPIMetricCollection(args=LogLevel.info,resp=LogLevel.info)
@Getter(value=AccessLevel.PROTECTED)
public class SampleMetricLog {

    public Object calculate(String input) {
        // some logic
    }
}

By Default, the logging level for arguments & response is debug and info for entry and exit. Also, method exit logs the execution time of the method in milliseconds.

Client applications can annotate the custom config beans with @EnableAPIMetricCollection to enable a similar logging mechanism.

EnableAPIMetricCollection Attributes
Name Type Default Description

args

LogLevel

debug

LogLevel for logging method arguments

resp

LogLevel

debug

LogLevel for logging method response

5.1.3. View Config Annotations

Accordion

Accordion groups a collection of contents in tabs.

Allowed Parent Components

Form, Section

Allowed Children Components

AccordionTab

Sample Configuration
@Accordion
private VAQuestionnaire vaQuestionnaire;

@Model
@Getter @Setter
public static class VAQuestionnaire {

    @AccordionTab
    private VATTab1 vatTab1;

    @AccordionTab
    private VATTab2 vatTab2;

    @Model
    @Getter @Setter
    public static class VATTab1 { ... }

    @Model
    @Getter @Setter
    public static class VATTab2 { ... }
}
AccordionTab

AccordionTab contains a collection of contents for an individual section within an Accordion.

Allowed Parent Components

Accordion

Allowed Children Components

Accordion Tab supports all of the same children that are supported as children by Form.

Sample Configuration
@AccordionTab
private VATTab1 vatTab1;

@Model
@Getter @Setter
public static class VATTab1 {

    @TextBox
    private String txt1;

    @ButtonGroup
    private VBGMain vbgMain;
}
ActionTray

ActionTray is an extension to the standard button input element with icons and theming.

Allowed Parent Components

Domain

Note
ActionTray can only be contained within a View Layout definition.
Allowed Children Components

Button

Sample Configuration
@ActionTray
private VSActionTray vsActionTray;

@Model
@Getter @Setter
public static class VSActionTray {

    @Label(value = " ")
    @Config(url = "...")
    @Button(imgSrc = "fa-id-card", cssClass = "icon btn btn-icon mr-0", title = "Record a Contact")
    private String contactRecord;
}
Autocomplete

Autocomplete is a text input component.

Allowed Parent Components

Form, Section

Allowed Children Components

None. @Autocomplete should decorate a field having a simple type.

Sample Configuration
@Autocomplete(display="label", postEventOnChange = true, minLength = 2)
@Config(url = "/p/owner/_search?fn=lookup&where=owner.firstName.containsIgnoreCase('<!autocompletesearchvalue!>')&projection.mapsTo=code:id,label:firstName")
private String autocompleteField;
Button

Button is an extension to the standard button input element with icons and theming.

Allowed Parent Components

ActionTray, ButtonGroup, Form, Grid, Section

Allowed Children Components

None. @Button should decorate a field having a simple type.

Sample Configuration
@Config(url = "...")
@Button(type = Button.Type.DESTRUCTIVE)
private String delete;
ButtonGroup

ButtonGroup contains a collection of Button components.

Allowed Parent Components

Form, Section

Allowed Children Components

Button

Sample Configuration
@ButtonGroup(cssClass="text-sm-right")
private VBGGoals vbgGoals;

@Model
@Getter @Setter
public static class VBGGoals {

    @Button
    private String btn1;

    @Button
    private String btn2;
}
Calendar

Calendar is an input component to select a date.

Allowed Parent Components
Allowed Children Components

None. @Calendar should decorate a field having a simple type of a supported date type:

  • LocalDate

  • LocalDateTime

  • ZonedDateTime

  • Date

Sample Configuration
@Path
@Calendar(postEventOnChange=true, showtime=true, hourFormat="24")
private LocalDate startDate;

@Path
@Calendar(postEventOnChange=true, timeOnly=true)
private LocalDateTime startDate;
CardDetail

CardDetail is a flexible container component.

Allowed Parent Components

Accordion, AccordionTab, CardDetailsGrid, Section

Allowed Children Components

CardDetail.Header, CardDetail.Body

Sample Configuration
@CardDetail(title="Member Overview", cssClass="contentBox right-gutter bg-alternate mt-0")
private VCDMember vcdMember;

@Model
@Getter @Setter
public static class VCDMember {

    @CardDetail.Header
    VCDHMain header;

    @CardDetail.Body
    VCDBMain body;

    @Model
    @Getter @Setter
    public static class VCDHMain { ... }

    @Model
    @Getter @Setter
    public static class VCDBMain { ... }
}
Note
contentBox right-gutter bg-alternate mt-0 overrides the default cssClass specified for the @CardDetail component.
CardDetail.Body

CardDetail.Body is a container component within CardDetail that contains the main content.

Allowed Parent Components

CardDetail

Allowed Children Components

FieldValue, Link, Paragraph, StaticText

Sample Configuration
@CardDetail.Body
VCDBMain body;

@Model
@Getter @Setter
public static class VCDBMain {

    @FieldValue
    private String value1;
}
CardDetail.Header

CardDetail.Header is a container component within CardDetail that contains the header content.

Allowed Parent Components

CardDetail

Allowed Children Components

ButtonGroup, FieldValue, Paragraph

Sample Configuration
@CardDetail.Header
VCDHMain header;

@Model
@Getter @Setter
public static class VCDHMain {

    @FieldValue
    private String value1;
}
CardDetailsGrid

CardDetailsGrid contains a collection of CardDetail components.

Allowed Parent Components

Accordion, Section

Allowed Children Components

A field decorated with @CardDetailsGrid should be a collection/array with a defined type. The defined type is treated as the definition for the row data in the rendered content. This is referred to as the collection element type. The allowed children components of the collection element’s type are:

Sample Configuration
@CardDetailsGrid(onLoad = true)
@Config(url = "...")
@Path(linked = false)
private List<ConcernsLineItem> vcdgConcerns;

@Model
@Getter @Setter
public static class ConcernsLineItem {

    @CardDetail
    private VCDMain vcdMain;

    @Model
    @Getter @Setter
    public static class VCDMain { ... }
}
Chart

Chart renders a visually appealing graph/chart based on an open source HTML5 based charting library: Charts.js

Allowed Parent Components

Section

Allowed Children Components

None. @Chart should decorate a field having a collection with type DataGroup (e.g. List<DataGroup>.)

Allowed Chart Types
  • Line

  • Bar

  • Pie

  • Doughnut

Attributes
  • xAxisLabel = ``

    • Applicable only for cartesian graphs and denotes the label to be shown for X-axis

  • yAxisLabel = ``

    • Applicable only for cartesian graphs and denotes the label to be shown for Y-axis

  • stepSize = ``

    • Applicable only for cartesian graphs and denotes the incrments of the Y-axis. If not given, the size would be random based on the Y-axis values supplied to the graph

Sample Configuration
@Label("Bar Graph")
@Chart(value=Type.BAR, xAxisLabel="Domain", yAxisLabel="Count")
private List<DataGroup> barGraph;

@Label("Line Graph")
@Chart(value=Type.LINE, xAxisLabel="Domain", yAxisLabel="Count")
private List<DataGroup> lineGraph;

@Label("Pie Graph")
@Chart(value=Type.PIE)
private List<DataGroup> pieGraph;

@Label("Doughnut Chart")
@Chart(value=Type.DOUGHNUT)
private List<DataGroup> doughnutGraph;
Data Type of Chart

Every chart that is configured with a datatype as List<DataGroup>. In a chart there could be 1 or more graphs that can be plotted. Each DataGroup qualifies to be painted as a graph. A DataGroup has a legend that indicates what the graph is and has a list of x,y co-ordinates.

If line graph configured above has the following dataset provided to the UI -

[
    {
        "legend": "Region 1"
        "datapoints": [
            {1,1},{2,2},{3,3}
        ]``
    }
    {
        "legend": "Region 2"
        "datapoints": [
            {10,10},{20,20},{30,30}
        ]
    }
]

There would be 2 line graphs rendered on the page, one for Region 1 and the other for Region 2.

CheckBox

Checkbox is an extension to standard checkbox element.

Allowed Parent Components

Form

Allowed Children Components

None. @CheckBox should decorate a field having a simple type of Boolean or boolean.

Sample Configuration
@CheckBox(postEventOnChange=true)
private boolean admin;
CheckBoxGroup

CheckBoxGroup is used for multi-select CheckBox components.

Allowed Parent Components

Form

Allowed Children Components

None. @CheckBoxGroup should decorate a field having a collection/array of type String.

Sample Configuration
@CheckBoxGroup(postEventOnChange=true)
private String[] days;
ComboBox

Combobox is used to select an item from a collection of options.

Allowed Parent Components

Form, Section

Allowed Children Components

None. @ComboBox should decorate a field having a simple type.

Sample Configuration
@Path
@Values(url = "...")
@ComboBox
private String goalCategory;
Supplying Values to a ComboBox

When decorated with @Values, the resolved values as defined by the logic within Values will be used as the options for the rendered @ComboBox component.

FieldValue

FieldValue is a container for displaying a single value. Has the ability to become an in place edit field with several configurable properties.

Allowed Parent Components

CardDetail.Body, CardDetail.Header, FieldValueGroup

Allowed Children Components

None. @FieldValue should decorate a field having a simple type.

Sample Configuration
@MapsTo.Type(Goal.class)
@Getter @Setter
public static class VCDHGoals {

    @FieldValue(showName = false, cols = "2")
    @Path
    private String description;

    @FieldValue(showName = false, iconField = "date")
    @Path
    private LocalDate targetDate;

    @FieldValue(showName = false, iconField = "planned")
    @Path("/status")
    private String status;

    @FieldValue(showName = false, datePattern = "MM/dd/yyyy")
    @Path
    private LocalDate startDate;
}
FieldValueGroup

FieldValue is a container for displaying a logical grouping of multiple FieldValue components.

Allowed Parent Components

CardDetail.Body

Allowed Children Components

FieldValue

Sample Configuration
@FieldValueGroup
private AddressGroup addressGroup;

@MapsTo.Type(Address.java)
@Getter @Setter
public static class AddressGroup {

    @FieldValue
    @Path
    private String line1;

    @FieldValue
    @Path
    private String line2;

    @FieldValue
    @Path
    private String city;

    @FieldValue
    @Path
    private String state;

    @FieldValue
    @Path
    private String zip;
}
FileUpload

FileUpload is an advanced uploader with dragdrop support, multi file uploads, auto uploading, progress tracking and validations.

Allowed Parent Components

Form

Allowed Children Components

None. @FileUpload should decorate a field having a simple type.

Sample Configuration
@FileUpload(type = "${app.config.upload.allowedTypes}")
private String uploader;
Form

A Form is an HTML form container for user input contents.

Allowed Parent Components

Section

Sample Configuration
@Form(cssClass = "twoColumn")
@Path(linked = false, state = State.External)
private VFGoals vfGoals;

@Model
@Getter @Setter
public static class VFGoals { ... }
FormElementGroup

FormElementGroup is a grouping container that enables the form elements to be grouped in such a way that they appear to look as a sub section of the form. For example if there is a Name of person that needs to be gathered with 3 sub fields firstName, middleName and lastName, this can be displayed as a FormElementGroup for better user experience.

Sample Configuration
@Model @Getter @Setter
public static class VSAddEditForm {

    @Form
    private VFAddEditForm vfAddEditForm;
}

@Getter @Setter
public static class VFAddEditForm {

    @Label("Name")
    @FormElementGroup(cssClass="oneColumn")
    private SectionName sectionName;

}

@Model
@Getter @Setter
public static class SectionName {

    @Label("First Name")
    @TextBox(cssClass="inline")
    @NotNull
    @Path
    private String firstName;

    @Label("Middle Name")
    @TextBox(cssClass="inline")
    @NotNull
    @Path
    private String middleName;

    @Label("Last Name")
    @TextBox(cssClass="inline")
    @NotNull
    @Path
    private String lastName;

}

The above code illustrates the usecase of showing name field in the form of a sub group of 3 other fields and gives the visual experience of grouping them for better undertanding. The css class "oneColumn" helps in formatting the fields as if they fall under the "name" field. If the existing style classes are not sufficient, a custom css class can be added to the app styles and be used.

Allowed Parent Components

TODO

Allowed Children Components

TODO

Grid

Grid is a table container capable of displaying tabular data.

Allowed Parent Components

Form, Section

Allowed Children Components

@Grid should decorate a field having a collection/array with a defined type. The defined type is treated as the definition for the row data in the rendered HTML table. This is referred to as the collection element type. The allowed children components of the collection element’s type are:

Sample Configuration
@Grid(export=true)
private List<PetLineItem> vgPets;

@Model
@Getter @Setter
public static class PetLineItem {

    @GridColumn
    private String field1;

    @LinkMenu
    private VLMMain vlmMain;

    @GridRowBody
    private ExpandedRowContent expandedRowContent;

    @Model
    @Getter @Setter
    public static class VLMMain { ... }

    @Model
    @Getter @Setter
    public static class ExpandedRowContent { ... }
}
Multiple row selection

@Grid offers the ability to select multiple rows of data and submit the indexes of the selected rows as JSON data to the server for processing.

To enable this feature, the following attributes must be set:

  • rowSelection = true

    • renders a checkbox at the beginning of each row in the table for row selection

  • postButton = true

    • renders a button below the rendered @Grid that when clicked, submits an HTTP POST request with a payload containing the selected index to the path at postButtonUri

  • postButtonUri

    • a parameter path relative to the decorated @Grid parameter to invoke an HTTP POST request on with the selected indexes

  • postButtonTargetPath

    • the key to be used for the JSON object sent as a payload

  • (Optional) postButtonLabel

    • controls the label of the button rendered by postButton

  • (Optional) headerCheckboxToggleAllPages

    • when true, selecting the "select all" checkbox in the header of the @Grid will select all the records in the dataset across all pages. When false, only within the current page items will be selected

  • (Optional) export

    • exposes the capability to download the grid data in .csv format

Example configuration for multiple row selection using @Grid
@Grid(onLoad = true, rowSelection = true, postButton = true, postButtonUri = "../actionRemove", postButtonTargetPath = "ids", postButtonLabel = "Remove")
@Path
@Config(url = "/p/member/_search?fn=example")
private List<MemberLineItem> membersGrid;

@Config(url = "/page/tile/section/selectedIdsWrapper/_replace") //(1)
@Config(url = "/p/member:<!/../membersGrid/<!col!>/memberId!>/_delete", col = "<!/selectedIdsWrapper/ids!>") //(2)
private String actionRemove;

private SelectedIdsWrapper selectedIdsWrapper;

@Model
@Getter @Setter
public static class SelectedIdsWrapper {
    private List<String> ids;
}

@MapsTo.Type(Member.class)
@Getter @Setter
public static class MemberLineItem { ... }

In this example, assume there are ten rows of data and the user selects indexes 1, 3, and 7 (Indexes are zero based). When clicking the "Remove" button, the an HTTP POST call is sent to the param at actionRemove with the payload:

{
    "ids": ["1", "3", "7"]
}

Next, the @Config statements decorating actionRemove are executed. Config #1 uses _replace to update the sepectedIdsWrapper ids field to be ["1", "3", "7"]. Config #2 is a bit trickier to read, but it iterates over the values just set into "selectedIdsWrapper/ids" and for each id, retrieves a record from membersGrid. From that point, the object in context is a MemberLineItem object, from which memberId is retrieved and used with _delete to delete the selected members them from the member domain.

Exporting grid content

The attribute export=true instructs the grid component to show an export link (clicking of which the grid data can be exported). There are 2 kinds of scenarios where the export of grid data can be used. 1. When grid is configured to use client side pagination, the export functionality would export all the data to a csv file that gets downloaded on the machine. 2. When the grid is configured with server side pagination, the export functionality would export only the current page data.

Note
The data downloaded would be in CSV format.
Using inline edit for @Grid records

@Grid supports inline editing, allowing the user to edit the first-level visible fields in a table row.

@Grid(editRow = true) (1)
private List<SampleLineItem> editableGrid;

@Model @Getter @Setter
public static class SampleLineItem {

    private String _action_onEdit; (2)

    // Line item fields below here
}
  1. Toggling the attribute editRow will enable/disable an edit icon which will make a _get request parameter defined by onEdit.

  2. Decorate _action_onEdit with any @Config or framework supported annotations.

Configuring server side pagination

By default, if the the @Grid component is configured to use pagination, it uses client side pagination. The rendered table can be configured to work with the framework by providing Command DSL statements that the framework then interpolates and hands off to Spring to take advantage of it’s rich server side pagination features.

Certain pagination specific query parameters can be used to leverage framework support.

Pagination Query Parameters
page

An integer representing the starting page number

pageSize

An integer for the number of elements to display per page

sortBy

A "key:value" string with the key representing the field over which to sort and the value representing the sort direction. Supported values are determined by the org.springframework.data.domain.Direction (asc, desc, etc.)

Server side pagination example
@MapsTo.Path(linked = false)
@Grid(onLoad = true, pageSize = "3", lazyLoad = true) (1)
@Config(url = "/vpVisitsServer/vtVisits/vsVisits/visits.m/_process?fn=_set&url=/p/visit/_search?fn=example&<!page=y!>") (2)
private List<VisitLineItem> visits;

Setting lazyLoad = true as seen in <1> informs the client side to use server side pagination by sending Command DSL statements to the server. The client side will send pagination information as query parameters and payload data depending upon the action taken upon the rendered @Grid component.

Setting <!page=y!> as seen in <2>, invokes the Path Variable Resolver to construct/pass along any query parameters/payload data sent from the incoming Command DSL statement for the pagination query parameters. For example, the following statement:

would result in <2> being invoked as:

/vpVisitsServer/vtVisits/vsVisits/visits.m/_process?fn=_set&url=/p/visit/_search?fn=example&page=0&pageSize=5

Note
When serverside pagination is enabled and <!page=y!> is given, it is expected that \_get calls invoking the parameter would also send the pagination query parameters, otherwise pagination will not work as expected.
Sorting with server side pagination

Server side sorting can be achieved by setting sortBy as a query paremeter in the Command DSL statement.

/vpVisitsServer/vtVisits/vsVisits/visits.m/_process?fn=_set&url=/p/visit/_search?fn=example&page=0&pageSize=5&sortBy=firstName:DESC

Filtering with server side pagination

Server side filtering can be achieved by setting the filters JSON in the raw payload alongside the Command DSL statement.

Request URL: /vpVisitsServer/vtVisits/vsVisits/visits.m/_process?fn=_set&url=/p/visit/_search?fn=example&page=0&pageSize=5

Payload:

filters: [
    {
        "code": "firstName",
        "value": "bob"
    }
]
GridColumn

GridColumn is a container for displaying a single value within a Grid.

Allowed Parent Components

Grid

Allowed Children Components

None. @GridColumn should decorate a field having a simple type.

Sample Configuration
@Model
@Getter @Setter
public static class PetLineItem {

    @GridColumn
    private String field1;
}
GridRowBody

GridRowBody is used to display additional content about the row data within a Grid.

Allowed Parent Components

Grid

Allowed Children Components

@GridRowBody will display children components in the same manner as Section does. See the Allowed Children Components of Section for more details.

Sample Configuration
@MapsTo.Type(Pet.class)
@Getter @Setter
public static class PetLineItem {

    @GridColumn
    @Path
    private String name;

    @GridRowBody
    private ExpandedRowContent expandedRowContent;

    @Model
    @Getter @Setter
    public static class ExpandedRowContent {

        @CardDetail
        private CardDetails cardDetails;
    }

    @Model
    @Getter @Setter
    public static class CardDetails {

        @CardDetail.Body
        private CardBody cardBody;
    }

    @Model
    @Getter @Setter
    public static class CardBody {

        @FieldValue
        @Path
        private String id;
    }
}
Header

Header is a container with a text header, equivalent to an HTML header.

Allowed Parent Components

Form

Allowed Children Components

None. @Header should decorate a field having a simple type.

Sample Configuration
@Header(size = Header.Size.H1)
private String header;
Hints

Hints is a UI styling behavior applied for some components. The behavior applied is delegated to each individual component where @Hints is supported.

Hints.java
@Link(url = "/#/h/cmdashboard/vpDashboard", imgSrc = "anthem-rev.svg")
@Hints(AlignOptions.Left)
@PageHeader(Property.LOGO)
private String linkHomeLogo;
InputMask

InputMask is a text input component used to enter input in a certain format such as numeric, date, currency, email and phone.

Allowed Parent Components

Form

Allowed Children Components

None. @InputMask should decorate a field having a simple type.

Sample Configuration
@InputMask(mask = "(999) 999-9999")
@Label("Phone Number")
InputSwitch

InputSwitch is used to select a boolean value.

Allowed Parent Components

Form, Section

Allowed Children Components

None. @InputSwitch should decorate a field having a simple type.

Sample Configuration
@Label("On/Off")
@InputSwitch
private boolean on;
Image

An image component used for displaying images.

Allowed Parent Components

None.

Allowed Children Components

None. @Image should not decorate a field and will typically be used as an annotation attribute in other components.

Sample Configuration
@MenuPanel(imgType = Image.Type.SVG, imgSrc = "notesIcon")
private VMPVisits vmpVisits;
Label

Label is used to display label text for a view component.

@Label can be used on all View annotations (e.g. Page, Tile, TextBox, etc.), wherever content is necessary. Multiple @Label annotations can be provided for managing content for different locales.

Sample Configuration
@Label("First Name")
private String firstName;

@Label(value="Last Name")
private String lastName;

@Label(value = "Select 3" , helpText = "Please select atleast 3 cities")
@CheckBoxGroup
private String[] city;

@Tile
@Label("Dashboard")
private VTDashboard vtDashboard;

@Page
@Label(" ")
private VPDashboard vpDashboardWithEmptyLabel;

@Label(value="Test Label C in English", helpText="some tooltip text here C")
@Label(value="Test Label A in French", localeLanguageTag="fr")
private String staticText;

@Textbox
private String addressline;

Link is a hyperlink component used for navigation or user interaction of displayed text.

Allowed Parent Components

CardDetail, Grid, LinkMenu, Menu, Section

Allowed Children Components

None. @Link should decorate a field having a simple type.

Sample Configuration
// UI Navigation to page vpHome under domain petclinic
@Link(url = "/h/petclinic/vpHome")
private String linkToHome;

// Executes a request against _subscribe, and sets the image
@Link(url = "/notifications/_subscribe:{id}/_process", b="$executeAnd$configAnd$nav", method = "POST")
private String subscribeToEmailNotifications;

// Creates an external link, as in a link navigating outside of the context of the Nimbus framework.
@Link(url = "https://www.mywebsite.com", value = Link.Type.EXTERNAL, target = "_new", rel = "nofollow")
private String myWebsiteLink;

LinkMenu is a dropdown component used for displaying links within a Grid row.

Allowed Parent Components

Grid

Allowed Children Components

Link

Sample Configuration
@LinkMenu
private VLMMain vlmMain;

@Model
@Getter @Setter
private static class VLMMain {

    // Display a link that performs some @Config action
    @Label(value = "View Case")
    @Link
    @Config(url = "...")
    private String viewCase;

    // Display a link that performs some @Config action and uses an image icon.
    @Label(value = "Assign Case Owner")
    @Link(imgSrc = "task.svg")
    @Config(url = "...")
    private String assignCase;

    // Display a link that performs an external navigation in a new tab and passing a dynamic value
    @Link(value = Type.EXTERNAL, target = "_blank", url = "${app.endpoints.documents}/showDocument.aspx?documentid={documentId}")
    @Path(linked = false)
    private String viewDoc;

    private String documentId;
}
Menu

Menu is a container intended to display other navigation components.

Allowed Parent Components

Section

Allowed Children Components

Link

Sample Configuration
@Menu
private SideMenu sideMenu;

@Model
@Getter @Setter
public static class SideMenu {

    @Link(url = "#")
    private String home;

    @Link(url = "#")
    private String signup;
}

MenuLink is nestable menu link component that is exclusively used with MenuPanel.

Allowed Parent Components

MenuPanel

Allowed Children Components

None. @MenuLink should decorate a field having a simple type.

Sample Configuration
@Label("Link 1")
@MenuLink
private String link1;

@Label("Link 2")
@MenuLink
private String link2;
MenuPanel

MenuPanel is a hybrid of accordion-tree components.

Allowed Parent Components

MenuPanel, Page

Allowed Children Components

MenuLink, MenuPanel

Attributes
  • (Optional) flow

    • If specified, aids the breadcrumb to identify the parent domain reference and renders the links correctly when a domain(other than the parent domain reference) and page is configured as a menulink

Sample Configuration
@MenuPanel
private VMPView vmpView;

@Model
@Getter @Setter
public static class VMPView {

    @Label("Show View...")
    @MenuLink
    private String showView;

    @Label("Appearance")
    @MenuPanel(flow="domainAalias")
    private VMPAppearance appearance;

    @Model
    @Getter @Setter
    public static class VMPAppearance {

        @Label("Toggle Full Screen")
        @MenuLink(url="domainAalias/page1")
        private String toggleFullScreen;

        @Label("Toggle Always on Top")
        @MenuLink(url="domainBalias/page1")
        private String toggleAlwaysOnTop;

        @Label("Toggle with Animation")
        @MenuLink(url="domainAalias/page2")
        private String toggleWithAnimation;
    }
}

In the above configuration, the MenuLink has a url that points to either domainAalias or domainBalias. Lets say VMPView belongs to the domain that is the layout of the domainA. In other words, the view domain A has the layout of MenuPanel and MenuLink. So initially, When 'toggle full screen' MenuLink is clicked the breadcrumb would say - Home→Toggle Full Screen When 'Toggle Always on Top' MenuLink is clicked, the breadcrumb would say Home→Toggle Always On Top. When 'Toggle with Animation' is clicked the breadcrumb would say Home→Toggle With Animation Its important to note that even though the transition is from domainBalias to domainAalias, the flow attribute tells the MenuPanel that the parent flow of MenuLink clicked is domainAalias. The value domainAalias is looked up in the breadcrumb list and the value is appended as a suffix.

Modal

Modal is a container to display content in an overlay window.

Allowed Parent Components

Tile

Allowed Children Components

Section

Sample Configuration
@Label("Modal Title")
@Modal
private VMMyModal vmMyModal;

@Model
@Getter @Setter
public static class VMMyModal {

    @Section
    private VSMain vs;

    @Model
    @Getter @Setter
    public static class VSMain { ... }
}
Displaying @Modal on Load

@Modal makes use of @ParamContext to render it’s visible property to false during initialization by default. This and other similiar behaviors can be overridden by supplying the context attribute of @Modal with a @ParamContext.

@Modal(context = @ParamContext(enabled = true, visible = true))
private VMMyModal vmMyModal;

public static class VMMyModal { ... }

See ParamContext for more details.

Page

Page is a container component that groups a collection of contents.

Allowed Parent Components

Domain

Allowed Children Components

Tile

Sample Configuration
@Page(defaultPage = true)
private VPHome vpHome;

@Page
private VPPets vpPets;

@Model
@Getter @Setter
public static class VPHome {

    @Tile
    private VTMain vtMain;

    @Model
    @Getter @Setter
    public static class VTMain { ... }
}

@Model
@Getter @Setter
public static class VPPets { ... }
PageHeader

TODO

Allowed Parent Components

TODO

Allowed Children Components

TODO

Sample Configuration
@PageHeader(PageHeader.Property.USERNAME)
private String fullName;
Paragraph

Paragraph is a container for displaying text content.

Allowed Parent Components

Form, Header, Section

Allowed Children Components

None. @Paragraph should decorate a field having a simple type.

Sample Configuration
@Paragraph("Hello! Welcome to Pet Clinic.")
private String welcome;
PickList

PickList is used to reorder items between different lists.

Allowed Parent Components

Form

Allowed Children Components

PickListSelected

Sample Configuration
@PickList(sourceHeader = "Available Category", targetHeader = "Selected Category")
@Values(value = SomeCategory.class)
private PicklistType category;

@MapsTo.Type(SomeClass.class)
@Getter @Setter
public static class PicklistType {

    @PickListSelected(postEventOnChange = true)
    @Path("category")
    @Values(value = SomeCategory.class)
    @NotNull
    private String[] selected;
}
Using @PickList with Dynamic Values

The @PickList implementation is works closely with Values. In the previous sample configuration @PickList and @PickListSelected are using the same @Values source. If the values assigned to the @PickList component need to be conditionally set, meaning that the values on the source list are able to change, ValuesConditional can be used. See the following example:

@ComboBox(postEventOnChange = true)
@Values(value = petType.class)
@ValuesConditional(target = "../category", condition = {
    @ValuesConditional.Condition(when = "state == 'Dog'", then = @Values(value = DogCategory.class)),
    @ValuesConditional.Condition(when = "state == 'Cat'", then = @Values(value = CatCategory.class))
})
@VisibleConditional(targetPath = { "../category" }, when = "state != 'Horse'")
@EnableConditional(targetPath = { "../category" }, when = "state != 'Parrot'")
private String type;

@PickList(sourceHeader = "Available Category", targetHeader = "Selected Category")
private PicklistType category;

@MapsTo.Type(SomeClass.class)
@Getter @Setter
public static class PicklistType {

    @PickListSelected(postEventOnChange = true)
    @Path("category")
    @Values(value = AllCategory.class)
    @NotNull
    private String[] selected;
}

One caveat to this implementation is that the @PickListSelected component should have it’s values set to the comprehensive set of possible values that the parent source list could contain. (e.g. The values in DogCategory.class and CatCategory.class should be contained in AllCategory.class). Not doing so may result in undesirable results.

PickListSelected

PickListSelected is a required supplementary configuration for PickList. The @PickList works exclusively with @PickListSelected by providing the UI a server-side location to store the selected values and any other metadata associated with those selected values.

Allowed Parent Components

PickList

Allowed Children Components

None. @PickListSelected should decorate an array or collection.

Sample Configuration
@PickListSelected(postEventOnChange = true)
@Path("category")
@Values(value = AllCategory.class)
@NotNull
private String[] selected;
Radio

Radio is an extension to standard radio button element.

Allowed Parent Components

Form

Allowed Children Components

None. @Radio should decorate a field having a Boolean or boolean value.

Sample Configuration
@Label("Some question:")
@Radio(postEventOnChange = true, controlId = "27")
@Values(url="~/client/orgname/staticCodeValue/_search?fn=lookup&where=staticCodeValue.paramCode.eq('/q14')")
@Path
private String q14;
RichText

RichText is a rich text editor based on <a href="https://www.primefaces.org/primeng/#/editor">PrimeNG’s Editor</a>.

Allowed Parent Components

Form

Allowed Children Components

None. @RichText should decorate a field having a simple type.

Sample Configuration
@RichText
private String richTextbox;
Displaying Readonly Content

The rich text editor supports readonly mode, which makes the field a true readonly form field that preserves rich text HTML formatting and removes any extra rich text features (e.g. toolbar and other similar features).

Sample Configuration
@RichText(readOnly = true)
private String richTextbox;
Tip
Use readOnly when needing to simply display the state of a decorated field containing an HTML formatted string.
Configuring the Toolbar

The rich text editor toolbar by default has most of the available features enabled by default (see the Javadoc for explicit details). Toolbar features can be enabled/disabled via the following:

Sample Configuration
@RichText(toolbarFeatures = { ToolbarFeature.BOLD, ToolbarFeature.ITALIC, ToolbarFeature.UNDERLINE })
private String richTextbox;
Configuring Fonts

Assuming the Toolbar feature ToolbarFeature.FONT is enabled, the fonts that are able to be used are entirely configurable and controlled by providing the following:

  • A unique string value for the font label

  • A CSS definition within the client application styles

Sample Configuration
@RichText
@Fonts({ "Times New Roman", "CoolFont" })
private String richTextbox;
Sample Configuration
.ql-editor .ql-font-times-new-roman {
    font-family: "Times New Roman";
}
.ql-editor .ql-font-cool-font {
    font-family: "Cool Font";
}
Note
In order to use custom fonts (such as "Cool Font"), clients must ensure to load any/all necessary font files (e.g. .woff, .ttf).
Section

Section is a container component that groups a collection of contents.

Allowed Parent Components

Grid, Modal, Section, Tile, Tab

Sample Configuration
@Section
private VSMain vsMain;

@Model
@Getter @Setter
public static class VSMain { ... }
Signature

Signature is an HTML canvas element that can be used to capture signature content. The @Signature component will render the following on the UI:

  • canvas on which the user can draw

  • Clear button, which will clear the canvas

  • Accept button, which will store the user-drawn content captured in canvas as a data-url representation

  • <img>, which contains the stored image after clicking accept button

Allowed Parent Components

Form

Allowed Children Components

None. @Signature should decorate a field having a simple type.

Sample Configuration
@Signature(postEventOnChange = true)
private String employeeSignature;
StaticText

StaticText is a container for displaying html content or text "as is" to the UI. @StaticText may be used to bind unsafe HTML directly onto the page.

With great power comes great responsibility.

— Voltaire
Allowed Parent Components

CardDetail, Section

Allowed Children Components

None. @StaticText should decorate a field having a simple type.

Sample Configuration
@StaticText
private String description;
Tab

Tab is a container component to group content with TabPanels.

Allowed Parent Components

Tile Section

Allowed Children Components

@TabPanel. TabPanel can support section or another Tab within it.

Sample Configuration
@Tab
    @Path(linked=false)
    private Tab tab;

     @Model
@Getter @Setter
public static class Tab  {

    @Label("Tab Panel")
    @TabPanel
    @Path(linked=false)
    private TabPanel panel;

}

    @Model
@Getter @Setter
public static class TabPanel  {


 @Section
 @Path(linked=false)
 private TabContent tabContent;


    @Tab
    @Path(linked=false)
    private AnotherTabPanel anotherPanel;

}
TextArea

TextArea is a text input component that allows for a specified number of rows.

Allowed Parent Components

Form

Allowed Children Components

None. @TextArea should decorate a field having a simple type.

Sample Configuration
@TextArea
private String description;
TextBox

TextBox is a text input component.

Allowed Parent Components

Form, Section

Allowed Children Components

None. @TextBox should decorate a field having a simple type.

Sample Configuration
@TextBox
private String sampleTextField;
Tile

Tile is a container component that groups a collection of contents.

Allowed Parent Components

Page

Allowed Children Components

Header, Modal, Section, Tile, Tab

Sample Configuration
@Tile(size = Tile.Size.Large)
private VTMain vtMain;

@Model
@Getter @Setter
public static class VTMain { ... }
TreeGrid

TreeGrid is used to display hierarchical data in tabular format.

Allowed Parent Components

Section, Form

Allowed Children Components

@TreeGrid should decorate a field having a collection/array with a defined type. The defined type is treated as the definition for the row data in the rendered HTML table. This is referred to as the collection element type. The allowed children components of the collection element’s type are:

Sample Configuration
@TreeGrid
@Path(linked = false)
@Config(url = "<!#this!>/.m/_process?fn=_set&url=/p/pethistory/_search?fn=example")
private List<PetHistoryLineItem> treegrid;

@Model
@Getter @Setter
public static class PetHistoryLineItem { ... }
TreeGrid

TreeGridChild is the recursive child of TreeGrid and is used to display hierarchical data in tabular format.

Allowed Parent Components

TreeGrid

Allowed Children Components

@TreeGridChild should decorate a field having a collection/array with a defined type. The defined type is treated as the definition for the row data in the rendered HTML table. This is referred to as the collection element type. The type should ALWAYS match the collection element type of the parent field decorated with @TreeGrid and consequently, the allowed children are the same as the allowed children of TreeGrid.

Sample Configuration
@TreeGrid
@Path(linked = false)
@Config(url = "<!#this!>/.m/_process?fn=_set&url=/p/pethistory/_search?fn=example")
private List<PetHistoryLineItem> treegrid;

@MapsTo.Type(PetHistory.class)
@Getter @Setter
public static class PetHistoryLineItem {

    @GridColumn
    @Path
    private String name;

    @TreeGridChild
    @Path
    private List<PetHistoryLineItem> children;
}
ViewRoot

ViewRoot is the entry point for a view domain definition.

Allowed Parent Components

None.

Allowed Children Components

Page

ViewRoot.java
@Domain(value = "sampleview", includeListeners = { ListenerType.websocket }, lifecycle = "sampleview")
@MapsTo.Type(SampleView.class)
@Repo(value = Database.rep_none, cache = Cache.rep_device)
@ViewRoot(layout = "sampleviewlayout")
@Getter @Setter
public class VRSampleView { ... }

5.2. Examples

5.2.1. Conditional Configuration Examples

The following samples showcase how to apply common conditional scenarios using the framework’s @Conditional annotations.

Make Textbox "Read-Only" by Default
@TextBox
@ParamContext(enabled = false, visible = true)
private String textbox;
Make Textbox "Read-Only" When a Question is Answered as "Yes"
@TextBox(postEventOnChange = true)
@EnableConditional(when = "state == 'Yes'", targetPath = { "/../textbox" })
private String condition;

@TextBox
private String textbox;
Make Textbox Mandatory by Default
@TextBox
@NotNull
private String textbox;
Make Textbox Mandatory When a Question is Answered as "Yes"
@TextBox(postEventOnChange = true)
@ValidateConditional(when = "state == 'Yes'", targetGroup = ValidationGroup.GROUP_1.class })
private String condition;

@TextBox
@NotNull(groups = { ValidationGroup.GROUP_1.class })
private String textbox;
Make Textbox Validate by Pattern and be Mandatory When a Question is Answered as "Yes"
@TextBox(postEventOnChange = true)
@ValidateConditional(when = "state == 'Yes'", targetGroup = ValidationGroup.GROUP_1.class })
private String condition;

@TextBox
@NotNull(groups = { ValidationGroup.GROUP_1.class })
@Pattern(regexp = "Hello (.*)!", groups = { ValidationGroup.GROUP_1.class })
private String textbox;
Make Textbox Editable by Default
@TextBox
private String textbox;
Make Combobox Values Change and be Mandatory Based on Another Field
@TextBox(postEventOnChange = true)
@ValidateConditional(when = "state == 'Yes'", targetGroup = ValidationGroup.GROUP_1.class })
private String condition;

@ComboBox(postEventOnChange = true)
@NotNull(groups = { ValidationGroup.GROUP_1.class })
@ValuesConditional(target = "../condition", condition = {
    @Condition(when = "state=='Yes'", then = @Values(YES_VALUES.class)),
    @Condition(when = "state=='No'", then = @Values(NO_VALUES.class))
})
private String combobox;
Make TextBox Editable or Not-Editable by Default When a Form Loads
// Editable by default
@TextBox
private String textbox1;

// Not Editable by default - Method 1 (Preferred)
@TextBox
@ParamContext(enabled = false, visible = true)
private String textbox2;

// Not Editable by default - Method 2
@TextBox
@EnableConditional(when = "false", targetPath = "../")
private String textbox2;
Make Textbox Editable and Apply Validations Based on the Value of Another Field
@TextBox(postEventOnChange = true)
@EnableConditional(when = "state == 'Yes'", targetPath = { "/../textbox" })
@ValidateConditional(when = "state == 'Yes'", targetGroup = ValidationGroup.GROUP_1.class })
private String condition;

@TextBox
@NotNull(groups = { ValidationGroup.GROUP_1.class })
private String textbox;
Make Textbox Already Having Validations have Additional Validations Based on the Value of Another Field
@TextBox(postEventOnChange = true)
@EnableConditional(when = "state == 'Yes'", targetPath = { "/../textbox" })
@ValidateConditional(when = "state == 'Yes'", targetGroup = ValidationGroup.GROUP_1.class })
private String condition;

@TextBox
@NotNull(groups = { ValidationGroup.GROUP_1.class })
@Min(10)
private String textbox;
Make Form/Section Disabled Based on Some Condition
@TextBox(postEventOnChange = true)
@EnableConditional(when = "state == 'Yes'", targetPath = { "/../form" })
private String condition;

@Form
private MyForm form;

@Model @Getter @Setter
public static class MyForm {
    // Form implementation here...
}
Make Textbox Hidden on Some Condition and On Another Condition, Make it Visible and Mandatory.
@TextBox(postEventOnChange=true)
@ActivateConditional(when = "state == 'No'", targetPath = { "../textbox" })
@ValidateConditional(when = "state == 'Yes'", targetGroup = GROUP_1.class)
private String condition;

@TextBox
@NotNull(groups = { GROUP_1.class })
private LocalDateTime textbox;
Make Textbox Mandatory When State is Anything but a Particular Value
@TextBox(postEventOnChange=true)
@ValidateConditional(when = "state != null && state != 'Yes'", targetGroup = GROUP_1.class)
private String condition;

@TextBox
@NotNull(groups = { GROUP_1.class })
private String textbox;
Make Textbox "Read-Only" When State is Anything but a Particular Value
@TextBox(postEventOnChange=true)
@EnableConditional(when = "state != null && state != 'Yes'", targetGroup = GROUP_1.class)
private String condition;

@TextBox
@NotNull(groups = { GROUP_1.class })
private String textbox;
Make Textbox Hidden When State is All but a Particular Value
@TextBox(postEventOnChange=true)
@ActivateConditional(when = "state!= null && state != 'Yes'", targetGroup = GROUP_1.class)
private String condition;

@TextBox
@NotNull(groups = { GROUP_1.class })
private String textbox;

5.2.2. _replace Examples

The following samples showcase how to apply common scenarios using the framework’s _replace action.

The following class will be used in the _replace scenarios to follow.

@Domain("sample_entity")
@Getter @Setter
public class SampleEntity {

    private String single_element;
    private List<String> collection;
}

The following scenarios will also assume that the param at sample_entity:1 has a pre-existing state of:

{
    "single_element": "empty_value",
    "collection": ["empty_value_1"]
}

Replacing a single element state

@Config(url = "sample_entity:1/single_element/_replace?rawPayload='hello'")

Resulting State of sample_entity:1
{
    "single_element": "hello",
    "collection": ["empty_value_1"]
}

As the path in this scenario is pointing to sample_entity:1/single_element, only the field single_element is subject to be replaced with the rawPayload. The value for the field of collection is unaffected and retains it’s previous value.


Replacing a collection element state

@Config(url = "sample_entity:1/collection/0/_replace?rawPayload='hello'")

Resulting State of sample_entity:1
{
    "single_element": "empty_value",
    "collection": [ "hello" ]
}

As the path in this scenario is pointing to sample_entity:1/collection/0, only the collection element having index 0 for the field collection is subject to be replaced with the rawPayload. The value for the field of single_element is unaffected and retains it’s previous value.


Replacing a collection state

@Config(url = "sample_entity:1/collection/_replace?rawPayload='[\"1\", \"2\"]'")

Resulting State of sample_entity:1
{
    "single_element": "empty_value",
    "collection": [ "1", "2" ]
}

As the path in this scenario is pointing to sample_entity:1/collection, only the field collection is subject to be replaced with the rawPayload. The value for the field of single_element is unaffected and retains it’s previous value.


Replacing a complex object

@Config(url = "sample_entity:1/_replace?rawPayload='{\"single_element\": \"hello\"}'")

Resulting State of sample_entity:1
{
    "single_element": "hello"
}

As the path in this scenario is pointing to sample_entity:1, both fields are subject to be replaced with the rawPayload. In this scenario, only single_element has been provided which results in collection having a null value.

Note
The important thing to note in this scenario, is that like all other scenarios the existing state is replaced. This means that if collection of the entity located at sample_entity:1 had an existing value, it will be set to null since the rawPayload parameter did not explicitely set it.

Replacing an entity state with the value of another entity state

@Config(url = "sample_entity:1/single_element/_replace?rawPayload=<!json(PATH_TO_PARAM)!>)

where PATH_TO_PARAM is path which points to another param, relative to the location of single_element.

As the path in this scenario is pointing to sample_entity:1/single_element, only the field single_element is subject to be replaced with the rawPayload. The value to be set will be the same as the state of the object identified by PATH_TO_PARAM.

Tip
This scenario can be used to copy a param’s state from one param to another.

Replacing an entity state with post data

@Config(url = "sample_entity:1/single_element/_replace")

Post Data

"hello"

Resulting State of sample_entity:1
{
    "single_element": "hello",
    "collection": ["empty_value_1"]
}

As the path in this scenario is pointing to sample_entity:1/single_element, only the field single_element is subject to be replaced with the HTTP post data. The value for the field of collection is unaffected and retains it’s previous value.


Replacing an entity state to null

@Config(url = "sample_entity:1/single_element/_replace)

  • Assumes post data is not present

Resulting State of sample_entity:1
{
    "collection": ["empty_value_1"]
}

As the path in this scenario is pointing to sample_entity:1/single_element, only the field single_element is subject to be replaced with the HTTP post data. In this case the post data is null so the resulting value for single_element will then be null. The value for the field of collection is unaffected and retains it’s previous value.

5.2.3. _update Examples

The following samples showcase how to apply common scenarios using the framework’s _update action.

The following class will be used in the _update scenarios to follow.

@Domain("sample_entity")
@Getter @Setter
public class SampleEntity {

    private String single_element;
    private List<String> collection;
}

The following scenarios will also assume that the param at sample_entity:1 has a pre-existing state of:

{
    "single_element": "empty_value",
    "collection": ["empty_value_1"]
}

Updating a single element state

@Config(url = "sample_entity:1/single_element/_update?rawPayload='hello'")

Resulting State of sample_entity:1
{
    "single_element": "hello",
    "collection": ["empty_value_1"]
}

As the path in this scenario is pointing to sample_entity:1/single_element, only the field single_element is subject to be updated with the value of rawPayload. The value for the field of collection is unaffected and retains it’s previous value.


Updating a collection element state

@Config(url = "sample_entity:1/collection/0/_update?rawPayload='hello'")

Resulting State of sample_entity:1
{
    "single_element": "empty_value",
    "collection": ["hello"]
}

As the path in this scenario is pointing to sample_entity:1/collection/0, only the collection element at index 0 for the field collection is subject to be updated with the value of rawPayload. The value for the field of single_element is unaffected and retains it’s previous value.


Updating a collection state

@Config(url = "sample_entity:1/collection/_update?rawPayload='\"new_value\"'")

Resulting State of sample_entity:1
{
    "single_element": "empty_value",
    "collection": ["empty_value_1", "new_value"]
}

As the path in this scenario is pointing to sample_entity:1/collection, only the field collection is subject to be updated with the value of rawPayload. Performing an _update call on a collection essentially performs an add operation. The value for the field of single_element is unaffected and retains it’s previous value.

Tip
If needing to replace elements within the collection as a whole, consider using _replace.

It is also permissible to pass an array as a payload.

@Config(url = "sample_entity:1/collection/_update?rawPayload='[\"new_value_1\", \"new_value_2\"]'")

Resulting State of sample_entity:1
{
    "single_element": "empty_value",
    "collection": ["empty_value_1", "new_value_1", "new_value_2"]
}

In this scenario, all of the array elements within rawPayload would be added to the collection.


Updating a complex object

@Config(url = "sample_entity:1/_update?rawPayload='{\"single_element\": \"hello\"}'")

Resulting State of sample_entity:1
{
    "single_element": "hello",
    "collection": ["empty_value_1"]
}

As the path in this scenario is pointing to sample_entity:1, both fields are subject to be updated with the value of rawPayload. Performing an _update call in this call preserves any previously existing state for fields not specified in the rawPayload. Consequently, the value for the field of single_element is updated, while collection is unaffected and retains it’s previous value.

Tip
Nested domain models can become quite large! Use _update when needing to target specific values within a domain entity.

Updating an entity state with the value of another entity state

@Config(url = "sample_entity:1/single_element/_update?rawPayload=<!json(PATH_TO_PARAM)!>)

where PATH_TO_PARAM is path which points to another param, relative to the location of single_element.

As the path in this scenario is pointing to sample_entity:1/single_element, only the field single_element is subject to be updated with the value of rawPayload. The value to be set will be the same as the state of the object identified by PATH_TO_PARAM.

Tip
This scenario can be used to copy a param’s state from one param to another.

Updating an entity state with post data

@Config(url = "sample_entity:1/single_element/_update")

Post Data

"hello"

Resulting State of sample_entity:1
{
    "single_element": "hello",
    "collection": ["empty_value_1"]
}

As the path in this scenario is pointing to sample_entity:1/single_element, only the field single_element is subject to be updated with the HTTP post data. The value for the field of collection is unaffected and retains it’s previous value.

5.2.4. Values Configuration Examples

The following samples showcase how to apply common scenarios using the framework’s @Values annotation.

Using a static Source implementation to define a set of values
@Values(SampleStaticSource.class)
@CheckBoxGroup
private String petTypes;

In this example, all of the values retrieved from SampleStaticSource.getValues will be displayed as a collection of checkboxes.

Using a url-based Source implementation to define a set of values
@Values(url="CLIENT_ID/ORG/p/staticCodeValue/_search?fn=lookup&where=staticCodeValue.paramCode.eq('/petType')")
@CheckBoxGroup
private String petTypes;

In this example, all of the values retrieved from the url defined in @Values will be displayed as a collection of checkboxes.