Overview DSL Reference
High Level Constructs
unit
Units are groupings of functions and also variables.
They can be thought of as modules or namespaces. A Mezzanine script must have at least one unit.Unit must be followed with a unique unit name. Units are defined as follows.
- unit MyUnit;
- // Other code follows
- string myVar;
- int factorial(int x) {
- if(x == 0 || x == 1) {
- return 1;
- } else if (x > 1) {
- return factorial(x - 1);
- }
- return 0;
- }
HEADS UP Objects, enums and validators are global, so they can optionally be in a separate source code file without a unit.
object
Indicates the start of an object declaration. Must be followed by a unique object name and attribute block
- object Person
- {
- string fname;
- string sname;
- int age;
- }
persistent
Used to indicate that an object is persistent, must precede the object definition
- persistent object Person
- {
- string fname;
- string sname;
- int age;
- }
The _id attribute is also available from any persistent object. It is a uuid type.
enum
Used to declare an enumeration. Must be followed by an enumID (all caps and underscores)
- enum GENDER
- {
- Male, Female
- }
validator
Used to declare a field validator
- validator FirstnameValidator
- {
- minlen(2); maxlen(250);
- }
- validator AgeValidator
- {
- minval(0);
- }
Flow control
return
Returns the value of the expression that follows from a function
- string getGreeting(string name)
- {
- return Strings:concat("Hello, ", name);
- }
if / else
If statement
- if(name == "Tom")
- {
- Mez:log("Hello Tom!");
- }
- else if (name == "Frank")
- {
- Mez:log("Hello Frank!");
- }
- else
- {
- Mez:log("Hi! Who are you?");
- }
for
For loop
- for(;;)
- {
- // Technically not possible, as iterations
- // have a limit.
- Mez:log("Infinite loop!");
- }
- for(int i = 1; i <= 10; i++)
- {
- Mez:log(i);
- }
- int i = 0;
- for(; i < 10;)
- {
- Mez:log("While style loop!");
- }
Primitive types
Type | Description |
---|---|
int | Integers |
string | Strings |
decimal | Numeric type e.g. 3.14 or 123 |
bool | Boolean values e.g. true or false |
date | Date |
datetime | Date and time |
uuid | UUID type 4 (36 characters) |
void | Void type |
blob | Binary values e.g. photos |
Annotations
@Role
Indicates that the persistent object is associated with a user role (using this annotation on an object automatically makes it persistent, so you don’t have to explicitly use the persistent keyword)
- @Role("Manager")
- object ManagerProfile
- {
- string fname;
- string sname;
- string title;
- }
HEADS UPAny persistent object that is annotated with @Role has these implicit attributes:
- _firstNames
- _nickName
- _surname
@RoleName
Allows a custom role name to be displayed in the app that differs from role name specified in the data model and can be determined at runtime.
@RoleName string getGymEmployeeRoleName(GymEmployee employee){ EN_GYM_EMPLOYEE_ROLE_TYPE employeeRoleType = employee.employeeType; if(employeeRoleType == EN_GYM_EMPLOYEE_ROLE_TYPE.Gym_Trainer){ return "Trainer"; } else if(employeeRoleType == EN_GYM_EMPLOYEE_ROLE_TYPE.Gym_Admin){ return "Gym Admin"; } else{ return "Gym Employee"; } }
@Restrict
Use this for users of Helium Android to be able to access specific types. The selector defines which objects of the requested type are accessible to the specified role. Objects that have relationships to objects included by an @Restrict
are not automatically synchronized, they must have their own @Restrict
annotations in order for the relationship to remain valid across the network. This technique is especially useful with many-to-many relationships, which can be used to reduce the amount of data synchronized to Helium Android users, and improve the user-experience of the application.
"Manager" indicates for which roles(s) it applies and all() is the selector.
- @Restrict("Manager", all())
- object EmployeeRecord
- {
- string fname;
- string sname;
- }
Types without an @Restrict
annotation will not be synchronized to Helium Android users, and may cause null-pointer errors in your application.
@RolesAllowed
Specifies the access rights for a specific role on an object (like the previous annotation, this annotation implicitly makes the object persistent)
- @RolesAllowed("Manager", "rw")
- object EmployeeRecord
- {
- string fname;
- string sname;
- }
@Scheduled
Specifies a scheduled task to run at a certain interval
- // Run at 2:15 AM every day
- @Scheduled("15 2 * * *")
- void foobar() { ... }
- // Run at 06:00 every week-day (days 1 to 5)
- @Scheduled("0 6 * * 1-5")
- void foobar() { ... }
- // Run every ten minutes
- @Scheduled("*/10 * * * *")
- void foobar() { ... }
To summarize the schedule string format:
"minute hour day-of-month month day-of-week"
@ReceiveSms
Indicates that a function can receive an SMS.
- @ReceiveSms("Test description")
- void receiveSmsNumberText(string number, string text) { ... }
- @ReceiveSms("Test description")
- void receiveSmsObjectText(Nurse nurse, string text) { ... }
- @ReceiveSms("Test description")
- void receiveSmsObjectNumberText(Nurse nurse, string number, string text) { ... }
@Test
Indicates that a function is a unit test.
- @Test
- void testIncrement(){
- int i = 5;
- i++;
- Assert:isEqual(6, i, "Increment failed");
- }
@{ID} eg. @FirstnameValidator
Annotations that start with @ followed by an ID, and that are used on object attributes are validator annotations. The ID value must be the same as any validator that is defined in the application:
- object Person
- {
- @FirstnameValidator("validr.msg.fname")
- string fname;
- @SurnameValidator("validr.msg.sname")
- string sname;
- }
@OneToOne @ManyToOne @ManyToMany @OneToMany
Indicates the multiplicity of a relationship
- object Person
- {
- string name;
- @OneToMany
- Person children;
- @ManyToOne
- House home;
- }
@NotTracked
Indicates that an object is not tracked and will therefore not result in the generation of any change log entries.
- @NotTracked
- persistent object Person
- {
- string name;
- int age;
- }
@OnSmsResultUpdate
Used to annotate a function that will be used as a callback function by Helium when there is any update on sms results. The annotation cannot be used in conjunction with other function annotations and the function must take exactly one parameter of type __sms_result__. Note that this feature will be deployed as part of Helium 1.5. The example below demonstrates the use of this annotation.
@OnSmsResultUpdate void smsResultUpdateCallback(__sms_result__ smsResult) { if(smsResult.success == false) { FailedSms failedSms = FailedSms:new(); failedSms.failureRecordedOn = Mez:now(); failedSms.smsResultId = smsResult._id; failedSms.save(); } }
Note that duplicating the data from the __sms_result__ object in the above code snippet is done purely for demonstration purposes. Records from this object can be queried directly without needing to duplicate the data into a custom object.
@OnScheduledFunctionResultUpdate
Used to annotate a function that will be used as a callback function by Helium when there is any update on scheduled function results. The annotation cannot be used in conjunction with other function annotations and the function must take exactly one parameter of type __scheduled_function_result__. Note that this feature will be deployed as part of Helium 1.5. The example below demonstrates the use of this annotation.
@OnScheduledFunctionResultUpdate void scheduledFunctionResultUpdateCallback(__scheduled_function_result__ scheduledFunctionResult) { if(scheduledFunctionResult.success == false) { FailedScheduledFunction failedScheduledFunction = FailedScheduledFunction:new(); failedScheduledFunction.failureRecordedOn = Mez:now(); failedScheduledFunction.scheduledFunctionResultId = scheduledFunctionResult._id; failedScheduledFunction.save(); } }
Built In Objects
The Identity object
The Identity object is an implicit interface that is implemented by every persistence object in your application that has a @Role annotation. This object has the following implicit declaration:
- object Identity {
- string _firstNames;
- string _nickName;
- string _surname;
- string _locale;
- string _timeZone;
- }
The Identity object is read-only and cannot be directly instantiate by Identity:new() All attributes of the Identity object are read-only as well and are automatically populated from the information users update in Mezzanine ID.
The compiler can implicitly convert any custom persistent object with a @Role annotation to an Identity object instance. The following shows an example:
- @Role("Doctor")
- persistent object Doctor{
- }
- @Role("Nurse")
- persistent object Nurse {
- }
- persistent object Patient {
- Doctor doctor;
- Nurse nurse;
- }
- Identity getUser(Patient p){
- if(p.doctor != null)
- return p.doctor;
- else
- return p.nurse;
- }
Since all persistent custom objects with @Role annotations implement the Identity object you can also access the Identity attributes directly from such custom objects, for example:
- @Role(“Doctor”)
- persistent object Doctor{
- }
- void test(){
- Doctor d = Doctor:new();
- string firstNames = d._firstNames;
- string surname = d._surname;
- //etc.
- }
The __sms_result__ object
The __sms_result__ object represents SMS results from Helium that are duplicated in the app schema for easy access to app developers. Records belonging to this object can be queried using the standard selectors. Note that this feature is to be deployed as part of Helium 1.5. The object declaration is shown below:
@NotTracked persistent object __sms_result__ { datetime datetimestampStarted; datetime datetimestampFinished; string destination; int attempt; bool success; string error; bool doneProcessing; uuid smsOutboundConfigurationVersionId; string smsOutboundConfigurationName; uuid smsId; }
The __scheduled_function_result__ object
The __scheduled_function_result__ object represents scheduled function results from Helium that are duplicated in the app schema for easy access to app developers. Records belonging to this object can be queried using the standard selectors. Note that this feature is to be deployed as part of Helium 1.5. The object declaration is shown below:
@NotTracked persistent object __scheduled_function_result__ { datetime datetimestampStarted; datetime datetimestampFinished; string qualifiedName; string schedule; string error; string stackTrace; bool success; }
Date Built-in Functions
These built-in functions are accessed via the pseudo unit “Date”
Date:addSeconds
Adds an interval to an existing date or datetime value. The interval expression should return a positive or negative integer value that represents the number of days that is to be added to the date/datetime value. The function returns a new date or datetime instance and doesn't alter the original value.
- @Test
- void testAddDays(){
- startDate = Date:today();
- date midDate = Date:addSeconds(startDate, 5);
- date endDate = Date:addSeconds(startDate, 10);
- Assert:isNotNull(midDate, "Date:addSeconds returned null for valid date and interval values");
- Assert:isTrue(midDate > startDate, "Date:addSeconds returned an incorrect date value");
- Assert:isTrue(endDate > midDate, "Date:addSeconds returned an incorrect date value");
- }
Date:addDays
Adds an interval to an existing date or datetime value. The interval expression should return a positive or negative integer value that represents the number of days that is to be added to the date/datetime value. The function returns a new date or datetime instance and doesn't alter the original value.
- @Test
- void testAddDays(){
- date startDate = Date:today();
- date midDate = Date:addDays(startDate, 5);
- date endDate = Date:addDays(startDate, 10);
- Assert:isNotNull(midDate, "Date:addDays returned null for valid date and interval values");
- Assert:isTrue(midDate > startDate, "Date:addDays returned an incorrect date value");
- Assert:isTrue(endDate > midDate, "Date:addDays returned an incorrect date value");
- }
Date:addMonths
Adds an interval to an existing date or datetime value. The interval expression should return a positive or negative integer value that represents the number of months that is to be added to the date/datetime value. The function returns a new date or datetime instance and doesn't alter the original value.
If the target month has less days than the given one then the date may be reduced so as to not skip an additional month. ie. A month added to 31st January will result in the 28th of February (non leap year).
- @Test
- void testAddMonths(){
- date startDate = Date:today();
- date midDate = Date:addMonths(startDate, 5);
- date endDate = Date:addMonths(startDate, 10);
- Assert:isNotNull(midDate, "Date:addMonths returned null for valid date and interval values");
- Assert:isTrue(midDate > startDate, "Date:addMonths returned an incorrect date value");
- Assert:isTrue(endDate > midDate, "Date:addMonths returned an incorrect date value");
- }
Date:daysBetween
Returns the number of full days that have to pass to get from the date returned by the first expression to the date returned by the second expression.
- @Test
- void testDaysBetween(){
- date d1 = Date:fromString("2015-01-01");
- date d2 = Date:fromString("2015-01-03");
- int days = Date:daysBetween(d1, d2);
- Assert:isEqual(2, days, "Date:daysBetween returned an incorrect number of days");
- days = Date:daysBetween(d2, d1);
- Assert:isEqual(-2, days, "Date:daysBetween returned an incorrect number of days");
- }
Date:extract
Returns an integer representing the component of the date which is identified by the field name string
@Test void testFromTimeString(){ datetime dt = Date:fromTimeString("2013-1-20 08:45:12 GMT"); Assert:isEqual(2013, Date:extract(dt, "year"), "Date:extract returned an incorrect number of years"); Assert:isEqual(1, Date:extract(dt, "month"), "Date:extract returned an incorrect number of months"); Assert:isEqual(20, Date:extract(dt, "day"), "Date:extract returned an incorrect number of days"); Assert:isEqual(8, Date:extract(dt, "hour"), "Date:extract returned an incorrect number of hours"); Assert:isEqual(45, Date:extract(dt, "minute"), "Date:extract returned an incorrect number of minutes"); Assert:isEqual(12, Date:extract(dt, "second"), "Date:extract returned an incorrect number of seconds"); }
Date:monthsBetween
Returns the number of full months that have to pass to get from the date returned by the first expression to the date returned by the second expression.
- @Test
- void testMonthsBetween(){
- date d1 = Date:fromString("2015-01-20);
- date d2 = Date:fromString("2015-02-22");
- int months = Date:monthsBetween(d1, d2);
- Assert:isEqual(1, months, "Date:monthsBetween returned an incorrect number of months");
- d1 = Date:fromString("2015-01-20");
- d2 = Date:fromString("2015-02-20");
- months = Date:monthsBetween(d1, d2);
- Assert:isEqual(1, months, "Date:monthsBetween returned an incorrect number of months");
- d1 = Date:fromString("2015-01-20");
- d2 = Date:fromString("2015-02-19");
- months = Date:monthsBetween(d1, d2);
- Assert:isEqual(0, months, "Date:monthsBetween returned an incorrect number of months");
- }
Date:fromString
Returns a date representation of the string value expression
- @Test
- void testFromString(){
- string s = "2013-01-02";
- date dt = Date:fromString(s);
- Assert:isNotNull(dt, "Date:fromString returned null for a valid date value ");
- Assert:isTrue(dt > Mez:now(), "Date:fromString returned an incorrect datetime value");
- s = "abc";
- dt = Date:fromString(s);
- Assert:isNull(dt, "Date:fromString didn't return null for an invalid date value");
- }
Date:fromTimeString
Returns a date-time representation of the string value expression. The time zone component of the expression is optional and will default to the logged in user's preferred time zone.
@Test void testFromTimeString(){ datetime dt = Date:fromTimeString("2013-1-20 08:45:12 GMT"); Assert:isEqual(2013, Date:extract(dt, "year"), "Date:extract returned an incorrect number of years"); Assert:isEqual(1, Date:extract(dt, "month"), "Date:extract returned an incorrect number of months"); Assert:isEqual(20, Date:extract(dt, "day"), "Date:extract returned an incorrect number of days"); Assert:isEqual(8, Date:extract(dt, "hour"), "Date:extract returned an incorrect number of hours"); Assert:isEqual(45, Date:extract(dt, "minute"), "Date:extract returned an incorrect number of minutes"); Assert:isEqual(12, Date:extract(dt, "second"), "Date:extract returned an incorrect number of seconds"); dt = Date:fromTimeString("2013-1-20 08:45:12 Africa/Johannesburg"); Assert:isEqual(2013, Date:extract(dt, "year"), "Date:extract returned an incorrect number of years"); Assert:isEqual(1, Date:extract(dt, "month"), "Date:extract returned an incorrect number of months"); Assert:isEqual(20, Date:extract(dt, "day"), "Date:extract returned an incorrect number of days"); Assert:isEqual(6, Date:extract(dt, "hour"), "Date:extract returned an incorrect number of hours"); Assert:isEqual(45, Date:extract(dt, "minute"), "Date:extract returned an incorrect number of minutes"); Assert:isEqual(12, Date:extract(dt, "second"), "Date:extract returned an incorrect number of seconds"); }
Date:secondsBetween
Returns the total number of seconds between the first and second date-time expressions. The value will be negative if the second argument is before the first argument.
@Test void testSecondsBetween(){ datetime dt1 = Date:fromTimeString("2013-1-20 08:45:12 GMT"); datetime dt2 = Date:fromTimeString("2013-1-20 08:45:40 GMT"); int diff1 = Date:secondsBetween(dt1, dt2); Assert:isEqual(28, diff1, "Date:secondsBetween returned an incorrect number of seconds"); int diff2 = Date:secondsBetween(dt2, dt1); Assert:isEqual(-28, diff2, "Date:secondsBetween returned an incorrect number of seconds"); }
Date:now()
Returns the current date and time as a datetime value.
- @Test
- void testNow(){
- datetime dt = Date:now();
- Assert:isNotNull(dt, "Date:now() returned null");
- }
Date:today()
Returns the current date as a date value.
- @Test
- void testToday(){
- date d = Date:today();
- Assert:isNotNull(d, "Date:today() returned null");
- }
Integer Built-in Functions
These built-in functions are accessed via the pseudo unit “Integer”
Integer:fromString
Returns an integer representation of the string value expression
- @Test
- void testFromString(){
- string s = "12";
- int value = Integer:fromString(s);
- Assert:isNotNull(value, "Integer:fromString returned null for a valid integer value");
- Assert:isEqual(value, 12, "Integer:fromString returned an incorrect integer value");
- s = "abc";
- value = Integer:fromString(s);
- Assert:isNull(value, "Integer:fromString didn't return null for an invalid integer value");
- }
Decimal Built-in Functions
These built-in functions are accessed via the pseudo unit “Decimal”
Decimal:fromString
Returns an decimal representation of the string value expression
decimal value = Decimal:fromString(s);
Uuid Built-in Functions
These built-in functions are accessed via the pseudo unit “Uuid”
Uuid:fromString
Returns an UUID representation of the string value expression
uuid value = Uuid:fromString(s);
Mez (System) BIFs
These BIFs are accessed via the pseudo unit “Mez”
now
Returns the current time
- datetime t = Mez:now();
today
Returns the today's date
- date d = Mez:today();
alert
Displays an alert dialog in the UI with the given message
- Mez:alert(“translation.key”);
log
Writes a log message with level INFO. The logging entry is saved in the __logging_log__ table of your application instance's schema. It will also be available via the Logging Service.
- Mez:log(“Hello, World”);
warn
Writes a log message with level WARNING
- Mez:warn(“translation.key”);
error
Writes a log message with level SEVERE
- Mez:error(“translation.key”);
userRole
Returns a string representation of the current logged in user role
- Mez:userRole();
Helium BIFs
platform
bool isServerEnvironment() { return Helium:platform() == HELIUM_PLATFORM.Server; } bool isMobileEnvironment() { return Helium:platform() == HELIUM_PLATFORM.Mobile; }
Math BIFs
These BIFs are accessed via the pseudo unit “Math”
pow
Raises the first argument to the power of the second argument
sqrt
Calculates the square root of the argument
random
Generates a random decimal
String Built-in Functions
These built-in functions are accessed via the pseudo unit “String”
String:concat
Returns a string value that is the concatenated result of two or more arguments passed to the function. As of Helium 1.3.1 null values will be treated as empty strings.
- @Test
- void testConcatenate(){
- Assert:isEqual("abcdef", String:concat("abc","def"), "Concatenate returned an incorrect string");
- Assert:isEqual("abcdef", String:concat("abc",null,"def"), "Concatenate returned an incorrect string");
- }
String:split
Returns an array of string values that represent the tokens from the value expression that are separated by the separator expression.
Note that in order to split a special character such as '|', one needs to escape the string with a '\' character. The '\' may need to be escaped as well, so in this case one will end up with String:split(someText, "\\|").
- @Test
- void testSplit(){
- string[] result = String:split("abc def", " ");
- Assert:isEqual(2, result.length(), "Split returned an incorrect number of tokens");
- Assert:isEqual(result.get(0), "abc", "Split returned an incorrect first token");
- Assert:isEqual(result.get(1), "def", "Split returned an incorrect second token");
- }
String:length
Returns the length of the string being passed
- int i = String:length("Hello world");
String:substring
Substring of a string based on index positions
- @Test
- void testSubstring(){
- Assert:isEqual("ello", String:substring("Hello World", 1, 4), "String:substring failed");
- Assert:isNotEqual("ello", String:substring("Hello World", 1, 3), "String:substring failed");
- }
String:lower
Convert string to lowercase
- @Test
- void testLower(){
- Assert:isEqual("hello world", String:lower("Hello World"), "String:lower failed");
- Assert:isNotEqual("Hello world", String:lower("Hello World"), "String:lower failed");
- }
String:upper
Convert string to uppercase
- @Test
- void testUpper() {
- Assert:isEqual("HELLO WORLD", String:upper("hello world"), "String:upper failed");
- Assert:isNotEqual("hello world", String:upper("hello world"), "String:upper failed");
- }
String:startsWith
Check if a particular string starts with a string
- @Test
- void testStartsWith(){
- Assert:isTrue(String:startsWith("Hello World", "Hello"), "String:startsWith failed");
- Assert:isFalse(String:startsWith("Hello World", "ello"), "String:startsWith failed");
- }
String:endsWith
Check if a particular string ends with a string
- @Test
- void testEndsWith(){
- Assert:isTrue(String:endsWith("Hello World", "World"), "String:endsWith failed");
- Assert:isFalse(String:endsWith("Hello World", "Worl"), "String:endsWith failed");
- }
String:indexOf
Returns the index within this string of the first occurrence of the specified substring.
- @Test
- void testIndexOf(){
- Assert:isEqual(0, String:indexOf("Hello World", "Hello"), "String:indexOf failed");
- Assert:isEqual(1, String:indexOf("Hello World", "ello "), "String:indexOf failed");
- Assert:isEqual(2, String:indexOf("Hello World", "llo W"), "String:indexOf failed");
- Assert:isEqual(3, String:indexOf("Hello World", "lo Wo"), "String:indexOf failed");
- }
String:join
Join a collection of strings into one result string with the specified join character
- @Test
- void testJoin(){
- string original = "abc def ghi";
- string separator = " ";
- Assert:isEqual(original, String:join(String:split(original, separator), separator), "String:join failed");
- }
Assert Built-in Functions
These built-in functions can be accessed via the psuedo unit “Assert”
isEqual
Assert that two expressions are equal
- Assert:isEqual(3, MyUnit:getNumber(), "Values are not equal.");
isTrue
Assert that the expression returns true
- Assert:isTrue(MyUnit:isPositive(3), "Function returned false.");
isFalse
Assert that the expression returns false
- Assert:isFalse(true, "Expected false");
isNull
- Person p = Person:new();
- Assert:isNull(p.fname, "Expected value to be null.");
isNotNull
- Person p = Person:new();
- Assert:isNotNull(p.fname, "Expected value to not be null.");
isGreaterOrEqual
- Assert:isGreaterOrEqual(p.children.length(), 0, "Expected a non-negative value.");
isLessOrEqual
- Assert:isGreaterOrEqual(doctor.patients.length(), 100, "Doctors shouldn’t have more than 100 patients assigned.");
isLess
isBoth
- Assert:isBoth(true, isNameSteve(p), "Failed name test.");
isEither
Assert:isEither(p.age > 5, isName(“Steve”, p), “Failed test.”);
- Assert:isEither(p.age > 5, isName("Steve", p),"Failed test.");
isNotEqual
Assert that the two expression do not return the same value
- Assert:isNotEqual(4, 9, "Failed");
Collections
These BIFs can be called on any variable that holds an object/collection instance. On variables that hold a reference to a list
pop
Pops (remove and return) the first element from the collection
- Person [] plist = Person:all();
- Person p = plist.pop();
drop
- Person [] plist = Person:all();
- Person p = plist.drop();
length
Returns the current length (number of elements) in the a list
- int len = plist.length();
first
Returns the first element in the list (without removing it)
- Car c = person.cars.first();
last
Returns the last element in the list (without removing it)
- Car c = person.cars.last();
append
Adds an element to the back of a list
- Car c = Car:new();
- c.make = “BMW”;
- person.cars.append(c);
prepend
Adds an element to the front of the list
- person.children.prepend(Person:new());
get
Returns an element in the specific location in the list
- Person child = p.children.get(2);
add
Adds an element to the specified position in the list
- p.children.add(1, Person:new());
remove
Remove the element at the specified position in the list
- p.children.remove(2);
sortAsc
Sort the list in ascending order
- numbers.sortAsc();
sortDesc
Sort the list in descending order
- numbers.sortDesc();
clear
Remove all elements from the list
- p.cars.clear();
If the variable holds a reference to an object instance
save
Saves the object to the persistence layer if the object is persistent
- Person p = Person:new();
- p.save();
notify
Prompts Helium to send a notification to the user associated with the object if the object declaration was annotated with @Role (this method can be called on a variable holding a reference to collection as well, provided that objects in the collection are instances of @Role objects)
- p.notify(“description.key”, “sms.content.key”,
- “email.subj.key”, “email.content.key”);
- plist.notify(“description.key”, “sms.content.key”,
- “email.subj.key”, “email.content.key”);
select
Returns a subset of the items in the original collection as a new collection.
object Person { string name; } void sampleSubSelect(){ Person[] persons; Person p = Person:new(); p.name = "A"; persons.append(p); p = Person:new(); p.name = "A"; persons.append(p); p = Person:new(); p.name = "A"; persons.append(p); p = Person:new(); p.name = "B"; persons.append(p); Persons[] subset = persons.select(all()); //subset will be a copy of the original collection subset = persons.select(equals(name, "A")); //subset will contain only the first three items in the original collection subset = persons.select(and(equals(name, "A"), equals(name, "B"))); //and on mutually exclusive selectors will return an empty collection }
Persistence BIFs
These BIFs can be used to build up a complex selector for reading sets of data from the persistence layer. These BIFs are accessed via the pseudo unit with the same name as the object that is being queried eg. “Person” or “Car”
Type | Description |
---|---|
all | Simple all |
equals | Equality |
empty | Determine whether a variable is empty |
between | Select a range of data between two values |
lessThanOrEqual | Less or equals check |
lessThan | Check if value is less than the value being compared to |
greaterThan | Check if value is greater than the value being compared to |
attributeIn | Attribute In |
relationshipIn | Relationship In |
contains | Check is collection has element(s) |
beginsWith | Begin with |
endsWith | End with |
notEquals | Not Equals |
notEmpty | Not empty |
notBetween | Not between the data or elements |
notContains | Collection does not contain element(s) |
notBeginWith | Not begin with |
notEndsWith | Check if value does not end with value being compared to |
notAttributeIn | Attribute not in the given domain |
notRelationshipIn | Use to check for relationship |
union | Union |
diff | Use to check for differences |
intersect | Intersect |
and | And (in most cases, use this rather than intersect) |
These BIFs perform operations on single persistent entities. They are accessed via a method call on a variable holding an object instance or on the pseudo unit with the same name as the object that the method is being performed on
new
Creates a new instance of an object. The object doesn’t have to be persistent for this method to be called on it
- Person p = Person:new();
save
If the object instance is a persistent object it saves the object to the persistence layer, (if the developer added objects to the instance’s relationship member collections, they will be persisted aswell)
- Person p = Person:new();
- p.cars.append(Car:new());
- p.cars.append(Car:new());
- p.save();
read
Reads an existing object instance from the persistence layer
- Person p = Person:read(getUserUUID());
delete
Delete the instance
- Person:delete(plist.pop());
user
Get the current user object
- Nurse currentUser = Nurse:user();
invite
Creates an identity for the object if one doesn't already exist and grants the user associated with the identity permission to use the current app with the role associated with the object
@Role("Nurse") persistent object Nurse { string mobileNumber; } void sampleInvite(){ Nurse n = Nurse:new(); n.mobileNumber = "27820010001"; n.invite(n.mobileNumber); }
HEADS UPYou cannot append this directly to a collection, so first assign it to a variable (as in the example above), and then append it.
RemoveRole
Practically acts as an "uninvite".
@Role("Nurse") persistent object Nurse { string mobileNumber; } void exampleToRemoveRole(Nurse nurse){ nurse.removeRole(); }
Trigger Function
Trigger functions are special functions defined within an object declaration, and are executed at specific times during the object’s life cycle.
beforeCreate
Executes before the object is created, there is one implicit variable that developers can use within the function body, and that is the variable “before”
- persistent object Person
- {
- string fname;
- string sname;
- int count;
- beforeCreate
- {
- before.count = 0;
- }
- }
afterCreate
Executes after the object is created, there is one implicit variable that developers can use within the function body, and that is the variable “after”
- persistent object Person
- {
- string fname;
- string sname;
- int count;
- afterCreate
- {
- after.count = 0;
- {
- }
beforeDelete
Executes before the object is deleted, there is one implicit variable that developers can use within the function body, and that is the variable “before”
- persistent object Person
- {
- string fname;
- string sname;
- int count;
- beforeDelete
- {
- before.count = 0;
- }
- }
afterDelete
Executes after the object is deleted, there is one implicit variable that developers can use within the function body, and that is the variable “after”
- persistent object Person
- {
- string fname;
- string sname;
- int count;
- afterDelete
- {
- after.count = 0;
- }
- }
beforeUpdate
Executes before the object is updated, there are two implicit variables that developers can use within the function body, and that is the variables “before” and “after”
- persistent object Person
- {
- string fname;
- string sname;
- int count;
- beforeUpdate
- {
- after.count = before.count + 1;
- }
- }
afterUpdate
Executes after the object is updated, there are two implicit variables that developers can use within the function body, and that is the variables “before” and “after”
- persistent object Person
- {
- string fname;
- string sname;
- int count;
- afterUpdate
- {
- after.count = before.count + 1;
- }
- }
before
A variable that holds a reference of an instance of the object that reflects the state before creation
after
A variable that holds a reference of an instance of the object that reflects the state after creation
Atomic validators
These are the building blocks used for creating complex validators
notnull
Check that the attribute is not null. This validator does not take any arguments.
regex
Checks that the value conforms to a regular expression.
- // Allows upper & lower case and spaces
- regex("^[A-Z a-z]*$");
- // Alphanumeric with spaces
- regex("^[A-Za-z0-9 ]*$");
- // Number starting with 27
- regex("^27[0-9]*$");
- regex("\b[A-Za-z0-9._%-]+@[A-Za-z0-9.-]+[.][A-Za-z]{2,4}\b");
minval
Checks that the value is not less than the supplied minimum value.
- minval(3.145);
maxval
Checks that the values is not greater than the supplied maximum value.
- maxval(6.18);
minlen
Checks that a string value does not have less characters than the supplied minimum value.
- minlen(2);
maxlen
Checks that a string value does not have more characters than the supplied maximum value.
- maxlen(255);
Some examples:
- validator PersonNameValidator {
- notnull(); minlen(2); maxlen(50);
- }
- validator AgeValidator {
- notnull(); minval(0);
- }
- validator CTMetroPhoneNumber {
- notnull(); regex(“021-[7..9]{7}”);
- }
Comments
Two types of comments are supported
Single line comments
- // date tstamp = System.now();
- // decimal rand = System.random();
And multi-line comments:
- /* date tstamp = System.now();
- decimal rand = System.random(); */
Punctuation
Each block contains exactly one token, even if it has two characters, so typing [ ] (with a space between them) it is not the same token as [] (with no nested whitespace).
Type | Description |
---|---|
= | Assignment operator |
. | unknown |
++ | Increment operation |
-- | Decrement operation |
+ | additive |
- | Subractive |
* | Multiplicative |
/ | Multiplicative |
, | Unknown |
@ | Unknown |
[] | Array, block or grouping |
== | Equality |
!= | Equality |
< | Relational |
> | Relational |
<= | Relational |
>= | Relational |
|| | Logical OR |
&& | Logical AND |
; | End of |
: | Scope |
- | Unknown |
{ | Begin Scope |
} | End Scope |
( | Open brackets |
) | Close brackets |
Emails and SMS
There are essentially four ways to send email messages:- Sending to a single object instance that implements the Identity interface. The Mezzanine ID e-mail address associated with the Identity will automatically be used.
- Sending to an arbitrary e-mail address
An example for sending outgoing SMS is also included below.
HEADS UP Note that it is possible to send emails, with optional attachments. Html tags (<table>,<br>,<h2>,<b>,...) can be used to format text.
- @Role(“Doctor”)
- persistent object Doctor{
- }
- persistent object Patient {
- string emailAddress;
- }
- void mailToIdentity(Doctor d){
- Mez:email(d, "email.descriptionKey", "email.subjectKey", "email.bodyKey");
- }
- void mailToAddress(Patient p){
- Mez:email(p.emailAddress, "email.descriptionKey", "email.subjectKey", "email.bodyKey");
- }
- void mailAttachments(Doctor d){
- Mez:email(d, "email.descriptionKey", "email.subjectKey", "/jasper/report1/master.jxrml");
- }
- void sendSms() {
- Client client = Client:new();
- client.mobileNumber = "27730085850";
- Mez:sms(client, "mobileNumber", "exampleKey.smsText");
- }
Additionally if more flexible naming is required for the emailed reports, there exists an additional syntax that specifies a "naming" function. This function is a 0-arg function that returns a string that will be run when the report is generated/emailed. A function can be specified for each report, or the same function can be specified multiple times. A single report "pair" can be specified or multiple.
string myNamingFunctionTwo() { return "Trainee_Workout_Report_No_Two"; } string myNamingFunction() { return "Trainee_Workout_Report_No_One"; } void mailWorkouts() { Trainee uTrainee = Trainee:user(); string traineeFname = uTrainee.fname; string traineeSname = uTrainee.sname; string traineeUUID = uTrainee._id; Mez:emailAttach(uTrainee.email, "email_desc.trainee_workouts_report", "email_subject.trainee_workouts_report", "email_body.trainee_workouts_report", {"trainee-workouts-report/trainee_workouts_report.jrxml", myNamingFunction()}, {"trainee-workouts-report/trainee_workouts_report_two.jrxml", myNamingFunctionTwo()}); }
Specifying Custom E-mail Templates
To change the layout, images or colours of the e-mail for e.g. custom branding purposes, a final enum parameter can be added to the Mez:email or Mez:emailAttach BIFs to specify a custom (html) e-mail template. The enumerated type is EMAIL_TEMPLATES, with the specified enum member the html file name (without the extension), as included by the developer under the web-app/email-templates directory. Images for this template is to be included under web-app/images, and prefixed by "cid" in the html.
Mez:email(emailAddress, "input.email_subject_static", "input.email_subject_static", "input.email_body_static", EMAIL_TEMPLATES.myEmailTemplate);
<img alt="logo" width="190" height="61" src="cid:myCustomLogo.png" />
Please note that it is not mandatory to have the email-template folder. If one is not included the original default email template in service will be used to send messages.
To override this behaviour you can include just one template called "default.html" which will then be used for all the mails instead of the original "default" template. Just including the file will enable the behaviour without you having to explicitly mention the template when the email syntax is used. More customization will require you to indicate the template you want to use via above mentioned enumeration.
└── web-app ├── email-templates │ └── myEmailTemplate.html └── images └── myCustomLogo.png
Please refer to the default e-mail template as an example or starting point for your custom template: email_template.html. Note that it contains ${subject}, ${body} and ${applicationInstanceName} placeholders that are replaced at runtime. In essence these placeholders should always be standard and included in any variation of custom template you introduce.
CSV
Blob variables and attributes can be parsed to collections of object instances.
- Currently the parser only supports plain CSV files. Native Excel formats etc. are not supported.
- CSV files must have a single header line.
- Column names must match the attribute names as defined in the associated object type. For example an object’s first_name attribute will only be populated from the CSV file if the CSV file has a column with a header that is first_name Column headers that don’t match attributes from the associated object type will be ignored.
- Parsing is type-safe. For example an object with a birth_date attribute that is of type date must contain values that can be converted to a date value using the acting user’s preferred Locale and Time Zone.
- Any parsing errors will result in the entire transaction being rolled back. Detailed feedback to users in terms of where the parser failed is a work in progress.
- Complex attributes are supported. For example the CSV file may contain a column header clinic.name This will automatically populage the name attribute of an object that is associated with the primary object through a simple relationship with the name clinic. Simple relationships are one-to-one and many-to-one relationships. Other relationships aren’t supported and will be ignored. The object instance that represents the named relationships will only be created if the value in the column is not null. So if none of the complex attributes access through a specific relationships is set to a non-null value, then that related object won’t be created.
Example code for extracting nurses from CSV in a blob type:
- persistent object Clinic {
- string name;
- }
- persistent object Nurse {
- string first_name;
- date birth_date;
- }
- object UploadedFile {
- blob data;
- }
- Nurse[] extractNurses(UploadedFile f) {
- return Nurse:fromCsv(f.data);
- }
Detailed Examples
Unit
Units are groupings of functions and also variables. They can be thought of as modules or namespaces. A Mezzanine script must have at least one unit. Units are defined as follows
- unit MyUnit;
- string myVar;
- int factorial(int x) {
- if(x == 0 || x == 1) {
- return 1;
- } else if (x > 1) {
- return factorial(x - 1);
- }
- }
This unit has one unit variable named myVar. This variable has unit scope and can be accessed by any function in the unit. The unit also has a function named factorial. A unit must have at least one function. To refer to a unit’s member functions or variables from another unit, you have to use the scope operator
- unit TestUnit;
- void test() {
- MyUnit:myVar = "New string value!";
- int f = MyUnit:factorial(5);
- }
Note: Unit variables cannot be in line initialized when they are declared. The following code will not compile
- unit MyUnit;
- string myVar = "some value"; // this is not a legal statement
Objects
Although the Mezzanine scripting language is not an object oriented language, objects still play a very important role. The most basic objects in Mezzanine scripts can be likened to structs in C. A very basic object will look like this
- object Book {
- string author;
- string title;
- string ISBN;
- boolean inPrint;
- int edition;
- }
You will typically use this object as follows
- // this creates a new instance of book
- Book myBook = Book:new();
- // assign a value to the author member
- myBook.author = "Isaac Asimov";
- myBook.title = "Foundation";
- // pass it to a function
- printBook(myBook);
The real power of objects become apparent when you make them persistent. By defining objects as persistent a lot of behind-the-scenes functionality springs into action on the Mezzanine platform.
For every object that is declared as ‘persistent’ in your app Mezzanine will create a database table to store the entity in and synchronise it across all devices that use your app, without you writing a single line of SQL. The easiest way to declare a persistent object is to use the ‘persistent’ keyword
- persistent object Book {
- string author;
- string title;
- string ISBN;
- boolean inPrint;
- int edition;
- }