Table of Contents
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.
An SJS statement is made of 4 parts :
The name of the statement, e.g. :
EntityThe identifier of the underlying component, e.g. :
Entity('Employee')or even simpler without parenthesis :
Entity 'Employee'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'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.
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.
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']
Language artifacts are sometimes optional. Omitting them can improve the reading.
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'
}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'
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.
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'
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.
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.
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.
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
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:trueAs 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.
A template is a special type of paramSet that is implicitely applied to statements that have the same name as the template identifier.
Here is an example of a template to be applied to the application forms :
template 'form', parent:'decoratedView',
labelsPosition:'ABOVE', columnCount:2There 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:2The 'form' template as implicitely been
applied to the form statement.
template is only supported in SJS Front
DSL.
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.