Content
- Models
- Experiences
- Automatic generation of the UI of the experience
- Events of the experiences
- Rules and Contexts
Models
A model is a basic structure of representation and data modeling for a Mat|r application.
The syntax to define a model is by using the reserved word: Model
.
A model permits the storage of a set of data of different type, such as simple types (characters, integers, floating point numbers) and compound types (other models, Array, Map).
Each piece of data or element stored inside a model is called an attribute.
Example of model with simple type of data:
Model Person {
String name
Double height
Integer age
Bool hasPartner
Date birthday
}
Example of models with compound type of data:
Model Class {
Array<Person> students
Person teacher
}
Experiences
Experiences are models for which a visual representation is auto-generated. This representation is called Layout
and it allows us to obtain input from the user, execute rules that modify model and display the result.
Let’s analyse the following example:
Experience Profile {
String name label("Enter username") as TextField
Decision save action('context.rule1') label('Save')
}
We can see that the attributes of an experience admit modifiers (as TextField in the example) that indicate which visual component will be generated in the layout of the experience. And, in turn, the attribute will be bound to the visual component. In other words, once the textfield is edited, the value of the attribute will be updated in the underlying model and after editing the model, the change will be reflected in the TextField component.
The resulting layout of this example is:
Layouts in UIBuilder and binding configuration
Through UIBuilder
, it is possible to preview, generate and set up the appearance of the graphic representations or layouts
. The edition of graphic properties can be done in two different ways: through the property panel or by editing theXML del Layout
.
Below, we can see the XML generated for the experience of the previous example:
<layout alpha="1" backgroundColor="#eeeff1" fontFamily="Roboto, sans-serif" paddingRight="18" paddingTop="18" paddingBottom="18" paddingLeft="18" name="Profile">
<datasource type="Profile" name="profile"/>
<navigationBar title="Profile" hideBackButton="false">
<leftButtons/>
<rightButtons/>
</navigationBar>
<textfield wpercentage="1" borderColor="#cccccc" borderWidth="1" cornerRadius="3" alpha="1" fontFamily="Roboto, sans-serif" fontSize="14" marginTop="0" marginBottom="8" marginLeft="0" marginRight="0" paddingRight="8" paddingTop="4" paddingBottom="4" paddingLeft="8" placeholder="Enter username" bind="profile.name"/>
<button wpercentage="1" textAlignment="center" cornerRadius="2" alpha="1" backgroundColor="#a9b4ba" fontSize="15" marginTop="4" marginBottom="8" paddingRight="8" paddingTop="8" paddingBottom="8" paddingLeft="8" textColor="#ffffff" text="Save">
<selectedState/>
<highlightedState/>
<event on="TapEvent">
<action type="RunRuleAction" ruleContext="context" ruleName="rule1"/>
</event>
</button>
</layout>
An important property to highlight within the XML of the layout definition is the entry.
<datasource type="Profile" name="profile"/>
Here, it is indicated that the Model which will be bound to the layout is of Perfil
type. Therefore, in order to view or surf this experience with a navigation statement broker.ui.push()
, it is necessary to use the name of the layout as well as the model to be bound. We can also leave out the parameter of the model to be bound and an instance of the model will be auto-generated.
Example of programmatical visualization of the layout:
Application {
OnInit {
Profile p = Profile()
broker.ui.push("Profile", p)
//which is equivalent to do:
broker.ui.push("Profile")
}
}
Decision
It is a unique and fundamental clause of an experience that generates a button on the screen without an attribute bound in the underlying model. It allows the user to set up the execution of a rule in the tap event of the button. Through the action
modifier, the context and the rule to be executed are defined and the label
tag defines the text of the button.
Experience Form {
String name as TextField
String adddress as TextField
Decision saveBtn action("ruleContext.saveData") label("Save")
}
Automatic generation of the UI of the experience.
The syntax to define an attribute that is bound to a visual component is the following:
DataType attributeName [as VisualComponent]
Below, you can see the list of all the data types, with the visual components that can be set up:
Visual Component | Description | Type of data supported | Sentence example |
---|---|---|---|
TextField | Text field editable by the user. | String, Integer, Double, Date | String userName as TextField |
Label | Text field not editable by the user. | String, Integer, Double, Date | String userNameTittle as Label |
Image | Component that permits the rendering of an image through a URL. | String | Image profileImage as Image |
Checkbox | Component that permits the visualization of the status of a boolean variable. | Bool | Bool includeAttachment as Checkbox |
Select | Component that permits the visualization and selection of a list of options. In case the binding is done with a group of models, it is necessary to use templates . |
Array<String>, Array<Integer>, Array<Double>, Array<Date>, Array<Model> with templates | Array<String> selectCountry as Select |
List | Component to list a group of values. In case the binding is done with a group of models, it is necesary to use templates . |
Array<String>, Array<Integer>, Array<Double>, Array<Date>, Array<Model> with templates | Array<String> selectCountry as Select |
Video | Component that permits the visualization of a video through a URL. | String | String videoTutorial as Video |
Component that permits the visualization of a PDF file through a URL. | String | String videoTutorial as Video |
|
MapView | Component that permits the visualization of a group of geolocated points together with its information of interest. See model Marker. | Array<Marker> | Array<Marker> nearPlaces as MapView |
Events of the experiences
When defining an experience, we can add code blocks that will be executed when life-cycle events of the experience are triggered. Therefore, it is possible to add logic at the moment an experience is created, when it can be viewed or before its destruction.
Experience Perfil {
OnCreate {
//When the experience is created through a push or present navigation statement for the first time.
}
OnResume {
//Whenever the experience can be viewed, i.e it reaches the top of the navigation stack
}
OnDestroy {
//before its destruction
}
}
Application Clause and Application Events
As the experience has a life cycle and we can execute statements in its events, within the Application
clause we can add code blocks to be executed for the different events in the life cycle of the application. Moreover, in this section it is possible to declare variables of global scope.
Application {
Bool appIsInBackground = false //Global variable can be accessed from all the app's code blocks
OnInit {
//Executed at application startup
}
OnResume {
//executed when application's state goes to foreground
appIsInBackground = false
}
OnEnterBackground {
//executed just before application's state goes to background
appIsInBackground = true
}
}
Rules and Contexts
The rules in Mat|r constitute the way to group code statements to be executed as a reaction to different events. These code statements are defined by the key word Rule
and have to be declared within a context that group them together.
The syntax of a rule is the following:
Rule ruleName [listen clause] [when clause] {
[rule body]
}
Events that trigger the execution of a rule:
- Tap a button on the screen by interaction of the user Decision.
- Tap a visual component, to which an action of rule execution was bound from
Configuración de Eventos en UIBuilder
. - By start-up of a
listen
: the model and/or attribute which the rule is observing was modified.
Listen Clause
The listen
clause allows the user to configure a rule, so that it can observe one or more attributes or chain of attributes of a model and runs if any of its values changes.
The syntax of a rule with listen clause is the following:
listen([* | attributeName1 [, attributeName2]*] from model as varName):
- attributeName: name of the attribute to be observed. The * character can be used to observe all the attributes of the model.
- model: name of the model that contains the attribute to be observed.
- varName: name of the local variable that will be used within the body of the rule to specify the instance of the model whose attribute was modified.
In the following example, we can see how temperature conversion can be modelled. When the user updates the value of the celsius
attribute, through the edition of the first TextField of the experience TemperatureConverter
, the rule updateKelvin
will be executed.
Experience TemperatureConverter {
Double celsius as TextField
Double kelvin as TextField
}
RuleContext UpdateRules {
Rule updateKelvin listen(celsius from TemperatureConverter as ct) {
ct.kelvin = ct.celsius + 273,15
}
Rule updateCelsius listen(kelvin from TemperatureConverter as ct) {
ct.celsius = ct.kelvin - 273,15
}
}
When Clause
The when
clause allows the user to add conditions for the execution of the rule, so that they can be assessed and, depending on the result, the rule will be executed or not.
The syntax of a rule with when clause is the following:
Rule ruleName when (boolean expression) [&& [(boolean expression)] | [ || [(boolean expression)]
Notice that the expressions to be assessed can be combined with the logical operators and &&
and or ||
.
If we go back to the example of temperature conversion, we add a boolean attribute, tied to a checkbox component. We then write a new rule in charge of persisting the value of the temperature in celsius, validating if the persistence checkbox is activated under the condition that the value is other than null.
Experience TemperatureConverter {
Double celsius value(0) as TextField
Double kelvin value(0) as TextField
Bool persistSample as Checkbox
}
RuleContext PersistenceRules {
Rule persistCelsiusSample listen(* from TemperatureConverter as ct) when ct.persistSample && ct.celsius != null {
broker.localPersistence.save("lastCelsiusSample", ct.celsius)
}
}
Execution order and listen behaviour
During the execution of the body of a rule A, it is possible that the attribute of a model which is being observed by another rule B is modified (listen clause). At that time, the execution of rule A will be interrupted and the execution of rule B will take place, and, in turn, rule A will complete its execution once rule B is finished. This nested behaviour applies to rule B once again, triggering the serial execution of many other rules, before rule A -which started the process- completes its execution.
There is an exception to the behaviour described above: when, within a rule, the attribute of a model that observes the same rule is modified, the execution of the rule is not interrupted. In other words, a rule cannot trigger its own interruption.
Execution order in chains of attributes
When the user changes the value in an observed chain of attributes, the rules will be executed in relation to the trajectory of the chain, triggering the execution of rules that observe changes from the first attribute on the right, to the last one on the left. In case attributes of equal depth are affected, the order in which the rules were declared is considered.
In the following example, you can see the models:
Model Coordinates {
Double lat
Double long
}
Model Country {
Coordinates coors
}
Model State {
Coordinates coors
}
Model User {
Country country
}
And rules with different depths of chains of attributes to be observed:
Rule changeUserCoordinates {
//global variable 'user' of type User
user.country.coors = Coordinates(lat: -32.88, long: -68,8)
}
Rule listen_lat_from_coors listen (lat from Coordinates as l) {
//... rule body observing changes of 'lat' attribute of models type Coordinates
}
Rule listen_long_from_coors listen (long from Coordinates as l) {
//... rule body observing changes of 'long' attribute of models type Coordinates
}
Rule listen_coors_from_country listen (coors from Country as c) {
//... rule body observing changes of 'coors' attribute of models type Country
}
Rule listen_country_coors_lat_from_user listen (country.coors.lat from User as u) {
//... rule body observing changes of 'country.coors.lat' chain of attributes of models type Country
}
Thus, the execution of the rule changeUserCoordinates
, will trigger the execution of the following rules in this order:
- Coordinates Model, rule
listen_lat_from_coors
- Coordinates Model, rule
listen_long_from_coors
- Country Model, rule
listen_coors_from_country
- User Model, rule
listen_country_coors_lat_from_user