Lyra provides a quick way to develop user interface forms linked to Celesta cursors. Cursors can be filtered and sorted.

In order to use lyra, one should add the following dependency to their project:

<dependency>
<groupId>ru.curs</groupId>
<artifactId>lyra</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>

Lyra Form

Lyra forms can be of two types: Card and Grid. Each form is defined by a class inherited from the appropriate base class (BasicCardForm or BasicGridForm). The form itself is set declaratively and is characterized by:

  • Cursor, to which the form is linked. Each of the Lyra forms must override the getCursor (CallContext context) method, which returns the corresponding Celesta cursor. If necessary, in the same method, filters and sorting can be applied to the cursor.

  • set of bound and unbound fields displayed on the form.

Lyra takes on the task of transferring information between the form and the server, as well as navigating through the table records. The grid form also by itself solves the problem of fast display, scrolling and positioning of the grid with a large number of records. For the developer it is sufficient only to declare the fields they would like to have on the form, with order and properties.

Field Types

Fields, as already mentioned, can be of two types:

  • bound with the cursor field (i.e. column of the table), the values of the bound fields are stored in the database automatically,

  • unbound, that is, not linked with any of the fields of the cursor, but with a getter/setter method of the form class.

When the user edits the bound fields, their new values are written into the fields of the cursor and stored in the database automatically. The values of the unbound fields are passed as parameters to the appropriate methods, and Java code executed on the server can use these values.

Designing a Form from Scratch in Five Steps

To create a Lyra form from scratch, you must follow the following steps:

Step 1. Create a class inherited from BasicCardForm or BasicGridForm, optionally annotated with @LyraForm:

class TestForm extends BasicCardForm{
....
}

or

@LyraForm(gridwidth="600px",
      gridheight="200px")
class TestForm extends BasicGridForm{
....
}

If you add FormInstantiationParameters argument to the form’s constructor, the respective object will be passed to the constructor automatically. You may also define FormInstantiationParameters field annotated with @FormParams annotation, and its value will be automatically injected.

Step 2. Override getCursor(CallContext context) method so it returns sorted and filtered cursor. You may use FormInstantiationParameters object defined in the previous step to define sorting and filtering.

This cursor is going to be the source of the record set for the form:

   TestCursor getCursor(CallContext context){
        TestCursor result = new TestCursor(context);
        result.setRange("myField", myFilterValue);
        return result;
   }

Step 3 (optional). If you need unbounded fields on the form, you should declare them as getters and, optionally, setters, annotated with @FormField. Getters / setters should follow the Java getter/setter names convention:

    @FormField(celestatype="INT",
               caption="Подпись поля",
               width=30)
    public int getMyField() {
        return this.my;
    }

    public void setMyField(int value) {
       this.my = value;
    }

Step 3 (optional). You may use CelestaSQL’s CelestaDoc to set the bound field’s properties. You may omit this step as well: first, Lyra will choose reasonable default values (e.g. table field name for caption, as needed in most cases); second, all the properties definitions can be set in the form’s class itself.

create table test (
/**
 {"caption": "Identifier"}
 */
id int not null default seq primary key,

/**
 {"caption": "Integer Value"}
 */
attrInt int default 3
);

Step 5. In the form class constructor, define the set and order of form fields by calling the following methods:

  • LyraFormField createField(String name) adds a field with the given name to the form and returns an object of type LyraFormField. The name value must match

    • either one of the column names of the form cursor (this creates a bound field),

    • or with the name of the property of the form class declared with @FormField annotation, this creates and unbound field. The respective methods must be named according to common convention, e. g. int getSomething(..) for integer property named something, or boolean isSomething(..) if the type of the property is boolean.

The type of unbound field is determined from the return type of @FormField-annotated method in the following way:

Java Type

Lyra Form Type

boolean

BIT

int

INT

ru.curs.celesta.dbutils.BLOB

BLOB

java.util.Date

DATETIME

String

VARCHAR

double

REAL

@FormField-annotated method can either have or don’t have the CallContext parameter.

