diff --git a/README.md b/README.adoc similarity index 71% rename from README.md rename to README.adoc index 4c843577b..fba4575d8 100644 --- a/README.md +++ b/README.adoc @@ -1,16 +1,19 @@ -# Dynamo += Dynamo + The Dynamo Web Application Accelerator is a software development framework initially developed by Open Circle Solutions that aims to increase productivity by using design principles such as convention over configuration, model-driven development and DRY (don’t repeat yourself). At the core of Dynamo is the concept of the Entity Model. The Entity Model is defined as the model describes the attributes and behaviour of an entity (or domain object) in your application. This entity model can then be used as the basis for creating forms, tables, search screens etc. The Entity Model of an entity can be automatically generated based on the properties of the attribute model (using sensible defaults as described by the convention over configuration principle) and can further be modified by using attributes and message bundle overwrites. The main goal is to reduce the amount of (boilerplate) code required to perform common actions like creating search screens and edit forms. -Complementing the Entity Model is a set of user interface components (widgets) that can be used to quickly construct screens for common use cases, and a number of base classes for the Data Access and Service layers. -The Framework is built around a number of proven and highly productive set of technologies: -* Vaadin as the user interface framework -* Spring as the application framework -* JPA2 for ORM -* QueryDSL for type-safe query generation -* Apache Camel for integration +Complementing the Entity Model is a set of user interface components (widgets) that can be used to quickly construct screens for common use cases, and a number of base classes for the Data Access and Service layers. +The Framework is built around a number of proven and highly productive set of technologies: +* Vaadin as the user interface framework +* Spring as the application framework +* JPA2 for ORM +* QueryDSL for type-safe query generation +* Apache Camel for integration + +Documentation of Dynamo can be found xref:dynamo-documentation/index.adoc[here]. -Interested in contributing? Check our [wiki](https://github.com/opencirclesolutions/dynamo/wiki)! +Interested in contributing? Check our https://github.com/opencirclesolutions/dynamo/wiki[wiki]! \ No newline at end of file diff --git a/dynamo-documentation/index.adoc b/dynamo-documentation/index.adoc new file mode 100644 index 000000000..f1b1b0f64 --- /dev/null +++ b/dynamo-documentation/index.adoc @@ -0,0 +1,5609 @@ += Dynamo +:toc: + +image:media/logo-dynamo.png[image] + +Web Application Accelerator Framework + +Developed, maintained and sponsered by + +image:media/logo-opencirclesolutions.svg[image,width=256,height=53] + +Main committer: Bas Rutten + +Copyright © 2014 - 2023 Open Circle Solutions BV. + +Nothing from this document may be copied and/or made public by use of +print, photocopy, microfilm or by any other means, without prior +permission of Open Circle Solutions BV. + + +== Introduction + +The Dynamo Web Application Accelerator Framework is a software +development framework developed by Open Circle Solutions that aims to +increase productivity by using design principles such as convention over +configuration, model-driven development and DRY (don’t repeat yourself). + +At the core of Dynamo is the concept of the _Entity Model_. The Entity +Model describes the attributes and behaviour of an entity (or domain +object) in your application. This Entity Model can then be used as the +basis for creating forms, , search screens etc. + +The _Entity Model_ of an entity is automatically constructed based on +the properties of the attributes of the entity (using sensible defaults +as described by the convention over configuration principle) and can +further be modified by using annotations and message bundle entries. The +main goal is to reduce the amount of (boilerplate) code required to +perform common actions like creating search screens and edit forms. + +Complementing the _Entity Model_ is a set of user interface components +(widgets) that can be used to quickly construct screens for common use +cases, and several base classes for the Data Access and Service layers. + +The Dynamo framework is built around a number of proven and highly +productive set of technologies: + +* JPA2 for ORM +* QueryDSL for type-safe query generation +* Spring Boot as the application framework +* Vaadin 14 as the user interface framework + +== The Entity Model + +=== Basics + +To create the entity model, you need access to an *EntityModelFactory*. +The *EntityModelFactory* is a Spring singleton and can be acquired by +injection (@Inject or @Autowired). You can also acquire a reference to +the *EntityModelFactory* by calling the *getEntityModelFactory* method +on the *ServiceLocator*_._ + +You can then acquire the *EntityModel* for a certain entity by calling +the *getModel(Class clazz)* method. This will retrieve the entity +model for the specified class, lazily constructed when needed. Note that +the entity model is effectively immutable and application-scoped (or +more precisely, it has the Spring Singleton scope, i.e. there is one +instance per Spring application context). + +This also means that the same entity model is in principle used by all +screens within an application. Since this would be too restrictive in +practice, it is possible to construct separate instances for separate +screens or use cases, by calling the *getModel(String reference, +Class clazz)* method. This will construct the Entity Model based on +the provided class (the second parameter), but it will allow you to +override certain attributes using message bundle entries (more on this +later). The *reference* string is the unique identifier that you assign +to the model (if you just call the *getModel* variant with a single +parameter, then the simple name of the class is used as the reference) +and which is then used as part of the message bundle entry. + +The classes for which you create an Entity Model must inherit from the +*com.ocs.dynamo.domain.AbstractEntity* class. See chapter 6 for details. + +=== Entity level settings + +The Entity Model supports several attributes that define how the entity +itself is represented. These include: + +* *displayName*: the name of the entity (e.g. “Car”) +* *displayNamePlural*: the name of the entity, in plural form (e.g. +“Cars”) +* *description*: textual description of the entity +* *displayProperty*: the name of the property to use when displaying the +entity inside e.g. a combo box. This property is also used as the title +of the entity that is placed above an edit form. +* *sortOrder*: how the entities are sorted by default when displayed in +a grid or list. The *sortOrder* consists of a comma separated list of +attribute names and sort directions, e.g. “name asc, age desc”. The +direction is optional and if it is not supplied, “asc” will be used by +default. This should be familiar to anybody who has worked with SQL. +* *filterProperty*: this is a new property that comes into play when you +define an entity field (like a combo box) that used a paginated data +model. When this is the case, the values will not all be loaded into +memory at once, but instead retrieved on-demand. In this case, you can +use the *filterProperty* setting to define the property to filter on +while searching the database. If this property is not explicitly set, +the application will revert to the *displayProperty* instead but it can +be necessary to override this e.g. if the *displayProperty* is a +calculated property. + +=== Attributes + +Every Entity Model consists of a number of *Attribute Models*. By +default, an Attribute Model is created for every valid property of the +entity. E.g. if you have an entity Person with properties “name” and +“age”, then the attribute model for the Person entity will contain two +attribute models, one for “name” and one for “age”. + +The following rules apply when constructing the attribute models: + +* An attribute model will be created for every public, non-static +no-parameter getter-method that follows the JavaBean naming convention +(e.g., “getAge()”; for Boolean or boolean properties, the getter may +also start with “is”, e.g. “isValid”). +* You can use Lombok to generate getters and setters for you. +* The entity class does not necessarily have to contain an actual field +corresponding to the property. This allows you to create attribute +models for read-only or composite properties (e.g. a “*getNameAndAge*()” +method which concatenates the name and age as a String). Note that such +an attribute will have to be defined as read-only. +* Certain attributes are ignored. Currently this includes only “version” +(used for JPA optimistic locking) and “class” (every object has a +“getClass()” method). +* Attributes can be simple (String, Integer, Long, enumerated types +etc.) or complex (a reference to another entity, a collection of +primitive values, or a collection of other entities). The Entity Model +generation is nested, which means that if a property of an entity is +again an entity, then an entity model for the nested property will also +be generated. This entity model is separate from the non-nested entity +model that would be constructed directly for the entity. +** Nearly all settings for nested models are treated the same as the +setting on the top level, but there is one exception: the *searchable* +setting on attributes of nested entities is ignored – this is because +when you are creating a search screen for an entity, you normally want +to search on the attributes of that entity, not on the nested +attributes. +* Getters that are annotated with _@_*AssertTrue* or _@_*AssertFalse* +are skipped (these are methods that are used for Bean Validations, not +properties for the meta model). + +An attribute model has a *name* attribute that is equal to the name of +the property. This *name* can be used to retrieve the attribute model +from the entity model: + +AttributeModel getAttributeModel(String attributeName); + +For a nested attribute model, the name of the model consists of the +concatenation of the names of the non-nested models separated by +periods. E.g. if you have a Person entity that has an attribute +*address* of type Address, then the “*houseNumber*” attribute model of +the address has the path “**address.houseNumbe**r”. + +Note: this should all make sense as it corresponds to the paths that are +used in e.g. JPQL queries and by the Vaadin data binding framework. + +=== Defaults and attribute overrides + +The Entity Model generation is based on sensible defaults and metadata. +E.g. the value of the *type* setting of an Attribute Model is directly +taken from the Java type of the property, and certain other aspects e.g. +whether the attribute is visible in a grid or can be used in a search +form are derived from this type (e.g. by default a complex attribute +will not be visible in a grid). + +In addition to this, the Entity Model generation process will take +certain JSR-303 annotations (e.g. *@NotNull, @Size*) into account. A +detailed explanation for each setting will be given below. + +If the default values are not sufficient, you can override them by using +annotations: + +* On the entity level, you can use the *@Model* annotation. +* On the attribute level, you can use the *@Attribute* annotation. + +The *@Model* annotation can be used like this: + +@Model(displayProperty = "description") + +*public* *class* Meeting *extends* AbstractEntity \{ + +The *@Attribute* annotation can be placed either directly on the +property, or on its getter method. Annotations placed on the getter +method override those placed on the property, to easily allow you to +override default behaviour in subclasses. Within a single entity class, +you can use both access types interchangeably. + +=== Message bundle overrides + +The annotation override mechanism is quite powerful, but it has some +drawbacks. E.g. it hard-codes certain String values (display name, +description) into your application and it does not directly allow for +internationalization. It also only allows you to override the behaviour +of the “default” Entity Model that is based directly on the class, and +not the behaviour of any derived Entity Models. + +If you need to override the behaviour of a derived Entity Model, you can +use the message bundle mechanism to achieve this. Message bundle +overrides must be placed in the *entitymodel.properties* file (create a +locale-specific version of this file if you need to; the normal Java +message bundle mechanic is supported). + +Message bundle entries in general have the following structure: + +*[Reference].[Attribute Model Name].[Attribute]=[Value]* + +Where: + +* *[Reference]* is the reference to the attribute model. This is the +simple class name of the entity for a standard entity model, and the +user-provided reference for a non-standard model. +* *[Attribute Model Name]* is the (possibly nested) name of the +attribute model. This is empty in case you are directly overriding a +setting of the Entity. +* *[Attribute]* is the setting that you want to modify. For a full list, +see the *EntityModel* class which contains constants that denote the +possible values (or refer to the sections below). +* *[Value]* is the desired value of the setting. + +The *[Attribute Model Name]* part must be omitted when you want to +directly set an attribute of the Entity Model itself. + +Some examples: + +*Organization.displayName=Criminal Organization* + +Sets the display name of the Organization entity to “Crimal +Organization”. + +*Person2.displayName=Gang Member* + +Sets the display name for Person in the “Person2” entity model to “Gang +Member” + +*Person.name.visible=true* + +Sets the visibility of the “name” attribute model to *true* + +*Person.address.street.readOnly=true* + +Sets the “read only” setting of the *address.street* attribute model (a +nested attribute model) to false. + +Please observe the following: + +* For Boolean values, use the (lower case) values “true” and “false”. +* For numeric values, simply use the String representation of the +numeric value. Use the period “.” as the decimal separator. +* For enumeration values, use the upper-case String representation of +the enumeration value. +* For dates and times, use the formats that are specified by the system +properties: +** “ocs.default.date.format” (dd-MM-yyyy) for dates +** “ocs.default.time.format” (HH:mm:ss) for times +** “ocs.default.datetime.format” (dd-MM-yyyy HH:mm:ss) +** “ocs.default.datetime.zone.format” (dd-MM-yyyy HH:mm:ssZ) +* For the *visible* and *visibleInGrid* settings, both the enumeration +values (SHOW/HIDE) and the boolean values *true* and *false* are +supported. + +== Attribute Model settings + +In this section, we explain all the supported settings of the attribute +model. + +=== AllowedExtensions + +In message bundle: *allowedExtensions = [String]* + +This setting can be used to specify the extensions of the files that are +accepted by the file upload component that is generated for a LOB +property. By default, its value is empty, which means there are no +restrictions on the file type. + +The value can be set to a comma-separated list of supported extensions, +e.g. *bmp,jpg,png.* Note that you must not include the “.” Characters. + +On the *@Attribute* annotation, you can use an array of String values +instead of a comma-separated String. Extensions are not case-sensitive +and you must not include the “.” character. + +=== AttributeType + +The *attributeType* setting is a classification of the type of the +property. It is determined automatically during the Entity Model +generation process and can have the following values: + +* *BASIC*: represents a simple property like a String, a number, a date +etc. +* *DETAIL*: a property that appears as a *@OneToMany* or *@ManyToMany* +relation in the entity class, e.g. the *orderLines* attribute inside an +*Order* entity will be considered a *DETAIL* attribute. +* *MASTER*: a property that appears as a *@OneToOne* or *@ManyToOne* in +the entity class. +* *LOB*: a property that is annotated with *@Lob* and represents a large +binary object (like a file or an image) +* *EMBEDDED*: used during the Entity Model construction process to +handle embedded properties (using the *@Embedded* annotation). This will +be covered in the _Advanced_ section. +* *ELEMENT_COLLECTION*: a property that is annotated with the +*@ElementCollection* annotation, i.e. a collection of simple values like +integers and Strings (note: the JPA spec does allow more complex element +collections, but these are not currently supported by Dynamo). + +The attribute type in combination with the Java type determines how a +certain attribute will be displayed on-screen in an edit form: + +* For a *BASIC* property, a simple user interface component will be +displayed, based on the type of the property: +** For String fields and numeric fields, a text field will be rendered. +For a String property, you can use the *textFieldMode* setting to render +a text area or a password field instead. For an Integer field, you can +use the *numberFieldMode* setting to render an “integer field” instead. +** For a Boolean, a check box will be rendered +** For a *LocalTime* attribute, a time picker will be rendered. +** For a *LocalDate* attribute, a date picker will be rendered. +** For a *LocalDateTime* attribute, a date/time picker will be rendered. +** For a *ZonedDatetime* attribute, a date/time picker (with a time zone +component) will be rendered. +** For an enumeration, a combo box will be created. You can use the +message bundle mechanism to specify translations for the enumeration +values (more on this below) +* For a *LOB* property, a file upload field will be created. +* For a *MASTER* property, by default a combo box that contains all the +possible values (as retrieved from the repository) will be created. You +can replace this by a lookup field or ListSelect by changing the value +of the *selectMode* setting. +* For a *DETAIL* property, by default a token field will be displayed. +This can be modified using the *selectMode* setting and can e.g. by +changed to a lookup field. +* For an *ELEMENT_COLLECTION* property, the application renders a simple +grid component that can be used to manage the collection of simple +values. + +Inside a search form the rendering is a slightly different: + +* For a *BASIC* property: +** For a String property, a text field is created. This text field can +be used to perform a search. You can use additional properties to toggle +the case sensitivity and whether to allow prefix or substring matches. +The *textAreaMode* setting is ignored in search forms. +** For a numeric or a date/time property, two search fields are +generated. These allow the user to perform an interval search (return +all values that are higher than the value in the first field and lower +than the value in the second field). This interval search is inclusive +(i.e. the boundaries match). If you do not want this behaviour, you can +change the value of the *searchForExactValue* setting to *true*_;_ if +you do this then only a single search field will appear. +*** For a property of type LocalDateTime or ZonedDateTime you can set +the *searchForDate* setting to true. If you do this then only a single +search field will be created. In this field you can select the date to +search on. +** For a Boolean property, a combo box is displayed. This combo box +contains three options: true, false, and “no value”. +** For an enumeration, a combo box containing all values of the +enumeration is displayed. +* For a *MASTER* property, by default a combo box containing all +possible values of the master entity is displayed. You can use the +*searchSelectMode* and/or *selectMode* settings to replace this by a +lookup field or a listbox. +* For a *DETAIL* property, by default a token field is created. You can +use the *searchSelectMode* and/or *selectMode* settings to replace this +by a lookup field or a combo box. +* *LOB* properties cannot be used in search forms. + +The *attributeType* setting also determines whether the property will be +visible by default: + +* In a results grid, by default only *BASIC* attributes will be visible. +Use the *visibleInGrid* attribute to show a complex attribute inside a +grid. +** For a *MASTER* property, the value of its *displayProperty* property +will be used. +** For a *DETAIL* property (remember, this represents a collection!), +the values of the *displayProperty* properties of all individual +entities in the collection will be displayed, separated by commas. +* a *MASTER* or *DETAIL* attribute will by default not be displayed +inside an edit form. You can change this by setting the +*complexEditable* setting of the attribute model to true. Note that in +some cases you may also have to implement some custom logic for dealing +with these properties. + +When displaying an enumeration value inside a combo box, the values that +are displayed inside the combo box are taken from the message bundle: + +*[Simple Class Name].[Enumeration Value]=[desired value]* + +E.g.: + +Reputation.REALLY_NOT_FEARSOME=Really not [.underline]#fearsome# + +Reputation.MILDLY_FEARSOME=Mildly [.underline]#fearsome# + +Reputation.FEARSOME=[.underline]#Fearsome# + +Reputation.EXTREMELY_FEARSOME=Extremely [.underline]#fearsome# + +=== Cascade + +In message bundle: + +*cascade.[index] = [path to attribute]* + +*cascadeFilterPath.[index] = [path to attribute]* + +*cascadeMode.[index] = BOTH | EDIT | SEARCH* + +The *cascade* setting can be used to define “cascading search” for +selection components. Cascading search means that when you select a +value in a certain selection component, the available values in another +component change based on this choice. E.g. suppose that you are editing +or searching for an *Organization* and you have selection fields for a +country and for a list of members of the organization– choosing a +country from the list will limit the values in member list to the people +that originate from that country. + +To set up cascading, you can define one or more *@Cascade* annotations +as part of the *@Attribute* annotation. Each @*Cascade* annotation takes +three parameters: + +* *cascadeTo* - this is the path to the attribute for which the +selection must change in response to a change of the annotated +attribute. In our example, the attribute to change is “members” (see +below). +* *filterPath* – this is the path that determines which filter to apply +to the selection component that is on the receiving end of the cascade +action. In our example, we want to filter a list of Person so that only +persons from a certain country are returned – this country is stored in +the *countryOfOrigin* property of the person so this is our filter path. +* *mode* - this specifies whether cascading should be enabled in search +forms, in edit forms, or in both cases. The default is *BOTH*. + +@Attribute(visibleInGrid = VisibilityType.*_SHOW_*, searchable = +*SearchMode.ALWAYS*, complexEditable = *true*, cascade = +@Cascade(cascadeTo = "members", filterPath = "countryOfOrigin", mode = +CascadeMode.*_EDIT_*)) + +*private* Country countryOfOrigin; + +@Attribute(searchable = *true*, complexEditable = *true*) + +*private* Set members = *new* HashSet<>(); + +Setting up cascading in a message bundle is a bit more involved. You can +do so by defining two or three messages like this: + +Organization.countryOfOrigin.cascade.1=members + +Organization.countryOfOrigin.cascadeFilterPath.1=countryOfOrigin + +Organization.countryOfOrigin.cascadeMode.1=EDIT + +The *cascade* message defines the property to apply the cascading to – +the *cascadeFilterPath* is the property path to filter on and the +optional *cascadeMode* determines when to apply the cascading. Each +message must end with a number that is used to group the messages +together. The numbering starts at “1” and must use increments of 1, so +if e.g. you want to define another cascade for the same attribute, that +would look like this: + +Organization.cascade.2=[some other property to cascade] + +Organization.cascadeFilterPath.2=[some other path] + +Organization.cascadeMode.2=EDIT + +=== ClearButtonVisible + +In message bundle: *clearButtonVisible = true | false* + +This setting can be used to specify whether a “clear” button (by default +a big X) must appear at the end of a component. This button can be used +to easily clear the contents of the component. This is supported for +most components, but not for e.g. check boxes and list boxes. Also, for +the lookup field the clear button is always present and hence this +setting will be ignored for that component. + +=== CollectionTableFieldName + +In message bundle: *N/A* + +This setting is used to store the value of the name of the field inside +the collection table that is used to store an attribute of type +*ELEMENT_COLLECTION*. The value for this setting is normally determined +by the “name” specified on the *@Column* annotation. E.g., in this case: + +@ElementCollection + +@CollectionTable(name = "person_lucky_numbers") + +@Column(name = "lucky_number") + +@Attribute(complexEditable = *true*, minValue = 10, maxValue = 25) + +*private* Set luckyNumbers = *new* HashSet<>(); + +the value of the *collectionTableFieldName* setting is automatically set +to “lucky_number” + +This setting is used internally by the framework when for searching +inside the collection table and cannot be modified directly. + +=== CollectionTableName + +In message bundle: *N/A* + +This setting is used to store the value of the name of the collection +table that is used to store an attribute of type *ELEMENT*_*COLLECTION*. +The value for this setting is normally determined by *name* specified on +the *@CollectionTable* annotation. E.g., in this case: + +@ElementCollection + +@CollectionTable(name = "person_lucky_numbers") + +@Column(name = "lucky_number") + +@Attribute(complexEditable = *true*, minValue = 10, maxValue = 25) + +*private* Set luckyNumbers = *new* HashSet<>(); + +the value of the *collectionTableName* setting is set to +“person_lucky_numbers”. + +This setting is used internally by the framework when for searching +inside the collection table. You do not normally have to modify it. + +=== ComplexEditable + +In message bundle: *complexEditable = true | false* + +This setting can be used to determine whether a complex property (i.e. +of type *MASTER* or *DETAIL*) shows up in an edit form. Note that in +some cases, some additional code might be needed to properly deal with +these properties (e.g. you might have to create a *DetailsEditGrid* +yourself). + +This setting defaults to *false*_._ + +When set to *true*, for an attribute of type *DETAIL*, by default a +token field will be rendered. This generally works fine for many-to-many +relations but will not work if the details must be manually created. In +this case, you need to write some custom code. This will be covered +later in the manual (see the section about the *DetailsEditGrid*). + +For an attribute of type *MASTER*, by default a combo box will be +rendered. You can modify this by changing the value of the *selectMode* +setting. It is also possible to provide a custom implementation, e.g. a +nested grid or nested form (this will be covered later in the manual). + +=== Currency + +In message bundle: *currency = true | false* + +This setting can be used to specify that a numeric field (currently only +supported for BigDecimal properties) contains a currency value. If this +setting is changed to *true* for a property, then a currency symbol will +be displayed in front of the value of the property. The currency symbol +defaults to the Euro sign (€) but can be modified using the system +property *ocs.default.currency.symbol*. + +=== CurrencyCode + +In message bundle: *currencyCode = [string]* + +=== Custom + +It is also possible to specify custom attribute settings. This can be +done by using the @CustomSetting annotation which can be applied as +follows: + +@Attribute(main = *true*, textFieldMode = +AttributeTextFieldMode.*_TEXTAREA_*, custom = \{ + +@CustomSetting(name = "customString", value = "string"), + +@CustomSetting(name = "customInt", value = "4", type = +CustomType.*_INT_*), + +@CustomSetting(name = "customBool", value = "true", type = +CustomType.*_BOOLEAN_*) }) + +*private* String name; + +In the above, we specify three custom settings named “customString”, +“customInt” and “customBool”. Each setting consists of a name, a value +and a type. The type can be omitted in which case it defaults to +CustomType.STRING. + +You can retrieve the value of a custom setting by calling the +*AttributeModel.getCustomSetting(String name)* method. + +You can also add custom settings via the message bundle. This looks as +follows: + +Organization.name.custom.1=customString + +Organization.name.customValue.1=countryOfOrigin + +Organization.name.customType.1=INT + +For each custom setting, you specify three properties, “custom”, +“customValue” and “customType”. Each property must contain a number, +starting at 1, to indicate which properties belong together. Custom +settings defined in the message bundle overwrite the settings defined +using the @CustomSetting annotations. + +=== DateType + +In message bundle: *dateType = TIMESTAMP | DATE | TIME* + +The *dateType* setting can be used to determine how an attribute of type +*LocalTime, LocalDate*, *LocalDateTime* or *ZonedDateTime* will be +managed: + +The allowed values are: + +* *TIMESTAMP*: In this case the application renders a date picker that +includes a time selection component. +* *DATE*: in this case the application renders a date picker without a +time selection component. +* *TIME*: in this case a custom time selection component is rendered. + +By default, the value of the *dateType* setting is derived from the Java +type of the property. You do not normally have to manually override it. + +Dynamo does not support the Legacy Java date types (java.util.Date and +java.sql.Date). + +=== DefaultValue + +In message bundle: *defaultValue = [String]* + +The *defaultValue* setting can be used to set the default value that +appears inside the user interface component when creating a new entity. + +You always specify this setting as a String; if the value must be +converted to a decimal number, use the period (“.”) as the decimal +separator. For enumeration values, use the upper-case String +representation of the desired value. + +For date attributes, use the String representations according to the +system properties *ocs.default.date.format* _(dd-MM-yyyy),_ +*ocs.default.time.format* _(HH:mm:ss),_ *ocs.default.datetime.format* +_(dd-MM-yyyy HH:mm:ss)_, *ocs.default.datetime.zone.format* (dd-MM-yyyy +HH:mm:ssZ) + +=== Description + +In message bundle: *description = [String]* + +The *description* setting determines the value of the tooltip that the +user will see when hovering over the input field for the property. + +If not explicitly set, it will default to the value of the *displayName* +setting**.** + +=== DisplayFormat + +In message bundle: *displayFormat = [String]* + +The *displayFormat* setting indicates how date/time values will be +formatted. It is supported for attributes of a Java 8 date/time type +(LocalDate, LocalTime etc). + +The value of the *displayFormat* attribute must be a valid Java +data/time formatting pattern, e.g. “dd-MM-yyyy”, but you can use +different separators like “dd/MM-yyyy” or use formats like “yyyy-MM-dd”. + +If you do not specify a displayFormat for an attribute, the framework +will default to the value of the *ocs.default.date.format, +ocs.default.time.format,* *ocs.default.datetime.format,* or +*ocs.default.datetime.zone.format* system variables depending on the +*dateType* of the attribute model. + +Note: this currently only applies to how the date/time value will be +displayed in read-only mode, and to setting default values. Inside a +date picker or time picker, the formatting will depend on the user’s or +application’s locale. This is due to a limitation in the underlying +framework. + +The description is localized – you can use message bundles to define +custom display names depending on the locale of the user. + +=== DisplayName + +In message bundle: *displayName=[String]* + +The *displayName* setting determines how the attribute will be named +onscreen. By default, it is derived from the *name* setting, replacing +CamelCase notation by spaces and then capitalizing individual words, +e.g. “mininumAge” will be translated to “Minimum Age”. You can use the +system property *ocs.capitalize.words* and set it to *false* so that +only the first word will be capitalized. + +The display name is localized – you can use message bundles to define +custom display names depending on the locale of the user. + +=== EditableType + +In message bundle: *editableType = READ_ONLY | CREATE_ONLY | EDITABLE* + +The *editableType* setting specifies when an attribute can be edited. +The default value *EDITABLE* means that the attribute can always be +edited. *CREATE_ONLY* means that the attribute can only be edited when +creating a new entity. *READ_ONLY* means that the property is read-only +and cannot be edited in a the interface. + +Note: in previous versions of the framework, this setting was called +“readOnly” and could only be used to toggle between editable and +read-only state. + +=== Email + +In message bundle: *N/A* + +The *email* setting can be used to specify that a field must contain a +valid email address. It is automatically set to *true* if the property +is annotated with the (custom) *@Email* annotation. + +=== Embedded + +In message bundle: *embedded = true | false* + +The *embedded* setting can be used to specify that a certain (complex) +attribute must be treated as an embedded attribute. An embedded +attribute is treated as an integral part of the entity, and no separate +entity model is created for an embedded attribute. + +By default, any attribute annotated with the JPA *@Embedded* attribute +will have its *embedded* setting set to true but you can override this +using the *@Attribute* annotation or a message bundle. + +// inside Gift.java + +@Attribute(embedded = *true*) + +@JoinColumn(name = "logo") + +@OneToOne(fetch = FetchType.*_LAZY_*, optional = *false*, cascade = +CascadeType.*_ALL_*) + +*private* GiftLogo logo = *new* GiftLogo(); + +// inside GiftLogo.java + +@Lob + +@Attribute(image = *true*, fileNameProperty = "logo.fileName") + +*private* *byte*[] image; + +@Attribute(readOnly = *true*, showInTable = VisibilityType.*_HIDE_*) + +*private* String fileName; + +In the example above, we see a *Gift* class with an embedded attribute +“logo” of type *GiftLogo.* The *GiftLogo* class contains several fields +like “image” and “filename”. + +By setting *embedded* to true, we effectively make sure that there is no +entity model for *GiftLogo*, and all the attributes from *GiftLogo* are +made part of the entity model of the embedding class, *Gift.* When you +want to access an attribute model for an attribute inside *GiftLogo,* +you must use its full path, e.g. “logo.fileName” or “logo.image” rather +than just “fileName’ or “image”. + +=== FileNameProperty + +In message bundle: *fileNameProperty = [property name]* + +The *fileNameProperty* setting can be used to specify the name of the +property that is used to store the name of an uploaded file after a file +upload. This setting is intended to be used on attributes of type *LOB*: + +@Lob + +@Attribute(image = *true*, fileNameProperty = "logo.fileName") + +*private* *byte*[] image; + +// hide in grid to prevent fetch issues + +@Attribute(readOnly = *true*, visibleInGrid = VisibilityType.*_HIDE_*) + +*private* String fileName; + +By default, if you define an attribute of type LOB, then the application +will render a file upload component for editing this attribute. The byte +content of the uploaded file will be stored in the property itself, but +the file name of the file that was uploaded will not be persisted. + +If you want to store the file name as well, simply create another +property (of type String) and then set the *fileNameProperty* of the +*@Attribute* annotation that is placed on the property that holds the +byte content to point to this property. The framework will then store +the name of the uploaded file in this property as part of the file +upload process. + +Note that the actual “fileName” property is annotated as “readOnly” +since it is automatically set by the framework and does not need to be +modified by the user. + +=== GridSelectMode + +In message bundle: *gridSelectMode = COMBO | LOOKUP | LIST | TOKEN* + +The *gridSelectMode* setting is used to specify how a component for an +attribute of type *MASTER* or *DETAIL* will be rendered inside a grid +(*EditableGridLayout*). + +By default, the value of the *gridSelectMode* setting is equal to the +value of the *selectMode* but you can change it explicitly if you want a +different component to be rendered inside a grid. + +It is typically not advisable to use a component that takes up more than +one row inside an editable grid, so it is probably best to stick to +*COMBO* or *LOOKUP*. + +=== GroupTogetherWith + +In message bundle: *groupTogetherWith = [comma separated list of +attribute names]* + +The *groupTogetherWith* setting can be used to specify that the input +components for several attributes must be placed together on a single +row in an edit form. This is a good way of saving screen space. The +value of this setting consists of a list of attribute names. The input +components for these attributes will be placed behind the original +attribute, in the order in which they are defined. + +Here you see an example of how the “region” attribute + +@Attribute(complexEditable = *true*, visibleInGrid = +VisibilityType.*_SHOW_*, searchable = *true*, groupTogetherWith = +\{"region"}) + +*private* Country country; + +And this is the input form that will be generated: + +image:vertopal_1eda064bf39f485baa9f64be38994881/media/image3.png[image,width=560,height=64] + +You can still use all available settings to modify the behaviour of the +components for the “extra” attributes that are placed behind the first +attribute. The framework makes sure that the extra attributes do not +show up more than once. + +Note that for this to work properly, the attribute that the +*groupTogetherWith* setting refers to must occur in the attribute order +*after* the attribute that does the referring (in term of the example +above, *region* must come after *country*)*.* If this rule is not +observed, then a warning message will be logged during the entity model +creation process and the behaviour is undefined. + +You can modify how the grouped together fields scale when the screen is +made larger or smaller by using the *setGroupTogetherMode* and +*setGroupTogetherWidth* methods found on many layouts. By default, the +values of these properties are determined by the system properties +*ocs.default.group.together.mode* and +*ocs.default.group.toghether.width* . The mode supports two values, +*pixel* and *percentage.* Pixel means that each field will be the value +of *groupTogetherWidth* pixels wide. Percentage means that every field +will take up an equal percentage of the available space. + +=== IgnoreInSearchFilter + +In message bundle: *ignoreInSearchFilter = true | false* + +This setting can be used for rare occasions in which you want to use an +attribute inside a search form (e.g. for setting up cascading) but you +want to ignore the selected value when actually performing a search. + +=== Image + +In message bundle: *image = true | false* + +This setting can be used on a LOB property to specify whether it +represents an image. By default, this setting has the value *false*_._ +If set to *true*, the application will try to render a preview image of +the value (byte contents) of the property. + +=== Main + +In message bundle: *main = true | false* + +The Boolean setting *main* can be used to specify that a certain +property is the main property of an entity. The main property is the +property that will be used as the default field to search on using a +quick search field inside a SplitLayout. + +By default, the first encountered property of type String will be marked +as the main attribute but you can use this setting to override the +default. + +In the past, the main attribute was also used for creating the title +that appears above a search form. However, currently the displayProperty +from the @Model annotation is used for this. + +=== MaxLength + +In message bundle: *maxLength = [integer value]* + +The *maxLength* setting can be used to specify the maximum allowed +length of the individual values inside a collection of Strings for a +property of type *ELEMENT_COLLECTION*. + +=== MaxLengthInGrid + +In message bundle: *maxLengthInGrid = [integer value]* + +The *maxLengthInGrid* setting can be used to set the maximum length of +the value of a String property when it is displayed inside a grid – if +the value of the property is longer than this, the value will be +truncated after the first *maxLengthInGrid* characters. This can help +save space in grids. + +=== MaxValue + +In message bundle: *maxValue = [integer value]* + +The *maxValue* setting can be used to specify the maximum allowed value +of the individual numeric values that make up the value of a property of +type *ELEMENT_COLLECTION*. + +This is an example of the use of *maxValue* on a collection of Integers: + +@ElementCollection + +@CollectionTable(name = "person_lucky_numbers") + +@Column(name = "lucky_number") + +@Attribute(complexEditable = *true*, minValue = 10, maxValue = 25) + +*private* Set luckyNumbers = *new* HashSet<>(); + +=== MemberType + +In message bundle: *N/A* + +The *memberType* setting can be used to explicitly set the member type +(i.e. the type of an individual entity) of an attribute type *DETAIL*. +Normally, the member type can be derived from the source code +automatically, but there are certain cases in which this is not +possible, e.g. when working with a property that does not directly map +to a member field, but rather returns a collection that is calculated on +the fly. In this case, you can use the *memberType* to set the exact +type of the members of the collection. + +This setting is only supported as an annotation override. + +=== MinLength + +In message bundle: *minLength = [integer value]* + +The *minLength* setting can be used to specify the minimum allowed +length of individual String values inside a property of type +*ELEMENT_COLLECTION*. + +=== MinValue + +In message bundle: *minValue = [integer value]* + +The *minValue* setting can be used to specify the minimum allowed value +of the individual numeric values that make up the value of a property of +type *ELEMENT_COLLECTION*. + +An example of the use of *minValue* on a collection of Integers: + +@ElementCollection + +@CollectionTable(name = "person_lucky_numbers") + +@Column(name = "lucky_number") + +@Attribute(complexEditable = *true*, minValue = 10, maxValue = 25) + +*private* Set luckyNumbers = *new* HashSet<>(); + +This specifies that the individual values inside the collection must all +be greater than or equal to 10. + +=== MultiSelectMode + +In message bundle: *multiSelectMode = CHECKBOX | ROWSELECT* + +The *multiSelectMode* can be used to specify the way in which multiple +items can be selected inside a search dialog (used when the select mode += LOOKUP). When this setting is set to *CHECKBOX*, each row is prefixed +with a check box. When it is set to *ROWSELECT* then the user can select +multiple rows by Ctrl- and Shift-clicking on the rows. + +The default value of this setting depends on the value of the system +property ocs.use.grid.selection.checkboxes (if set to true, then the +default will be CHECKBOX). + +=== MultipleSearch + +In message bundle: *multipleSearch = true | false* + +The *multipleSearch* setting can be used to allow searching on multiple +values at once for attributes of type *MASTER*. By default, you would +only be allowed to search on a single value at a time for such +attributes, but if you set this setting to *true* you will be allowed to +select multiple values (and the application will return all entities +that match at least one of the selected values). This will also change +the component that is rendered by default from a combo box to a token +field. + +You can use the *searchSelectMode* to further modify the type of the +search component that is rendered (you can also use a lookup field). + +Default searching for many-to-one property *–>* combo box: + +@Attribute(visibleInGrid = VisibilityType.*_SHOW_*, searchable = *true*, +complexEditable = *true*) + +*private* Country countryOfOrigin; + +image:vertopal_1eda064bf39f485baa9f64be38994881/media/image4.png[image,width=508,height=140] + +Multiple search enabled –> lookup field: + +@Attribute(visibleInGrid = VisibilityType.*_SHOW_*, searchable = *true*, +multipleSearch = *true*, complexEditable = *true*) + +*private* Country countryOfOrigin; + +Multiple search on *MASTER* attributes is currently only supported when +using *LOOKUP* select mode (*TOKEN* is not supported). + +image:vertopal_1eda064bf39f485baa9f64be38994881/media/image5.png[image,width=498,height=71] + +You can also use the *multipleSearch* setting for searching on distinct +String values – As you have seen before, by default the framework will +generate a text field. However, if you set *multipleSearch* to true and +*searchSelectMode* to *TOKEN* then the application will render a token +field that allows you to enter multiple values to search on. The token +field will contain a list of all distinct values of the property from +which the user can choose. + +@Attribute(searchable = *true*, main = *true*, maxLengthInGrid = 10, +multipleSearch = *true*, searchSelectMode = +AttributeSelectMode.*_TOKEN_*) + +*private* String name; + +image:vertopal_1eda064bf39f485baa9f64be38994881/media/image6.png[image,width=499,height=271] + +=== Navigable + +In message bundle: *navigable = true | false* + +The *navigable* setting can be used to specify that a hyperlink for +in-application navigation must be rendered for a certain property. This +works both in a grid and in a detail form. This is only supported for +properties of type *MASTER* (i.e. many-to-one relations). When a +property is declared to be navigable, a button that looks like a +hyperlink will be rendered to represent it (in a grid or in a form in +edit mode) and clicking this button will kick of the +*navigateToEntityScreen* method (see below). + +In order to use this form of navigation, you need to do three things: + +* Set the *navigable* setting to *true.* +* In the *UIHelper* class, which can be injected in your own classes, +call the *addEntityNavigationMapping* method. This method takes as its +parameters the class for which you want to defined a navigation, and a +Java 8 consumer that defines the action to be take. Typically, what you +will want to do in this method is assign the selected object (the +argument of the consumer) to a field on your UI, and then navigate to +the appropriate view. +* In the view to which you navigate, you need to retrieve the object you +just stored from the *UIHelper*, and do something with it. Typically, +this will involve calling the *edit* method on a composite component, +which will cause the component to display an edit form containing the +selected entity. + +=== NumberFieldMode + +In message bundle: *numberFieldMode* = *TEXTFIELD | NUMBERFIELD* + +The number field mode can be used to set the field mode to use for a +numeric property (currently only supported for fields of type “int” or +“Integer”). When set to “TEXTFIELD” (the default), a text field will be +rendered to edit the attribute. This text field includes validation, but +it is still possible to input non-numeric characters. When set to +“NUMBERFIELD” a special numeric input component (IntegerField) will be +used instead. This component only accepts numeric input and also comes +with a set of plus and minus buttons. + +image:vertopal_1eda064bf39f485baa9f64be38994881/media/image7.png[image,width=560,height=70] + +The default value of this setting can be modified by changing the system +variable *ocs.default.number.field.mode*. + +=== NumberFieldStep + +In message bundle: *numberFieldStep* = ** + +The number field mode can be used to set the step size to be used for a +number field (see the previous section). The default value is 1, but you +can set this to any positive integer. + +=== PagingMode + +In message bundle: *pagingMode = PAGED | NON_PAGED* + +The *pagingType* setting is used to specify how the items within an +entity component (combo box, token field, or list select) will be +retrieved. When set to *PAGED* the items will be retrieved in an +on-demand matter (one page at a time). When set to *NON_PAGED* all the +items will be retrieved at once. If there are many items in the +collection, this might cause memory issues. + +With regard to filtering the items within the component, when you use +the *NON*_*PAGED* setting, the application will simply search inside the +descriptions of the items (which are simply the values of the +*displayProperties* of the items). When using the PAGED setting, the +value of the *filterProperty* setting of the entity model will be used +instead. + +Note: if you don’t specify a value for the *filterProperty,* the value +of the *displayProperty* will be used instead. If this property does not +exist in the database, you will get an error at run-time when the users +tries to filter the results. + +The default value of this setting can be set by using the system +property *ocs.default.paging.mode.* + +=== Percentage + +In message bundle: *percentage = true | false* + +The *percentage* setting is used to indicate whether a numeric value +represents a percentage. By default, this attribute has the value +*false*_._ If set to *true*, then the value of the property will be +displayed with a “%” sign following it, both in read-only and edit mode. + +The percentage sign is purely cosmetic; the actual value of the property +is not converted or changed in any way. + +=== Precision + +In message bundle: *precision = [Numeric value]* + +The *precision* setting determines the number of digits will be shown +behind the decimal separator when displaying non-integer numbers. By +default, it is set to *2* but you can change this by changing the value +of the system property *ocs.default.decimal.precision*. + +=== Prompt + +In message bundle: *prompt=[String]* + +The *prompt* setting determines the value of the prompt that shows up +inside the editable field for the property. + +If not set, it defaults to the value of the *displayName* setting. + +Note: in the Vaadin source code, this property is now called +*placeholder.* It is unfortunately not supported on all components. + +=== QuickAddAllowed + +In message bundle: *quickAddAllowed = true | false* + +The *quickAddAllowed* setting can be used to allow the creation of +entities directly from inside a form, for a UI component that is used to +manage a *MASTER* or *DETAIL* relation (e.g. a list select, combo box, +token select, or lookup field). Normally, in such a case a combo box, +list select or similar component will be rendered (depending on the +value of the *selectMode* setting) + +If you set the *quickAddAllowed* setting to *true*, an *Add* button will +be rendered next to the edit component for the property. When pressed, +this button will bring up a dialog that will allow the user to create a +new entity. + +When the user presses the *OK* button in this dialog, the framework will +create a new entity based on the contets of the dialog. This comes with +an automatic check for duplicate values, provided you have configured +this on the underlying service. + +As an example, consider the following: + +@NotNull + +@JoinColumn(name = "country_of_origin") + +@ManyToOne(fetch = FetchType.*_LAZY_*) + +@Attribute(complexEditable = *true*, quickAddAllowed = *true*, +selectMode = AttributeSelectMode.*_LOOKUP_*) + +*private* Country countryOfOrigin; + +Here, we define a “countryOfOrigin” property that is of type “Country”.. +We set the *quickAddAllowed* to “true”. Once the user now starts the +application, they will see an “Add” button behind the field that can be +used to create a new country. Once pressed, the button will bring up the +following dialog: + +image:vertopal_1eda064bf39f485baa9f64be38994881/media/image8.png[image,width=559,height=203] + +The user can now enter the properties of the country in the popup – once +the user presses the “OK” button the application will store the new +Country, add it to the options that are present in the selection +component, and select it. + +The application will carry out an automatic check for duplicates when +the user tries to save the entity (based on the *findIdenticalEntity* +functionality), and will then look for an error message stored under the +“.not.unique” key in order to display an error +message. E.g. in the example above, you should add a +“Country.not.unique” message to the message bundle. + +=== ReplacementSearchPath + +In message bundle: *replacementSearchPath = [desired string value]* + +The *replacementSearchPath* setting can be used to modify the search +path that is used when translating search filters into a query – it can +happen that you are using a derived property in your search screen (e.g. +to allow searching on only a subset of values) and when you take no +further action this will produce an error when carrying out the query +since the property is not known in JPA. In cases like this, you can use +the *replacementSearchPath* setting to specify the alternate (real) path +to use during the search. + +A common use case for this occurs when you are using the functionality +from the *dynamo-functional-domain* module, and you have multiple +many-to-many relations from an entity to various Domain subclasses. In +this case, you can model a single many-to-many relation between the +entity and the domain table as follows: + +@ManyToMany + +@JoinTable(name = "product_domain", joinColumns = \{ @JoinColumn(name = +"programme") }, inverseJoinColumns = \{ @JoinColumn(name = "domain") }) + +*private* Set domains = *new* HashSet<>(); + +You can then retrieve and set the values for a certain domain subclass +as follows: + +@[.underline]#Attribute#(memberType = Channel.*class,* +replacementSearchPath = "domains") + +*public* Set getChannels() \{ + +*return* DomainUtil._filterDomains_(Channel.*class*, domains); + +} + +*public* *void* setChannels(Set channels) \{ + +DomainUtil._updateDomains_(Channel.*class*, domains, channels); + +} + +In the above, we explicitly set the *memberType* so that the framework +knows the type of the elements of the collection, and set the +*replacementSearchPath* to “domains” so that when the framework +generates a query, it will use the *domains* relations. + +Note the getter and setter methods for the “channels” attribute don’t +directly manipulate the *domains* property but rather use methods from +the *DomainUtil* class that make sure the values are retrieved and +updated correctly. + +=== ReplacementSortPath + +In message bundle: *replacementSortPath = [desired string value]* + +You can use this setting to override the path to sort on when the user +clicks on a column header in a search results grid. By default, the +application will then sort on the exact path to the property, but if the +*replacementSortPath* is set, that value will be used instead. + +=== RequiredForSearching + +In message bundle: *requiredForSearching = true | false* + +The *requiredForSearching* setting determines if a property is required +before a search can be carried out inside a *SearchLayout.* If you +create a search form that contains properties that have +*requiredForSearching* set tot true, you will not be able to carry out a +search (i.e. the *Search* button will be disabled) until you provide a +search value for these properties. + +The default value of this setting is *false.* + +When you do want to perform a check before carrying out a search but the +fields that are required are dynamic (i.e. they depend on search +values), you can instead use the callback method *validateBeforeSearch* +offered by the *SearchLayout* components. This is described in more +detail later. + +=== Searchable + +In message bundle: *searchable = NONE | ALWAYS | ADVANCED* + +*searchable = NONE | ALWAYS | ADVANCED* + +The *searchable* setting determines whether a property will show up in a +search form on a search screen. By default, it is set to *NONE* which +means it will not show up in a search form. Setting this property to +*ALWAYS* means it will always show up in a search form. Setting it to +*ADVANCED* means it will only show up in search forms for which +“enabledAdvancedSearchMode” has been set to true (via the FormOptions) +and only when the user activates the “advanced mode” in the screen. + +=== SearchCaseSensitive + +In message bundle: *searchCaseSensitive = true | false* + +The *searchCaseSensitive* setting determines whether search operations +on the property are case sensitive. The default is given by the system +property *ocs.default.search.case.sensitive* which defaults to “false”. +This setting is only used for attributes of type String and ignored in +all other cases. + +On the attribute, you can use the values BooleanType.TRUE and +BooleanType.FALSE. + +=== SearchDateOnly + +In message bundle: *searchDateOnly = true | false* + +The *searchDateOnly* setting determines whether search operations on an +attribute that represents a date/time (either LocalDateTime or +ZonedDateTime) are carried out using only date selection fields rather +than time selection fields. + +By default, when searching on an a date/time attribute, the application +will render two timestamp search fields that allow you to specify a +search interval. When you change this setting to *true* then instead the +application will render to date selection fields. Searching using these +date selection fields will return any time stamps that fall within the +specified date interval (inclusive). E.g. if you enter the search values +*2020-04-04* to *2020-04-06* you will return any records for which the +time stamp value matches the interval from *2020-04-04 00:00:00* up to +*2020-04-06 23:59:599999999* + +=== SearchForExactValue + +In message bundle: *searchForExactValue = true | false* + +This setting determines whether to search for an exact value rather than +a range, when searching for numeric or date values. By default, for such +a field two search fields will be rendered: one for the lower bound of +the range to search for, and one for the upper bound of the range to +search for. + +By default, this setting has the value *false.* If set to *true,* then +instead of the two search fields, a single field will be rendered that +allows the user to search for an exact value. + +=== SearchPrefixOnly + +In message bundle: *searchPrefixOnly = true | false* + +The *searchPrefixOnly* setting determines whether search operations on +the property check only for a prefix match. If this is set to *true*, +then searching for e.g. “a” will only match “almond” (“a” appears at +start) but not “walnut” (“a” appears in the middle). If set to false, +then “a” will match both “almond” and “walnut”. + +By default, this setting has the value *false*. This setting is only +used for attributes of type String and ignored in all other cases. + +=== SearchSelectMode + +In message bundle: *searchSelectMode = COMBO | LOOKUP | LIST | TOKEN* + +The *searchSelectMode* setting is used to specify how the component for +searching an attribute of attribute type *MASTER* or *DETAIL* will be +rendered (inside a search form). It can also in some instances be used +for searching on a *BASIC* attribute of type String. + +By default, the value of the *searchSelectMode* setting is equal to the +value of the *selectMode* but you can change it explicitly if you want a +different component to be rendered inside a search form. + +The following restrictions apply: + +* For a property of type *MASTER* you can use the values *COMBO*, +*LOOKUP* or *LIST*. +* For a property of type *DETAIL* you can use the values *LOOKUP* and +*TOKEN* +* For an attribute of type String, you can use the value *TOKEN*, in +combination with setting the *multipleSearch* to *true.* This will +produce a search field that allows you to select multiple values from +among a list of all the distinct values of the attribute. + +=== ShowPassword + +In message bundle: *showPassword = true | false* + +The *showPassword* setting can be used to specify whether a button to +toggle between showing and hiding a password inside a password field +will be shown. When this setting has the value *false* (the default), +then the contents of the password field will always be shown obscured. +When this setting has the value *true,* a button to toggle between an +unobscured and obscured view is displayed at the end of the input +component. + +image:vertopal_1eda064bf39f485baa9f64be38994881/media/image9.png[image,width=197,height=68] + +=== Sortable + +In message bundle: *sortable = true | false* + +The *sortable* setting can be used to specify whether a grid can be +sorted on the attribute. By default, it is set to *true* for all +attributes. + +=== TextAreaHeight + +In message bundle: *textAreaHeight = [string value]* + +The *textAreaHeight* setting can be used to specify the height of a text +area. It is only used when *textFieldMode* is set to *TEXTAREA* and +accepts all ways to specify height in CSS/HTML (e.g. “px”, “em” etc). +The default value is the value of the system property +*ocs.default.text.area.height* + +=== TextFieldMode + +In message bundle: *textFieldMode = TEXTAREA | TEXTFIELD | PASSWORD* + +The *textFieldMode* setting can be used to specify whether to render +either a text field, a text area or a password field for editing an +attribute of type String. The default is *TEXTFIELD*_._ When value +*TEXTAREA* will be ignored inside a search form an inside an editable +grid. The value *PASSWORD* will be ignored inside a search form. + +=== ThousandsGroupingMode + +In message bundle: *thousandsGroupingMode = NEVER | ALWAYS | VIEW | +EDIT* + +The *thousandsGroupingMode* setting can be used to indicate when a +thousand-grouping separator must be used when formatting a floating +point number: + +* *NEVER* indicates that the thousands grouping separator must never be +used for this attribute. +* *ALWAYS* indicates that the separator must be used both in view and +edit mode. +* *VIEW* indicates that the separator must only be used when the +attribute value is displayed in read-only mode. +* *EDIT* indicates that the separator must only be used when the +attribute value is displayed in edit mode. + +=== TrimSpaces + +In message bundle: *trimSpaces = true | false* + +This indicates whether extraneous space characters will be trimmed from +the start and end of the input inside text areas and text fields. This +defaults to false but can be modified by changing the value of the +*ocs.trim.spaces* system property. + +On the @Attribute annotation, you can use the “trimSpaces” setting which +supports the values INHERIT, TRIM and NO_TRIM. When INHERIT is used, it +will just use the value of the system property. With TRIM and NO_TRIM +you can either enable or disable the trimming for this specific +attribute. + +=== TrueRepresentation and FalseRepresentation + +In message bundle: *trueRepresentation = [desired value]* + +____ +*falseRepresentation = [desired value]* +____ + +The *trueRepresentation* and *falseRepresentation* settings can be used +to modify how a boolean value is displayed in read-only mode. By +default, such a value will simply by displayed as “true” or “false”, but +this can be overruled by setting respectively the *trueRepresentation* +and *falseRepresentation* values**.** + +This setting does nothing in edit mode, since in that case a checkbox +will be rendered. + +This e.g. allows you to include a search field for “region” in a search +form, that when used will filter the available values in the “country” +search field – once a search query is executed, the value of the +(transient) region field will be ignored. + +=== Type + +The *type* setting represents the Java type of the property. It cannot +(for obvious reasons) be modified using the annotation or the message +bundle. + +=== URL + +In message bundle: *url = true | false* + +The *url* setting can be used to specify that a certain String property +must be rendered as a clickable URL. + +The default value is *false*_._ If set to *true,* then a validator will +be added to the field (when in edit mode) that checks if the entered +value is a valid URL (must start with http or https). Also, in read-only +mode the framework will render a clickable URL containing the value of +the attribute – when clicked it will open the provided URL in a separate +browser window. + +=== Visible + +In message bundle: *visible = true | false | SHOW | HIDE* + +The *visible* setting determines whether a property will be displayed +inside an edit form__.__ It is not to be confused with the +*visibleInGrid* attribute that governs whether a property shows up in a +grid. + +By default, all properties will have *visible* set to *true*, except for +the “id” property which is reserved for a technical primary key and will +by default be hidden from the user. + +Note that if you set the visible setting to *false* using the annotation +override, the *visibleInGrid* setting will also be set to *false*. If +you _do_ want the property to show up in a grid view, explicitly set the +*visibleInGrid* setting to *true*. + +Instead of *true* you can also use the value *SHOW* and instead of +*false* you can also sue the value *HIDE*. + +=== VisibleInGrid + +In message bundle: *visibleInGrid = true | false | SHOW | HIDE* + +The *visibleInGrid* setting determines whether a property will be +displayed in a search results grid. + +By default, all *BASIC* properties will have *visibleInGrid* set to +true, except for the “id” property which is used for a technical primary +key and will by default be hidden from the user. + +For all other properties, you must manually set the attribute to *true* +(or *SHOW*). + +=== Week + +In message bundle: *week = true | false* + +The *week* setting can be used to specify that a date field must be +treated as a week code (e.g. “2016-34”). The default for this setting is +*false.* If set to *true*, then instead of a date picker, a text field +will be rendered for editing the property, and this text field will only +accept input in the correct format (yyyy-ww). Also, in view mode the +date will be displayed as the week code. + +Note that under the covers, the value will still be treated and stored +as a java.time.LocalDate. + +== Attribute ordering and grouping + +=== Attribute ordering + +In message bundle: *attributeOrder=[comma separated list of attribute +names]* + +By default, the properties of an entity will be displayed in the order +in which they appear in the Java class file. This can be overruled by +using an *@AttributeOrder* annotation or setting the *attributeOrder* +via the message bundle. + +The *@AttributeOrder* annotation takes a single parameter, named +*attributeNames* which contains an array of field names – the order in +which the attributes appear in the array is the order in which they will +appear in the application. + +@AttributeOrder(attributeNames = \{ "name", "headQuarters", "address", +"countryOfOrigin", "reputation" }) + +*public* *class* Organization *extends* AbstractEntity \{ + +You can achieve the same effect by including a message like +*Organization.attributeOrder=name,headquarters,address,countryOfOrigin,reputation* +in the message bundle (use commas to separate the values). The message +in the bundle will overwrite the ordering set by @AttributeOrder. If +your entity has a large number of attributes this might get a bit +unwieldy though. + +The ordering does not have to be contain all properties; if you leave +out any attributes, then those will be placed (in the normal order) +after any attributes that are explicitly mentioned in the annotation or +the message bundle. + +=== Grid and search form attribute ordering + +Also by default, the attribute order in a search form and in results +grid is the same as the default attribute order (see the previous +paragraph). You can override this by using the *@GridAttributeOrder* and +*@SearchAttributeOrder* annotations. + +@GridAttributeOrder(attributeNames = \{ "name", "headQuarters", +"address", "countryOfOrigin", "reputation" }) + +@SearchAttributeOrder(attributeNames = \{ "name", "headQuarters", +"address", "countryOfOrigin", "reputation" }) + +*public* *class* Organization *extends* AbstractEntity \{ + +These annotations do the following: + +* *GridAttributeOrder* sets the order of the attributes in the search +results grid for the *SearchLayout* and the *SplitLayout*. +* *SearchAttributeOrder* set the order of the attributes in the search +form for the *SimpleSearchLayout* and in popup search screens. + +These additional attribute orders completely overwrite the default +attribute order, so you will have to redefine all attributes in the +order you want to see them. Any attributes that are not explicitly +mentioned are included at the end in alphabetical order. + +You can also overwrite using message bundles: + +*Organization.searchAttributeOrder=name,headquarters,address,countryOfOrigin,reputation* + +*Organization.gridAttributeOrder=name,headquarters,address,countryOfOrigin,reputation* + +=== Attribute grouping + +In addition to ordering the attributes, they can also be grouped +together. To do this, you can include an *@AttributeGroups* annotation +on your class definition, which can in turn include any number of +*@AttributeGroup* annotations. + +Each *@AttributeGroup* annotation contains the name of the group and an +array that contains the names of the properties that must be included in +the group. As an example, consider: + +@AttributeGroup(messageKey = "Organization.first", attributeNames = \{ +"name", "address", "headQuarters", "countryOfOrigin" }), + +@AttributeGroup(messageKey = "Organization.second", attributeNames = \{ +"reputation" }) + +@AttributeOrder(attributeNames = \{ "name", "headQuarters", "address", +"countryOfOrigin", "reputation" }) + +*public* *class* Organization *extends* AbstractEntity \{ + +The above defines two attribute groups identified by the message keys +“Organization.first” and “Organization.second”. The display names of the +groups can be defined in the message bundle: + +*Organization.first=First* + +*Organization.second=Second* + +When you want to achieve the same using a message bundle, you can do +this in the following way: + +*Organization.attributeGroup.1.messageKey=Organization.first* + +*Organization.attributeGroup.1.attributeNames=name,address,headquarters,countryOf +Origin* + +*Organization.attributeGroup.2.displayName=Organization.second* + +*Organization.attributeGroup.2.attributeNames=reputation* + +I.e. you include two messages for every attribute group: one containing +the message bundle key and one containing the attribute names as a list +of comma-separated attribute names. The messages are numbered starting +at “1”. + +The attribute grouping is only used to determine which properties to +group together, not to determine the order in which the attributes +appear within this group. This order is still determined by the +*@AttributeOrder* annotation as described in section 4.1. + +When you want to refer to a certain attribute group in your code, you +should do so by using the (unique) message key of that group. + +== Advanced entity model topics + +=== Nested entity models + +The Dynamo framework supports dealing with nested entities. When Dynamo +generates an entity model for an entity, it automatically creates nested +entity models for all complex properties it encounters. This is +currently supported up to three levels deep. The models are constructed +lazily when needed. + +Note that the entity model that is created for a nested entity is a +separate model from the top-level model for the entity. So, the direct +model for the “Address” entity is a different model than the nested +model for *Person.address.* + +Some settings behave differently for nested entity models. E.g. for any +properties of nested entities, the *searchable* and *visibleInGrid* +settings will be set to false by default. + +You can override settings on nested attribute models in the same way as +you can override attributes of non-nested entities, i.e. by including a +message in the message bundle that contains the full path to the +property (e.g. *Movie.director.name.displayName=Director Name*). The +*@Attribute* annotation only works on the non-nested entity model. + +=== Element collections + +The Entity Model framework also supports dealing with “element +collection” properties, i.e. properties that are collections of simple +types (currently String, Integer, Long and BigDecimal are supported) and +that are annotated with the *@ElementCollection* annotation. + +For these properties, the application will automatically generate a +simple grid component that allows you to add items to, remove items +from, and modify items in the collection. You can use the *minLength* +and *maxLength* settings to modify the minimum allowed length and +maximum allowed length of the individual items (in case of a collection +of Strings), or use the *minValue* and *maxValue* settings to define a +minimum or maximum value for a collection of numeric values. + +This is needed because the regular Java Validation *@Size* annotation +operates on the size of the collection, not the length of its individual +members. + +The grid component will look like this: + +image:vertopal_1eda064bf39f485baa9f64be38994881/media/image10.png[image,width=560,height=198] + +== Data access, Service layers and general concepts + +=== Data access layer and entities + +Dynamo has certain requirements regarding the Data Access layer and +Entity classes that are used in applications developed with the +framework. + +All Entity classes (classes that map to a table in the database) must +inherit from the *AbstractEntity* class. This means that they inherit a +*version* field (used for optimistic locking) and an *id* field that +denotes the technical primary key. The type of this id field is +configurable via the type parameter of the *AbstractEntity* class. + +An example Entity class looks like this: + +@Entity + +@Model(displayProperty = "name") + +@Table(name = "organization") + +*public* *class* Organization *extends* AbstractEntity \{ + +For every Entity class, you must (normally) create a Data Access Object +(DAO) interface and the accompanying implementation. The DAO must +inherit from the *BaseDao* interface: + +*public* *interface* OrganizationDao *extends* BaseDao \{ + +} + +And the implementation must inherit from *BaseDaoImpl*: + +@Repository("organizationDao") + +*public* *class* OrganizationDaoImpl *extends* BaseDaoImpl *implements* OrganizationDao \{ + +*private* QOrganization qOrganization = QOrganization.*_organization_*; + +@Override + +*public* Class getEntityClass() \{ + +*return* Organization.*class*; + +} + +@Override + +*protected* EntityPathBase getDslRoot() \{ + +*return* qOrganization; + +} + +} + +The minimal implementation contains just two methods: getEntityClass() +which returns the type of the entity that is managed by the DAO, and +getDslRoot() which returns the QueryDSL root. + +QueryDSL is a framework that is used by the Dynamo Framework to create +type-safe queries. Basically, what QueryDSL does is create a QueryDSL +class for every entity class in your application. When developing in +Eclipse, the IDE will automatically generate the appropriate classes. +You can also run a command line Maven build to generate them. + +Finally, note that the DAO implementation class is annotated with +*@Repository*, which will register it as a Spring bean (it also has +additional functionality in Spring Data, but Dynamo does not currently +use the Spring Data library). + +=== Service + +In addition to developing a DAO for your entity, you must also create a +service class. This service class in its basic form will serve as a +delegate to the DAO, but it is also the place where you can place +business logic. + +The declaration of a service interface is very easy; the service must +extend *BaseService*. + +*public* *interface* OrganizationService *extends* BaseService \{ + +} + +The implementation is equally simple: + +@Service("organizationService") + +*public* *class* OrganizationServiceImpl *extends* +BaseServiceImpl *implements* OrganizationService +\{ + +@Inject + +*private* OrganizationDao dao; + +@Override + +*protected* BaseDao getDao() \{ + +*return* dao; + +} + +} + +In its most basic form, you can defined a service by extending the +*BaseServiceImpl* class and inject the appropriate DAO. This DAO must +also be returned by the *getDao* method. Note that the service must be +annotated with @*Service*, registering it as a Spring service. + +By default, the methods of the service that manipulate data (basically, +*save* and *delete)* are already annotated with the *@Transactional* +annotation (from the Spring framework). If you add any methods yourself +that also need an active transaction, you either have to mark these +methods (in the service implementation class) as transactional. +Alternatively, you can place the *@Transactional* annotation on the +service implementation subclass in order to make all methods in that +service transactional. + +=== Commonly used service methods + +The *BaseService* (and *BaseDao*) class offer several commonly used +methods that should take care of the most basic data retrieval and +storage needs: + +* *long count()* -> return the number of entities in the database +* *long count(Filter filter, boolean distinct)* -> returns the number of +entities that match the provided filter. +* *T createNewEntity()* -> creates a new entity +* *void delete(List list*) -> Deletes a list of entities +* *void delete(T entity)* -> Deletes a single entity +* *List fetch(Filter filter, FetchJoinInformation… joins)* -> fetches +entities based on a filter (without a sort order) +* *List fetch(Filter filter, SortOrders sortOrders, +FetchJoinInformation… joins)* -> fetches entities based on a filter +(with a sort order) +* *List fetch(Filter filter, int pageNumber, int pageSize, +FetchJoinInformation… joins)* -> fetches a page of data (without a sort +order) +* *List fetch(Filter filter, int pageNumber, int pageSize, SortOrders +sortOrders, FetchJoinInformation… joins)* -> fetches a page of data +(with a specified sort order) +* *T fetchById(ID id, FetchJoinInformation… joins)* -> fetches an entity +and its relations based on a primary key +* *List fetchByIds(List ids, FetchjoinInformation… joins)* -> +Fetches a page of entities based on the IDs of the entities (without a +sort order) +* *List fetchByIds(List ids, SortOrders sortOrders, +FetchjoinInformation… joins)* -> Fetches a page of entities based on the +IDs of the entities (with a sort order) +* *T fetchByUniqueProperty(String property, Object value, boolean +caseSensitive, FetchJoinInformation… joins)* -> Fetches an entity based +on a unique property value. +* *List find(Filter filter)* -> Finds a list of entities based on the +provided filter +* *List find(Filter filter, SortOrder… orders)* -> Retrieves a page +data of data based on the provided filter. +* *List findAll(Order… orders)* -> Retrieves all entities of a +certain type. Use with caution +* *T findById(ID id)* -> Find an entity based on its primary key +* *T findByUniquePropertyId(String property, Object value, boolean +caseSensitive)* -> Retrieves an entity based on a unique property value. +* * List findDistinct(Filter filter, String distinctField, +Class elementType, SortOrder… orders)* -> Searches for all distinct +values that occur in a specific field +* * List findDistinctInCollectionTable(String tableName, String +distinctField, Class elementType)* -> Searches for all distinct +values inside a collection table. Used by the framework when creating a +search field for searching inside a collection table. +* *List findIds(Filter filter, SortOrder… orders)* -> Returns a list +of IDs that match the provided filter, sorted according to the provided +sort orders. +* *Class getEntityClass()* -> Returns the class of the entity that is +managed by this service. +* *T save(T entity)* -> Saves an entity +* *List save(List entities)* -> Saves a list of entities. + +=== Fetching and paging + +The Dynamo framework is built around the concept of fetching data (using +fetch join queries) whenever possible. The philosophy behind this is +that it is usually much faster to fetch all required data using a single +query, rather than performing numerous smaller queries to achieve the +same result. + +For this reason, we recommend to keep the use of eager fetching to an +minimum and use lazy fetching combined with fetch joins whenever +possible. + +The framework supports several methods that make it possible to fetch +data based on a primary key or collection of keys, and also allow you to +specify with relations to fetch as part of the query. + +Note e.g. the following method defined in *BaseService*: + +*public* T fetchById(ID id, FetchJoinInformation... joins); + +As you can see, this method accepts a _vararg_ parameter that specifies +which relations to fetch. If left empty, the application will use the +default setup, which you can specify by overriding the *getFetchJoins* +method in the entity’s DAO, e.g.: + +@Override + +*protected* FetchJoinInformation[] getFetchJoins() \{ + +*return* *new* FetchJoinInformation[] \{ *new* +FetchJoinInformation("countryOfOrigin"), + +*new* FetchJoinInformation("members") }; + +} + +This means that whenever you perform a fetch query using a standard +service method, and you do not explicitly specify which relations to +fetch, all relations returned by this method will be fetched. + +Note that when creating a *FetchJoinInformation* object, you can specify +the type of join. The default is LEFT JOIN which means that the entity +will be returned even if the relation to fetch is empty. You can change +this to INNER. This will often improve performance but only used this if +it relation you are fetching is mandatory and thus always present. + +Take great care not to include any substantially large relations, since +this can lead to poor performance. + +When creating user interface components (this will be covered in more +detail later), you will generally be able to specify which relations to +fetch when using that specific user interface component. If you do not +specify anything, the default relations defined in the DAO will be used. + +Note that if you create a component that contains a tabular display of +data, you can specify the way in which the data will be retrieved. There +are two options here: + +* *ID_BASED* – As described above. The application will execute a query +that will retrieve the primary keys of the entities to be displayed, +followed by a query that fetches a number of these entities (and their +relations) based on these primary keys and information about which +relations to fetch. +* *PAGING* – The application will first execute a query to determine the +amount of entities, and will then use a paging query (using +*firstResults* and *maxResults*) to retrieve a subset of the desired +entities). This approach supports the fetching of associated relations, +but take care that you must only fetch many-to-one or one-to-one +relations in this fashion. This is because if you fetch one-to-many or +many-to-many relations, the result set will contain multiple rows per +entity, which clashes with the *firstResults* and *maxResults* settings +and will cause the underlying ORM provider to retrieve the entire data +set first and do the filtering in memory. This is often very +inefficient. + +In both cases, the grid is filled lazily – only a small subset of the +available data will be retrieved. The best approach depends on the +situation – if you have a large data set and no relations to fetch then +paging is preferred. If you have a lot of relations to fetch (or if you +must fetch any one-to-many or many-to-many relations), use the ID-based +approach. + +=== Validation + +The validation facilities in the Dynamo Framework are based on the JSR +303 (Bean Validation) standard: to express validation rules, simply use +the standard annotations (@NotNull, @Size, @Min etc.) on the properties +of your entity. + +You can also use @AssertTrue and @AssertFalse to express more complex +(inter-field) validation rules, or write your own validations by +implementing the *ConstraintValidator* interface. To use @AssertTrue or +@AssertFalse, create a method on the entity class that returns a +Boolean, then annotate that method with either of these annotations – +during the validation process these methods will be executed and if the +return value does not match the value expected by the annotation then a +validation error will be reported. + +Custom validation messages can be included in the +*ValidationMessages.properties* message bundle. + +When you save an entity (by calling the service method *save*), it is +automatically validated against these validation rules, and an +*OCSValidationException* will be thrown if any validations fail. + +Also, note that when the application creates a default edit form, the +appropriate validators are automatically assigned to each field based on +the JSR 303 validation rules. So, if you enter a value of “1000” in a +field that has “999” as the maximum value, a validation error message is +automatically displayed. + +If you need to perform any custom validations for a certain entity +class, you can do so by overriding the *validate* method in the Service +implementation class for that entity. + +==== Checking for identical entities + +There is one additional feature with regard to validation that we must +mention here. In case you have an entity that contains a logical primary +key (either a single field or a combination of fields) the framework +provides an easy way to check for possible duplicates. To do so, you +only have to override the *findIdenticalEntity* method from the +*BaseServiceImpl* in your service implementation class. + +This method takes an entity as its only parameter; inside the method +body, you can perform any query to check if there already is an entity +that has the same values for the unique field or combination of fields. +If the method returns a non-null value, then the framework will throw an +*OCSValidationException* as part of the validation process. + +Consider the following example that checks if there already is an +organization with the same name as the organization you are trying to +save (which is passed as a parameter to the method): + +@Override + +*protected* Organization findIdenticalEntity(Organization entity) \{ + +*return* dao.fetchByUniqueProperty("name", entity.getName(), *false*); + +} + +Note that you do not have to check if the entity being returned is equal +to the entity being validated, the framework will take care of this for +you. + +=== Filtering + +Both the user interface and the service layer make use of a filtering +mechanism to limit the result sets returned by certain queries (e.g. the +*count* and *find* methods of the *BaseService*). + +This mechanism is based on the predicate functionality provided by +Vaadin: for every Vaadin data provider, you can add one or more filters +(instances of *com.vaadin.server.SerializablePredicate* and its +subclasses) which can be used to restrict the data set that is contained +in it. + +For convenience, the Dynamo framework includes a wide range of filters +that extend from the base Vaadin class. These include: + +* *PropertyPredicate* -> The base class for predicates that filter on a +single property value +** *ComparePredicate* -> The base class for predicates that compare a +single property value. +*** *EqualsPredicate* -> for an exact comparison. +*** *LessThanPredicate* -> for checking whether a property value is +below a certain value. +*** *LessOrEqualPredicate* -> for checking whether a property value is +less than or equal to a certain value. +*** *GreaterOrEqualPredicate* -> for checking whether a property value +is greater than or equal to a certain value. +*** *GreaterThanPredicate* -> for checking whether a property value is +greater than a certain value. +*** *IsNullPredicate* -> for checking whether a property value is null. +** *LikePredicate* -> for checking whether a String value matches a +certain pattern. +** *SimpleStringPredicate* -> for checking whether a String value +matches a certain pattern (more advanced than the *LikePredicate*). +** *InPredicate* -> for checking whether the property value occurs +within a collection of values. +** *ContainsPredicate* -> for checking whether the property value +contains a certain value. +** *ModuloPredicate ->* for checking whether the property value is equal +to some value modulo a provided number +** *BetweenPredicate ->* for checking whether the property value is +between an upper and lower bound (inclusive). +* *NotPredicate* -> The logical negation of another predicate. +* *OrPredicate* -> The logical disjunction of two or more predicates. +* *AndPredicate* -> The logical conjunction of two or more predicates. + +Most user interface components in Dynamo that can be used to display a +collection of data have the option to specify the Vaadin filters that +are used to filter the data that is retrieved by the component. + +In addition, some components offer the developer the option to specify a +*fieldFilters* map. This map contains key/value pairs that map an +attribute name (from the Entity Model framework) to a Vaadin filter. +This mechanism can be used to restrict the values that appear in e.g. a +combo box or a lookup field inside an edit form or search form. + +=== Default Services and DAO’s + +It can happen that you have a very simple entity for which you will only +need the default methods provided by *BaseService*_._ If this is the +case, then you do not have to go through the trouble of creating a DAO +and Service class. Instead, you can configure a *DefaultServiceImpl* +and/or *DefaultDaoImpl* in a configuration class. This looks as follows: + +@Bean + +*public* BaseDao regionDao() \{ + +*return* *new* DefaultDaoImpl<>(QRegion.*_region_*, Region.*class*); + +} + +@Bean + +*public* BaseService regionService(BaseDao regionDao) \{ + +DefaultServiceImpl regionService = *new* +DefaultServiceImpl<>(regionDao, "code"); + +*return* regionService; + +} + +As you can see, you can configure a bean that is an instance of +*DefaultServiceImpl* and supply the necessary arguments to the +constructor. This includes: + +* An instance of *DefaultDaoImpl*_._ This in turn has two (or three) +constructor arguments, namely: +** The QueryDSL base class (the QEntity class) +** The entity class. +** Optionally, the names of the properties to fetch when performing a +fetch query (these will always be fetched using a left join). +* Optionally, the name of the properties for which the values must be +unique. You can use a comma-separated list to specify multiple +properties, e.g. “code,name” means that both the “code” and “name” +properties must be unique. +* Optionally, a boolean parameter that indicates whether the search for +the unique value is case sensitive (defaults to *false*). + +After you have configured a service like this, you can inject it into +your code as follows. Note that an *@Qualifier* annotation that matches +the name of the bean is required: + +@Autowired + +@Qualifier("countryService") + +*private* BaseService countryService; + +== Project Setup + +=== Project structure + +By default, projects created using the Dynamo Framework consist of a +root project (with a root pom) that contains three sub-projects: +*domain*_,_ *core* and *ui*_:_ + +* The *domain* subproject contains the domain classes. +* The *core* subproject contains the service and business logic classes. +* The *ui* project contains the user interface. + +Each subproject follows the default structure of a Maven project and +thus has four source folders: + +The following directory structure shows how the projects are organized: + +* domain +** src/main/java +** src/main/resources +*** META-INF +**** *entitymodel.properties* the message bundle used to customize the +entity model generation process. +*** *application.properties* contains the application properties (Spring +Boot file). +*** *ui.messages.properties* the message bundle used for +internationalization. +*** *ValidationMessages.properties* the message bundle used for +configuring Bean Validation error messages +** src/test/java +** src/test/resources +* core +** src/main/java +** src/main/resources +** src/test/java +** src/test/resources +* ui +** src/main/java +** src/main/resources +** src/main/webapp -> you can place any image files in the +*frontend/images* directory. +** src/test/java +** src/test/resources +** frontend/styles -> contains custom CSS files + +=== Message bundles + +A Dynamo application uses a number of message bundles (see the tree in +the previous section for information on where these bundles are +located). These message bundles are made available to the Spring +Framework and you can retrieve a message from them using the +*MessageService* which is a Spring-managed singleton bean that you can +inject into your services. Note that many standard components already +have a reference to this *MessageService*_._ + +The message bundles used in the application all serve different +purposes: + +* *ui.messages.properties* is the message bundle that must be used for +all text messages that actually appear on your screens. E.g. if you want +a button to show the text “Click me” then you could include a message +like + +____ +*mybutton.caption=Click me* + +In the message bundle and then use the +*messageService.getMessage(“mybutton.caption”)* method in order to +retrieve the message. Note that you can use the standard features of the +Java message bundle mechanism to provide internationalization, e.g. you +can create a message bundle “messages_fr.properties” and fill that with +the French translations of your messages. This bundle will then be +picked up if the locale of the application is set to French. +____ + +* *ValidationMessages.properties* is the message bundle used configuring +Bean Validation error messages. You can refer to messages from this +bundle in the following way. Suppose that in your bundle you have the +following message: + +____ +product.aliases.different=Duplicate product aliases found. Please make +sure they are all different + +Then in the code you can refer to this message by placing the message +name within curly brackets: + +@AssertTrue(message = "\{product.aliases.different}") + +*public* *boolean* isAliasesDifferent() \{ +____ + +* *entitymodel.properties* is the message bundle that is used to +override the default behaviour of the Entity Model factory. See section +2.5 for instructions on how to use this override mechanism. +* *menu.properties* is the message bundle that is used to configure the +structure of your menu. It contains both the textual descriptions of +menu items, and the structure of the menu. More on this can be found in +chapter 8. + +The *MessageService* provides a number of methods for retrieving +messages. Some of these are used internally by the framework and should +not normally be used directly. The following methods are intended for +developers: + +* *getMessage(String key, Locale locale)* retrieves a message based on +its key, using the specified locale. If no message is found, then a +warning message will be returned. +* *getMessage(String key, Locale locale, Object… args)* retrieves a +message based on its key, using the specified locale, and using the +specified parameters. If the message contains placeholders (\{0}, \{1}, +\{2} etc.) these will be replaced by the provided parameters. + +If a message with a certain key cannot be found, then a default warning +message will be returned. If you do not want this behaviour, you can use +the *getMessageNoDefault* version of the method instead. This version +returns *null* when a message cannot be found. + +=== Spring Boot configuration + +A Dynamo application uses Spring boot as its application framework. This +means that there must be a main class that is annotated with +*@SpringBootApplication*. + +@SpringBootApplication + +@Import(ApplicationConfig.*class*) + +*public* *class* GtsApplication \{ + +*public* *static* *void* main(String[] args) \{ + +SpringApplication._run_(GtsApplication.*class*, args); + +} + +} + +As you can see, we use the @Import annotation to import the main +configuration file. Strictly speaking this is optional (you can add the +configuration directly in the main class) but using this setup allows +you to use a base class from Dynamo that will take care of instantiating +the basic Dynamo components. + +The ApplicationConfig class looks as follows: + +@ComponentScan(basePackages = \{ "com.ocs.gts", "com.ocs.dynamo" }) + +@EntityScan(basePackages = \{ "com.ocs.gts.domain", +"com.ocs.dynamo.functional.domain" }) + +*public* *class* ApplicationConfig *extends* +ApplicationConfigurationSupport \{ + +@Override + +*protected* String[] getBaseNames() \{ + +*return* *new* String[] \{ "classpath:/META-INF/entitymodel", +"classpath:/menu", "classpath:/ui.messages", "classpath:/ocscommon", + +"classpath:/ValidationMessages"}; + +} + +@Bean + +@UIScope + +*public* UIHelper uiHelper() \{ + +*return* *new* UIHelper(); + +} + +@Bean + +*public* BaseDao regionDao() \{ + +*return* *new* DefaultDaoImpl<>(QRegion.*_region_*, Region.*class*); + +} + +….other declarations + +} + +* Use @*ComponentScan* to specify which packages to scan for classes to +be instantiated by Spring. +* Use @*EntityScan* to specify which packages to scan for entity +classes. Normally this will be your own package (in this example +“com.ocs.gts.domain”). If you want to use the dynamo-functional-domain +module you must also add “com.ocs.dynamo.functional.domain”. +* Extends the *ApplicationConfigurationSupport* class to automatically +instantiate the base components needed by Dynamo. +* Implement the “getBaseNames” method to specify the all resources +bundles that are used by the application. +* Below this you can add your own bean definitions. If you use the +dynamo-functional-domain module then this will likely including some +BaseService and BaseDao declarations (see paragraph 6.7). +* You must also include a bean of class *UIHelper* with scope +*@UIScope*. + +As a Dynamo application is a Spring Boot application, you can add or +modify any properties by changing the *application.properties* file +which should be located in the src/main/resources directory of the UI +subproject. The properties specific to Dynamo will be covered in the +section on system properties. + +Dynamo does not provide any functionality with regard to login or +security. You can use Spring Security to set up your desired security +configuration, which is normally done by overwriting the +*WebSecurityConfigurerAdapter* from Spring. + +This is an example for Basic authentication with one in-memory user. + +@EnableWebSecurity + +@Configuration + +*public* *class* GtsSecurityAdapter *extends* +WebSecurityConfigurerAdapter \{ + +@Autowired + +*private* MyBasicAuthenticationEntryPoint entryPoint; + +@Autowired + +*public* *void* configureGlobal(AuthenticationManagerBuilder auth) +*throws* Exception \{ + +auth.inMemoryAuthentication().withUser("Dynamo").password("\{noop}Dynamo").authorities("user"); + +} + +@Override + +*protected* *void* configure(HttpSecurity http) *throws* Exception \{ + +// disable CSRF (already provided by [.underline]#Vaadin#) + +http.csrf().disable().authorizeRequests().anyRequest().authenticated().and().httpBasic().authenticationEntryPoint(entryPoint); + +} + +@Bean + +GrantedAuthorityDefaults grantedAuthorityDefaults() \{ + +*return* *new* GrantedAuthorityDefaults(""); + +} + +} + +Most of this set-up is not specific to Dynamo, but a couple of things +are worth mentioning: + +* We add a bean of type “GrantedAuthorityDefaults”, instantiated with an +empty String. This removes the “ROLE_” prefix that Spring Security +normally adds to role names. +* We disable CSRF protection by calling http.csrf().disable(). This is +not needed since Vaadin already provided this. + +=== System properties + +Dynamo several ways of ways of dealing with (system) properties. + +The easiest way of declaring a property is by including it in the +*application.properties* file which is located in the +*src/main/resources* directory of the *ui* subproject. This is of course +the standard file used by Spring boot, and as such you can add both your +own system properties to it, as well as using it to modify any Spring +Boot settings. You can use the default mechanisms offered by Spring Boot +(e.g. external configuration file, explicitly set system parameters) to +override the values in *application.properties*. + +The Dynamo Framework relies on several pre-configured system properties: + +[width="100%",cols="51%,21%,28%",options="header",] +|=== +|*Property Name* |*Default Value* |*Explanation* +|ocs.allow.list.export |false |Whether to allow the export of grid +contents to Excel (or CSV) by right-clicking in the grid + +|ocs.capitalize.words |True |Indicates whether to capitalize all words +that appear in captions. If set to “false” then only the first character +will be capitalized. + +|ocs.default.clear.button.visible |False |Whether a “clear” button +(displayed as a big gray X) will be rendered at the end of the input +component (not all components support this). + +|ocs.default.currency.symbol |€ |The default currency symbol to be +displayed in currency fields + +|ocs.default.date.format |dd-MM-yyyy |The pattern to use when formatting +date values + +|ocs.default.datetime.format |dd-MM-yyyy HH:mm:ss |The pattern to use +when formatting timestamp values + +|ocs.default.datetime.zone.format |dd-MM-yyyy HH:mm:ssZ |The pattern to +use when formatting timestamp values that include a time zone + +|ocs.default.date.locale |uk |The locale used for localization of date +and time fields. + +|ocs.default.locale |uk |The default locale. This is used for number +formatting and localization of messages and labels + +|ocs.default.decimal.precision |2 |The default number of decimals to use +when formatting/entering a decimal value + +|ocs.default.details.grid.sortable |False |Indicates whether the column +in a DetailsEditGrid are sortable by default + +|ocs.default.edit.form.columns.thresholds |0px a| +Sets the thresholds for when to use multiple columns of input fields in +an edit form. + +If set to the default value of “0px” then edit forms will always just +contain a single column. + +If e.g. you set it to “0px,800px”, then a second column will appear on +screens wider than 800 pixels. + +|ocs.default.edit.grid.height |200px |The default height of a grid that +is included in an edit form (i.e. a DetailsEditGrid or +ElementCollectionGrid) + +|ocs.default.false.representation |false |The default textual +representation of a boolean “false” value + +|ocs.default.grid.height |400px |The default height of the grid that is +used within composite layout components + +|ocs.default.group.together.mode |Pixel |The default “group together” +mode to use. This specifies how to scale fields when they are grouped +together. Supported values are *pixel* and *percentage.* + +|ocs.default.group.together.width |200 |The default width of a field +when it is grouped together width one or more other fields, in pixels + +|ocs.default.list.select.height |100px |Default height of list select +component + +|ocs.default.lookupfield.max.items |3 |The maximum number of selected +items to display in the label of a lookup field + +|ocs.default.max.edit.form.width |100% |The default width of the edit +form + +|ocs.default.max.search.form.width |100% |The default width of the +search form (for SearchLayouts) + +|ocs.default.message.display.time |2000 |The default time (in +milliseconds) that an error or information message is displayed + +|ocs.default.number.field.mode |TEXTFIELD a| +The default field type to use for integer properties + +Allowed values: TEXTFIELD, NUMBERFIELD + +|ocs.default.paging.mode |NON_PAGED a| +The default paging mode to use inside components that display a list of +elements (combo box, token field select) + +Allowed values: NON_PAGED, PAGED + +|ocs.default.search.case.sensitive |false |Whether to use case sensitive +mode when searching on string fields + +|ocs.default.search.dialog.grid.height |300px |The default height of a +results grid inside a search dialog grid + +|ocs.default.search.form.columns.thresholds |0px,650px,1300px a| +Sets the thresholds for when to use multiple columns of input fields in +a search + +If set to the default value of “0px” then edit forms will always just +contain a single column. + +If e.g. you set it to “0px,800px”, then a second column will appear on +screens wider than 800 pixels. + +|ocs.default.search.prefix.only |False |Whether to only look for matches +at the start of a string (rather than anywhere) when searching on sting +fields. + +|ocs.default.text.area.height |200px |The default height of a text area +component + +|ocs.default.search.case.sensitive |false |Whether searching on String +values is case sensitive by default. + +|ocs.default.search.dialog.grid.height |300px |The default height of the +results grid in a search dialog + +|ocs.default.search.prefix.only |false |Whether searching on String +values only considers matches at the start (rather than anywhere in the +String) + +|ocs.default.time.format |HH:mm:ss |The pattern to use when formatting +time values + +|ocs.default.true.representation |True |The default textual +representation of a boolean “true” value + +|ocs.default.thousands.grouping |NEVER a| +When to display a thousands grouping separator when editing or +displaying numerical values + +(allowed values are NEVER, ALWAYS, EDIT, VIEW) + +|ocs.enable.view.authorization |false |Whether to enable role-based +authorization for UI views + +|ocs.export.csv.escape |“” |The escape character to use when exporting +to CSV + +|ocs.export.csv.separator |; |The separator character to use when +exporting data to CSV + +|ocs.export.csv.quote |“ |The quote character to use when exporting data +to CSV + +|ocs.indent.grids |True |Determines whether to indent any +DetailsEditGrids and DetailsEditLayouts when the are used inside edit +forms + +|ocs.trim.spaces |false |Whether to remove extraneous spaces at the +start or end of the input in text fields and text areas + +|ocs.use.default.prompt.value |false |Whether to display a prompt/hint +value inside input components in edit forms + +|ocs.use.browser.time.zone |false |Whether to use the time zone as +retrieved from the user’s browser when formatting timestamps that +contain a time zone. If this is set to false (default) the system time +zone will be used instead. + +|ocs.use.grid.selection.checkboxes |true |Whether to use selection check +boxes for multiple select functionality inside grids. If set to false, +you can select multiple rows by using Ctrl- and Shift clicking. +|=== + +You can use the static methods from the *SystemPropertyUtils* class to +access these system properties from inside your code. + +=== DatePicker Customization + +It is possible to modify the way that the Date Picker component used for +modifying values of type LocalDate, LocalDateTime and ZonedDateTime is +localized. You can do this by including some messages in any of the +message bundles used by your application. + +The message keys are as follows: + +[width="100%",cols="50%,50%",options="header",] +|=== +|*Key* |*Usage* +|ocs.calendar.days |Comma-separated list of the names of the days of the +week, starting with Sunday + +|ocs.calendar.days.short |Comma-separated list of the short names of the +days of the week, starting with Sunday (Sun) + +|ocs.calendar.months |Comma-separated list of the names of the months, +starting with January + +|ocs.calendar.first |Index of the first day of the week. 0 = Sunday, 1 = +Monday, etc. +|=== + +Since this uses the standard Java message bundle functionality, you can +have different settings for different languages/locales simply by using +different message bundles, e.g. “messages_nl.properties” for Dutch, +“messages_es.properties” for Spanish etc. You can set which locale to +use for a certain user by calling the *VaadinUtils.storeDateLocale* +method. + +== UIs, Views, Menus, and authorization + +=== Basic UI setup + +A Dynamo application is expected to have a single entry point. This is +basically a class that implements the Vaadin *RouterLayout* interface +and defines the basic page structure. A simplified version may look as +follows: + +@UIScope + +@Theme(Lumo.*class*) + +@CssImport("./styles/shared-styles.css") + +@Viewport("width=device-width, minimum-scale=1.0, initial-scale=1.0, +user-scalable=yes") + +@CssImport(value = "./styles/vaadin-custom-field.css", themeFor = +"vaadin-custom-field") + +@CssImport(value = "./styles/vaadin-dialog-overlay.css", themeFor = +"vaadin-dialog-overlay") + +@CssImport(value = "./styles/vaadin-combo-box.css", themeFor = +"vaadin-combo-box") + +@CssImport(value = "./styles/vaadin-button.css", themeFor = +"vaadin-button") + +@CssImport(value = "./styles/vaadin-text-field.css", themeFor = +"vaadin-text-field") + +*public* *class* GtsUI *extends* VerticalLayout *implements* +RouterLayout \{ + +*private* *static* *final* *long* *_serialVersionUID_* = +-4652393330832382449L; + +*private* MenuBar menu; + +@Autowired + +*private* MenuService menuService; + +@Autowired + +*private* MessageService messageService; + +@Autowired + +*private* UIHelper uiHelper; + +@PostConstruct + +*protected* *void* init() \{ + +VaadinUtils._storeLocale_(*new* Locale("nl")); + +VaadinUtils._storeDateLocale_(*new* Locale("nl")); + +// construct your title bar + +// construct the menu + +menu = menuService.constructMenu("gts.menu"); + +add(menu); + +menuService.setLastVisited(menu, Views.*_ORGANIZATION_VIEW_*); + +uiHelper.addEntityNavigationMapping(Organization.*class*, item -> \{ + +uiHelper.setSelectedEntity(item); + +uiHelper.navigate(Views.*_ORGANIZATION_VIEW_*); + +}); + +} + +} + +* The class must be annotated with *@UIScope* to specify that it is +scoped to a single browser tab. +* The class must include the Vaadin Lumo theme. +* The *@CssImport* annotation is used to import several HTML and CSS +files that are used to customize the default styling. +* The method must implement the RouterLayout interface from Vaadin. +* The actual screen is constructed in a method annotated with +*@PostConstruct*. We start by storing the desired locale and date locale +in the session (more on this below) and then construct the application +menu. Note that you can add your own code for constructing e.g. a title +banner before the menu is added. +* Inside the method annotated by *@PostConstruct* you should construct +the part of the page that is shared between all pages in your +application. When creating specific pages, you will refer to this base +class to ensure that each specific page includes the generic part. + +Note that you must not subclass the UI class from Vaadin – this seems +like a logical thing to do but does not work properly. + +=== Views + +A screen in Vaadin was traditionally represented by a *View*. In the new +version of Vaadin this class no longer exists and any user interface +component can serve as the basis for a view. Dynamo provides the +*BaseView* class which offers some common functionality, and you are +encouraged to subclass *BaseView* to create your own views. + +A view is configured as follows: + +* The *@UIScope* annotation is used to indicate that the view is scoped +to a single browser tab. +* *@Route* is used to indicate how the user can navigate to the page. It +is recommended to store the available view names in a constants file. +The “value” attribute refers to the relative path at which the view will +be available. The “layout” attribute should point to your class that +contains the generic screen layout as described in section 8.1. +* *@RouteAlias* can be used to mark a view as the default view. This is +the view that will be opened when the user does not enter a relative +path in the browser address bar. At most one view in your entire +application may be annotated with *@RouteAlias*. +* *@PageTitle* can be used to set the page title (that will be displayed +in the user’s browser’s title bar) +* The *doInit* method can be used to add your own code. Inside this +method, you must construct your screen. You can add your own components +to the *VerticalLayout* that is passed as the constructor argument +(don’t forget this). + +@UIScope + +@Route(value = Views.*_ORGANIZATION_VIEW_*, layout = GtsUI.*class*) + +@RouteAlias(value = "", layout = GtsUI.*class*) + +@PageTitle("Organizations") + +*public* *class* OrganizationView *extends* BaseView \{ + +@Autowired + +*private* OrganizationService organizationService; + +*private* *static* *final* *long* *_serialVersionUID_* = +3310122000037867336L; + +@Override + +*protected* *void* doInit(VerticalLayout main) \{ + +.. your code goes here + +} + +} + +=== Navigation + +Navigating between the various views in your application is easy: you +can use *UI.getCurrent().navigate(viewName)* to navigate to the desired +view. Several components also provide a convenience method named +*navigate* which directly navigates to this method. + +The Navigator class which existed in previous versions of the Vaadin +framework no longer exists. + +=== View authorization + +By default, all views inside a Dynamo application are accessible to all +users. In order to protect one or more views, you must first ensure that +two application properties are properly set: + +ocs.enable.view.authorization=true + +ocs.view.package=com.ocs.gts + +*ocs.enable.view.authorization* makes sure the view based authorization +is enabled. *ocs.view.package* must be set to the base package that +contains your view classes. Any properly annotated classes in the +specified package (or its sub-packages) will be protected by Dynamo’s +authorization mechanism. + +Under the covers, using these application properties makes sure that a +number of Spring beans are autoconfigured. This includes: + +* The *PermissionChecker* that will scan the provided package for view +classes that must be protected. +* An *AuthorizationServiceInitListener* that will intercept any requests +and redirect them to an error page when the user is not properly +authorized. + +You can then annotate a view as follows: + +@UIScope + +@Route(value = Views.*_PERSON_VIEW_*, layout = GtsUI.*class*) + +@Authorized(roles = "admin") + +*public* *class* PersonView *extends* BaseView \{ + +@Autowired + +*private* PersonService personService; + +The above shows an example of a view definition. The view is annotated +with *@Route* as we’ve seen before. It is also annotated with +*@Authorized*. *@Authorized* can be configured with any number of roles +and only a user that is in at least one of the provided roles is allowed +to access the view. + +If a user is not allowed to access a certain view, that view is excluded +from the menu (see below) and if the user manually tries to navigate to +that view then they will be taken to the error view instead. + +Note that for this to work properly, you must create an appropriate +error view in your application. This can looks as follows: + +package com.ocs.gts.ui; + +import com.ocs.dynamo.constants.DynamoConstants; + +import com.ocs.dynamo.ui.component.DefaultVerticalLayout; + +import com.ocs.dynamo.ui.view.BaseView; + +import com.vaadin.flow.component.html.Label; + +import com.vaadin.flow.component.orderedlayout.VerticalLayout; + +import com.vaadin.flow.router.Route; + +import com.vaadin.flow.spring.annotation.UIScope; + +@UIScope + +@Route(value = DynamoConstants.ERROR_VIEW, layout = GtsUI.class) + +public class GtsErrorView extends BaseView \{ + +private static final long serialVersionUID = 3955677765990706688L; + +@Override + +*protected* *void* doInit(VerticalLayout [.underline]#main#) \{ + +VerticalLayout main = new DefaultVerticalLayout(true, true); + +VerticalLayout inside = new DefaultVerticalLayout(true, true); + +main.add(inside); + +Label errorLabel = new Label(message("ocs.view.unknown")); + +inside.add(errorLabel); + +add(main); + +} + +} + +The error view should container an *@Route* annotation with a value of +“errorView”. Other than that, it is up to you to determine what the view +looks like. + +=== Menu Construction + +Based on the above, Dynamo also supports the declarative construction of +a menu bar. For this, the *MenuService* is used. + +The *MenuService* is a Spring-managed bean which contains a +*constructMenu* method that you can use to construct the menu bar. The +*menuService* is configured using the *menu.properties* message bundle: + +gts.menu.1.displayName=Top Level Menu + +gts.menu.1.1.displayName=Organizations + +gts.menu.1.1.destination=organizationView + +gts.menu.1.2.displayName=Members + +gts.menu.1.2.destination=personView + +gts.menu.1.3.displayName=Gifts + +gts.menu.1.3.destination=giftView + +gts.menu.2.displayName=Deliveries + +gts.menu.2.destination=deliveryView + +When calling the *constructMenu* service, you must pass along a prefix +that will be used to retrieve the messages from the bundle. In the above +case, the prefix is *gts.menu* which means that the menu will be +constructed based on the messages starting with this prefix. You can +include multiple menu structures in the same “menu.properties” file and +use this prefix to determine which menu to construct and to switch menus +on the fly. + +The following code suffices to create a menu and add it to the UI (main +refers to the main layout of the UI): + +menu = menuService.constructMenu("gts.menu"); + +main.add(menu); + +You can switch the menu bar at any time by using Vaadin’s support for +replacing components (“menu” refers to a field on the UI that holds the +reference to the current menu). + +MenuBar other = menuService.constructMenu("gts.other", getNavigator()); + +main.replace(menu, other); + +menu = other; + +The *MenuService* will iterate over the messages, starting at +“gts.menu.1”, followed by “gts.menu.2”, “gts.menu.3” etc., and try to +recursively build a menu for each message. Child items can be specified +by including additional numbers, e.g. “gts.menu.1.1” is the first child +item of the first top-level menu item. The numbering must always start +at 1 and must be continuous (you are not allowed to skip any numbers). +Menu items can be nested arbitrarily deep, e.g. “gts.menu.1.4.3.2” would +be valid provide that you do not skip any of the intermediate levels). + +For every menu item, you can specify the following: + +* *displayName*: this is the name of the menu item (the text that will +appear in the menu). It is required +* *destination*: this is the name of the view to which the application +will navigate after the user selects the corresponding menu item. It is +an optional value. If you do not specify a destination, then the menu +item will not be clickable, and only serve as a container for child menu +items. +* *description*: This is the value that will be used as the tool tip of +the menu item. It is optional. When omitted, no tool tip will be shown. +* *mode*: This is an additional parameter that can be supplied to +specify a certain mode for a view. When this value is specified, it will +be placed in the *screenMode* field on the application’s *UIHelper* +(more on this below). You can retrieve this value inside your View and +use it to construct your screen in the correct mode. + +The menu service will use the above information to build a menu as +follows: + +* It will find the message gts.menu.1.displayName and use it to create a +top-level menu item named “Top Level Menu”. +* It will see that this menu item has children (gts.menu.1.1, +gts.menu.1.2, and gts.menu.1.3) and add three menu items named +“Organizations”, “Members” and “Gifts” to the top-level menu item. All +three items are clickable. +* It will then return to the top level, find the gts.menu.2.displayName +message and use this to construct a top-level menu item named +“Deliveries”, which is clickable and lead to the view named +“deliveryView”. + +Any menu items will automatically be hidden or shown based on the +*@Authorized* annotations placed on the views they correspond to. So, if +the framework detects that a menu item leading to the “personView” must +be included, but the user does not have the “admin” role needed to +access that view, then the menu item will be hidden automatically. Any +parent items that do not have any visible children will also be hidden. + +If you need to programmatically modify the visibility of a menu item, +you can call the *setVisible* method on *MenuService*. + +The framework will also by default highlight the top-level menu item +that leads to the last visited view – If you need to change this +programmatically you can use the *setLastVisited* method of the +*MenuService* to highlight a different top level menu item. + +=== Vertical menu + +New in Dynamo v2.10 is the option to add a vertical menu bar. This works +in nearly exactly the same way as the regular horizontal menu bar, +except that you need to insert a reference to the *VerticalMenuService* +in your code. You can then construct the menu in much the same way: + +Accordion verticalMenu = verticalMenuService.constructMenu("blip.menu"); + +The component returned by this method (the Accordion) can be added +anywhere in your layout to create a menu that displays the various +options below each other. Note that since this menu takes up a lot of +vertical space, it’s not generally feasible to add it to the general UI +class (the class that is implementing the RouterLayout) but you can add +it to the individual layouts pretty easily. + +=== View scoping + +A Spring Vaadin View can have several scopes that govern how long the +view sticks around – it can be newly created every time it is accessed +(@ViewScope), persist while the UI persists (@UIScope) or persist as +long as a Vaadin session exists. + +In general, to get the most out of the Dynamo Framework we suggest +creating views that have UI scope – this prevents the application from +having to recreate the view on every request and allows you to e.g. save +state (like which search filters were entered) in between subsequent +visits to the same page. + +Using @UIScope also ensures that the application will work across +multiple browser tabs: each browser tab that the user opens within the +same session gets its own Vaadin UI instance and can be used +independently. Note that for this to work, you must not yourself store +any tab-specific data in the Vaadin Session – the Vaadin session is +shared between tab sheets so this would cause unwanted interference. For +this reason, the Dynamo Framework also stores any state that needs to be +preserved between views on the BaseUI. + +Note that application state that can be shared across multiple browser +tabs (e.g. a locale setting or a user name) can still be stored this on +the Vaadin session. + +=== Preserving search filters during navigation + +By default, the Dynamo framework preserve search filters between page +navigations. By this we mean that if you enter some search terms in a +search form, execute a search, and then navigate to a different page and +then back again to the original page, any search terms that you entered +before will still be present. If you want to disable this behaviour for +a certain layout, you can use the *FormOptions.setPreserveSearchTerms* +to false for that screen. + +=== Sharing state between views + +Dynamo comes with some basic functionality for staring state between +views. This can e.g. be used when selecting an entity in one screen and +then navigating to a different screen that must display the data about +that entity. + +This state is stored in the *UIHelper* class which can be injected into +your views or accessed by calling the *getUIHelper* method inside your +components. + +The UIHelper offers methods for storing the following: + +* A *selectedEntity –* used for storing an entity +* A *selectedTab –* used for storing the index of a tab. This can be +used to select a specific tab when opening a screen that contains +multiple tabs +* A *screenMode –* used for storing a screen mode. This can be used to +open a view in a certain state or mode. + +These methods on the UIHelper are used internally by the framework but +you can use them programmatically as well. We already saw an application +where the *screenMode* can be set when the user clicks on a certain menu +item. Another example occurs when you mark a property of an entity as +*navigable.* When you do this, you must also provide an +EntityNavigationMapping as follows: + +uiHelper.addEntityNavigationMapping(Organization.*class*, item -> \{ + +uiHelper.setSelectedEntity(item); + +uiHelper.navigate(Views.*_ORGANIZATION_VIEW_*); + +}); + +This ensures that the application knows which view to navigate to (and +potentially which other actions to take) when the user clicks on a link +for that entity in e.g. a grid. + +The code above only ensures that the state is shared and that navigation +occurs, but it will not make sure the selected entity is displayed +correctly. This is because Dynamo does not make any assumptions about +the structure of your views, so you must write the code for this +yourself. Fortunately, normally this is not very hard. + +E.g. you can place the following code at the bottom of your view to +check if an entity has been set on the UIHelper, and then navigate to +the detail screen if needed. + +*if* (getUiHelper().getSelectedEntity() *instanceof* Organization) \{ + +Organization org = (Organization) getUiHelper().getSelectedEntity(); + +org = organizationService.fetchById(org.getId()); + +layout.edit(org); + +getUIHeler().clearState(); + +} + +The *clearState* method removes any state stored in the UIHelper. It is +recommended to use this to clear up after yourself. + +=== Asking for confirmation before navigation + +It is possible to configure the framework to ask for confirmation before +navigating away from a page. To do so, you must construct the view and +pass “true” to the constructor parameter *confirmBeforeLeave.* + +You can then overwrite the *isEditing* method to indicate when the +screen is in edit mode. In most cases you can simply delegate to the +*isEditing* method of the underlying composite component. + +Note: the check for whether something is currently being edited is a bit +simple, it merely checks if the component is currently displaying an +edit form, if the edit form opens in read only ode, and if this edit +form is currently in edit mode. If all conditions are true then the +*isEditing* returns true. + +=== Localization + +By default, the Dynamo framework uses the value of the system property +*ocs.default.locale* as the locale for localization. If you want to +modify the locale on a user-by-user basis, you can use the +*VaadinUtils.storeLocale* method to store a different locale in the +user’s session: + +E.g. the following will make sure that the locale from the user’s +browser is used: + +VaadinUtils._storeLocale_(VaadinRequest.getCurrent().getLocale()); + +Note that for date field (i.e. for displaying month names), Dynamo uses +a separate locale setting. By default, the value of the system property +*ocs.default.date.locale* is used for localization in date components. +You can use the *VaadinUtils.storeDateLocale* method to set a different +locale. + +In order to localize your application (e.g. to modify the contents of +fixed text labels) you can use the standard Java message bundle +mechanism. E.g. if you put your messages in the *ui.messages.properties* +file then you can include French translations in +*ui.messages_fr.properties* and Spanish texts in +*ui.messages_es.properties* + +It is also possible to localize the values used in the entity and +attribute model, e.g. the *displayName, displayNamePlural, description, +etc.* + +In order to do so: + +* Create a file named *entitymodel_.properties* where is the +name of the locale e.g. “fr” or “es” in the same directory that already +contains the *entitymodel.properties* file. You can also use constructs +like “entitymodel_nl_NL.properties” for the variant of Dutch spoken in +the Netherlands, or “entitymodel_en_US.properties”. +* Inside this file, place the desired translations in the same way as +you would include regular message bundle overrides. For example, these +are some lines that could be included in *entitymodel_es.properties* + +Organization.displayName=[.underline]#Organización# + +Organization.displayNamePlural=[.underline]#Organizaciones# + +Organization.description=[.underline]#Una# [.underline]#empresa# +criminal + +Person.displayName=[.underline]#Persona# + +Person.displayNamePlural=[.underline]#Personas# + +Organization.name.displayName=[.underline]#Nombre# + +Dynamo uses the following algorithm to determine the appropriate value: + +* First, look if the appropriate message can be found in +*entitymodel_.properties* +* If this is not the case, revert to the generic +*entitymodel.properties* +* If no message can be found in this file, revert to the value declared +using the @Attribute annotation. +* Finally, use the calculated default value. + +Localization is supported for the following settings: + +Entitymodel: + +* displayName +* displayNamePlural +* description + +AttributeModel + +* displayName +* description +* prompt +* trueRepresentation +* falseRepresentation + +For the *description* and *prompt* setting on the AttributeModel, the +algorithm is slightly different: + +* First, look if the appropriate message can be found in any of the +message bundles. +* If no message is found, look for the message for the *displayName* +instead (since the *description* and *prompt* default to the +*displayName* if not explicitly defined) +* If no message can be found in this file, revert to the value declared +using the @Attribute annotation. +* If all else fails, use the calculated default value. + +== Composite UI components + +Everything you have read until now is an introduction that is needed to +understand how Dynamo can be used to create complex screens and user +interface components. The next sections will cover some of the most +common use cases. + +=== SimpleSearchLayout + +The Dynamo framework offers the *SimpleSearchLayout* component for +creating search screens. A basic application of this +*SimpleSearchLayout* looks as follows: + +EntityModel em = +getModelFactory().getModel(Organization.*class*); + +FormOptions fo = *new* +FormOptions().setAttributeGroupMode(AttributeGroupMode.*_TABSHEET_*).setShowIterationButtons(*true*); + +SimpleSearchLayout layout = *new* +SimpleSearchLayout<>(organizationService, em, QueryType.*_ID_BASED_*, +fo, + +*new* SortOrder("name", SortDirection.*_ASCENDING_*)); + +As you can see, you create a new instance of the *SimpleSearchLayout* +and declare both the class of the primary key of the entity and the +class of the entity itself as type parameters. You then provide the +following: + +* The Service that is used to retrieve data from the database. +* The Entity Model that is used to construct the layout. +* The type of the query: this can either be *ID_BASED* or *PAGING* as +described in section 6. Please remember the restrictions that apply when +using *PAGING* as the query type (i.e. you shouldn’t fetch any +one-to-many or many-to-many relations). +* A *FormOptions* parameter object. This will be explained below. +* The default sort order (optional). +* The relations to fetch – this is a vararg parameter and thus optional. +It can be used to specify which relations to fetch when executing the +search query. If left empty, then the default joins defined in the data +access object associated with the entity will be used. + +And that is basically all the code you need to create a working search +screen. + +Note that the Entity Model that you have defined before will be used as +the template for creating the screen. I.e. all properties that are +marked as *searchable* will be present in the search form, all +properties that have *visibleInGrid* set to true will show up in the +results grid, etc. + +By default, the *SimpleSearchLayout* comes with an *Add* button that +will bring up a form that allows you to define a new entity, and a +*View/Edit* button that allows you to inspect the details of an entity +after you have selected a row in the grid. + +You can use the *FormOptions* object to specify the behaviour of the +screen. The *FormOptions* class is a *parameter* *object* that is used +to prevent excessively long lists of parameters. It uses the *builder* +style and has several methods that can be used to toggle the +availability of certain options (covered below). + +E.g., by default any detail screen will open in edit mode, but if you +don’t want this behaviour you can set the *openInViewMode* parameter to +*true*. If this parameter is set, the detail screen will be read-only +after being opened. + +Some commonly used settings of the *FormOptions* class includes: + +* *attributeGroupMode –* this is used to specify how attribute groups +will be displayed: the value *PANEL* means that a separate panel will be +created for every attribute group, with the panels being placed below +each other, and *TABSHEET* means that a tab sheet component will be +rendered that contains a separate tab for each attribute group. +* *complexDetailsMode –* When this is set to true, instead of displaying +a “simple” detail screen that allows you to only edit a single entity, a +lazy tab layout with multiple tabs will be rendered. This is explained +in more detail below. +* *confirmClear –* If this is set to *true* the user will be asked for +confirmation if the user presses the *Clear* button to clear all search +filters in a search form. +* *confirmSave* – If this is set to *true* the user will be asked for +confirmation before changes are saved in response to clicking on the +*Save* button in an edit form. +* *detailsEditLayoutButtonsOnSameRow –* this is used to specify whether +the buttons in a *DetailsEditLayout* that is nested in an edit form will +appear on the same row as the edit components. If set to *false* the +buttons will be displayed on a separate row below the edit components. +* *detailsGridSearchMode* – Used on a *DetailsEditGrid* to specify that +it must be used in search mode. In this mode, you can bring up a modal +search dialog for adding entities to the grid. +* *detailsGridSelectionMode –* Used to set to grid selection mode +(SINGLE or MULTIPLE) of a details grid. Normally, you will want to keep +this at the default value SINGLE, but setting this to multiple will +allow you to select multiple rows at a time. +* *detailsModeEnabled –* Indicates whether it is possible to navigate +from the search mode of a *SearchLayout* to the details view (the part +that shows the details of a single selected entity). Note that this is +different from setting *showEditButton* to false – setting *editAllowed* +to false ensures there is still a detail view but it cannot be used in +edit mode. Setting *detailsModeEnabled* to false completely turns off +the detail view. +* *doubleclickSelectAllowed –* Specifies whether the user can navigate +to a detail screen in a *SearchLayout* by double-clicking on a row in +the results grid. This is enabled by default. +* *editAllowed* – specifies whether the *Edit* button will show up +above/below and edit form when the screen is in view mode. Set +*openInViewMode* to true and this setting to false if you want to +disable editing for a component. +* *enableAdvancedSearchMode* – by setting this to true, you enable the +“advanced search mode” which means that you can toggle between the +simple and advanced search mode. Attributes with search mode set to +ADVANCED will only show up when the screen is in advanced mode. +* *exportAllowed* – This specifies whether it is allowed to export the +data in a grid to Excel or CSV by right-clicking on the grid. +* *exportMode* – This specifies the desired export mode. The possible +values are *FULL* (exports all visible attributes) or +*ONLY_VISIBLE_IN_GRID* (exports only attributes that are visible in the +grid). The default value is *ONLY_VISIBLE_IN_GRID.* +* *gridEditMode* – This specifies the desired edit mode inside an +*EditableGridLayout* (see below). The possible values are *SINGLE_ROW* +(edit one row at a time using an in-line editor) and *SIMULTANEOUS* +(edit multiple rows at once). +* *openInViewMode* – By default, any forms used for editing entities +will be shown in edit mode. If you set *openInViewMode* to true, then +the edit form will open in view mode and you need to click the *Edit* +button to put the form in edit mode. +* *placeButtonBarAtTop* – If set to true, the application will display +the button bar (containing *Save*, *Cancel*, *Edit* buttons etc.) above +of the title label (rather than behind it) inside the edit form. This is +*true* by default. +* *preserveAdvancedMode* – this setting is used to specify whether the +“advanced search mode” should be preserved when navigating away from a +search screen and then back. It defaults to *true.* +* *preserveSearchTerms* – this setting ensures that any search terms are +preserved when navigating away from the screen and then back. By default +it is set to *true.* +* *preserveSelectedTab* – This specifies whether the selected tab will +be preserved when switching between entities (by using the *Previous* +and *Next* buttons) in the detail view of a *SearchLayout*. +* *preserveSortOrder* – specifies whether the sort order will be +preserved and restored when the navigating away from a screen and then +returning. +* *screenMode* – Switches between displaying components next to each +other or below each other (for the various *SplitLayout* components). +* *searchImmediately* - The option to search immediately when opening a +search screen (as opposed to waiting until the user presses the search +button for the first time). This is *true* by default. If disabled, the +user will see a label indicating that he/she must perform a search +rather than a search results grid. +* *showAddButton* – Use this to show or hide the *Add* button that can +be used to create new entities. +* *showCancelButton* - The option to hide or show the *Cancel* button +when in edit mode (shown by default after switching to edit mode, after +the screen has been opened in view mode). +* *showClearButton* – Used to show or hide the *Clear* button that shows +up below the search fields in a search form. +* *showEditFormCaption* – Whether to display a caption (that lists the +name/description of the thing you are editing) above an edit form. This +defaults to *true.* +* *showDetailsGridDetailsPanel –* Whether to display a panel containing +the details of the selected row when a row is selected in a +DetailsEditGrid. +* *showDetailsGridPopup –* Whether to display a button that opens a +pop-up to view the details of the row that is selected in a +DetailsEditGrid. +* *showNextButton –* This will cause a *Next* button to be displayed in +the button bar of the detail screen of a *SearchLayout*. This button can +be used to navigate to the next entity in the search results. +* *showIterationsButtons* – This is shorthand for *showNextButton* and +*showPrevButton* and will toggle the appearance of the *Next* and +*Previous* buttons. +* *showPrevButton* – This will cause a *Previous* button to be displayed +in the detail screen of a *SearchLayout.* This button allows the user to +navigate to the previous entity in the search results. +* *showRefreshButton* – Specifies whether to display a “refresh” button +in the button bar of the details mode for a search layout. This button +can be used to refresh the currently displayed entity without having to +navigate away. +* *showRemoveButton* - The option to hide or show the Remove button +below the search results grid (hidden by default). +* *showToggleButton* - The option to show a “show/hide” button inside a +search form in *SearchLayout* that can be used to show/hide the search +form (hidden by default). +* *splitLayoutSearchButton* – Specify whether to include a search button +above a split layout. This search button will bring up a search dialog +that allows you to filter the results that show up in the grid. In +addition to the search button, Dynamo will also render a clear button +that can be used to revert to the default filtering. +* *startInAdvancedMode* – Specifies whether the search form will be +displayed in Advanced Mode (if available) when first opening a screen. + +Finally, you can use the *setReadOnly* method as a shorthand for making +sure that a screen always opens in read-only mode (under the covers this +hides the appropriate buttons and sets *openInViewMode* to true). + +The *SimpleSearchLayout* also supports disabling the *Search* button +until all required search fields have been filled (the required search +fields are those marked as *requiredForSearching* in the entity model) – +if your search form includes any mandatory field to search on, the +*Search* button will simply be disabled until values for all these +mandatory fields have been filled in by the user. + +*SimpleSearchLayout* offers some additional functionality: + +* The *defaultFilters* property can be used to set a list of filters +that will be applied to every search query (even when the user leaves +all search fields blank). Use this if you don’t want to search on all +entities in the system, but only on a certain subset. These filters are +always preserved, even after pressing the *Clear* button. +* The *addFieldFilter* method can be used to apply additional filters to +search fields that allow the user to pick from a set (notably, for +attributes of type *MASTER* or *DETAIL*). E.g. suppose that you have a +search field for attribute “country” that displays a list of countries. +Say you only want to display countries whose name contains the string +“ab” then you can add the following field filter: + +____ +layout.addFieldFilter("countryOfOrigin", *new* +LikePredicate("name", "%au%", *false*)); +____ + +* *detailJoins* is used to pass along the relations that must be fetched +when retrieving a single entity when displaying the details screen. If +you specify a non-empty list of relations here it will be used when +retrieving a single entity (e.g. just before that object is displayed in +a details screen). If left empty, this property will fall back to the +*joins* that are defined in the DAO implementation. Any joins that are +passed to the constructor will be ignored when retrieving a single +entity. +* The *addSortOrder* method can be used to add additional sort orders +(the first sort order can be passed to the constructor). +* *addFieldEntityModel* can be used to set a specific entity model to be +used for rendering a particular input component. This method takes two +arguments: the name of the attribute and the reference used to identity +the entity model. This method can e.g. be used if you want to modify +which fields show up inside a pop-up dialog in a lookup component (if +you don’t specify anything, the default entity model for the entity to +search for will be used, which will probably result in many more search +fields than will fit inside the pop-up). +* *setMaxResults* can be used to specify the maximum number of allowed +search results. If the number of search results exceeds this value, then +the result set will be truncated after the specified amount and the user +will be shown a warning message. + +You can also use the *setSearchValue* methods to explicitly set a +predefined search value for a certain search field. + +By default, the component starts in search mode, meaning that an empty +search form and a grid of search results are rendered. You can use the +following methods to modify the mode: + +* *reload* will revert the component to search mode, and clear the +search form +* *refresh* will also revert the component to search mode, leave the +search form intact, but refresh the search results. It will also refresh +the contents of any lookup components in the search form. +* *search* will carry out a search (using the currently entered search +terms) +* *searchMode* will revert the component to the search mode. +* *reloadDetails* will refresh the detail screen (only useful when you +are already in details mode). + +In addition to this, the behaviour of the component can be modified by +using callback methods. This is covered in a later section. + +==== Complex Details Mode + +Normally, when you select an entity in the results grid of a search +layout, you will be taken to an edit screen that allows you to edit the +selected entity. This generally should be sufficient, but sometimes it +can also be useful to be able to edit or view additional entities that +are connected to the entity being edited. E.g. when you select an +organization you also want to have a tab that allows you to view and +edit that organization’s members. This is where “complex details mode” +comes into play. To enable it, set the “*complexDetailsMode*” property +on the FormOptions to true. + +Next, you must call the *setDetailModeTabCaptions* to set the caption of +the individual tabs. Implicitly this will also specify the number of +tabs. + +layout.setDetailsModeTabCaptions(*new* String[] \{ "Organizations", +"Members" }); + +You can then use the *addDetailTabCreator* method to specify what the +individual tabs must look like: + +layout.addDetailTabCreator(0, (formOptions, isNew) -> \{ + +EntityModel em2 = +getModelFactory().getModel(Organization.*class*); + +*return* *new* SimpleEditLayout(layout.getSelectedItem(), organizationService, em2, + +formOptions, *new* FetchJoinInformation("members"), *new* +FetchJoinInformation("countryOfOrigin")); + +}); + +This method takes two parameters: the zero-based index of the tab and a +bifunction that takes the form options and a boolen that indicates +whether a new entity is being created, and must return the component +that is to be constructed (in the example above this is a +SimpleEditLayout). You must call this method once for every tab you want +to add. + +Now, when you select a row in the search results grid, you will be taken +to an edit screen that contains multiple tabs. Clicking on a tab will +bring up the appropriate composite layout component. + +If you want a title to be displayed above the tabs, you can use the +*setDetailsModeTabTitle* to set it. + +When you try to create a new entity using a search layout while in +complex details mode, you will not immediately get to see all the tab +sheets after clicking on the *Add* button. Instead, you will just see an +edit form for creating the entity. This edit form is constructed by +calling the *TabCreator* with index 0 so it’s identical to the first +tab. + +To display an icon in front of the title of tab, use the +*setIconCreator* method. + +=== FlexibleSearchLayout + +As an alternative to using the *SimpleSearchLayout* you can use the +*FlexibleSearchLayout.* This component is instantiated in almost exactly +the same way as the *SimpleSearchLayout* but behaves differently: once +you open the search screen you will see an empty search form and a +button that allows you to add extra filters. + +When pressed, this button will add a new filter row to the search form – +this filter row will contain a combo box that allows the user to select +the attribute they want to filter on. Selecting an attribute will then +cause another combo box to appear, which can be used to select the type +of filter (e.g. equals, greater than, etc.). When the user selects a +value from this second combo box, an additional input component (or in +some cases, two components) will show up that allows the user to enter +the exact value(s) to filter on – the component that will be rendered +here is the same as the component that would show up in the +*SimpleSearchLayout* and can be changed by using the *searchSelectMode* +and *selectMode* settings. + +The only significant way in which the *FlexibleSearchLayout* behaves +differently from the *SimpleSearchLayout* is with regard to mandatory +search properties: when you create a *FlexibleSearchLayout,* the +framework will automatically add a filter row to the search form for +every property that is marked as *requiredForSearching* (and these +filters cannot be removed). + +=== SplitLayout + +A *SplitLayout* is a layout that contains both a results grid and an +edit form, displayed either next to each other or below each other (with +the results grid on top). You can use the *screenMode* parameter on the +*FormOptions* object that you pass to the layout’s constructor to set +the desired orientation. + +An example of a split layout in horizontal mode can be seen here: + +image:vertopal_1eda064bf39f485baa9f64be38994881/media/image11.png[image,width=560,height=312] + +Note that there are several variants of the *SplitLayout*: + +* *ServiceBasedSplitLayout* – retrieves the data to display by calling +methods on the *BaseService*. +** *ServiceBasedDetailLayout* – a service based split layout that keeps +a reference to a parent entity that is typically used for filtering. +* *FixedSplitLayout* – a split layout which is instantiated with a +(fixed) collection of data to display (it doesn’t use the service to +retrieve the data) +** *FixedDetailLayout* – a fixed split layout that keeps track of a +parent entity + +The *Detail* variants that keep a reference to a parent entity are +useful when you are displaying a collection of that data that depends on +a parent object that has been selected before – imagine for example that +you have an application that first lets you pick an organisation and +then allows you to browse a list of members of that organisation – in +this case you can use a *ServiceBasedDetailLayout* that uses the +organisation as the parent object. + +A simple example of a *ServiceBasedSplitLayout* looks as follows: + +EntityModel em = getModelFactory().getModel(Person.*class*); + +FormOptions fo = *new* FormOptions(); + +ServiceBasedSplitLayout layout = *new* +ServiceBasedSplitLayout(personService, em, +QueryType.ID_BASED, fo, *null);* + +main.addComponent(layout); + +You can specify the following parameters when creating the layout: + +* The service that is used to retrieve data from the database. +* The Entity Model that is used to construct the layout. +* The query type (see section 6). +* A *FormOptions* parameter object. +* A sort order (optional). +* A list of relations to fetch when executing the search query (vararg). + +By default, the data displayed by the component will not be filtered. If +you want to apply a filter to the data, you can do so by using the +*setFilterCreator* method. + +Like the *SimpleSearchLayout*, the *SplitLayout* supports the use of +field filters__.__ + +The *SplitLayout* components support the use of the *FormOptions* +parameter object. Of special interest is the *screenMode* setting, which +allows you to specify whether to display the form in vertical mode (grid +above the edit form) or horizontal mode (grid to the left of the edit +form). The default value is *HORIZONTAL*_._ + +For the *ServiceBasedSplitLayout* you can also set the +*showQuickSearchField* property to true or false to specify whether a +quick search field must appear above the results grid. The default value +is *false.* If you set it to *true*, then the screen will contain a +quick search field for searching on the *main* attribute of the entity. +If this is not sufficient, you can use the *setQuickSearchSupplyFilter* +method to define you own custom search filter. This method takes a +function that translates the search term entered by the user to the +desired search predicate. + +As with the *SearchLayout* components, you can use the *reload*, +*refresh* and *reloadDetails* methods to refresh/reload the component. + +=== SimpleEditLayout + +The *SimpleEditLayout* component can be used to display or edit a single +entity. It is useful if you already have selected an entity that you +want to display or edit, without having to first select it using a +search screen or results grid. + +Creating a *SimpleEditLayout* is similar to creating the other composite +components and looks as follows: + +EntityModel em = +getModelFactory().getModel(Organization.class) + +FormOptions fo = *new* +FormOptions().setOpenInViewMode(*true*).setEditAllowed(*true*); + +SimpleEditLayout editLayout = *new* +SimpleEditLayout(getEntity(), +organizationService, em, fo); + +You can pass the following parameters to the constructor: + +* The entity of which you want to display the details (can be null; in +this case a new entity will be created automatically). +* The Service that is used to retrieve data from the database +* The Entity Model that is used to construct the layout. +* A *FormOptions* object that specifies how the layout behaves. +* A list of relations to fetch when executing the search query (vararg – +defaults to settings on the DAO if omitted). + +As there is no results grid in this layout, it is not possible to define +a sort order or a query type for this component. + +=== EditableGridLayout + +The *EditableGridLayout* component can be used to display and edit a +collection of entities inside a grid. The constructor for the +*EditableGridLayout* constructor looks as follows: + +*public* EditableGridLayout(BaseService service, EntityModel +entityModel, + +FormOptions formOptions, SortOrder sortOrder, FetchJoinInformation... +joins) \{ + +*super*(service, entityModel, formOptions, sortOrder, joins); + +} + +This results in a grid in which items can directly be edited, added, and +deleted. + +You can use the *GridEditMode* setting on the *FormOptions* to switch +between the two available edit modes: + +* *SINGLE_ROW* mode allows you to edit one row at a time. In this edit +mode, the data in the grid is shown in read-only mode. You can click on +a button in each row in the grid to open an in-line editor row that +allows you to edit the selected row. The in-line editor contains a +*Save* button that can be used to save just the changes to this row. +* *SIMULTANEOUS* mode allows you to edit multiple rows at once. In this +mode, all the rows in the grid are shown in edit mode, and a *Save* +button will show up below the grid. When clicked, this button will save +all the pending changes for all modified rows at once. + +Please note that in *SIMULTANEOUS* mode a lot of input components might +be have to be rendered at once. This can have consequences for the +performance of the application. We advise using the *SINGLE_ROW* mode by +default and only using the *SIMULTANEOUS* mode when you know that the +amount of data that will be shown in the grid is limited. + +image:vertopal_1eda064bf39f485baa9f64be38994881/media/image12.png[image,width=560,height=143] + +It may be tricky to display components that take up more than a single +line (e.g. *TokenFieldSelect*). We advise sticking to components that +only take up a single line. + +The *gridSelectMode* from the attribute model will be used to determine +which component to render inside an editable grid. + +Apart from these two grid modes, the EditableGrid also supports +switching between view mode and edit mode. This is independent of the +grid mode: if you set *openInViewMode* to true, the grid will +(obviously) open in view mode first, with an edit button below it. If +your press this button and the grid mode is *SINGLE_ROW* then the grid +will still be in read-only mode afterwards, but you can use the buttons +showing up at the end of the rows to put a single row in edit mode. + +It can be a bit awkward to use multi-line components like token fields +or list selects inside a grid. You can use the *gridSelectMode* setting +on the *@Attribute* annotation to specify which type of component to use +inside an editable grid. This overrides the value of the *selectMode* +setting. + +For an attribute of type String, Dynamo will always render a text field +(rather than a text area) inside an editable grid. The value of the +*textFieldMode* will be ignored in this case. + +When you want to add a new entity to using the EditableGridLayout, you +can do this (as usual) by clicking the *Add* button. This will bring up +a dialog that can be used to create the new entity: + +image:vertopal_1eda064bf39f485baa9f64be38994881/media/image13.png[image,width=560,height=439] + +=== HorizontalDisplayLayout + +The *HorizontalDisplayLayout* is a very simple component that simply +displays the attributes of an entity using a horizontal layout. Only +properties of type *BASIC* are displayed inside this component. + +=== TabLayout + +This is a component that is meant as an alternative to the standard +Vaadin Tabs component, which immediately loads all tab sheets when it is +first created. The *TabLayout* component improves upon this by only +loading a tab when it is opened. Until then, it simply constructs an +empty dummy tab. The *TabLayout* will also make sure that any tab is +properly refreshed before being opened to ensure that the most +up-to-date data is shown. + +To use this component, you need to create an instance of it and then set +a number of methods that define the captions, icons, and actual tab +contents: + +tabLayout = *new* TabLayout(selection); + +tabLayout.setDescriptionsCreator(index -> \{ + +*switch* (index) \{ + +*case* *_INDEX_PROGRAM_LIST_*: + +*return* message("blip.programme.list.description"); + +*case* *_INDEX_PROGRAM_COUNT_*: + +*return* message("blip.programme.count.layout.description"); + +*default*: + +*return* *null*; + +} + +}); + +tabLayout.setIconCreator(index -> \{ + +*switch* (index) \{ + +*case* *_INDEX_PROGRAM_LIST_*: + +*return* VaadinIcon.*_LIST_*.create(); + +*case* *_INDEX_PROGRAM_COUNT_*: + +*return* VaadinIcon.*_ABACUS_*.create(); + +*default*: + +*return* *null*; + +} + +}); + +tabLayout.setCaptions(*new* String[] \{ message("blip.programme.list"), +message("blip.programme.count.layout") }); + +tabLayout.setTabCreator(index -> \{ + +SerializablePredicate filter = fsf.extractFilter() == *null* +? statusFilter + +: *new* AndPredicate<>(fsf.extractFilter(), statusFilter); + +*switch* (index) \{ + +*case* *_INDEX_PROGRAM_LIST_*: + +*return* constructProgrammeListLayout(() -> fsf.extractFilter() == +*null* ? statusFilter + +: *new* AndPredicate<>(fsf.extractFilter(), statusFilter), +tabLayout.getEntity()); + +*case* *_INDEX_PROGRAM_COUNT_*: + +*return* constructProgrammeCountLayout(filter); + +*default*: + +*return* *null*; + +} + +}); + +These methods mostly follow a similar structure: they take a lambda +function as an argument – the lambda in turn takes the input of the tab +as the argument and return a component, icon or String. The exception is +the “setCaptions” method which is just used to define the captions of +the individual tabs (and implicitly, the total number of tabs). + +Note that whenever a tab is opened after the first time, the framework +automatically reloads the contents of that tab – if the tab contains any +of the standard Dynamo components, this will mean that they will be +reloaded automatically. + +If you want to create a tab that contains custom content, you can have +your custom component implement the *Reloadable* interface and put any +code that refreshes the component inside the *reload* method. + +=== ProgressForm and UploadForm + +*ProgressForm* is a component that can be used for executing +(synchronous) batch processes. It comes with a built-in progress bar +that can be used to give the user feedback on the status of the batch +process. + +Note that you can set the mode of the form: it can either be *SIMPLE* +(no progress bar is used) or *PROGESSBAR* (a progress bar is used to +keep track of the progression) + +It its most basic form, the *ProgressForm* is simply a class that offers +some basic method to construct a form: + +* The *buildMain* method can be used to build the form. It can contain +UI components that can be used to modify the parameters of the batch +process, or one or more buttons to start the process. Note that by +default the *ProgressForm* does _not_ contain a button to start the job. +* The *estimateSize* method can be used estimate the size of the data +that is being processed. You can use any means necessary in order to +arrive at an estimate. This method does not do anything in case the mode +of the progress form is set to *SIMPLE*. +* The *startWork* method must be called to start the batch process. +Typically, this is the method that you will call from e.g. a Button’s +ClickListener. +* The *setProcess* method is the method that contains the actual logic +that must be called to carry out the batch process. Typically, you will +want to delegate to a service method here. +* If you need to carry out any validations before starting the process, +call the *setIsFormValid* method an add any required checks. Return +*false* from this method if you want to abort the processing. + +Under the covers, the progress form will use the *prepare* method to +spawn two threads: one thread in which the actual processing is carried +out and one thread that will periodically poll for the progress of the +job and update the progress bar. Once the process starts, the component +will automatically switch to the progress bar view, and once it is done +it will switch back to the form view. You can use the +*afterWorkComplete* method to carry out an action after the job +completes. + +By default, the *ProgressForm* uses a *ProgessCounter* to keep track of +the progress – if you want to use this mechanic, you can pass it along +to the code that performs your batch processing and increment it +whenever a step of your processing is done. The polling thread will use +this to determine the completion rate of the job. + +*UploadForm* is a subclass of *ProgressForm* that can be used to easily +create a file upload form – it already contains a file upload component +that will kick off the batch process once the file has been uploaded. + +=== DetailsEditGrid + +The Dynamo framework contains built-in support for managing many-to-many +relations (to already existing entities) in edit forms (a token field +component will be rendered by default) but this does not cover the +situation in which you have an object that contains a one-to-many +relation in which the detail elements are an integral part of the entity +being edited. A classic example of this would be the creation of a new +order and the order lines that make up the order: the order lines do not +exist on their own but are an integral part of the order. + +To be able to create both an entity and its related sub-entities, you +can use a *DetailsEditGrid*_._ This is a component that you must +initialize explicitly. + +You can do so by calling the *addCustomField* method on the component to +which you want to add the DetailsEditGrid. + +This looks as follows: + +addCustomField("translations", context -> \{ + +FormOptions fo = *new* +FormOptions().setShowRemoveButton(*true*).setDetailsEditLayoutButtonsOnSameRow(*false*) + +.setDetailsGridSearchMode(*false*); + +EntityModel model = +getEntityModelFactory().getModel(GiftTranslation.*class*); + +DetailsEditGrid dt = *new* +DetailsEditGrid<>(model, context.getAttributeModel(), + +context.isViewMode(), fo); + +dt.setService(giftTranslationService); + +dt.setCreateEntity(() -> \{ + +GiftTranslation translation = *new* GiftTranslation(); + +translation.setDescription("desc"); + +getSelectedItem().addTranslation(translation); + +*return* translation; + +}); + +*return* dt; + +}); + +[arabic] +. The *DetailsEditGrid*, like many composite components, takes two type +parameters: the type of the primary key of the entity and the type of +the entity itself. +. The constructor takes a *FormOptions* parameter object which you can +set to e.g. specify if a *Remove* button must be shown. By default, an +*Add* button is shown but the *Remove* button is hidden. +. Finally, the constructor takes a Boolean *viewMode* parameter. You can +simply pass along the parameter from the *context* or set your own +value. +. The *setCreateEntity* method can be used to initialize a new entity. +It is called after the user clicks the *Add* button below the grid. +. Likewise, the *setRemoveEntity* method can be used to remove the +detail object from the parent entity in response to a click on the +*Remove* button. + +Note that it is also possible to use the *DetailsEditGrid* as a lookup +component for a many-to-many relation. You can do so by setting the +*detailsGridSearchMode* on the FormOptions parameter object to *true:* + +FormOptions fo = *new* FormOptions().setDetailsGridSearchMode(*true*); + +This will display the *DetailsEditGrid* in read-only mode and will also +add a *Search* button below the grid that will bring up a dialog that +allows you to select multiple (already existing) entities to add to the +grid/couple to the entity being edited. Note that in this mode it is not +possible to manually add new entities – you are looking up and coupling +existing entities rather than creating new ones. As such, the +*setCreateEntity* method is pointless in this mode. However, in this +scenario you must use the *setLinkEntity* method which must contain the +code that is required to link an item that was selected in the search +dialog to the entity that is being edited in the edit form. + +If the collection that you are using to store the entities that are +displayed in the grid does not allow duplicates (e.g. it is a Set), you +have to provide appropriate implementations of *hashCode* and *equals* +in your entity class that ensure that objects are never considered equal +if they have an ID that is *null* – without this measure, you can’t +(properly) add multiple entities at the same time without saving the +changes in between. + +=== ServiceBasedDetailsEditGrid + +In the case that you want to edit an 1-to-N relationship or N-to-N +relationship inside an edit form but the number of coupled entities is +potentially very large (e.g. the relation from a country to its +inhabitants), the approach described above using the *DetailsEditGrid* +is no longer sufficient. Instead, you can use the +*ServiceBasedDetailsEditGrid* which will lazily load the coupled +entities when needed. + +Note that in this case, there must not be a JPA relation modelled in +your Java code, so we need to employ some tricks in order to get an edit +component to show up. + +You can start by adding a property like this to your (parent) entity: + +@Attribute(visibleInGrid = VisibilityType.*_HIDE_*) + +*public* Integer getOrganizationId() \{ + +*return* id; + +} + +This is simply a getter that has the same value as the entity’s primary +key. It is simply used as a hook for starting the custom field +construction. + +Next, you can use the following code to instantiate the custom component +inside the *addCustomField* method: + +____ +ServiceBasedDetailsEditGrid grid += *new* ServiceBasedDetailsEditGrid<>( + +personService, getModelFactory().getModel(Person.*class*), +attributeModel, viewMode, + +*new* +FormOptions().setDetailsGridSearchMode(*false*).setShowRemoveButton(*true*)); + +grid.setFilterCreator(org -> *new* EqualsPredicate<>("organization.id", +org.getId())); + +grid.setCreateEntity(() -> \{ + +Person person = *new* Person(); + +person.setOrganization(getSelectedItem()); + +*return* person; + +}); + +grid.setRemoveEntity(person -> \{ + +person.setOrganization(*null*); + +personService.save(person); + +}); + +*return* grid; +____ + +As you can see, the *ServiceBasedDetailsEditGrid* takes four type +parameters; the first two are identical to the ones used by the basic +*DetailsEditGrid.* The third and fourth parameters respectively +represent the type of the primary key of the parent entity and the type +of the parent entity (the parent entity is the entity that you are +editing/displaying in the edit form that this component is part of). + +The methods for creating and removing entities (*setCreateEntity* and +*setRemoveEntity*) function in roughly the same way as for the +*DetailsEditGrid.* + +Of special interest is the *setFilterCreator* method, which is used to +specify the filter that is used to limit the entities that show up. In +this case, we apply a filter that makes sure that only the members for +which their organization matches the organization that is currently +being edited in the edit form show up. + +If you create a *ServiceBasedDetailsEditGrid* in the way described +above, the application will render a grid in read-only mode, with each +row in the grid containing an edit button. Clicking on this button will +bring up a dialog that allows you to edit the entity displayed in that +row. + +There will also be an *Add* button below the grid – this can be used to +bring up a dialog that can be used to add a new entity and couple it to +the parent entity being edited in the form. + +Note that if you have a pre-existing collection of entities that you +want to couple to the entity being edited (a many-to-many relation) you +can use the grid in couple mode as well. As with the basic +*DetailsEditGrid* you can specify this via the form options. + +FormOptions fo = *new* FormOptions().setDetailsGridSearchMode(*true*); + +In this mode, there will not be any *Add* or *Edit* buttons, but instead +you will see a *Search* button below the grid. Clicking this button will +bring up a search dialog that allows you to select the entities to link. +You can use the *setSearchDialogFilters* method to specify the (default) +filters that must be applied when within the search dialog. + +In this mode, you will need to use the *setLinkEntity* method to specify +the code that is to be carried out to link the selected item(s) from the +dialog screen to the entity that is being edited in edit form. The +method is called once for each entity that was selected in the dialog +screen. + +=== DetailsEditLayout + +A slightly more complex use case occurs in the situation in which you +want to manage a one-to-many relation inside an edit form, while using a +nested form per dependent entity rather than a grid for all dependent +entities together. This can be achieved by using a *DetailsEditLayout* +component which can be constructed in a way similar to the creation of a +*DetailsEditGrid:* + +The *DetailsEditLayout* constructor takes the following arguments: + +* The service used communicating with the database. +* The collection of entities to display within the component – for each +entity, a separate nested edit form will be rendered. +* The entity model used to render the edit forms. +* The attribute model that must be used to create the nested forms (this +is only used to set the caption that appears in the main form) +* The view mode parameter that determines whether the forms are in view +mode or edit mode. +* The familiar *FormOptions* object. +* A comparator that can be used to order the entities before the nested +forms are rendered (optional). + +As is the case with the *DetailsEditGrid,* you can use the +*setCreateEntity* and *setRemoveEntity* methods to specify what to do in +order to add a new entity or remove a coupled entity. + +DetailsEditLayout dt = *new* +DetailsEditLayout(giftTranslationService, +getEntityModelFactory().getModel(GiftTranslation.*class*), +attributeModel, + +viewMode, fo, *new* AttributeComparator<>("description")); + +dt.setCreateEntity(() -> \{ + +GiftTranslation gt = *new* GiftTranslation(); + +GiftLayout.*this*.getSelectedItem().addTranslation(gt); + +*return* gt; + +}); + +dt.setRemoveEntity(gt -> \{ + +Gift gift = GiftLayout.*this*.getSelectedItem(); + +gift.removeTranslation(gt); + +s}); + +As described above, the framework will render a sub-form that is nested +within the main edit form for every entity. For each sub-form a *Remove* +button will be included, and below the last sub-form an *Add* button +will be placed that can be used to create a new sub-form. + +It is not possible to individually save the sub-forms – any changes will +be saved to the database only when the *Save* button for the main edit +form is clicked. + +=== EntityPopupDialog + +The *EntityPopupDialog* is a simple modal dialog that can be used to +add, modify, or view a single entity. It can be used as follows: + +Button popupButton = *new* Button("Popup"); + +popupButton.addClickListener(event -> \{ + +EntityPopupDialog popupDialog = *new* +EntityPopupDialog(organizationService, + +getSelectedItem(), +getEntityModelFactory().getModel(Organization.*class*), *new* +HashMap<>(), *new* FormOptions()); + +popupDialog.build(); + +popupDialog.open(); + +}); + +buttonBar.add(popupButton); + +registerComponent(popupButton); + +As you can see, the *EntityPopupDialog* takes many of the same +parameters as the other composite components: + +* The service used to query the database. +* The entity to display (the “getSelectedItem” in the example above) +* The entity model used to construct the dialog. +* The *FormOptions* used to modify how the form is constructed. + +The code above will open a modal dialog that allows you to edit the +selected entity: + +image:vertopal_1eda064bf39f485baa9f64be38994881/media/image14.png[image,width=560,height=543] + +By default, the popup will open in edit mode. You can modify this by +using the *FormOptions* and setting *openInViewMode* to true. + +=== CompositionLayout + +A *CompositionLayout* is a layout that allows you to group multiple +composite components together. The most common use case for this is +inside a tab page of a lazy tab layout: once the tab is selected, the +framework will reload the contents of the *CompositionLayout,* which +will in turn delegate the reload request to all the components nested +within. + +When you want to use a *CompositionLayout,* call its +*setBuildMainLayout* method and create any components that you want to +nest inside this method. For each component, register it with the +*CompositionLayout* using the *addNestedComponent* method. + +== Callbacks and method overrides + +The previous sections should have given a good impression of the +capabilities of the Dynamo Framework and the declarative mechanisms that +are available to modify its functionality without writing custom code. +However, there are some situations in which these mechanisms are not +enough. To handle this, most components in the Dynamo framework offer +callback methods that can be used to interfere in the component +generation process. + +These methods typically come in two variations: + +* Methods that are called just once during the component creation +process +* Methods that are called in response to a user action, e.g. when the +user presses a button or selects a row in the grid. + +To gain a better understanding of this, note that nearly all components +in the Dynamo framework are lazily constructed and that the individual +moving parts of a composite component (e.g. the search grid, the edit +form) are lazily constructed as well. + +This means that at the time you declare an instance of a composite +component, its layout is not constructed yet. This allows to make +changes e.g. to modify the page length or to defined field filters or +default filters – at this point you are only calling setter methods. + +Once the component is added to its parent component, Vaadin will call +the *onAttach* method, and this method has been overridden to call the +*build* method which is implemented by all composite components and it +used to lazily construct the starting state of the component. E.g. for a +search layout, the framework will initialize the search form and the +results grid. During this process, callback methods are carried out. + +Other parts of the component are not immediately created, e.g. the edit +form in only created the first time the user presses the Add or +View/Edit button. This might also trigger some callback methods. + +In case it is not possible to directly attach a component to its parent +component, you can also programmatically call the *build* method to +construct the component. + +A consequence of this construction method is that there are many methods +that cannot be called before the component has been attached (or built) +– e.g. trying to retrieve the search result grid or search form will +simply result in a nullpointer. Therefore, you should always observe the +following order: instantiate – call setters – build/attach. + +Finally, note that even though components are constructed lazily and +just once as long as the user stays on the same view, they are still +rebuilt whenever the user navigates to a new view. + +The next sections will go over some of the available callback methods. +Not all callback methods are supported by (or make sense) for all +composite components. For each method, we specify for which composite +component it is available. We distinguish: + +* SearchLayouts (*SimpleSearchLayout* and *FlexibleSearchLayout*) +* SplitLayouts (*ServiceBasedSplitLayout*, *FixedSplitLayout*) +* *EditableGridLayout* +* *SimpleEditLayout* + +=== Initialization methods + +These are methods that are typically called to initialize the component. + +[width="100%",cols="31%,27%,42%",options="header",] +|=== +|*Method* |*Available on* |*Functionality* +|addCustomConverter |All composite components |Registers a custom +converter for a certain attribute model. When constructing the component +for that attribute model, this custom converter will be used instead of +the default converter + +|addCustomField |All composite components a| +This method can be used to modify the component creation process. To use +it, call it using the name of the attribute for which you want to create +the custom component, and a lambda function that accepts the component +creation context and must return your custom component. + +The context can be used to retrieve the attribute model for the +attribute, and can be used to check whether the component is being +constructed for the search form or for a form that is in view mode. (See +below for more explanation) + +|addCustomRequiredValidator |All composite components |Registers a +custom validator that will only check whether a value has been entered +for a certain attribute model. + +|addCustomValidator |All composite components |Registers a custom +validator for a certain attribute model. When constructing the component +for that attribute model, this custom converter will be used instead of +the default converter + +|addFieldEntityModel |All composite components |Used for registering an +entity model that is to be used for constructing a specific field. This +can be used to modify the entity model that is to be used inside an +EntityLookupField + +|addFieldFilter |All composite components |Adds a field filter that is +to be used when constructing a specific field + +|addSortOrder |All composite components except SimpleEditLayout |Adds a +sort order. This sort order is applied in addition to the sort order +that is provided in the constructor + +|registerComponent |All composite components a| +Registers a component (usually a button) that is added to the button +bar, so that it can be enabled/disabled when an entity is selected. By +default, a registered component will be enabled when a row is selected +in the results grid and disabled when this is not the case. + +See below for more explanation. + +|setAfterEditFormBuilt |SearchLayouts, SplitLayouts |Executed after the +edit form (inside a SearchLayout or SplitLayout) has been created for +the first time. + +|setAfterLayoutBuilt |All composite components |This method is built at +the end of the component construction process. Use this method to make +any changes to the constructed component, e.g. changing button captions, +hiding grid columns etc. + +|setDefaultFilters |SearchLayouts |Sets the default filters that will +always be applied to any search request, regardless of what is entered +in the search form. + +|setEditAllowed |All composite components |Use this method to specify +when the user is allowed to perform edit actions within the component. +This comes in addition to the settings from the form options, i.e. +editing is only possible if this method return true AND the component is +not in read-only mode. + +|setEditColumnThresholds |All composite components |Sets the threshold +values at which new columns will be rendered inside an edit form + +|setFindParentGroup |All composite components |This method provides a +mapping from an attribute group key (as defined on the entity) to the +parent group specified using the setParentGroupHeaders method + +|setGridHeight |All except for SimpleEditLayout |This method is used to +set the height of the search results grid + +|setLoadItemCreator |FixedSplitLayout |This code that is carried out to +retrieve the fixed collection of items to display in a FixedSplitLayout + +|setMustEnableComponent |All composite components |This method is called +after a user select a row in a grid, to see which button bar components +must be enabled + +|setOnAdd |SearchLayouts, SplitLayouts |The code that is carried out +after the Add button is clicked. By default this will open the details +mode for creating a new entity. + +|setOnEdit |SearchLayouts, SplitLayouts |The code that is carried out +after the Edit button is clicked. By default this will open the selected +entity in the details mode + +|setOnRemove |SearchLayouts, SplitLayouts |The code that is carried out +after the Remove button is clicked. By default this will delete the +selected entity + +|setPostProcessButtonBar |SimpleEditLayout a| +This method is called after the button bar of a component has been +constructed. It can be used to add additional buttons or other +components. + +This method is used to modify the button bar that appears above and +below an edit form + +|setPostProcessDetailButtonBar |All except for SimpleEditLayout a| +This method is called after the detail button bar of a component has +been constructed. It can be used to add additional buttons or other +components. + +This method is used to modify the button bar that appears above and +below an edit form + +|setPostProcessEditFields |All composite components |This method is +called after all fields inside an edit form have been constructed. It +can be used to e.g. add dependencies between the fields + +|setPostProcessMainButtonBar |All except for SimpleEditLayout a| +This method is called after the main button bar of a component has been +constructed. It can be used to add additional buttons or other +components. + +The “main” button bar is the button bar that appears below a results +grid + +|setPostProcessSearchButtonBar |SearchLayouts a| +This method is called after the search button bar of a SearchLayout has +been constructed. It can be used to add additional buttons or other +components. + +The search button bar is the button bar that appears below a results +grid + +|setFilterCreator |ServiceBasedSplitLayout, EditableGridLayout |Use this +method to construct the filter that is applied to limit the results that +show up in the results grid + +|setMaxResults |SearchLayouts, SplitLayouts |Sets the maximum allowed +number of search results + +|setParentGroupHeaders |All composite components |This method can be +used to specify an additional layer of grouping that can be added inside +an edit form. This method takes an array of Strings that will be used as +the headers for the extra tabs or panels. + +|setQuickSearchFilterCreator |ServiceBasedSplitLayout |Use this method +to construct the filter that will be applied to the search results grid +when the user enters a search term in the quick search field. Use +“showQuickSearchField” on the FormOptions to make this field show up. + +|setSearchColumnThresholds |SimpleSearchLayout |Sets the threshold +values at which extra columns will appear inside a search form +|=== + +=== Listeners + +These methods are typically called after an event occurs (e.g. a user +clicks a button or selects a row in a grid).s + +[width="100%",cols="32%,32%,1%,35%",options="header",] +|=== +|*Method* |*Available on* |*Functionality* | +|setAfterAdvancedModeToggled |SearchLayouts | |Executed after the search +form switches between normal and advanced mode + +|setAfterClear |SearchLayouts, | |Executed after the user clicks the +“Clear” button in a search form + +|setAfterEntitySelected |All composite components except +SimpleEditLayout | |Executed after an entity is selected in a results +grid and displayed in an edit form + +|setAfterEntitySet |All composite components except SimpleEditLayout | +|Executed after an entity has been selected to be displayed in an edit +form but before the form has actually been constructed. Typically used +to load additional data + +|setAfterModeChanged |All composite components | |Executed after the +mode of a component changes from view mode to edit mode or vice versa + +|setAfterSearchFormToggled |SearchLayouts | |Executed after the search +form inside a search layout is shown or hidden + +|setAfterSearchPerformed |SearchLayouts | |Executed after a successful +search is carried in a search layout. + +|setAfterTabSelected |All composite components | |Executed after a tab +is selected inside an edit form when attribute groups are used and the +screen mode is set to TABSHEET. + +|setAfterUploadCompleted |All composite components | |Code that is +executed after file has been successfully uploaded using an edit form + +|setCreateEntity |All composite components | |Executed after the user +clicks the “Add” button to construct a new entity. + +|setCustomSaveAction |All composite components | |Specifies the code to +be executed when the save button is clicked. This overrides the default +save behaviour so you will have to manually call the service to actually +save any changes. + +|validateBeforeSearch |SearchLayouts | |Use this method to perform extra +validation before a search is carried out. +|=== + +=== Operational methods + +These are methods that are typically called directly from your code +after the component has been created. + +[width="100%",cols="32%,,25%,43%",options="header",] +|=== +|*Method* | |*Available on* |*Functionality* +|customDetailsView | |SearchLayouts, SplitLayouts |Call this method if +you want to display a custom component or set or components instead of +the normal edit form. This method takes a single parameter of type +*Layout* that represents the layout that must be displayed. + +|detailsMode | |SearchLayouts, SplitLayouts |Opens the component in +details mode with the provided entity selected + +|emptyDetailView | |SplitLayouts |Clears the details view, replacing it +by a label that says “Select an item to view or edit its details” + +|isRegisteredComponent | |All composite components |Checks whether a +component was registered under a certain identifier. Used for advanced +scenarios in which to enable/disable certain buttons. + +|refresh | |All composite components |Use the *refresh* method to +refresh all selection components inside the search or edit forms inside +a composite component. + +|reload |All composite components | |Use the *reload* method to reload a +composite search component. This will clear any search filters and +perform a new search. It will also de-select any currently selected +entity. + +|search |SearchLayouts | |Carries out a search based on the currently +selected search terms (and any default filters) +|=== + +=== Further clarification + +This section shows some further clarifications and examples of how to +use some of the methods described in the sections above. + +==== setParentGroupHeaders and setFindParentGroup + +These methods can be used to add an additional layer of nesting to the +grouping of your attributes inside an edit form. As you saw before, you +can use the *@AttributeOrder* annotation to specify how the attributes +of an entity are grouped together in an edit form. Sometimes this is not +enough and in these cases, you can do the following: + +* {blank} ++ +____ +The *setParentGroupHeaders* method can be used to return an array of +Strings. These represent the captions of the extra groups you want to +create. +____ +* {blank} ++ +____ +The *setFindParentGroup* method can be used to link an existing +attribute group (as defined on your entity) to the newly created top +level group. +____ + +setParentGroupHeaders( + +*new* String[] \{ "blip.attributes.main", *_MSG_ATTRIBUTES_PROMOTED_*, +"blip.attributes.other" }); + +setFindParentGroup(childGroup -> \{ + +*switch* (childGroup) \{ + +*case* Constants.*_AUDIT_DATA_*: + +*return* "blip.attributes.other"; + +*case* Constants.*_PROMOTED_MAIN_*: + +*case* Constants.*_PROJECTED_FINAL_*: + +*case* Constants.*_OTHER_ATTRIBUTES_PROMOTED_*: + +// display on the promoted tab + +*return* *_MSG_ATTRIBUTES_PROMOTED_*; + +*default*: + +*return* "blip.attributes.main"; + +} + +}); + +The value of the *attributeGroupMode* from the *FormOptions* determines +the top-level attribute grouping – so if this is e.g. set to *TABSHEET* +then we get a tab component with two tabs, and on the lower level the +attributes are grouped together using panels. If you set the +*attributeGroupMode* to *PANEL* then you will get two panels on the top +level, and a tab component within each panel. + +==== Post-Processing button bars and registering component + +The composite components offer a number of methods for modifying the +various button bars that they contain. + +The *setPostProcessMainButtonBar* method can be used to make +modifications to the button bar that appears below the results grid in a +SearchLayout, a SplitLayout or a EditableGridLayout. + +The *setPostProcessDetailButtonBar* method can be used to make +modifications to the button bar that appears above and below the edit +form in a SearchLayout, a SplitLayout or an EditableGridLayout. For a +SimpleEditLayout you can achieve the same by using the +*setPostProcessButtonBar* method. + +Finally, the *setPostProcessSearchButton* method can be used to make +modifications to the button bar that appears below the search form in a +SearchLayout (and that normally contains the Search and Clear buttons). + +When a button or component must become enabled/disabled based on whether +a row is selected in the grid, then you can call the *registerComponent* +method (passing the component as the argument) inside one of the post +process method. If you need to further modify the conditions under which +the button is enabled, use the *setMustEnableComponent* method. + +As an example, consider the following: + +layout.setPostProcessMainButtonBar(buttonBar -> \{ + +Button notificationButton = *new* Button("Show name"); + +notificationButton.addClickListener(event -> \{ + +showErrorNotification(layout.getSelectedItem().getNickName()); + +}); + +buttonBar.add(notificationButton); + +layout.registerComponent("notificationButton", notificationButton); + +}); + +layout.setMustEnableComponent((com, ent) -> \{ + +*if* (layout.isRegisteredComponent("notificationButton", com)) \{ + +*return* ent.getFirstName().startsWith("M"); + +} + +*return* *true*; + +}); + +First, we create a button (“notificationButton”) that simply shows a +notification message when clicked. We then register this button under +the reference “notificationButton”. + +Now, every time a row is selected or deselected in the results grid of +the component, all registered buttons are evaluated. If no row is +selected, the button is disabled. If a row is selected, the +“mustEnableComponent” method is evaluated for every registered +component. In the example above, we check if that component is the +notificationButton (by calling the *isRegisteredComponent* method). + +==== setPostProcessEditFields + +If you want to add custom behaviour to the edit form, you can do so in +the *setPostProcessEditFields* method. This method can e.g. be used to +add extra behaviour to input fields (i.e. respond to the change of field +A by changing the value in field B, or by disabling field C). This +method is called once per composite layout component, after the first +time the edit form has been constructed (it is not called if the form is +in view mode, since in that case it doesn’t contain any actual input +components). + +The method invocation takes a lamdba has a single parameter of type +*ModelBasedEditForm.* This is a class from the Dynamo Framework that is +responsible for creating and managing an edit form based on the Entity +Model. If you want to retrieve the component used for editing a certain +attribute, you can use the *getField* method for this. + +The *getField* method will return null when there is no appropriate +field (e.g. because the associated attribute is read-only!) so use this +with care. + +Note: you must _not_ use this method to modify the characteristics of +the fields that you can easily manipulate using the entity model. E.g. +if you always want a field to be invisible or read-only, use the entity +model for this. Use the *setPostProcessEditFields* method only for +complex inter-field behaviour e.g. for having one field respond to a +Callback methods called in response to user action. + +== Additional Functionality + +This chapter covers some additional functionality offered by the +composite components in the Dynamo framework + +=== Excel and CSV Export + +The Dynamo Framework offers some functionality for automatically +exporting data that is displayed in results grid to Excel (.xlsx) or CSV +format. + +In order to use this functionality, you will first have to add the +appropriate dependencies to your project: + + + +org.dynamoframework + +dynamo-frontend-export + +$\{dynamo.version} + + + + + +com.monitorjbl + +xlsx-streamer + +2.1.0 + + + +Next, to enable the export for all applications in the table, you will +have to set the system property *ocs.allow.list.export* to true. This +will enable the export functionality for all grids in your application. +If you would rather enable the export on a screen-by-screen basis you +can use the *setExportAllowed* setting on the FormOptions to true. + +The result of this will be that when the user right-clicks on a search +results grid in the application, the following dialog will appear: + +image:vertopal_1eda064bf39f485baa9f64be38994881/media/image15.png[image,width=186,height=189] + +Clicking on the buttons will respectively export the data in the grid to +Excel or CSV. Note that the export that will be created contains all the +data in the result set (i.e. all data that matches the search criteria), +not just the rows that are currently displayed – Dynamo will iterate +over the entire data set using pagination. + +By default, only the columns that are visible in the grid will be +exported. You can switch to a full export of all the (visible) +attributes by using the FormOptions object: + +FormOptions fo = *new* FormOptions().setExportMode(ExportMode.*_FULL_*); + +Note that if you set the export mode to full, this might mean that the +application will try to export related entities that have not been +fetched, resulting in lazy loading exceptions. + +To remedy this, you can do the following: + +* You can use the *setExportJoins* method to specify which relations to +fetch when performing the export. +* You can use the *setExportEntityModel* method to specify a custom +entity model to use for the export. This overrides the default entity +model that is used to construct the search results grid. You can e.g. +use this to set the visibility of certain attributes to *false* so they +are excluded from the export. + +To further customize the export to Excel, it is possible to specify a +custom style generator. + +You can register such a custom style generator by using the static +*ExportDelegateHelper.addCustomStyleGenerator*. This method adds a +mapping between an entity model and a style generator. + +The style generator is a fairly straightforward functional interface +that defines a method with the following signature: + +CellStyle getCustomCellStyle(Workbook workbook, T entity, Object value, +AttributeModel am, Object pivotColumnKey); + +This method allows you to define custom style (that e.g. allows you to +change the color, font, border etc.) for a cell based on the entity that +is being displayed, the cell value, and the attribute model (the +pivotColumnKey is only used in very specific circumstances and can +usually be ignored). + +=== Lookup tables and parameters + +Dynamo contains an optional module for working with domains/lookup +tables. These are simple database tables that typically contain only a +couple of attributes and only a handful of records that are often more +or less stable over time. Some examples include a list of countries, +languages, order statuses etc. + +If you want to use Dynamo’s functionality for easily managing lookup +tables, you will have to include the following dependency in your +pom.xml file: + + + +org.dynamoframework + +dynamo-functional-domain + +$\{com.ocs.dynamo.version} + + + +This will give you access to a number of bases entities, DAO’s, and +services for managing lookup tables/domains. The most important one is +arguably the *Domain* entity which serves as the base for all your +lookup tables. + +@Inheritance + +@DiscriminatorColumn(name = "type") + +@Entity(name = "domain") + +@Model(displayProperty = "name", sortOrder = "name asc") + +*public* *abstract* *class* Domain *extends* AbstractEntity \{ + +The idea is that you can define a lookup table class by subclassing this +class as follows: + +@Entity + +@DiscriminatorValue("COUNTRY") + +@Model(displayNamePlural = "Countries", displayProperty = "name", +sortOrder = "name asc") + +*public* *class* Country *extends* DomainChild \{ + +As you can see, this example uses JPA inheritance to define a new entity +that will be stored in the “domain” table using JPA’s “single table” +inheritance model. + +You can then define simple DAO and service objects for your domain class +as follows (you would normally place these in a class annotated with +@Configuration). + +@Bean + +*public* BaseDao countryDao() \{ + +*return* *new* DefaultDaoImpl<>(QCountry.*_country_*, Country.*class*, +"parent"); + +} + +@Bean + +*public* BaseService countryService(BaseDao dao) \{ + +DefaultServiceImpl countryService = *new* +DefaultServiceImpl<>(dao, "code"); + +*return* countryService; + +} + +Note that the *DefaultDaoImpl* and *DefaultServiceImpl* classes are part +of the core dynamo implementation so you can use them without adding the +*dynamo-functional-domain* dependencies to your pom.xml. + +In addition to the *Domain* class, the *dynamo-functional-domain* module +contains several other classes like *DomainParent* and *DomainChild* +that can be used to manage lookup tables with simple hierarchies. + +Dynamo also comes with simply functionality for managing a list of +parametes. To use this functionality, add the following dependency: + + + +org.dynamoframework + +dynamo-parameter + +$\{com.ocs.dynamo.version} + + + +This makes the *Parameter* class as well as the associated +*ParameterDao* and *ParameterService* available to your application. + +The Parameter and Domain classes are database agnostic – this is great +because you can use them with multiple databases, but it does mean that +some additional configuration is required to make the module work with +your database. + +This configuration can be included in the *orm.xml* file that can be +included in the *src/main/resources/META-INF* directory. The following +shows the configuration for PostgreSQL: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Dynamo also contains some functionality for managing your lookup tables +in the user interface. To use this functionality you must include the +*dynamo-functional-domain-frontend* dependency: + + + +org.dynamoframework + +dynamo-functional-domain-frontend + +$\{com.ocs.dynamo.version} + + + +This dependency includes a single component named +*MultiDomainEditLayout* which offers a simple screen for managing your +lookup tables. + +The initialization of this component looks as follows: + +List> domains = *new* ArrayList<>(); + +domains.add(Country.*class*); + +domains.add(Region.*class*); + +MultiDomainEditLayout dom = *new* MultiDomainEditLayout(*new* +FormOptions(), domains); + +You can simply pass a list of the domain classes you want to manage to +the *MultiDomainEditLayout’s* constructor. This will then result in a +screen that looks as follows: + +image:vertopal_1eda064bf39f485baa9f64be38994881/media/image16.png[image,width=559,height=226] + +This component consists of a combo box that allows a user to select the +lookup table they want to edit. Selecting a value in the combo box will +cause a split layout for the selected lookup table to be rendered. The +user can then use this split layout to add, modify or delete the values +of the lookup table. + +=== Field Factory + +The component in Dynamo that is responsible for actually creating the +input components that show up inside edit forms is called the +*FieldFactory.* Normally, the field factory is used under internally by +the framework and you will rarely have to use it directly, but it can be +useful in situations in which you are creating custom screens that still +use input components backed by an entity model – in this case you can +use the field factory to create the components one-by-one. + +In order to acquire a reference to a FieldFactory, you can use the +static FieldFactory._getInstance_() method. + +You can then use the *constructField(AttributeModel am)* or +*constructField(FieldCreationContext ctx)* methods to create the field. + +The simplest version (with the attribute model) parameter simply creates +a field that is based on the provided attribute model, using all default +settings. + +The version that takes a *FieldCreationContext* allows you to further +customize the creation process. The *FieldCreationContext* allows you to +specify: + +* The attribute model +* The field entity model to use (if not specified, the default entity +model for the field’s type will be used) +* A map of field filters – if this map contains an entry that matches +the path of the attribute model, the field filter will be applied when +creating the field. +* Whether the field should be constructed for search mode (this is false +by default). +* Whether the field will be placed inside an editable grid. +* Whether the field should be constructed for view mode (this is false +by default) – in most cases the field factory will not construct a field +and return NULL when the *view* parameter is set to true. +* The shared data providers – this is used internally by the framework +in order to share the data providers that are used by similar components +inside an editable grid. + +In addition to the *constructField*-methods the Field Factory also +offers the *addConvertersAndValidators* method. This method has the +following signature: + + *void* addConvertersAndValidators(BindingBuilder builder, +AttributeModel am, + +Converter customConverter); + +This method is used internally by the framework in order to add +converters and validators to a field, but in case you are creating your +own form it can be useful as well. The method takes the following +parameters: + +* A *BindingBuilder* which is a class offered by the Vaadin framework +that can be used to bind a field to the underlying data model. +* The attribute model used for field construction. +* A custom converter that will be used instead of the default converter +used by the framework. + +All Dynamo applications require a FieldFactory but since it is by +autoconfigured by Spring Boot you do not manually have to create one. + +It is also possible to modify the component creation process directly. +To know how to do this, it is good to know that the FieldFactory +delegates the actual creation of the components to a number of +*ComponentCreators.* A ComponentCreator is an interface that has two +subclasses: *SimpleComponentCreator* and *EntityComponentCreator.* Each +of these has a number of subclasses that are each responsible for +creating a certain type of input component, e.g. a TextField or an +EntityComboBox (at this moment there are about twenty in total). Each +ComponentCreator subclass implements a *supports* method that is used by +the FieldFactory to check if it supports constructing a component for +the current attribute model and creation context – the first component +creator that is found is used by the FieldFactory to actually construct +the component by calling the *createComponent* method. Later in the +process, the FieldFactory also calls the *addConverters* and +*addValidators* method to add converters and validators to the component +(well, technically to the binding between the component and the backing +bean, but you get the point). + +When you fire up a Dynamo application, Spring Boot auto-configures all +of the relevant ComponentCreators and the FieldFactory, so there is +nothing you have to do for this to work out of the box. However, note +that you have two (obvious) options to interfere in the component +creation process: + +* First of all, you can make a class that is a subclass of one of the +ComponentCreators that come with Dynamo and annotate it as a Spring +component (e.g. by using @Service or @Component). Spring Boot will then +use your subclass instead of the default implementation. You can use +this subclass to make modifications to the component creation process, +e.g. by changing default settings, adding functionality etc. But if +needed you can go much further and use your subclass to construct a +completely different component. Note however that this will only work +properly as long as your component can deal with the same data types as +the original component – i.e. overwriting TextFieldCreator and having +your subclass return a date picker is not going to work very well. +* The ComponentCreators are also ordered using the Spring @Order +annotation, so what you can also do is define your own class (that +inherits from SimpleComponentCreator or EntityComponentCreator), then +register this component as a Spring bean and annotate it with an @Order +with a lower value than the default converters (the default converters +start with an order value of 1000 so anything below that will work). The +FieldFactory will start evaluating the ComponentCreators starting with +the one with the lowest order, so your custom creator will be found +before the default ComponentCreators. You can use this technique to +create components for fairly specific scenarios, while still using the +default components the rest of the time. + +== Pivot Layout + +== Styling + +It is possible to update the styling of Dynamo applications. To do so, +two separate approaches are possible (or necessary, depending on what +exactly you are trying to do). + +To make changes to the general styling of the application, create a file +named “shared-styles.css” and place this in the “frontend/styles” +subdirectory of your project root directory. + +To make sure the application can actually find your style sheet you must +pace an @CssLayout on the class in your application that imlements +RouterLayout: + +@CssImport("./styles/shared-styles.css") + +*public* *class* GtsUI *extends* VerticalLayout *implements* +RouterLayout \{ + +Inside this css file you can place your custom styles or make changes to +the default styles. + +One thing to note is that you can refer to or modify the variables +included with the Vaadin Lumo theme. A list of the available variables +can be found at +https://cdn.vaadin.com/vaadin-lumo-styles/1.5.0/demo/index.html. + +In order to modify a variable, you can include an html element in your +style sheet and then assign a new value as follows: + +*html* \{ + +--lumo-primary-color: _#f99d34_; + +} + +You can then refer to the variable in the remainder of the style sheet +as follows: + +border: _1px_ _solid_ _var(_-_-lumo-primary-color)_ _!important_; + +As you can see, you use the name of the variable enclosed in a *var()* +construct. + +Unfortunately, this does not work for all styles in the application. +That is because Vaadin now uses Web Components and the styling for these +is often included in the *Shadow DOM.* The Shadow DOM is a technique +that can be used to define styling in a way that is local to each +component. This can be very useful but it does mean that modifying the +styles from a global style sheet is tricky. + +To modify styles that are in the shadow DOM, create a file named +“.css” in the *frontend/styles* directory, where +“component-name” is the name of the component you want to modify, e.g. +“vaadin-custom-field.css”. + +Inside this file, you can place any valid CSS that you want. + +Next, add another CssImport annotation to your RouterLayout class and +make sure the value matches the name of the file you created. + +@CssImport(value = "./styles/vaadin-custom-field.css", themeFor = +"vaadin-custom-field") + +*public* *class* GtsUI *extends* VerticalLayout *implements* +RouterLayout \{ + +== Hibernate Envers Integration + +Dynamo offers some functionality for visualising the history that can be +generated using Hibernate Envers. For those unfamiliar with Hibernate +Envers, it is a library that allows the developer to define a number of +entities to be audited. Whenever any change is made to these audited +entities, Hibernate Envers automatically stores a snapshot of the new +state of the entity along with a time stamp. + +In order to make this possible, Hibernate needs a number of additional +database tables – one table to keep track of the time stamps and +revision keys, and one table for every “regular” table you want to +audit. By default, these shadow tables are named after the table they +are shadowing, postfixed with “_aud”. E.g. the shadow table for “person” +would be named “person_aud”. The table for keeping track of timestamps +is normally named “revinfo”. However, Dynamo makes some small additions +to the framework and stores the revision info in a table named +“RevisionEntity” instead. The definition for this table (MySQL) is as +follows: + +*CREATE* *TABLE* `RevisionEntity` ( + +`id` *int*(11) *NOT* *NULL* AUTO_INCREMENT, + +`**timestamp**` bigint(20) *NOT* *NULL*, + +`username` *varchar*(255) *DEFAULT* *NULL*, + +*PRIMARY* *KEY* (`id`) + +); + +And for Postgresql: + +CREATE TABLE IF NOT EXISTS public.revisionentity( + +id integer NOT NULL DEFAULT nextval('revision_entity_id_seq'::regclass), + +"timestamp" bigint NOT NULL, + +username character varying(255) , + +CONSTRAINT revisionentity_pkey PRIMARY KEY (id) + +) + +You must provide implementations for the shadow tables yourself. For +more information on this, consult the Hibernate Envers documentation. +However, note that compared to the original table: + +* There must be a REV column that contains the revision number +* There must be a REVTYPE column that contains the type of the revision +(insert, update, delete) +* The primary key must consist of the primary key of the original table +combined with the REV column. +* Columns (other than REV, REVTYPE and primary key fields) cannot be +required. This is because if the record in the shadowed table is +deleted, a mostly empty record is stored in the shadow table. + +Instead of creating these tables yourself, you can also set the +spring.jpa.hibernate.ddl-auto property to “auto”. This will cause +Hibernate to generate the missing shadow table the next time the +application starts. + +In order to use the Dynamo Envers functionality in your code, add the +following to your POM file: + + + +org.dynamoframework + +dynamo-envers + +$\{com.ocs.dynamo.version} + + + +Also, add the following to the UI project: + + + +org.dynamoframework + +dynamo-envers-frontend + +$\{com.ocs.dynamo.version} + + + +You will probably also explicitly have to add the hibernate-envers +dependency to the POM of your UI project. + +Then, add the following to your application.properties: + +[.underline]#spring.jpa.properties.org.hibernate.envers.revision_listener#=com.ocs.dynamo.envers.listener.DynamoRevisionListener + +This will make sure the framework uses the custom Dynamo revision +listener rather than the default Hibernate listener. + +Next, update the *@EntityScan* so that the +“com.ocs.dynamo.envers.domain” package is included. This will make sure +the “RevisionEntity” entity is recognized. + +@ComponentScan(basePackages = \{ "com.ocs.gts", "com.ocs.dynamo", " +com.ocs.dynamo.envers.domain" }) + +@EntityScan(basePackages = \{ "com.ocs.gts.domain", +"com.ocs.dynamo.functional.domain", "com.ocs.dynamo.envers.domain" }) + +*public* *class* ApplicationConfig *extends* +ApplicationConfigurationSupport \{ + +After that, make sure to annotate your entity classes with *@Audited* +and you should be good to go – whenever one of the audited entities +changes, a snapshot of the new state is stored in the shadow tables. + +Hibernate Envers supports two ways of querying the history: by time +stamp and by entity. The first method allows you to look up the state at +a certain moment in the past. The second allows you to retrieve the +revision history of an entity. + +Dynamo offers the *VersionedEntityDao* to expose some of this +functionality. In order to use this class, take the following steps: + +Create a *VersionedXXX* class, where XXX is the name of the original +class: + +@Model(displayName = "Organization revision", displayNamePlural = +"Organization revisions", displayProperty = "entity.name") + +*public* *class* VersionedOrganization *extends* +VersionedEntity \{ + +*private* *static* *final* *long* *_serialVersionUID_* = +4271855110308393329L; + +*public* VersionedOrganization(Organization entity, *int* revision) \{ + +*super*(entity, revision); + +} + +@Attribute(embedded = *true*) + +*public* Organization getEntity() \{ + +*return* *super*.getEntity(); + +} + +@Attribute(visible = VisibilityType.*_HIDE_*) + +*public* String getDescription() \{ + +*return* RevisionType.*_DEL_*.equals(getRevisionType()) ? "Deleted" : +getEntity().getName(); + +} + +} + +* Have the class extend *VersionedEntity* from the Dynamo framework. +* Override the *getEntity* method an annotate it with +*@Attribute(embedded = true)* +* Overwrite the getDescription() method to have it return a custom value +in case the revision type is “DEL” (deleted). +* If you want to define attribute groups and/or an attribute order, note +that you can refer to the properties from the original entity by +prefixing them with “entity”. You can also refer to the additional +attributes related to the history: *revision* (the unique revision +number), *revisionTimeStamp* (the time stamp), *revisionType* (addition, +deletion or modification) and *user* (name of the user that performed +the notification). + +Next, create a DAO like this: + +*public* *interface* VersionedOrganizationDao *extends* +VersionedEntityDao \{ + +} + +And the implementation associated with it: + +@Repository + +*public* *class* VersionedOrganizationDaoImpl *extends* +VersionedEntityDaoImpl + +*implements* VersionedOrganizationDao \{ + +@Override + +*public* Class getEntityClass() \{ + +*return* VersionedOrganization.*class*; + +} + +@Override + +*protected* VersionedOrganization createVersionedEntity(Organization t, +*int* revision) \{ + +*return* *new* VersionedOrganization(t, revision); + +} + +@Override + +*public* Class getBaseEntityClass() \{ + +*return* Organization.*class*; + +} + +@Override + +*protected* *void* doMap(VersionedOrganization u) \{ + +*super*.doMap(u); + +*if* (u.getEntity().getCountryOfOrigin() != *null*) \{ + +u.getEntity().getCountryOfOrigin().getName(); + +} + +*if* (u.getEntity().getMembers() != *null*) \{ + +u.getEntity().getMembers().forEach(c -> c.getFirstName()); + +} + +*if* (u.getEntity().getMainActivity() != *null*) \{ + +u.getEntity().getMainActivity().getName(); + +} + +} + +} + +Most of this implementation is very straightforward. Only the *doMap* +method requires some additional clarification. In this method, you +should retrieve all relations that must be loaded when loading a +revision. + +Finally, you must create a *Service* and *ServiceImpl* for you entity: + +*public* *interface* VersionedOrganizationService *extends* +BaseService, VersionedOrganization> \{ + +} + +@Service + +*public* *class* VersionedOrganizationServiceImpl *extends* +BaseServiceImpl, VersionedOrganization> + +*implements* VersionedOrganizationService \{ + +@Autowired + +*private* VersionedOrganizationDao dao; + +@Override + +*protected* BaseDao, VersionedOrganization> +getDao() \{ + +*return* dao; + +} + +} + +After this, you can use the entity like you would use any other entity +with regard to the creation of custom components. E.g. to create a +simple search layout for browsing the revision table, do the following: + +SimpleSearchLayout, VersionedOrganization> +versLayout = *new* SimpleSearchLayout<>( + +versionService, +getModelFactory().getModel(VersionedOrganization.*class*), +QueryType.*_ID_BASED_*, + +*new* FormOptions().setReadOnly(*true*), *null*); + +main.add(versLayout); + +Dynamo offers one more convenient feature for showing the revision +history of a single entity. This is the *ViewRevisionDialog*: + +ViewRevisionDialog dialog += [.underline]#*new* ViewRevisionDialog(# + +[.underline]#versionService, +getModelFactory().getModel(VersionedOrganization.*class*),# + +[.underline]#*new* +FormOptions().setAttributeGroupMode(AttributeGroupMode.*_TABSHEET_*),# + +[.underline]#layout.getSelectedItem().getId())#; + +dialog.buildAndOpen(); + +The code above will create and open a popup dialog that displays the +revisions for just the selected entity. The FormOptions parameter can be +used to specify the screen mode (horizontal/vertical) and group mode +(tabsheet/panel). + +There is one known limitation with regard to searching on the revision +history – it is not possible to search for attributes of type DETAIL +(i.e. one-to-many or many-to-many relations) or MASTER. + +== Composite components class diagram + +image:vertopal_1eda064bf39f485baa9f64be38994881/media/image17.png[image,width=560,height=629] + +The diagram above displays the most important classes that play a role +in the generation of composite user interface components. + +At the heart of the diagram is the *BaseCustomComponent* class which +inherits from the Vaadin *CustomComponent* class and adds standard +methods for e.g. accessing the message service. There are several +subclasses of this *BaseCustomComponent:* + +* *BaseGridWrapper* which is the base class for the components that +display a search results grid. The grid itself is an instance of +*ModelBasedGrid.* +* *AbstractModelBasedForm* which is the base class for components that +display input components (can either be a search or an edit form). This +class delegates to the *FieldFactory* for constructing the actual +components. +* *SimpleEditLayout* and *BaseCollectionLayout* are the base classes for +components that respectively allow the user to manage a single entity or +a collection of entities. +* *ProgressForm* (not shown in diagram) is a form that allows the user +to start a batch process (like a file upload) and contains a progress +bar that can be used to keep track of this process. +* {blank} +* *TabLayout* (not shown in diagram) is a tab sheet components that has +the functionality for creating certain tabs on the fly. + +As you can see, there are two subclasses of *AbstractModelBasedForm:* a +*ModelBasedEditForm* and an *AbstractModelBasedSearchForm:* the former +is the class that is used for rendering an edit form based on the entity +model. It is one of the most complex classes in the framework and is +used by any composite component that contains an edit form. The latter +is the base class for all search forms within the framework. It has two +subclasses: *ModelBasedSearchForm* and *ModelBasedFlexibleSearchForm*. + +As you can see, each of the composite components (the ones whose name +ends in “layout”) are comprised of several other components: + +* The *SimpleEditLayout* is straightforward and is mostly a wrapper +built around the *ModelBasedEditForm.* +* The *BaseCollectionLayout* is used to manipulate a collection of +entities. It comes in three main varieties: +** *EditableGridLayout* – this is the simplest variant, it contains only +a table wrapper that displays a search results table. +** *Split layouts* – consisting of a table with an edit form next to it +or below it. There are several variants of Split Layout but each one +contains both a table wrapper (which hold the search results table) and +an edit form. +** *Search layouts* - these are the most complex composite components. A +search layout contains a search form, a search results grid, and an edit +form. There are two variants: a *SimpleSearchLayout* that contains a +“traditional” search form (*ModelBasedSearchForm*) and a +*FlexibleSearchLayout* that contains a *ModelBasedFlexibleSearchForm* +that allows the user to click together complex search filters. + +Note that the *SimpleSearchLayout* is also re-used inside the +*ModelBasedSearchDialog* that is used to perform searches for attributes +that have their select mode set to *LOOKUP*. + +Next, there is a *BaseDetailsEditGrid* that serves as the base class for +grid components that are used inside edit forms (e.g. the +*ModelBasedEditForm).* The *BaseDetailsEditGrid* has two subclasses: + +* *DetailsEditGrid –* for managing an in-memory collection of dependent +entities. +* *ServiceBasedDetailsEditGrid* – for managing a lazily loaded (and +potentially very large) collection of dependent entities. + +Finally, note that this diagram omits some of the less known components +and some subclasses (e.g. the subclasses of *BaseSplitLayout)* in order +to be able to focus on the main architecture. diff --git a/dynamo-documentation/media/image10.png b/dynamo-documentation/media/image10.png new file mode 100644 index 000000000..6fd0c76d3 Binary files /dev/null and b/dynamo-documentation/media/image10.png differ diff --git a/dynamo-documentation/media/image11.png b/dynamo-documentation/media/image11.png new file mode 100644 index 000000000..ec58a5b51 Binary files /dev/null and b/dynamo-documentation/media/image11.png differ diff --git a/dynamo-documentation/media/image12.png b/dynamo-documentation/media/image12.png new file mode 100644 index 000000000..5f67a661c Binary files /dev/null and b/dynamo-documentation/media/image12.png differ diff --git a/dynamo-documentation/media/image13.png b/dynamo-documentation/media/image13.png new file mode 100644 index 000000000..171d2962f Binary files /dev/null and b/dynamo-documentation/media/image13.png differ diff --git a/dynamo-documentation/media/image14.png b/dynamo-documentation/media/image14.png new file mode 100644 index 000000000..3c40eb2aa Binary files /dev/null and b/dynamo-documentation/media/image14.png differ diff --git a/dynamo-documentation/media/image15.png b/dynamo-documentation/media/image15.png new file mode 100644 index 000000000..3ea18d634 Binary files /dev/null and b/dynamo-documentation/media/image15.png differ diff --git a/dynamo-documentation/media/image16.png b/dynamo-documentation/media/image16.png new file mode 100644 index 000000000..9f580a23c Binary files /dev/null and b/dynamo-documentation/media/image16.png differ diff --git a/dynamo-documentation/media/image17.png b/dynamo-documentation/media/image17.png new file mode 100644 index 000000000..ee3d9a298 Binary files /dev/null and b/dynamo-documentation/media/image17.png differ diff --git a/dynamo-documentation/media/image3.png b/dynamo-documentation/media/image3.png new file mode 100644 index 000000000..3cd59af9e Binary files /dev/null and b/dynamo-documentation/media/image3.png differ diff --git a/dynamo-documentation/media/image4.png b/dynamo-documentation/media/image4.png new file mode 100644 index 000000000..6d435ade7 Binary files /dev/null and b/dynamo-documentation/media/image4.png differ diff --git a/dynamo-documentation/media/image5.png b/dynamo-documentation/media/image5.png new file mode 100644 index 000000000..39bc4cc7b Binary files /dev/null and b/dynamo-documentation/media/image5.png differ diff --git a/dynamo-documentation/media/image6.png b/dynamo-documentation/media/image6.png new file mode 100644 index 000000000..caa19c758 Binary files /dev/null and b/dynamo-documentation/media/image6.png differ diff --git a/dynamo-documentation/media/image7.png b/dynamo-documentation/media/image7.png new file mode 100644 index 000000000..4b90ce073 Binary files /dev/null and b/dynamo-documentation/media/image7.png differ diff --git a/dynamo-documentation/media/image8.png b/dynamo-documentation/media/image8.png new file mode 100644 index 000000000..05f66d243 Binary files /dev/null and b/dynamo-documentation/media/image8.png differ diff --git a/dynamo-documentation/media/image9.png b/dynamo-documentation/media/image9.png new file mode 100644 index 000000000..cdeca6662 Binary files /dev/null and b/dynamo-documentation/media/image9.png differ diff --git a/dynamo-documentation/media/logo-dynamo.png b/dynamo-documentation/media/logo-dynamo.png new file mode 100644 index 000000000..e1634b04c Binary files /dev/null and b/dynamo-documentation/media/logo-dynamo.png differ diff --git a/dynamo-documentation/media/logo-opencirclesolutions.svg b/dynamo-documentation/media/logo-opencirclesolutions.svg new file mode 100755 index 000000000..cd09832aa --- /dev/null +++ b/dynamo-documentation/media/logo-opencirclesolutions.svg @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +