Chapter I. Using SJS

Table of Contents

I.1. Authoring SJS source files
I.1.1. General syntax
I.1.2. Arguments types
I.1.3. Arguments syntax
I.1.4. Simplified forms
I.1.5. Focus on the special "custom" argument
I.1.6. Properties defined as arguments or closures
I.1.7. Arguments factorization
I.1.8. Namespaces
I.2. Examples
I.2.1. SJS Domain
I.2.2. .SJS Front
I.3. Key concepts
I.3.1. Entity relationships
I.3.2. Business rules
I.3.3. Actions
I.3.4. Arbitrary beans
I.4. SJS source files organization
I.4.1. Default organization
I.4.2. Further modularization
I.4.3. Identifiers unicity scope and "spec" section

An SJS application description is a set of statements that are used to describe and assemble Jspresso components the same way you would do it using Spring XML. The big difference is the expression language itself.

I.1. Authoring SJS source files

I.1.1. General syntax

An SJS statement is made of 4 parts :

  1. The name of the statement, e.g. :

    Entity
  2. The identifier of the underlying component, e.g. :

    Entity('Employee')

    or even simpler without parenthesis :

    Entity 'Employee'
  3. Arguments that configure the underlying component properties, e.g. :

    Entity('Employee', icon:'employee-48x48.png')

    or even simpler without parenthesis :

    Entity 'Employee', icon:'employee-48x48.png'
  4. Nested SJS statements between curly brackets (a "closure"). In that case, parenthesis cannot be omitted, e.g.

    Entity('Employee', icon:'employee-48x48.png') {
        string_32 'name'
        string_32 'firstName'
    }

    Or even with deeper nesting :

    split_vertical('Departments.and.teams.view',
                   top:'Company-departments.table',
                   cascadingModels:true) {
        bottom {
            split_horizontal (left:'Department-teams.table',
                              right:'Team-teamMembers.table',
                              cascadingModels:true)
        }
    }

    Statement nesting is a key feature for SJS flexibility.

Only statement name is always mandatory. Other parts may be required or not and SJS will enforce these rules.Some statements dont have any parameter. To summarize, every SJS statement conforms to the following scheme :

name('identifier', parameters) {closure}

where closure is a list of SJS statements. This means that there can be an arbitrary nesting of SJS statements.

I.1.2. Arguments types

The following types are used for SJS argument values :

  • String : 'value'

  • Boolean : [true|false]

  • Integer : 12

  • Decimal : 12.34

  • List : ['value1','value2']

  • Map: [key1:'value1', key2:'value2']

SJS will enforce argument types. Under certain circumstances, expected argument types are references themselves, in which case, SJS will check their validity. Used reference types can be :

  • Domain elements, i.e. entities, components or interfaces.

  • Domain element properties, i.e. fields.

  • Views (form, table, split, ...).

  • Actions and Action maps.

  • Generic arbitrary beans for advanced usage.

I.1.3. Arguments syntax

Arguments are defined using the following scheme :

argumentName:argumentValue

For instance :

icon:'traceable-48x48.png'

Whenever multiple arguments need to be declared, they are coma separated, e.g. :

icon:'traceable-48x48.png', preferredWidth:45

An argument value can be a complex type, e.g. a list :

uncloned:['createTimstamp','lasUpdateTimestamp']

or a map (associative array), e.g. :

ordering:['name':'ASCENDING','firstName':'DESCENDING']

I.1.4. Simplified forms

Language artifacts are sometimes optional. Omitting them can improve the reading.

I.1.4.1. Parenthesis in statements

Whenever a statement does not contain a closure, parenthesis can be omitted, e.g. :

Entity 'Employee', icon:'employee-48x48.png'

Whenever the closure is present, the identifier and arguments section must be surrounded by parentheses, e.g. :

Entity('Employee', icon:'employee-48x48.png') {
    string_32 'name'
    string_32 'firstName'
}

I.1.4.2. Square backets in lists

Whenever a list contains more than 1 element, it has to be surrounded by square backets, e.g. :

Entity 'Employee', uncloned:['name','firstName']

Whenever a list contains only 1 element, square backets can be omitted, e.g. :

Entity 'Employee', uncloned:'name'

I.1.4.3. Properties composed in statement names

Some SJS statements allow for a property to be composed in their names in order to improve code readability :

  • string_n and text_n where n is the maximum length of the string or text property, e.g. string_32 ot text_1024.

  • split_vertical and split_horizontal where vertical and horizontal are the allowed values for the split container orientation.

I.1.4.4. View model inference

In order to simplify the code to write, a view model can be inferred from the view name. By convention, a view identifier should begin with the model identifier, followed by a dot, followed by an arbitrary complement, e.g. :

form 'Employee.mainView'

If you follow this convention and if the model property is not set, SJS will look for a model with the identifier being the view id prefix. If it finds one, it will use it exactly as if the model property had been set. This means that if the Employee model exists, the following statements are equivalent :

form 'Employee.mainView'
form 'Employee.mainView', model:'Employee'

I.1.5. Focus on the special "custom" argument

The argument named custom is a special argument that opens SJS built-in statements to further arbitrary customization. This argument is especially useful to customize generic action instances that are assigned to a view. Another important usage of the custom argument concerns the bean statement that allows to create instances of arbitrary classes.

The custom argument value is a map (associative array).

Supported map value types are :

  • String : 'value'

  • Boolean : [true|false]

  • Integer : 12

  • Decimal : 12.34

  • List : ['value1','value2']

  • Map: [key1:'value1', key2:'value2']