The LyraFormField object returned by the createField method afterwards can be modified via its properties.

  • createAllBoundFields(), which is equivalent to calling the createField method for each of the table fields.

  • createAllUnboundFields(), which is equivalent to calling the createField method for each of the properties of a class declared with @FormField annotation.

For example, if we want all unbound fields in the form to go first, and then all bound fields, and we are satisfied with the default (CelestaDoc or annotation-set) field property values, then we can write this:

    public TestForm(CallContext context){
        super(context);
        createAllUnboundFields();
        createAllBoundFields();
    }

When writing a form constructor, the developer can choose one of the strategies so that the code is the most elegant, concise and flexible. As a rule, the choice of strategy is determined by one of the typical scenarios that one has to face:

Scenario

Form construction strategy

There is only one table-based form in the entire application. Or there can be many forms for one table, but on any form you need to display all the fields of the table or view in accordance with the CelestaDoc-specified properties.

You should use the createAllBoundFields() method, setting the CelestaDoc for the fields, if necessary. In particular, if no CelestaDoc is specified, a form containing all fields of the table will be constructed, and the names of these fields will be used as captions, which is very convenient for quick-and-dirty grid construction. Fields that have visible = False at the CelestaDoc level will not be displayed on the form. To add all unbound fields, use the createAllUnboundFields() method.

Only a very small quantity of the fields should be displayed on the form, or the form should be made very specific, not paying attention to what is indicated in CelestaDoc.

You should use several calls to the createField(name) method for each of the fields. If necessary, the properties of objects returned by calls to this method can be changed.

In general, the properties specified in CelestaDoc are fine, but for some of the fields you need to override them.

You must first use the createAllBoundFields() method to add all the fields with their properties taken from CelestaDoc, and then, after receiving the metadata for each of the created fields using the getFieldsMeta(…​) method, alter them via their property setters.

Warning
Note that field names within a form must be unique, just as field names in a table. Therefore, calling the createAllBoundFields() method twice, as well as calling the createField(name) method twice for the same name, will lead to an error. An error will also result in creating an unbound field with a name coinciding with a table field added to the form.

afterReceiving(…​) and beforeSending(…​) Methods

A form class may and should also contain business logic that performs certain actions when values are entered by a user into a form. Two main entry points available in each of form classes are

void afterReceiving (BasicCursor c)

void beforeSending (BasicCursor c)

The afterReceiving (BasicCursor c) method is called after receiving form data from the client, but before the data is flushed to the database. Thus, if you change the cursor fields in it, then the changed values will be transferred into the database. The argument c contains a cursor with fields that come from the form.

The beforeSending (BasicCursor c) method is called before serialization of data and sending it to the form. Thus, if you change the fields in it, the modified values will be displayed on the form. The argument c contains a cursor with fields that come from the database.

Business logic can also be contained in getters and setters of unbound fields.

beforeShow(…​) Method

The method is invoked before the form is displayed to the user. In this method, some preparatory actions can be performed: for example, the cursor can be positioned on the desired record.

Form Attributes

Each form has a set of attributes that can be defined using optional named parameters of @LyraForm annotation:

  • gridwidth — ширина грида (в пискелах)

  • gridheight — высота грида (в пикселах)

Field Attributes

Each form field (LyraFormField class instance) has a set of following attributes:

  • caption — 'human-readable' caption of the field.

  • editable — set to false, is the field needs to be read-only.

  • sortable — indicates whether or not the grid should allow sorting by values in this field, by clicking on the column’s header cell. Defaults to true.

  • visible — set to false, if the field needs to be hidden from form.

  • required — required field. Warning: bound fields related to not null table fields will be always treated as required, regardless of the value of required property.

  • scale — maximum decimal point numbers (for REAL-typed fields).

  • width — visible width of the field (in pixels).

  • dateFormat — format of the datetime field.

Methods of Setting Field Attributes

