Preface
The Galite Project is a framework that lets you build business applications with few code. It’s the successor of the Kopi project which is a framework from the company DMS that allows the development of database applications using Java, JDBC, SWING and Vaadin 7.
Galite allows creating database applications in a high level specification language using Kotlin DSL and Exposed.

The basic architecture of Visual Galite package is made of a three strongly independent parts:
-
DSL: The DSL is the Kotlin functions provided for the user to develop an application.
-
Model: The model layer is a set of model classes responsible for structuring and processing data. Also, it contains a high specification interfaces that any UI implementation should respect.
-
UI: The UI is the presentation layer that represents the user interface. This can be extended to implement many technologies (web, mobile, desktop, etc.). For the moment Galite can generate a web application implemented in Vaadin flow (21+).
Organization of This Document
This documentation includes two main Chapters:
-
CHAPTER 1 : Getting Started
This chapter is intended to show how to configure the application and essential features to query the database.
-
CHAPTER 2 : The Visual Galite application Framework
In this chapter, we will explore the capabilities of this framework.
Getting Started
Entry point
Galite provides an entry point to the application that should be implemented to customize the application.
If you are creating a Vaadin application, you should implement the class VApplication
and specify your application configurations by overriding the class properties:
@Route("")
class MyApp : VApplication(Registry(domain = "GALITE", parent = null)) {
override val sologanImage get() = "galite-slogan.png"
override val logoImage get() = "galite-logo.png"
override val logoHref get() = "http://[mywebsite]"
override val alternateLocale get() = Locale("de", "AT")
override val supportedLocales get() = arrayOf(Locale.FRANCE, Locale("de", "AT"), Locale("ar", "TN"))
companion object {
init {
ApplicationConfiguration.setConfiguration(ConfigurationManager)
}
}
object ConfigurationManager : ApplicationConfiguration() {
override val version get(): String = "1.0"
override val applicationName get(): String = "MyApp"
override val informationText get(): String = "info"
override val logFile get(): String = "log.txt"
override val debugMailRecipient get(): String = "mail@adress"
override fun getSMTPServer(): String = "smtp.server"
override val faxServer get(): String = "fax.server"
override fun mailErrors(): Boolean = false
override fun logErrors(): Boolean = true
override fun debugMessageInTransaction(): Boolean = true
// And many other configurations. See ApplicationConfiguration.kt
}
}
The Route
annotation allows you to define the navigation URL of your web application.
Galite standard tables
There is a list of standard database tables used by Galite inorder to manage application modules and users. you may create these tables in your first database migration.
The definition of these tables is in the file DBSchema.kt. You can use Exposed to create these tables using SchemaUtils.create(table)
.
This is the role of every table:
-
MODULE: This table contains the modules of our application.
Column | Type | Description |
---|---|---|
ID |
integer |
Table identifier |
UC |
integer |
Number of changes applied on the current row. |
TS |
integer |
Timestamp of last changes on the current row. |
KURZNAME |
varchar(25) |
The short name of this module. |
VATER |
integer |
The ID of the parent of this module, if our module is a form we put here the menu id of this form. |
QUELLE |
varchar(255) |
The source name of the module. |
PRIORITAET |
integer |
The priority of the module compared to another module. Example module with priority 10 is placed before the module of priority 20 in the same menu. |
OBJEKT |
varchar(255) |
The object name or the class name that contains the code of this module. |
SYMBOL |
integer |
The icon of this module. |
-
BENUTZERRECHTE: The table
User Rights
displays the modules that each user is allowed to use.
Column | Type | Description |
---|---|---|
ID |
integer |
Table identifier. |
TS |
integer |
Timestamp of last changes on the current row. |
BENUTZER |
integer |
The user id |
MODUL |
integer |
The module id. |
ZUGRIFF |
bool |
The access value. |
-
GRUPPENRECHTE: This table displays the modules that each group of users is allowed to use.
Column | Type | Description |
---|---|---|
ID |
integer |
Table identifier. |
TS |
integer |
Timestamp of last changes on the current row. |
GRUPPE |
integer |
The group id. |
MODUL |
integer |
The module id. |
ZUGRIFF |
bool |
The access value. |
-
GRUPPENZUGEHOERIGKEITEN: This table contains the groups that each user belongs to. Each user can belong to many groups.
Column | Type | Description |
---|---|---|
ID |
integer |
Table identifier. |
TS |
integer |
Timestamp of last changes on the current row. |
BENUTZER |
integer |
The user id. |
MODUL |
integer |
The module id. |
ZUGRIFF |
bool |
The access value. |
-
SYMBOLE: This table store the icons for each module.
Column | Type | Description |
---|---|---|
ID |
integer |
Table identifier. |
TS |
integer |
Timestamp of last changes on the current row. |
KURZNAME |
varchar(20) |
The short name of this icon. |
BEZEICHNUNG |
varchar(50) |
The description of this icon. |
OBJEKT |
varchar(50) |
This column present the icon of the module. |
-
FAVORITEN: This table contains favorite modules for each user.
Column | Type | Description |
---|---|---|
ID |
integer |
Table identifier. |
TS |
integer |
Timestamp of last changes on the current row. |
BENUTZER |
integer |
The user id. |
MODUL |
integer |
The module id. |
-
KOPI_USERS: This table contains information about the users who use this application
Column | Type | Description |
---|---|---|
ID |
integer |
Table identifier. |
UC |
integer |
Number of changes applied on the current row. |
TS |
integer |
Timestamp of last changes on the current row. |
KURZNAME |
varchar(10) |
The short name of the user. |
NAME |
varchar(50) |
The name of the user. |
ZEICHEN |
varchar(10) |
The type of the user. |
TELEFON |
varchar(20) |
The phone number of the user. |
varchar(40) |
The email of the user. |
|
AKTIV |
bool |
is this user active? |
ERSTELLTVON |
timestamp |
Creation date of this user. |
ERSTELLTAM |
integer |
Created by. |
GEAENDERTAM |
timestamp |
The date of the change for this user. |
ERSTELLTAM |
integer |
Changed by. |
-
GRUPPEN: This table contains the names of the groups
Column | Type | Description |
---|---|---|
ID |
integer |
Table identifier. |
TS |
integer |
Timestamp of last changes on the current row. |
KURZNAME |
varchar(10) |
The short name of the group. |
BEZEICHNUNG |
varchar(40) |
The short description of the group. |
-
DUMMY: This table is for database sequences
Column | Type | Description |
---|---|---|
dummy |
char(1) |
Table identifier. |
Transactions
Every SQL statements should be executed within transaction. This allows to re-execute statements interrupted by deadlock (after asking the user if he wants to) or abort a whole transaction if something is wrong (like if a Java exception is thrown). The syntax is:
transaction("an optional message") {
...
... // a list of Exposed queries
...
}
In a visual context, the optional message allows to inform the user that the transaction is being processed. For example: Showing a dialog containing a spinner with the message (For example: "Inserting the record…" or "Loading…", etc..).
The new Types
Galite introduces 2 types to the basic types available in the Kotlin language :
-
Month : Years months values.
-
Week : Years weeks values.
Galite application framework
The visual Galite Framework is an application framework using the Kotlin DSL in order to create database applications easily. In this chapter we will see how to create Galite powered forms, reports and charts.
Generalities
Predefined Field Types
Every form or report field in Galite have field type that can be one of the 11 ready to use predefined types :
- DECIMAL // used to insert decimal values
- IMAGE // used to insert images
- INT // used to insert integer values
- TEXT // used to insert text values
- STRING // used to insert string values
- DATETIME // used to insert a datetime value
- BOOL // used to insert a true or false value
- MONTH // used to insert years months value
- TIME // used to insert a hours:minutes time value
- TIMESTAMP // used to insert a timestamp value
- WEEK // used to insert years weeks value
The STRING Field Type
A STRING is used to enter characters which can be either letters, numbers or both. The width has always to be given. Moreover, you can optionally indicate how many rows it will contain and how many will finally be displayed on the form. If these optional arguments are used, you have to indicate the carriage return method by specifying either the FIXED ON or the FIXED OFF option.
There are also three other options you can use in order to modify the String’s case :
-
Convert.NAME (Converts the first letter of each word to capital letter)
-
Convert.UPPER (Convert the whole text to capital letters)
-
Convert.LOWER (Converts the whole text to normal letters)
Syntax:
STRING(
width: Int,
height: Int,
visibleHeight: Int,
[FixOption: Fixed,]
[StringFormat: Convert,]
[styled: Boolean]
)
FixOption : Fixed.ON
Fixed.OFF
StringFormat : Convert.UPPER
Convert.LOWER
Convert.NAME
styled : true // present rich text
false // present text area
Example
val name = visit(domain = STRING(40, 10, 4, Fixed.ON, Convert.UPPER), position = at(1, 1)) {
label = "Description"
help = "A description"
}
// For better readability it's recommended to specify the parameters names:
STRING(width = 40, height = 10, visibleHeight = 4, fixed = Fixed.ON, convert = Convert.UPPER)
In this example, the text inserted will contain up to 40 characters and up to 10 rows. However, only the first 4 rows will be displayed on the form. Moreover, All the letters in the text will be converted to capital letters.
The TEXT Field Type
A Text and a String are similar apart from the fact that in a text, two parameters have always to be given: namely the width and the height of the field whereas you only need to determine the width in a string.
For example, you can write STRING(40, 10, 4)
or STRING(40)
but you have to write TEXT(40, 10)
or TEXT(40, 10, 4)
.
Syntax:
TEXT(width: Integer ,height: Integer ,visible: Integer, [FixOption])
[FixOption] : FIXED.ON
FIXED.OFF
Example
val informations = visit(domain = STRING(80, 50, 10, Fixed.ON, styled = true), position = at(3, 1)) {
label = "Richtext"
help = "A Richtext editor"
}
In this example, the text inserted will contain up to 80 characters and up to 50 rows. However, only the first 10 rows.
The field is a richtext editor beacause the option styled
is set to true.
The IMAGE Field Type
This field type is used to insert an illustration or a picture. When introducing an IMAGE, you have to determine its width and height. These values have to be integers and are measured in pixel. In this case, the two attributes are compulsory. The field will look like a file chooser that lets you choose an image file to show in the field.
Syntax:
IMAGE(width: Int, height: Int)
Example:
val image = visit(domain = IMAGE(width = 20, height = 10), position = at(1, 3)) {
label = "image"
help = "The product image"
}
In this field, the image will have a width of 20 pixel and a height of 10 pixel.
The DECIMAL Field Type
A DECIMAL is used to insert numbers, integers, decimal numbers. The maximal width has to be determined for all them. The maximal scale i.e the number of characters standing after the comma has also to be defined. Also the comma has to be counted as a character. You can also set the minimum and the maximum values for the DECIMAL field with the optional parameters minValue and maxValue.
Syntax:
DECIMAL(width: Int, scale: Int)
OR
object CustomDecimal: Domain<Decimal>(width: Int, scale: Int) {
min = Decimal.valueOf("1.9")
max = Decimal.valueOf("5.9")
}
Example:
val price = visit(domain = DECIMAL(width = 10, scale = 5), position = at(1, 1)) {
label = "price"
help = "The price"
}
The INTEGER Field Type
Integer field type is INT is used to insert integers. Only the text width is to be defined. The minValue and maxValue options are also available for this type.
Syntax:
INT(width: Int)
OR
object CustomInt: Domain<Int>(width: Int) {
min = 1
max = 130
}
Example
val count = visit(domain = INT(3), position = follow(name)) {
label = "Cars"
help = "The number of cars"
}
Code Field Types
In addition to the predefined field types already available in Galite, you can define more specific types like CODE type.
The CODE types
The code Filed can be defined with many types like Boolean, Int, Decimal, etc… These types enable you to have a list of item-value pairs, the items will be displayed in the field and the values will be assigned instead.
-
The boolean code domain
In a CODE BOOL or BOOLEAN you have to assign a Boolean value to the item you have entered. Boolean values are values like "True" or "False" and "Yes" or "No".
Syntax:
object "NameToUse" : CodeDomain<Boolean>() {
init {
CodeBooleanList
}
}
CodeBooleanList : CodeBoolean [CodeBooleanList]
CodeBoolean : code : String "keyOf" value : Boolean
Example
object Situation: CodeDomain<Boolean>() {
init {
"married" keyOf true
"single" keyOf false
}
}
-
The integer code domain
In a CODE INT, you assign to each String item you have entered a INT value.
Syntax:
object "NameToUse" : CodeDomain<Int>() {
init {
CodeIntegerList
}
}
CodeIntegerList : CodeInteger [CodeIntegerList]
CodeInteger : code : String "keyOf" value : Int
Example
object Days: CodeDomain<Int>() {
init {
"Sunday" keyOf 1
"Monday" keyOf 2
"Tuesday" keyOf 3
"Wednesday" keyOf 4
"Thursday" keyOf 5
"Friday" keyOf 6
"Saturday" keyOf 7
}
}
-
The decimal code domain
In a CODE DECIMAL, each item you have entered will get a DECIMAL value, i.e integers, fixed point numbers and fraction numbers.
Syntax:
object "NameToUse" : CodeDomain<Decimal>() {
init {
CodeFixedList
}
}
CodeFixedList : CodeFixed [CodeFixedList]
CodeFixed : code : String "keyOf" value : Decimal
Example
object Accounting: CodeDomain<Decimal>() {
init {
"piece" keyOf Decimal.valueOf("1.00")
"per cent" keyOf Decimal.valueOf("0.01")
}
}
-
The string code domain
In a CODE STRING, each item you have entered will get a STRING value, this can be useful for shortcut of long strings for example.
Syntax:
object "NameToUse" : CodeDomain<String>() {
init {
CodeStringType
}
}
CodeStringType : CodeString [CodeStringType]
CodeString : code : String "keyOf" value : String
Example
object Currency: CodeDomain<String>() {
init {
"tnd" keyOf "Tunisian Dinar"
"usd" keyOf "US Dollar"
"eur" keyOf "Euro"
}
}
The SELECT command
As you had the possibility to call up a list or a table from the database, with the option LIST, you now can make Galite sort out information from a whole list or a table which the option SELECT and this, according to criteria you have to define. Let’s say you want to view the degrees which can be achieved in a certain year. For this, you have to use the SELECT command as you see in the following rows:
Syntax:
class "ListName" : ListDomain<String>(20) {
override val table = TableName
init {
ListColumns
}
}
ListColumns : ListColumn [ListColumns]
ListColumn : SimpleName "keyOf" TableName.columnName [hasWidth width: Int]
When the type of the data in our database is different of VarChar
or Binary
we need to use the keyword hasWidth
to specify the width of the data displayed :
Example
class CurrentDegree(year: Int) : ListDomain<String>(20) {
override val table = query(
Degree.selectAll {
Degree.year eq year
}
)
init {
"Symbol" keyOf Degree.Symbol
"Description" keyOf Degree.Description
"year" keyOf Degree.year hasWidth 5
}
}
"Degree" is the database table to which we have to access in order to select the information. In this example, we have used Exposed syntax to declare the select query from the Degree table, then we have created a list that contains two columns Symbol and Description those columns receive data from the result of the query.
The function keyOf
is used in order to enter the columns in which Galite has to make its research.
The fucntion hasWidth
is used in order to specify the width of the data displayed.
As a result of your selection, you will then have a table with two columns which will contain the different sorts of degrees achievable in the year you have entered.
Visual Galite Forms
The aim of Galite is to enable you to create applications. An application is made up of forms. A form is a set of pages consisting of blocks. A block is nothing else than a table which is divided into columns (vertical) and rows (horizontal). A row is made up of fields, and each field contains a data value at the intersection of a row and a column that can be of different Types. By the end of this chapter, you will learn how to create a form, but before, you need to learn how to create a field and how to form blocks.
Creating Form Fields
In Galite, fields are containers in which you can define different types of data.
To define a field you can call one of the 4 functions mustFill()
, visit()
, skipped()
or hidden()
. You can pass the domain and the position of the field in the block as arguments.
Syntax
val fieldName = mustFill(FieldDomain, [FieldPosition]) {
FieldBody
}
|
val fieldName = visit(FieldDomain, [FieldPosition]) {
FieldBody
}
|
val fieldName = skipped(FieldDomain, [FieldPosition]) {
FieldBody
}
|
val fieldName = hidden(FieldDomain, [FieldPosition]) {
FieldBody
}
FieldBody : [FieldLabel] [FieldHelp] [FieldAlignment]
[FieldDropList] [FieldOptions] [FieldColumns]
[FieldCommands] [FieldTriggers]
Field Access Modifiers
An access modifier determines the way the application user will have to handle each field in a form as well as the field accessibility. There are 4 sorts of access modifier:
Syntax:
AccessModifier: mustFill()
|
visit()
|
skipped()
|
hidden()
-
mustFill : As indicated, a MUSTFILL field has to be filled by the user.
-
visit : A VISIT field is a field which the user can fill if he wants to.
-
skipped : A SKIPPED field is a field which is displayed on the form but which the user can not overwrite. A field is skipped if the user needs to know the information written in it.
-
hidden : A HIDDEN field that is not displayed on the form as the information it provides is of no importance for the user, this kind of fields is usually used for database table join operations or for ID fields.
As the user moves from a field to another when filling the form, the current field is always colored in red.
Example
val password = mustFill(domain = STRING(20), position = at(2, 1)) {
...
}
val id = hidden(domain = INT(20)) {
...
}
Field Position
This entry defines the position of the field in the current block. HIDDEN fields does not have positions, and in multiple block (Defined in the Blocks creation Section of this document) you may use the NO DETAIL Block option to pass positioning fields. There are two possibilities to define the field position.
-
Absolute Position With the function
at()
- You can define it according to one integer or more:
-
-
at(row)
-
at(row , column)
-
at(row, column..multifield)
-
Syntax:
FieldPosition : at(row: Int [,column: Int])
|
at(rowRange: IntRange, [,column: Int])
|
at(row: Int [,columnRange: IntRange])
|
at(rowRange: IntRange, [,columnRange: IntRange])
|
follow(otherField)
Example
val name = visit(domain = STRING(20), position = at(1, 1..4)) {
...
}
The first integer indicates the row number while the second defines the column.
In fact, Galite automatically divides up the window in rows and columns when setting up a form.
When defining the width of a column, thus,
it always adopts the width of the longest field. Since the form wide is not unlimited,
you can put a long field in two or more columns in order to spare place as it is the case in the example above.
-
Relative Position With follow
You can also make use of the following structure : follow(otherField)
Example
val age = visit(domain = INT(3), position = follow(name)) {
...
}
If you declare a field with the follow function, it means that this field will be placed directly next to the first field defined by the follow property (field "Name") on the same row. The two fields will then form one single column.
Field label
The field label is optional, you declare it with the label property inside the field body, the syntax is the following :
Syntax
FieldLabel : label = "NAME OF THE FIELD"
If you don’t want your field to have a label, you can set the property label to empty.
Example
val secondName = visit(domain = STRING(20), position = at(1, 1)) {
label = "The second Name"
...
}
Field Help Text
this property is used to insert an explanation text for the application user in order to help him filling in the form. This Help text will then be displayed when the user places the mouse on the field.
Syntax:
HelpText: help = "HELP TEXT"
Example
val lesson = visit(domain = STRING(20), position = at(1, 1)) {
label = "lesson"
help = "The lesson you would like to attend"
...
}
Field Types
The field type is a required entry, you may enter a predefined field type :
Example
val name = visit(domain = STRING(20), position = at(1, 1)) {
...
}
val age = visit(domain = INT(3), position = at(1, 2)) {
...
}
You can also use self defined field types that you have previously defined in the type definition section of your form.
Example
val day = mustFill(domain = Days, position = at(1, 1)) {
label = "day"
help = "The day"
}
In this example, Days is a type you should have defined previously using standard types, Code
types and SELECT
command …
Field Alignment
This property is used to define the localization of the field’s content inside the field. There are three types of alignment.
-
FieldAlignment.RIGHT the value is displayed at the right inside the field
-
FieldAlignment.LEFT the value is displayed at the left inside the field
-
FieldAlignment.CENTER the value is centered in the field
Example
val name = visit(domain = STRING(20), position = at(1, 1)) {
label = "name"
align = FieldAlignment.LEFT
}
Field Drop files
This command is used to make a field accept to drop files into it, meaning you can drag files and drop them in your field.
Syntax:
DroppableDefinition : "droppable" ExtensionList
ExtentionList : extension : String [,ExtenstionList]
Example
val cv = visit(domain = STRING(20), position = at(4, 1)) {
label = "Cv"
help = "The user curriculum vitae"
columns(u.cv)
droppable("pdf")
trigger(ACTION) {
FileHandler.fileHandler!!.openFile(form.model.getDisplay()!!, object : FileHandler.FileFilter {
override fun accept(pathname: File?): Boolean {
return (pathname!!.isDirectory
|| pathname.name.toLowerCase().endsWith(".pdf"))
}
override val description: String
get() = "PDF"
})
}
}
In this example, you can drag pdf files and drop them in the field named "cv".
Field Options
In this part of the field definition, you can use one or more option from the 9 options available for fields in Galite, here is the a list of these field options defined under the class FieldOption and that you can use them by declaring the function options().
-
NOECHO : If this option is used, characters typed in the field will not be displayed and a star(*) will be displayed instead, this option is useful for password fields.
-
NOEDIT : This option makes it impossible to change the data of the field or to overwrite it.
-
SORTABLE : This option adds two opposed arrows icons(up and down) just before the field, clicking on the icon changes the way data are sorted in the field, you can click the icon three times to have ascending sort, descending sort and default sort,
-
TRANSIENT : This option make the field transient, meaning that the system can no trail it, if a transaction calls this field and then this transaction is aborted, the field will not be able to backup or roll-back to its original value, besides this option makes changes ignored for this field.
-
NO DELETE ON UPDATE : If the field is a lookup is a column of a lookup table, using this option prevent the system to clear the field when inserting new rows or updating rows.
-
NO DETAIL : If the block is in detailed, using this option on a field make it invisible in the detail.
-
NO CHART : If the block is multiple, using this option on a field exclude it from the chart.
-
QUERY UPPER : Whatever the string you input, this option will make Galite transform it to capital letters.
-
QUERY LOWER : the opposite of the previous option it transform strings to lower case.
Example
val password = mustFill(domain = STRING(20), position = at(2, 1)) {
label = "password"
help = "The user password"
options(FieldOption.NOECHO)
}
Field Columns
The columns method option is used to establish a connection between a certain column in the database with the field.Once such connection established, the field will have a direct access to the database column allowing insertions, modification …+
You may enter this option in order to specify which table and which column the field refers.
The same field can refer to more than one column.
You can also use the key method option to specify a key column in the database or the nullable method option to specify an outer joint.+
Two more option are available with the columns
function, the index and the priority options.
Syntax:
columns(ColumnList) {
ColumnsBody
}
ColumnsBody : [ColumnIndex] [ColumnPriority]
ColumnList : Column [, Column ]
Column : ["KEY (QualifiedName)"] ["NULLABLE (QualifiedName)"]
Example
val id = hidden(domain = INT(20)) {
label = "id"
help = "The user id"
columns(u.id)
}
Note
|
that a field can be connected to more than one table and column. In this case, the statement you will type will be: |
Example
val id = hidden(domain = INT(20)) {
label = "id"
help = "The user id"
columns(I.NumInvoice, ID.Invoice)
}
In this example, I
is the invoices table and ID
is the invoice details table and they have an outer join connection throw columns NumInvoice and Invoice.
-
Indexes
This option is used to define a value in the database which is to remain unique so that it can not appear anymore in another field of the same column. you need to use the index method to declare an index in Galite.
Let’s sum up with the following syntax:
Syntax:
ColumnIndex : "val i = index(message : String)"
If two or more fields are given the same index value, it means that two similar combinations of these field values will not be accepted.
For example, two different lessons cannot be given in the same room. In this case, the three fields, namely the fields "professor", "time" and "lesson" are to be attributed the same index. Thus, at least one of the three values needs to be changed so that the combination can be accepted by the machine.
Example
val i = index(message = "this should be unique")
val lesson = mustFill(domain = INT(11), position = at(1, 1)) {
label = "Lesson"
help = "The lesson you have to attend to"
column(LEC.Lesson) {
index = i
}
}
val lecturer = mustFill(domain = INT(11), position = at(2, 1)) {
label = "Lecturer"
column(LES.Lecturer) {
index = i
}
}
val time = mustFill(domain = STRING(20), position = at(3, 1)) {
label = "Time"
help = "The lesson you have to attend to"
column(LES.Time) {
index = i
}
}
However, this example would implicate a professor can give two different lessons at the same time. In order to avoid such errors, you can attribute one field two or more indexes. So you can associate the two fields "professor" and "time" together. Thus, you will have:
Example
val lessonIndex = index("Lesson should be unique")
val timeIndex = index("Time should be unique")
mustFill(domain = LONG(20)) {
label = "Lesson"
help = "The lesson you have to attend to"
column(LEC.Lesson) {
index = lessonIndex
}
}
visit(domain = LONG(10)) {
label = "Lecturer"
column(T.Lecturer) {
index = lessonIndex + timeIndex
}
}
mustFill(domain = STRING(20)) {
label = "Time"
column(LEC.Time) {
index = timeIndex
}
}
In this case, notice that the "Lecturer" field has been associated with two indexes: lessonIndex and timeIndex.
The index value is ascendant. When attributing an index value to a field combination, you shall always take the value nexting that you have taken the last.
-
Priority
Syntax:
ColumnPriority: "priority = Integer"
This option is used in order to define the column order within a list when this list is displayed. A priority is always followed by an integer according to the structure given above. The column with the biggest priority value will appear on the extreme left side of the table and the one with the least value will be on the extreme right side.
We shall notice that negative values are also permitted in this option. However, the minus sign ("-") standing before the number does not have any influence over its value but simply indicates the way all the information will be sorted out within a column. Actually, the different fields are always sorted in the ascending way, i.e from A to Z in case of an alphabetical text and from 1 to x+1 for numbers. Now, if the integer is preceded by a minus, the column content will be sorted in the other way round.
Example
val i = index(message = "this should be unique")
val surname = mustFill(domain = STRING(20), position = at(1, 1)) {
label = "Surname"
column(User.Surname) {
priority = 3
}
}
If columns "Surname", "Name" and "Date of Birth" are respectively given the priorities 3,4 and 1, "Name" will come first and will be followed by "Surname" and "Date of Birth". The same order applies with the values 3, -4 and 1, with the only difference that the names will be sorted out from Z to A.
Moreover, two columns with the same priority will be displayed according to the same order in which the user has listed them.
Field Commands
Once you have defined the columns , you may define the field commands.
Syntax:
command(item : Actor, [AccessMode]) {
CommandBody
}
CommandBody : CommandAction
CommandAction : KotlinCode
AccessMode : "arrayOf(" vararg ListOfModes.. ")" | vararg ListOfModes..
Standard Field Command
The command can be a Galite predefined command or you can make new Actors and commands you can use in the field command definition.
Example:
command(item = clear, Mode.UPDATE, Mode.INSERT, Mode.QUERY) {
...
}
val clear = actor()
Field Command using Modes
All the previous command definition ways can be preceded by one mode or more. There are three sorts of mode which are to be sorted according to the following hierarchy:
-
QUERY to start an inquiry within the database
-
INSERT to create a new row in the database
-
UPDATE to enter new information within a row
Example
command(item = insertMode, Mode.UPDATE, Mode.QUERY) {
...
}
If you have entered a mode for the command
, it means this command can only be invoked if the block is in the mode you have determined.
Field Access modifiers using Modes
This method type is used to change the access to the field using the block Mode. In fact the access is not changed directly but the higher access possible is set to the indicated access.
We have 12 method allow us to change the visibility of our field :
-
onQueryHidden() : in mode Query set field to Hidden access
-
onQuerySkipped() : in mode Query set field to Skipped access
-
onQueryVisit() : in mode Query set field to Visit access
-
onQueryMustFill() : in mode Query set field to MustFill access
-
onInsertHidden() : in mode Insert set field to Hidden access
-
onInsertSkipped() : in mode Insert set field to Skipped access
-
onInsertVisit() : in mode Insert set field to Visit access
-
onInsertMustFill() : in mode Insert set field to MustFill access
-
onUpdateHidden() : in mode Update set field to Hidden access
-
onUpdateSkipped() : in mode Update set field to Skipped access
-
onUpdateVisit() : in mode Update set field to Visit access
-
onUpdateMustFill() : in mode Update set field to MustFill access
Example
val file = visit(domain = STRING(25), position = at(3, 1)) {
label = "test"
help = "The test"
onQueryHidden()
onInsertHidden()
}
In this example, we have determined that the field will be invisible
if the block is in the QUERY
or the INSERT
mode.
Field Triggers
Triggers are events that you can use to execute actions when they occur, there are field triggers, block triggers and form triggers that you can use following this syntax :
Syntax
Trigger : "trigger (" EventList ")" TriggerAction
EventList : Event [,EventList]
TrigerAction : {KOTLIN code}
Field Triggers are events that concern the fields behavior, here is a list of all Galite field triggers available :
-
PREFLD : is executed upon entry of field
-
POSTFLD : is executed upon exit of field
-
POSTCHG : is executed on field content change
-
PREVAL : is executed before validating any new entry
-
VALFLD : is executed after field change and validation
-
VALIDATE : this is the same trigger as VALFLD
-
DEFAULT : Defines the default value of the field to be set if the setDefault() method is called (this method is automatically called when the user choose the insert command)
-
FORMAT : Not defined actually
-
ACCESS : ACCESS is a special trigger that defines how a field can be accessed. This trigger must return one of these values ACS_SKIPPED, ACS_HIDDEN, ACS_VISIT or ACS_MUSTFILL.
-
VALUE : equates the value of two fields
-
AUTOLEAVE : must return a boolean value, if "true" the cursor will move to the next field
-
PREINS : is executed before inserting a row of the database
-
PREUPD : is executed before updating a row of the database
-
PREDEL : is executed before deleting a row of the database
-
POSTINS : is executed after inserting a row of the database
-
POSTUPD : is executed after updating a row of the database
Examples
val age = visit(domain = INT(3), position = follow(name)) {
label = "age"
trigger(POSTCHG) {
name.value = "userName"
}
}
in the example above we will assign the value "userName" to the name field each time we change the value of the age field.
val password = mustFill(domain = STRING(20), position = at(2, 1)) { label = "password" trigger(ACCESS) { if (name.value == "hidden") { Access.HIDDEN } else { Access.SKIPPED } } }
Creating Form Blocks
As you already know, a form is composed of blocks. A block is a set of data which are stocked in the database and shown on a form. A block is created in order to either view the content of a database, to insert new data in the database or to update existing data in the database. To create a block you need first to create a class that extend from Block class here we can specify the name, the buffer size and the rows number of the block as parameter, in second part you need to use insertBlock method that allow you to add the block to the form.
Syntax
class Block: Block(blockName : String, buffer: Int, rows: Int) {
BlockBody
}
BlockBody : [blockBoder] [blockAlignement] [blockHelp]
[blockOptions] [blockTables]
[blocIndices] [blockCommands] [blockTriggers] blockFields
[blockContextFooter]
Block Types
There are actually two types of blocks in Galite, the only difference between them in the definition syntax is the buffer Integer.
-
single blocks
A single block is a block in which only one single row of a database table is displayed on the form. Each time, the computer will memorize only one entire row and a certain quantity of ID numbers through which it will retrieve another rows from the database if you want to view them.
Example
class BlockCommand : Block("Commands", 1, 5) {
...
}
// usage:
val BlockCommand = page.insertBlock(BlockCommand()) {
...
}
The first integer indicates the block type. In case of a single block, the first integer will always be 1. The second integer indicates the maximal number of the automatically memorized IDs.
-
Multiple Blocks
A multiple block is a block in which more than one row are displayed on the form. These rows are retrieved all at once from the database and are memorized by the computer. Actually, you can define the number of the displayed rows which can be less than this of the memorized rows. In this case, there will be no need anymore to retrieve the hidden rows from the database when you want to view them.
Example
class BlockCommand : Block("Commands", 10, 5) {
...
}
The first integer indicates the number of rows to be automatically memorized while the second defines the number of displayed rows. Notice the first integer value must always be greater than 1.
Block Names
The example bellow show how to create a block, you need to specify the block title as parameter in the Block class.
Example
class Degree : Block("Degree Block", 10, 5)
Block Border
You can insert the optional border statement that defines the Block’s frame type into the init block. Besides, the Title of the block will appear only if the Block’s Border type has been specified. There are actually four Border options defined into Border enumeration class :
-
Border.LINE to frame the block with lines.
-
Border.RAISED to enhance a block by setting it on the foreground.
-
Border.LOWERED to put it at the background.
-
Border.ETCHED to carve a frame in the form.
Example
inner class Degree : Block("Degree Block", 10, 5) {
init {
border = Border.LINE
}
}
Block Alignment
Alignment statements are useful to align a block(source block) referring to another one(target block). Use the align method and you have to specify the referred block name followed by one or many pairs of two fields the pairs are separated by a comma. As for the pair fields, the one in the left is the source block field while the other one is for the target block field.
For Example, let us suppose we have a multiple block Prices with 4 columns, with column 3 filled with Quantities and Column 4 with Prices, we also have a single block TotalPriceswith two fields totalQuantity and totalPrices, we want these fields to be aligned with the correct fields of the Prices block, so we specify that the totalQuantity field is aligned with the field quantity of the block Prices, same thing for totalPrice field it’s aligned with the price field of the block Prices:
Example
val targetBlock = insertBlock(Prices())
val TotalPrices = block("Total", 1, 1) {
val totalQuantity = visit(INT(3), position = at(1, 1)) {
label = "Total"
help = "Total"
}
val totalPrice = visit(INT(7), position = at(1, 2)) {}
align(targetBlock, totalQuantity to targetBlock.quantity, totalPrice to targetBlock.price)
}
Block Help
This optional command is used to define the help text for each field of the block. You need to set the value of help property.
Example
inner class Degree : Block("Degree Block", 10, 5) {
init {
border = Border.LINE
help = "test"
...
}
}
Block Options
You need to use options function in your block to specify block options. you can find the list of available options in BlockOption class :
-
NOCHART : Disables the chart(grid) rendering of a multiple bloc to make it look like a single block.Only possible on multiple blocks displaying only one row, Besides the fields must be positioned with the AT command.
-
NODETAIL : Disables the positioning of fields and displays the block as a chart (grid), Only possible on multiple blocks, the fields should not be positioned with the AT command.
-
NODELETE : Prevent the user from removing fields content.
-
NOINSERT : Prevent the user from inserting data in fields.
-
NOMOVE : Prevent the user from moving between records.
-
ACCESS_ON_SKIPPED : Makes the block accessible even if or its fields have SKIPPED access.
-
UPDATE_INDEX : If used, saving a block would delete all its rows and reinsert them one by one, by doing so, you can update the table rows even when you change the index fields without worrying about the "row already exist exception".
inner class Degree : Block("Degree Block", 10, 5) {
init {
options(BlockOption.NODETAIL)
...
}
}
Block Tables
When making use of this section, you have to type the function table and enter the table name, assign this function to an variable. This variable will further be used as a shorthand in place of the complete table name in order to access to the table. These names refer to certain tables in the database whereby the first table is the one on which the user will work. The remaining tables are the so-called "look-up tables", i.e tables that are associated with the first one.
Syntax:
BlockTables: table(vararg ExposedTables)
Example
inner class School : Block("School", 1, 1) {
val L = table(Lessons)
val P = table(Lecturers)
val R = table(Rooms)
...
}
The user will make use of these look-up tables as references when bringing in changes in the principal table.
Block Indexes
If you plan to enter one index or more when defining your fields, you also have to define one index text or more which will appear if you make a mistake by entering an indexed data or a data combination twice. This command can be followed by an error message contained in a string.
Syntax:
BlockIndices: "index(" message : String ")" [BlockIndices]*
Example
inner class Degree : Block("Degree", 1, 1) {
val unique = index(message = "This data already exists")
...
}
Block Commands
Block commands are commands accessible only from the block where they are called. There are Three ways to call block commands:
-
Calling A Standard Command
Structure of command:
"command (" SimpleItemName ")" {KOTLIN Code}
Example
command(item = Validate) {
validate ()
}
-
Calling The Block Commands With Modes
Blocks have 3 possible Modes:
-
QUERY : When querying the database
-
INSERT : When inserting a new row in the database
-
UPDATE : When updating rows in the database
You can combine these modes with the previous block command structure to have more control over your command Use the mode function inside you command body declaration to set the modes :
Syntax
"command (" SimpleItemName, [Modes]")" {}
Modes: Mode*
This means that the command called is only accessible when the block is in the specified Mode.
Example
command(item = Validate, Mode.UPDATE, Mode.QUERY) {
validate ()
}
-
Calling The Block Commands With Modes And An Access Modifier
in Galite, a field can have different access types or modifiers, here is the definition of the four available access modifiers listed by ascending level.
-
HIDDEN : HIDDEN field are invisible in the form, they are used to store hidden operations and database joins.
-
SKIPPED : SKIPPED fields are read only fields, you can read the value but you can’t modify it.
-
VISIT : fields with this access type are accessible, can be modified but not necessary.
-
MUSTFILL : MUSTFILL fields are accessible fields that the user must fill with a value.
In the block command section, you can set the highest access level for the block fields according to the mode in wich the block would be ording to the mode in which the block would be.
Example
blockVisibility(Access.SKIPPED, Modes.QUERY, Modes.UPDATE)
blockVisibility(Access.HIDDEN, Modes.UPDATE)
blockVisibility(Access.VISIT, Modes.QUERY)
In the first example, all fields in the block will be either SKIPPED or HIDDEN in the query and update modes and can neither be MUSTFILL nor VISIT. For the second example, all the fields in the block will be HIDDEN when the block is in update mode. In the last example, all the fields in the block will be either VISIT, SKIPPED or HIDDEN in the query mode and can not be MUSTFILL.
Block Triggers
The block triggers are the same as form triggers on the block level. There are actually 20 block triggers you can use to execute actions once they are fired.
Syntax
BlocTrigger: "trigger(" TriggerAction : BlocEventList ")"
BlocEventList: BlockEvent [,BlockEvent]*
Concerning the trigger action, which is the action to execute when the trigger is activated they can by : ** {KOTLIN code}
Here is a list of all available block triggers or block events in Galite.
-
PREQRY : executed before querying the database
-
POSTQRY : executed after querying the database
-
PREDEL : executed before a row is deleted
-
POSTDEL : executed after a row is deleted
-
PREINS : executed before a row is inserted
-
POSTINS : executed after a row is inserted
-
PREUPD : executed before a row is updated
-
POSTUPD : executed after a row is updated
-
PRESAVE : executed before saving a row
-
PREREC : executed upon record entry
-
POSTREC : executed upon record exit
-
PREBLK : executed upon block entry
-
POSTBLK : executed upon block exit
-
VALBLK : executed upon block validation
-
VALREC : executed upon record validation
-
DEFAULT : is executed when the block is in the InsertMode. This trigger becomes active when the user presses the key F4. It will then enable the system to load standard values which will be proposed to the user if he wishes to enter new data.
-
INIT : executed upon block initialization
-
RESET : executed upon Reset command (ResetForm)
-
CHANGED : a special trigger that returns a boolean value of wether the block have been changed or not, you can use it to bypass the system control for changes this way :
trigger(CHANGED) { return false; }
-
ACCESS : defines whether a block can or not be accessed, it must always return a boolean value
trigger(ACCESS) { return Block.getMode() == MOD_QUERY // Tests if the block is in query mode, this block is only accessible on query mode }
Examples
object Degree : Block("Degree block", 10, 5) {
trigger(PREBLK, INIT) {
Kotlin-Code
}
trigger(PREINS) {
println("Inserting date");
...
}
...
}
Block Fields Declaration
In this section, all you have to do is to write at least on block field definition that begins with an access modifier and ends with brace "}", you can enter as much fields as you may need following the field definition we saw in the previous chapter.
Examples
val name = visit(domain = STRING(25), position = at(1, 2)) {
label = "Last name"
help = "The client name"
columns(c.name)
}
Creating Forms
There are actually two types of forms in Galite, normal forms and BLOC INSERT forms which are special forms containing blocks that you may insert in other forms.
A form begins always with FORM and ends with END FORM, while a BLOC INSERT form begins with BLOC INSERT and ends with END INSERT.
In Galite to create a form you need to extend from this list off classes :
-
Form : Represents a form.
-
ReportSelectionForm : Represents a report selection form.
-
DictionaryForm : Represents a dictionary form.
When creating a form, you will have to include the block and the field definitions. Moreover, you will have to define the menus as well as the different commands. Finally, you can also define some form triggers. Concretely, the structure is the following:
Syntax:
FormDef :
class FormSample: Form("title =" formTitle : String, ["locale =" formLocalization : String])
{
[MenuDefinition]
[ActorDefinition] [TypeDefinition]
[CommandDefinition] [InsertDefinition]
[FormCommands] [FormsTriggers] [BlocksDefinition]
}
Form Localization
This is an optional step in which you may define the language of your forms menus and messages, the latter have to be defined in xml files.
Syntax
"Form(title =" formTitle : String, "locale =" formLocalization : String")"
Example:
class LecturersForm : Form(title = "Form", locale = Locale.UK) {}
Form Title
Every form have can have a title (optional).
Syntax
"Form(title =" formTitle : String")"
Example
class LecturersForm : Form(title = "Lecturers List", locale = Locale.UK) {}
Menus Definition
Defining a menu means adding an entry to the menu bar in the top of the form, you can add actors to this menu later by specifying the menu name in the actor definition.
Syntax:
MenuDefinition: "menu (" label : String ")"
Example
class ListLecturersForm : ReportSelectionForm(title = "List of the Lecturers", locale = Locale.UK) {
val file = menu("file")
...
}
Actors Definition
An Actor is an item to be linked with a command, if its ICON is specified, it will appear in the icon_toolbar located under the menu bar, otherwise, it will only be accecible from the menu bar. ICON and KEY are optional, the KEY being the keyboard shortcut to assign to the actor.
Syntax:
ActorDefinition: "actor"(
"menu" = SimpleName,
"label" = label : String,
"help" = helpText : String,
) {
[key = key : String]
[icon = icon : Icon]
}
Example
class ListLecturersForm : ReportSelectionForm(title = "List of the Lecturers", locale = Locale.UK) {
val file = menu("file")
val cut = actor(
menu = file,
label = "cut",
help = "cut element",
) {
key = Key.F2 // key is optional here
icon = Icon.CUT // icon is optional here
}
....
}
Types Definition
After having defined your menus and actor, you can enter different field types definitions based on the standard field types or code field types, you can also use SELECT commands to customize these new types.
Example
class ListLecturersForm : ReportSelectionForm("") {
object Days: CodeDomain<Int>() {
init {
"Sunday" keyOf 1
"Monday" keyOf 2
"Tuesday" keyOf 3
"Wednesday" keyOf 4
"Thursday" keyOf 5
"Friday" keyOf 6
"Saturday" keyOf 7
}
}
object CurrentDegree : ListDomain<String>(20) {
override val table = query(Degree.selectAll())
init {
"Symbol" keyOf Degree.Symbol
"Description" keyOf Degree.Description
}
}
}
Commands Definition
In this section you may want to define new commands, to do so, all you need is an already defined Actor from which you will call the command in order to execute an Action on the form. every command have a effective ray of action (VField, VBlock, VForm)
-
Simply writing the body of the action using the ACTION command, the parameters are optional and can be VField, VBlock, VForm.
Command Defined in this section can be form level commands, block level commands or field level commands, this will depend on the action called by the command and where the command is actualy called.
Syntax
cmdDef : "command (" SimpleItemName ")" { commandBody }
commandBody: { KOTLIN statements }
Example Writing the action’s body :
command(item = PrintBlock) {
blockName.validate()
blockName.getForm().close(VForm.CDE-Validate)
}
After the different definitions we have made (types, menus, actors,commands), we need to begin the declaration of our form.Here, we will set up the structure and the behaviour of the form throw 4 sections :
-
Form Options
-
Form Commands
-
Form Triggers
-
Blocks
Form Commands Declaration
There still one more think to know about form command declaration, in fact, you can also control the accessibility to a command by the Mode of the form.+ A form have 3 possible Modes :
-
QUERY : When inquiring the database
-
INSERT : When inserting a row in the database
-
UPDATE : When updating a row in the database
The Syntax to use command with modes is the following :
Syntax
"command (" SimpleItemName, Modes ")" {}
Modes: Mode*
This means that the command called is only accessible when the block is in the specified Mode.
Example
command(item = Validate, Mode.UPDATE, Mode.QUERY) {
validate ()
}
Form Triggers Definition
Form Triggers are special events that once switched on you can execute a set of actions defined by the following syntax :
Syntax
FormTrigger : "trigger(" TriggerAction : FormEventList ")" FormEventList: FormEvent [,FormEvent]*
Galite actually defines 6 Form Triggers or Form Events :
-
INIT : executed when initializing the form and before the PREFORM Trigger, also executed at ResetForm command
-
PREFORM : executed before the form is displayed and after the INIT Trigger, not executed at ResetForm command
-
POSTFORM : executed when closing the form
-
QUITFORM : actually not available
-
RESET : executed upon ResetForm command
-
CHANGED : a special trigger that returns a boolean value of whether the form have been changed or not, you can use it to bypass the system control for changes this way :
val postform = trigger(POSTFORM) { println("post form trigger works") }
Examples
val preform = trigger(INIT) { println("init form trigger works") } val initialisation = trigger(INIT, PREFORM) { //KOTLIN statements }
Form Pages
You can create Pages in your form using the page function after the trigger declaration section,this is optional and will create a Tab for each page you create under the form’s toolbar. You can put as much blocks you want in each page, the same goes for form without pages.
Example
val p1 = page("Page1")
Visual Galite Reports
Visual Galite allows you to create dynamic reports. They have a very simple structure, in fact, all you have to do to create such reports is to list the definitions of all the fields you need, then you will that will load data into these fields in the init method by creating a query with Exposed syntax. The so created reports will have dynamic functions such as sorting and group making, you will also be able to print it or export to different file formats.You can even add other fields to the report after it’s generated, do more calculus, customize the report columns… The Galite Reports are generated from Visual Galite forms by different methods that we will see in this chapter, along with the process of making a dynamic report.
Creating Report Fields
As we said in the introduction, the report structure is based on report fields, these fields are declared based on field function and have the following syntax :
Syntax
ReportFieldDef: "field(" FieldType ")" {
[FieldLabel] [HelpText] [FieldAlign] [FieldOptions]
[GroupCommand] [FieldCommands] [FieldTriggers]
}
GroupCommands : "group =" field
Example
val name = field(STRING(20)) {
label = "name"
help = "The user name"
align = FieldAlignment.LEFT
group = age
format { value ->
value.toUpperCase()
}
}
val age = field(INT(3)) {
label = "age"
help = "The user age"
align = FieldAlignment.LEFT
compute {
// Computes the average of ages
Triggers.avgInteger(this)
}
}
Report Field Label
The field label is the name that will be shown in the dynamic report, you can define a label by setting the label variable using String value.
Example
val name = field(STRING(20)) {
label = "Person Name"
...
}
val age = field(INT(3)) {
label = "age"
...
}
val ID = field(INT(3)) {
label = ""
...
}
Report Field Help Text
Help variable is used to insert an explanation text for the application user. This Help text will then be displayed when the user places the mouse on the report field.
Example
val name = field(STRING(20)) {
label = "Person Name"
help = "The Person's First Name"
...
}
Report Field Type
The field type is a required entry, you may enter a predefined field type as parameter for the field function :
Example
val name = field(STRING(20)) {
label = "Person Name"
...
}
val age = field(INT(3)) {
label = "age"
...
}
You can also use self defined field types that you have previously defined in the type definition section of your report.
Example
val objectList = field(Objects) {
label = "age"
...
}
In this example, Objects is a type you should have defined previously in type definition section of the report using standard types, CODE types and SELECT command …
Report Field Alignment
This command is used to define the localization of the field’s content inside the field. There are three types of alignment.
-
ALIGN RIGHT the value is displayed at the right inside the field
-
ALIGN LEFT the value is displayed at the left inside the field
-
ALIGN CENTER the value is centered in the field
Example
val Name = field(STRING(10)) {
label = "name"
align = FieldAlignment.CENTER
}
Report Field Options
There is actually only one option for the dynamic report fields in Galite: the HIDDEN OPTION , if this option is used on a field, he will not be visible the the report. This options have to be used on the last visible field of the report to avoid rendering bugs.
Example
val Name = field(STRING(10)) {
label = "name"
hidden
}
The field Name will not be visible on the report.
Report Field Group
You can create clickable groups in your report by using the keyword GROUP in you field followed by the field you want to be grouped by the actual field.
Example
val Customers = field(STRING(5)) {
label = "Customers"
}
val Articles = field(STRING(10)) {
label = "Articles"
}
val Articles = field(STRING(10)) {
label = "Articles"
}
val Articles = field(STRING(10)) {
label = "Articles"
}
val InvoiceNum = field(STRING(10)) {
label = "InvoiceNum"
group = Articles
group = Customers
}
In this report, you can click on the InvoiceNum field to group customers and articles.
Report Field Command
In report Fields, you can call commands with this syntaxe :
"command (" SimpleItemName ")" {KOTLIN Code}
Report Field Triggers
Report field triggers are special events that you can catch to execute other actions. In the field body call compute or format function and inside this function specify your code.
Syntax
Trigger : TrigerAction : EventList
EventList : Event [,EventList]*
TrigerAction : compute | format { KOTLIN code }
Here is the two triggers available for report fields :
-
FORMAT : applay style on the field content example change it to uppercase.
-
COMPUTE : executed when the report is displayed and can be used to compute expressions on the report columns and show the result.
Example
val salary = field(DECIMAL(width = 10, scale = 5)) {
label = "salary"
help = "The user salary"
align = FieldAlignment.LEFT
compute {
// Computes the average of ages
Triggers.avgDecimal(this)
}
}
val name = field(STRING(20)) {
label = "name"
help = "The user name"
align = FieldAlignment.LEFT
group = age
format { value ->
value.toUpperCase()
}
}
Creating Reports
Visual Galite Dynamic reports have a unique structure, you need to create new class that extend from Report class as described by the following syntax :
Syntax
ReportDefinition :
class ReportClass : Report(ReportTitle, [ReportHelp], [ReportLocalization]) {
[ContextHeader] [ReportDefinitions]
[ReportCommands] [ReportTriggers] (ReportFields)
[ContextFooter]
}
ReportTitle : "title =" Title : String
ReportDefinitions : [MenuDefinition] [ActorDefinition] [TypeDefinition]
[CommandDefinition]
[InsertDefinition]
Report Localization
This is an optional step in which you may define the language of your forms menus and messages, the latter have to be defined in xml files.
Example:
Report(title : String, help: String, locale : Locale)
Report Title
To set you report title you need to override the title variable of Report class.
Example
class ClientR : Report(title = "Clients_Report", locale = Locale.UK) {
...
}
Report Help Text
You can enter a help text for the report using the following syntax:
Syntax
Report(title : String, help : String, locale : Locale)
Actually every report has a help menu that tries to describe the structure of the report by giving information about its commands and fields in a document, the help text will be on the top of this help menu document.
Example
class OrdersReport : Report(title = "Orders Report", help = "This report lists purchase orders", locale = Locale.UK) {}
Report Menus Definition
Defining a menu means adding an entry to the menu bar in the top of the report, you can add actors to this menu later by specifying the menu name in the actor definition.
Syntax:
MenuDefinition: val SimpleName = "menu (" label : String ")"
Example
class ListLecturersR : Report(title = "List of the Lecturers", locale = Locale.UK) {
val newMenu = menu("newMenu")
}
Report Actors Definition
An Actor is an item to be linked with a command, if its ICON is specified, it will appear in the icon_toolbar located under the menu bar, otherwise, it will only be accessible from the menu bar. ICON and KEY are optional, the KEY being the keyboard shortcut to assign to the actor.
Syntax:
ActorDefinition:
actor("menu =" SimpleName,
"label =" label : String,
"help" = helpText : String
) {
[key = key : String]
[icon = icon : Icon]
}
Example
class ListLecturersR : Report(title = "List of the Lecturers", help = "Clients_Report", locale = Locale.UK) {
val newMenu = menu("newMenu")
val printReport = actor(
menu = newMenu,
label = "Print",
help = "Print the report",
) {
key = Key.F9 // key is optional here
icon = ICON.PRINT // icon is optional here
}
...
}
Report Types Definition
After having defined your menus and actor, you can enter different field types definitions based on the standard field types or code field types, you can also use the LIST and SELECT commands to customize these new types.
Syntax:
TypeDefinition: "object" SimplName":" CodeDomain<FieldType>() {[TypeList] } | "object" SimplName":" ListDomain<FieldType>() {[TypeList] }
Example
class LisLecturersR : Report("") {
object Days: CodeDomain<Int>() {
init {
"Sunday" keyOf 1
"Monday" keyOf 2
"Tuesday" keyOf 3
"Wednesday" keyOf 4
"Thursday" keyOf 5
"Friday" keyOf 6
"Saturday" keyOf 7
}
}
object CurrentDegree : ListDomain<String>(20) {
override val table = query(Degree.selectAll())
init {
"Symbol" keyOf Degree.Symbol
"Description" keyOf Degree.Description
}
}
}
Report Commands Definition
In this section you may want to define new commands, to do so, all you need is an already defined Actor from which you will call the command in order to execute an Action on the form. every command have an effective ray of action (VRField, VReport)
-
Simply writing the body of the action using the ACTION command, the parameters are optional and can be VRField or VReport.
Syntax
cmdDef : "command (" SimpleItemName ")" { commandBody }
commandBody: { KOTLIN statements }
Example Writing the action’s body :
val PrintReport = command(item = PrintReport) {
action = {
// KOTLIN code
}
}
Report Triggers Declaration
Report Triggers are special events that once switched on you can execute a set of actions defined by the following syntax :
Syntax
ReportTrigger : "trigger(" TriggerAction : ReportEventList ")" ReportEventList: ReportEvent [,FormEvent]*
Galite actually defines 2 report Triggers or report Events :
-
PREREPORT : executed before the report is displayed.
-
POSTREPORT : executed after the report is closed.
Example
class SimpleReport : Report(title = "SimpleReport", locale = Locale.UK) {
val preReport = trigger(PREREPORT) {
println("---------PREREPORT TRIGGER-------------")
}
val postReport = trigger(POSTREPORT) {
println("---------POSTREPORT TRIGGER-------------")
}
...
}
Report Fields Declaration
As you already know, a dynamic report is based on field that will be shown as report columns, in this section you have to write at least on field definition or more following the definition and the structure we saw in the previous chapter.
Report data initialization:
You can define the report initial data in the constructor of your report class. You can use Exposed Syntax to create query that select data from you database tables, then you need to iterate this query and use the add statement to add the row to the report this function affect you data to the fields
For example here is a dynamic report named UserList with 3 fields (FirstName, LastName, Age), we will retrieve data from the User
table on the database. Then affect the query result to our report to create rows.
Example
class UserList : Report("") {
init {
transaction {
User.selectAll().forEach { result ->
add {
this[firstName] = result[Client.firstNameClt]
this[lastName] = result[Client.lastNameClt]
this[age] = result[Client.age]
}
}
}
}
}
Calling reports
A report is always called from a form, if the caller form extends from the DictionaryForm class you have to do the following steps :
-
Change DictionaryForm to ReportSelectionForm
-
Add a command that calls the
createReport
method and pass the report as a parameter to this method.
Example
command(item = report) {
createReport(ClientR())
}
Otherwise you can create a normal form or block command that executes the following code :
WindowController.windowController.doNotModal(UserList())
Visual Galite Charts
Visual Galite allows you to create charts. They have a very simple structure, in fact, all you have to do to create such charts is to define the dimensions you need and their measures, then you will have to load data into these fields in the init section. With Galite Charts, you will also be able to print or export the created chart to different file formats.
The Visual Galite Charts are generated from Visual Galite form by different methods that we will see in this chapter, along with the process of making a visual charts.
Creating Charts dimensions and measures
As we said in the introduction, the chart structure is based on chart dimensions and measures. To create a dimension field you need to use dimension function and specify the type of the field, the field have the following syntax :
Syntax
ChartDimensionDef: "dimension(" DimensionType ")" {[DimensionLabel] [HelpText] DimensionType [DimensionCommands] [DimensionTriggers] }
Example
val city = dimension(STRING(10)) {
label = "dimension"
format {
object : VColumnFormat() {
override fun format(value: Any?): String {
return (value as String).toUpperCase()
}
}
}
}
Use the measure function to create a measure field, this field have the following syntax :
Syntax
ChartDimensionDef: "measure(" DimensionType ")" {[DimensionLabel] [HelpText] DimensionType [DimensionCommands] [DimensionTriggers] }
Chart "dimension / measure" Label
The label is the name that will be shown in the chart, you can define a label by setting the label value.
Example
val Name = dimension(STRING(10)) {
label = "Person Name"
...
}
val ID = dimension(STRING(10)) {
label = ""
...
}
Chart "dimension / measure" Help Text
Help variable is used to insert an explanation text for the application user . This Help text will then be displayed when the user places the mouse on the chart measure or dimension.
Example
val Name = dimension(STRING(10)) {
label = "Person Name"
help = "The Person's First Name"
}
Chart dimension Type
The dimension type is a required entry, you may enter a predefined dimension type :
Example
val Name = dimension(STRING(10)) {
label = "Person Name"
help = "The Person's First Name"
}
val Month = dimension(MONTH) {
label = "Month"
}
You can also use self defined field types that you have previously defined in the type definition section of your chart.
Example
val objectList = dimension(Objects) {
label = "objects"
}
In this example, Objects is a type you should have defined previously in type definition section of the chart using standard types, CODE types nd SELECT command …
Chart measure Type
The dimension type is a required entry but it is restricted to number types. This means that for a measure you can use only INT and DECIMAL as types. The measure type is checked at the compilation step. If you use not a numeric type for a measure, the following compiler error will be revealed :
Measure "Measure name" is not numeric.
The measure type can be defined with different ways :
Example
val Name = measure(INT(5)) {
label = "Name"
}
val Name = measure(Price) {
label = "Name"
}
In the last example, the price type should be a numeric type otherwise it would not be accepted.
Chart "dimension / measure" Command
In chart dimensions or measures, you can call commands with one of the following syntaxes :
"command (" SimpleItemName ")" { action = " {KOTLIN Code} }
Chart "dimension / measure" Triggers
Chart dimensions or measures triggers are special events that you can catch to execute other actions.
Syntax
Trigger : TrigerAction : EventList
EventList : Event [,EventList]*
TrigerAction : color | format { KOTLIN code }
Here is the two triggers available for chart fields (dimensions & measures) :
-
FORMAT : Called for formatting a dimension value. The trigger should return a org.kopi.galite.visual.chart.VColumnFormat instance. This trigger is not available for measures.
-
COLOR : Called to specify a measure color. The trigger should return a org.kopi.galite.visual.visual.VColor instance. This trigger is not available for dimensions.
Example
val city = dimension(STRING(10)) {
label = "dimension"
help = "test"
format {
object : VColumnFormat() {
override fun format(value: Any?): String {
return (value as String).toUpperCase()
}
}
}
}
Creating Charts
Visual Galite charts have a unique structure, you need to create new class that extend from Chart class as described by the following syntax :
Syntax
ChartDefinition :
classChartClass : Chart(ChartTitle, [CharttLocalization]) {
[ContextHeader] [CharttHelp] [ChartDefinitions]
[ChartCommands] [ChartTriggers] (ChartFields)
[ContextFooter]
}
ChartTitle : "title =" Title : String
ChartDefinitions : [MenuDefinition] [ActorDefinition] [TypeDefinition]
[CommandDefinition]
[InsertDefinition]
Chart Localization
This is an optional step in which you may define the language of your forms menus and messages, the latter have to be defined in xml files.
Example:
class ChartSample: Chart(title = "", locale = Locale.UK)
Chart Title
To set you chart title you need to override the title variable of Chart class.
Example
class ChartSample: Chart(title = "Area/population per city", locale = Locale.UK) {
...
}
Chart Help Text
You can enter a help text for the chart using the following syntax:
Syntax
class ChartSample: Chart(title = titleText :String, help = helpText :String, locale = Locale.UK)
Actually every chart has a help menu that tries to describe the structure of the chart by giving information about its commands and fields in a document, the help text will be on the top of this help menu document.
Example
class OrderedChart: Chart(
title = "Ordered quantities per month",
help = "TThis chart lists the ordered quantities per month",
locale = Locale.UK
) {
...
}
Chart Menus Definition
Defining a menu means adding an entry to the menu bar in the top of the chart, you can add actors to this menu later by specifying the menu name in the actor definition. In the menu definition, the LABEL is optional.
Syntax:
MenuDefinition: val SimpleName = "menu (" label : String ")"
Example
class OrderedChart: Chart(title = "Ordered quantities per month", locale = Locale.UK) {
val newMenu = menu("newMenu")
...
}
Chart Actors Definition
An Actor is an item to be linked with a command, if its ICON is specified, it will appear in the icon_toolbar located under the menu bar, otherwise, it will only be accessible from the menu bar. ICON,LABEL and KEY are optional, the KEY being the keyboard shortcut to assign to the actor.
Syntax:
ActorDefinition: "actor("
"menu =" SimpleName,
"label =" label : String,
"help" = helpText : String,
) {
[key = key : String]
[icon = icon : Icon]
}
Example
class OrderedChart: Chart(title = "Ordered quantities per month", locale = Locale.UK) {
val newMenu = menu("newMenu")
val printChart = actor(
menu = newMenu,
label = "Print",
help = "Print the chart",
) {
key = Key.F9 // key is optional here
icon = Icon.Print // icon is optional here
}
...
}
Chart Types Definition
After having defined your menus and actor, you can enter different field types definitions based on the standard field types or code field types, you can also use the LIST and SELECT commands to customize these new types.
Syntax:
TypeDefinition: "object" SimplName":" CodeDomain<FieldType>() {[TypeList] } | "object" SimplName":" ListDomain<FieldType>() {[TypeList] }
Example
class OrderedChart : Chart("") {
object Days: CodeDomain<Int>() {
init {
"Sunday" keyOf 1
"Monday" keyOf 2
"Tuesday" keyOf 3
"Wednesday" keyOf 4
"Thursday" keyOf 5
"Friday" keyOf 6
"Saturday" keyOf 7
}
}
object CurrentDegree : ListDomain<String>(20) {
override val table = query(Degree.selectAll())
init {
"Symbol" keyOf Degree.Symbol
"Description" keyOf Degree.Description
}
}
}
Chart Commands Definition
In this section you may want to define new commands, to do so, all you need is an already defined Actor from which you will call the command in order to execute an Action on the chart. every command have an effective ray of action (VDimension | VMeasure, VChart)
-
Simply writing the body of the action using the ACTION command, the parameters are optional and can be VColumn or VChart.
Syntax
cmdDef: "command (" SimpleItemName ")" { commandBody }
cmdBody: { KOTLIN statements }
Example
Calling a local action :
val print = command(item = printActor) {
// KOTLIN code
}
Chart Triggers Declaration
Chart Triggers are special events that once switched on you can execute a set of actions defined by the following syntax :
Syntax
ChartTrigger : TriggerAction : ChartEventList ChartEventList: ChartEvent*
Galite actually defines 4 chart Triggers or chart Events :
-
PRECHART : executed before the chart is displayed.
-
INIT : executed at chart initialization.
-
CHARTTYPE : executed after the chart initialization. This trigger should return org.kopi.galite.visual.chart.VChartType and will a fixed type for the chart.
-
POSTCHART : executed after the chart is closed.
Example
class OrderedChart: Chart(title = "Ordered quantities per month", locale = Locale.UK) {
val init = trigger(INITCHART) {
chartType = VChartType.BAR
}
// This is the type that will be taken because CHARTTYPE is executed after INIT
val type = trigger(CHARTTYPE) {
VChartType.BAR
}
}
Chart Fields Declaration
As you already know, a chart is based on field that will be shown as chart series, in this section you have to write at least on dimension and one measure definition or more following the definition and the structure we saw in the previous chapter.
Chart data initialization:
You can fill the chart’s lines or rows in the constructor of you chart class.
All you need to do is importing your data (from a variable, file, database query using Exposed…) , declaring a row in the chart then add the add()
function to add the row to the chart.
For example here we have added three dimension and for each dimension we have to specify 2 measures
Example
init {
city.add("Tunis") {
this[area] = Decimal("34600")
this[population] = 1056247
}
city.add("Kasserine") {
this[area] = Decimal("806600")
this[population] = 439243
}
city.add("Bizerte") {
this[area] = Decimal("568219")
this[population] = 368500
}
}
Chart types
Galite offers five predefined chart types :
-
Pie charts
-
Bar charts
-
Column charts
-
Line charts
-
Area charts
The chart type can be defined by calling the setType(VChartType) method. The VChartType class contains the five definitions described above :
-
VChartType.PIE
-
VChartType.BAR
-
VChartType.COLUMN
-
VChartType.LINE
-
VChartType.AREA
If no type is defined, the chart type will be set to the default type which is the VChartType.DEFAULT = VChartType.COLUMN. The chart type can be set at INIT trigger or can be fixed using the CHARTTYPE trigger.
If you want to define a new type, you need to subclass the VChartType object and define the data series by overriding the createDataSeries(VChart). The view implementations should also be provided by implementing the UChartType component. The new chart type should be mentioned in the ChartTypeFactory class which is responsible for creating views for every chart type.
You should note that calling setType will create the data series and refresh the chart view. So it can be called whenever you want to change the chart type.
Calling charts
A chart is always called from a form. You have to do the following steps :
-
Add a command that calls the
createReport
method and pass the report as a parameter to this method.
Example
command(item = graph) {
showChart(ChartSample())
}
Otherwise you can create a normal form or block command that executes the following code :
WindowController.windowController.doNotModal(ChartSample())
The Galite Project Future
After reading this document, you should be able to create efficient database applications using the Galite framework, you can also create dynamic reports and charts. Actually, the project owners are working on a web version of the Galite framework. This version features many capabilities and is based on the Vaadin framework which is an open source web application framework for rich Internet applications Vaadin, uses java as programming language to create web content and web components to render the resulting web page.