Regarding List and Map value types, the supported keys and values are :

  • String : 'value'

  • Boolean : [true|false]

  • Integer : 12

  • Decimal : 12.34

As a consequence, it is impossible to declare to nest maps into a custom argument value. The following example shows a simple usage of the custom argument on an action definition :

action 'chooseEmployeeAction',
    parent: 'lovAction',
    custom: [autoquery:false,
             entityDescriptor_ref:'Employee',
             initializationMapping:['company': 'company'],
             okAction_ref:'addFromList']

There are certain situations where you need to assign a reference to a custom property. On the previous example, the okAction_ref is such a property as it references another action whose identifier is addFromList (same applies for the entityDescriptor_ref property). In that case, SJS must be aware of the value being a reference to :

  • generate correct Spring XML

  • control the validity of the reference

Since the custom argument contains arbitrary properties, there is no way for SJS to know for the expected type of the value. That is why a reference property must be sufixed by _ref so that the value is interpreted as a reference to another component.

Whenever a list valued property is _ref suffixed, every of its values is interpreted as a reference.

A map valued property cannot be _ref suffixed itself. But each of its entry can be individually.

I.1.6. Properties defined as arguments or closures

Some properties can be declared indifferently as an argument or as a closure. These properties are generally reference properties. Let's take an example.

A vertical split container references 2 views : 1 for the top part (the "top" property) and 1 for the bottom part (the "bottom" property).

You can simply reference existing views by using top and bottom arguments, e.g. :

split_vertical 'Departments.and.teams.view',
    top:'Company-departments.table',
    bottom:'Department-teams.table'

Or you can use closures to define your top and bottom views inline, e.g. :

split_vertical('Departments.and.teams.view') {
    top {
        table(parent:'Company-departments.table') {
            ...
        }
    }
    bottom {
        table(parent:'Department-teams.table') {
            ...
        }
    }
}

Or you can even mix both, e.g. :

split_vertical('Departments.and.teams.view',
    top:'Company-departments.table') {
    bottom {
        table(...) {
            ...
        }
    }
}

Using a closure to define a property value allows :

  • to inherit an existing component but override some of its configuration locally.

  • to define brand new "anonymous" components inline.

Using a closure will often be referenced as an inline declaration. The main difference between an inline declaration and a top-level declaration is that the inline one is anonymous, i.e. it cannot be referenced from outside (using _ref suffix for instance). You will typically favorize inlined declarations for parts of the application that are hardly to be reused. On the other hand, top-level declarations are required to be named, thus can be later referenced.

I.1.7. Arguments factorization

I.1.7.1. paramSet

You will often face situations where you will want to name and reuse a set of arbitrary characteristics without introducing inheritance between your components. In order to achieve this goal, SJS provides the paramSet statement.

Declaration

As for every SJS statement, the paramSet statement can be named. However, unlike other statements its arguments are un-constrained and a paramSet statement does not allow for a closure.

Here is an example of a paramSet declaration :

paramSet 'roMandatory', readOnly:true, mandatory:true
Usage

Every SJS statement allows for a paramSets argument that expects a list of paramSet identifiers. When performing generation, SJS will simply de-reference each paramSet and apply its arguments to the owning declaration.

Here is an example using the paramSet defined in the former section :

string_32 'firstName', paramSets:['roMandatory']

which is equivalent to :

string_32 'firstName',
    readOnly:true,
    mandatory:true

Note

As any SJS statement, paramSet allows for the paramSets argument ! This feature allows to nest paramSets between eachother.

Whenever the same argument is defined in different applied paramSets, the last one in the list wins, meaning that declared paramSets are applied in the order they are declared. This way, a paramSet value can always be overriden by the owning statement.

Sometimes, you will need to remove an argument from a paramSet when using it. Overriding a paramSet argument with a null value ill simply remove the argument from the paramSet scope.

paramSet and paramSets can be used in both SJS Domain and SJS Front DSLs.

I.1.7.2. template

A template is a special type of paramSet that is implicitely applied to statements that have the same name as the template identifier.

Declaration

Here is an example of a template to be applied to the application forms :

template 'form', parent:'decoratedView',
    labelsPosition:'ABOVE', columnCount:2
Usage

There is nothing explicit to code in order to use a template. Once the template of the former section has been declared, the following declarations are both equivalent :

form 'Employee.pane'
form 'Employee.pane', parent:'decoratedView',
    labelsPosition:'ABOVE', columnCount:2

The 'form' template as implicitely been applied to the form statement.

template is only supported in SJS Front DSL.

I.1.8. Namespaces

Namespace usage allows to simplify SJS based source code by inferring package names of classes and resources that are produced. This package determination is based on conventions covering best practices about a Jspresso project layout.

A namespace is opened using the namespace statement, e.g. :

namespace('org.jspresso.hrsample') {
    ...
}

The namespace will be applied to all statements that are placed into the namespace scope (between the curly braces).

Using the formerly defined namespace will make thefolowing declarations equivalent (note how the namespace has been applied to the image resource) :

Entity 'City', icon:'city-48x48.png'
Entity 'org.jspresso.hrsample.model.City',
    icon:'classpath:org/jspresso/hrsample/images/city-48x48.png'

When a project is generated using the Jspresso archetype, a default namespace is created using the root package name. All declarations placed into standard SJS ource files are implicitely placed into this namespace.

Whenever SJS finds a "/" (slash) in a resource name or a "." (dot) in a class name, the enclosing namespace is ignored and the resource path or class name is considered absolute (i.e. not relative to the namespace).

Namespaces can be nested. In that case, nested namespace names are concatenated.