So, the properties of form fields in Lyra can be set:

  • In design time:

    • for bound fields in table fields' CelestaDoc,

    • for unbound fields by setting the properties of the @FormField annotation.

  • In run time: for any fields by changing the properties of the LyraFormField object, obtained either by calling the createField(name) method, or by retrieving from the dictionary returned by the getFieldsMeta() method.

To set the field attributes for Lyra in CelestaDoc, you need to insert an object in JSON format into CelestaDoc, for example, like this:

CREATE TABLE table1
(
  /** {"caption": "human-readable field name",
       "visible": false}*/
  column1  INT NOT NULL IDENTITY PRIMARY KEY,
  /** игнорируемый текст {"caption": "field name with \"quoted\" words",
       "editable": false,
       "visible": true} this text will be ignored*/
  column2  REAL,
  column3 BIT NOT NULL DEFAULT 'FALSE'
 );
Warning
Setting the field attributes in CelestaDoc is convenient because the attribute specified in one place (i. e. in the CelestaSQL script) will be used by default in all forms that use the corresponding table as a data source. If needed, in each specific form, you can always redefine attributes at run time. If the form using the table is only one, then the correct approach is to set the corresponding field attributes directly in CelestaDoc. Note that the system automatically selects from the CelestaDoc text the first occurring JSON object, ignoring the rest of the text content that may also be present there for other purposes.

The @FormField annotation is added to functions that return the values of unbound fields, and also has parameters caption, editable,` visible`, etc. These are optional parameters that correspond to the field attributes of the same name.

If multiple values of the same property are defined in different places, they get overwritten in a certain order.

Property

Precedence order for unbound fields

Precedence order for bound fields

caption

1. @FormField annotation’s caption parameter,

2. if not set, then the getter method name.

1. table field’s CelestaDoc (caption attribute), 2. if not set, then the table field’s name.

editable

1. @FormField annotation’s editable parameter,

2. or else true.

1. CelestaDoc’s editable attribute,

2. if not set, then true.

visible

1. @FormField annotation’s visible parameter,

2. if not set, then true.

1. table field’s CelestaDoc (visible attribute),

2. if not set, then true.

Lyra Forms Implementation

Below is a UML diagram of Lyra’s Java classes:

Lyra

TODO: redraw this diagram in PlantUML

Implementation Example with Comments

@LyraForm(gridWidth = 100, gridHeight = 10)
public class TestForm extends BasicGridForm<OrderLineCursor> {

    //Constructor will be run only once: each form is a Spring's singleton Component
    public TestForm(CallContext c, GridRefinementHandler handler) {
        super(c, handler);
        //First, we add to the form all the table's fields in the order they declared in SQL
        createAllBoundFields();

        //Add a field to the form and then alter its caption
        LyraFormField f = createField("field2");
        f.setCaption("Unbound field caption");

        //Add a field to a form with default attributes (inherited from CelestaDoc or chosen by default)
        createField("field1");
    }

    @Override
    public OrderLineCursor getCursor(CallContext callContext) {
        //sorting and filtering can also be performed here
        return new OrderLineCursor(callContext);
    }

    @FormField(caption = "Field Caption")
    public String getField1(CallContext ctx) {
        return "foo";
    }

    public void beforeSending(OrderLineCursor c){
        //do something before the cursor is serialized and sent to the form
    }

}

Client part

The Lyra config

The Lyra config should be specified as a function that returns the config object:

function getLyraConfig() {
    return {
        baseUrl: "http://localhost:8081",
    };
}

window.getLyraConfig = getLyraConfig;

Property

Description

baseUrl

It’s a base URL to which the Lyra’s endpoint paths are appended.

Lyra context

Lyra context is an object:

    {
        part1: 'part1',
        part2: 'part2',
        refreshParams:
            {
                selectKey: ['74000004000079300'],
                sort: ['name', 'code'],
                filter: {
                    condition1: 'filter conditions1',
                    condition2: 'filter conditions2',
                }
            }
    };
  • part1, part2 - arbitrary parameters,

  • refreshParams - parameters used to build the Lyra grid on the server:

    • selectKey - primary key for positioning on a specific record,

    • sort - sorting conditions,

    • filter - filter conditions.